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
6 changes: 3 additions & 3 deletions docs/architecture/07-cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ slightly during zoom ramps, nearest keeps it pixelated and steady. Pure rules:
session-end reset of `ixPubLevel_` keeps a quick zoom back to the same level from skipping the
publish nudge, and the hide-cursor hotkey's show-again transition nudges after the blanker
restore like the zoom-out does.
- **Pan glide (#430, `panGlideMaxPx`, experimental, 0 = off).** A soft stop: when a mouse movement
- **Pan glide (#430, `panGlideMaxPx`, 0 = off).** A soft stop: when a mouse movement
stops, the POINTER eases on at the hand's speed and slows to rest within `panGlideMaxPx` SCREEN px
at any zoom (`src/pan_glide.h`); `panGlideMs` is the ease's time constant, shortened for a fast hand
so the cap holds. The view follows by DWM centring. Any hand movement, a mouse button, a game,
at any zoom (`src/pan_glide.h`). One setting since #434: the ease's time constant follows the
distance (`PanGlideTauS`, 40 px = 60 ms), shortened for a fast hand so the cap holds. The view follows by DWM centring. Any hand movement, a mouse button, a game,
Inspect, a detached view or 1x ends it. Wind's own steps move the tick baseline (`lastSetVirtual`),
so the lock detector and the gain learner only ever see hand motion. A first, uncapped version
(momentum = speed x time) could throw the pointer across the screen and was replaced. It replaces
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/08-config-profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Every key works in the ini whether or not Settings shows it. Keys hot-reload unl
| Keys | Meaning |
|---|---|
| `maxLevel` (12), `zoomInSpeed`, `zoomOutSpeed`, `zoomEaseOutMs`, `smoothZoom*` | Range and feel of the zoom |
| `cursorSensitivity` (1.0), `panSpeed` (1.0), `panGlideMaxPx` (0), `panGlideMs` (60) | Mouse and arrow-key pan speed; the pan glide's distance cap and ease (#430) |
| `cursorSensitivity` (1.0), `panSpeed` (1.0), `panGlideMaxPx` (0) | Mouse and arrow-key pan speed; the pan glide's distance, which also sets its ease (#430, #434) |
| `mouseAlign` (0 centred, 1 within the edges), `mouseMarginPct` | Where the pointer sits while the view moves |
| `trackCaret` (1), `trackFocus` (0), `trackAlign`, `trackGlideMs` (200) | Follow the text caret and keyboard focus |
| `lockApps`, `warpLock` (0) | Games whose sessions pan from raw mouse motion; heuristics for unlisted games |
Expand Down
6 changes: 1 addition & 5 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ Config ParseConfig(const std::string& text) {
else if (key == "dwmFlush") c.dwmFlush = ParseIntStrict(val);
else if (key == "diagnostics") c.diagnostics = ParseIntStrict(val);
else if (key == "cursorSensitivity") c.cursorSensitivity = ParseDoubleStrict(val);
else if (key == "panGlideMs") c.panGlideMs = ParseIntStrict(val);
else if (key == "panGlideMaxPx") c.panGlideMaxPx = ParseIntStrict(val);
else if (key == "cursorConstantSize") c.cursorConstantSize = ParseIntStrict(val);
else if (key == "cursorVisibility") c.cursorVisibility = val;
Expand Down Expand Up @@ -296,8 +295,6 @@ Config ParseConfig(const std::string& text) {
c.smoothZoomAccel = clampd(c.smoothZoomAccel, 1.0, 8.0);
c.smoothZoomRamp = clampd(c.smoothZoomRamp, 0.1, 3.0);
c.cursorSensitivity = clampd(c.cursorSensitivity, 0.25, 4.0);
if (c.panGlideMs < 10) c.panGlideMs = 10;
if (c.panGlideMs > 300) c.panGlideMs = 300;
if (c.panGlideMaxPx < 0) c.panGlideMaxPx = 0;
if (c.panGlideMaxPx > 400) c.panGlideMaxPx = 400;
c.sharpness = clampd(c.sharpness, 0.0, 1.0);
Expand Down Expand Up @@ -518,9 +515,8 @@ std::string DefaultIniText() {
"; No effect in a free transform session (the pointer itself drives the view)\n"
"cursorSensitivity=1.0\n"
"; panGlideMaxPx: when a mouse movement stops the pointer eases on at most this many screen\n"
"; px before it rests (Transform engine, zoomed); 0=off. panGlideMs: the ease's time (ms)\n"
"; px before it rests (Transform engine, zoomed); 0=off. A longer glide also eases longer\n"
"panGlideMaxPx=0\n"
"panGlideMs=60\n"
"; cursorConstantSize: 0=the cursor grows with the zoom (default); 1=keep it at normal\n"
"; desktop size at every zoom (render engine only). Replaces cursorScaleWithZoom (ignored).\n"
"cursorConstantSize=0\n"
Expand Down
5 changes: 2 additions & 3 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,9 @@ struct Config {
double cursorSensitivity = 1.0;
// Pan glide (issue #430, src/pan_glide.h): when a mouse movement stops, the pointer eases on and
// comes to rest instead of halting dead. panGlideMaxPx caps how far, in SCREEN px at any zoom
// (0 = off, the default); panGlideMs is the ease's time constant (a fast hand eases more steeply
// to stay inside the cap). Hot. Replaces cursorSmoothing (ignored).
// (0 = off, the default). The ease's time follows the distance (PanGlideTauS, one setting since
// #434; the old panGlideMs key is ignored). Hot. Replaces cursorSmoothing (ignored).
int panGlideMaxPx = 0;
int panGlideMs = 60;
// 0 (default) = the render engine's cursor grows with the zoom, matching the transform
// engine (DWM magnifies its sprite); 1 = opt-in constant desktop-size pointer. Replaces
// cursorScaleWithZoom (issue #253), which is IGNORED: the default template wrote it as an
Expand Down
2 changes: 1 addition & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1869,7 +1869,7 @@ static void RunTick(TickState& t) {
!t.viewDetached && !btn;
if (t.glide.active && (!glideOk || curDx != 0 || curDy != 0)) wind::PanGlideCancel(t.glide);
if (!t.glide.active)
wind::PanGlideObserve(t.glide, curDx, curDy, dt, cur.x, cur.y, t.cfg.panGlideMs / 1000.0,
wind::PanGlideObserve(t.glide, curDx, curDy, dt, cur.x, cur.y, wind::PanGlideTauS(t.cfg.panGlideMaxPx),
t.cfg.panGlideMaxPx / lvl, glideOk); // the cap is in screen px
double gx = 0.0, gy = 0.0;
if (t.glide.active && wind::PanGlideStep(t.glide, dt, gx, gy)) {
Expand Down
8 changes: 8 additions & 0 deletions src/pan_glide.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ inline constexpr double kGlideVelTauS = 0.03; // velocity estimate: the
inline constexpr double kGlideMinStartSpeed = 40.0; // px/s: below this the hand was resting anyway
inline constexpr double kGlideStopDist = 0.5; // desktop px left to travel: the glide is over

// One setting drives the glide (#434): its ease time grows with its distance, so a longer glide also
// slows down more gently. 40 px gives 60 ms, the old separate default.
inline double PanGlideTauS(int maxPx) {
double ms = 40.0 + maxPx * 0.5;
if (ms > 240.0) ms = 240.0;
return ms / 1000.0;
}

// One tick of hand motion (dx, dy desktop px over dt s), with the pointer now at (curX, curY).
// Starts a glide (returns true) on the first still tick after motion when the session allows it.
// tauS is the ease's time constant; maxDist (desktop px, > 0) caps how far it travels: a hand fast
Expand Down
4 changes: 2 additions & 2 deletions src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#define WIND_VER_MAJOR 0
#define WIND_VER_MINOR 26
#define WIND_VER_PATCH 1
#define WIND_VER_PATCH 2

// String form for logs/snapshot/UI. Keep in sync with the numeric parts above.
#define WIND_VERSION_STR "0.26.1"
#define WIND_VERSION_STR "0.26.2"
10 changes: 3 additions & 7 deletions tests/test_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ TEST_CASE("renderer knobs have sane defaults") {
CHECK(c.bilinear == 1);
CHECK(c.sharpness == doctest::Approx(0.0)); // off by default
CHECK(c.panGlideMaxPx == 0);
CHECK(c.panGlideMs == 60);
CHECK(c.zorderBand == 0); // shipped unbanded (issue #162): band 16 sat UNDER the
// Snipping Tool overlay, which cost the cursor entirely
CHECK(c.brightness == doctest::Approx(1.0));
Expand Down Expand Up @@ -140,9 +139,9 @@ TEST_CASE("hdrTonemap can be disabled") {
Config c = ParseConfig("hdrTonemap=0\n");
CHECK(c.hdrTonemap == 0);
}
TEST_CASE("parses panGlideMs, z-order band, brightness; the old cursorSmoothing is ignored") {
TEST_CASE("parses z-order band, brightness; the old cursorSmoothing and panGlideMs are ignored") {
Config c = ParseConfig("panGlideMs=150\ncursorSmoothing=0.7\nzorderBand=16\nbrightness=0.85\n");
CHECK(c.panGlideMs == 150);
CHECK(c.panGlideMaxPx == 0);
CHECK(c.zorderBand == 16);
CHECK(c.brightness == doctest::Approx(0.85));
}
Expand Down Expand Up @@ -178,14 +177,12 @@ TEST_CASE("numeric fields are clamped to documented ranges") {
CHECK(ParseConfig("smoothZoomAccel=0\n").smoothZoomAccel == doctest::Approx(1.0));
CHECK(ParseConfig("smoothZoomRamp=-1\n").smoothZoomRamp == doctest::Approx(0.1));
CHECK(ParseConfig("cursorSensitivity=10\n").cursorSensitivity == doctest::Approx(4.0));
CHECK(ParseConfig("panGlideMs=5000\n").panGlideMs == 300);
CHECK(ParseConfig("panGlideMs=-1\n").panGlideMs == 10);
CHECK(ParseConfig("panGlideMaxPx=9999\n").panGlideMaxPx == 400);
CHECK(ParseConfig("sharpness=9\n").sharpness == doctest::Approx(1.0));
CHECK(ParseConfig("brightness=0\n").brightness == doctest::Approx(0.5));
// In-range values pass through untouched.
CHECK(ParseConfig("maxLevel=8\n").maxLevel == doctest::Approx(8.0));
CHECK(ParseConfig("panGlideMs=150\n").panGlideMs == 150);
CHECK(ParseConfig("panGlideMaxPx=40\n").panGlideMaxPx == 40);
}
TEST_CASE("multiMonitor can be set") {
CHECK(ParseConfig("multiMonitor=0\n").multiMonitor == 0);
Expand Down Expand Up @@ -460,7 +457,6 @@ TEST_CASE("the first-run ini template parses to the struct defaults (issue #274)
CHECK(t.dwmFlush == d.dwmFlush);
CHECK(t.diagnostics == d.diagnostics);
CHECK(t.cursorSensitivity == doctest::Approx(d.cursorSensitivity));
CHECK(t.panGlideMs == d.panGlideMs);
CHECK(t.panGlideMaxPx == d.panGlideMaxPx);
CHECK(t.cursorConstantSize == d.cursorConstantSize);
CHECK(t.cursorVisibility == d.cursorVisibility);
Expand Down
8 changes: 8 additions & 0 deletions tests/test_pan_glide.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ TEST_CASE("the cap bounds the distance at any speed and keeps the hand's start s
}
}

TEST_CASE("one setting: the ease time grows with the distance (#434)") {
CHECK(PanGlideTauS(40) == doctest::Approx(0.060)); // the old separate default
CHECK(PanGlideTauS(0) == doctest::Approx(0.040));
CHECK(PanGlideTauS(200) == doctest::Approx(0.140));
CHECK(PanGlideTauS(400) == doctest::Approx(0.240));
CHECK(PanGlideTauS(1000) == doctest::Approx(0.240));
}

TEST_CASE("no glide when it is off or the session does not allow it") {
PanGlide off;
CHECK_FALSE(Flick(off, 20, 20.0, 0.0, 0.0)); // panGlideMs = 0
Expand Down
12 changes: 6 additions & 6 deletions ui/src/Onboarding.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
}
function back() { if (cur > 0) cur -= 1; }
function skip() { setConfig('onboarded', '1'); onDone(); }
const zoomInRow = { label:'Zoom in', desc:'Hold to magnify', buttonKey:'zoomInButton', vkKey:'zoomInVk', modsKey:'zoomInMods', buttonModsKey:'zoomInButtonMods' };
const zoomOutRow = { label:'Zoom out', desc:'Hold to zoom back', buttonKey:'zoomOutButton', vkKey:'zoomOutVk', modsKey:'zoomOutMods', buttonModsKey:'zoomOutButtonMods' };
const zoomInRow = { label:'Zoom in', desc:'Hold to zoom in', buttonKey:'zoomInButton', vkKey:'zoomInVk', modsKey:'zoomInMods', buttonModsKey:'zoomInButtonMods' };
const zoomOutRow = { label:'Zoom out', desc:'Hold to zoom out', buttonKey:'zoomOutButton', vkKey:'zoomOutVk', modsKey:'zoomOutMods', buttonModsKey:'zoomOutButtonMods' };
</script>
<div class="wnd win" data-palette={palette}>
<div class="caption" style="app-region:drag;-webkit-app-region:drag">
Expand Down Expand Up @@ -68,16 +68,16 @@
</svg>
</div>
<h1>Welcome to Wind</h1>
<p>Barely there. Everywhere. Let's set up the essentials.</p>
<p>A few quick steps to get started</p>
</div>
<!-- Step 1: Set your zoom keys -->
<div class="step" class:show={cur === 1}>
<h1>Set your zoom keys</h1>
<p>Pick the buttons you'll hold to zoom. Mouse side-buttons work great, or choose keyboard keys.</p>
<div class="orow"><div class="ot"><div class="rlabel" id="ob-in-l">Zoom in</div><div class="rdesc" id="ob-in-d">Hold to magnify</div></div>
<p>Pick the buttons or keys you hold to zoom</p>
<div class="orow"><div class="ot"><div class="rlabel" id="ob-in-l">Zoom in</div><div class="rdesc" id="ob-in-d">Hold to zoom in</div></div>
<div class="rctl"><KeybindCapture row={zoomInRow} values={keys} onChange={live}
labelledby="ob-in-l ob-in-v" describedby="ob-in-d" valueId="ob-in-v" /></div></div>
<div class="orow"><div class="ot"><div class="rlabel" id="ob-out-l">Zoom out</div><div class="rdesc" id="ob-out-d">Hold to zoom back</div></div>
<div class="orow"><div class="ot"><div class="rlabel" id="ob-out-l">Zoom out</div><div class="rdesc" id="ob-out-d">Hold to zoom out</div></div>
<div class="rctl"><KeybindCapture row={zoomOutRow} values={keys} onChange={live}
labelledby="ob-out-l ob-out-v" describedby="ob-out-d" valueId="ob-out-v" /></div></div>
</div>
Expand Down
16 changes: 12 additions & 4 deletions ui/src/Settings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,23 @@
<style>
/* Hidden until the session is loaded, so no empty controls flash; the host paints the theme background behind it. */
.app.pending { visibility: hidden; }
.app { width: 100vw; height: 100vh; display: grid; grid-template-rows: 38px 1fr; position: relative; overflow: hidden; }
.body { display: grid; grid-template-columns: 240px 1fr; min-height: 0; }
.app { --side-w: 240px; width: 100vw; height: 100vh; display: grid; grid-template-rows: 38px 1fr; position: relative; overflow: hidden; }
.body { display: grid; grid-template-columns: var(--side-w) minmax(0, 1fr); min-height: 0; }
/* Narrow window: the sidebar moves above the page as a wrapped strip instead of eating the content. */
@media (max-width: 700px) {
.app { --side-w: 0px; }
.body { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
}
.body { position: relative; } /* the scroll chip sits on the right edge of the page column */
/* No native scrollbar on the page: ScrollChip shows the position instead (#329). */
.main { position: relative; min-height: 0; overflow-y: auto; padding: 0 40px; outline: none; scrollbar-width: none; }
/* The page is a size container: --gx is the side gutter (smaller when narrow) and caps the content at 960px on wide windows. */
.main { position: relative; min-height: 0; min-width: 0; overflow-y: auto; overflow-x: hidden; padding: 0; outline: none; scrollbar-width: none;
container-type: inline-size; }
.page, .tail { --gx: max(clamp(16px, 4cqw, 40px), calc((100cqw - 960px) / 2)); padding-inline: var(--gx); }
.main[data-page="tray"] :global(.banner + .card) { margin-top: 20px; } /* k01: a caption-less first card sits 20px under the band */
.page.fade { animation: pagein var(--dur-fast) var(--ease); }
@keyframes pagein { from { opacity: 0; } }
.tail { height: 110px; } /* clearance so the capsule never covers the last row */
.tail { height: 110px; padding: 0; } /* clearance so the capsule never covers the last row */
.main[data-page="tray"] .tail { height: 120px; } /* k01: the scroller pads 120px under the last card */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
Expand Down
2 changes: 1 addition & 1 deletion ui/src/controls/About.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<p class="tag">Barely there. Everywhere.</p>
{#if version}<p class="ver">v{version}</p>{/if}
<button type="button" class="link" onclick={onRepo}>Star on GitHub</button>
<p class="ask">If Wind helps you, a star helps others find it.</p>
<p class="ask">A star helps others find Wind</p>
</div>

<style>
Expand Down
15 changes: 11 additions & 4 deletions ui/src/controls/SettingRow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,25 @@
{/if}

<style>
.row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 24px; min-height: 62px; padding: 8px 18px; }
.row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 12px 24px; min-height: 62px; padding: 8px 18px; }
.row.disabled { opacity: .45; }
.row.wide { grid-template-columns: minmax(0, 1fr); gap: 10px; padding-bottom: 12px; } /* the control sits under the text, full width */
.row.wide .ctl { justify-content: flex-start; min-width: 0; }
.meta { min-width: 0; }
.lrow { display: flex; align-items: baseline; gap: 10px; }
.lrow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 10px; }
.tab { font: 11px var(--s); letter-spacing: .02em; color: var(--fg3); opacity: .8; }
.tag { font: 500 10.5px var(--s); letter-spacing: .03em; color: var(--fg3); padding: 1px 6px;
border: 1px solid var(--chipb); border-radius: 999px; }
.label { font: 500 13.5px var(--s); color: var(--fg); }
.desc { margin-top: 1px; font: 12.5px var(--s); color: var(--fg3); }
.ctl { display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
.desc { margin-top: 1px; font: 12.5px var(--s); color: var(--fg3); text-wrap: pretty; overflow-wrap: break-word; }
.ctl { display: flex; align-items: center; justify-content: flex-end; gap: 6px; min-width: 0; max-width: 100%; flex-wrap: wrap; }
/* A narrow card stacks the control under the label and subtext instead of squeezing the text. */
@container (max-width: 640px) {
.row { grid-template-columns: minmax(0, 1fr); gap: 8px; padding-bottom: 12px; }
.row .ctl { justify-content: flex-start; }
.row .ctl :global(.sl) { width: 100%; }
.row .ctl :global(.sl input) { flex: 1 1 120px; width: auto; }
}
.chip { height: 28px; padding: 0 12px; border-radius: var(--srad); background: var(--chip);
border: 1px solid var(--chipb); color: var(--fg); font: 13px var(--s); }
.chip:hover:not(:disabled) { border-color: var(--outline); }
Expand Down
4 changes: 2 additions & 2 deletions ui/src/controls/Slider.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
</div>

<style>
.sl { display: inline-flex; align-items: center; gap: 6px; }
.sl { display: inline-flex; max-width: 100%; min-width: 0; align-items: center; gap: 6px; }
.sl.disabled { opacity: .45; }
input { -webkit-appearance: none; appearance: none; width: 220px; height: 20px; margin: 0 10px 0 0;
input { -webkit-appearance: none; appearance: none; width: 220px; max-width: 100%; flex: 1 1 120px; min-width: 0; height: 20px; margin: 0 10px 0 0;
background: transparent; cursor: pointer; }
input::-webkit-slider-runnable-track {
height: 3px; border-radius: 2px;
Expand Down
2 changes: 1 addition & 1 deletion ui/src/lib/KeybindCapture.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@
on:click={arm}
on:blur={() => { if (armed) cancel(); }}
on:contextmenu|preventDefault={onContextMenu}
title="Click to bind (combos like Ctrl+Alt+F1 work), right-click to clear">
title="Click to set a key and right click to clear">
{#if armed}
{row.modsOnly ? 'Hold the modifier keys...' : row.buttonKey ? 'Press a key, combo, or button...' : 'Press a key or combo...'}
{:else if split && lbl !== null}
Expand Down
Loading
Loading