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
92 lines
2.5 KiB
Dart
92 lines
2.5 KiB
Dart
import "package:flutter/widgets.dart";
|
|
|
|
import "package:garage_ui/theme/garage_theme.dart";
|
|
|
|
// A chrome panel - the bordered, rounded surface the shell docks things in.
|
|
// Fills with the scheme's panel colour and lights its border with the
|
|
// highlighted colour when active.
|
|
//
|
|
// Two modes:
|
|
// - standalone: leave [active] null and it tracks its own hover.
|
|
// - controlled: pass [active] and the parent drives the lit state (used by the
|
|
// shell so it can keep the panels mutually exclusive - only one lit at once,
|
|
// which also dodges flutter dropping an onExit during a fast mouse move and
|
|
// leaving a panel stuck on).
|
|
class Panel extends StatefulWidget {
|
|
const Panel({
|
|
super.key,
|
|
required this.child,
|
|
this.active,
|
|
this.borderRadius,
|
|
this.borderWidth = 1.15,
|
|
this.clip = true,
|
|
this.clipBehavior = Clip.antiAlias,
|
|
});
|
|
|
|
final Widget child;
|
|
|
|
// null = self-track hover; non-null = parent controls the lit state.
|
|
final bool? active;
|
|
|
|
// null = use the theme's panelRadius.
|
|
final double? borderRadius;
|
|
final double borderWidth;
|
|
|
|
// clip the child to the rounded corners.
|
|
final bool clip;
|
|
final Clip clipBehavior;
|
|
|
|
@override
|
|
State<Panel> createState() => _PanelState();
|
|
}
|
|
|
|
class _PanelState extends State<Panel> {
|
|
bool _hovered = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = GarageTheme.of(context);
|
|
final cs = theme.colorScheme;
|
|
final controlled = widget.active != null;
|
|
final lit = controlled ? widget.active! : _hovered;
|
|
final radius = BorderRadius.circular(
|
|
widget.borderRadius ?? theme.panelRadius,
|
|
);
|
|
|
|
Widget content = widget.child;
|
|
if (widget.clip) {
|
|
final clipRadius = BorderRadius.circular(
|
|
((widget.borderRadius ?? theme.panelRadius) - 1)
|
|
.clamp(0, double.infinity)
|
|
.toDouble(),
|
|
);
|
|
content = ClipRRect(
|
|
borderRadius: clipRadius,
|
|
clipBehavior: widget.clipBehavior,
|
|
child: content,
|
|
);
|
|
}
|
|
|
|
Widget box = Container(
|
|
decoration: BoxDecoration(
|
|
color: cs.background,
|
|
borderRadius: radius,
|
|
border: Border.all(
|
|
color: lit ? cs.panelBorderHighlighted : cs.panelBorder,
|
|
width: widget.borderWidth,
|
|
),
|
|
),
|
|
child: content,
|
|
);
|
|
|
|
// controlled panels let the parent own the MouseRegion.
|
|
if (controlled) return box;
|
|
|
|
return MouseRegion(
|
|
onEnter: (_) => setState(() => _hovered = true),
|
|
onExit: (_) => setState(() => _hovered = false),
|
|
child: box,
|
|
);
|
|
}
|
|
}
|