fix(view): hold ZOOMFACTOR to its own range, finish the exponential zoom

The exponential camera step is the right call. `0.9 ^ delta` is strictly
positive for every real delta, in and out cancel exactly where the
subtraction lost a percent of the view on every round trip, and the file's
own header has promised an "exponential feel" since it was written. Three
surfaces were left behind it, and the wider range exposed a fourth thing.

The range of the system variable. 3 to 100 is the range ZOOMFACTOR has, and
the User Preferences page was built against it; `SETVAR ZOOMFACTOR` accepts
that and nothing else again. A faster wheel is the Options window's business
instead: the slider covers the variable's own range, the field beside it
reaches 500, and a read-out says what a notch actually does ("10.0% a
notch") so the number being set is readable rather than implied. A value
above 100 therefore lives only in this application's own configuration file,
where it is nobody else's to read, and a bare `SETVAR ZOOMFACTOR` still
reports it. The bounds and the clamp live in one place now
(`settings::clamp_zoom_factor`): four literal copies of `3, 100` are what
let a surface be missed in the first place.

A floating viewport in MSPACE. It zooms its own view through
`zoom_active_viewport`, which kept subtracting: the factor reached zero at
6.67 steps, so from ZOOMFACTOR 360 up every notch inside a viewport bottomed
out on the clamp — a tenfold jump a notch, and the top third of the slider
doing nothing at all. Exponential there too, from a base of 0.85 so the
default notch still moves a viewport the 15% it always has. Each surface
keeps its own long-standing feel rather than inheriting the other's.

The trackpad pinch. `pinch_zoom_steps` was calibrated against the
subtraction (`1 - step / 10 = 1 / (1 + m)`) so that a pinch of m leaves the
view exactly 1 + m times closer — the property docs/trackpad.md documents
and the reason the gesture tracks the fingers at all. Inverting the zoom law
by hand is what let the two drift apart, so the step is asked of the camera
now (`Camera::zoom_steps_for_ratio`), which cannot drift by construction.
`pinch_is_one_to_one` recomputed the old formula rather than the camera's,
so it kept passing through all of this; it zooms a real camera and measures
the distance it lands at instead.

Also: `zoom` had a floor and no ceiling, and zooming out multiplies. At
ZOOMFACTOR 500 a notch out is x2.4, so about ninety notches — a couple of
seconds of scrolling — ran the distance past what an f32 far plane
(`distance * 1000`) can hold, and the viewport went black on an infinite
projection. A degenerate pinch magnification of -1 or beyond reached the
same place through the other side. Both are bounded now.

Also: two doc comments quoted the linear law (`zoom_view_at_cursor` and the
one on `pinch_zoom_steps`), and docs/trackpad.md stated the pinch identity
and claimed the tests covered it against the camera. All updated, with what
a notch does and where each surface's range comes from written down.

Tests, each confirmed against the behaviour it describes: the zoom law at
every factor the application can hold (positive, monotonic in the setting,
in and out cancelling), the ceiling under a long scroll out in both
projections, the published inverse and what it does with a nonsense ratio,
the viewport factor and a real viewport zoomed three notches at the top of
the range, the SETVAR range including a value the Options field set, the
Options field (above 100, above the ceiling, cleared, not a number), its
agreement with the slider, Close putting both the value and the field back,
and the read-out's percentages. 1882 lib tests pass.

The two new Options strings are not in the locale catalog yet, so they
render in English until it is regenerated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-10-02 10:51:26 +03:00
1 parent 558b5f6526
commit 9fe2e4fa58
12 files changed
+549 -32

No files matched your search

+21 -3
View File
@@ -17,6 +17,20 @@ together with the middle button orbits, exactly as before.
| Middle-button drag | Pan. Shift together with the middle button orbits the model view. |
| `PAN` / `P` | Pan with the left button until Escape. |
`ZOOMFACTOR` scales what one notch does. The system variable takes 3 to 100 —
the range it has everywhere — and `SETVAR ZOOMFACTOR` accepts nothing outside
it. Options → User Preferences reaches 500 for anyone who wants a faster wheel:
the slider covers the variable's own range, the field beside it takes any value
up to that ceiling, and both show what a notch does as a percentage. A value
above 100 therefore lives only in this application's own configuration, and a
bare `SETVAR ZOOMFACTOR` still reports it.
A notch multiplies the camera distance by `0.9 ^ (ZOOMFACTOR / 60)`, so the
default 60 brings the view a tenth closer, a notch in and a notch out cancel
exactly, and no setting can drive the distance to zero or through it. A floating
viewport in MSPACE zooms its own view the same way, from a base of 0.85 — the
15% a notch has always moved a viewport.
Panning covers the same ground whichever way it starts: the active model tile,
a paper sheet, or the model view inside a floating viewport in MSPACE. A pan
dragged out of a two-finger gesture and one dragged with the middle button move
@@ -46,8 +60,10 @@ from the split described below; it is not a separate setting.
means. A wheel reports notches and zooms; a precise-scrolling device reports
pixels and pans. There is no modifier and no setting in that decision.
`pan_active_view` and `zoom_view_at_cursor` are the bodies shared by the mouse
and the gestures, and they apply a pinch as `1 - step / 10 = 1 / (1 + m)` so a
pinch of `m` leaves the view exactly `1 + m` times closer.
and the gestures. A pinch of `m` leaves the view exactly `1 + m` times closer:
`pinch_zoom_steps` asks the camera for the step that applies that ratio
(`Camera::zoom_steps_for_ratio`) instead of inverting the zoom law here, which
is what let the two drift apart when the law stopped being a subtraction.
- `src/app/mod.rs` carries one `Message::TrackpadPinch(f32)` per gesture event.
Pixel deltas pan only where they can only mean a trackpad. The web build
@@ -64,7 +80,9 @@ cargo test --locked --lib scroll_intent
```
The tests cover the decision between a notch and a pixel, and the 1:1 mapping of
a pinch onto the camera. The gestures themselves are checked by hand: macOS
a pinch onto the camera — the mapping by zooming a real `Camera` and measuring
the distance it ends at, so it is checked against the camera's own zoom law and
not against a second copy of it. The gestures themselves are checked by hand: macOS
offers no way to synthesize a magnify event, so a pinch cannot be driven by a
test. Two-finger pan and pinch are exercised in model space, in a tiled view, on
a paper sheet, and inside a floating viewport in MSPACE.