Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions docs/architecture/05-transform-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,14 @@ DWM magnifies with nearest neighbour unless something calls
`quickZoomDefault`): above it the jumps reach 15-25 px and the pointer runs off the screen.
Report:
`Documents\Claude\research\wind\2026-10-10-high-zoom-shake.md`.
- No release glide with the high resolution cursor (#427): the ladder could only cut a glide short
or let its slow tail cross rounding steps (which shook the image and showed it doubled, closed
#426), so `zoomEaseOutMs` is not applied while `txSamplingMode=1`; the zoom stops on release.
- Release glide at both sampling modes (#439, measured 2026-10-10). The high resolution cursor
had its glide turned off (#427) because the slow tail shook and showed the image doubled; the
doubling was the ladder stepping back below a request it had overtaken (#429, `LadderDir`). The
glide is back and stops just before its slow tail (`EaseOutShouldStop`, on the level on screen):
240-260 ms at zoomInSpeed 2.7 / 115 ms, no backward step, pointer steady. The full tail runs
480-550 ms but lands in 0.5-0.9 % ladder hops, which the field read as shake; snapping it to
the exact glide level (`stepRel` capped at the glide's own motion) eases perfectly but shook
11-13 px. Both rejected.
- Nearest while the level moves and smooth at rest was tried: steady, but the switch from pixel to
smooth is plainly visible, so it was rejected (2026-10-07). The older "swap shifted the image
1-2 px" verdict predates the working setter and is void.
Expand Down
10 changes: 6 additions & 4 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1295,10 +1295,12 @@ static void RunTick(TickState& t) {
// root-caused elsewhere - issue #148 - so the user's configured speed applies everywhere.)
t.zoom.setProfile(t.cfg.zoomInSpeed, t.cfg.zoomOutSpeed, t.cfg.smoothZoom != 0,
t.cfg.smoothZoomAccel, t.cfg.smoothZoomRamp);
// No release glide with the high resolution cursor (#427): its ladder can only cut a glide short
// (EaseOutShouldStop) or let the slow tail cross DWM's rounding steps, which shook the image and
// showed it doubled (field 2026-10-10, closed #426). The zoom stops on release instead.
t.zoom.setEaseOut(t.cfg.txSamplingMode == 1 ? 0.0 : t.cfg.zoomEaseOutMs / 1000.0);
// The release glide runs at both sampling modes (#439). With the high resolution cursor it stops
// just before its slow tail (EaseOutShouldStop below): the full tail lands in ladder hops, which
// the field reads as shake (2026-10-10). Turned off in #427 because the tail also showed the image
// doubled; that was the ladder stepping back below a request it had overtaken (#429, LadderDir).
// Measured with the cut (speed 2.7, 115 ms): 240-260 ms of glide, no backward step, pointer steady.
t.zoom.setEaseOut(t.cfg.zoomEaseOutMs / 1000.0);
// Quick-zoom trigger. Modifier mode (quickZoomHotkeyMode==0): hold the configured modifier
// (Ctrl/Alt/Shift; "None" = off) and tap a zoom key. While the modifier is held it toggles quick
// zoom (below) instead of hold-zooming, so suppress the hold-zoom direction (the toggle snaps the
Expand Down
6 changes: 3 additions & 3 deletions src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#pragma once

#define WIND_VER_MAJOR 0
#define WIND_VER_MINOR 27
#define WIND_VER_PATCH 1
#define WIND_VER_MINOR 28
#define WIND_VER_PATCH 0

// String form for logs/snapshot/UI. Keep in sync with the numeric parts above.
#define WIND_VERSION_STR "0.27.1"
#define WIND_VERSION_STR "0.28.0"
3 changes: 1 addition & 2 deletions ui/src/settings-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ export const groups = [
{ key: 'maxLevel', type: 'slider', label: 'Max zoom', keywords: ['maximum', 'limit', 'highest', 'magnification', 'level', 'cap', 'zoom level', 'factor', 'times', 'upper'], desc: 'The highest zoom level', min: 2, max: 30, step: 1, def: 12.0, unit: 'times' },
{ key: 'zoomInSpeed', type: 'slider', label: 'Zoom-in speed', keywords: ['faster', 'slower', 'rate', 'magnify speed', 'zoom rate', 'acceleration', 'velocity', 'how fast'], desc: 'How fast the view zooms in', min: 0.25, max: 4, step: 0.05, def: 1.0, unit: 'times' },
{ key: 'zoomOutSpeed', type: 'slider', label: 'Zoom-out speed', keywords: ['faster', 'slower', 'rate', 'zoom rate', 'velocity', 'how fast'], desc: 'How fast the view zooms out', min: 0.25, max: 4, step: 0.05, def: 1.0, unit: 'times' },
{ key: 'zoomEaseOutMs', type: 'slider', label: 'Release glide', keywords: ['inertia', 'coast', 'momentum', 'ease out', 'easing', 'smooth stop', 'slow down', 'deceleration', 'stop', 'let go', 'release', 'delay', 'milliseconds'], desc: 'How long the zoom coasts after you let go', min: 0, max: 300, step: 5, def: 45, unit: 'ms',
offIf: { key: 'txSamplingMode', eq: '1' } },
{ key: 'zoomEaseOutMs', type: 'slider', label: 'Release glide', keywords: ['inertia', 'coast', 'momentum', 'ease out', 'easing', 'smooth stop', 'slow down', 'deceleration', 'stop', 'let go', 'release', 'delay', 'milliseconds'], desc: 'How long the zoom coasts after you let go', min: 0, max: 300, step: 5, def: 45, unit: 'ms' },
] },
{ caption: 'Easing', rows: [
// Smooth zoom is always on (core default 1); its shape sliders are advanced.
Expand Down
9 changes: 3 additions & 6 deletions ui/tests/settings.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,17 +185,14 @@ test('View: the edge margin shows only when the pointer is kept within the edges
await expect(key(page, 'mouseMarginPct')).toBeVisible();
});

test('High resolution cursor is tagged Experimental and turns the release glide off (#427)', async ({ page }) => {
test('High resolution cursor is tagged Experimental and keeps the release glide (#439)', async ({ page }) => {
await page.goto('/');
await go(page, 'view');
await expect(key(page, 'txSamplingMode').locator('.tag')).toHaveText('Experimental');
await go(page, 'zoom');
await expect(key(page, 'zoomEaseOutMs')).not.toHaveClass(/disabled/);
await go(page, 'view');
await key(page, 'txSamplingMode').getByRole('switch').check({ force: true });
await go(page, 'zoom');
await expect(key(page, 'zoomEaseOutMs')).toHaveClass(/disabled/);
await expect(key(page, 'zoomEaseOutMs').locator('input[type=range]')).toBeDisabled();
await expect(key(page, 'zoomEaseOutMs')).not.toHaveClass(/disabled/);
await expect(key(page, 'zoomEaseOutMs').locator('input[type=range]')).toBeEnabled();
});

test('Typing and focus: caret on, focus off by default; a toggle writes at once', async ({ page }) => {
Expand Down
Loading