Repository: psychopy/psychopy

Issue:
Textbox caret isn't flipped with text



Discussion:

Resolving patch:
diff --git a/psychopy/visual/textbox2/textbox2.py b/psychopy/visual/textbox2/textbox2.py
--- a/psychopy/visual/textbox2/textbox2.py
+++ b/psychopy/visual/textbox2/textbox2.py
@@ -952,7 +952,10 @@ def _layout(self):
         # Convert the vertices to be relative to content box and set
         self.vertices = vertices / self.contentBox._size.pix + (-0.5, 0.5)
         if len(_lineBottoms):
-            self._lineBottoms = max(self.contentBox._vertices.pix[:, 1]) + np.array(_lineBottoms)
+            if self.flipVert:
+                self._lineBottoms = min(self.contentBox._vertices.pix[:, 1]) - np.array(_lineBottoms)
+            else:
+                self._lineBottoms = max(self.contentBox._vertices.pix[:, 1]) + np.array(_lineBottoms)
             self._lineWidths = min(self.contentBox._vertices.pix[:, 0]) + np.array(_lineWidths)
         else:
             self._lineBottoms = np.array(_lineBottoms)
@@ -1382,7 +1385,10 @@ def vertices(self):
             # Get top of this line
             bottom = textbox._lineBottoms[self.row]
         # Top will always be line bottom + font height
-        top = bottom + self.textbox.glFont.size
+        if self.textbox.flipVert:
+            top = bottom - self.textbox.glFont.size
+        else:
+            top = bottom + self.textbox.glFont.size
         return np.array([
             [x, bottom],
             [x, top]


Test patch:
diff --git a/psychopy/tests/test_visual/test_textbox.py b/psychopy/tests/test_visual/test_textbox.py
--- a/psychopy/tests/test_visual/test_textbox.py
+++ b/psychopy/tests/test_visual/test_textbox.py
@@ -1,5 +1,7 @@
 from pathlib import Path
 
+import numpy as np
+
 from psychopy import layout
 from psychopy.alerts._errorHandler import _BaseErrorHandler
 from psychopy.tests.test_visual.test_basevisual import _TestColorMixin, _TestUnitsMixin
@@ -152,6 +154,61 @@ def test_colors(self):
                 # self.win.getMovieFrame(buffer='back').save(Path(utils.TESTS_DATA_PATH) / filename)
                 utils.compareScreenshot(Path(utils.TESTS_DATA_PATH) / filename, self.win, crit=20)
 
+    def test_caret_position(self):
+
+        # Identify key indices to test
+        indices = (
+            4,  # first line
+            30,  # wrapping line
+            64  # newline line
+        )
+        # One array for anchor and alignment as they use the same allowed vals
+        anchalign = (
+            'center',
+            'top-center',
+            'bottom-center',
+            'center-left',
+            'center-right',
+            'top-left',
+            'top-right',
+            'bottom-left',
+            'bottom-right',
+        )
+        # Try with all anchors
+        for anchor in anchalign:
+            # Try with all alignments
+            for align in anchalign:
+                # Try flipped horiz and unflipped
+                for flipHoriz in (True, False):
+                    # Try flipped vert and unflipped
+                    for flipVert in (True, False):
+                        # Create a textbox with multiple lines of differing length from both wrapping and a newline char
+                        textbox = TextBox2(
+                            self.win,
+                            text="A PsychoPy zealot knows a smidge of wx, but JavaScript is the \nquestion.",
+                            size=(128, 64), pos=(0, 0), units='pix',
+                            anchor=anchor, alignment=align, flipHoriz=flipHoriz, flipVert=flipVert
+                        )
+                        # Check that caret position at key indices matches character positions
+                        for i in indices:
+                            # Set caret index
+                            textbox.caret.index = i
+                            # Draw textbox to refresh verts
+                            textbox.draw()
+                            textbox.caret.draw()
+                            # Compare bottom vert of caret to bottom right vert of character (within 1 px)
+                            caretVerts = textbox.caret.vertices
+                            charVerts = textbox._vertices.pix[range((i-1) * 4, (i-1) * 4 + 4)]
+                            assert all(np.isclose(caretVerts[0], charVerts[2], 1)), (
+                                f"Textbox caret at index {i} didn't align with bottom right corner of "
+                                f"matching char when:\n"
+                                f"anchor={anchor}, alignment={align}, flipHoriz={flipHoriz}, flipVert={flipVert}.\n"
+                                f"Caret vertices were:\n"
+                                f"{caretVerts}\n"
+                                f"Char verts were \n"
+                                f"{charVerts}\n"
+                            )
+
     def test_typing(self):
         """Check that continuous typing doesn't break anything"""
         # Make sure the textbox has the right colours