win32: opacity on a child widget fades it (tweened); screenshot reads the screen; curve tests theme-agnostic - #169
Merged
Conversation
…creen
The ABI's aether_ui_set_opacity was top-level-only on win32, so a widget
styled { opacity(0.25) } in a build block stayed opaque while GTK4 and
AppKit faded it; style_opacity (the CSS path) already used a layered child
window. Both now share one path, tweened when a transition was declared.
Two things had to be learned for a widget styled during the build: a
child given WS_EX_LAYERED before its top-level has been shown never
paints, so the alpha is owed until the window shows (WM_WINDOWPOSCHANGED
with SWP_SHOWWINDOW); and the switch to a layered window, not the alpha,
is what cost the first frames of a tween, so a transition declaration
makes the widget layered at full alpha up front.
The driver's /screenshot reads the composed frame from the screen when
the window is on screen and unobscured (raised without activation), and
falls back to PrintWindow otherwise; PrintWindow mid-tween of a layered
child now and then returns a frame without the child. The two curve tests
measure contrast against the ground rather than "ink" (a dark theme read a
fade as no fade), filter single-frame holes and anchor on the middle of
three start captures. Both pass on win32: ease-out ratio ~5-6x, spring
peak progress ~1.10 at 360ms, the designed curve. TODO.md's "win32
child-widget opacity" is closed with the measurements.
Verified natively: runtime test (owed alpha), 74 headless specs unchanged
against main, 16 on-screen specs, screen grabs of the probe and demo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes TODO.md's "win32 child-widget opacity (and the capture path that would prove it)" — both halves, with measurements.
What was wrong
opacity(v)in a build block (text("…") { opacity(0.25) }) reachesaether_ui_set_opacity, which on win32 was top-level-only: the widget stayed opaque while GTK4 and AppKit faded it.style_opacity(the CSS path) had used a layered child window all along, so the two verbs disagreed on one backend.PrintWindowcapture, mid-tween of a layered child, now and then returns a frame without the child.What changed
Backend (
backend/aether_ui_win32.c)w32_apply_opacity: model value recorded at once (what the driver reports), presentation tweened when a transition was declared, snapped otherwise — for a top-level and a child alike.WS_EX_LAYEREDbefore its top-level has ever been shown never paints (measured: absent from the screen, a forced redraw did not bring it back, the same alpha applied after the show fades it). So the alpha is owed while the window is unmapped and settled fromWM_WINDOWPOSCHANGED/SWP_SHOWWINDOW, whichever path showed the window./screenshotreads the composed frame from the screen when the window is on screen and every corner and the centre belong to it (after raising it without activation);PrintWindow(PW_RENDERFULLCONTENT)otherwise — the headless case, still fine for still frames.Tests
test_easing_curve.sh/test_spring_curve.sh: contrast against the ground (mean distance from the frame's median) instead of ink; median-of-three over the samples and the middle of three start captures, so a single dropped frame is not a point on the curve. Header notes rewritten to what was measured.tests/win32/win32_runtime_test.c:child_opacity_is_owed_until_the_window_shows.Measured (Windows 11, window on screen, screen grabs — not the driver)
{ opacity(0.25) }label: 38.9 mean brightness against 60.3 opaque; a{ opacity(0.4) }button 39.0 against 50.0.CHANGELOG under
[current]; TODO.md section closed.