Skip to content

Commit

Permalink
Merge pull request #832 from gaphor/macos-scroll-click
Browse files Browse the repository at this point in the history
Improve scroll and zoom behavior
  • Loading branch information
amolenaar authored Aug 20, 2024
2 parents 7b27506 + f732836 commit d283f7a
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 43 deletions.
7 changes: 3 additions & 4 deletions gaphas/tool/scroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")


Expand Down
21 changes: 14 additions & 7 deletions gaphas/view/gtkview.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)):
Expand Down Expand Up @@ -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:
Expand Down
55 changes: 24 additions & 31 deletions gaphas/view/scrolling.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
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
self.hscroll_policy: Gtk.ScrollablePolicy | 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":
Expand All @@ -41,28 +41,36 @@ 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":
self.vscroll_policy = 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.
"""
Expand All @@ -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())
2 changes: 1 addition & 1 deletion tests/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit d283f7a

Please sign in to comment.