Skip to content

Feature: circular magnifying glass - #572

Merged
luisangelsm merged 7 commits into
YACReader:developfrom
starlit-void:feature/circular-magnifying-glass
Jul 29, 2026
Merged

Feature: circular magnifying glass#572
luisangelsm merged 7 commits into
YACReader:developfrom
starlit-void:feature/circular-magnifying-glass

Conversation

@starlit-void

Copy link
Copy Markdown
Contributor

Add an option for a circular magnifying glass.

  1. the magnifying glass size reads from the max dimension of the rectangle size so there's no extra data being stored. The rectangle behavior should be unchanged.
  2. add an optional easing curve to the magnifier. this allows the edges and corners of the page to be examined closely without pushing the mouse cursor into the edge of the window.
  3. change the mouse scroll wheel to be smoother and require more intention before changing the size of the magnifying glass. (this is not presented as an option, it is a new default behavior.)
  4. New: clamp the circular size to the view size so it doesn't get too big.

CHANGELOG updated.

Please let me know if you want any other changes, additions, or removals.
Thank you!

starlit-void and others added 7 commits July 29, 2026 14:14
Introduce an optional circular loupe for the magnifying glass, toggled
from the Options window (General tab). The circle's diameter is the wider
of the two configured rectangle dimensions, derived at display time only:
the rectangle size setting (MAG_GLASS_SIZE) is never overwritten, so
switching modes restores the exact saved size.

MagnifyingGlass now separates its logical rectangle (the source of truth
for size gestures and the persisted setting) from its display geometry,
which grows to a max(w, h) square while circular. The shape is realised
with a widget mask for corner transparency plus a custom paintEvent for
antialiased interior scaling.

A secondary bezel-ring option (default on, enabled only when circular is
active) draws an antialiased filled annulus inset just inside the mask so
its outer edge forms a smooth silhouette that blends into the page,
hiding the aliased mask edge.
The loupe's sampled content is pushed outward toward the viewport edges so
edge content is reachable without moving the cursor all the way to the
edge, while tracking the cursor 1:1 in the middle.

- Viewer::easeViewerPos normalizes the cursor about the viewport center
  (the region the page is actually drawn in — the top-level window would
  fold in the toolbar/chrome and ease too hard), runs it through a
  smoothstep ramp, and scales it by the loupe half-extent. The half-size
  cap keeps the cursor's point inside the loupe view and ties the strength
  to loupe size: a minimum loupe is nearly linear, a large one eases hard.
  Rectangular loupes cap per axis; circular loupes cap the displacement
  vector radially.
- Two tuning knobs: edgeReach saturates the ramp before the cursor reaches
  the border, and edgeStrength scales the peak push below the half-extent.
- Per-axis letterbox gate: easing only helps where there is off-page
  content to bring toward the cursor, so it is skipped on an axis once the
  page is letterboxed by more than minLetterboxFraction (10%) of its own
  size there. Overflow, an exact fit, or a thin margin still ease, so the
  effect does not vanish the instant a page is a hair smaller than the
  viewport. In continuous view the horizontal extent is the page under the
  cursor, so the gate can vary per row.
- Only the content is eased; the loupe widget follows the cursor, so the
  zoomed image swims a little toward the edge inside the loupe.
- MouseHandler's mouse-move path routes through updateImage so both update
  paths share the eased positioning.
- New MAG_GLASS_EDGE_EASE config (default on) with an Options toggle,
  applied live via Viewer::updateConfig().
… delta)

Resizing the magnifying glass by scrolling used to step on the mere sign of
each wheel event, so a trackpad's many tiny high-resolution events each fired
a full step and the faintest two-finger brush resized the loupe.

Accumulate the active gesture's signed angleDelta and only take a step once
the running total crosses a 120-unit threshold (looping so a fast multi-notch
event still steps several times). A real mouse wheel delivers 120 per notch in
one event, so it still steps once per notch; the 120 threshold is itself the
trackpad/mouse discriminator, so no device-type branching is needed. The
accumulator resets when the gesture changes (different modifier) or after
~400ms idle so a stale partial gesture can't leak forward. Applies to the
size, height (Ctrl), width (Alt), and zoom (Shift) branches.
The circle's diameter is the wider of the two logical dimensions, so squaring
up a wide, short rectangle could exceed the parent on the other axis even
though the rectangle itself fit: a logical 800x175 became an 800x800 widget,
taller than the viewport it magnifies. The grow steps only ever clamped the
logical rectangle, so nothing caught this.

Clamp the circle's side to the same fraction of the parent that the grow steps
enforce, taken on whichever axis is tighter. The logical size is untouched, so
the saved setting and the rectangular mode are unaffected.
It introduced a regression that causes the magnifying glass to start tiny because the size of the parent is not know when the magnifying glass is setup, and it would require to  update the clamping for every resize of the parent widget. Clamping ads complexity and some UX inconsistencies that are not really necessary IMO.
@luisangelsm
luisangelsm merged commit 53d4e00 into YACReader:develop Jul 29, 2026
12 checks passed
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.

2 participants