The Garage SDKs, in the open
garage_auth, garage_entitlements, garage_iap and garage_ui, moved out of Garage-Services and Metro-Map-Maker into one public repo. MIT, one readme, docs under docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013F4NWNvYcdeSgqbWMT1VQ7
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import "dart:ui" show ImageFilter;
|
||||
|
||||
import "package:flutter/widgets.dart";
|
||||
|
||||
import "package:garage_ui/theme/garage_theme.dart";
|
||||
|
||||
/// The entrance a Garage pane dialog makes: blurred backdrop, dimmed scrim,
|
||||
/// fade and scale in, width clamped to the viewport.
|
||||
///
|
||||
/// This lived twice in Arcs & Angles - once for the titled pane and once for
|
||||
/// the Blender-language one - identical apart from the shell widget wrapped
|
||||
/// around the content. The presentation is the generic half, so it lives here
|
||||
/// and [builder] supplies the shell.
|
||||
///
|
||||
/// [builder] is handed an `onClose` that pops the route, or null when
|
||||
/// [dismissible] is false, so the shell can wire its own close affordance.
|
||||
/// Content pops with its own result the usual way.
|
||||
Future<T?> showPaneOverlay<T>({
|
||||
required BuildContext context,
|
||||
required Widget Function(BuildContext context, VoidCallback? onClose) builder,
|
||||
double maxWidth = 450,
|
||||
bool dismissible = true,
|
||||
String? barrierLabel,
|
||||
Duration duration = const Duration(milliseconds: 180),
|
||||
double blurSigma = 3,
|
||||
double scrimOpacity = 0.35,
|
||||
double enterScale = 0.93,
|
||||
double viewportInset = 24,
|
||||
}) {
|
||||
// read before the route is pushed so it reflects the theme at the call site
|
||||
// rather than whatever sits above the navigator.
|
||||
final scrim = GarageTheme.of(context).colorScheme.background;
|
||||
|
||||
return showGeneralDialog<T>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
barrierColor: const Color(0x00000000),
|
||||
barrierLabel: barrierLabel,
|
||||
transitionDuration: duration,
|
||||
pageBuilder: (ctx, anim, secondaryAnim) => const SizedBox.shrink(),
|
||||
transitionBuilder: (ctx, anim, secondaryAnim, _) {
|
||||
final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut);
|
||||
final scale = Tween<double>(
|
||||
begin: enterScale,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut));
|
||||
|
||||
final screenW = MediaQuery.sizeOf(ctx).width;
|
||||
final maxW = (screenW - viewportInset).clamp(0.0, maxWidth);
|
||||
|
||||
void close() => Navigator.of(ctx).pop();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: FadeTransition(
|
||||
opacity: fade,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: blurSigma, sigmaY: blurSigma),
|
||||
child: GestureDetector(
|
||||
// a dismiss scrim, not a control - Escape and the dialog's
|
||||
// own close button are the reachable ways out. Left in the
|
||||
// tree it reads as a full-screen unnamed button.
|
||||
excludeFromSemantics: true,
|
||||
onTap: dismissible ? close : null,
|
||||
child: ColoredBox(
|
||||
color: scrim.withValues(alpha: scrimOpacity),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: FadeTransition(
|
||||
opacity: fade,
|
||||
child: ScaleTransition(
|
||||
scale: scale,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: maxW, maxWidth: maxW),
|
||||
child: builder(ctx, dismissible ? close : null),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user