Files
kaptan/ground-suite/lib/shared/widgets/surfaces.dart
T

217 lines
5.5 KiB
Dart

import 'dart:ui';
import 'package:flutter/material.dart';
import '../../app/theme.dart';
/// A blurred surface for stable application chrome only.
///
/// Keep this out of scrollable content. Diagnostic rows and metric panels use
/// [InstrumentPanel], which has the same visual language without creating a
/// compositing layer per item.
class FrostedChrome extends StatelessWidget {
const FrostedChrome({
required this.child,
super.key,
this.borderRadius = BorderRadius.zero,
});
final Widget child;
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
final highContrast = MediaQuery.maybeOf(context)?.highContrast ?? false;
final surface = _SurfaceFrame(
borderRadius: borderRadius,
chrome: true,
opaque: highContrast,
child: child,
);
if (highContrast) {
return surface;
}
return ClipRRect(
borderRadius: borderRadius,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: surface,
),
);
}
}
/// An opaque, high-legibility panel for operational data and event rows.
class InstrumentPanel extends StatelessWidget {
const InstrumentPanel({
required this.child,
super.key,
this.borderRadius = const BorderRadius.all(Radius.circular(12)),
this.accent,
});
final Widget child;
final BorderRadius borderRadius;
final Color? accent;
@override
Widget build(BuildContext context) => _SurfaceFrame(
borderRadius: borderRadius,
accent: accent,
opaque: true,
child: child,
);
}
class _SurfaceFrame extends StatelessWidget {
const _SurfaceFrame({
required this.child,
required this.borderRadius,
this.chrome = false,
this.opaque = false,
this.accent,
});
final Widget child;
final BorderRadius borderRadius;
final bool chrome;
final bool opaque;
final Color? accent;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final dark = scheme.brightness == Brightness.dark;
final opacity = opaque ? 0xf6 : (dark ? 0xbf : 0xd9);
final top = dark
? Color.fromARGB(
opacity,
chrome ? 12 : 16,
chrome ? 17 : 23,
chrome ? 14 : 18,
)
: Color.fromARGB(opacity, 255, 255, 255);
final bottom = dark
? Color.fromARGB(opacity, 8, 11, 9)
: Color.fromARGB(opacity, 244, 248, 244);
final border =
accent?.withValues(alpha: 0.74) ??
scheme.outline.withValues(alpha: dark ? 0.92 : 0.82);
return DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [top, bottom],
),
border: Border.all(color: border),
borderRadius: borderRadius,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: dark ? 0.18 : 0.06),
blurRadius: 18,
offset: const Offset(0, 8),
),
],
),
child: child,
);
}
}
/// The static canvas behind Ground Suite's chrome and instrument surfaces.
class AppCanvas extends StatelessWidget {
const AppCanvas({required this.child, super.key});
final Widget child;
@override
Widget build(BuildContext context) {
final dark = Theme.of(context).brightness == Brightness.dark;
final base = dark ? const Color(0xff080b09) : const Color(0xfff3f6f3);
final primaryHaze = (dark ? accentMint : const Color(0xff087a3b))
.withValues(alpha: dark ? 0.075 : 0.05);
return ColoredBox(
color: base,
child: Stack(
children: [
Positioned(
top: -250,
right: -200,
child: IgnorePointer(child: _Haze(color: primaryHaze)),
),
Positioned(
bottom: -330,
left: -260,
child: IgnorePointer(
child: _Haze(color: primaryHaze.withValues(alpha: 0.55)),
),
),
Positioned.fill(child: child),
],
),
);
}
}
class _Haze extends StatelessWidget {
const _Haze({required this.color});
final Color color;
@override
Widget build(BuildContext context) => Container(
width: 600,
height: 600,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(colors: [color, Colors.transparent]),
),
);
}
class ConsoleLabel extends StatelessWidget {
const ConsoleLabel(this.text, {super.key, this.color});
final String text;
final Color? color;
@override
Widget build(BuildContext context) => Text(
text.toUpperCase(),
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: color ?? Theme.of(context).colorScheme.onSurfaceVariant,
fontFamily: 'monospace',
fontWeight: FontWeight.w700,
letterSpacing: 1.25,
),
);
}
class StatusBadge extends StatelessWidget {
const StatusBadge({required this.label, super.key, this.color});
final String label;
final Color? color;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final tone = color ?? scheme.onSurfaceVariant;
return DecoratedBox(
decoration: BoxDecoration(
color: tone.withValues(alpha: 0.11),
border: Border.all(color: tone.withValues(alpha: 0.45)),
borderRadius: BorderRadius.circular(7),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
child: ConsoleLabel(label, color: tone),
),
);
}
}