Raise the declared minimums in pubspec.yaml to what our deps already require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist 0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is the binding floor. Pin the exact build toolchain in .fvmrc (Flutter 3.44.1). Moving to the Dart 3.9 language level switches `dart format` to the new "tall" style and enables two new lints. This commit is the resulting mechanical churn, isolated from any behaviour change: - whole-tree `dart format` reformat (tall style) - `dart fix` for unnecessary_underscores + use_null_aware_elements No runtime behaviour change; `make test` green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
85 lines
2.3 KiB
Dart
85 lines
2.3 KiB
Dart
import 'package:clide/kernel/src/theme/controller.dart';
|
|
import 'package:clide/widgets/src/clide_text.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
class ClideTooltip extends StatefulWidget {
|
|
const ClideTooltip({super.key, required this.message, required this.child, this.showDelay = const Duration(milliseconds: 500)});
|
|
|
|
final String message;
|
|
final Widget child;
|
|
final Duration showDelay;
|
|
|
|
@override
|
|
State<ClideTooltip> createState() => _ClideTooltipState();
|
|
}
|
|
|
|
class _ClideTooltipState extends State<ClideTooltip> {
|
|
OverlayEntry? _entry;
|
|
bool _hovering = false;
|
|
|
|
void _show() {
|
|
final overlay = Overlay.maybeOf(context);
|
|
if (overlay == null) return;
|
|
_entry?.remove();
|
|
final box = context.findRenderObject() as RenderBox?;
|
|
if (box == null) return;
|
|
final target = box.localToGlobal(Offset.zero);
|
|
final size = box.size;
|
|
final screenSize = MediaQuery.of(context).size;
|
|
|
|
_entry = OverlayEntry(
|
|
builder: (ctx) {
|
|
final tokens = ClideTheme.of(ctx).surface;
|
|
final tooltip = Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: tokens.tooltipBackground,
|
|
border: Border.all(color: tokens.tooltipBorder),
|
|
borderRadius: BorderRadius.circular(3),
|
|
),
|
|
child: ClideText(widget.message, color: tokens.tooltipForeground, fontSize: 12),
|
|
);
|
|
|
|
final spaceBelow = screenSize.height - target.dy - size.height;
|
|
final showAbove = spaceBelow < 60;
|
|
|
|
if (showAbove) {
|
|
return Positioned(left: target.dx, bottom: screenSize.height - target.dy + 4, child: tooltip);
|
|
}
|
|
return Positioned(left: target.dx, top: target.dy + size.height + 4, child: tooltip);
|
|
},
|
|
);
|
|
overlay.insert(_entry!);
|
|
}
|
|
|
|
void _hide() {
|
|
_entry?.remove();
|
|
_entry = null;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_hide();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Semantics(
|
|
tooltip: widget.message,
|
|
child: MouseRegion(
|
|
onEnter: (_) async {
|
|
_hovering = true;
|
|
await Future<void>.delayed(widget.showDelay);
|
|
if (mounted && _hovering) _show();
|
|
},
|
|
onExit: (_) {
|
|
_hovering = false;
|
|
_hide();
|
|
},
|
|
child: widget.child,
|
|
),
|
|
);
|
|
}
|
|
}
|