feat: configurable mouse gestures with dedicated settings page - #390
Draft
knipknap wants to merge 3 commits into
Draft
feat: configurable mouse gestures with dedicated settings page#390knipknap wants to merge 3 commits into
knipknap wants to merge 3 commits into
Conversation
Navigation and menu gestures on the 2D canvas, 3D canvas, and in the sketch editor can now be rebound in a dedicated Mouse Gestures page in the settings dialog. The new rayforge.ui_gtk.gestures package provides: - GestureSpec/GestureSlot/GestureContext: a declarative model of rebindable gestures (button + modifier combinations for click, drag, and scroll input). - gesture_registry: an addon-capable registry (wired into REGISTRY_TABLE for automatic cleanup) that resolves the effective binding per slot from config with default fallback. - GestureRouter: attaches generic any-button drag/click and scroll controllers to a widget and dispatches input to the slot handlers configured for its context, claiming matched sequences and denying everything else so existing canvas behavior is unaffected. WorldSurface and CameraController route their pan/zoom/orbit/Z-rotate/ context-menu input through the router instead of hard-coded button-bound controllers. The sketcher addon registers its own "sketcher" context with a rebindable tool-menu gesture. Bindings persist as gesture_bindings in config.yaml. This also gives users a workaround for hardware/driver setups where specific buttons are not delivered (#241). Documented in website/docs/ui/settings.md.
- Use a module-level singleton instead of a function call in the GestureSpec dataclass default (RUF009). - Import Callable from collections.abc (UP035). - Replace function-call argument defaults in test fakes with a module-level constant (B008) and reuse the existing fake config instead of a mutable class attribute (RUF012). - Assert button presence before use in the preferences page test.
- Narrow the button type in GestureSpec.display_label before dict lookup. - Drop the gesture/controller parameter annotations on the router's private GTK signal handlers, matching the untyped pattern already used by CameraController's handlers and keeping the test fakes assignable. - Handle the Optional str returned by get_label() in the preferences page tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #241 (provides the workaround; see note below)
Summary
Mouse navigation gestures are now configurable in a dedicated Mouse Gestures page in the settings dialog. The architecture is context-based so it covers the 2D canvas, the 3D canvas, the sketcher addon, and gestures contributed by future addons.
Architecture
New package
rayforge/ui_gtk/gestures/:GestureSpec/GestureSlot/GestureContext(model.py) — a declarative model of rebindable gestures. A spec identifies a physical input (click/drag/scroll+ button + modifier mask, serialized e.g. as"drag+shift+middle"); a slot is a rebindable meaning ("Pan the view") with a default binding; a context groups slots per interaction area (canvas2d,canvas3d,sketcher).gesture_registry(registry.py) — an addon-capable registry following the existingSettingsPageRegistry/ContextMenuExtensionRegistrypattern, registered inREGISTRY_TABLEso addon contributions are cleaned up automatically on unload. Resolves the effective binding per slot fromConfig.gesture_bindingswith default fallback, and detects binding conflicts within a context.GestureRouter(router.py) — attaches one any-button drag gesture, one any-button click gesture, and a scroll controller to a widget. On each input it resolves the actual (button, modifiers) against the context's bindings: a match claims the sequence and dispatches to the slot's handlers; no match denies the sequence, leaving existing canvas behavior (element selection, tools, etc.) untouched.Wiring
WorldSurfaceroutes pan/zoom/context-menu/reset-view through a router keyed by acontext_idclass attribute;WorkSurfaceusescanvas2d,SketchCanvasoverrides it tosketcher.CameraControllerroutes orbit/pan/Z-rotate/zoom through the router. The previous Shift-modifier distinction became two explicit slots (orbit= middle-drag,pan= Shift+middle-drag), each independently rebindable.sketchercontext (pan, zoom, tool menu, reset view) via its existing registration path — no new hookspec needed.Settings page
GesturePreferencesPage(built intoSettingsWindow, position after General) renders one group per registered context and rebuilds live when the registry changes, so addon-contributed gesture contexts appear/disappear automatically. Each slot row opens a capture popover ("press a mouse button now, or scroll"), with conflict rejection, per-slot unassign/reset-to-default, and row labels that update live.Persistence
Config.gesture_bindings(context_id -> slot_id -> spec string | Nonefor unassigned), auto-saved through the existing config mechanism; malformed or unknown entries are dropped on load and unknown slots are legal (e.g. written by a disabled addon).Notes
website/docs/ui/settings.md.Test plan
tests/ui_gtk/gestures/,tests/core/test_config.py).CameraControllertests to the new per-slot handler API (33/33 pass).tests(4867 passed),tests/ui_gtk -m uiunder xvfb (627 passed), builtin addon tests (same result asmain).ruff check,flake8clean; translation compile step verified.