Skip to content

Stop drawing every border blurred, and give the app back its motion - #1

Merged
wrstt merged 1 commit into
mainfrom
nocturne-render-polish
Aug 25, 2026
Merged

wrstt merged 1 commit into
mainfrom
nocturne-render-polish

Conversation

@wrstt

@wrstt wrstt commented Aug 25, 2026 •

Copy link
Copy Markdown
Owner

Three rendering and interaction defects, all of which came from the interface having only ever been reviewed under libgdiplus on Linux.

Every outlined surface was drawn blurred

Cards, buttons, inputs, tags, toggles, checkboxes, table rows, the sidebar rule and the window's own edge all reach the screen through one hairline helper, which inset its path by a whole pixel before stroking. The app paints under PixelOffsetMode.HighQuality, which puts sample points on pixel corners, so a 1px pen on an integer coordinate straddles two rows.

Measured on Windows — a #3F424D edge on #161826:

result
before two rows of #2B2D3A
after one row of #3F424D

The stroke now sits on a half-pixel centreline, with the corner radius pulled in so its outer edge still follows the fill beneath it. Verified in the built binary at 150% scale: column 359 background, column 360 exactly 63,66,77, column 361 card surface.

libgdiplus does not reproduce the split, so on Linux the borders had always looked correct.

Snap, the window menu and the minimise animation did nothing

FormBorderStyle.None strips WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX and WS_SYSMENU. The window was reporting 0x16010000 — no thick frame, no system menu — so Windows treated it as a fixed panel. Restored in CreateParams. The window still draws all its own chrome, and maximise stays inside the work area because WM_GETMINMAXINFO was already answered correctly.

Rounded corners, shadow and the DWM border were never affected; Windows 11 gives those to any top-level window regardless. That was checked before changing anything.

Nothing responded to the pointer

Only the toggle knob had ever animated. That curve is now NAnim, a reusable eased value; NControl and NPanel each drive one for hover and one for press, so painters read a float instead of a bool. Buttons, select cards and text inputs use it.

SPI_GETCLIENTAREAANIMATION is honoured, so anyone who has turned animation off in Windows gets none of it.

Scrolling also eases toward a target rather than jumping 54px per notch, and the wheel router no longer keeps a second copy of the scroll distance.

Checks

  • Builds clean under MSBuild Release, 0 warnings, 0 errors, with TreatWarningsAsErrors on.
  • Hairline fix verified by sampling pixels in the running binary at 150% scale.
  • Easing curve measured directly: 0.323 → 0.503 → 0.663 → 0.787 → 0.885 → 0.954 → 0.991 → 1.000, settled and stopped at 133ms.
  • App launches clean, no new entries in ConfigurO.log.

Not verified by execution: the reduced-motion path (would need the Windows setting toggled), and snap/menu behaviour end to end — the style bits are confirmed set, the interactions are not.

version.txt is deliberately untouched; cutting 3.2 is a separate step.

The hairline helper every outlined surface goes through inset its path by a
whole pixel before stroking. Under PixelOffsetMode.HighQuality that lands the
centreline on a pixel boundary, so GDI+ split each border across two rows at
roughly two-thirds strength -- measured, a #3F424D edge on #161826 came back as
two rows of #2B2D3A. The stroke now sits on a half-pixel centreline. Invisible
under libgdiplus, which is why it survived a redesign reviewed on Linux.

Restores the window style bits FormBorderStyle.None strips, so Aero Snap, Snap
Layouts, the Alt+Space menu and the minimise animation work again.

Adds NAnim, a reusable eased value generalised from the toggle knob -- the one
thing in the app that had ever animated. NControl and NPanel drive one for
hover and one for press; buttons, select cards and text inputs now fade rather
than switch. Honours SPI_GETCLIENTAREAANIMATION.

Scrolling eases toward a target instead of jumping 54px per notch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4ad09f868

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

_dragging = true;
_dragOffset = e.Y - t.Y;
Capture = true;
_glide.Stop();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Synchronize the target when cancelling a glide

If the user presses the scrollbar thumb while a wheel glide is still in progress and releases it without moving, this stops the timer but leaves _target at the old destination. The next wheel event is therefore accumulated from that stale destination rather than the visible ScrollY, causing the panel to resume the cancelled glide and potentially jump hundreds of pixels. Set _target to the current scroll position when the thumb cancels the glide.

Useful? React with 👍 / 👎.

@wrstt
wrstt merged commit 7b73b61 into main Aug 25, 2026
1 check passed
@wrstt
wrstt deleted the nocturne-render-polish branch August 25, 2026 00:57
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