From d18894bb4edf4b7176eeeb869611e8c5ed632d83 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 20 Aug 2024 15:39:40 +0200 Subject: [PATCH 1/6] Simplify scroll logic We no longer need to remember the previous value, which cases issues with trackpad scrolling on macOS. --- gaphas/view/gtkview.py | 10 ++++++---- gaphas/view/scrolling.py | 39 ++++++++++----------------------------- 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/gaphas/view/gtkview.py b/gaphas/view/gtkview.py index 504ff309..0bbb2ccc 100644 --- a/gaphas/view/gtkview.py +++ b/gaphas/view/gtkview.py @@ -93,9 +93,12 @@ def __init__(self, model: Model | None = None, selection: Selection | None = Non self.set_focusable(True) self.connect_after("resize", GtkView.on_resize) - def alignment_updated(matrix: Matrix) -> None: + def alignment_updated(x: float | None, y: float | None) -> None: if self._model: - self._matrix *= matrix + if x is not None: + self.matrix.set(x0=-x) + if y is not None: + self.matrix.set(y0=-y) self._scrolling = Scrolling(alignment_updated) @@ -327,10 +330,9 @@ def update_bounding_box(self, items: Collection[Item]) -> None: qtree.add(item=item, bounds=(x, y, w, h)) - @g_async(single=True) def update_scrolling(self) -> None: self._scrolling.update_adjustments( - self.get_width(), self.get_height(), self.bounding_box + self.get_width(), self.get_height(), self._qtree.soft_bounds ) def _debug_draw_bounding_box(self, cr, width, height): diff --git a/gaphas/view/scrolling.py b/gaphas/view/scrolling.py index aba84084..844a1c13 100644 --- a/gaphas/view/scrolling.py +++ b/gaphas/view/scrolling.py @@ -5,13 +5,14 @@ from gi.repository import Gtk from gaphas.geometry import Rectangle -from gaphas.matrix import Matrix class Scrolling: """Contains Gtk.Adjustment and related code.""" - def __init__(self, scrolling_updated: Callable[[Matrix], None]) -> None: + def __init__( + self, scrolling_updated: Callable[[float | None, float | None], None] + ) -> None: self._scrolling_updated = scrolling_updated self.hadjustment: Gtk.Adjustment | None = None self.vadjustment: Gtk.Adjustment | None = None @@ -19,8 +20,6 @@ def __init__(self, scrolling_updated: Callable[[Matrix], None]) -> None: self.vscroll_policy: Gtk.ScrollablePolicy | None = None self._hadjustment_handler_id = 0 self._vadjustment_handler_id = 0 - self._last_hvalue = 0 - self._last_vvalue = 0 def get_property(self, prop): if prop.name == "hadjustment": @@ -41,18 +40,16 @@ def set_property(self, prop, value): self.hadjustment.disconnect(self._hadjustment_handler_id) self.hadjustment = value self._hadjustment_handler_id = self.hadjustment.connect( - "value-changed", self.on_adjustment_changed + "value-changed", self.on_hadjustment_changed ) - self._scrolling_updated(Matrix()) elif prop.name == "vadjustment": if value is not None: if self.vadjustment and self._vadjustment_handler_id: self.vadjustment.disconnect(self._vadjustment_handler_id) self.vadjustment = value self._vadjustment_handler_id = self.vadjustment.connect( - "value-changed", self.on_adjustment_changed + "value-changed", self.on_vadjustment_changed ) - self._scrolling_updated(Matrix()) elif prop.name == "hscroll-policy": self.hscroll_policy = value elif prop.name == "vscroll-policy": @@ -61,8 +58,7 @@ def set_property(self, prop, value): raise AttributeError(f"Unknown property {prop.name}") def update_adjustments(self, width: int, height: int, bounds: Rectangle) -> None: - """Update scroll bar values (adjustments in GTK), and reset the scroll - value to 0. + """Update scroll bar values (adjustments in GTK). The value will change when a scroll bar is moved. """ @@ -72,36 +68,21 @@ def update_adjustments(self, width: int, height: int, bounds: Rectangle) -> None u = c + Rectangle(width=width, height=height) if self.hadjustment: - self.hadjustment.set_value(0) self.hadjustment.set_lower(u.x) self.hadjustment.set_upper(u.x1) self.hadjustment.set_step_increment(width // 10) self.hadjustment.set_page_increment(width) self.hadjustment.set_page_size(width) - self._last_hvalue = 0 if self.vadjustment: - self.vadjustment.set_value(0) self.vadjustment.set_lower(u.y) self.vadjustment.set_upper(u.y1) self.vadjustment.set_step_increment(height // 10) self.vadjustment.set_page_increment(height) self.vadjustment.set_page_size(height) - self._last_vvalue = 0 - def on_adjustment_changed(self, adj): - """Change the transformation matrix of the view to reflect the value of - the x/y adjustment (scrollbar).""" - value = adj.get_value() - if value == 0: - return + def on_hadjustment_changed(self, adj): + self._scrolling_updated(adj.get_value(), None) - m = Matrix() - if adj is self.hadjustment: - m.translate(self._last_hvalue - value, 0) - self._last_hvalue = value - elif adj is self.vadjustment: - m.translate(0, self._last_vvalue - value) - self._last_vvalue = value - - self._scrolling_updated(m) + def on_vadjustment_changed(self, adj): + self._scrolling_updated(None, adj.get_value()) From 7e980bb882d1c1c10789297a5ca7204a7baff25a Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 20 Aug 2024 15:40:47 +0200 Subject: [PATCH 2/6] Replace deprecated GTK .show call --- tests/test_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_view.py b/tests/test_view.py index 1ca668ed..716080ac 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -57,7 +57,7 @@ def test_view_registration(): def test_view_registration_2(view, canvas, window): """Test view registration and destroy when view is destroyed.""" - window.show() + window.present() assert len(canvas._registered_views) == 1 assert view in canvas._registered_views From 0c5009ef44cc45e0d59d9b25ef61173337ab0d88 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 20 Aug 2024 17:13:48 +0200 Subject: [PATCH 3/6] Pan tool updates matrix directly --- gaphas/tool/scroll.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gaphas/tool/scroll.py b/gaphas/tool/scroll.py index 7e2f5dbe..e09335d5 100644 --- a/gaphas/tool/scroll.py +++ b/gaphas/tool/scroll.py @@ -60,16 +60,15 @@ def pan_tool() -> Gtk.GestureDrag: def on_drag_begin(gesture, start_x, start_y, pan_state): view = gesture.get_widget() - pan_state.h = view.hadjustment.get_value() - pan_state.v = view.vadjustment.get_value() + pan_state.h = view.matrix[4] + pan_state.v = view.matrix[5] set_cursor(view, "move") gesture.set_state(Gtk.EventSequenceState.CLAIMED) def on_drag_update(gesture, offset_x, offset_y, pan_state): view = gesture.get_widget() - view.hadjustment.set_value(pan_state.h - offset_x) - view.vadjustment.set_value(pan_state.v - offset_y) + view.matrix.set(x0=pan_state.h + offset_x, y0=pan_state.v + offset_y) set_cursor(view, "move") From 36ef0962f3ed46bad230186f89b3a5078a988962 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 20 Aug 2024 17:26:25 +0200 Subject: [PATCH 4/6] Make scroll bars show the right position on scroll and zoom --- gaphas/view/gtkview.py | 9 ++++++--- gaphas/view/scrolling.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/gaphas/view/gtkview.py b/gaphas/view/gtkview.py index 0bbb2ccc..3606d4f5 100644 --- a/gaphas/view/gtkview.py +++ b/gaphas/view/gtkview.py @@ -331,9 +331,11 @@ def update_bounding_box(self, items: Collection[Item]) -> None: qtree.add(item=item, bounds=(x, y, w, h)) def update_scrolling(self) -> None: - self._scrolling.update_adjustments( - self.get_width(), self.get_height(), self._qtree.soft_bounds - ) + matrix = Matrix(*self._matrix) # type: ignore[misc] + matrix.set(x0=0, y0=0) + bounds = Rectangle(*transform_rectangle(matrix, self._qtree.soft_bounds)) + + self._scrolling.update_adjustments(self.get_width(), self.get_height(), bounds) def _debug_draw_bounding_box(self, cr, width, height): for item in self.get_items_in_rectangle((0, 0, width, height)): @@ -386,6 +388,7 @@ def on_matrix_update(self, matrix, old_matrix_values): # Test if scale or rotation changed if tuple(matrix)[:4] != old_matrix_values[:4]: self.update_scrolling() + self._scrolling.update_position(-matrix[4], -matrix[5]) self.update_back_buffer() def on_resize(self, _width: int, _height: int) -> None: diff --git a/gaphas/view/scrolling.py b/gaphas/view/scrolling.py index 844a1c13..c49b4f64 100644 --- a/gaphas/view/scrolling.py +++ b/gaphas/view/scrolling.py @@ -1,5 +1,6 @@ from __future__ import annotations +from math import isclose from typing import Callable from gi.repository import Gtk @@ -57,6 +58,17 @@ def set_property(self, prop, value): else: raise AttributeError(f"Unknown property {prop.name}") + def update_position(self, x: float, y: float) -> None: + if self.hadjustment and not isclose(self.hadjustment.get_value(), x): + self.hadjustment.handler_block(self._hadjustment_handler_id) + self.hadjustment.set_value(x) + self.hadjustment.handler_unblock(self._hadjustment_handler_id) + + if self.vadjustment and not isclose(self.vadjustment.get_value(), y): + self.vadjustment.handler_block(self._vadjustment_handler_id) + self.vadjustment.set_value(y) + self.vadjustment.handler_unblock(self._vadjustment_handler_id) + def update_adjustments(self, width: int, height: int, bounds: Rectangle) -> None: """Update scroll bar values (adjustments in GTK). From d1e5f943165e85dfbb994088a37cbd5e319e2288 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 20 Aug 2024 17:30:55 +0200 Subject: [PATCH 5/6] Move sign change down into scrolling class Let's use the "matrix" interface. --- gaphas/view/gtkview.py | 6 +++--- gaphas/view/scrolling.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gaphas/view/gtkview.py b/gaphas/view/gtkview.py index 3606d4f5..07765332 100644 --- a/gaphas/view/gtkview.py +++ b/gaphas/view/gtkview.py @@ -96,9 +96,9 @@ def __init__(self, model: Model | None = None, selection: Selection | None = Non def alignment_updated(x: float | None, y: float | None) -> None: if self._model: if x is not None: - self.matrix.set(x0=-x) + self.matrix.set(x0=x) if y is not None: - self.matrix.set(y0=-y) + self.matrix.set(y0=y) self._scrolling = Scrolling(alignment_updated) @@ -388,7 +388,7 @@ def on_matrix_update(self, matrix, old_matrix_values): # Test if scale or rotation changed if tuple(matrix)[:4] != old_matrix_values[:4]: self.update_scrolling() - self._scrolling.update_position(-matrix[4], -matrix[5]) + self._scrolling.update_position(matrix[4], matrix[5]) self.update_back_buffer() def on_resize(self, _width: int, _height: int) -> None: diff --git a/gaphas/view/scrolling.py b/gaphas/view/scrolling.py index c49b4f64..cdd680f1 100644 --- a/gaphas/view/scrolling.py +++ b/gaphas/view/scrolling.py @@ -61,12 +61,12 @@ def set_property(self, prop, value): def update_position(self, x: float, y: float) -> None: if self.hadjustment and not isclose(self.hadjustment.get_value(), x): self.hadjustment.handler_block(self._hadjustment_handler_id) - self.hadjustment.set_value(x) + self.hadjustment.set_value(-x) self.hadjustment.handler_unblock(self._hadjustment_handler_id) if self.vadjustment and not isclose(self.vadjustment.get_value(), y): self.vadjustment.handler_block(self._vadjustment_handler_id) - self.vadjustment.set_value(y) + self.vadjustment.set_value(-y) self.vadjustment.handler_unblock(self._vadjustment_handler_id) def update_adjustments(self, width: int, height: int, bounds: Rectangle) -> None: @@ -94,7 +94,7 @@ def update_adjustments(self, width: int, height: int, bounds: Rectangle) -> None self.vadjustment.set_page_size(height) def on_hadjustment_changed(self, adj): - self._scrolling_updated(adj.get_value(), None) + self._scrolling_updated(-adj.get_value(), None) def on_vadjustment_changed(self, adj): - self._scrolling_updated(None, adj.get_value()) + self._scrolling_updated(None, -adj.get_value()) From f732836d216adda9f214d964ec2a582e4fca7698 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Tue, 20 Aug 2024 17:36:19 +0200 Subject: [PATCH 6/6] Use tolerance when checking if scrolling needs updating on matrix change --- gaphas/view/gtkview.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gaphas/view/gtkview.py b/gaphas/view/gtkview.py index 07765332..ba678434 100644 --- a/gaphas/view/gtkview.py +++ b/gaphas/view/gtkview.py @@ -1,6 +1,7 @@ """This module contains everything to display a model on a screen.""" from __future__ import annotations +from math import isclose from collections.abc import Collection, Iterable import cairo @@ -332,6 +333,7 @@ def update_bounding_box(self, items: Collection[Item]) -> None: def update_scrolling(self) -> None: matrix = Matrix(*self._matrix) # type: ignore[misc] + # When zooming the bounding box to view size, disregard current scroll offsets matrix.set(x0=0, y0=0) bounds = Rectangle(*transform_rectangle(matrix, self._qtree.soft_bounds)) @@ -386,7 +388,7 @@ def on_selection_update(self, item: Item | None) -> None: def on_matrix_update(self, matrix, old_matrix_values): # Test if scale or rotation changed - if tuple(matrix)[:4] != old_matrix_values[:4]: + if not all(map(isclose, matrix, old_matrix_values[:4])): self.update_scrolling() self._scrolling.update_position(matrix[4], matrix[5]) self.update_back_buffer()