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") diff --git a/gaphas/view/gtkview.py b/gaphas/view/gtkview.py index 504ff309..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 @@ -93,9 +94,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,11 +331,13 @@ 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 - ) + 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)) + + 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)): @@ -382,8 +388,9 @@ 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() def on_resize(self, _width: int, _height: int) -> None: diff --git a/gaphas/view/scrolling.py b/gaphas/view/scrolling.py index aba84084..cdd680f1 100644 --- a/gaphas/view/scrolling.py +++ b/gaphas/view/scrolling.py @@ -1,17 +1,19 @@ from __future__ import annotations +from math import isclose from typing import Callable 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 +21,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 +41,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": @@ -60,9 +58,19 @@ 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), 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 +80,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 - - 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_hadjustment_changed(self, adj): + self._scrolling_updated(-adj.get_value(), None) + + def on_vadjustment_changed(self, adj): + self._scrolling_updated(None, -adj.get_value()) 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