Repository navigation
Native glass, one material, and full screen as the window itself: the drag's glass in Liquid Glass, every glass on one slider - #227
Merged
Conversation
This was referenced Oct 5, 2026
foxnne
force-pushed
the
claude/native-glass
branch
from
October 5, 2026 17:33
d456455 to
0f0dc08
Compare
foxnne
added a commit
that referenced
this pull request
Oct 5, 2026
…tive forms of every glass surface The user's design: one slider from clear refracting glass through blur and the window's colour to opaque in the window's colour, for everything drawn as glass; every glass surface in two forms on it — in-app (the shader, every platform) and native (the OS's glass, for surfaces that are OS windows, and NSMenu for context menus on macOS). The native way along it as built in #227 (a two-layer crossfade between Liquid Glass variants, the window's colour in the merged shape over it), and why the aimed bubble is lit in the fill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
force-pushed
the
claude/native-glass
branch
from
October 5, 2026 17:45
0f0dc08 to
7e2d834
Compare
foxnne
added a commit
that referenced
this pull request
Oct 5, 2026
…tive forms of every glass surface The user's design: one slider from clear refracting glass through blur and the window's colour to opaque in the window's colour, for everything drawn as glass; every glass surface in two forms on it — in-app (the shader, kept: the web and every platform without native window glass, tuned to look like macOS's glass) and native (the OS's glass in the OS's own style, for surfaces that are OS windows, and NSMenu for context menus on macOS). The native way along it as built in #227 (a two-layer crossfade between Liquid Glass variants, the window's colour under the glass so it keeps its shine, flat colour only at the top), and why the aimed bubble is lit in that colour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
force-pushed
the
claude/native-glass
branch
from
October 5, 2026 17:54
7e2d834 to
1aed2fb
Compare
foxnne
force-pushed
the
claude/native-glass
branch
from
October 5, 2026 20:57
c1b03b5 to
8e29d9b
Compare
… OS has it On macOS 26 (`viewports.liquidGlass`; `FIZZY_NATIVE_GLASS=0` keeps the app's glass) with floats as windows, a view drag's glass is the OS's: one overlay window over the main window's display (`viewports.openOverlay`, passive, at the pop-up level) holds an `NSGlassEffectContainerView` with a Liquid Glass view (the lens) for each piece of glass the frame declares in place of drawing it (`core.native_glass`): every drop zone bubble showing (in the main window and in float windows' bands, mapped onto the main window's frame) and the carried drop's head and tail, or its card. The container runs pieces within the app's merge distance together (`DropZones.merge`: the bubbles partly, the carried drop into the one it is aimed at, which takes the glass's pressed look). The bubbles' coming and going — growing out of the drop and pinching off, and running back together after the drag — is the OS's glass too: it stays the OS's until the drops have gone. The carried photograph is the overlay's picture, over its glass; glass frames are set in the Core Animation transaction the picture presents in. A float the drop opens grows out of a carry window of its own. One display (the main window's) for now; the private lens variant and pressed state are gated on macOS 26 and on their setters. Logged in a sandbox: the 1800×1169 overlay holding the aimed place's six bubbles plus the drop's head and tail, the aimed bubble lit, and the six bubbles still the overlay's after the release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t on macOS The user: default macOS to pop-out and native glass, "it will be fully supported on this platform". Floats are OS windows on macOS without `FIZZY_POPOUT` (`FIZZY_POPOUT=0` keeps them in the main window); elsewhere they still need `FIZZY_POPOUT=1` until their windows are dressed there. The drag's glass was already the OS's wherever Liquid Glass exists (`FIZZY_NATIVE_GLASS=0` keeps the app's). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the colour under the glass The OS's glass took nothing from the window opacity setting, so the app could not be any degree of opaque with it (the user). One slider (`Editor.window_opacity`) now runs the drag's glass (`Popout.glassLook`): the window's colour comes in under the clear lens — the background, the lens bending what of the desktop still shows through it and lighting its rim over it — opaque near the top; the lens turns to the glass's light frost only late, the two blended as two layers of the same pieces crossfaded (measured: smooth, the outlines stay one); and at the very top the glass goes, handing over to the colour drawn flat and opaque in the shape the pieces make (`core.liquid_blob.fill`, its smooth union meshed per group). A first mapping went into frost and heavier frost early and blurred everything at every point (the user): the colour, not blur, is what makes it opaque. The colour is under the glass, as native shape layers in the overlay window beneath both glass layers, so the glass bends and lights it and keeps its shine; drawn over the glass it muted the shine (the user). No necks between pieces: drawn under the glass's bridges they showed past them as dark bars (the user's capture). A mesh with no triangle in it — discs shrunk to nothing as they come and go — draws nothing; it tripped dvui's assert. The drop zones' icons are over the glass: in a layer of their own the overlay takes, as it takes the carried view (`core.screens.markCarried`, several a frame now), each replayed at the main window's part of the frame and at each float window's band. Under the glass they were seen only through it, blurred. The carried drop read as a separate layer over the bubble it snapped onto: that bubble was lit with the glass's pressed look, and the OS never runs pressed glass together with its neighbours (measured). The aimed bubble is lit in the colour under the glass instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he clear lens The user: fade the blur out at the edges, inset about 10 points, so the edges stay the clear refracted glass, as the app's own glass is. The overlay's lens layer is now drawn whole, and the frost layer over it at its share of the slider with a mask of radial gradients, one per piece, opaque in its middle and clear 4 points in from its edge over a 10-point feather (`overlayFrostMask`): the middle frosts, the rim stays the lens, bending what is behind it with its rim light bright. On the container, so the container still runs the pieces together; a bridge between two stays clear. Measured in a test window over stripes: the masked frost is clear at the rim and frosted in the middle, at full and half share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s a lens like Apple's `core/gfx/glass_look.zig` (std-only, unit-tested) is the one mapping from the window opacity to every glass: the OS's form (`native`, the overlay's Liquid Glass, which `Popout` now reads in place of its own copy) and the app's (`inApp`), on the same breakpoints — the window's colour in from 0.15, frost from 0.6, the glass's shine handing over to flat colour from 0.92. A surface carrying text keeps some frost over a clear lens (`forText`; panes — dialogs, menus — are marked). The app's glass program now draws Apple's lens when a look is published (`LiquidField.publishLook`, which the editor does each frame from the windowed opacity): the picture pulled *inward* from a band near the rim (29% of the shape's shorter half, at most 20 points) by up to 1.1 bands at the edge, falling off as t², so it magnifies toward the rim and folds past a slope of 1 — measured on Apple's lens over stripes; the band clear glass while the middle frosts; the folded band a little darker; a brighter rim line all round; light across the band that a full tint does not hide; a little colour fringe where it bends hardest. The window's colour comes in under clear glass too (the tint no longer waits on the blur). Every field — dialogs, menus, drops — follows it. The new model rides in uniform slots that were free (`dither.yzw`, `backdrop.yzw`) and is off when they are 0, so a program that never sees them draws exactly as before. GLSL (the web) and Metal (macOS) compile at run time and draw the lens; the HLSL source has it too, but the SPIR-V and DXIL that Vulkan and D3D12 run are precompiled and were not rebuilt (no shadercross here) — they keep the earlier glass until compiled again with the commands at the top of the HLSL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user saw a circle about half the bubble's size trailing and jiggling under the carried drop: its tail, which follows the head on a spring. In the app's glass the tail runs into the head and wobbles it; declared to the OS's glass as a piece of its own, with the window's colour under it, it read as a separate circle. Only the head is declared now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… once under the merged glass The drag lost its jiggle with the tail (the user): the tail, following the head on a spring and run into it by the glass, is what wobbles the drop. It is declared to the OS's glass again. What read as a darker circle trailing inside the head was the window's colour drawn under each piece, twice where they overlap. The colour is now one shape layer for the union of every piece that is all there, so overlaps — head and tail, the drop snapped onto a bubble — are coloured once; a piece still coming or going keeps a layer of its own at its own opacity, and a lit piece's light goes over the union. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…compact toolbar's corners The user: can the app's main window be a Liquid Glass window — it had a tighter corner and not the same material. On macOS 26 the main window and every float's window now stand on Liquid Glass (`fizzy_macos_window_liquid_glass`), beside SDL's view in the window's frame view as a float's window always was (the main window no longer wraps SDL's view in a vibrancy view, so no responder repair): the window's colour, the clear lens over it, frost over the lens. Each frame they take the one slider (`Editor.windowGlassLook`, `core.glass_look.native` at the window's eased opacity): the colour under the glass — so the glass bends and lights it, its shine kept — frost coming in late, and the glass going at the very top, opaque when maximized or full screen. The frame then draws no base of its own (the main window's outer box, a float's `Popout.backing`), and the window's background is clear. Before macOS 26, vibrancy as before. Corners: what macOS 26 rounds a window by is whether it has a toolbar — measured: none 17 points (fizzy until now), compact 20.5 with a 40-point title bar, unified 27 with 66. The user chose compact: an empty `NSWindowToolbarStyleUnifiedCompact` toolbar, no separator, the window's frame kept as it was when it is added (AppKit grew it by the toolbar's height — a restored float's window would have grown 30 points every launch). The grow animation rounds to 20. Verified in a sandbox: the main window's corner measured 20.5 points; a float opened by a drop grows out of the glass and hands over at matching sizes; no change to the main window's frame. Not seen: the glass against the desktop (the sandbox sits behind the user's windows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts own path on the slider, no toolbar bar in full screen, fading with the transition A window of Liquid Glass (the main window, a float's) was the drop glass's material: at 0.7 the lens was barely frosted and the window far too see-through. A window now has its own mapping (`core.glass_look.window`): frost comes in early, then the plain blur behind the window (the vibrancy fizzy's windows wore before) — about 0.9 of it at 0.7 — with the window's colour at about half there, opaque at the top. The body fades into a clear band along the edge (6 pt clear, then 40 pt of feather on a smoothstep), so the edge stays the refracting lens while the layers in the window stand apart from what is behind it. - visual_effect_view.m: the window's parts are now fill, lens, frost, blur (an NSVisualEffectView behind the window) and the colour again over the blur; one small nine-sliced feather image, drawn again only when the radius, the band or the window's scale change, masks the frost, the blur and the body colour. Each part has its own layer: AppKit dropped a mask set on a layer it lent a view that never asked for one. - Full screen: a window with a toolbar kept it in full screen, a grey bar over the top of the app. fizzy's answer to window:willUseFullScreenPresentationOptions:, over SDL's delegate, adds NSApplicationPresentationAutoHideToolbar where AppKit allows it, so the toolbar hides with the menu bar. - The window's opacity fades with the OS's fullscreen transitions: `Editor.WindowOpacity` eases over 500 ms on a smoothstep from the moment the window starts to cover the desktop or stops (`backend.coversDesktop`: maximized, and not on its way out of a fullscreen Space). It used to stay opaque until the exit had finished and then drop to translucent with most of the change in the first 150 ms. The cards' opacity (`host.contentOpacity`) eases with it, rather than snapping when the window stops being maximized. Checked: unit tests (8 glass-look tests), test-integration 286/286, check-web, the SDK lock, and the Windows and Linux cross-builds; fizzy's own window glass code run in a harness over stripes at 0, 0.35, 0.68 and 0.9. Full-screen entry and exit not yet run live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er the lens, one band rule, windows from today's 0.5 Every native glass is now one model: the lens at the bottom, bending the surface behind it; the window's colour and the frost over it, both fading out across a clearing bevel along the edge. - Drop glass: the window's colour sat under the lens, so the lens bent a disc of colour where the surface being dragged over should be (the user). The overlay is now lens → colour → frost, the colour and the frost masked alike by each piece's bevel. - One bevel rule (`core.glass_look.band`), the in-app glass's own: 29% of the shape's shorter half, at most 20 pt, the first fifth clear and the rest the middle coming in on a smoothstep. It hugs the rim of small glass. A window's bevel is 20 pt, down from 46. A round piece's mask is a radial gradient; a rounded rect's (a carried card) is the bevel's nine-sliced image, so its corners frost too instead of an ellipse leaving them clear. - Windows: at 1.0 the plain blur, faded by the bevel and sitting over the colour, let the desktop through a ring inside the edge (about a quarter of it mid-band). The window is now blur → one uniform colour → lens → frost, so every part has the same colour and 1.0 is opaque everywhere. - A window's slider starts where today's 0.5 is (`window_from`): below that a window was all but clear glass. A saved 0.68 now looks like the old 0.84; about 0.36 matches it. Drops, dialogs and menus keep the full range. Checked: unit tests (9 glass-look tests), test-integration 287/287, check-web, the SDK lock, the Windows and Linux cross-builds; fizzy's own window and overlay code in harnesses over stripes and a code surface (window opaque at 1.0; drops bending the text, frosted cards to the corners); sandbox launches at 0.36 and 1.0 without errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the colour Two things the user saw in the window at 0.7 (now the old 0.85), both from how the glass stacked: - A dark band round the edge instead of clear refracting glass: the window's colour was one wash under the lens, so the bevel was that colour over the desktop with no frost or blur. - A brighter window, plainest from 0.9 to 1.0, where it went considerably darker: the frost sat over the colour, and its own light lifted everything until the glass went at the top. Now, from the bottom: the lens; then the body — frost, the plain blur, the window's colour — all fading out across the clearing bevel; then the colour over all of it, the bevel too, coming in only as the glass goes (`glass_look.Window.top_fill`, 1 − shine). The bevel is clear glass bending the desktop until the very top. The middle is the colour from about 0.92 up, so 0.9 and 1.0 match there and only the bevel changes as the lens fades. The drop glass is reordered the same way (lens, frost, then the colour), so it no longer lightens toward the top either. Checked in harnesses running fizzy's own window and overlay code: the window at 0, 0.7, 0.9 and 1.0 (the middle's tone the same at 0.9 and 1.0, the bevel clear at 0.7), drops at 0.68 and 0.9 over a code surface. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er, a little shine left at the top The user: the slider should read as the glass's roughness — 0 clear refracting glass (windows a little frosted), the middle frosted glass with its shine at a smoothed, feathered bevel, 1 the window's colour all over with no translucency and only a little edge shine. It read instead as a feathered fill fading in: the colour came in ahead of the frost. - `core.glass_look.way`: frost whole by 0.6 (`rough_by`); the window's colour from 0.3, whole at 1; from 0.8 the colour covers the edge too and the shine goes to a quarter (`shine_min`). - Drops and the carried view (native and in-app) take the window's colour only from 0.8: from 0.3 it was a disc of colour over the surface being dragged across (the user). Dialogs and menus keep it from 0.3 for their text (`InApp.text_mix`, `forText`). - Windows: 0.35 frost at 0, the plain blur whole by about 0.55, the colour after it. The window's slider no longer starts at today's 0.5. At the top the colour comes in under the lens, all over, so the lens's last shine shows over an opaque window, and the blur and the frost go: the blur shows the desktop whatever is under it, so faded across the bevel over an opaque colour it was a ring of desktop inside the edge. - The frost reaches the edge: no clear strip (`bevel_clear` 0), and the bevel fades out of the edge quickly, strong most of the way to it (the user). - A float's window glass sat a title bar lower than its window: its parts were made before the window had its full-size content. Every part now follows SDL's view each frame. Checked: 11 glass-look tests, fizzy's window and overlay code in harnesses across the slider. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rom further off - Glass now starts running together at 36 pt (`DropZones.merge`, up from 24), so the carried drop reaches for a bubble from further off (the user). - A drop's bubbles rest 30% of that apart (about 11 pt, was 16): two shapes join below half the merge distance — the app's smooth union and macOS's glass container alike, measured — so the drop rests as one glass with a full neck to each bubble. As the bubbles swing past their places when motion is playful, they split, then settle back joined. The trash sits in the crook, the same gap from the right and the bottom. Just inside half the merge, the necks were a thread (the user). - The overlay's spacing follows the drop: `core.native_glass.mergeWithin` carries the merge the bubbles were drawn at, so a wheel fitted smaller to a small place joins as the app's glass draws it. - The test that bubbles never run together at rest now checks the reverse: every bubble rests joined to one beside it, and none overlaps another. Checked: test-integration 289/289, unit tests, check-web, the SDK lock, Windows and Linux cross-builds; a wheel in macOS's glass at rest and at the swing in a harness. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…glass moves to fit it A clear window's shadow and edge are the OS's reading of what it showed. A float's window glass was made a title bar out of place (fixed in the change before, which moves it to SDL's view each frame), and the OS kept the shadow it read from that: the float's edge read a little different from the main window's against a dark background (the user). Moving the glass now invalidates the window's shadow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n it, the trash keeps apart - The drop rests as the symmetric cross the user called perfect, each side bubble joined to the middle. An irregular cluster tried in between read uncentered and lopsided. - The trash is a drop of its own (the user): on the diagonal, past the merge distance from the right and the bottom bubbles even while one of them swells. - The bubble the carried view is aimed at swells as it lights (10%), alone, not the whole drop (the user). Its necks thicken and it runs into what is carried. - The carried drop holds where it settled on a bubble until the pointer wanders 6 pt: a hand held still is never still, and each pixel moved the neck between the drop and the bubble, a shimmer (the user). With the tape's still pointer the overlay's glass stops changing once grown in, and macOS's static glass is stable frame to frame (measured), so it was the drop following tremor. - Joined to a bubble, the drop's photograph fades to a quarter (it is 80% opaque on the content colour, and over the bubble it read as an opaque disc under it). - After the view has been carried as a drop, a card or tab over a strip hangs down and right from the pointer's corner. Kept at the original grab point, a place grabbed far from its corner hung up and left of the pointer, anchored at its bottom right (the user). Tests: settled bubbles each join one beside them except the trash; the aimed bubble, swollen, overlaps none and the trash still joins none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…creen square and opaque through its animations, the grown glass the window's colour, the lit bubble a glow - Clicking the main window brought it in front of an overlapping float until fizzy put the float back on its next frame: the main window's content showed through the float at every press (the user). The floats are now put back in the same call that orders the main window forward (`orderWindow:relativeTo:` on its class, AppKit's own under it), and right after a press on it or as it becomes key, for any way AppKit brings it forward without that. - Full screen: the window glass is square there, and covers the whole window. It followed SDL's view, which stops short of the top in full screen, leaving a black band and the rounded corner. - AppKit animates a window into and out of a fullscreen Space as pictures of it. Fading to see-through on the way out made those pictures a double window, and the window itself, already see-through, popped in at the end (the user). A window is now opaque from the moment it sets out for a Space (at once, so the pictures are opaque too) and through the whole of the way back, and fades to its windowed opacity over half a second once it has landed. - A float's window grew out of the carried glass with none of the window's colour, then popped darker as the window took over (the user). The growing glass takes the colour as the picture arrives, as much as the window it becomes will have (`Popout.windowShade`). - The lit bubble (the one the carried view is aimed at) lights with a soft glow, brightest in its middle and gone before its edge, instead of a fill that read as a disc under the glass. Not run live: full-screen entry and exit (they would take over the user's Space); the window ordering on a press. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The carried drop follows the pointer on its springs again while it is joined to a bubble. Held until the pointer wandered 6 pt, it jumped each time it let go: jerky (the user). The neck between the drop and the bubble may shimmer with a hand's tremor again. - The middle bubble is a quarter bigger (65 pt, was 52), the others out with it at the same gaps: the drop reads as one glass round its middle rather than a cross of equals (the user). The trash stays past the merge distance from everything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dow and outline, no OS animation The OS draws a 1-point outline round a titled window as part of its shadow — dark against a light background, light against a dark one — and the main window had it while a float's window did not (the user). Measured in a sandbox: the float's window had no shadow. The routine that dresses a float's window as the main window is (transparent title bar, the OS's shadow, no OS animation) was the vibrancy one, skipped wherever the window stands on Liquid Glass. The dressing is now its own (`fizzy_macos_viewport_dress`), applied either way: the float's window has its shadow and outline again, and no show or close animation (AppKit's never finished under fizzy's frame loop and left ghost windows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user: the drop's bubbles still merged a little at rest; they should sit apart until something makes them swell and merge. - The bubbles rest 22 pt apart, past the 18 pt below which two run together (half the merge, for the app's glass and the OS's container alike, measured): each is born inside the middle, pinches off it on its way out, and rests a drop of its own. - The bubble the carried view is aimed at swells by a quarter as it lights (was a tenth), enough to close the gap: a side runs into the middle, the middle into every side, and into what is carried. The trash stays out of reach even beside a swollen bubble. - The wheel is about 330 across now; the test of a narrow place that keeps the wheel moves from 310 to 340 pt wide to stay the same case. Tests: settled, no two bubbles run together; aimed, a swollen bubble runs into one beside it, overlaps none, and the trash joins none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ting for the mouse - A window of Liquid Glass's toolbar is there for its corners alone, and in full screen there are none: shown there, it stayed at the top after the window had gone full screen, then went (the user). It is hidden from the moment the window sets out for a Space (after the windowed title bar's height is kept for the way back) and shown again once it is back, the frame kept. - Leaving full screen, the window sat opaque until the mouse moved (the user): its opacity's steps asked for the next frame with a plain refresh, which from a frame the window monitor drew wakes nothing. They wake the backend now, and the monitor draws frames for a moment after AppKit has settled the window, for the fade to begin. Not run live: full screen takes over the user's Space. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it stops With the trail laid rather than sprung, the carried drop had little life left in it: not much interactivity, jiggle or wateriness (the user). The give now lives in the drop's shape, not its position: - Eight points round the rim, each a mass on a spring inside the drop, swung by the hand's acceleration as water is in a carried cup. Inertia pushes out the side away from where the hand speeds up, and the acceleration draws the drop out along its way too, so it stretches back as it is set going and is thrown out ahead as it stops, wobbling a few times (damping 0.22 at playful, 3.2 Hz) before it settles. - Neighbours pull on each other, so the rim moves as one surface. Six independent points read as a lumpy potato in the first recording. - Each point is a circle of glass inside the head at rest (0.68 of its radius, 0.32 from the middle), bulging out of it as it swings, so the outline gives in any direction. - From the hand alone, as the trail is: a bubble's pull is no acceleration of the hand, so a drop hovering or crossing between bubbles is not set wobbling; joined to one, its give is damped to critical and drawn in. - Critically damped where motion is minimal, none where it is off. Every piece of the drop now goes to the glass every frame, however small, one with nothing to show lying inside the head: the OS's glass is handed its pieces by their place in the list, and a trail drop coming and going as the drop joined a bubble re-formed the whole of it. Drops run in up to 12 carried pieces with their bubbles in the app's glass (was 4). Recorded in the sandbox: after a fast sweep the drop wobbles as it stops, its outline swinging as one surface; crossing between two bubbles and hovering stays still. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The eight-point give is gone (the user): it jittered badly over the drop zones and read as groups of orbs loosely springing together, not one drop of water. It was swung by the hand's acceleration, the noisiest thing a hand gives: a hand held still trembles, and eight coupled springs twitched with it, each twitch re-forming the merged glass. The trail is restored as it was before it, with every piece no longer sent whatever its size, and drops run in with 4 carried pieces again. In its place the drop sloshes as one: the trail's length is on a soft spring (2.6 Hz, damping 0.32 at playful, critically damped where motion is minimal), driven by the hand's pace, which is smooth. As the hand stops, the trail swings past the head, the drop bulging out ahead along the way it was going, and back, before it settles. It lies along the way it last lay while it does. - A hand slower than 90 points a second draws no trail (`trail_still`), so tremor through a held press drives nothing. - Joined to a bubble, the trail pours into the head as before and its spring settles at once. - The hand's pace is taken up faster (0.04 s), so a stop is a stop the spring can swing from. Recorded in the sandbox: a moving drop carries a smooth teardrop behind it, which swings to the other side as it stops or turns before the drop rounds out. Hovering on a bubble with a simulated ±1.5-point tremor, the trail drew nothing; what changed was only the head following the pointer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a drop, for a frame or so the new float's title flashed, folded back on itself (the user). The drag's glass lives in its overlay, a popup-level window over every other, and the new float's window opened beneath it, in the very place the drop zones it was let go over were still going and the growth was still in the glass. Their lens bent whatever of the window showed through: recorded, a drop zone shrank away in the middle of the new window for several frames after it had taken over. A float born of a drop now goes over the overlay, at its level, for as long as the overlay holds glass (`Popout.liftOver`, `viewports.lift`), and back at a window's own level once the overlay is gone (`viewports.settle`). The growth, the zones going and the end of the trail all pass beneath the window, seen through its glass, never bending what it shows. The growth's picture stays over the window as it fades out. Recorded in the sandbox: once the window has taken over, nothing of the drag's glass lies over it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The jitter in the drop zones' joins came back with the sloshing trail (the user): we are done with springs that swing. Whatever moved the drop's glass on its own near the bubbles re-formed their joins under a hand held still: drops chasing one another, eight points round the rim, a sloshing trail, and the head's own spring swinging past where it was going. - The head follows the hand critically damped: eased, arriving without swinging past. - The trail's length is the hand's pace, eased (0.07 s), with no spring of its own. - A hand slower than 90 points a second still draws no trail. The drop's glass now changes only when the hand moves. Measured in the sandbox, hovering on a bubble with a simulated tremor: the glass is handed a change once for each step of the hand and never between them, and not once in the 1.4 s it is held still after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Out of a fullscreen Space the main window still sat fully opaque until the mouse moved over it or something else woke the app (the user). It covers the desktop through the whole way out (`windowCovers`), and lets go of that on AppKit's and SDL's own time, after the transition and the 40 frames pumped after it: whichever flag is last to settle, nothing woke a frame when it did, so the fade (`easeWindowOpacity`) never began. From the moment the window sets out of the Space (its chrome showing again), frames now keep coming until it no longer covers the desktop, at most 4 s (`space_exit_watch_s`). The fade then starts as soon as it may, and wakes its own frames from there as before. Not tried in the sandbox: a fullscreen Space there takes over the screen the user is working on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… main one A popped-out float over the main window went behind it at a press on the main window's title bar, as the user expected, and came back in front of it at the next press anywhere on the main window, so the part of the main window under the float could not be worked in (the user). The float windows were kept over the main one: its ordering swizzled to put them back, a local mouse-down monitor and a became-key observer doing the same after every press. All three are gone. A float's window is put over the main window once: as it first shows, since SDL orders a window it shows without activating it below the key window; and every float's as the main window shows, is restored, or is first focused, the app activating at launch bringing it over the floats restored with it. From there a float's window stacks as any window does: a press on the main window brings it in front of the floats, a press on a float brings that one forward. A new float coming up no longer pulls back the ones the user sent behind. Windows still owns float windows to the main one (always above it); macOS only here. Not tried in the sandbox: a demo's presses are dvui's, and do not reorder the OS's windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er its snapshot The OS's frost was laid over the lens at a share of its opacity, so the sharp picture came through it: lightened, not blurred (the user, against the app's own glass on pixi's bubbles, which blurs). And the carried drop's snapshot, drawn faint over a clear lens, made it hard to tell what was being carried. - The overlay's frost is whole where it shows, faded out toward each piece's rim by the mask it already had, so a bubble's middle is a blur of what is under it and its rim the clear lens. - Each piece takes as much of it as it says (`core.native_glass.Shape.frost`): a drop zone's bubble, carrying an icon, the whole blur; the carried drop none. - The carried drop's snapshot goes beneath the OS's glass (`core.native_glass.publishUnder`): a window of its own just under the overlay, the picture on the window's colour, opaque, rounded to the head, so the drop's lens bends it as glass over a picture does (`Popout.photoFrame`). The drag hands it over (`ViewDrag.photo_under`) rather than drawing it over the glass. - The drop crops its picture round what the view shows (`ViewDrag.contentFocus`, read once from the capture's pixels at lift: the middle of everything it drew), not its middle: Files, whose rows sit at its top, showed nothing. Not seen yet: the display was asleep, and nothing renders then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wed to its icon and name A file row lifted in the explorer was the tree's own drag — a floating row in the app's frosted glass — until the pointer left the tree, and only then the app's view drag, in the OS's glass where that is on. Now it is handed to the view drag on the frame the tree's drag begins: one drag and one glass over the tree and off it. Over the tree the tree reads it as it already read a file carried back (`carriedOver`): the rows faded in place, the line or folder it would go to, a release there moving it — the selection with it. It grows out of the row's icon and name (`rowContent`), not the whole row, and is carried as only what it shows: the width floor the pill kept from a lifted row is gone. Rows lifted together grow out of their icons and names together, so they run into the one pill. A loose drag keeps its middle where the middle of what was grabbed was (`ViewDrag.grab`), so the name stays where it was in the row; grabbed off what it shows — out along the row past the name — it is brought in under the pointer as it grows, the pointer kept off its very end. Folders, and files nothing opens, are still the tree's own drag: the view drag carries only a surface or a document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… bubbles a plain blur The bubbles' joins shimmered again under a hand held still: the drop eased after the pointer, so every tremble of it moved the drop a little, and the OS re-formed the join each frame; a tremble fast enough drew the trail out; and a pointer near the line between two bubbles flicked which one lit. The drop now moves only as the hand does (the user would rather lose the motion): `holdHand` keeps it where it is until the pointer is `hand_still` (3 points) from it, then drags it along at that distance — nothing eases, nothing swings — and the drop aims from that held point too, so the bubble it is joined to does not change under a tremble either. The pull toward the bubble it is aimed at comes in by time, not by the hand. The trail is gone, and with it the landing drops a float grew from; the float still grows out of its budding drops. Recorded held still for three seconds joined to the middle bubble: no pixel of the glass changes. The carried view's photograph beneath the drop's lens never showed: `photoFrame` asked for "no material" with `carryShape(null)`, which hides the window. A carry window can now be bare (`viewports.carryBare`: its material taken out, no shadow), and is rounded to its shape whether it has a material or not — bare, its square corners showed round the drop. The bubbles' body is a plain blur over the clear lens, not the OS's frost. Measured, the frost is a material, not a blur: it pulls whatever is under it ~29% toward one mid grey and erases detail — a bubble over the dark window's colour came out a lighter grey disc, over a canvas a flat one (the user); every frost variant, and every vibrancy material, does the same or worse. Core Animation's backdrop layer with only a Gaussian blur on it (private, as the OS's materials are built from it) blurs what is behind the window and keeps its colour: a bubble over the window's colour is that colour, a canvas under one shows through softly. `overlayBlurLayer`, one view sized to the frosted pieces each frame through the frost's own edge fade, `core.glass_look.drop_blur` points (2 at the bottom of the slider, 6 at the top of its roughness). Where the OS has no such layer, the frost as before, now three quarters over the lens rather than whole (a muddy disc) or half (a bloom), and reaching nearer the rim (`drop_bevel`, 22% of the radius). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he overlay round its glass The carried view's photograph under the drop's lens was a window of its own, presented every frame of a drag, and asked the window server where it stood each frame: a third window for the compositor. It is now an image in the overlay itself, under the glass (`overlayPhoto`, `viewports.overlayPhotoImage`/`overlayPhoto`): its pixels — already read at lift to find what the view shows — handed over once a drag, after which it only moves, in the glass's own transaction. The lens over it bends it, as it did the window. Fitted to what it shows (`ViewDrag.photoFit`): the content's box (`contentBox`, was only its middle) as wide as the square inside the round head, its top at that square's top — Files' rows across the drop from its top rather than a strip of them lost in its middle (the user) — never enlarged. And frosted as the bubbles are: the same plain blur, on the picture at the size the view would be under a bubble (a Gaussian filter on the image, not normalised at its edges, which over a picture mostly clear turned everything clear black). The overlay covered the whole display, and its picture — cleared, drawn and composited every frame of a drag — held drags to 60 frames a second where the display has 120 (the user saw 40 in a Debug build). It now covers its glass, with room to spare (`overlayArea`): moved only when the glass reaches past it or it is far bigger than the glass needs, in the same transaction as the glass. Measured in the sandbox, three runs each: 60–61 fps through a drag before, 120–121 after, fast swings across the window included. The bare carry window the photograph had (`carryBare`) goes with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rop drawn to them sooner The carried view's picture under the drop's lens shimmered as the drop moved, even a little (the user). Two causes. Its glass goes to the OS only once it has moved half a point (that keeps the joins still), and the picture moved every frame: it slid about inside its lens. Now it is held to its drop's glass by the same rule (`viewportOverlayPhoto`). And the picture is the view shrunk several times over, sampled afresh at each fraction of a pixel it moved: now mipmapped (trilinear minification). Recorded drifting slowly, the picture stays still in its lens. It stays the drop's disc, following it, under every bubble: where the drop runs into one, that bubble's lens bends it and its blur takes it — the bubbles' backdrop blur takes what is in the overlay under it, measured — without the picture taking the bubble's shape (tried, and it did not work with the merging: the user). Joined, less of it fades (`aim_fade_under`), so it reads through the bubble it is joined to. More attraction near the bubbles (the user, against the web's): a drop aims at a bubble a little before it touches it (`DropZones.disc_reach`, 1.2 of their radii together), is drawn further into it (`drop_pull` 0.55, was 0.45) and sooner (`aim_s` 90 ms, was 150), and before that leans toward the nearest bubble as it comes within `lean_reach` of it (`drop_lean`): all by where the hand holds it, or by time — held still, nothing in the glass moves, recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…explorer's headings are The pull into a bubble felt soft beside the app's own glass on the web (the user), where the drop was on a spring that carried it past the bubble and back. Recorded side by side in one build, the two glasses draw the same frames: what was missing was the pour, not the OS. The drop is now drawn into a bubble it is newly aimed at — and over to another, and let go of one — as one motion from where it was (`pull`, `aim_at`, over `join_ms` at the user's motion speed), past the bubble and back as far as motion is playful (`pour`), let go smoothly. Time alone moves it: the hand held still sets nothing going, and none of the springs that shook the joins under a trembling hand come back. Recorded held still against bubbles: nothing in the glass moves. The drop's name is in the heading font, uppercase, as the explorer's headings are (`Chooser.label`), in the window's text colour (the user). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…over the frost; floats under the main window cover nothing of it The carried drop was a clear lens over its snapshot on the view's own ground: a disc unlike the bubbles it is carried among (the user). It is now their glass — frosted, a plain blur of what is behind it — and the snapshot is drawn over that frost (the overlay's picture layer moved above the blur): what the view shows and nothing of its ground, crisp, a few points inside the rim, as a bubble's icon is on its own frost. Its name over it as before. A view let go over its own place's middle pops out into a float, which grows out of the drag's glass lifted over it (`fizzy_macos_viewport_lift`) — and dropped back to a window's level once the glass had gone, it went under the main window (the user). It is put over the main window as it settles (`fizzy_macos_viewport_settle`), as a window just opened is. Tested by tape both ways: under without it, over with it. And a float's window under the main window — clicked behind it, since floats stack as any window does — was still taken by a drag as lying over the main window's places: their drops were cut to the part clear of it, a strip along the top over a single place, a drop off in a corner of a split (the user: drops no longer anchored to their places). The backend reads which floats' windows lie under the main one once whenever the stacking may have changed — a window shown, hidden, or come forward (`stack_stale`, `Viewport.under_main`), not each frame — and such a float covers nothing of the main window: not for its places' drops (`ViewDrag.mapOccluders`), and not for where a held pointer over the main window is read (`heldPoint`). Where its window still shows, past the main window's edge, it is read in its band as ever. Checked by log with the float sent behind: read under, and covering nothing at the lift. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Let go as a float, the drop's snapshot went at once and the glass grew empty into the window (the user): where the OS draws the drag's glass, the growth drew its picture into a carry window it then hid (`carryShape(null)` hides; it was meant as "no material"). The snapshot now grows with the window in the overlay, from the image the drag already handed over (`grow_photo`): from where it lay in the drop (`ViewDrag.photo_last`, `Floats.Landing.photo_from`) to the window's top left at the size it was taken — its heading where the window's header is — clipped to the growing glass, and going as the window comes in over it. The overlay keeps the image past the drag (`hidePhoto` only hides it): the frame the drop is let go shows none of it, the frames after grow it. Under the OS's glass the hidden carry window is no longer drawn or reordered each frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at opens as its place was The drop's snapshot was the whole width of what the view shows shrunk into the drop — Files' tree at a third of its size — and it was hard to tell what was carried (the user). Where what it shows is too wide to read so, the drop shows the square of it that reads at `photo_zoom_least` (0.6) with the most drawn in it — of those with nearly the most, the highest and then the furthest left, as a view reads: its heading and first rows, an editor's first lines (`focusBox`, graded once at lift on a coarse summed grid of the picture's pixels). Let go as a float with its picture, the view opens as its place was — its size, and where it was when let go over that place (`float_rules.asTaken`), not a share of it centred there — so the picture growing with it out of the drop lands on the view as the float shows it; the picture frosts over as the glass grows round it and clears as the window comes in (the user: expand to the size the popped out region was, aligning with its snapshot, which then unblurs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asked from a command, `toggleFullScreen:` ran inside a frame, and AppKit's will-enter, which it posts before returning, had the window monitor run a frame of the app nested in that one. dvui's state did not survive it: every later frame began on the nested one's, and the app slept in its wait through the rest of the transition. The enter took 8 to 9 s, the exit 12 s, and the window sat opaque after leaving full screen until the mouse moved. A monitor frame asked for while a frame is open now only wakes the loop (`SDLBackend.appFrameOpen`), and a frame run from a callback never waits after it presents (`callbackFrames`); it wakes SDL's loop when it wants another. AppKit pictures the window as it will be in full screen from inside `toggleFullScreen:`, and grows that picture to the screen. With a frame open, the picture was the frame then open: laid out at the old size in the corner of the full-size window, the real window popping in at the end. The toggle now waits for the run loop when a frame is open, as the green button asks, so the monitor draws the frame AppKit pictures. Out of full screen, AppKit drops the window's full-screen style at will-exit. The window now covers the desktop until did-exit (`windowInSpaceTransition`): opaque while AppKit shows pictures of it, then fading in place, where before it faded behind opaque pictures and popped in see-through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AppKit's own animation into and out of a fullscreen Space is of pictures: one of the window as it was and one as it will be, taken as the transition starts and cross-faded while they grow. The picture as it was showed through the growing one (its top edge above the full screen window until the end), and the window could not fade between translucent and opaque as it went, only the pictures moved. The window's delegate (SDL's listener; the methods are added to its class) now asks for the window's own animation (`customWindowsToEnterFullScreenForWindow:` and the rest), and AppKit takes no pictures: - The window moves itself, from where it is to the full screen frame and back to the frame it set out from, a step at the start of each of the app's frames (`fizzy_macos_window_space_step`), so it moves at the display's rate. The monitor's 60 Hz pump only keeps the app awake through it; a frame of its own between the app's waited on the same drawables and halved the rate. - Its opacity follows how far into full screen it is (`backend.spaceFullness`), and so does the title strip (`window_layout.titlebarStripAtFullness`, unit-tested): the fade, the content closing up, and the move are one motion. The traffic lights fade with it. - On the way out it takes its windowed shape at once (Apple's own custom animation clears the full screen style there), so it shrinks with its corners and title bar, not square. - It lands where AppKit lets a windowed window be (`constrainFrameRect:toScreen:`), and is held there for half a second: SDL puts its own style back at did-exit and fizzy's goes on again over it, each keeping the content rect, not the frame, and the window landed a title bar or a toolbar off where it set out from, a little more each round trip. - If AppKit gives up on the transition (it does when the app is not active), the window goes back at once, toolbar and all, rather than covering the desktop until the watchdog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w shrinks For a surface that scrolls itself (the settings pane, the plugin store), the explorer's scroll area stood aside by giving it a content size equal to its viewport. That size was last frame's: the scroll area decides on its bars from it before it measures itself, and while the window shrank, every frame of it, last frame's height overflowed this frame's and the explorer's vertical bar showed beside the pane until the window stopped. The pane was laid out at that height too, a frame too tall as the window shrank (its foot clipped) and a frame too short as it grew. Around such a surface the explorer's scroll area now shows no bars at all, and the surface is given this frame's viewport, set right after the scroll area measures itself and before it lays out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At will-enter SDL's listener puts its own style on the window (titled, the content not under the title bar), and AppKit keeps the content rect across a style change: the window grew by its title bar. The monitor's will-enter came after it and kept that grown frame as the one to come back to, so the window came out of full screen a title bar taller every time. The frame is now kept when AppKit first asks for the window's own way in (`customWindowsToEnterFullScreenForWindow:`), before AppKit or SDL touch it, with the title bar's height for the way back; at will-enter fizzy's style and that frame go back on, so the way in starts from the window as it was too. Traced over two round trips: in from 1421x860, out to 1421x860, twice, nothing moving it after it lands, 860 saved at quit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Each step of a window's own way into or out of full screen is taken in a Core Animation transaction that stays open until the frame drawn for it is presented (`present_hook`, new on the SDL backend; the layer presents with the transaction), so the window's new frame and its picture reach the screen together, as a float's window and its picture already do. - While the main window moves itself, a float's or a dialog's window keeps its last picture: AppKit hands their layers drawables back slowly through a Space transition, and waiting on them every frame halved the move's rate with one open (Debug, About open: enter 60 -> ~94 fps, exit ~100 -> ~114). - SDL puts its own style on the window at will-enter and did-exit (the content not under the title bar), and AppKit keeps the content rect across each change: the window jumped up a title bar and back on landing, a frame of it shown. A window the monitor follows now keeps its content under the title bar through any style it is given (`setStyleMask:` on SDL's window class, as `constrainFrameRect:toScreen:` already is), so those resets no longer move it at all. - The full screen frame on a display with a camera housing is the visible frame's top (1130 on a 14" panel), as AppKit makes it, not the inset's (1131): no point of a step at did-enter. Measured over back-to-back transitions (frames during each move): Debug 115-120 fps entering and 120+ leaving without a dialog; Release ~113 fps both ways with or without one, median 8 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The About dialog capped its window at 440x400, and its body had grown past 400 (the update status line, the spinner's slot): the window clipped the body at the cap, and the Close button, its last row, showed only its top half. Fizzy's other capped dialogs (the unsaved and file-changed prompts, the plugin update and file type lists) fit theirs today, but a capped height can only ever cut off the buttons under a body that runs longer — the lists cap their own height and scroll. All six now cap their width only, and their height is their body's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The carried view's picture lay sharp on its drop's frosted glass, printed on it rather than in it. It is now a touch out of focus (`glass_look.drop_photo_blur`, 1.25 pt), far less than the bubbles' backdrop (2-6 pt) so what the view shows still reads (the user: "a very slight frost"). Let go as a float, the picture's frost grows from there as the glass grows round it, rather than from sharp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A view dropped on its own place's middle floats into a window of its own, which grows out of the drop at a raised level and then settles to a window's own (`fizzy_macos_viewport_settle`). SDL had shown it below the main window (a window it does not activate goes below the key window), and as its level came down AppKit put it back there; the settle then asked `orderedIndex` whether it was already in front, which just after the level change still said so, and left it: the float opened, then went behind the main window. Coming down from over every window, it now goes over the main one whatever `orderedIndex` reads. Traced with a drop on the sidebar's own middle: SDL's show below the main window, the raised level read as in front, the settle's order above; on screen, the float over the main window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window's toolbar (hidden at will-enter) came back only after did-exit, so the frame that landed laid the content out under a title bar without it, 4 pt higher (the live inset 36, not 40), and the next frame dropped it back: a one-frame jump up as the window came out of full screen. The toolbar is given back as the way out starts, with the windowed style: the title bar's height the content keeps clear of is the windowed one from the first step to the last, and the window has its corners on the way. Traced over two round trips: the strip opens 10 -> 40 pt through the move and holds 40 across did-exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
force-pushed
the
claude/native-glass
branch
from
October 7, 2026 19:33
f9f953d to
dc7bd20
Compare
foxnne
marked this pull request as ready for review
October 7, 2026 19:34
…r rim The one-slider look (`glass_look.inApp`: Apple's lens, frosting and taking the window's colour up the slider) was published on every platform, so the web's glass, with no OS glass beside it to match, frosted and tinted at the default opacity and grew the lens's bright rim: it lost its glassy look and read as having a border (the user). On the web nothing is published now, and every field draws the earlier glass, `main`'s, as its own tint, lift and blur say (the shader's path for it is unchanged). The OS's Liquid Glass and the in-app glass beside it on macOS keep the slider's look. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Step 2 of the native windows plan (#226): a view drag's glass is the OS's Liquid Glass wherever it exists, every glass runs on one slider, and the windows go into and out of full screen as themselves. Floats as windows and native glass are on by default on macOS;
FIZZY_POPOUT=0orFIZZY_NATIVE_GLASS=0turns them off. The web and other platforms are unchanged.The drag's glass
One overlay. During a view drag one transparent window, passive and at the pop-up level, holds an
NSGlassEffectContainerViewwith a Liquid Glass view (the lens variant) for each piece of the drag's glass: every drop-zone bubble showing, in the main window and in float windows, and the carried drop or card. The app draws none of that glass while the overlay shows. The frame declares each piece tocore.native_glass, and the overlay places the matching views in the Core Animation transaction its picture presents in. The overlay is sized to its glass (a display-sized one halved drag frame rates to 60).How it behaves:
core.motion), never driven by the hand. It leans toward bubbles near it, and bubbles reach for it from further away.CABackdropLayerwith only a Gaussian filter), not the OS's frost material. Measured, every macOS material pulls what is under it toward a mid grey, which read as grey discs; the plain blur keeps the window's colour under a bubble.One material, one slider
The window opacity slider runs every glass, from clear refracting glass at 0, through frosted glass with its shine at a feathered bevel, to the window's colour at 1. Liquid Glass has no blur setting, so the overlay holds the clear lens and a frost over it and crossfades them.
core/gfx/glass_look.zig(std-only, unit-tested) maps the slider to the OS's glass and to the app's own glass on the same breakpoints.On the web, where there is no OS glass to match, nothing is published to the slider's mapping: the in-app glass stays the clear liquid glass it was on
main(the slider's frost, tint and the lens's bright rim had cost it its glassy look and read as a border). macOS keeps the slider's look for the in-app glass beside the OS's.On macOS 26 the main window and float windows stand on Liquid Glass placed beside SDL's view: the window's colour, the clear lens, and a body of frost, blur and colour that fades out across a bevel so the edge keeps refracting the desktop. They get a compact toolbar's 20.5 pt corners, with the frame kept when the toolbar comes and goes. Before macOS 26, windows keep vibrancy.
Floats are peer windows
A float's window stacks as any OS window does: it comes up in front when it opens, and the main window can come in front of it. A float let go from a drop comes down over the main window as its raised level settles, rather than behind it: AppKit put it back where SDL first showed it, and
orderedIndexstill read it as in front just after the level changed. A float lying under the main window covers nothing of a drag over the main window.Full screen grows the window itself
AppKit's own transition animates pictures of the window (before and after, cross-faded), not the window: the old window's edge showed above the full-screen one until the end, and the window couldn't fade while the pictures moved. The window's delegate (SDL's listener; the methods are added to its class) now asks for the window's own animation (
customWindowsToEnterFullScreenForWindow:and the rest):Fixed along the way:
toggleFullScreen:ran inside a frame, and the window monitor ran a second frame inside it. dvui's state didn't survive: the transition took 8–12 s, and the window stayed opaque after exit until the mouse moved. A monitor frame asked for during an open frame now only wakes the loop, frames run from AppKit's callbacks never wait for events, and the toggle waits for the run loop.Measured over back-to-back transitions: Release about 113 fps both ways, with or without a dialog open; Debug 115–120 without one.
Also
Safety
Liquid Glass and its container are looked up at runtime. The private lens variant and pressed state are set only on macOS 26 and only where their setters exist;
CABackdropLayerlikewise, with the public frost behind it. Without them the app's own glass draws as before. The delegate methods for full screen are added only where SDL's listener doesn't define them, and a window the monitor doesn't follow keeps AppKit's animation.Verified
zig build,zig build test,zig build test-integration(28/28 steps, 292/292 tests),zig build check-web,zig build test-sdk-version, and cross-builds for Windows and Linux, on the top of the stack.Not verified
Next
🤖 Generated with Claude Code