Repository navigation
Windows: real CSS-style box-shadow via DirectComposition (#210 follow-up) #230
Description
Context
#210 wired 4 of the 5 Windows styling stubs (decoration / opacity / border_color / border_width) at v0.5.347. The fifth — widget.shadow — remained Stub. Storage was already in place at crates/perry-ui-windows/src/widgets/mod.rs::SHADOW_PARAMS; what's missing is the rendering pass.
Why a separate issue
Real CSS box-shadow rendering on Win32 child controls (vs top-level windows) needs DirectComposition:
IDCompositionDevice/IDCompositionVisual/IDCompositionEffectGroupIDCompositionDropShadowEffect(the named CSS-equivalent shape: color, blur, offset)- DXGI swap chain integration so DirectComposition can composite atop the GDI render
Or alternatively a custom alpha-blended WM_PAINT pass on the parent that draws N concentric translucent rectangles to approximate Gaussian blur. This is the classic GDI "fake shadow" — visible but not pixel-accurate.
Either path is multi-day work that didn't fit inside #210's scope. Decoration / opacity / border could land via small per-prop apply functions; shadow needs new infrastructure.
Acceptance criteria
widget.shadowrow in the styling matrix flips fromStub→Wiredfor Windows.- A widget calling
widgetSetShadow(handle, r, g, b, a, blur, offsetX, offsetY)produces a visible drop shadow under the widget's bounds, matching the CSSbox-shadowreference within reasonable approximation (color, offset, and at least 8-16px of blur radius). - Existing widgets with corner radius (which uses
SetWindowRgn) still render correctly — the shadow path must compose with the rounded clip, not fight it. docs/examples/ui/styling/visual_test.tssection 4 (Shadow) renders visible shadows on Windows.
Hints for the implementer
crates/perry-ui-macos/src/widgets/mod.rs::set_shadowis the reference (CALayer-based — setssetMasksToBounds: false+shadowOpacity/shadowRadius/shadowOffset). Behavior to match: opaque shadow color with alpha riding on shadowOpacity; positiveoffsetY= downward (HTML convention).- The
SHADOW_PARAMSstore already exists (mod.rs:798) with the full 7-tuple. Theapply_corner_radiusdeferred-paint pattern atmod.rs:962is the canonical "call from layout pipeline" hook —apply_shadow(handle)should slot in symmetrically. - DirectComposition requires Windows 8+ (matches Perry's minimum). The
windows-rscrate already enablesWin32_Graphics_DirectCompositionwould need to be added tocrates/perry-ui-windows/Cargo.toml'swindowsfeatures. - Two-stage strategy that keeps initial scope manageable: (1) GDI alpha-blend approximation first — visible shadow but not perfectly Gaussian; (2) DirectComposition upgrade later for true CSS parity. This issue could be split if the GDI step is enough for a v1.
Closes
When this lands: Windows reaches 43/43 Wired alongside every other Perry-supported platform. Combined with #210 (now closed except this row), the styling matrix is fully green for issue #185.
Activity
feat(geisterhand): #185 Phase D step 3 — live screenshot preview in i…
proggeramlug commentedon May 10, 2026
Closing as already-implemented — this slipped through tracking.
Shipped in commit 3f79c09 ("feat(windows): #210 wire shadow paint pass — matrix is 43/43 Wired"), v0.5.347. That commit closed #210 but forgot to also close this follow-up.
Acceptance criteria met:
docs/src/ui/styling-matrix.md) shows Windowsshadow ✓ Wired.crates/perry-ui-windows/src/widgets/mod.rs:920-1323:set_shadow(handle, r, g, b, a, blur, offset_x, offset_y)stores params inSHADOW_PARAMSand registers the widget with its parent HWND's shadow paint subclass.paint_shadow_for_childrenders into a 32bpp top-down DIB viaCreateDIBSection, applies per-pixel quadratic falloff (alpha = base * (1 - d/blur)^2) as a cheap visible Gaussian approximation, thenAlphaBlends onto the parent DC withAC_SRC_ALPHA + AC_SRC_OVER.SetWindowRgn-based corner radius — shadow lands on the parent surface, not the child's clipped surface.Went a different path than the issue's "DirectComposition with
IDCompositionDropShadowEffect" suggestion — DirectComposition would be more pixel-accurate but adds a DXGI swap-chain dep and substantial new infrastructure. The GDI alpha-blend approximation is the same path the issue's "Hints for the implementer" section listed as the bounded v1, and shipped within #210's scope rather than as a separate effort.If anyone hits a real-world fidelity gap with the GDI approximation (banding on very wide blur, color-bleeding at high alpha, etc.) please open a fresh issue with a screenshot — DirectComposition can be revisited for v2 then.
fix(ui-windows): Mica-by-default + WM_DPICHANGED relayout (#4681) (#4776