Repository navigation
v0.2.0
A minor version rather than a patch: new components, new parameters on the
sheet, the tab bar and the controls, and a declared backdrop. Existing code
builds unchanged; the one layout change is in the ripple shader's uniforms
(see Performance). Three defaults behave differently: a pressed GlassButton
swells (GlassPress.none turns it off), a sheet stands on the keyboard
instead of under it, and a switch, slider or segmented control inside a
horizontal scrollable takes a horizontal drag from the page.
A backdrop the application declares:
GlassBackdropdeclares what is behind the glass in a subtree, and that
glass samples a texture made from the declaration instead of a capture of
the screen:GlassBackdrop.color,.image(anImageProvider, placed by
fitandalignment),.texture(aui.Imagethe application holds) and
.gradientand.painter(anyGlassProxyPainter). The texture is
made once per finish blur and size, through the same shader and the same
blur arithmetic as a captured slot, and the host leaves those surfaces out
of its capture: a screen whose glass is all declared takes no snapshot.
Over a declared colour the glass draws what it draws over a captured screen
of that colour, to 2 code values.- Scoped to the subtree, innermost first:
GlassBackdrop.livehands a part
back to the capture, so a page can declare its wallpaper and still let the
bar over its list refract the list. The widget paints what it declares under
its child unlesspaintBackdrop: false, and an image that has not loaded
leaves the glass on the capture until it has; so does a replaced image,
with the old one still painted until the new one arrives. - Glass over a declared backdrop is ordinary content for the capture of other
glass, as a cheap surface is: a captured bar over declared cards shows them.
Fused groups sample the declaration too, and a fused member is left out of
the capture exactly when its group is, whateverGlassBackdropsits between
them. A draw holds the texture it was recorded with, so glass that moves
without repainting keeps drawing while other glass under the same
declaration asks for new blurs, and no texture is made while a frame is
composited. Adaptive glass does not read a
declared backdrop yet, and falls back to its declarations. RenderGlassSurface.paintsWithDeclaredBackdrop,
RenderGlassGroup.paintsWithDeclaredBackdrop,
RenderGlassSurface.readsDeclaredBackdropand
GlassBackdropDeclaration.renderssay what was sampled and how many
textures were made;GlassSurfaceRecord.declaredandreadsCapturesay
what the ledger priced.
Components:
GlassStepper: a minus and a plus in one glass capsule, asUIStepper. A
press steps at once; held, a half repeats after
kGlassStepperRepeatDelay(500 ms) everykGlassStepperRepeatInterval
(100 ms), and the half that would pass a limit is disabled unlesswraps.
One surface: a press, the repeat and a glyph dimming are drawn inside the
glass and are no capture. A screen reader hears one adjustable control.
Under right to left it is mirrored, with the minus at the end.GlassPageControl: page dots on a glass capsule, asUIPageControlon its
platter. It follows aPageControllerwithout a build and turns it; a tap
moves one page toward the side tapped, as iOS's does, and a drag scrubs.
One surface. APageViewbeside it is retaken while it scrolls (43 captures
a swipe in the package's tests), which belongs to the view: the same 43 with
dots that stay put. Under right to left the first dot is at the right, and
acountthat shrinks below the current page clamps it.GlassSearchBar: the glass search field with a clear button while there is
text, and a Cancel that slides in while it has the focus. One surface, the
field's; typing and the clear button are no capture. Cancel's slide narrows
the glass, and that is a capture a frame forcancelDuration(16 over
250 ms at 60 Hz, each way), one frame under reduced motion.GlassBadge: a count, a label or a dot on the corner of an icon or a tab,
as an opaquesystemRedcapsule. Not glass, as iOS 26's badge is not; it
costs no surface, and a count changing on glass is a repaint inside the
glass.- Their sizes (
kGlassStepperSize,kGlassPageDot,kGlassPageDotGap,
kGlassPageControlHeight,kGlassBadgeHeight,kGlassBadgeDot) are
layout, not Apple measurements: none of the four was among the controls
measured. The stepper, the page control and the search bar are laid
out at leastkGlassMinTapTargettall.
iOS 26 behaviours:
- Sheet detents:
showGlassSheet(detents:, initialDetent:, onDetentChanged:)withGlassSheetDetent.medium(the content's height, as
before; Apple's is half the window) andGlassSheetDetent.large(the whole
height below the top safe area less 8, edge to edge). A drag between them
follows the finger and settles at the nearer, or where a flick was heading;
a tap on a settling sheet, or on a button in it, leaves the settle alone. - A large sheet is
largeFinish, by default the finish with its tint at full
alpha. Once it is all the way up it is drawn onGlassTier.cheapand reads
no backdrop: headless, with a glass bar under it, one surface of two
captured instead of two, and 20,608 px² of capture against 337,408. The
other way: the sheet is then content to the capture of the glass around it,
so a change inside it is a retake (1 against 0). A translucent
largeFinishkeeps it glass. GlassTabBar(minimizeBehavior: GlassTabBarMinimizeBehavior.onScrollDown)
collapses the bar to a circle of the selected tab's icon when the content
scrolls down, and expands it on a scroll up or a tap on the circle, which
selects nothing. The scroll is read by aGlassTabBarMinimizerabove both
the scroll view and the bar;GlassScaffoldis one. The collapse moves
inside a declared travel region: no record over 60 frames, against 60
without the declaration. Collapsed, a screen reader hears one button named
for the selected tab.GlassTabBarMinimizer.notificationPredicate(and
GlassScaffold.minimizeNotificationPredicate) lets a list one level down,
in aPageView,TabBarVieworNestedScrollView, collapse it, and the
direction is read on the screen, so areverse: truelist collapses it as
any list does.GlassTabBar.bottomAccessory: a glass capsulekGlassTabAccessoryHeight
(48) tall above the bar, which moves down beside the collapsed circle. The
bar's box keeps its height, so the body is not laid out again for it.- The gap above a large sheet, its radius, the circle's size, the accessory's
height and gaps,kGlassTabMinimizeScroll(12) and the springs are layout
taste: none was measured on Apple's devices.
Interaction:
GlassPress: a pressedGlassButtonswells by 12 px on its longest side
and leans up to 3 px toward a dragging finger, then springs back. Declared
onGlassHost.press,GlassThemeData.pressorGlassButton.press;
GlassPress.noneturns it off, and so do reduced motion and a disabled
button. A feel rather than a measurement: Apple's press was not measured,
and 17 px, another package's number, would be 1.39× a 44 px button. It
costs two captures a press (touch-down and settle) and nothing at rest,
because the region it grows in is declared only while it moves; declared
always, it doubled a 44 px button's captured area (3,600 to 7,056 px²).
Toolbar cells take the focus but do not swell: the group is one surface.GlassSlider.divisions: every input, a drag, a tap, a key or a screen
reader, lands on a stop, andonChangedis called only when the stop
changes.- The switch, the slider and the segmented control drag from touch-down.
Inside a horizontal scrollable such as aPageViewa horizontal drag that
starts on them moves the control and not the page, and a vertical swipe
still scrolls the vertical list around them, a carousel row's included. - The keyboard: the button, switch, slider, segmented control, stepper, page
control and toolbar cells take the focus (focusNode,autofocus); Space
and Enter activate, and the arrows step the slider, the segmented control,
the stepper and the page control. The ring
(kGlassFocusRingColor,kGlassFocusRingWidth,kGlassFocusRingGap) is
drawn inside the glass's own subtree, which no capture sees; a switch's or a
segmented control's ring under other glass costs one capture as the focus
changes. - Right to left: the switch is on at the left, the slider fills from the
right, the segmented control's first segment is at the right, the toolbar's
first item is at the right, the stepper's minus and the page control's first
dot are at the right, and the arrow keys follow.
SliderGeometry.fillEndtakes atextDirection. GlassConcentric: Apple's concentric corners as arithmetic, a shape's
radius as its container's less the inset, with a floor. The segmented
control's capsule (pixel for pixel what it was) and the focus rings use it.
Fixes:
- A dismissible sheet follows the finger: a 116 px drag moved it about
4.5 px. It closes past a third of its height or on a flick, and while it
moves it declares where it travels: one retake a drag instead of 16. - Under right to left, the segmented control's capsule sat under the wrong
label, andGlassButtonGroupbrightened the wrong cell. - A sheet stands on the keyboard, as an iOS sheet does: 8 px above it at
medium, on it at large. Its content sees no bottomviewInsets, so content
already padded by them is not padded twice. - A sheet popped while a finger drags it no longer pops the page under it when
the finger lifts, and a drag down from large after the content or the window
changed no longer jumps. GlassSlidersends oneonChangeStartbefore its changes and one
onChangeEndafter, carrying the last value it sent, for a tap, a hold, a
drag, a cancelled pointer and a slider disabled mid-gesture: a tap used to
end before it started, and a hold then a drag started twice.- A segmented control in a vertical list no longer selects a segment after the
list scrolled away under the finger. GlassButtonGroupthrew, or kept its ring on a cell that was gone, when the
focused or held item was removed.
Performance:
- The shaders' uniforms are packed into
vec4s: 19 slots to 9 for a surface
and a group, 24 to 12 for the ripple. Metal binds each declared float
uniform on its own every draw, where Vulkan binds one struct. The pixels are
byte-identical; the raster thread spends 0.9 to 1.4 µs less a surface draw
and 1.0 to 1.8 µs less a group draw, on macOS under Impeller/Metal. The ripple's
count,reachandlightmoved after its arrays, which matters only to
code that writes the uniforms by hand. Everyflutter testrun checks the
lane order against an unpacked copy of each shader, with a lane swap as the
control that it fails.
Platforms:
- The floor is Flutter 3.47.0 (Dart 3.13.0):
sdk: ^3.13.1shut 3.47.0 out
although the package runs on it unchanged, and CI now tests that exact
version rather than the newest 3.47 patch. Below 3.47 the package would need
code changes, and on Apple hosts that opt into SDF rendering it would draw
differently, so the floor stays there.
Documentation:
- "Misuse and common errors", in the README and on the site: each mistake
with what it looks like, the error text where there is one, the cause and
the fix. - The site has a page with a live demo for the stepper, the page control, the
search bar and the badge, and the sheet, tab bar, button, slider and
segmented control pages show detents, the collapse, the press, divisions,
the keyboard and right to left. The README has a pattern for a tab bar that
collapses on scroll, and the agent skill has the same pages and rules.