import 'package:clide/widgets/src/clide_settings.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 createState() => _ClideTooltipState(); } class _ClideTooltipState extends State { 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 = ClideSettings.theme.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.delayed(widget.showDelay); if (mounted && _hovering) _show(); }, onExit: (_) { _hovering = false; _hide(); }, child: widget.child, ), ); } }