Skip to content

Feature: Desktop Mouse Support (Cursor styling, Scroll Panning & Smooth Zoom-to-Cursor)#571

Open
quaaantumdev wants to merge 1 commit intobluefireteam:mainfrom
quaaantumdev:feat-mouse-support
Open

Feature: Desktop Mouse Support (Cursor styling, Scroll Panning & Smooth Zoom-to-Cursor)#571
quaaantumdev wants to merge 1 commit intobluefireteam:mainfrom
quaaantumdev:feat-mouse-support

Conversation

@quaaantumdev
Copy link
Copy Markdown

@quaaantumdev quaaantumdev commented Dec 29, 2025

This PR significantly improves the user experience for Web and Desktop targets by implementing native-feeling mouse interactions. It introduces proper scroll-to-pan, Ctrl+scroll-to-zoom, and adaptive cursor styling, powered by a new internal animation engine that ensures butter-smooth transitions.

Key Features

  1. Native Desktop Interaction (PhotoViewScrollHandler)

    • Vertical Pan: Standard Mouse Wheel scrolls the image vertically.
    • Horizontal Pan: Shift + Mouse Wheel scrolls horizontally.
    • Zoom-to-Cursor: Ctrl + Mouse Wheel (or Trackpad Pinch) zooms in/out centered on the mouse cursor.
    • Unified Input: Seamlessly handles inputs from Mouse Wheels, Trackpads (continuous gestures), and Browser scale events.
  2. Adaptive Cursor Styling (PhotoViewMouseRegion)

    • Visual Feedback: Displays SystemMouseCursors.grab when hovering over the image and grabbing when dragging.
    • Smart Fallback: Reverts to the default cursor when hovering the empty background (outside the image bounds).
    • Robust Hit-Testing: Uses an algebraic "Inverse Transform" to correctly detect the image pixels under the mouse, even when the image is rotated, scaled, or offset.
  3. Smooth Additive Animations (PhotoViewAnimationEngine)

    • Butter-Smooth Feel: Unlike standard tweens that restart on every event, this implementation uses physics-based decay. Rapid inputs (like spinning a scroll wheel) accumulate momentum rather than stuttering.
    • Stable Zooming: Uses vector math to ensure the pixel under the mouse cursor remains perfectly stationary during the zoom, regardless of current movement or rotation.

Implementation Note

We aimed to keep the refactoring footprint minimal to preserve the stability of the existing code. However, the architectural changes included here—specifically extracting the Animation Engine—were deemed necessary because they add major value:

  1. Decoupling: Moving complex physics/math out of the State class makes the core logic significantly easier to read and maintain.
  2. Robustness: It allows complex, additive mouse animations to run side-by-side with standard touch gestures without "fighting" for control or dirtying the widget state.

Technical Implementation Details

  • Architecture Refactor (Engine/Delegate Pattern):

    • PhotoViewAnimationEngine: Extracted all animation logic (Fling, Rebound, Double-Tap) out of PhotoViewCore into a dedicated class. This separates "Physics & Math" from "Rendering & Event Listening."
    • PhotoViewControllerDelegate: Updated the controller architecture to allow the Core to receive imperative animation commands directly from the controller.
  • Refactor: PhotoViewCore Build Logic:

    • Fixed Critical Async Gap: Updated the StreamBuilder to read controller.value directly instead of relying on the potentially delayed snapshot.data. This prevents one-frame lag artifacts when driving the view via the animation engine which is very noticeable when zooming with Ctrl+scroll-to-zoom, making the rendered view jumping back and forth unexpectedly.
    • Flattened Widget Tree: Refactored the build method to chain widget wrappers sequentially, improving readability with the new additional widgets.
  • File Reorganization (Diff Notice):

    • Split photo_view_controller.dart: To keep the files manageable, the abstract base classes (PhotoViewControllerBase, PhotoViewControllerValue) were moved to a new file photo_view_controller_base.dart.
    • Note to Reviewers: The diff for photo_view_controller.dart will show a large deletion, but this code was simply moved to the base file. The implementation logic remains in photo_view_controller.dart, there is just additional code for the attach/detach procedure and the animate commnads.

Usage & Public API

This functionality is integrated directly into PhotoViewCore and works out of the box.

Additionally, the PhotoViewController now exposes new imperative methods (animateScaleBy, animatePositionBy).

Why?
Previous versions required setting controller.scale = value directly to zoom programmatically, which resulted in instant, jarring jumps. Exposing these animation methods allows developers to easily implement external UI controls—such as Zoom In/Out buttons or directional pads—that utilize the same smooth, physics-based engine as the internal gestures.

// Smoothly zoom in by 50% (e.g. via a Toolbar "+" button)
controller.animateScaleBy(factor: 1.5);

// Smoothly pan by 100 pixels
controller.animatePositionBy(delta: Offset(100, 0));

Checklist

  • Scroll wheel pans vertically (and horizontally with Shift).
  • Ctrl+Scroll zooms smoothly towards the mouse cursor.
  • Cursor changes based on interaction (Hover/Grab/Background).
  • Animations use additive physics (no jitter on rapid input).
  • Standard animations (Double Tap, Fling) preserved and isolated.
  • Hit-testing works correctly when image is rotated or zoomed.

@quaaantumdev
Copy link
Copy Markdown
Author

I change the implementation behind this PR to a much nicer implementation, with physics based animations which feels really good now. So that's the reason for the forced-push, it's not an incremental updated but a larger restructuring of what i had before.

Hope the work is appreciated, would be great to see this in the official package :)
Thanks for your efforts on building that package in the first place👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant