Repository navigation
Releases: PlugFox/g1455
Release list
v0.3.0
A minor version rather than a patch, for two changes in behaviour: under an
adaptive host the tab bar's accessory now wears the bar's branch instead of
its own, and GlassLoad counts only glass that draws. Existing code builds
unchanged.
Adaptive glass:
- The tab bar adapts, as one control. It did already, by accident, because
the bar is aGlassBar, while the guide said it did not; its accessory, a
GlassBarof its own, read its own backdrop, so a light bar could sit
under a dark accessory as rows scrolled by. The accessory now wears the
bar's verdict, and the two cross between branches together. The guide,
the tab bar page and the README say so (#10). - The
GlassBackdropReadingexample, the site's "Content on the glass" and
the skill picked an icon colour fromreading.brightness, which switches
at a different level from the glass: over a mid grey in the light
appearance that is a white icon on light glass. They give
GlassTheme.of(context).legibility().labelfor a colour now, and keep
brightnessfor content that follows the backdrop itself (#11).
Cost:
GlassLedger.readleaves glass that draws nothing out of the load. A
control's drop at rest is a registered surface atmaterialize0; it
paints no glass and the host captured nothing for it, yet the ledger
counted it, so twenty resting switches read as twenty captured surfaces
and twenty surfaces of fragmentation excess. They are
GlassLoad.restingSurfaceCountnow, and in none of the other counts or
areas (#12).GlassSurfaceRecord.drawsandGlassSurfaceRecord.captured: the one test
the host's capture and the ledger both use, where each kept its own.
Fixes:
- A
GlassSurfaceremoved under a held finger no longer asserts on the rest
of the gesture: the binding keeps routing it to the targets of the down's
hit test, and the surface transformed the position while detached (#13,
by @melo936). GlassTabBarandGlassSegmentedControlremoved under a held finger — a
route popped, a list recycled the row — ignore the rest of the gesture.
They moved animation controllers they had disposed, which asserts in debug
and throws a null check in release. The button, switch, slider, stepper
and page control are tested the same way and were already safe.- A tab bar's accessory beside the collapsed circle keeps its lower rim. On
the side-by-side layout the bar is 44 tall, and a 48-tall capsule centred
on the circle hung 2 px past the bar's box, which clipped its bottom edge
and corners. Beside the circle it is no taller than the circle now (#9). - A surface that replaces another at the same rect draws. The host's
oracle watches rectangles and a frame finds its slots by identity, so a
swap that moved no rectangle, behind a repaint boundary of its own, held
the capture and the newcomer drew nothing until something else changed —
GlassMorphsettling into a plain surface did. The host now retakes when
the set of surfaces it captures for changes. The same check catches glass
that comes back to the top rung at the rects it left. - The capture walk counts a colour filter under an
OpacityLayerthat
arrives throughpushLayer, as it did under a composited one, and sends
that subtree to the stock capture. FilteringCanvasdrops a mask-filtered paint on every draw that takes a
paint — points, images, vertices, atlases — not only on the shapes, as
dropMaskFilteredis documented to.GlassMenuAnchor,GlassPopoverAnchorand aGlassPageControlgiven a
PageControllercreate their animation controller ininitState. Never
opened, or never animated, they created it for the first time in
dispose, which Flutter tolerates only from 3.47.- A
GlassMenuControllergiven to aGlassMenuAnchoror a
GlassPopoverAnchorin a rebuild drives it. The anchor kept the
controller it was built with for life, so the new one'sopenand
closedid nothing; taken away, the anchor's own takes over, and an open
panel stays open. - A
GlassPageControlwhosePageControlleris taken away shows
currentPage. Its own position stood still while the controller led, so
acurrentPagemoved meanwhile was not shown, nor told to a screen
reader, until it moved again. - The capture walk keeps its count of open opacities when a painter throws
insidepushOpacity, as it does inpushLayer. The count stayed raised,
and every colour filter later in the pass was sent to the stock capture. GlassBadge,GlassScaffoldandGlassTabBarbuild without an ambient
Directionality(#8).- The site's service worker precaches the shaders with the code that writes
their uniforms: a returning browser ran the cached code of its last visit
against the new shaders, and a press drew the glass black (#8).
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...
v0.1.4
showGlassSheet(barrierDismissible: false)is a sheet only its content
closes, as UIKit'sisModalInPresentation: a drag or a flick used to close
it whatever the flag said. Now a pull gives a quarter of the way, no more
than half the sheet's height, and springs back; the dim and Escape were
already held by the flag.
Documentation:
- The API reference is grouped into topics, each with a page of its own:
Getting started, Foundations, Panels and controls, Modals, Composition,
Capture control, Cost and policy, Diagnostics. Every public name has a doc
comment, the main widgets an example and a list of what to see next with
their page on the site, anddart docreports no unresolved reference. GlassProxy, the way to tell the capture what a subtree is, is in the
README at last: a stand-in for a video, a map or a platform view (which
record nothing, so the glass over them showed a hole), a subtree left out,
an opaque cover, a blur the shadow filter keeps. The site has a page for it,
live: Capture control.- Every measured number now says where it comes from: the devices, their
systems, the renderer, the metric, the dates, and the Flutter and engine
revisions, in the README and on the site's How it works. - An agent skill for Claude Code, Codex, Cursor, Antigravity, Gemini CLI and
Copilot, inskills/g1455/: the rules for writing glass that works, with
every page of the site as a reference.npx skills add PlugFox/g1455, or
/plugin marketplace add PlugFox/g1455in Claude Code. The site publishes
it at/.well-known/agent-skills/(npx skills add https://g1455.plugfox.dev), withllms.txt,llms-full.txtand every
page as markdown at its address plus.md. - pub.dev shows a square, still picture of the package: its icon leads the
screenshots, ahead of the loops, which it showed as one frame. pub.dev takes
ten, so the materialize loop makes room; the alert's shows the same arrival.
v0.1.3
- The README's images show on pub.dev again. 0.1.2 linked the banner, the
showcase loops and the How it works cards by relative path, and pub.dev
drops a relative image; they are linked from GitHub again, as in 0.1.1.
v0.1.2
Glass that reads its backdrop:
GlassHost(adaptive: GlassAdaptive())tells eachGlassBar,GlassCard
andGlassButtonthe mean level of the captured backdrop inside its own
box, and the glass picks the branch of.regularand its label from it: a
bar over a photograph's sky and a button over its shadow each wear the
branch Apple's material would. Off by default and free when off. On, a
frame that captures reads back a 4 × 4-pixel cell per surface,
asynchronously, at most once perinterval(250 ms, a second on the web,
where CanvasKit reads back synchronously), and a frame that keeps its
capture reads nothing. A band of 12 code values and a hold of 600 ms keep a
glass near the threshold from flickering; crossings tween over 300 ms and
cut under reduced motion. A finish a component, the host or a theme names
is kept, and only its label follows; underrichBackdropthe label stays
chosen against every backdrop.GlassTheme.of(context).readinghands the
verdict to content on the glass. A rawGlassSurface, the scroll edge, the
tab bar and the segmented control do not adapt yet.
Motion:
GlassMorph: swap the child and the glass flows to its size, the way a
button becomes its menu. It measures the child;widthandheightpin an
axis, andalignmentnames the point that holds still. While it grows it
is a two-shape union, a body on a spring and a capsule running ahead, so
the outline has a neck. At rest it is one plain surface, and a settled
morph is that surface pixel for pixel.GlassMorphMotion.fluidand
.calm; under reduced motion it changes at once. A morph is a capture per
frame, unless aGlassTravelaround a fixed-size ancestor holds it.- The held drop in the switch, the slider, the segmented control and the tab
bar stretches as it sets off and squashes as it stops, from its
acceleration, and springs back round; at rest and at a constant speed it
keeps its shape.GlassHost.dropMotionorGlassThemeData.dropMotionsets
it for the app, and each control'sdropMotionoverrides it;
GlassDropMotion.noneturns it off, and so does reduced motion. It costs no
capture: the drop deforms inside the region it already travels in. It does
repaint the drop's own layer on the frames it deforms. GlassTabItemtakes aniconBuilderand alabelBuilder, handed the
colour the bar draws the item in, so an SVG, an image or a badge matches its
neighbours. Breaking for code that reads it:GlassTabItem.iconis now
nullable, soitem.iconused as anIconDataneeds a null check.
Start-up and scaffolding:
GlassHost.precache()compiles the package's shaders before the first
frame. Call it inmain()afterWidgetsFlutterBinding.ensureInitialized(),
and the first frame that has a capture is drawn through the optics rather
than as the plain blurred backdrop. It shares the loads a host starts on its
own, so it never compiles a program twice;group: falseand
ripple: falseleave those out.GlassScaffoldis a screen wired the way the README recommends: a host when
none is above it, a top bar in a softGlassScrollEdge, an optional bottom
bar and floating action, and a body that scrolls under the bars and is told
their extents throughMediaQuery.padding.
Fixed:
- A shader that fails to load no longer escapes as an uncaught error. A host
or a ripple that waited on it reports it throughFlutterError, and the
failed load is forgotten, so the next host orGlassHost.precache()tries
again rather than being handed the same failure. - On the web, a painter that throws while a glass layer records no longer
leaks the shaders it drew with before the throw.
The package page and the README:
- The package page links the site as its documentation, names what is in the
box in its description, and addsglassto its topics. - The README opens with a loop of a whole app screen, shot headless like the
tiles bytool/showcase.sh screen. How it works is four cards. The quick
start is a whole app that runs as pasted. New sections give the values of
everyGlassFinishpreset, five common patterns, and what is not here and
why: no dispersion, because Apple's material has none to measure (D103),
and one shape,RSuperellipse. A test keeps the README's code identical to
files that are analyzed and pumped, and its finish table equal to the
constants. Every image is linked relatively.
v0.1.1
What a screen reader is told and can do:
GlassSwitchandGlassSlidertake asemanticLabel. A screen reader steps
GlassSliderwith increase and decrease, bysemanticStep(a tenth by
default), throughonChangeStart,onChangedandonChangeEnd, and never
past either end.GlassButtontakes asemanticLabelthat is read in place of its child,
for a button that is only an icon.showGlassSheettakes abarrierLabel, asshowGlassDialogalready did.
GlassMenuAnchorandGlassPopoverAnchortake one too. Their barrier used
to be a tap target with no label, so a screen reader had no way to close a
popover.
On a page:
showGlassSheettakesconstraints, asshowModalBottomSheetdoes. A
maxWidthkeeps the sheet at its content's width, centred, on a window
wider than that. Null, it spans the window as before.- The labels of
GlassButton,GlassSegmentedControlandGlassTabBarare
not text to select. Under aSelectionArea, a drag across the page used to
take them along with the prose around them.
Fixed:
- Glass draws on CanvasKit, which is every Safari and every browser without
WasmGC. CanvasKit keeps a fragment shader's uniforms by reference until the
picture is rasterized, and the glass released its shaders straight after
the draw. So it drew from freed memory: a shape in the wrong place, a solid
grey or green slab, a different one each frame. On the web a shader now
lives as long as the picture drawn with it. - Glass standing on glass no longer moves what is under the glass it stands
on. When a level was captured, the walk painted the levels below it, and a
CompositedTransformTargetamong them wrote the walk's offset into its own
liveLeaderLayer. ASelectionAreacreates one. So with a popover open
over a bar, the bar's text jumped right and down, and it stayed there until
the bar repainted. The walk now puts back every live offset it touches. - A
GlassGroupdrawn in tiles no longer loses members on the web. Skwasm's
fragment shader keeps its uniforms by reference rather than copying them on
each draw, so every tile drew the last tile's members, and blobs flickered
in and out. On the web each tile now gets its own shader. - A platform view or a
Textureis a hole in the capture of an upper level,
as it already was in the base level. It used to be a grey stub. On the web
everySelectionArealays a transparent platform view under what it holds,
so a bar over a selectable page showed grey.
Documented:
- The README says what the glass costs on the web outside Chromium — Safari,
Firefox and every browser on iOS, Chrome for iOS included, get CanvasKit,
where each capture is a synchronous GPU readback — and what to do about it:
allow Skwasm on WebKit, or declare a cheaper rung where CanvasKit remains.