liquid_glass_widgets 0.9.6
liquid_glass_widgets: ^0.9.6 copied to clipboard
A Flutter UI kit implementing the iOS 26 Liquid Glass design language. Features shader-based glassmorphism, physics-driven jelly animations, and dynamic lighting.
0.9.6 #
๐ Fix โ GlassModalSheet interaction glow in full state #
Thanks to @yukinoaruu for PR #38.
- Haptic & glow suppression in full state:
HapticFeedback.selectionClick()and_saturationController.forward()were firing on every touch when the sheet was inSheetState.fullโ where the glass surface is fully opaque and neither effect is visible. Both are now gated on!isFull, eliminating spurious haptic feedback and redundant animation ticks. - Background glass hides when content glass is active: Added an
Opacity(0.0)on the backgroundAdaptiveGlasslayer whenexpandProgress > 0.98andmaintainContentGlassis enabled. Prevents "glass on glass" shader conflicts in Premium mode at full expansion. - Interaction glow threshold tightened:
GlassGlowpulse guard lowered fromexpandProgress < 0.98to< 0.9to match the existing saturation gate โ consistent behaviour across all glow signals. GlassModalSheetgeometry defaults refined:topBorderRadiusdefaults to56(wasnull),horizontalMarginto5.0(was8.0),bottomMarginto6.0(was8.0) for tighter, more native-feeling geometry.InteractionNotificationexported:InteractionNotificationis now part of the public API surface, enabling consumers to dispatch Smart Silence events from their own widgets.- Corner radius tuning:
GlassThemeHelpers.resolveAdaptiveRadiusvalues updated to 54 / 46 / 46 (Pro Max / Pro / Notch) for a more conservative, closer-to-system look.
๐งช Tests โ Coverage improvements (1,573 tests) #
Extended branch coverage across five previously under-tested subsystems. Full test count grew from 1,491 โ 1,573 (+82 tests).
0.9.5 #
โจ Feature โ Asymmetric corner radii & floating peek geometry for GlassModalSheet #
Thanks to @yukinoaruu for PR #37.
- Shader fix:
lightweight_glass.fragnow supports per-quadrant corner radii via a newuData6uniform (slots 24โ27). A sentinel ofuCornerRadius = -1.0enables asymmetric mode; all existing symmetric shapes fall through unchanged. - Clip gap fix:
ClipPathgeometry on the Skia/Web path is now aligned toRoundedRectangleBorder(circular arc) to match the shader SDF โ eliminates the sub-pixel transparent notch at sheet corners. - Peek geometry: Five new optional params on
GlassModalSheet/GlassModalSheetScaffoldโpeekWidth,peekHorizontalMargin,peekBottomMargin,peekTopBorderRadius,peekBottomRadiusโ for Apple Maps-style floating pill peek states. - Cleanup:
forceSpecularRimremoved fromAdaptiveGlass,GlassSheet,GlassModalSheet, andGlassModalSheetScaffold. The shader renders the specular rim natively; no migration needed.
๐ Fix โ GlassSearchableBottomBar dismiss pill focus & keyboard restoration #
The dismiss (ร) pill was calling FocusScope.of(context).unfocus() which left the FocusNode in a "previously focused" state. This caused Flutter to restore the keyboard on back-navigation, and made the first post-dismiss tap get swallowed by focus routing.
Fixed by:
- Replacing
FocusScope.unfocus()withFocusManager.instance.primaryFocus?.unfocus()in the DismissPill, fully clearing focus state. - The ร button now only dismisses the keyboard โ it does not collapse the search state. This matches the real Apple Music / Apple News behaviour where the search bar remains visible (unfocused/ready) after tapping ร. The caller explicitly collapses search by tapping the home pill or switching tabs.
onCancelTapfires first (before the unfocus) so callers can react (clear results, analytics, etc.) before focus is released.
A new onCancelTap: VoidCallback? on GlassSearchBarConfig gives callers a hook into the ร tap.
โจ Demo โ Apple Music mini-player refinements #
High-fidelity improvements to the Apple Music demo to match the real Apple Music app:
- Play pill visibility: The floating play pill now stays visible when the search bar is in the "search ready" state (keyboard dismissed). It only hides when the keyboard is actively up (
_searchFieldFocused), matching real Apple Music behaviour. - Dynamic icon colour:
collapsedLogoBuildernow shows the selected (red) icon in scroll-collapse mini mode and the unselected (white) icon when search is active, via a static_kTabsfield so tab definitions aren't duplicated. - Play pill positioning:
aboveBarBottomis now responsive to the bar's current height โ switching tocollapsedNavBarHwhen search is active so the pill doesn't drop excessively when the bar shrinks. - Play pill animates on search from mini mode: When search is activated from the scroll-collapsed mini state, the play pill animates from the mini gap position back to its full-width position above the expanded search bar, matching real Apple Music.
- Home pill restores full bar from any state: Tapping the home pill now always calls
_dismissMiniMode()when in mini mode โ whether arriving from scroll-collapse or from search โ scrolling to top and restoring the full 3-tab bar. - Library default preserved:
collapsedLogoBuilderin the library remainsunselectedIconColorโ the Apple Music colour logic is isolated to the demo'sGlassSearchBarConfig. - Multi-tab scroll fix:
_dismissMiniModenow uses_activeScrollController(per active tab) instead of hardcoding the home tab's controller, fixing a bug where tapping Radio/Library in mini mode would leave the bar stuck.
0.9.4 #
โจ Feature โ GlassSearchableBottomBar programmatic interaction callbacks #
Addresses two community-requested quality-of-life gaps for GlassSearchableBottomBar.
1. onBarTap โ tap-to-restore after scroll-to-hide #
A new onBarTap: VoidCallback? parameter on GlassSearchableBottomBar fires whenever the user taps anywhere on the bar. The callback is wired through a translucent GestureDetector wrapper, so all internal handlers (tab selection, search toggle, indicator drag) continue to work normally โ there is zero interference.
Primary use-case is restoring the bar after a scroll-to-hide animation that is managed in the caller's code:
GlassSearchableBottomBar(
onBarTap: () => setState(() => _barVisible = true),
...
)
When onBarTap is null (the default) no extra widget is inserted into the tree โ zero overhead.
2. onSearchFieldTap โ detect taps on the active search field #
A new onSearchFieldTap: VoidCallback? parameter on GlassSearchBarConfig, passed directly to TextField.onTap. Fires on every tap of the expanded search field body, including re-focus taps after the keyboard was dismissed.
Useful for navigating to a dedicated search screen, showing a suggestion overlay, or logging an analytics event without needing to own the FocusNode:
GlassSearchBarConfig(
onSearchToggle: ...,
onSearchFieldTap: () {
showSuggestions();
analytics.log('search_field_tapped');
},
)
Zero breaking changes. Both parameters are optional with null defaults.
0.9.3 #
โจ Feature โ GlassModalSheet system & rendering performance refinement #
Big thanks to @yukinoaruu for PR #33 โ a comprehensive and beautifully engineered contribution that brings a whole new class of interactive modal sheet to the library.
1. GlassModalSheet system #
A new, comprehensive modal sheet implementation supporting three interactive states: peek, half, and full.
- Physics-Driven Transitions: Spring physics for fluid, organic state changes.
- Asymmetric Geometry: Morphs from a rounded floating pill to a sharp-bottomed full-screen container using the new
LiquidVerticalRoundedSuperellipse. - Isolated Mechanics: Logic separated into a robust state machine (
glass_modal_sheet_state.dart) and physics handler (glass_modal_sheet_mechanics.dart) โ a clean architectural blueprint for future complex components.
2. Device-Aware Adaptive Radius #
An intelligent radius resolution algorithm that infers the ideal corner curvature from the device's physical safe area โ Dynamic Island vs. Notch vs. Android Home Bar โ automatically matching glass curvature to device hardware without manual updates.
3. Advanced Visual Feedback โ Pulse System #
A global pulse synchronisation system in the rendering layer allows GlassModalSheet to trigger coordinated saturation and lighting pulses during high-velocity interactions, giving the glass surface a "living", organic feel.
4. Smart Silence โ suppressInteractionOnChildren #
InteractionNotification support prevents the "double-reacting" artifact where both a button and the sheet scale simultaneously on a single tap. Child buttons/switches can seamlessly suppress the parent sheet's scaling and glow effects when tapped.
5. New shapes & LiquidStretch constraints #
LiquidVerticalRoundedSuperellipse: Enables asymmetric corner radii (top-rounded, bottom-flat) essential for the modal sheet's full-screen morphing animation.- Axis constraints:
allowPositive/allowNegativepivot support prevents the sheet from "collapsing" downward when dragged โ it only stretches upward as a tactile response.
Documentation & Testing #
docs/assets/GLASS_MODAL_SHEETS_GUIDE.mdโ comprehensive developer guide covering the full parameter surface and state behaviours.test/widgets/overlays/glass_modal_sheet_test.dartโ 679 lines of rigorous unit and widget tests covering state transitions, gesture arena logic, and physics edge cases.
Zero breaking changes. GlassModalSheet is additive โ all existing GlassSheet usages are unaffected.
๐ Fix โ Selected icon colour washed out by glass indicator #
A huge shoutout and thanks to @jfhair for spotting this issue and putting together PR #29 โ it was a fantastic catch, and you had exactly the right instinct on the fix!
The active-tab icon was visually muted ("dull") at rest because the AnimatedGlassIndicator glass lens was painting over the icon layer. Simply moving the indicator behind the icons restores vibrancy but kills the refraction effect โ the glass shader needs icons beneath it to warp them as the pill moves.
The fix uses a split-pass sandwich: the pill's solid background renders below the icons (full vibrancy at rest), while the glass shader renders above them (refraction preserved during animation). Both GlassBottomBar and GlassSearchableBottomBar are updated. Zero breaking changes.
๐ Fix โ GlassSheet specular rim artifact & washed-out inner elements #
Inspired by @yukinoaruu's work in PR #33, who introduced the forceSpecularRim flag and first surfaced this class of visual fidelity issue with the lightweight glass renderer.
The problem & fix #
On the Skia/Web (lightweight) rendering path, a refractiveIndex of 0.7 on a large GlassSheet produced a hard, visible border around the sheet โ a bright "line" that looked like an artifact rather than a premium glass surface.
Lowering it globally to fix the sheets caused components inside the sheet to lose their specular highlights and become washed out.
We've introduced semantic preset separation via two distinct RecommendedGlassSettings presets to solve this:
RecommendedGlassSettings.overlay(refractiveIndex: 0.7): For cards, buttons, and small interactive widgets.RecommendedGlassSettings.sheet(refractiveIndex: 0.15): For large bottom sheets and modal overlays.
All GlassSheet.show() calls in the demo app now use the sheet preset, while every GlassButton.custom and GlassCard inside a sheet explicitly passes settings: RecommendedGlassSettings.overlay. The package-level default for GlassSheet (glass_sheet_defaults.dart) has also been updated to use refractiveIndex: 0.15 for a better out-of-the-box experience.
Zero breaking changes.
0.9.2 #
๐ Fix โ GlassSwitch initial-state bloom anchor & polish #
-
First-click bloom anchored correctly. A switch initialised with
value: truenow anchors the bloom to the right edge on the very first tap, matching all subsequent interactions. Previously_isMovingForwardwas hardcoded totrueat construction regardless ofwidget.value. -
_justEndedDragrace condition eliminated. The flag is now consumed atomically insidedidUpdateWidgetrather than being reset one frame later viaaddPostFrameCallback, preventing a rare double-bloom after a drag toggle. -
Floating-point guard hardened. Animation controller resets now use
>= 0.99instead of== 1.0, making the bloom sequence robust against sub-epsilon drift during rapid consecutive toggles. -
Dead code removed (
glassOverlayno-op widget). -
Haptic feedback added.
GlassSwitchnow emitsHapticFeedback.lightImpact()on tap-toggle, when the thumb crosses the 50 % midpoint during a drag, and on drag-release snap (when the midpoint was never crossed, e.g. a fast flick). Opt out withenableHaptics: false. -
3 new regression tests added;
GlassSwitchtest count now 24.
Zero breaking changes.
0.9.1 #
๐ Fix โ Adaptive quality system calibration #
Three coordinated improvements to GlassAdaptiveScope / GlassQualityAdapter that
prevent modern flagship devices from being incorrectly demoted to standard quality
during app startup.
1. Startup-skip window (skipInitialFrames = 60) #
Phase 2 now discards the first 60 frames (โ 1 second at 60 Hz) before collecting warmup data. Those frames capture shader compilation, the first route transition, and provider/localisation initialisation โ all artificially inflated and unrepresentative of steady-state glass rendering. Discarding them means the warmup benchmark reflects actual glass workload, not cold-start overhead.
The constant is tunable for testing: GlassQualityAdapter.skipInitialFrames = 0.
2. Raised premium threshold: 12 ms โ 16 ms #
The old threshold of 12 ms (75 % of a 60 fps frame budget) was too tight. The new threshold is 16 ms โ one full 60 fps frame budget โ which has a cleaner semantic meaning: "can the device render a premium glass frame within the 60 fps budget at P75? Yes โ premium."
| P75 raster time | Before | After |
|---|---|---|
| < 12 ms | premium | โ |
| < 16 ms | standard | premium |
| 16โ20 ms | standard | standard |
| > 20 ms | minimal | minimal |
3. allowStepUp defaults to true #
Previously allowStepUp defaulted to false, meaning a Phase 2 decision could never
be corrected at runtime. If Phase 2 still makes a conservative call (e.g. on a device
under thermal load at startup), Phase 3 can now self-correct after 10 consecutive
under-budget windows (โ 20 seconds) + an 8-second cooldown.
The step-up is deliberately slow and invisible to users. Set allowStepUp: false
explicitly if you need to lock quality for the session.
Zero breaking changes (adaptive fix) #
All three changes are additive or alter defaults in a user-beneficial direction.
Explicit constructor overrides (allowStepUp: false, skipInitialFrames, custom
threshold via targetFrameMs) continue to take precedence.
๐ Fix โ GlassSwitch drag interaction #
GlassSwitch now supports tap and horizontal drag simultaneously without either
interaction interfering with the other.
What was fixed:
- Tap animation restored โ registering both
onTapandonHorizontalDrag*on the sameGestureDetectorcaused Flutter's gesture arena to drop one interaction after the first touch. Taps now useonTapDown/onTapUpso they share the gesture stream cleanly with drags. - Slow drag no longer cancels โ Flutter fires
onTapCancelbefore confirming a horizontal drag, which was deflating the "liquid bloom" pill prematurely._onDragStartnow stops any in-progress deflation and restores the plump state immediately. - Animation resets between interactions โ the thickness animation controller
was left at
1.0after its first cycle and silently skipped the bloom on subsequent taps. It now resets to0.0before each new forward pass.
Gesture behaviour unchanged from the user's perspective: tap = full liquid jump animation; drag = thumb tracks finger with symmetric pill stretch; flick = velocity-based snap.
Zero breaking changes #
No API changes. All existing GlassSwitch usages continue to work without
modification.
๐ Fix โ interactionGlowColor now reads from GlassThemeData #
GlassBottomBar and GlassSearchableBottomBar (including its collapsedLogoBuilder
state and SearchPill) previously used a hardcoded white glow (0x33FFFFFF) when
no explicit interactionGlowColor was set, silently ignoring any GlassThemeData
override on the ancestor tree.
Resolution order is now:
interactionGlowColor param โ GlassThemeData.glowColorsFor(context).primary โ internal fallback
This means setting the primary glow color in GlassThemeData now takes effect
on the press-interaction highlight across both bar variants, including the collapsed
logo pill, without requiring any code changes at the call site.
Affected widgets #
| Widget | Location |
|---|---|
GlassBottomBar |
TabIndicator interaction glow |
GlassSearchableBottomBar |
SearchableTabIndicator (normal + collapsed/logo state) |
GlassSearchableBottomBar |
SearchPill expanded glow |
Zero breaking changes #
Explicit interactionGlowColor parameters continue to win with highest priority.
This only changes what happens when the parameter is left null.
โจ Feature โ glowBlurRadius, glowSpreadRadius, glowOpacity on GlassGlowColors #
Three new appearance fields on GlassGlowColors give fine-grained control over the
shape of the directional press-glow across all glass widgets:
| Field | Type | Default | Effect |
|---|---|---|---|
glowBlurRadius |
double |
4.0 |
Gaussian blur sigma via MaskFilter.blur โ softens the glow edge into a natural liquid-glass halo |
glowSpreadRadius |
double |
0 |
Extra circle radius as a fraction of the layer's shortest side |
glowOpacity |
double |
1 |
Master opacity multiplier (0โ1) applied on top of the glow color's own alpha |
Usage #
Set them globally via GlassThemeData to affect all glass widgets at once:
GlassTheme(
data: GlassThemeData(
light: GlassThemeVariant(
glowColors: GlassGlowColors(
primary: Color(0x55FFFFFF),
glowBlurRadius: 8, // soft, diffuse halo
glowSpreadRadius: 0.15, // bleeds 15 % beyond touch radius
glowOpacity: 0.75, // 75 % of the color's own alpha
),
),
),
child: ...,
)
Or override per-widget via GlassButton.glowBlurRadius / glowSpreadRadius /
glowOpacity โ widget-level values take precedence over the theme.
Defaults preserve existing visual behaviour #
glowSpreadRadius and glowOpacity default to 0 and 1 respectively,
preserving previous rendering. glowBlurRadius defaults to 4.0 โ
a soft, natural halo that better fits the liquid-glass aesthetic.
MaskFilter.blur is guarded at zero so there is no GPU cost when the value
is left at 0. Set glowBlurRadius: 0 explicitly for a hard-edge disc.
Affected widgets #
All widgets that render GlassGlow consume these fields, including:
GlassButton, GlassBottomBar, GlassSearchableBottomBar
(both the tab pill and the search pill), GlassSlider, GlassSwitch.
Zero breaking changes #
Existing code that does not set these fields continues to render identically.
copyWith, ==, and hashCode all include the three new fields.
0.9.0 #
โจ New โ tabWidth on GlassBottomBar #
tabWidth is now available on both GlassBottomBar and GlassSearchableBottomBar.
Both bar variants share identical compact-sizing semantics and the same default.
API #
GlassBottomBar(
// Default (no tabWidth): expand โ pill fills available space.
// tabWidth: 88.0 โ iOS 26 compact sizing
tabWidth: 88.0,
...
)
tabWidth |
Behaviour | 2 tabs | 3 tabs | 4 tabs |
|---|---|---|---|---|
null (default) |
Expand โ fills available space | fills bar | fills bar | fills bar |
88.0 |
Compact โ iOS 26 style | 176 px | 264 px | 352 px |
The pill is automatically clamped so it never overflows its container, regardless of how many tabs are present or how narrow the screen is.
Zero breaking changes #
tabWidth defaults to null (expand) on both GlassBottomBar and
GlassSearchableBottomBar. Existing code that does not pass tabWidth
continues to behave exactly as before โ the tab pill fills the bar.
Pass tabWidth: 88.0 to opt-in to iOS 26 compact sizing.
Shared infrastructure (internal) #
bar_layout_utils.dartโ new pure-Dart file containingresolveTabPillWidth. BothGlassBottomBarandSearchableBottomBarControllerdelegate to this single function, eliminating two separate inline implementations of the same arithmetic.kBottomBarGlassDefaultsโ the 9-fieldLiquidGlassSettingsconstant that was previously copy-pasted into both bar state classes is now defined once inbottom_bar_internal.dartand referenced from both locations.
Production hardening #
- Extra button pinned to trailing edge in
GlassBottomBar. Previously the extra button sat immediately adjacent to the tab pill when using compacttabWidthsizing, leaving empty space to its right. It is now always pinned to the far-right edge (usingExpanded+Align(centerRight)) to match the searchable bar's layout. ThemaxTabWarithmetic is unchanged; only the Row structure changed. Works correctly in both compact and expand modes. resolveTabPillWidthguards against negativemaxAvailablevalues (math.max(0.0, maxAvailable)before theclamp) to prevent aRangeErrorin unusual layout constraint environments.- Both constructors now assert
tabWidth == null || tabWidth > 0โ passing a negative value previously produced a zero-width pill silently. - Golden regression sentinel added for
tabWidth: null(expand mode), so a layout regression in legacy behaviour is caught by the pixel-test suite.
Example #
example/lib/tab_width_demo.dart โ covers both GlassBottomBar
and GlassSearchableBottomBar via a Bar variant chip, with live metrics
showing the computed pill width in real time.
0.8.4 #
CI & Tooling #
-
CI: Multi-platform test matrix. The CI pipeline now runs the full test suite on
ubuntu-latest,macos-latest, andwindows-latestacross bothstableandbetaFlutter channels. Previously onlymacos-latest / stablewas tested, which silently allowed the three Windows shader regressions shipped in 0.7.9โ0.7.12. Fail-fast is disabled so all platform failures are visible in a single run. -
CI: Windows shader validation gate.
glslangValidator(the same SPIR-V compiler core Flutter uses on Windows) now runs in CI on every push and PR via theshader-validationjob. Any shader that would produce a "index expression must be constant" or "loop bounds must be compile-time constants" error is caught before it reachesmain. Previously this check only ran locally viabash scripts/validate_shaders.shon macOS. -
CI: pub.dev publish dry-run gate. A dedicated
pub-checkjob runsdart pub publish --dry-runon every push and PR. Catches missing dartdoc comments,pubspec.yamlissues, platform declaration gaps, and score regressions before they land in a release. -
CI: Coverage threshold guard (โฅ 90 % effective). The pipeline now fails if effective line coverage drops below 90 % on the stable channel. Effective coverage is computed after stripping
lib/src/renderer/*โ 16 GPUCustomPainter/RenderObjectfiles that cannot execute in a headless VM (no GPU rasterizer; documented as untestable inARCHITECTURE.md). Current effective coverage is 91.8 % (4 146 / 4 514 lines). A.codecov.ymlconfig now mirrors this exclusion so the pub.dev / GitHub badge agrees with the CI gate rather than showing the raw ~81 % figure that included the untestable renderer paths. -
CI: Run concurrency cancel. Added
concurrencygroup so redundant in-progress runs on the same branch are cancelled automatically, saving CI minutes on rapid-push workflows. -
Tooling:
scripts/validate_shaders.shcross-platform update. The shader validation script now resolvesglslangValidator/glslangValidator.exeautomatically, works on Windows (Git for Windows bash), and prints correct install instructions for macOS (brew), Ubuntu (apt-get), and Windows (choco/winget). Path resolution is now robust regardless of which directory the script is called from.
GlassAdaptiveScope Diagnostics (experimental) #
-
GlassAdaptiveDiagnosticโ rich quality change event. A new immutable data class is emitted wheneverGlassAdaptiveScopechanges quality tier. It carries the full context of why the change happened:from/toquality,reason(warmupComplete,thermalDegradation,thermalRecovery,restoredFromCache,staticProbe),phase, and the P75/P95 raster timing that triggered the decision. -
GlassAdaptiveScope.onDiagnosticโ a new optional callback that receives aGlassAdaptiveDiagnosticalongside the existingonQualityChanged. The old callback is unchanged โ this is purely additive. -
GlassAdaptiveScope.debugLogDiagnostics: trueโ zero-wiring diagnostic mode. Add this flag to print a structured console block on every quality change in debug builds (no-op in profile/release). Designed to lower the barrier for community threshold calibration reports:โโ ๐ GlassAdaptiveScope โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Change : premium โ standard โ Reason : warmupComplete โ Phase : runtime โ P75 : 14.2 ms โ Frames : 10 โ โ ๐ฌ Post to: github.com/sdegenaar/liquid_glass_widgets/discussions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -
GlassQualityChangeReasonenum โ exported publicly so analytics pipelines can filter on specific event types (e.g. only logwarmupCompleteand skiprestoredFromCachenoise). -
Adapter diagnostic tracking โ
GlassQualityAdapternow recordslastP75Ms,lastP95Ms,lastFramesMeasured, andlastChangeReasonbefore every quality decision so the scope can snapshot them synchronously before the asyncaddPostFrameCallbackgap.
Bug Fixes #
-
FIX: Refraction inverted on Android (Pixel 7, Mali GPU, OpenGL ES emulator). On all devices where Impeller uses the OpenGL ES backend, the liquid glass refraction effect appeared to bend inward rather than outward โ content beneath the glass lens distorted toward the centre instead of away from it. The glass bottom bar, segmented control indicator, and all premium-quality glass surfaces were affected.
Root cause: OpenGL ES stores render-to-texture outputs with a bottom-left Y origin (Y increases upward), whereas Flutter's widget coordinate system uses Y-down. The shaders already flip
screenUV.yandgeometryUV.ywith1.0 โ yto compensate when sampling textures. However, thedisplacementvector (inliquid_glass_final_render.frag) andedgeOffsetLogical(ininteractive_indicator.frag) were computed in Flutter's Y-down space and added directly to the Y-up UV without correcting the Y component. A positive Y displacement (outward at the bottom edge) therefore moved the sample toward the centre in UV space โ the exact opposite of the intended direction.Fix: Under
#ifdef IMPELLER_TARGET_OPENGLES, negate the Y component of the displacement/offset vector before applying it to the sampled UV. This re-aligns the Y-down displacement with the Y-up UV coordinate space.The Metal (iOS/macOS) and Vulkan (Samsung S22 / Adreno / AMD Xclipse) code paths are unchanged โ the fix is gated entirely by
IMPELLER_TARGET_OPENGLESand verified against both a Pixel 7 API 35 emulator and a physical Samsung Galaxy S22.
0.8.3 #
Performance & Bug Fixes #
-
GlassBottomBar/GlassSearchableBottomBarโ glass lens now correctly refracts active tab icons. Previously the selected icon layer was rendered above theAnimatedGlassIndicatorin a separate compositor layer, making it invisible to theBackdropFilter. The glass pill swept over a blank canvas, producing a flat, unrefracted active icon. Both the selected and unselected icon layers are now combined into a singleRepaintBoundaryplaced behind the glass lens, so all icon colours are physically sampled and warped by the chromatic aberration as the pill moves โ matching iOS 26 behaviour. -
Performance improvement. The fix eliminates 5โ9 redundant GPU compositor layers per bar render frame: the per-tab
RepaintBoundarynodes on both the selected and unselected icon rows have been removed in favour of a single shared compositor texture for the entire icon canvas. Fewer texture uploads, oneBackdropFiltersample โ net improvement at 120 Hz.
0.8.2 #
Bug Fixes #
-
GlassQuality.premiumno longer crashes outside aLiquidGlassLayer. Previously caused an opaqueNull check operatorcrash. Now throws a descriptiveAssertionErrorin debug builds and falls back gracefully (renders child without glass) in release. Fix: adduseOwnLayer: trueto any standaloneGlassButtonusingpremiumquality. -
GlassBottomBar/GlassSearchableBottomBarโ repeat-tap on active tab now firesonTabSelected(#22). Previously theindex != widget.tabIndexguard silently suppressed callbacks when the user tapped the already-selected tab, making it impossible to implement scroll-to-top or refresh-on-retap patterns. The guard has been removed;onTabSelectedis now always called once per gesture lifecycle regardless of whether the tab index changes. -
GlassBottomBar/GlassSearchableBottomBarโ drag-end snaps to correct tab (#23). A coordinate-space mismatch in_onDragEndcaused the indicator to snap to the wrong tab: dragging to the centre of a 5-tab bar landed on tab 3 instead of tab 2. The fix corrects the inversion formula toi = round(relX ร (n โ 1)), which is the exact inverse of the alignment spacecomputeAlignment(i, n) = โ1 + 2i/(nโ1). -
GlassBottomBar/GlassSearchableBottomBarโonTabSelectedno longer fires twice per tap.BottomBarTabItemhad its ownonTap: () => onTabSelected(i)callback that fired independently of the outerTabIndicator'sonTapDownhandler, causing every tap to callonTabSelectedtwice. The item-level callback is nownull; the outer indicator is the single source of truth for all selection events.Credit: These interaction fixes were identified and originally patched by @qinshah in PR #23. The implementation was refactored to preserve the existing jelly physics, desktop tap support, and fling-based navigation that the PR removed, and extended to cover
GlassSearchableBottomBarwith shared logic via the new internalTabDragGestureMixin.
API #
GlassSearchBarConfig.expandWhenActive(new). Controls whether the search pill expands whenisSearchActiveistrue. Defaulttrueโ no change needed for standard usage. Set tofalsefor advanced layouts (e.g. Apple Music Play Pill pattern) where the search pill should remain compact whileisSearchActivedrives a non-search transition independently.
Examples #
apple_music_demoโ added as a reference for the Play Pill pattern: a floatingGlassButton(useOwnLayer: true,GlassQuality.premium) that animates between a full-screen player and a mini-mode docked pill usingAnimatedPositioned+AnimatedOpacity, synchronized withGlassSearchableBottomBar's spring morph viaexpandWhenActive.
0.8.1 #
New Features #
GlassInteractionBehavior โ precise, orthogonal control of press interactions #
A new first-class enum that independently controls the two dimensions of press
feedback on GlassBottomBar, GlassSearchableBottomBar, and GlassTextField
(as well as its derivative inputs):
| Value | Glow | Scale |
|---|---|---|
none |
โ | โ |
glowOnly |
โ | โ |
scaleOnly |
โ | โ |
full (default) |
โ | โ |
The glow is the iOS 26-style directional light spotlight that follows the touch position across the glass surface. The scale is the spring-physics size pulse on press.
// Glow only โ light follows your finger, no bounce:
GlassBottomBar(
interactionBehavior: GlassInteractionBehavior.glowOnly,
...
)
// Scale only โ spring bounce, no glow:
GlassSearchableBottomBar(
interactionBehavior: GlassInteractionBehavior.scaleOnly,
pressScale: 1.06,
...
)
// Disable both for a completely static bar:
GlassBottomBar(
interactionBehavior: GlassInteractionBehavior.none,
...
)
Zero overhead when disabled. When interactionBehavior suppresses glow (none
or scaleOnly), the GlassGlow sensor widget is removed from the tree entirely โ
saving 3 widget allocations and 3 RenderBox nodes per tab indicator per frame.
Scale is resolved at build time to a scalar 1.0 with no animation controller
overhang.
New parameters on GlassBottomBar, GlassSearchableBottomBar, and GlassTextField #
GlassTextField now shares the same interactionBehavior API as the bar-family
widgets. The scale dimension maps onto the subtle press-bounce animation
(field squishes slightly when pressed down); the glow dimension is the directional
spotlight that tracks touch position across the glass surface.
GlassPasswordField and GlassTextArea delegate to GlassTextField and inherit
the new parameter automatically.
| Parameter | Widget(s) | Type | Default |
|---|---|---|---|
interactionBehavior |
All three | GlassInteractionBehavior |
.full |
pressScale |
Bar widgets / Inputs | double |
1.04 (bars) / 1.03 (inputs) |
interactionGlowColor |
Bar widgets | Color? |
null (theme default) |
glowColor |
GlassTextField |
Color? |
null (~12% white) |
interactionGlowRadius |
Bar widgets | double |
1.5 |
glowRadius |
GlassTextField |
double |
1.5 |
All defaults preserve existing 0.8.0 visual behaviour โ no migration required.
Migration from enableGlow / enableFocusAnimation
GlassTextField.enableGlow and GlassTextField.enableFocusAnimation have been
replaced by interactionBehavior. The mapping is direct:
// Before (0.8.0):
GlassTextField(enableGlow: false, enableFocusAnimation: false)
// After (0.8.1):
GlassTextField(interactionBehavior: GlassInteractionBehavior.none)
// Before: glow only
GlassTextField(enableGlow: true, enableFocusAnimation: false)
// After:
GlassTextField(interactionBehavior: GlassInteractionBehavior.glowOnly)
Bug Fixes #
-
FIX:
SearchPillwas silently ignoringinteractionBehavior. TheinteractionGlowColorparameter was never passed to theSearchPillconstructor, so the search pill always rendered with a visible glow regardless of the bar'sinteractionBehaviorsetting. The glow was hardcoded toColor(0x1FFFFFFF)even whenbehavior = none. -
FIX:
SearchPillStatehad no glow short-circuit on the expanded pill path. Added_wrapWithGlowhelper (matching the pattern already inTabIndicatorStateandSearchableTabIndicatorState) to skipGlassGlowallocation when glow is suppressed.
0.8.0 #
New Features #
GlassAdaptiveScope (experimental) โ automatic runtime quality adaptation #
A new scope widget that automatically adjusts GlassQuality for its subtree
based on real raster performance observed from SchedulerBinding frame timings.
Handles the three device scenarios that are impossible to test on a developer
device:
- Broken / slow shader drivers (e.g. Pixel 4a, Galaxy A22 class): detected
synchronously at startup via
ImageFilter.isShaderFilterSupportedand capped immediately tominimal. - Warm-up jank ("wrong quality at startup"): resolved by a ~180-frame benchmark that measures real P75 raster durations and sets the initial quality tier before the user notices.
- Thermal throttling ("fine at launch, janky after 10 minutes"): detected and corrected by a continuous runtime hysteresis engine.
Three-phase adaptation:
| Phase | Trigger | Action |
|---|---|---|
| Phase 1 โ Static probe | Mount | Forces minimal on unsupported hardware; caps at standard on web |
| Phase 2 โ Warm-up | First ~180 frames (~3 s at 60 fps) | Sets initial quality from real P75 raster durations |
| Phase 3 โ Runtime hysteresis | Ongoing | Degrades after 3 bad windows; recovers after 10 good windows (8 s cooldown) |
The scope acts as a quality ceiling โ widgets with an explicit quality:
parameter are unaffected. The ceiling is enforced by
GlassThemeHelpers.resolveQuality, which reads GlassAdaptiveScopeData from
the nearest ancestor scope.
// Per-screen control:
GlassAdaptiveScope(
child: Scaffold(...),
)
// Advanced โ conservative start for fragmented Android market:
GlassAdaptiveScope(
initialQuality: GlassQuality.standard, // earn your way up to premium
allowStepUp: true,
onQualityChanged: (from, to) => analytics.log('glass_quality_changed'),
child: child,
)
Experimental in 0.8.0.
GlassAdaptiveScopeandGlassAdaptiveScopeConfigare annotated@experimental. The three-phase adaptation logic is architecturally sound and fully tested, but the Phase 2 timing thresholds (P75 < 12 ms โ premium, 12โ20 ms โ standard, > 20 ms โ minimal) have been validated by reasoning, not yet by broad real-device data across the Android fragmentation landscape.How to enable it:
LiquidGlassWidgets.wrap(myApp, adaptiveQuality: true)(opt-in, defaultfalse).If you observe unexpected behaviour โ quality too low on a mid-range device, or stuck at
standardon a flagship โ please file an issue with your device model and raster timings from Flutter DevTools. Your data will be used to tune the thresholds for a future release.
GlassAdaptiveScopeConfig (experimental) โ portable configuration value object #
Bundles all GlassAdaptiveScope parameters into a single const-constructible,
equality-comparable value object. Used by LiquidGlassWidgets.wrap() and useful
for passing scope configuration through APIs that cannot accept widget parameters
directly.
const config = GlassAdaptiveScopeConfig(
initialQuality: GlassQuality.standard,
allowStepUp: true,
targetFrameMs: 8, // 120 Hz ProMotion
);
API Refactor โ initialize() and wrap() separation #
The responsibilities of initialize() and wrap() have been clarified and
made consistent with the broader Flutter ecosystem (cf. easy_localization,
MaterialApp):
| Method | Responsibility |
|---|---|
initialize() |
Async platform / engine setup only (shader prewarming, Impeller pipeline, debug monitor) |
wrap() |
Widget-tree composition and all behavioral configuration |
wrap() โ new parameters #
runApp(LiquidGlassWidgets.wrap(
const MyApp(),
respectSystemAccessibility: false, // moved from initialize()
adaptiveQuality: true, // new โ inserts GlassAdaptiveScope
adaptiveConfig: GlassAdaptiveScopeConfig(
initialQuality: GlassQuality.standard,
allowStepUp: true,
),
));
Scope nesting order inserted by wrap() #
GlassAdaptiveScope โ GlassBackdropScope โ child
Breaking Changes #
initialize(respectSystemAccessibility:) removed #
respectSystemAccessibility has moved from initialize() to wrap().
Migration (one-line change):
// Before (0.7.x):
await LiquidGlassWidgets.initialize(respectSystemAccessibility: false);
runApp(LiquidGlassWidgets.wrap(const MyApp()));
// After (0.8.0):
await LiquidGlassWidgets.initialize();
runApp(LiquidGlassWidgets.wrap(const MyApp(), respectSystemAccessibility: false));
The LiquidGlassWidgets.respectSystemAccessibility getter and setter remain
available as an escape hatch for tests and advanced runtime overrides. In
production code, set it through wrap().
Bug Fixes #
Glass invisible on white / light backgrounds (transparency regression) #
-
FIX: Standalone glass widgets (
GlassButton,GlassContainer,GlassTextField,GlassCard, and all widgets that delegate to them) rendered with zero opacity on light backgrounds when no explicitsettings:were provided. Root cause: these widgets fell through toInheritedLiquidGlass.ofOrDefault(), which returnsLiquidGlassSettings()โ a default withglassColor: Color(0x00FFFFFF)(alpha = 0). The lightweight shader computesbody tint = glassColor.alpha ร 0.15, so0 ร 0.15 = 0โ the glass body was literally transparent regardless ofthicknessorblur.Fix: Replaced all
InheritedLiquidGlass.ofOrDefault()call sites with the newGlassThemeHelpers.resolveSettings(), which traverses the full 5-level priority chain:- Widget-level
settings:parameter (explicit wins) InheritedLiquidGlassโ nearest parentAdaptiveLiquidGlassLayerLiquidGlassWidgets.globalSettingsโ app-level overrideGlassThemeDataโ brightness-aware theme variant (light / dark)LiquidGlassSettings()โ absolute last resort
Standalone widgets now correctly resolve to the theme's
glassColorand are always visible out of the box. - Widget-level
Light theme defaults rebalanced #
-
TWEAK:
GlassThemeVariant.lightupdated for an icy-frosted aesthetic that reads clearly on white backgrounds:Property Before After blur10.0 6.0 glassColor0x73FFFFFF(45% neutral white)0x4AD2DCF0(~29% cool blue-white)chromaticAberration0.1 0.3 thickness16.0 20.0 lightIntensity1.0 1.2 The cool blue-white tint (
D2DCF0) matches the icy tone of iOS 26 frosted glass. Blur 6 gives visible background diffusion without obscuring content.
API #
GlassBackdropScope now exported from the main barrel #
-
FIX:
GlassBackdropScopewas missing fromliquid_glass_widgets.dart. Consumers had to use the internal pathpackage:liquid_glass_widgets/widgets/shared/glass_backdrop_scope.dart, which is fragile and undocumented. It is now a first-class public export.Migration โ update any direct internal imports:
// Before (workaround, fragile): import 'package:liquid_glass_widgets/widgets/shared/glass_backdrop_scope.dart'; // After (correct): import 'package:liquid_glass_widgets/liquid_glass_widgets.dart'; -
CHORE: add CI and Codecov badges.
0.7.16 #
Bug Fixes #
- FIX:
GlassSearchableBottomBarโ memory leak whencontrollerwas swapped at runtime. The old controller's listener was never removed before attaching to the new controller. Now correctly removed indidUpdateWidget. - FIX:
DraggableIndicatorPhysicsโ velocity NaN/Infinity guard. A zero-size render box (e.g. during widget tree warm-up) could produceInfinityorNaNforvelocityX, which propagated into the spring physics and caused erratic snapping. Now clamped to 0 when the box has no size.
Refactor (zero breaking changes) #
- REFACTOR: Extracted
GlassSearchBarConfigfromglass_searchable_bottom_bar.dartinto a dedicated filelib/widgets/surfaces/shared/glass_search_bar_config.dart. Resolves a circular import between the public widget and its internal sub-widgets.GlassSearchBarConfigis re-exported from the barrel file โ no consumer-facing API change. - REFACTOR: Extracted
_TabIndicator/_TabIndicatorStatefromglass_bottom_bar.dartintoshared/bottom_bar_internal.dartasTabIndicator/TabIndicatorState(package-internal, not exported). Follows the same pattern used forGlassSearchableBottomBar.glass_bottom_bar.dartreduced from 1,406 โ ~895 lines. - REFACTOR: Extracted
_TabBarContent,_TabBarContentState, and_TabItemfromglass_tab_bar.dartintoshared/tab_bar_internal.dart.glass_tab_bar.dartreduced from 728 โ ~310 lines. Architecture is now consistent across all bar-family widgets.
Test Coverage #
- TEST: Reached 91.85% effective coverage (up from 89.6% in 0.7.15 โ excluding GPU/shader renderer paths that are physically untestable in a headless VM). Total: 1,031 tests, all passing, 0 analyzer warnings.
- TEST: New
test/widgets/surfaces/glass_bottom_bar_drag_test.dartโ 7 regression tests covering_onDragEndphysics snapping,_onDragCancel(mid-drag and no-drag), slow drags, fast flings, and full-bar sweeps. These paths are the highest-risk regressions in navigation UX.
0.7.15 #
Bug Fixes #
- FIX:
lib/theme/glass_theme_settings.dartwas accidentally omitted from version control in 0.7.14. All consumers ofGlassThemeSettingsreceived a compile error (type 'GlassThemeSettings' is not a subtype). This release commits the missing file. No API change โGlassThemeSettingswas already exported fromliquid_glass_widgets.dart. - FIX:
GlassPerformanceMonitor._emitWarningโ division-by-zero crash whenrasterBudgetwas sub-millisecond (< 1 ms). Protected with amax(1, ...)guard.
Refactor (zero breaking changes) #
- REFACTOR: Consolidated 18 quality-resolution chains (
widgetQuality ?? inherited?.quality ?? themeData.qualityFor(context) ?? GlassQuality.standard) into a single canonical helper:GlassThemeHelpers.resolveQuality(context, widgetQuality: ..., fallback: ...). Surface widgets (GlassAppBar,GlassToolbar,GlassBottomBar,GlassSearchableBottomBar,GlassSideBar) passfallback: GlassQuality.premiumto preserve their documented defaults. All other widgets default toGlassQuality.standard. - REFACTOR: Extracted
_buildIconShadowsfromBottomBarTabItemto a@visibleForTestingtop-level functionbuildIconShadows(...)inbottom_bar_internal.dart. No behaviour change โ enables isolated unit testing of the shadow-outline geometry.
Test Coverage #
- TEST: Reached 90%+ effective test coverage (90.15% โ excluding
src/rendererGPU/shader layer where headless simulation is impossible). Total: 949 tests, all passing. - TEST: New
test/theme/glass_theme_helpers_test.dartโ 5 widget tests covering all 4 priority levels ofGlassThemeHelpers.resolveQuality(). - TEST: New
test/widgets/surfaces/build_icon_shadows_test.dartโ 6 unit tests coveringbuildIconShadows(): null thickness, active-icon suppression, shadow count, 45ยฐ offset math, and color propagation. - TEST: Added
test/theme/,test/renderer/,test/types/,test/constants/,test/utils/, andtest/widgets/test suites (committed for the first time โ these were written during the 0.7.13โ0.7.14 coverage push but never staged).
0.7.14 #
Bug Fixes #
- FIX:
GlassSearchableBottomBarโextraButtonnow fades out smoothly when search activates instead of being visually clipped/shrunk between the collapsing tab pill and the expanding search pill. Layout space is still reserved during the morph (no pills jump), only the visual opacity transitions. Taps on the extra button are also correctly blocked while hidden. Fades in when search closes. - FIX:
GlassSearchableBottomBarโ spring morph animations no longer produce a visible jump when reversing direction. Previously the three spring controllers (tabW,searchLeft,searchW) were each started in separateaddPostFrameCallbackcalls, introducing a 1-frame desync at reversal. All three are now started in a single batched callback, so the morph is perfectly synchronized in both directions. - FIX: Indicator fade animation in
GlassBottomBar/GlassSearchableBottomBarโ replacedOpacitywrapper withLiquidGlassSettings.visibilityfading. Wrapping aBackdropFilterinOpacitycomposites into an offscreen buffer, breaking backdrop sampling and causing the indicator to snap in/out instead of fading. Thevisibilitypath is a single GPU pass โ no offscreen buffer โ improving drag animation performance and working uniformly for allblurvalues. - FIX:
GlassBottomBar,GlassSearchableBottomBar,GlassAppBar,GlassToolbar, andGlassSideBarresolved toGlassQuality.standardinstead of their documentedGlassQuality.premiumdefault. Fixed by settingquality: nullin the built-in light/dark variants so each widget's documented default is respected. - FIX: Setting any property in
GlassThemeVariant.settingssilently zeroed out all unset properties (e.g. setting onlythickness: 50also resetglassColorto fully transparent). Fixed by introducingGlassThemeSettings: a parallel class with all-nullable fields that merges onto each widget's own defaults. Only the fields you explicitly set are applied; everything else inherits from the widget.GlassThemeVariant.settingsnow acceptsGlassThemeSettings?. - FIX:
GlassSearchableBottomBarโ multiple layout-math regressions in the morph animation corrected:- Reserved layout width now correctly scales to
min(size, searchBarHeight)during search, eliminating the bloated gap whensearchBarHeight < barHeight. - Extra button rendered width now matches the layout reserve (
extraTargetW), preventing a 14 px overflow into the search pill whensearchBarHeight < barHeight. - Restored
+ widget.spacingintargetSearchLeft; an erroneoustabToNextGapvariable had suppressed the gap between the tab pill and search pill when no extra button was present. collapseOnSearchFocusnow exclusively controls visibility/opacity โ it no longer affects layout geometry. Toggling it mid-animation no longer triggers the spring or causes the button to jump inside the collapsed tab circle.
- Reserved layout width now correctly scales to
- FIX:
BottomBarTabItemโ removed a fixedvertical: 4padding wrapping the tab column. The padding consumed constraint space beforeFittedBoxcould scale, causing a 2 pxRenderFlexoverflow when the bar morphed tosearchBarHeight.
New #
- NEW:
GlassThemeSettingsโ a partial settings type for use inGlassThemeVariant. Accepts the same parameters asLiquidGlassSettingsbut all are nullable. Only non-null fields override the target widget's defaults, enabling precise single-property theme overrides without disturbing others. - NEW:
GlassTabPillAnchorenum +GlassSearchableBottomBar.tabPillAnchorโ controls how the tab pill is anchored during the morph animation.GlassTabPillAnchor.start(default) preserves existing left-anchor behaviour.GlassTabPillAnchor.centermakes both edges collapse symmetrically from the pill's centre for a more balanced look. The search pill position adjusts automatically in center mode. - NEW:
GlassSearchBarConfig.showsCancelButtonnow defaults totrue. Tapping the dismiss pill unfocuses the keyboard and collapses search, matching the system-level behaviour seen across iOS apps (Weather, App Store, Apple News). PassshowsCancelButton: falseto opt out. - NEW:
GlassSearchBarConfig.collapsedTabWidthis now nullable. When omitted, the collapsed tab pill automatically matchesGlassSearchableBottomBar.searchBarHeight, ensuring it morphs into a geometric circle with no leftover horizontal margin. Pass an explicit value to override. - NEW:
GlassBottomBarExtraButton.collapseOnSearchFocus(defaulttrue) โ controls whether the extra button collapses when the search field is focused. Whentrue, the button fades out and its layout space spring-animates to zero, giving the search input the full available width (matching native iOS behaviour). Whenfalse, the button remains fully visible and tappable alongside the search input โ useful for contextually relevant actions like a Filter button that applies to search results. - EXAMPLE:
searchable_bar_repro.dartadded to the example app โ exercisesGlassSearchableBottomBaredge cases (extra-button fade, spring desync, bar-height scale, dismiss pill) in isolation. Run standalone:flutter run -t example/lib/searchable_bar_repro.dart.
0.7.13 #
New โ GlassQuality.minimal #
-
FEAT:
GlassQuality.minimalโ third quality tier: a crisp frosted glass surface with zero custom fragment shader execution on any platform. UsesBackdropFilterblur- Rec. 709 saturation matrix + a light-angle specular rim stroke. No refraction warping or chromatic aberration โ a deliberately flat, clean aesthetic that looks excellent on any background and never adds GPU shader cost.
Two distinct use cases:
Device fallback โ for hardware where even [standard] is too heavy: very old Android devices with limited shader driver support, or any device where
ImageFilter.isShaderFilterSupportedreturnsfalse.GPU budget management โ for shader-dense screens: use [minimal] for background panels, list cards, and decorative containers while keeping [standard] or [premium] on the focal element. A screen with 15 glass list cards running [minimal] fires zero shader invocations during scroll โ only
BackdropFiltercompositing.AdaptiveGlass( quality: GlassQuality.minimal, child: child, ) -
FEAT:
GlassThemeVariant.minimalโ static preset that applies.minimalquality globally viaGlassThemeData:GlassTheme( data: GlassThemeData( light: GlassThemeVariant.minimal, dark: GlassThemeVariant.minimal, ), child: child, )
New โ GlassPerformanceMonitor #
-
FEAT: Debug/profile-only performance monitor that watches raster frame durations while
GlassQuality.premiumsurfaces are active. When frames exceed the GPU budget for 60 consecutive frames, a singleFlutterErroris emitted with actionable guidance (specific widget parameters, device compatibility notes, and alternative quality tiers).Zero production overhead โ the monitor never registers a callback in release builds. Enabled by default in debug/profile builds via
LiquidGlassWidgets.initialize():// Default: auto-enabled in debug/profile, zero-cost in release await LiquidGlassWidgets.initialize(); // Opt out: await LiquidGlassWidgets.initialize(enablePerformanceMonitor: false); // Custom thresholds (advanced): GlassPerformanceMonitor.rasterBudget = const Duration(microseconds: 8333); // 120 fps GlassPerformanceMonitor.sustainedFrameThreshold = 120; // 2 seconds at 60 fpsThe monitor correctly attributes slowdowns to premium glass by counting active
GlassQuality.premiumsurfaces. It stays silent when no premium widgets are mounted, avoiding false positives from other parts of the app.
0.7.12 #
Bug Fixes #
-
FIX: Interactive blend-group stretch asymmetry โ
LiquidStretchnow expands geometry symmetrically from the widget centre, fixing the left-leans-in / right-resists imbalance during touch-drag on button groups. -
FIX: Erroneous highlight bias โ removed a legacy shader hack that skewed surface normals horizontally. Normals are now derived accurately from the SDF gradient, eliminating optical hotspots that made straight groups look crooked.
-
PERF: Zero-jitter animation bounds โ geometry texture mapping is now strictly bound to the physical size it was rasterised for, stopping frame-lag wobble when buttons change scale during interactive drags.
-
FIX: Theme quality cascade โ audited 15+ widgets (
GlassBottomBar,GlassSwitch,GlassTextField, and others) that were silently overriding the globalGlassThemeVariantquality setting withGlassQuality.premium. All widgets now correctly inherit and respect the global quality profile, protecting frame rate and thermal limits on older devices (e.g. iPhone 12 and below). -
FIX: Zero-thickness blur โ setting
thickness: 0no longer makes the glass fully transparent. Backdrop blur now renders correctly on glass surfaces regardless of geometric thickness, restoring backward-compatible behaviour. -
FEAT:
GlassSearchBarConfig.focusNodeโ optionalFocusNodeforGlassSearchBarConfig. When provided, the caller has full programmatic focus control (requestFocus(),unfocus(),addListener()) independent ofautoFocusOnExpand. The widget adopts the caller-provided node without disposing it (caller owns lifecycle), matching Flutter's ownTextField.focusNodecontract. -
FEAT:
GlassSearchBar.focusNodeโ sameFocusNodesupport added to the standaloneGlassSearchBarfor consistency.GlassTextFieldalready had this. -
FIX:
ExtraButtonPositionโ new enum onGlassBottomBarExtraButton. Set.position = ExtraButtonPosition.afterSearchto pin the extra button to the right of the search pill. Spring geometry calculations reserve space correctly to preventRenderFlexoverflows during expand/collapse. Default isExtraButtonPosition.beforeSearchโ fully backwards-compatible. -
FIX: Windows / SkSL shader compilation โ eliminated all dynamic array index expressions from
sdf.glsl. The previousgetShapeSDFFromArray(int index)computed offsets at runtime, which SkSL/glslang on Windows rejects with "index expression must be constant". Replaced with literal-indexedsdf0()โฆsdf15()helpers and a fully-unrolledsceneSDFfor 1โ16 shapes.MAX_SHAPESstays 16; no API or visual change. -
TOOLING:
scripts/validate_shaders.shโ macOS script that validates all shaders against Windows/SkSL compiler rules usingglslangValidator. Runbash scripts/validate_shaders.shbefore releasing. Requiresbrew install glslang(one-time).
0.7.11 #
Bug Fixes #
-
FIX: Windows/Android build failure โ three shader compilation errors on the SPIR-V/glslang path: loop bounds must be compile-time constants;
dFdx/dFdyon a scalarfloatis rejected by glslang (geometry shader now uses#ifdef IMPELLER_TARGET_METALto keep hardware derivatives on iOS/macOS and fall back to ยฑ0.5 px finite differences on Vulkan/OpenGL ES); global non-constant initialisers at file scope inliquid_glass_final_render.fragmoved intomain(). -
FIX: Blend-group asymmetry โ the liquid-glass merge neck between grouped buttons leaned toward the left button. Fixed with a bidirectional smooth-union pass (LโR + RโL, averaged 50/50) that cancels the directional bias exactly.
0.7.10 #
Bug Fixes #
- FIX: Windows build (
flutter build windows) โ two shader issues fatal on SkSL/glslang but silently accepted on Metal:no match for min(int, int)(replaced with a ternary) and global non-constant initialisers (moved intomain()). No visual change on any platform.
0.7.9 #
Bug Fixes #
- FIX: Windows build failure โ
uShapeData[MAX_SHAPES * 6]was passed as a by-value function parameter, which glslang rejects. Fixed by accessing it as a global uniform. No visual change.
Tweaks #
- TWEAK:
GlassSearchableBottomBariOS 26 Apple News parity โ animated inlineรclear button replaces microphone when text is present; simplified hit-testing layout replacesOverlaylayers; guaranteed GPU liquid-glass merging between the search and dismiss pills in a single shader pass.
0.7.8 #
Tweaks #
- TWEAK:
GlassThemeVariant.lightnow defaults to a cool-tintedglassColor(Color(0x32D2DCF0)), strongerrefractiveIndex, and boostedambientStrengthto ensure premium specular rendering and visible refraction on flat white backgrounds.
Examples #
- Apple News demo โ replaced
Image.networkcalls with pre-sized bundled assets (example/assets/news_images/) to fix Impeller GPU command-buffer overflow on iOS 26 physical devices. - Apple News demo โ
collapsedLogoBuildernow mirrors the active tab icon instead of a static badge.
0.7.7 #
Refactor #
- Internal: Removed
GlassIndicatorTapMixinand migratedGlassTabBarandGlassSegmentedControlfully to rawListenerpointer events, matchingGlassBottomBar's robust drag-cancel and press-and-hold handling. No API change.
0.7.6 #
Bug Fixes #
-
FIX:
LiquidGlassBlendGroupasymmetry โ left buttons attracted their neighbours more strongly than right buttons in groups of 3+. Fixed with a bidirectional smooth-union pass (LโR + RโL, averaged 50/50). Two-shape groups are mathematically identical to before. -
FIX:
GlassButtonGroupโ glass effect could bleed as a dark rectangle on Impeller withGlassQuality.premiumanduseOwnLayer: true. AClipRRect(antiAlias)now hard-clips the bleed at the superellipse boundary without forcing a quality downgrade.
0.7.5 #
Bug Fixes #
-
FIX:
GlassBottomBar/GlassSearchableBottomBarโ addedHitTestBehavior.opaqueto the rootGestureDetectorso the full bar height reliably consumes pointer events on simulator and desktop. -
FIX:
GlassSearchableBottomBarโ keyboard no longer flickers on physical devices; focus is requested after the expansion animation completes. -
FIX:
GlassSearchableBottomBarโ dead zone at expanded search pill edges resolved; the full glass surface now claims taps and routes them to the search field.
New โ GlassSearchBarConfig parameters #
Seven new parameters (all backwards-compatible):
| Parameter | Type | Default | Description |
|---|---|---|---|
autoFocusOnExpand |
bool |
false |
Keyboard opens automatically on expand. |
trailingBuilder |
WidgetBuilder? |
null |
Replaces the mic icon with any custom widget. |
textInputAction |
TextInputAction? |
null |
Keyboard action key (search, done, go, โฆ). |
keyboardType |
TextInputType? |
null |
Keyboard layout (url, emailAddress, โฆ). |
autocorrect |
bool |
true |
Disable for codes, usernames, etc. |
enableSuggestions |
bool |
true |
Controls QuickType bar on iOS. |
onTapOutside |
TapRegionCallback? |
null |
Called when user taps outside the field. |
0.7.4 #
New Components #
GlassSearchableBottomBarโGlassBottomBarwith a morphing search pill that shares the sameAdaptiveLiquidGlassLayeras the tab pill, producing iOS 26 liquid-merge blending. WhenisSearchActiveistruethe tab pill collapses and the search pill expands via spring animation. Configured viaGlassSearchBarConfig.
Examples #
- Apple News demo (
example/lib/apple_news/apple_news_demo.dart) โ iOS 26 Apple News replica showcasingGlassSearchableBottomBar.
Visual Fixes #
- FIX: Default glow color on press changed from iOS system blue to a brightness-adaptive neutral white (~35% light / ~22% dark), matching iOS 26 glass press behaviour.
0.7.3 #
Performance #
- PERF: Deleted unused
rotate2d()fromrender.glslโ it was compiled into every shader binary but never called. - PERF: Eliminated a redundant
normalize()ininteractive_indicator.fragby reusing an already-computed length. - PERF: Removed a no-op
canvas.save()/canvas.restore()pair inGlassGlowpaint.
Bug Fixes #
- FIX:
GlassGlowtracking โ glow gradient is now correctly recreated each frame whenglowOffsetchanges, fixing the spotlight freezing at its initial position. - FIX: Glow on Skia/Web โ
LightweightLiquidGlassnow wraps inGlassGlowLayer, giving the Skia path the same light-follows-touch behaviour as Impeller. - FIX: Glow on first touch โ spotlight now appears immediately at the tap position instead of sliding in from the widget's top-left corner.
- FIX: Glow tracking inside button groups โ converted from widget-local to global coordinates so the spotlight correctly follows touches regardless of nesting depth.
- FIX: Glow radius on wide buttons โ switched from
shortestSidetoโ(width ร height)so the spotlight scales proportionally to the button area.
0.7.2 #
Performance & Polish #
- PERF: Lightweight shader (
lightweight_glass.frag) โ reduced ALU instruction count ~10โ15 ops per fragment; restored thenormalZFresnel ramp tosqrt(1 โ dot(n,n)). - PERF: Impeller final render shader โ eliminated
length()/normalize()from anisotropic specular; madegetHeight()fully branchless; collapsed fourstep()multiplications into one. - PERF: Dart side โ cached light direction trig in
LiquidGlassRenderObject(only recomputed whenlightAnglechanges); changedGlassGroupLink.shapeEntriesfromListtoIterableto eliminate per-frame heap allocation. - FIX: Adjusted
GlassBottomBar,GlassTabBar, andGlassSegmentedControlspring from 500msbouncySpringto 350mssnappySpring, matching iOS 26 segment-indicator physics.
0.7.1 #
Bug Fixes #
- FIX:
GlassBottomBar,GlassTabBar,GlassSegmentedControlโ rapid taps no longer prematurely snap the indicator, killing spring physics. Removed pixel-snapping fromonHorizontalDragDownso taps correctly use spatial distance for the iOS 26 jump animation.
0.7.0 #
New Components #
GlassDividerโ iOS 26-style hairline separator, horizontal and vertical. Theme-adaptive opacity (dark: 20% white / light: 10% black).GlassListTileโ iOS 26 Settings-style row with leading icon, title, subtitle, trailing widget, and automatic grouped dividers. Use inside a zero-paddingGlassCard. Convenience constants:GlassListTile.chevron,GlassListTile.infoButton.GlassStepperโ iOS 26UIStepperequivalent. Compactโ/+glass pill with auto-repeat on hold,min/maxclamping,wrapscycling, fractionalstep, and haptic feedback.GlassWizard+GlassWizardStepโ multi-step flow with numbered indicators, checkmarks, and expandable step content.
Accessibility #
GlassAccessibilityScopeโ reads platform Reduce Motion and Reduce Transparency preferences and propagates them to all glass widgets in its subtree:- Reduce Motion: spring animations snap instantly.
- Reduce Transparency: replaces the full glass shader pipeline with a plain
BackdropFilter(blur)+ frosted container.
- Semantics updated across all remaining widgets to match iOS
UIAccessibilityconventions.
Performance #
- PERF:
GlassSpecularSharpnessenum โ replacespow(lightCatch, exponent)(two transcendentals per fragment) with a pure squaring chain inlightweight_glass.frag. Zero transcendentals. Default:.medium. - PERF:
pow(x, 1.5)โxยทโxin Impeller edge lighting โsqrt()is a single hardware SFU instruction. - PERF: Anisotropic specular and Fresnel rim brightening ported from the Impeller path to
lightweight_glass.frag, closing the largest visual gap between rendering paths. - PERF: Content-adaptive glass strength โ intensity auto-adjusts based on backdrop luminance on Impeller, or
MediaQuery.platformBrightnesson Skia/Web.
Developer Experience #
GlassRefractionSourceโ renamed fromLiquidGlassBackgroundto better reflect its role.LiquidGlassBackgroundremains as a deprecatedtypedef(removed in 1.0.0).- Synchronous background capture โ rebuilt using
boundary.toImageSync()on native (zero CPUโGPU readback) and asynctoImage()on web.
0.6.1 #
Visual Quality #
- FIX: True surface normal storage in geometry texture โ the geometry pass now stores the SDF-gradient-derived surface normal instead of the refraction displacement vector. The render shader decodes and recomputes displacement via
refract(). Specular highlights on blended glass shapes (e.g. two overlapping pills) now correctly follow true surface curvature rather than the refraction direction. Single-shape surfaces are visually identical to 0.6.0. - FIX: Anisotropic specular highlights (Impeller) โ specular lobe stretched 20% along the surface tangent, producing the horizontal oval highlight that matches iOS 26.
- FIX: Fresnel edge luminosity ramp (Impeller) โ gentle brightness ramp at grazing angles matching iOS 26's centre-to-edge luminosity gradient.
- FIX: Luminosity-preserving glass tint in lightweight shader โ replaced additive tint with the same
applyGlassColor()model as the Impeller path: achromatic glass lifts toward white, chromatic glass shifts hue while preserving luminance.
Performance #
- PERF: Branchless
smoothUnionโ eliminated a conditional branch that caused warp divergence when glass shapes transition between merged and separate. - PERF:
if/else ifdispatch in shape SDF โ GPU now short-circuits after the first type match; default changed to0.0for a clearly visible failure mode. - PERF: Single texture fetch when chromatic aberration is disabled โ
interactive_indicator.fragpreviously sampled the background three times unconditionally; 66% fewer texture reads in the common case. - PERF: Flat-interior early-exit in final render shader โ pixels where
normalXY โ 0skiprefract()and all texture samples, replaced with a single background sample. Lossless.
0.6.0 #
Breaking Changes #
LiquidGlassLayer.useBackdropGroupremoved. Glass layers now automatically detect aBackdropGroupancestor. RemoveuseBackdropGroup: truefrom anyLiquidGlassLayer(...)calls.
New Features #
LiquidGlassWidgets.wrap()โ wraps your app in aGlassBackdropScopein one line:runApp(LiquidGlassWidgets.wrap(const MyApp()));GlassMotionScopeโ drives glass specular angle from anyStream<double>(e.g. device gyroscope). No new dependencies required.
Performance #
- PERF:
GlassBackdropScopeauto-activation โ glass layers automatically share a single GPU backdrop capture when a scope ancestor is present. - PERF: Local-space geometry rasterization โ geometry texture cached until pill size or shape changes, eliminating per-frame rebuilds during animation.
- PERF: Shader UV bounds check โ discards fragments where geometry UV falls outside
[0, 1], preventing the thin "protruding line" artifact during jelly-physics expansion.
Visual #
- FIX: Refraction UV โ uses
uSizeuniform (always valid on first frame) instead oftextureSize()which returns(0,0)on the first frame in Impeller. - FIX:
precision highp floatin final render shader (wasmediump, risking colour banding on mobile). - FIX: iOS 26 glass tint model โ preserves backdrop luminance while shifting chroma. Replaces Photoshop Overlay mode.
- FIX: Leading-dot rim artifact โ
x / (1 + x)soft-clamping on highlight intensity prevents bright corner artifact during drag. - FIX: Impeller indicator clipping โ jelly physics animations no longer clip at the static bounding box (
clipExpansionparameter added). - FIX: Web & WASM โ removed
dart:ioimports from shader resolution logic.
Dependencies #
- Removed
motordependency โ replaced with self-containedglass_spring.dart. Zero third-party runtime dependencies beyond the Flutter SDK.
0.5.0 #
Breaking Changes #
LiquidGlass removed from the public API.
It was inadvertently exposed and silently renders nothing on Skia/Web. Use AdaptiveGlass instead:
// Before
LiquidGlass(settings: LiquidGlassSettings(...), child: ...)
// After
AdaptiveGlass(settings: LiquidGlassSettings(...), child: ...)
LiquidGlassLayer, LiquidGlassBlendGroup, LiquidGlassSettings, LiquidShape, GlassGlow, and debugPaintLiquidGlassGeometry remain public.
New Features #
GlassBackdropScopeโ halves GPU blur capture cost when multiple glass surfaces are on screen simultaneously. Wrap yourMaterialApporScaffoldto activate:
GlassBackdropScope(
child: MaterialApp(
home: Scaffold(
appBar: GlassAppBar(...),
bottomNavigationBar: GlassBottomBar(...),
),
),
)
Renderer #
The renderer from liquid_glass_renderer (whynotmake.it, MIT) is now vendored directly, giving full control over the rendering pipeline with no user-facing API changes.
0.4.1 #
Bug Fixes #
- FIX:
GlassBottomBarand other surfaces now correctly respond to dynamicglassSettingschanges onGlassQuality.standardโAdaptiveGlassin grouped mode now inherits settings fromInheritedLiquidGlassinstead of using empty defaults. - FIX: Luminance-aware ambient floor for white glass on
GlassQuality.standardโ high-opacity white glass no longer renders as dark grey.
New #
- FEAT:
GlassBottomBar.iconLabelSpacingโ configurable vertical gap between tab icon and label (default:4.0). Thanks @baneizalfe (#11).
Breaking Changes #
Library-wide IconData โ Widget API migration. All icon parameters now accept any Widget:
// Before
GlassButton(icon: CupertinoIcons.heart, onTap: () {})
// After
GlassButton(icon: Icon(CupertinoIcons.heart), onTap: () {})
// Or any custom widget:
GlassButton(icon: SvgPicture.asset('assets/heart.svg'), onTap: () {})
GlassBottomBarTab.selectedIcon renamed to activeIcon to match Flutter's BottomNavigationBarItem convention.
0.4.0 #
New Components #
GlassMenu/GlassMenuItem/GlassPullDownButtonโ iOS 26 morphing context menu with spring physics and position-aware expansion.GlassButtonGroupโ joined-style container for related actions (e.g. Bold/Italic/Underline toolbar).GlassFormField/GlassPasswordField/GlassTextArea/GlassPickerโ full iOS 26 input suite.GlassSideBarโ vertical navigation surface with header, footer, and scrollable items.GlassToolbarโ standard iOS-style action toolbar.GlassTabBarโ horizontal tab navigation bar with animated indicator and scrollable mode for 5+ tabs.GlassProgressIndicatorโ circular and linear variants (indeterminate and determinate), iOS 26 specs.GlassToast/GlassSnackBarโ 5 notification types, 3 positions, auto-dismiss, swipe-to-dismiss.GlassBadgeโ count and dot status badges, 4 positions.GlassActionSheetโ iOS-style bottom-anchored action list.
Performance #
- Universal Platform Support โ
AdaptiveGlassandAdaptiveLiquidGlassLayerintroduced. All 26 widgets deliver consistent glass quality on Web, Skia, and Impeller. - Batch-blur optimisation โ glass containers share a single
BackdropFilter(was: one per widget). ~5ร faster in common multi-widget layouts. - Impeller pipeline warm-up โ shaders pre-compile at startup to eliminate first-frame jank.
Theme System #
GlassTheme/GlassThemeData/GlassThemeVariantโ global styling and quality inheritance across all widgets. Set once, inherited everywhere.
0.3.0 โ 0.1.0 #
Early access and preview releases establishing the core widget library, initial glass rendering pipeline (LiquidGlass, LiquidGlassLayer, LiquidGlassBlendGroup), and foundational components (GlassBottomBar, GlassButton, GlassSwitch, GlassCard, GlassSearchBar, GlassSlider, GlassChip, GlassSegmentedControl, GlassSheet, GlassDialog, GlassIconButton).