@@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart';
1212
1313import 'colors.dart' ;
1414import 'feedback.dart' ;
15+ import 'text_theme.dart' ;
1516import 'theme.dart' ;
1617import 'tooltip_theme.dart' ;
1718import 'tooltip_visibility.dart' ;
@@ -388,23 +389,17 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
388389 static const bool _defaultEnableFeedback = true ;
389390 static const TextAlign _defaultTextAlign = TextAlign .start;
390391
391- late double _height;
392- late EdgeInsetsGeometry _padding;
393- late EdgeInsetsGeometry _margin;
394- late Decoration _decoration;
395- late TextStyle _textStyle;
396- late TextAlign _textAlign;
397- late double _verticalOffset;
398- late bool _preferBelow;
399- late bool _excludeFromSemantics;
400- OverlayEntry ? _entry;
401-
402- late Duration _showDuration;
403- late Duration _hoverShowDuration;
404- late Duration _waitDuration;
405- late TooltipTriggerMode _triggerMode;
406- late bool _enableFeedback;
392+ final OverlayPortalController _overlayController = OverlayPortalController ();
393+
394+ // From InheritedWidgets
407395 late bool _visible;
396+ late TooltipThemeData _tooltipTheme;
397+
398+ Duration get _showDuration => widget.showDuration ?? _tooltipTheme.showDuration ?? _defaultShowDuration;
399+ Duration get _hoverShowDuration => widget.showDuration ?? _tooltipTheme.showDuration ?? _defaultHoverShowDuration;
400+ Duration get _waitDuration => widget.waitDuration ?? _tooltipTheme.waitDuration ?? _defaultWaitDuration;
401+ TooltipTriggerMode get _triggerMode => widget.triggerMode ?? _tooltipTheme.triggerMode ?? _defaultTriggerMode;
402+ bool get _enableFeedback => widget.enableFeedback ?? _tooltipTheme.enableFeedback ?? _defaultEnableFeedback;
408403
409404 /// The plain text message for this tooltip.
410405 ///
@@ -438,14 +433,16 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
438433 case AnimationStatus .dismissed:
439434 entryNeedsUpdating = _animationStatus != AnimationStatus .dismissed;
440435 if (entryNeedsUpdating) {
441- _removeEntry ();
436+ Tooltip ._openedTooltips.remove (this );
437+ _overlayController.hide ();
442438 }
443439 case AnimationStatus .completed:
444440 case AnimationStatus .forward:
445441 case AnimationStatus .reverse:
446442 entryNeedsUpdating = _animationStatus == AnimationStatus .dismissed;
447443 if (entryNeedsUpdating) {
448- _createNewEntry ();
444+ _overlayController.show ();
445+ Tooltip ._openedTooltips.add (this );
449446 SemanticsService .tooltip (_tooltipMessage);
450447 }
451448 }
@@ -620,11 +617,6 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
620617 // (even these tooltips are still hovered),
621618 // iii. The last hovering device leaves the tooltip.
622619 void _handleMouseEnter (PointerEnterEvent event) {
623- // The callback is also used in an OverlayEntry, so there's a chance that
624- // this widget is already unmounted.
625- if (! mounted) {
626- return ;
627- }
628620 // _handleMouseEnter is only called when the mouse starts to hover over this
629621 // tooltip (including the actual tooltip it shows on the overlay), and this
630622 // tooltip is the first to be hit in the widget tree's hit testing order.
@@ -646,7 +638,7 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
646638 }
647639
648640 void _handleMouseExit (PointerExitEvent event) {
649- if (! mounted || _activeHoveringPointerDevices.isEmpty) {
641+ if (_activeHoveringPointerDevices.isEmpty) {
650642 return ;
651643 }
652644 _activeHoveringPointerDevices.remove (event.device);
@@ -694,6 +686,7 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
694686 void didChangeDependencies () {
695687 super .didChangeDependencies ();
696688 _visible = TooltipVisibility .of (context);
689+ _tooltipTheme = TooltipTheme .of (context);
697690 }
698691
699692 // https://material.io/components/tooltips#specs
@@ -719,8 +712,8 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
719712 };
720713 }
721714
722- double _getDefaultFontSize () {
723- return switch (Theme . of (context). platform) {
715+ static double _getDefaultFontSize (TargetPlatform platform ) {
716+ return switch (platform) {
724717 TargetPlatform .macOS ||
725718 TargetPlatform .linux ||
726719 TargetPlatform .windows => 12.0 ,
@@ -730,58 +723,50 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
730723 };
731724 }
732725
733- void _createNewEntry () {
734- final OverlayState overlayState = Overlay .of (
735- context,
736- debugRequiredFor: widget,
737- );
738-
739- final RenderBox box = context.findRenderObject ()! as RenderBox ;
726+ Widget _buildTooltipOverlay (BuildContext context) {
727+ final OverlayState overlayState = Overlay .of (context, debugRequiredFor: widget);
728+ final RenderBox box = this .context.findRenderObject ()! as RenderBox ;
740729 final Offset target = box.localToGlobal (
741730 box.size.center (Offset .zero),
742731 ancestor: overlayState.context.findRenderObject (),
743732 );
744733
745- // We create this widget outside of the overlay entry's builder to prevent
746- // updated values from happening to leak into the overlay when the overlay
747- // rebuilds.
748- final Widget overlay = Directionality (
749- textDirection: Directionality .of (context),
750- child: _TooltipOverlay (
751- richMessage: widget.richMessage ?? TextSpan (text: widget.message),
752- height: _height,
753- padding: _padding,
754- margin: _margin,
755- onEnter: _handleMouseEnter,
756- onExit: _handleMouseExit,
757- decoration: _decoration,
758- textStyle: _textStyle,
759- textAlign: _textAlign,
760- animation: CurvedAnimation (
761- parent: _controller,
762- curve: Curves .fastOutSlowIn,
763- ),
764- target: target,
765- verticalOffset: _verticalOffset,
766- preferBelow: _preferBelow,
734+ final (TextStyle defaultTextStyle, BoxDecoration defaultDecoration) = switch (Theme .of (context)) {
735+ ThemeData (brightness: Brightness .dark, : final TextTheme textTheme, : final TargetPlatform platform) => (
736+ textTheme.bodyMedium! .copyWith (color: Colors .black, fontSize: _getDefaultFontSize (platform)),
737+ BoxDecoration (color: Colors .white.withOpacity (0.9 ), borderRadius: const BorderRadius .all (Radius .circular (4 ))),
767738 ),
768- );
769- final OverlayEntry entry = _entry = OverlayEntry (builder : ( BuildContext context) => overlay);
770- overlayState. insert (entry);
771- Tooltip ._openedTooltips. add ( this );
772- }
739+ ThemeData (brightness : Brightness .light, : final TextTheme textTheme, : final TargetPlatform platform) => (
740+ textTheme.bodyMedium ! . copyWith (color : Colors .white, fontSize : _getDefaultFontSize (platform)),
741+ BoxDecoration (color : Colors .grey[ 700 ] ! . withOpacity ( 0.9 ), borderRadius : const BorderRadius . all ( Radius . circular ( 4 ))),
742+ ),
743+ };
773744
774- void _removeEntry () {
775- Tooltip ._openedTooltips.remove (this );
776- _entry? .remove ();
777- _entry? .dispose ();
778- _entry = null ;
745+ final TooltipThemeData tooltipTheme = _tooltipTheme;
746+ return _TooltipOverlay (
747+ richMessage: widget.richMessage ?? TextSpan (text: widget.message),
748+ height: widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight (),
749+ padding: widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding (),
750+ margin: widget.margin ?? tooltipTheme.margin ?? _defaultMargin,
751+ onEnter: _handleMouseEnter,
752+ onExit: _handleMouseExit,
753+ decoration: widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration,
754+ textStyle: widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle,
755+ textAlign: widget.textAlign ?? tooltipTheme.textAlign ?? _defaultTextAlign,
756+ animation: CurvedAnimation (
757+ parent: _controller,
758+ curve: Curves .fastOutSlowIn,
759+ ),
760+ target: target,
761+ verticalOffset: widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset,
762+ preferBelow: widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow,
763+ );
779764 }
780765
781766 @override
782767 void dispose () {
783768 GestureBinding .instance.pointerRouter.removeGlobalRoute (_handleGlobalPointerEvent);
784- _removeEntry ( );
769+ Tooltip ._openedTooltips. remove ( this );
785770 _longPressRecognizer? .dispose ();
786771 _tapRecognizer? .dispose ();
787772 _timer? .cancel ();
@@ -798,47 +783,9 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
798783 return widget.child ?? const SizedBox .shrink ();
799784 }
800785 assert (debugCheckHasOverlay (context));
801- final ThemeData theme = Theme .of (context);
802- final TooltipThemeData tooltipTheme = TooltipTheme .of (context);
803- final TextStyle defaultTextStyle;
804- final BoxDecoration defaultDecoration;
805- if (theme.brightness == Brightness .dark) {
806- defaultTextStyle = theme.textTheme.bodyMedium! .copyWith (
807- color: Colors .black,
808- fontSize: _getDefaultFontSize (),
809- );
810- defaultDecoration = BoxDecoration (
811- color: Colors .white.withOpacity (0.9 ),
812- borderRadius: const BorderRadius .all (Radius .circular (4 )),
813- );
814- } else {
815- defaultTextStyle = theme.textTheme.bodyMedium! .copyWith (
816- color: Colors .white,
817- fontSize: _getDefaultFontSize (),
818- );
819- defaultDecoration = BoxDecoration (
820- color: Colors .grey[700 ]! .withOpacity (0.9 ),
821- borderRadius: const BorderRadius .all (Radius .circular (4 )),
822- );
823- }
824-
825- _height = widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight ();
826- _padding = widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding ();
827- _margin = widget.margin ?? tooltipTheme.margin ?? _defaultMargin;
828- _verticalOffset = widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset;
829- _preferBelow = widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow;
830- _excludeFromSemantics = widget.excludeFromSemantics ?? tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics;
831- _decoration = widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration;
832- _textStyle = widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle;
833- _textAlign = widget.textAlign ?? tooltipTheme.textAlign ?? _defaultTextAlign;
834- _waitDuration = widget.waitDuration ?? tooltipTheme.waitDuration ?? _defaultWaitDuration;
835- _showDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultShowDuration;
836- _hoverShowDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultHoverShowDuration;
837- _triggerMode = widget.triggerMode ?? tooltipTheme.triggerMode ?? _defaultTriggerMode;
838- _enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback;
839-
786+ final bool excludeFromSemantics = widget.excludeFromSemantics ?? _tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics;
840787 Widget result = Semantics (
841- tooltip: _excludeFromSemantics ? null : _tooltipMessage,
788+ tooltip: excludeFromSemantics ? null : _tooltipMessage,
842789 child: widget.child,
843790 );
844791
@@ -854,8 +801,11 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
854801 ),
855802 );
856803 }
857-
858- return result;
804+ return OverlayPortal (
805+ controller: _overlayController,
806+ overlayChildBuilder: _buildTooltipOverlay,
807+ child: result,
808+ );
859809 }
860810}
861811
0 commit comments