From 2af9a7f19cbe28c60cdc76322f24cad62a6dd541 Mon Sep 17 00:00:00 2001 From: salt-die <53280662+salt-die@users.noreply.github.com> Date: Fri, 23 Feb 2024 20:43:54 -0600 Subject: [PATCH] `scroll_x()` methods of ScrollView made public. Added `scroll_to_rect()` method to ScrollView. Horizontal cursor movement on text input resets `_last_x`. --- src/batgrl/gadgets/console.py | 28 ++++----- src/batgrl/gadgets/file_chooser.py | 6 +- src/batgrl/gadgets/scroll_view.py | 97 ++++++++++++++++++++++-------- src/batgrl/gadgets/text_pad.py | 22 +++---- 4 files changed, 93 insertions(+), 60 deletions(-) diff --git a/src/batgrl/gadgets/console.py b/src/batgrl/gadgets/console.py index a917e7ac..3abaadb4 100644 --- a/src/batgrl/gadgets/console.py +++ b/src/batgrl/gadgets/console.py @@ -147,16 +147,7 @@ def cursor(self, cursor: int): if self.parent is None: return - console = self.console - rel_x = console._container.x + console._prompt.width + cursor - port_width = console._scroll_view.port_width - - if cursor == 0: - console._scroll_view.horizontal_proportion = 0 - elif rel_x < 0: - console._scroll_view._scroll_right(rel_x) - elif rel_x >= port_width: - console._scroll_view._scroll_right(rel_x - port_width + 1) + self.console._scroll_view.scroll_to_rect((0, cursor + self.x), (1, 1)) if self.is_selecting: self._selection_end = self.cursor @@ -193,6 +184,9 @@ def on_key(self, key_event: KeyEvent) -> bool | None: if key_event == KeyEvent(Key.Tab, Mods.NO_MODS): self._tab() return True + if key_event == KeyEvent(Key.Left, Mods.NO_MODS) and self.cursor == 0: + self.console._scroll_view.horizontal_proportion = 0 + return True return super().on_key(key_event) def on_paste(self, paste_event: PasteEvent) -> bool | None: @@ -471,12 +465,16 @@ def fix_input_pos(): self._input.left = self._prompt.right def update_bars(): - self._scroll_view.show_vertical_bar = ( - self._container.height > self._scroll_view.port_height - ) - self._scroll_view.show_horizontal_bar = ( - self._container.width > self._scroll_view.port_width + show_vertical_bar = self._container.height > self._scroll_view.port_height + show_horizontal_bar = self._container.width > self._scroll_view.port_width + update_cursor = ( + show_vertical_bar != self._scroll_view.show_vertical_bar + or show_horizontal_bar != self._scroll_view.show_horizontal_bar ) + self._scroll_view.show_vertical_bar = show_vertical_bar + self._scroll_view.show_horizontal_bar = show_horizontal_bar + if update_cursor: + self._input.cursor = self._input.cursor self._prompt.bind("size", fix_input_pos) self._prompt.bind("pos", fix_input_pos) diff --git a/src/batgrl/gadgets/file_chooser.py b/src/batgrl/gadgets/file_chooser.py index 3a5e2e04..401ae4c9 100644 --- a/src/batgrl/gadgets/file_chooser.py +++ b/src/batgrl/gadgets/file_chooser.py @@ -183,11 +183,7 @@ def on_key(self, key_event): return super().on_key(key_event) if self.selected_node is not None: - top = self.selected_node.top + self.top - if top < 0: - self.parent._scroll_up(-top) - elif top >= self.parent.height - 1: - self.parent._scroll_down(self.parent.height - top) + self.parent.scroll_to_rect(self.selected_node.pos) return True diff --git a/src/batgrl/gadgets/scroll_view.py b/src/batgrl/gadgets/scroll_view.py index 0a4d6e42..d8313121 100644 --- a/src/batgrl/gadgets/scroll_view.py +++ b/src/batgrl/gadgets/scroll_view.py @@ -336,6 +336,16 @@ class ScrollView(Themable, Grabbable, Gadget): Methods ------- + scroll_left(n=1) + Scroll the view left `n` characters. + scroll_right(n=1) + Scroll the view right `n` characters. + scroll_up(n=1) + Scroll the view up `n` lines. + scroll_down(n=1) + Scroll the view down `n` lines. + scroll_to_rect(pos, size=(1, 1)) + Scroll the view so that a given rect is visible. update_theme() Paint the gadget with current theme. grab(mouse_event) @@ -634,13 +644,13 @@ def on_key(self, key_event: KeyEvent) -> bool | None: match key_event.key: case "up": - self._scroll_up() + self.scroll_up() case "down": - self._scroll_down() + self.scroll_down() case "left": - self._scroll_left() + self.scroll_left() case "right": - self._scroll_right() + self.scroll_right() case _: return super().on_key(key_event) @@ -648,10 +658,24 @@ def on_key(self, key_event: KeyEvent) -> bool | None: def grab_update(self, mouse_event: MouseEvent): """Scroll on grab update.""" - self._scroll_up(self.mouse_dy) - self._scroll_left(self.mouse_dx) + self.scroll_up(self.mouse_dy) + self.scroll_left(self.mouse_dx) - def _scroll_left(self, n=1): + def on_mouse(self, mouse_event: MouseEvent) -> bool | None: + """Scroll on mouse wheel.""" + if self.scrollwheel_enabled and self.collides_point(mouse_event.position): + match mouse_event.event_type: + case MouseEventType.SCROLL_UP: + self.scroll_up() + return True + case MouseEventType.SCROLL_DOWN: + self.scroll_down() + return True + + return super().on_mouse(mouse_event) + + def scroll_left(self, n=1): + """Scroll the view left `n` characters.""" if self._view is not None: if self.total_horizontal_distance == 0: self.horizontal_proportion = 0 @@ -660,10 +684,12 @@ def _scroll_left(self, n=1): (-self.view.left - n) / self.total_horizontal_distance, 0, 1 ) - def _scroll_right(self, n=1): - self._scroll_left(-n) + def scroll_right(self, n=1): + """Scroll the view right `n` characters.""" + self.scroll_left(-n) - def _scroll_up(self, n=1): + def scroll_up(self, n=1): + """Scroll the view up `n` lines.""" if self._view is not None: if self.total_vertical_distance == 0: self.vertical_proportion = 0 @@ -672,18 +698,39 @@ def _scroll_up(self, n=1): (-self.view.top - n) / self.total_vertical_distance, 0, 1 ) - def _scroll_down(self, n=1): - self._scroll_up(-n) - - def on_mouse(self, mouse_event: MouseEvent) -> bool | None: - """Scroll on mouse wheel.""" - if self.scrollwheel_enabled and self.collides_point(mouse_event.position): - match mouse_event.event_type: - case MouseEventType.SCROLL_UP: - self._scroll_up() - return True - case MouseEventType.SCROLL_DOWN: - self._scroll_down() - return True - - return super().on_mouse(mouse_event) + def scroll_down(self, n=1): + """Scroll the view down `n` lines.""" + self.scroll_up(-n) + + def scroll_to_rect(self, pos: Point, size: Size = Size(1, 1)): + """ + Scroll the view so that a given rect is visible. + + The rect is assumed to be within the view's bounding box. + + Parameters + ---------- + pos : Point + Position of rect. + + size : Size, default: Size(1, 1) + Size of rect. + """ + if self.view is None: + return + + y, x = pos + h, w = size + h = clamp(h, 1, None) + w = clamp(w, 1, None) + gy, gx = self.view.pos + ay, ax = gy + y, gx + x + if ay < 0: + self.scroll_up(-ay) + elif ay + h >= self.port_height: + self.scroll_down(ay + h - self.port_height) + + if ax < 0: + self.scroll_left(-ax) + elif ax + w >= self.port_width: + self.scroll_right(ax + w - self.port_width) diff --git a/src/batgrl/gadgets/text_pad.py b/src/batgrl/gadgets/text_pad.py index 8f761476..07c05453 100644 --- a/src/batgrl/gadgets/text_pad.py +++ b/src/batgrl/gadgets/text_pad.py @@ -361,23 +361,9 @@ def cursor(self, cursor: Point): """After setting cursor position, move pad so that cursor is visible.""" y, x = cursor self._cursor.pos = Point(y, x) - - max_y = self._scroll_view.port_height - 1 - if (rel_y := y + self._pad.y) > max_y: - self._scroll_view._scroll_down(rel_y - max_y) - elif rel_y < 0: - self._scroll_view._scroll_up(-rel_y) - - max_x = self._scroll_view.port_width - 1 - rel_x = x + self._pad.x - if rel_x > max_x: - self._scroll_view._scroll_right(rel_x - max_x) - elif rel_x < 0: - self._scroll_view._scroll_right(rel_x) - + self._scroll_view.scroll_to_rect(cursor) if self.is_selecting: self._selection_end = self.cursor - self._highlight_selection() def _highlight_selection(self): @@ -624,6 +610,7 @@ def move_cursor_down(self, n: int = 1): def move_word_left(self): """Move cursor a word left.""" + self._last_x = None last_x = self.cursor.x first_char_found = False while True: @@ -646,6 +633,7 @@ def move_word_left(self): def move_word_right(self): """Move cursor a word right.""" + self._last_x = None last_x = self.cursor.x first_char_found = False while True: @@ -798,10 +786,12 @@ def _pgdn(self): def _home(self): self.unselect() + self._last_x = None self.cursor = self.cursor.y, 0 def _end(self): self.unselect() + self._last_x = None y = self.cursor.y self.cursor = y, self._line_lengths[y] @@ -839,10 +829,12 @@ def _shift_pgdn(self): def _shift_home(self): self.select() + self._last_x = None self.cursor = self.cursor.y, 0 def _shift_end(self): self.select() + self._last_x = None y = self.cursor.y self.cursor = y, self._line_lengths[y]