From f846729c614dcdc0b2a2e0c156dc9250b6e7a59c Mon Sep 17 00:00:00 2001 From: Ryan Napolitano Date: Fri, 30 Aug 2024 12:40:10 -0400 Subject: [PATCH 1/3] Add external control for tooltip animation --- .history/.gitignore_20240830121146 | 15 + .history/.gitignore_20240830122219 | 15 + .../lib/src/super_tooltip_20240830121146.dart | 569 +++++++++++++++++ .../lib/src/super_tooltip_20240830122654.dart | 576 ++++++++++++++++++ .../lib/src/super_tooltip_20240830123422.dart | 572 +++++++++++++++++ ...per_tooltip_controller_20240830121146.dart | 33 + ...per_tooltip_controller_20240830122654.dart | 38 ++ ...per_tooltip_controller_20240830122709.dart | 38 ++ ...per_tooltip_controller_20240830122857.dart | 38 ++ ...per_tooltip_controller_20240830123422.dart | 38 ++ ...per_tooltip_controller_20240830123454.dart | 37 ++ ...per_tooltip_controller_20240830123526.dart | 37 ++ ...per_tooltip_controller_20240830123535.dart | 37 ++ ...per_tooltip_controller_20240830123552.dart | 37 ++ lib/src/super_tooltip.dart | 19 +- lib/src/super_tooltip_controller.dart | 6 +- 16 files changed, 2096 insertions(+), 9 deletions(-) create mode 100644 .history/.gitignore_20240830121146 create mode 100644 .history/.gitignore_20240830122219 create mode 100644 .history/lib/src/super_tooltip_20240830121146.dart create mode 100644 .history/lib/src/super_tooltip_20240830122654.dart create mode 100644 .history/lib/src/super_tooltip_20240830123422.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830121146.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830122654.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830122709.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830122857.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830123422.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830123454.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830123526.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830123535.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830123552.dart diff --git a/.history/.gitignore_20240830121146 b/.history/.gitignore_20240830121146 new file mode 100644 index 0000000..89fad00 --- /dev/null +++ b/.history/.gitignore_20240830121146 @@ -0,0 +1,15 @@ +.DS_Store +.atom/ +.vscode/ +.dart_tool/ +.idea +.packages +.pub/ +packages +ios/Runner/GeneratedPluginRegistrant.h +ios/Runner/GeneratedPluginRegistrant.m +pubspec.lock +.vscode +example/flutter_weather_demo/pubspec.lock +/*.code-workspace +example/android.iml diff --git a/.history/.gitignore_20240830122219 b/.history/.gitignore_20240830122219 new file mode 100644 index 0000000..89fad00 --- /dev/null +++ b/.history/.gitignore_20240830122219 @@ -0,0 +1,15 @@ +.DS_Store +.atom/ +.vscode/ +.dart_tool/ +.idea +.packages +.pub/ +packages +ios/Runner/GeneratedPluginRegistrant.h +ios/Runner/GeneratedPluginRegistrant.m +pubspec.lock +.vscode +example/flutter_weather_demo/pubspec.lock +/*.code-workspace +example/android.iml diff --git a/.history/lib/src/super_tooltip_20240830121146.dart b/.history/lib/src/super_tooltip_20240830121146.dart new file mode 100644 index 0000000..b09fee8 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830121146.dart @@ -0,0 +1,569 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830122654.dart b/.history/lib/src/super_tooltip_20240830122654.dart new file mode 100644 index 0000000..2e95464 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830122654.dart @@ -0,0 +1,576 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + void _showTooltip() async { + // If externalControl is true, don't proceed with showing tooltip based on tap + if (!_superTooltipController!.externalControlOnly) { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + +void _hideTooltip() async { + // If externalControl is true, don't proceed with hiding tooltip based on tap + if (!_superTooltipController!.externalControlOnly) { + widget.onHide?.call(); + + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830123422.dart b/.history/lib/src/super_tooltip_20240830123422.dart new file mode 100644 index 0000000..e06cd6d --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830123422.dart @@ -0,0 +1,572 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + void _showTooltip() async { + // If externalControl is true, don't proceed with showing tooltip based on tap + if (!_superTooltipController!.externalControlOnly) { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_controller_20240830121146.dart b/.history/lib/src/super_tooltip_controller_20240830121146.dart new file mode 100644 index 0000000..cea7355 --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830121146.dart @@ -0,0 +1,33 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + + late Event event; + + Future showTooltip() { + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} diff --git a/.history/lib/src/super_tooltip_controller_20240830122654.dart b/.history/lib/src/super_tooltip_controller_20240830122654.dart new file mode 100644 index 0000000..bfa626b --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830122654.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = false; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830122709.dart b/.history/lib/src/super_tooltip_controller_20240830122709.dart new file mode 100644 index 0000000..ec6378b --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830122709.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = true; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830122857.dart b/.history/lib/src/super_tooltip_controller_20240830122857.dart new file mode 100644 index 0000000..eb9a8c8 --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830122857.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = true; + + late Event event; + + Future showTooltip({bool external = true}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip({bool external = true}) { + externalControlOnly = external; + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830123422.dart b/.history/lib/src/super_tooltip_controller_20240830123422.dart new file mode 100644 index 0000000..65709e0 --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830123422.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = true; + + late Event event; + + Future showTooltip({bool external = true}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830123454.dart b/.history/lib/src/super_tooltip_controller_20240830123454.dart new file mode 100644 index 0000000..a7d3cae --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830123454.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = true; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830123526.dart b/.history/lib/src/super_tooltip_controller_20240830123526.dart new file mode 100644 index 0000000..8355ba4 --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830123526.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = true; + + late Event event; + + Future showTooltip({bool external = true}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830123535.dart b/.history/lib/src/super_tooltip_controller_20240830123535.dart new file mode 100644 index 0000000..a7d3cae --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830123535.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = true; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830123552.dart b/.history/lib/src/super_tooltip_controller_20240830123552.dart new file mode 100644 index 0000000..7fa56df --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830123552.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = false; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/lib/src/super_tooltip.dart b/lib/src/super_tooltip.dart index b09fee8..e06cd6d 100644 --- a/lib/src/super_tooltip.dart +++ b/lib/src/super_tooltip.dart @@ -457,17 +457,20 @@ class _SuperTooltipState extends State } } - _showTooltip() async { - widget.onShow?.call(); + void _showTooltip() async { + // If externalControl is true, don't proceed with showing tooltip based on tap + if (!_superTooltipController!.externalControlOnly) { + widget.onShow?.call(); - // Already visible. - if (_entry != null) return; + // Already visible. + if (_entry != null) return; - _createOverlayEntries(); + _createOverlayEntries(); - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } } _removeEntries() { diff --git a/lib/src/super_tooltip_controller.dart b/lib/src/super_tooltip_controller.dart index cea7355..7fa56df 100644 --- a/lib/src/super_tooltip_controller.dart +++ b/lib/src/super_tooltip_controller.dart @@ -8,10 +8,13 @@ class SuperTooltipController extends ChangeNotifier { late Completer _completer; bool _isVisible = false; bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = false; late Event event; - Future showTooltip() { + Future showTooltip({bool external = false}) { + externalControlOnly = external; event = Event.show; _completer = Completer(); notifyListeners(); @@ -31,3 +34,4 @@ class SuperTooltipController extends ChangeNotifier { } } } + From b6bf5778d52046e4c1b5e27334798954d44ba7d0 Mon Sep 17 00:00:00 2001 From: Ryan Napolitano Date: Fri, 30 Aug 2024 14:06:08 -0400 Subject: [PATCH 2/3] Revert and instead rely on boolean property --- .history/example/lib/main_20240830121146.dart | 113 ++++ .history/example/lib/main_20240830132520.dart | 116 ++++ .history/example/lib/main_20240830133748.dart | 113 ++++ .history/example/lib/main_20240830133918.dart | 113 ++++ .history/example/lib/main_20240830133953.dart | 113 ++++ .../lib/src/super_tooltip_20240830125756.dart | 586 ++++++++++++++++++ .../lib/src/super_tooltip_20240830130535.dart | 572 +++++++++++++++++ .../lib/src/super_tooltip_20240830131523.dart | 575 +++++++++++++++++ .../lib/src/super_tooltip_20240830131705.dart | 578 +++++++++++++++++ .../lib/src/super_tooltip_20240830132213.dart | 578 +++++++++++++++++ .../lib/src/super_tooltip_20240830133612.dart | 571 +++++++++++++++++ .../lib/src/super_tooltip_20240830133953.dart | 569 +++++++++++++++++ .../lib/src/super_tooltip_20240830134254.dart | 572 +++++++++++++++++ .../lib/src/super_tooltip_20240830140105.dart | 573 +++++++++++++++++ .../lib/src/super_tooltip_20240830140124.dart | 573 +++++++++++++++++ .../lib/src/super_tooltip_20240830140139.dart | 573 +++++++++++++++++ .../lib/src/super_tooltip_20240830140344.dart | 574 +++++++++++++++++ .../lib/src/super_tooltip_20240830140347.dart | 574 +++++++++++++++++ .../lib/src/super_tooltip_20240830140406.dart | 573 +++++++++++++++++ .../lib/src/super_tooltip_20240830140450.dart | 573 +++++++++++++++++ .../lib/src/super_tooltip_20240830140457.dart | 573 +++++++++++++++++ ...per_tooltip_controller_20240830125828.dart | 37 ++ ...per_tooltip_controller_20240830130556.dart | 37 ++ ...per_tooltip_controller_20240830132235.dart | 37 ++ ...per_tooltip_controller_20240830132556.dart | 38 ++ ...per_tooltip_controller_20240830132612.dart | 38 ++ ...per_tooltip_controller_20240830140105.dart | 33 + lib/src/super_tooltip.dart | 33 +- lib/src/super_tooltip_controller.dart | 6 +- 29 files changed, 9993 insertions(+), 21 deletions(-) create mode 100644 .history/example/lib/main_20240830121146.dart create mode 100644 .history/example/lib/main_20240830132520.dart create mode 100644 .history/example/lib/main_20240830133748.dart create mode 100644 .history/example/lib/main_20240830133918.dart create mode 100644 .history/example/lib/main_20240830133953.dart create mode 100644 .history/lib/src/super_tooltip_20240830125756.dart create mode 100644 .history/lib/src/super_tooltip_20240830130535.dart create mode 100644 .history/lib/src/super_tooltip_20240830131523.dart create mode 100644 .history/lib/src/super_tooltip_20240830131705.dart create mode 100644 .history/lib/src/super_tooltip_20240830132213.dart create mode 100644 .history/lib/src/super_tooltip_20240830133612.dart create mode 100644 .history/lib/src/super_tooltip_20240830133953.dart create mode 100644 .history/lib/src/super_tooltip_20240830134254.dart create mode 100644 .history/lib/src/super_tooltip_20240830140105.dart create mode 100644 .history/lib/src/super_tooltip_20240830140124.dart create mode 100644 .history/lib/src/super_tooltip_20240830140139.dart create mode 100644 .history/lib/src/super_tooltip_20240830140344.dart create mode 100644 .history/lib/src/super_tooltip_20240830140347.dart create mode 100644 .history/lib/src/super_tooltip_20240830140406.dart create mode 100644 .history/lib/src/super_tooltip_20240830140450.dart create mode 100644 .history/lib/src/super_tooltip_20240830140457.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830125828.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830130556.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830132235.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830132556.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830132612.dart create mode 100644 .history/lib/src/super_tooltip_controller_20240830140105.dart diff --git a/.history/example/lib/main_20240830121146.dart b/.history/example/lib/main_20240830121146.dart new file mode 100644 index 0000000..f49ae52 --- /dev/null +++ b/.history/example/lib/main_20240830121146.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:super_tooltip/super_tooltip.dart'; + +void main() => runApp(const MainApp()); + +class MainApp extends StatelessWidget { + const MainApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Super Tooltip Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const ExamplePage(), + ); + } +} + +class ExamplePage extends StatefulWidget { + const ExamplePage({ + Key? key, + }) : super(key: key); + @override + State createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: TargetWidget(), + ), + ); + } +} + +class TargetWidget extends StatefulWidget { + const TargetWidget({Key? key}) : super(key: key); + + @override + State createState() => _TargetWidgetState(); +} + +class _TargetWidgetState extends State { + final _controller = SuperTooltipController(); + Future? _willPopCallback() async { + // If the tooltip is open we don't pop the page on a backbutton press + // but close the ToolTip + if (_controller.isVisible) { + await _controller.hideTooltip(); + return false; + } + return true; + } + + @override + Widget build(BuildContext context) { + return PopScope( + onPopInvoked: (didPop) => _willPopCallback, + child: GestureDetector( + onTap: () async { + await _controller.showTooltip(); + }, + child: SuperTooltip( + showBarrier: true, + controller: _controller, + popupDirection: TooltipDirection.down, + backgroundColor: Color(0xff2f2d2f), + left: 30, + right: 30, + arrowTipDistance: 15.0, + arrowBaseWidth: 20.0, + arrowLength: 20.0, + borderWidth: 2.0, + constraints: const BoxConstraints( + minHeight: 0.0, + maxHeight: 100, + minWidth: 0.0, + maxWidth: 100, + ), + touchThroughAreaShape: ClipAreaShape.rectangle, + touchThroughAreaCornerRadius: 30, + barrierColor: Color.fromARGB(26, 47, 45, 47), + content: const Text( + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", + softWrap: true, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + ), + ), + child: Container( + width: 40.0, + height: 40.0, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, + ), + child: Icon( + Icons.add, + color: Colors.white, + ), + ), + ), + ), + ); + } +} diff --git a/.history/example/lib/main_20240830132520.dart b/.history/example/lib/main_20240830132520.dart new file mode 100644 index 0000000..18c2853 --- /dev/null +++ b/.history/example/lib/main_20240830132520.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:super_tooltip/super_tooltip.dart'; + +void main() => runApp(const MainApp()); + +class MainApp extends StatelessWidget { + const MainApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Super Tooltip Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const ExamplePage(), + ); + } +} + +class ExamplePage extends StatefulWidget { + const ExamplePage({ + Key? key, + }) : super(key: key); + @override + State createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: TargetWidget(), + ), + ); + } +} + +class TargetWidget extends StatefulWidget { + const TargetWidget({Key? key}) : super(key: key); + + @override + State createState() => _TargetWidgetState(); +} + +class _TargetWidgetState extends State { + final _controller = SuperTooltipController(); + Future? _willPopCallback() async { + // If the tooltip is open we don't pop the page on a backbutton press + // but close the ToolTip + if (_controller.isVisible) { + await _controller.hideTooltip(); + return false; + } + return true; + } + + @override + Widget build(BuildContext context) { + return PopScope( + onPopInvoked: (didPop) => _willPopCallback, + child: GestureDetector( + onTap: () async { + await _controller.showTooltip(); + }, + child: SuperTooltip( + showBarrier: true, + controller: _controller, + popupDirection: TooltipDirection.down, + backgroundColor: Color(0xff2f2d2f), + left: 30, + right: 30, + arrowTipDistance: 15.0, + arrowBaseWidth: 20.0, + arrowLength: 20.0, + borderWidth: 2.0, + constraints: const BoxConstraints( + minHeight: 0.0, + maxHeight: 100, + minWidth: 0.0, + maxWidth: 100, + ), + touchThroughAreaShape: ClipAreaShape.rectangle, + touchThroughAreaCornerRadius: 30, + barrierColor: Color.fromARGB(26, 47, 45, 47), + onShow: () { + _controller.showTooltip(external: true); + }, + content: const Text( + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", + softWrap: true, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + ), + ), + child: Container( + width: 40.0, + height: 40.0, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, + ), + child: Icon( + Icons.add, + color: Colors.white, + ), + ), + ), + ), + ); + } +} diff --git a/.history/example/lib/main_20240830133748.dart b/.history/example/lib/main_20240830133748.dart new file mode 100644 index 0000000..d13e1b8 --- /dev/null +++ b/.history/example/lib/main_20240830133748.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:super_tooltip/super_tooltip.dart'; + +void main() => runApp(const MainApp()); + +class MainApp extends StatelessWidget { + const MainApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Super Tooltip Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const ExamplePage(), + ); + } +} + +class ExamplePage extends StatefulWidget { + const ExamplePage({ + Key? key, + }) : super(key: key); + @override + State createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: TargetWidget(), + ), + ); + } +} + +class TargetWidget extends StatefulWidget { + const TargetWidget({Key? key}) : super(key: key); + + @override + State createState() => _TargetWidgetState(); +} + +class _TargetWidgetState extends State { + final _controller = SuperTooltipController(); + Future? _willPopCallback() async { + // If the tooltip is open we don't pop the page on a backbutton press + // but close the ToolTip + if (_controller.isVisible) { + await _controller.hideTooltip(); + return false; + } + return true; + } + + @override + Widget build(BuildContext context) { + return PopScope( + onPopInvoked: (didPop) => _willPopCallback, + child: GestureDetector( + onTap: () async { + await _controller.showTooltip(external: true); + }, + child: SuperTooltip( + showBarrier: true, + controller: _controller, + popupDirection: TooltipDirection.down, + backgroundColor: Color(0xff2f2d2f), + left: 30, + right: 30, + arrowTipDistance: 15.0, + arrowBaseWidth: 20.0, + arrowLength: 20.0, + borderWidth: 2.0, + constraints: const BoxConstraints( + minHeight: 0.0, + maxHeight: 100, + minWidth: 0.0, + maxWidth: 100, + ), + touchThroughAreaShape: ClipAreaShape.rectangle, + touchThroughAreaCornerRadius: 30, + barrierColor: Color.fromARGB(26, 47, 45, 47), + content: const Text( + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", + softWrap: true, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + ), + ), + child: Container( + width: 40.0, + height: 40.0, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, + ), + child: Icon( + Icons.add, + color: Colors.white, + ), + ), + ), + ), + ); + } +} diff --git a/.history/example/lib/main_20240830133918.dart b/.history/example/lib/main_20240830133918.dart new file mode 100644 index 0000000..ae5e603 --- /dev/null +++ b/.history/example/lib/main_20240830133918.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:super_tooltip/super_tooltip.dart'; + +void main() => runApp(const MainApp()); + +class MainApp extends StatelessWidget { + const MainApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Super Tooltip Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const ExamplePage(), + ); + } +} + +class ExamplePage extends StatefulWidget { + const ExamplePage({ + Key? key, + }) : super(key: key); + @override + State createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: TargetWidget(), + ), + ); + } +} + +class TargetWidget extends StatefulWidget { + const TargetWidget({Key? key}) : super(key: key); + + @override + State createState() => _TargetWidgetState(); +} + +class _TargetWidgetState extends State { + final _controller = SuperTooltipController(); + Future? _willPopCallback() async { + // If the tooltip is open we don't pop the page on a backbutton press + // but close the ToolTip + if (_controller.isVisible) { + await _controller.hideTooltip(); + return false; + } + return true; + } + + @override + Widget build(BuildContext context) { + return PopScope( + onPopInvoked: (didPop) => _willPopCallback, + child: GestureDetector( + onTap: () async { + await _controller.showTooltip(external: false); + }, + child: SuperTooltip( + showBarrier: true, + controller: _controller, + popupDirection: TooltipDirection.down, + backgroundColor: Color(0xff2f2d2f), + left: 30, + right: 30, + arrowTipDistance: 15.0, + arrowBaseWidth: 20.0, + arrowLength: 20.0, + borderWidth: 2.0, + constraints: const BoxConstraints( + minHeight: 0.0, + maxHeight: 100, + minWidth: 0.0, + maxWidth: 100, + ), + touchThroughAreaShape: ClipAreaShape.rectangle, + touchThroughAreaCornerRadius: 30, + barrierColor: Color.fromARGB(26, 47, 45, 47), + content: const Text( + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", + softWrap: true, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + ), + ), + child: Container( + width: 40.0, + height: 40.0, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, + ), + child: Icon( + Icons.add, + color: Colors.white, + ), + ), + ), + ), + ); + } +} diff --git a/.history/example/lib/main_20240830133953.dart b/.history/example/lib/main_20240830133953.dart new file mode 100644 index 0000000..f49ae52 --- /dev/null +++ b/.history/example/lib/main_20240830133953.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:super_tooltip/super_tooltip.dart'; + +void main() => runApp(const MainApp()); + +class MainApp extends StatelessWidget { + const MainApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Super Tooltip Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const ExamplePage(), + ); + } +} + +class ExamplePage extends StatefulWidget { + const ExamplePage({ + Key? key, + }) : super(key: key); + @override + State createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: TargetWidget(), + ), + ); + } +} + +class TargetWidget extends StatefulWidget { + const TargetWidget({Key? key}) : super(key: key); + + @override + State createState() => _TargetWidgetState(); +} + +class _TargetWidgetState extends State { + final _controller = SuperTooltipController(); + Future? _willPopCallback() async { + // If the tooltip is open we don't pop the page on a backbutton press + // but close the ToolTip + if (_controller.isVisible) { + await _controller.hideTooltip(); + return false; + } + return true; + } + + @override + Widget build(BuildContext context) { + return PopScope( + onPopInvoked: (didPop) => _willPopCallback, + child: GestureDetector( + onTap: () async { + await _controller.showTooltip(); + }, + child: SuperTooltip( + showBarrier: true, + controller: _controller, + popupDirection: TooltipDirection.down, + backgroundColor: Color(0xff2f2d2f), + left: 30, + right: 30, + arrowTipDistance: 15.0, + arrowBaseWidth: 20.0, + arrowLength: 20.0, + borderWidth: 2.0, + constraints: const BoxConstraints( + minHeight: 0.0, + maxHeight: 100, + minWidth: 0.0, + maxWidth: 100, + ), + touchThroughAreaShape: ClipAreaShape.rectangle, + touchThroughAreaCornerRadius: 30, + barrierColor: Color.fromARGB(26, 47, 45, 47), + content: const Text( + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", + softWrap: true, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + ), + ), + child: Container( + width: 40.0, + height: 40.0, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, + ), + child: Icon( + Icons.add, + color: Colors.white, + ), + ), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830125756.dart b/.history/lib/src/super_tooltip_20240830125756.dart new file mode 100644 index 0000000..7c96985 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830125756.dart @@ -0,0 +1,586 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + +void _showTooltip() async { + // If externalControlOnly is true, proceed with showing tooltip only if triggered externally + if (_superTooltipController!.externalControlOnly) { + // Tooltip should only be shown if externalControlOnly is set to true and is triggered externally + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } else { + // When externalControlOnly is false, show the tooltip normally (e.g., on tap) + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } +} + + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830130535.dart b/.history/lib/src/super_tooltip_20240830130535.dart new file mode 100644 index 0000000..e06cd6d --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830130535.dart @@ -0,0 +1,572 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + void _showTooltip() async { + // If externalControl is true, don't proceed with showing tooltip based on tap + if (!_superTooltipController!.externalControlOnly) { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830131523.dart b/.history/lib/src/super_tooltip_20240830131523.dart new file mode 100644 index 0000000..86ae089 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830131523.dart @@ -0,0 +1,575 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + // Only show the tooltip on tap if externalControlOnly is false + if (!widget.controller!.externalControlOnly) { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + void _showTooltip() async { + // If externalControl is true, don't proceed with showing tooltip based on tap + if (!_superTooltipController!.externalControlOnly) { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830131705.dart b/.history/lib/src/super_tooltip_20240830131705.dart new file mode 100644 index 0000000..1271dad --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830131705.dart @@ -0,0 +1,578 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + // Only show the tooltip on tap if externalControlOnly is false + if (!widget.controller!.externalControlOnly) { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + // Show the tooltip if either externalControlOnly is true or the tap logic allows it + if (_superTooltipController!.externalControlOnly || !_superTooltipController!.isVisible) { + _showTooltip(); + } + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + void _showTooltip() async { + // If externalControl is true, don't proceed with showing tooltip based on tap + if (!_superTooltipController!.externalControlOnly) { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830132213.dart b/.history/lib/src/super_tooltip_20240830132213.dart new file mode 100644 index 0000000..daca40b --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830132213.dart @@ -0,0 +1,578 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + // Only show the tooltip on tap if externalControlOnly is false + if (!widget.controller!.externalControlOnly) { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + // Show the tooltip if either externalControlOnly is true or the tap logic allows it + if (_superTooltipController!.externalControlOnly || !_superTooltipController!.isVisible) { + _showTooltip(); + } + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + void _showTooltip() async { + // If externalControlOnly is true, don't proceed with showing tooltip based on internal events + if (!_superTooltipController!.externalControlOnly || _superTooltipController!.event == Event.show) { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830133612.dart b/.history/lib/src/super_tooltip_20240830133612.dart new file mode 100644 index 0000000..1cb7f2b --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830133612.dart @@ -0,0 +1,571 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + if (!widget.controller!.externalControlOnly) { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + void _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830133953.dart b/.history/lib/src/super_tooltip_20240830133953.dart new file mode 100644 index 0000000..b09fee8 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830133953.dart @@ -0,0 +1,569 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830134254.dart b/.history/lib/src/super_tooltip_20240830134254.dart new file mode 100644 index 0000000..1e71c3d --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830134254.dart @@ -0,0 +1,572 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + void _showTooltip() async { + // Check if externalControl is true; if so, don't show the tooltip based on internal logic + if (!_superTooltipController!.externalControlOnly) { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830140105.dart b/.history/lib/src/super_tooltip_20240830140105.dart new file mode 100644 index 0000000..e61e627 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830140105.dart @@ -0,0 +1,573 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + final bool showOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.showOnTap = true, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.showOnTap) { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830140124.dart b/.history/lib/src/super_tooltip_20240830140124.dart new file mode 100644 index 0000000..9985177 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830140124.dart @@ -0,0 +1,573 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + final bool showOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.showOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.showOnTap) { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830140139.dart b/.history/lib/src/super_tooltip_20240830140139.dart new file mode 100644 index 0000000..e61e627 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830140139.dart @@ -0,0 +1,573 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + final bool showOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.showOnTap = true, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.showOnTap) { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830140344.dart b/.history/lib/src/super_tooltip_20240830140344.dart new file mode 100644 index 0000000..5e107fe --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830140344.dart @@ -0,0 +1,574 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + final bool showOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.showOnTap = true, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + if (widget.showOnTap) { + _superTooltipController!.showTooltip(); + + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830140347.dart b/.history/lib/src/super_tooltip_20240830140347.dart new file mode 100644 index 0000000..5e107fe --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830140347.dart @@ -0,0 +1,574 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + final bool showOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.showOnTap = true, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + if (widget.showOnTap) { + _superTooltipController!.showTooltip(); + + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830140406.dart b/.history/lib/src/super_tooltip_20240830140406.dart new file mode 100644 index 0000000..c46463f --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830140406.dart @@ -0,0 +1,573 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + final bool showOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.showOnTap = true, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + if (widget.showOnTap) { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830140450.dart b/.history/lib/src/super_tooltip_20240830140450.dart new file mode 100644 index 0000000..7f0b0a5 --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830140450.dart @@ -0,0 +1,573 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + final bool showOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.showOnTap = false, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + if (widget.showOnTap) { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_20240830140457.dart b/.history/lib/src/super_tooltip_20240830140457.dart new file mode 100644 index 0000000..c46463f --- /dev/null +++ b/.history/lib/src/super_tooltip_20240830140457.dart @@ -0,0 +1,573 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:super_tooltip/src/utils.dart'; + +import 'bubble_shape.dart'; +import 'enums.dart'; +import 'shape_overlay.dart'; +import 'super_tooltip_controller.dart'; +import 'tooltip_position_delegate.dart'; + +typedef DecorationBuilder = Decoration Function( + Offset target, +); + +class SuperTooltip extends StatefulWidget { + final Widget content; + final TooltipDirection popupDirection; + final SuperTooltipController? controller; + final void Function()? onLongPress; + final void Function()? onShow; + final void Function()? onHide; + final bool snapsFarAwayVertically; + final bool snapsFarAwayHorizontally; + final bool? hasShadow; + final Color? shadowColor; + final double? shadowBlurRadius; + final double? shadowSpreadRadius; + final Offset? shadowOffset; + final double? top, right, bottom, left; + final bool showCloseButton; + final CloseButtonType closeButtonType; + final Color? closeButtonColor; + final double? closeButtonSize; + final double minimumOutsideMargin; + final double verticalOffset; + final Widget? child; + final Color borderColor; + final BoxConstraints constraints; + final Color? backgroundColor; + final DecorationBuilder? decorationBuilder; + final double elevation; + final Duration fadeInDuration; + final Duration fadeOutDuration; + final double arrowLength; + final double arrowBaseWidth; + final double arrowTipDistance; + final double borderRadius; + final double borderWidth; + final bool? showBarrier; + final Color? barrierColor; + final Rect? touchThroughArea; + final ClipAreaShape touchThroughAreaShape; + final double touchThroughAreaCornerRadius; + final EdgeInsetsGeometry overlayDimensions; + final EdgeInsetsGeometry bubbleDimensions; + final bool hideTooltipOnTap; + final bool hideTooltipOnBarrierTap; + final bool toggleOnTap; + final bool showOnTap; + + //filter + final bool showDropBoxFilter; + final double sigmaX; + final double sigmaY; + final List? boxShadows; + + SuperTooltip({ + Key? key, + required this.content, + this.popupDirection = TooltipDirection.down, + this.controller, + this.onLongPress, + this.onShow, + this.onHide, + /** + * showCloseButton + * This will enable the closeButton + */ + this.showCloseButton = false, + this.closeButtonType = CloseButtonType.inside, + this.closeButtonColor, + this.closeButtonSize, + this.showBarrier, + this.barrierColor, + this.snapsFarAwayVertically = false, + this.snapsFarAwayHorizontally = false, + this.hasShadow, + this.shadowColor, + this.shadowBlurRadius, + this.shadowSpreadRadius, + this.shadowOffset, + this.top, + this.right, + this.bottom, + this.left, + // TD: Make edgeinsets instead + this.minimumOutsideMargin = 20.0, + this.verticalOffset = 0.0, + this.elevation = 0.0, + // TD: The native flutter tooltip uses verticalOffset + // to space the tooltip from the child. But we'll likely + // need just offset, since it's 4 way directional + // this.verticalOffset = 24.0, + this.backgroundColor, + + // + // + // + this.decorationBuilder, + this.child, + this.borderColor = Colors.black, + this.constraints = const BoxConstraints( + minHeight: 0.0, + maxHeight: double.infinity, + minWidth: 0.0, + maxWidth: double.infinity, + ), + this.fadeInDuration = const Duration(milliseconds: 150), + this.fadeOutDuration = const Duration(milliseconds: 0), + this.arrowLength = 20.0, + this.arrowBaseWidth = 20.0, + this.arrowTipDistance = 2.0, + this.touchThroughAreaShape = ClipAreaShape.oval, + this.touchThroughAreaCornerRadius = 5.0, + this.touchThroughArea, + this.borderWidth = 0.0, + this.borderRadius = 10.0, + this.overlayDimensions = const EdgeInsets.all(10), + this.bubbleDimensions = const EdgeInsets.all(10), + this.hideTooltipOnTap = false, + this.sigmaX = 5.0, + this.sigmaY = 5.0, + this.showDropBoxFilter = false, + this.hideTooltipOnBarrierTap = true, + this.toggleOnTap = false, + this.showOnTap = true, + this.boxShadows, + }) : assert(showDropBoxFilter ? showBarrier ?? false : true, + 'showDropBoxFilter or showBarrier can\'t be false | null'), + super(key: key); + + static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); + static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); + static Key barrierKey = const Key("barrierKey"); + static Key bubbleKey = const Key("bubbleKey"); + + @override + State createState() => _SuperTooltipState(); +} + +class _SuperTooltipState extends State + with SingleTickerProviderStateMixin { + final LayerLink _layerLink = LayerLink(); + late AnimationController _animationController; + SuperTooltipController? _superTooltipController; + OverlayEntry? _entry; + OverlayEntry? _barrierEntry; + OverlayEntry? blur; + + bool showCloseButton = false; + CloseButtonType closeButtonType = CloseButtonType.inside; + Color? closeButtonColor; + double? closeButtonSize; + late bool showBarrier; + Color? barrierColor; + late bool hasShadow; + late Color shadowColor; + late double shadowBlurRadius; + late double shadowSpreadRadius; + late Offset shadowOffset; + late bool showBlur; + + @override + void initState() { + _animationController = AnimationController( + duration: widget.fadeInDuration, + reverseDuration: widget.fadeOutDuration, + vsync: this, + ); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + + // TD: Mouse stuff + super.initState(); + } + + @override + void didUpdateWidget(SuperTooltip oldWidget) { + if (_superTooltipController != widget.controller) { + _superTooltipController!.removeListener(_onChangeNotifier); + _superTooltipController = widget.controller ?? SuperTooltipController(); + _superTooltipController!.addListener(_onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + // @override + @override + void dispose() { + if (_entry != null) _removeEntries(); + _superTooltipController?.removeListener(_onChangeNotifier); + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + showCloseButton = widget.showCloseButton; + closeButtonType = widget.closeButtonType; + closeButtonColor = widget.closeButtonColor ?? Colors.black; + closeButtonSize = widget.closeButtonSize ?? 30.0; + showBarrier = widget.showBarrier ?? true; + barrierColor = widget.barrierColor ?? Colors.black54; + hasShadow = widget.hasShadow ?? true; + shadowColor = widget.shadowColor ?? Colors.black54; + shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; + shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; + shadowOffset = widget.shadowOffset ?? Offset.zero; + showBlur = widget.showDropBoxFilter; + + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + if (widget.showOnTap) { + _superTooltipController!.showTooltip(); + } + } + }, + onLongPress: widget.onLongPress, + child: widget.child, + ), + ); + } + + void _onChangeNotifier() { + switch (_superTooltipController!.event) { + case Event.show: + _showTooltip(); + break; + case Event.hide: + _hideTooltip(); + break; + } + } + + void _createOverlayEntries() { + final renderBox = context.findRenderObject() as RenderBox; + + final overlayState = Overlay.of(context); + RenderBox? overlay; + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlay = overlayState.context.findRenderObject() as RenderBox?; + } + + final size = renderBox.size; + final target = renderBox.localToGlobal(size.center(Offset.zero)); + final animation = CurvedAnimation( + parent: _animationController, + curve: Curves.fastOutSlowIn, + ); + final offsetToTarget = Offset( + -target.dx + size.width / 2, + -target.dy + size.height / 2, + ); + final backgroundColor = + widget.backgroundColor ?? Theme.of(context).cardColor; + + var constraints = widget.constraints; + var preferredDirection = widget.popupDirection; + var left = widget.left; + var right = widget.right; + var top = widget.top; + var bottom = widget.bottom; + + if (widget.snapsFarAwayVertically) { + constraints = constraints.copyWith(maxHeight: null); + left = right = 0.0; + + if (overlay != null) { + if (target.dy > overlay.size.center(Offset.zero).dy) { + preferredDirection = TooltipDirection.up; + top = 0.0; + } else { + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.down; + bottom = 0.0; + } + } else if (widget.snapsFarAwayHorizontally) { + constraints = constraints.copyWith(maxHeight: null); + top = bottom = 0.0; + + if (overlay != null) { + if (target.dx < overlay.size.center(Offset.zero).dx) { + preferredDirection = TooltipDirection.right; + right = 0.0; + } else { + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } else { + // overlay is null - set default values + preferredDirection = TooltipDirection.left; + left = 0.0; + } + } + + _barrierEntry = showBarrier + ? OverlayEntry( + builder: (context) => FadeTransition( + opacity: animation, + child: GestureDetector( + onTap: widget.hideTooltipOnBarrierTap + ? _superTooltipController!.hideTooltip + : null, + child: Container( + key: SuperTooltip.barrierKey, + decoration: ShapeDecoration( + shape: ShapeOverlay( + clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, + clipAreaShape: widget.touchThroughAreaShape, + clipRect: widget.touchThroughArea, + barrierColor: barrierColor, + overlayDimensions: widget.overlayDimensions, + ), + ), + ), + ), + ), + ) + : null; + + blur = showBlur + ? OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.sigmaX, + sigmaY: widget.sigmaY, + ), + child: Container( + width: double.infinity, + height: double.infinity, + ), + ), + ), + ) + : null; + _entry = OverlayEntry( + builder: (BuildContext context) => FadeTransition( + opacity: animation, + child: Center( + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: offsetToTarget, + child: CustomSingleChildLayout( + delegate: TooltipPositionDelegate( + preferredDirection: preferredDirection, + constraints: constraints, + top: top, + bottom: bottom, + left: left, + right: right, + target: target, + // verticalOffset: widget.verticalOffset, + overlay: overlay, + margin: widget.minimumOutsideMargin, + snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, + snapsFarAwayVertically: widget.snapsFarAwayVertically, + ), + // TD: Text fields and such will need a material ancestor + // In order to function properly. Need to find more elegant way + // to add this. + child: Stack( + fit: StackFit.passthrough, + children: [ + Material( + color: Colors.transparent, + child: GestureDetector( + onTap: () { + if (widget.hideTooltipOnTap) + _superTooltipController!.hideTooltip(); + }, + child: Container( + key: SuperTooltip.bubbleKey, + margin: SuperUtils.getTooltipMargin( + arrowLength: widget.arrowLength, + arrowTipDistance: widget.arrowTipDistance, + closeButtonSize: closeButtonSize, + preferredDirection: preferredDirection, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + padding: SuperUtils.getTooltipPadding( + closeButtonSize: closeButtonSize, + closeButtonType: closeButtonType, + showCloseButton: showCloseButton, + ), + decoration: widget.decorationBuilder != null + ? widget.decorationBuilder!(target) + : ShapeDecoration( + color: backgroundColor, + shadows: hasShadow + ? widget.boxShadows ?? + [ + BoxShadow( + blurRadius: shadowBlurRadius, + spreadRadius: shadowSpreadRadius, + color: shadowColor, + offset: shadowOffset, + ), + ] + : null, + shape: BubbleShape( + arrowBaseWidth: widget.arrowBaseWidth, + arrowTipDistance: widget.arrowTipDistance, + borderColor: widget.borderColor, + borderRadius: widget.borderRadius, + borderWidth: widget.borderWidth, + bottom: bottom, + left: left, + preferredDirection: preferredDirection, + right: right, + target: target, + top: top, + bubbleDimensions: widget.bubbleDimensions, + ), + ), + child: widget.content, + ), + ), + ), + _buildCloseButton(), + ], + ), + ), + ), + ), + ), + ); + + // ignore: unnecessary_null_comparison + if (overlayState != null) { + overlayState.insertAll([ + if (showBlur) blur!, + if (showBarrier) _barrierEntry!, + _entry!, + ]); + } + } + + _showTooltip() async { + widget.onShow?.call(); + + // Already visible. + if (_entry != null) return; + + _createOverlayEntries(); + + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); + } + + _removeEntries() { + _entry?.remove(); + _entry = null; + _barrierEntry?.remove(); + _entry = null; + blur?.remove(); + } + + _hideTooltip() async { + widget.onHide?.call(); + await _animationController + .reverse() + .whenComplete(_superTooltipController!.complete); + + _removeEntries(); + } + + Widget _buildCloseButton() { + /** + * return Sizebox.shrizk if showCloseButton is false + */ + if (!showCloseButton) { + return const SizedBox.shrink(); + } + + double right; + double top; + + switch (widget.popupDirection) { + // + // LEFT: ------------------------------------- + case TooltipDirection.left: + right = widget.arrowLength + widget.arrowTipDistance + 3.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // RIGHT/UP: --------------------------------- + case TooltipDirection.right: + case TooltipDirection.up: + right = 5.0; + if (closeButtonType == CloseButtonType.inside) { + top = 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // DOWN: ------------------------------------- + case TooltipDirection.down: + // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance + // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. + right = 2.0; + if (closeButtonType == CloseButtonType.inside) { + top = widget.arrowLength + widget.arrowTipDistance + 2.0; + } else if (closeButtonType == CloseButtonType.outside) { + top = 0.0; + } else { + throw AssertionError(closeButtonType); + } + break; + + // --------------------------------------------- + + default: + throw AssertionError(widget.popupDirection); + } + + // --- + + return Positioned( + right: right, + top: top, + child: Material( + color: Colors.transparent, + child: IconButton( + key: closeButtonType == CloseButtonType.inside + ? SuperTooltip.insideCloseButtonKey + : SuperTooltip.outsideCloseButtonKey, + icon: Icon( + Icons.close_outlined, + size: closeButtonSize, + color: closeButtonColor, + ), + onPressed: () async => await widget.controller!.hideTooltip(), + ), + ), + ); + } +} diff --git a/.history/lib/src/super_tooltip_controller_20240830125828.dart b/.history/lib/src/super_tooltip_controller_20240830125828.dart new file mode 100644 index 0000000..3dac059 --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830125828.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + + // External control flag + bool externalControlOnly = false; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControlOnly = external; // Set the flag based on external trigger + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} diff --git a/.history/lib/src/super_tooltip_controller_20240830130556.dart b/.history/lib/src/super_tooltip_controller_20240830130556.dart new file mode 100644 index 0000000..7fa56df --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830130556.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = false; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830132235.dart b/.history/lib/src/super_tooltip_controller_20240830132235.dart new file mode 100644 index 0000000..1cbc7e3 --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830132235.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = false; + + late Event event; + + Future showTooltip({bool external = true}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830132556.dart b/.history/lib/src/super_tooltip_controller_20240830132556.dart new file mode 100644 index 0000000..70b023a --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830132556.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControl = false; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControl = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip({bool external = false}) { + externalControl = external; + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830132612.dart b/.history/lib/src/super_tooltip_controller_20240830132612.dart new file mode 100644 index 0000000..bfa626b --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830132612.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + // External control flag + bool externalControlOnly = false; + + late Event event; + + Future showTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip({bool external = false}) { + externalControlOnly = external; + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} + diff --git a/.history/lib/src/super_tooltip_controller_20240830140105.dart b/.history/lib/src/super_tooltip_controller_20240830140105.dart new file mode 100644 index 0000000..cea7355 --- /dev/null +++ b/.history/lib/src/super_tooltip_controller_20240830140105.dart @@ -0,0 +1,33 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'enums.dart'; + +class SuperTooltipController extends ChangeNotifier { + late Completer _completer; + bool _isVisible = false; + bool get isVisible => _isVisible; + + late Event event; + + Future showTooltip() { + event = Event.show; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = true); + } + + Future hideTooltip() { + event = Event.hide; + _completer = Completer(); + notifyListeners(); + return _completer.future.whenComplete(() => _isVisible = false); + } + + void complete() { + if (!_completer.isCompleted) { + _completer.complete(); + } + } +} diff --git a/lib/src/super_tooltip.dart b/lib/src/super_tooltip.dart index e06cd6d..c46463f 100644 --- a/lib/src/super_tooltip.dart +++ b/lib/src/super_tooltip.dart @@ -57,6 +57,7 @@ class SuperTooltip extends StatefulWidget { final bool hideTooltipOnTap; final bool hideTooltipOnBarrierTap; final bool toggleOnTap; + final bool showOnTap; //filter final bool showDropBoxFilter; @@ -133,6 +134,7 @@ class SuperTooltip extends StatefulWidget { this.showDropBoxFilter = false, this.hideTooltipOnBarrierTap = true, this.toggleOnTap = false, + this.showOnTap = true, this.boxShadows, }) : assert(showDropBoxFilter ? showBarrier ?? false : true, 'showDropBoxFilter or showBarrier can\'t be false | null'), @@ -221,11 +223,13 @@ class _SuperTooltipState extends State link: _layerLink, child: GestureDetector( onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } + if (widget.toggleOnTap && _superTooltipController!.isVisible) { + _superTooltipController!.hideTooltip(); + } else { + if (widget.showOnTap) { + _superTooltipController!.showTooltip(); + } + } }, onLongPress: widget.onLongPress, child: widget.child, @@ -457,20 +461,17 @@ class _SuperTooltipState extends State } } - void _showTooltip() async { - // If externalControl is true, don't proceed with showing tooltip based on tap - if (!_superTooltipController!.externalControlOnly) { - widget.onShow?.call(); + _showTooltip() async { + widget.onShow?.call(); - // Already visible. - if (_entry != null) return; + // Already visible. + if (_entry != null) return; - _createOverlayEntries(); + _createOverlayEntries(); - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } + await _animationController + .forward() + .whenComplete(_superTooltipController!.complete); } _removeEntries() { diff --git a/lib/src/super_tooltip_controller.dart b/lib/src/super_tooltip_controller.dart index 7fa56df..cea7355 100644 --- a/lib/src/super_tooltip_controller.dart +++ b/lib/src/super_tooltip_controller.dart @@ -8,13 +8,10 @@ class SuperTooltipController extends ChangeNotifier { late Completer _completer; bool _isVisible = false; bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = false; late Event event; - Future showTooltip({bool external = false}) { - externalControlOnly = external; + Future showTooltip() { event = Event.show; _completer = Completer(); notifyListeners(); @@ -34,4 +31,3 @@ class SuperTooltipController extends ChangeNotifier { } } } - From 81fa5f03dc6d9adb63927771eac05be26e5cca78 Mon Sep 17 00:00:00 2001 From: Ryan Napolitano Date: Fri, 30 Aug 2024 14:12:44 -0400 Subject: [PATCH 3/3] Remove history --- .history/.gitignore_20240830121146 | 15 - .history/.gitignore_20240830122219 | 15 - .history/example/lib/main_20240830121146.dart | 113 ---- .history/example/lib/main_20240830132520.dart | 116 ---- .history/example/lib/main_20240830133748.dart | 113 ---- .history/example/lib/main_20240830133918.dart | 113 ---- .history/example/lib/main_20240830133953.dart | 113 ---- .../lib/src/super_tooltip_20240830121146.dart | 569 ----------------- .../lib/src/super_tooltip_20240830122654.dart | 576 ----------------- .../lib/src/super_tooltip_20240830123422.dart | 572 ----------------- .../lib/src/super_tooltip_20240830125756.dart | 586 ------------------ .../lib/src/super_tooltip_20240830130535.dart | 572 ----------------- .../lib/src/super_tooltip_20240830131523.dart | 575 ----------------- .../lib/src/super_tooltip_20240830131705.dart | 578 ----------------- .../lib/src/super_tooltip_20240830132213.dart | 578 ----------------- .../lib/src/super_tooltip_20240830133612.dart | 571 ----------------- .../lib/src/super_tooltip_20240830133953.dart | 569 ----------------- .../lib/src/super_tooltip_20240830134254.dart | 572 ----------------- .../lib/src/super_tooltip_20240830140105.dart | 573 ----------------- .../lib/src/super_tooltip_20240830140124.dart | 573 ----------------- .../lib/src/super_tooltip_20240830140139.dart | 573 ----------------- .../lib/src/super_tooltip_20240830140344.dart | 574 ----------------- .../lib/src/super_tooltip_20240830140347.dart | 574 ----------------- .../lib/src/super_tooltip_20240830140406.dart | 573 ----------------- .../lib/src/super_tooltip_20240830140450.dart | 573 ----------------- .../lib/src/super_tooltip_20240830140457.dart | 573 ----------------- ...per_tooltip_controller_20240830121146.dart | 33 - ...per_tooltip_controller_20240830122654.dart | 38 -- ...per_tooltip_controller_20240830122709.dart | 38 -- ...per_tooltip_controller_20240830122857.dart | 38 -- ...per_tooltip_controller_20240830123422.dart | 38 -- ...per_tooltip_controller_20240830123454.dart | 37 -- ...per_tooltip_controller_20240830123526.dart | 37 -- ...per_tooltip_controller_20240830123535.dart | 37 -- ...per_tooltip_controller_20240830123552.dart | 37 -- ...per_tooltip_controller_20240830125828.dart | 37 -- ...per_tooltip_controller_20240830130556.dart | 37 -- ...per_tooltip_controller_20240830132235.dart | 37 -- ...per_tooltip_controller_20240830132556.dart | 38 -- ...per_tooltip_controller_20240830132612.dart | 38 -- ...per_tooltip_controller_20240830140105.dart | 33 - 41 files changed, 12055 deletions(-) delete mode 100644 .history/.gitignore_20240830121146 delete mode 100644 .history/.gitignore_20240830122219 delete mode 100644 .history/example/lib/main_20240830121146.dart delete mode 100644 .history/example/lib/main_20240830132520.dart delete mode 100644 .history/example/lib/main_20240830133748.dart delete mode 100644 .history/example/lib/main_20240830133918.dart delete mode 100644 .history/example/lib/main_20240830133953.dart delete mode 100644 .history/lib/src/super_tooltip_20240830121146.dart delete mode 100644 .history/lib/src/super_tooltip_20240830122654.dart delete mode 100644 .history/lib/src/super_tooltip_20240830123422.dart delete mode 100644 .history/lib/src/super_tooltip_20240830125756.dart delete mode 100644 .history/lib/src/super_tooltip_20240830130535.dart delete mode 100644 .history/lib/src/super_tooltip_20240830131523.dart delete mode 100644 .history/lib/src/super_tooltip_20240830131705.dart delete mode 100644 .history/lib/src/super_tooltip_20240830132213.dart delete mode 100644 .history/lib/src/super_tooltip_20240830133612.dart delete mode 100644 .history/lib/src/super_tooltip_20240830133953.dart delete mode 100644 .history/lib/src/super_tooltip_20240830134254.dart delete mode 100644 .history/lib/src/super_tooltip_20240830140105.dart delete mode 100644 .history/lib/src/super_tooltip_20240830140124.dart delete mode 100644 .history/lib/src/super_tooltip_20240830140139.dart delete mode 100644 .history/lib/src/super_tooltip_20240830140344.dart delete mode 100644 .history/lib/src/super_tooltip_20240830140347.dart delete mode 100644 .history/lib/src/super_tooltip_20240830140406.dart delete mode 100644 .history/lib/src/super_tooltip_20240830140450.dart delete mode 100644 .history/lib/src/super_tooltip_20240830140457.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830121146.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830122654.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830122709.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830122857.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830123422.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830123454.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830123526.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830123535.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830123552.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830125828.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830130556.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830132235.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830132556.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830132612.dart delete mode 100644 .history/lib/src/super_tooltip_controller_20240830140105.dart diff --git a/.history/.gitignore_20240830121146 b/.history/.gitignore_20240830121146 deleted file mode 100644 index 89fad00..0000000 --- a/.history/.gitignore_20240830121146 +++ /dev/null @@ -1,15 +0,0 @@ -.DS_Store -.atom/ -.vscode/ -.dart_tool/ -.idea -.packages -.pub/ -packages -ios/Runner/GeneratedPluginRegistrant.h -ios/Runner/GeneratedPluginRegistrant.m -pubspec.lock -.vscode -example/flutter_weather_demo/pubspec.lock -/*.code-workspace -example/android.iml diff --git a/.history/.gitignore_20240830122219 b/.history/.gitignore_20240830122219 deleted file mode 100644 index 89fad00..0000000 --- a/.history/.gitignore_20240830122219 +++ /dev/null @@ -1,15 +0,0 @@ -.DS_Store -.atom/ -.vscode/ -.dart_tool/ -.idea -.packages -.pub/ -packages -ios/Runner/GeneratedPluginRegistrant.h -ios/Runner/GeneratedPluginRegistrant.m -pubspec.lock -.vscode -example/flutter_weather_demo/pubspec.lock -/*.code-workspace -example/android.iml diff --git a/.history/example/lib/main_20240830121146.dart b/.history/example/lib/main_20240830121146.dart deleted file mode 100644 index f49ae52..0000000 --- a/.history/example/lib/main_20240830121146.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:super_tooltip/super_tooltip.dart'; - -void main() => runApp(const MainApp()); - -class MainApp extends StatelessWidget { - const MainApp({Key? key}) : super(key: key); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Super Tooltip Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const ExamplePage(), - ); - } -} - -class ExamplePage extends StatefulWidget { - const ExamplePage({ - Key? key, - }) : super(key: key); - @override - State createState() => _ExamplePageState(); -} - -class _ExamplePageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: Center( - child: TargetWidget(), - ), - ); - } -} - -class TargetWidget extends StatefulWidget { - const TargetWidget({Key? key}) : super(key: key); - - @override - State createState() => _TargetWidgetState(); -} - -class _TargetWidgetState extends State { - final _controller = SuperTooltipController(); - Future? _willPopCallback() async { - // If the tooltip is open we don't pop the page on a backbutton press - // but close the ToolTip - if (_controller.isVisible) { - await _controller.hideTooltip(); - return false; - } - return true; - } - - @override - Widget build(BuildContext context) { - return PopScope( - onPopInvoked: (didPop) => _willPopCallback, - child: GestureDetector( - onTap: () async { - await _controller.showTooltip(); - }, - child: SuperTooltip( - showBarrier: true, - controller: _controller, - popupDirection: TooltipDirection.down, - backgroundColor: Color(0xff2f2d2f), - left: 30, - right: 30, - arrowTipDistance: 15.0, - arrowBaseWidth: 20.0, - arrowLength: 20.0, - borderWidth: 2.0, - constraints: const BoxConstraints( - minHeight: 0.0, - maxHeight: 100, - minWidth: 0.0, - maxWidth: 100, - ), - touchThroughAreaShape: ClipAreaShape.rectangle, - touchThroughAreaCornerRadius: 30, - barrierColor: Color.fromARGB(26, 47, 45, 47), - content: const Text( - "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", - softWrap: true, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), - ), - child: Container( - width: 40.0, - height: 40.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.blue, - ), - child: Icon( - Icons.add, - color: Colors.white, - ), - ), - ), - ), - ); - } -} diff --git a/.history/example/lib/main_20240830132520.dart b/.history/example/lib/main_20240830132520.dart deleted file mode 100644 index 18c2853..0000000 --- a/.history/example/lib/main_20240830132520.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:super_tooltip/super_tooltip.dart'; - -void main() => runApp(const MainApp()); - -class MainApp extends StatelessWidget { - const MainApp({Key? key}) : super(key: key); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Super Tooltip Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const ExamplePage(), - ); - } -} - -class ExamplePage extends StatefulWidget { - const ExamplePage({ - Key? key, - }) : super(key: key); - @override - State createState() => _ExamplePageState(); -} - -class _ExamplePageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: Center( - child: TargetWidget(), - ), - ); - } -} - -class TargetWidget extends StatefulWidget { - const TargetWidget({Key? key}) : super(key: key); - - @override - State createState() => _TargetWidgetState(); -} - -class _TargetWidgetState extends State { - final _controller = SuperTooltipController(); - Future? _willPopCallback() async { - // If the tooltip is open we don't pop the page on a backbutton press - // but close the ToolTip - if (_controller.isVisible) { - await _controller.hideTooltip(); - return false; - } - return true; - } - - @override - Widget build(BuildContext context) { - return PopScope( - onPopInvoked: (didPop) => _willPopCallback, - child: GestureDetector( - onTap: () async { - await _controller.showTooltip(); - }, - child: SuperTooltip( - showBarrier: true, - controller: _controller, - popupDirection: TooltipDirection.down, - backgroundColor: Color(0xff2f2d2f), - left: 30, - right: 30, - arrowTipDistance: 15.0, - arrowBaseWidth: 20.0, - arrowLength: 20.0, - borderWidth: 2.0, - constraints: const BoxConstraints( - minHeight: 0.0, - maxHeight: 100, - minWidth: 0.0, - maxWidth: 100, - ), - touchThroughAreaShape: ClipAreaShape.rectangle, - touchThroughAreaCornerRadius: 30, - barrierColor: Color.fromARGB(26, 47, 45, 47), - onShow: () { - _controller.showTooltip(external: true); - }, - content: const Text( - "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", - softWrap: true, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), - ), - child: Container( - width: 40.0, - height: 40.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.blue, - ), - child: Icon( - Icons.add, - color: Colors.white, - ), - ), - ), - ), - ); - } -} diff --git a/.history/example/lib/main_20240830133748.dart b/.history/example/lib/main_20240830133748.dart deleted file mode 100644 index d13e1b8..0000000 --- a/.history/example/lib/main_20240830133748.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:super_tooltip/super_tooltip.dart'; - -void main() => runApp(const MainApp()); - -class MainApp extends StatelessWidget { - const MainApp({Key? key}) : super(key: key); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Super Tooltip Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const ExamplePage(), - ); - } -} - -class ExamplePage extends StatefulWidget { - const ExamplePage({ - Key? key, - }) : super(key: key); - @override - State createState() => _ExamplePageState(); -} - -class _ExamplePageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: Center( - child: TargetWidget(), - ), - ); - } -} - -class TargetWidget extends StatefulWidget { - const TargetWidget({Key? key}) : super(key: key); - - @override - State createState() => _TargetWidgetState(); -} - -class _TargetWidgetState extends State { - final _controller = SuperTooltipController(); - Future? _willPopCallback() async { - // If the tooltip is open we don't pop the page on a backbutton press - // but close the ToolTip - if (_controller.isVisible) { - await _controller.hideTooltip(); - return false; - } - return true; - } - - @override - Widget build(BuildContext context) { - return PopScope( - onPopInvoked: (didPop) => _willPopCallback, - child: GestureDetector( - onTap: () async { - await _controller.showTooltip(external: true); - }, - child: SuperTooltip( - showBarrier: true, - controller: _controller, - popupDirection: TooltipDirection.down, - backgroundColor: Color(0xff2f2d2f), - left: 30, - right: 30, - arrowTipDistance: 15.0, - arrowBaseWidth: 20.0, - arrowLength: 20.0, - borderWidth: 2.0, - constraints: const BoxConstraints( - minHeight: 0.0, - maxHeight: 100, - minWidth: 0.0, - maxWidth: 100, - ), - touchThroughAreaShape: ClipAreaShape.rectangle, - touchThroughAreaCornerRadius: 30, - barrierColor: Color.fromARGB(26, 47, 45, 47), - content: const Text( - "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", - softWrap: true, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), - ), - child: Container( - width: 40.0, - height: 40.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.blue, - ), - child: Icon( - Icons.add, - color: Colors.white, - ), - ), - ), - ), - ); - } -} diff --git a/.history/example/lib/main_20240830133918.dart b/.history/example/lib/main_20240830133918.dart deleted file mode 100644 index ae5e603..0000000 --- a/.history/example/lib/main_20240830133918.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:super_tooltip/super_tooltip.dart'; - -void main() => runApp(const MainApp()); - -class MainApp extends StatelessWidget { - const MainApp({Key? key}) : super(key: key); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Super Tooltip Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const ExamplePage(), - ); - } -} - -class ExamplePage extends StatefulWidget { - const ExamplePage({ - Key? key, - }) : super(key: key); - @override - State createState() => _ExamplePageState(); -} - -class _ExamplePageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: Center( - child: TargetWidget(), - ), - ); - } -} - -class TargetWidget extends StatefulWidget { - const TargetWidget({Key? key}) : super(key: key); - - @override - State createState() => _TargetWidgetState(); -} - -class _TargetWidgetState extends State { - final _controller = SuperTooltipController(); - Future? _willPopCallback() async { - // If the tooltip is open we don't pop the page on a backbutton press - // but close the ToolTip - if (_controller.isVisible) { - await _controller.hideTooltip(); - return false; - } - return true; - } - - @override - Widget build(BuildContext context) { - return PopScope( - onPopInvoked: (didPop) => _willPopCallback, - child: GestureDetector( - onTap: () async { - await _controller.showTooltip(external: false); - }, - child: SuperTooltip( - showBarrier: true, - controller: _controller, - popupDirection: TooltipDirection.down, - backgroundColor: Color(0xff2f2d2f), - left: 30, - right: 30, - arrowTipDistance: 15.0, - arrowBaseWidth: 20.0, - arrowLength: 20.0, - borderWidth: 2.0, - constraints: const BoxConstraints( - minHeight: 0.0, - maxHeight: 100, - minWidth: 0.0, - maxWidth: 100, - ), - touchThroughAreaShape: ClipAreaShape.rectangle, - touchThroughAreaCornerRadius: 30, - barrierColor: Color.fromARGB(26, 47, 45, 47), - content: const Text( - "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", - softWrap: true, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), - ), - child: Container( - width: 40.0, - height: 40.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.blue, - ), - child: Icon( - Icons.add, - color: Colors.white, - ), - ), - ), - ), - ); - } -} diff --git a/.history/example/lib/main_20240830133953.dart b/.history/example/lib/main_20240830133953.dart deleted file mode 100644 index f49ae52..0000000 --- a/.history/example/lib/main_20240830133953.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:super_tooltip/super_tooltip.dart'; - -void main() => runApp(const MainApp()); - -class MainApp extends StatelessWidget { - const MainApp({Key? key}) : super(key: key); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Super Tooltip Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const ExamplePage(), - ); - } -} - -class ExamplePage extends StatefulWidget { - const ExamplePage({ - Key? key, - }) : super(key: key); - @override - State createState() => _ExamplePageState(); -} - -class _ExamplePageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: Center( - child: TargetWidget(), - ), - ); - } -} - -class TargetWidget extends StatefulWidget { - const TargetWidget({Key? key}) : super(key: key); - - @override - State createState() => _TargetWidgetState(); -} - -class _TargetWidgetState extends State { - final _controller = SuperTooltipController(); - Future? _willPopCallback() async { - // If the tooltip is open we don't pop the page on a backbutton press - // but close the ToolTip - if (_controller.isVisible) { - await _controller.hideTooltip(); - return false; - } - return true; - } - - @override - Widget build(BuildContext context) { - return PopScope( - onPopInvoked: (didPop) => _willPopCallback, - child: GestureDetector( - onTap: () async { - await _controller.showTooltip(); - }, - child: SuperTooltip( - showBarrier: true, - controller: _controller, - popupDirection: TooltipDirection.down, - backgroundColor: Color(0xff2f2d2f), - left: 30, - right: 30, - arrowTipDistance: 15.0, - arrowBaseWidth: 20.0, - arrowLength: 20.0, - borderWidth: 2.0, - constraints: const BoxConstraints( - minHeight: 0.0, - maxHeight: 100, - minWidth: 0.0, - maxWidth: 100, - ), - touchThroughAreaShape: ClipAreaShape.rectangle, - touchThroughAreaCornerRadius: 30, - barrierColor: Color.fromARGB(26, 47, 45, 47), - content: const Text( - "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", - softWrap: true, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), - ), - child: Container( - width: 40.0, - height: 40.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.blue, - ), - child: Icon( - Icons.add, - color: Colors.white, - ), - ), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830121146.dart b/.history/lib/src/super_tooltip_20240830121146.dart deleted file mode 100644 index b09fee8..0000000 --- a/.history/lib/src/super_tooltip_20240830121146.dart +++ /dev/null @@ -1,569 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830122654.dart b/.history/lib/src/super_tooltip_20240830122654.dart deleted file mode 100644 index 2e95464..0000000 --- a/.history/lib/src/super_tooltip_20240830122654.dart +++ /dev/null @@ -1,576 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - void _showTooltip() async { - // If externalControl is true, don't proceed with showing tooltip based on tap - if (!_superTooltipController!.externalControlOnly) { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - -void _hideTooltip() async { - // If externalControl is true, don't proceed with hiding tooltip based on tap - if (!_superTooltipController!.externalControlOnly) { - widget.onHide?.call(); - - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830123422.dart b/.history/lib/src/super_tooltip_20240830123422.dart deleted file mode 100644 index e06cd6d..0000000 --- a/.history/lib/src/super_tooltip_20240830123422.dart +++ /dev/null @@ -1,572 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - void _showTooltip() async { - // If externalControl is true, don't proceed with showing tooltip based on tap - if (!_superTooltipController!.externalControlOnly) { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830125756.dart b/.history/lib/src/super_tooltip_20240830125756.dart deleted file mode 100644 index 7c96985..0000000 --- a/.history/lib/src/super_tooltip_20240830125756.dart +++ /dev/null @@ -1,586 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - -void _showTooltip() async { - // If externalControlOnly is true, proceed with showing tooltip only if triggered externally - if (_superTooltipController!.externalControlOnly) { - // Tooltip should only be shown if externalControlOnly is set to true and is triggered externally - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } else { - // When externalControlOnly is false, show the tooltip normally (e.g., on tap) - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } -} - - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830130535.dart b/.history/lib/src/super_tooltip_20240830130535.dart deleted file mode 100644 index e06cd6d..0000000 --- a/.history/lib/src/super_tooltip_20240830130535.dart +++ /dev/null @@ -1,572 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - void _showTooltip() async { - // If externalControl is true, don't proceed with showing tooltip based on tap - if (!_superTooltipController!.externalControlOnly) { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830131523.dart b/.history/lib/src/super_tooltip_20240830131523.dart deleted file mode 100644 index 86ae089..0000000 --- a/.history/lib/src/super_tooltip_20240830131523.dart +++ /dev/null @@ -1,575 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - // Only show the tooltip on tap if externalControlOnly is false - if (!widget.controller!.externalControlOnly) { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - void _showTooltip() async { - // If externalControl is true, don't proceed with showing tooltip based on tap - if (!_superTooltipController!.externalControlOnly) { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830131705.dart b/.history/lib/src/super_tooltip_20240830131705.dart deleted file mode 100644 index 1271dad..0000000 --- a/.history/lib/src/super_tooltip_20240830131705.dart +++ /dev/null @@ -1,578 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - // Only show the tooltip on tap if externalControlOnly is false - if (!widget.controller!.externalControlOnly) { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - // Show the tooltip if either externalControlOnly is true or the tap logic allows it - if (_superTooltipController!.externalControlOnly || !_superTooltipController!.isVisible) { - _showTooltip(); - } - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - void _showTooltip() async { - // If externalControl is true, don't proceed with showing tooltip based on tap - if (!_superTooltipController!.externalControlOnly) { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830132213.dart b/.history/lib/src/super_tooltip_20240830132213.dart deleted file mode 100644 index daca40b..0000000 --- a/.history/lib/src/super_tooltip_20240830132213.dart +++ /dev/null @@ -1,578 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - // Only show the tooltip on tap if externalControlOnly is false - if (!widget.controller!.externalControlOnly) { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - // Show the tooltip if either externalControlOnly is true or the tap logic allows it - if (_superTooltipController!.externalControlOnly || !_superTooltipController!.isVisible) { - _showTooltip(); - } - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - void _showTooltip() async { - // If externalControlOnly is true, don't proceed with showing tooltip based on internal events - if (!_superTooltipController!.externalControlOnly || _superTooltipController!.event == Event.show) { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830133612.dart b/.history/lib/src/super_tooltip_20240830133612.dart deleted file mode 100644 index 1cb7f2b..0000000 --- a/.history/lib/src/super_tooltip_20240830133612.dart +++ /dev/null @@ -1,571 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - if (!widget.controller!.externalControlOnly) { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - void _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830133953.dart b/.history/lib/src/super_tooltip_20240830133953.dart deleted file mode 100644 index b09fee8..0000000 --- a/.history/lib/src/super_tooltip_20240830133953.dart +++ /dev/null @@ -1,569 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830134254.dart b/.history/lib/src/super_tooltip_20240830134254.dart deleted file mode 100644 index 1e71c3d..0000000 --- a/.history/lib/src/super_tooltip_20240830134254.dart +++ /dev/null @@ -1,572 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - void _showTooltip() async { - // Check if externalControl is true; if so, don't show the tooltip based on internal logic - if (!_superTooltipController!.externalControlOnly) { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830140105.dart b/.history/lib/src/super_tooltip_20240830140105.dart deleted file mode 100644 index e61e627..0000000 --- a/.history/lib/src/super_tooltip_20240830140105.dart +++ /dev/null @@ -1,573 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - final bool showOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.showOnTap = true, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.showOnTap) { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830140124.dart b/.history/lib/src/super_tooltip_20240830140124.dart deleted file mode 100644 index 9985177..0000000 --- a/.history/lib/src/super_tooltip_20240830140124.dart +++ /dev/null @@ -1,573 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - final bool showOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.showOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.showOnTap) { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830140139.dart b/.history/lib/src/super_tooltip_20240830140139.dart deleted file mode 100644 index e61e627..0000000 --- a/.history/lib/src/super_tooltip_20240830140139.dart +++ /dev/null @@ -1,573 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - final bool showOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.showOnTap = true, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.showOnTap) { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830140344.dart b/.history/lib/src/super_tooltip_20240830140344.dart deleted file mode 100644 index 5e107fe..0000000 --- a/.history/lib/src/super_tooltip_20240830140344.dart +++ /dev/null @@ -1,574 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - final bool showOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.showOnTap = true, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - if (widget.showOnTap) { - _superTooltipController!.showTooltip(); - - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830140347.dart b/.history/lib/src/super_tooltip_20240830140347.dart deleted file mode 100644 index 5e107fe..0000000 --- a/.history/lib/src/super_tooltip_20240830140347.dart +++ /dev/null @@ -1,574 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - final bool showOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.showOnTap = true, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - if (widget.showOnTap) { - _superTooltipController!.showTooltip(); - - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830140406.dart b/.history/lib/src/super_tooltip_20240830140406.dart deleted file mode 100644 index c46463f..0000000 --- a/.history/lib/src/super_tooltip_20240830140406.dart +++ /dev/null @@ -1,573 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - final bool showOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.showOnTap = true, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - if (widget.showOnTap) { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830140450.dart b/.history/lib/src/super_tooltip_20240830140450.dart deleted file mode 100644 index 7f0b0a5..0000000 --- a/.history/lib/src/super_tooltip_20240830140450.dart +++ /dev/null @@ -1,573 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - final bool showOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.showOnTap = false, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - if (widget.showOnTap) { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_20240830140457.dart b/.history/lib/src/super_tooltip_20240830140457.dart deleted file mode 100644 index c46463f..0000000 --- a/.history/lib/src/super_tooltip_20240830140457.dart +++ /dev/null @@ -1,573 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:super_tooltip/src/utils.dart'; - -import 'bubble_shape.dart'; -import 'enums.dart'; -import 'shape_overlay.dart'; -import 'super_tooltip_controller.dart'; -import 'tooltip_position_delegate.dart'; - -typedef DecorationBuilder = Decoration Function( - Offset target, -); - -class SuperTooltip extends StatefulWidget { - final Widget content; - final TooltipDirection popupDirection; - final SuperTooltipController? controller; - final void Function()? onLongPress; - final void Function()? onShow; - final void Function()? onHide; - final bool snapsFarAwayVertically; - final bool snapsFarAwayHorizontally; - final bool? hasShadow; - final Color? shadowColor; - final double? shadowBlurRadius; - final double? shadowSpreadRadius; - final Offset? shadowOffset; - final double? top, right, bottom, left; - final bool showCloseButton; - final CloseButtonType closeButtonType; - final Color? closeButtonColor; - final double? closeButtonSize; - final double minimumOutsideMargin; - final double verticalOffset; - final Widget? child; - final Color borderColor; - final BoxConstraints constraints; - final Color? backgroundColor; - final DecorationBuilder? decorationBuilder; - final double elevation; - final Duration fadeInDuration; - final Duration fadeOutDuration; - final double arrowLength; - final double arrowBaseWidth; - final double arrowTipDistance; - final double borderRadius; - final double borderWidth; - final bool? showBarrier; - final Color? barrierColor; - final Rect? touchThroughArea; - final ClipAreaShape touchThroughAreaShape; - final double touchThroughAreaCornerRadius; - final EdgeInsetsGeometry overlayDimensions; - final EdgeInsetsGeometry bubbleDimensions; - final bool hideTooltipOnTap; - final bool hideTooltipOnBarrierTap; - final bool toggleOnTap; - final bool showOnTap; - - //filter - final bool showDropBoxFilter; - final double sigmaX; - final double sigmaY; - final List? boxShadows; - - SuperTooltip({ - Key? key, - required this.content, - this.popupDirection = TooltipDirection.down, - this.controller, - this.onLongPress, - this.onShow, - this.onHide, - /** - * showCloseButton - * This will enable the closeButton - */ - this.showCloseButton = false, - this.closeButtonType = CloseButtonType.inside, - this.closeButtonColor, - this.closeButtonSize, - this.showBarrier, - this.barrierColor, - this.snapsFarAwayVertically = false, - this.snapsFarAwayHorizontally = false, - this.hasShadow, - this.shadowColor, - this.shadowBlurRadius, - this.shadowSpreadRadius, - this.shadowOffset, - this.top, - this.right, - this.bottom, - this.left, - // TD: Make edgeinsets instead - this.minimumOutsideMargin = 20.0, - this.verticalOffset = 0.0, - this.elevation = 0.0, - // TD: The native flutter tooltip uses verticalOffset - // to space the tooltip from the child. But we'll likely - // need just offset, since it's 4 way directional - // this.verticalOffset = 24.0, - this.backgroundColor, - - // - // - // - this.decorationBuilder, - this.child, - this.borderColor = Colors.black, - this.constraints = const BoxConstraints( - minHeight: 0.0, - maxHeight: double.infinity, - minWidth: 0.0, - maxWidth: double.infinity, - ), - this.fadeInDuration = const Duration(milliseconds: 150), - this.fadeOutDuration = const Duration(milliseconds: 0), - this.arrowLength = 20.0, - this.arrowBaseWidth = 20.0, - this.arrowTipDistance = 2.0, - this.touchThroughAreaShape = ClipAreaShape.oval, - this.touchThroughAreaCornerRadius = 5.0, - this.touchThroughArea, - this.borderWidth = 0.0, - this.borderRadius = 10.0, - this.overlayDimensions = const EdgeInsets.all(10), - this.bubbleDimensions = const EdgeInsets.all(10), - this.hideTooltipOnTap = false, - this.sigmaX = 5.0, - this.sigmaY = 5.0, - this.showDropBoxFilter = false, - this.hideTooltipOnBarrierTap = true, - this.toggleOnTap = false, - this.showOnTap = true, - this.boxShadows, - }) : assert(showDropBoxFilter ? showBarrier ?? false : true, - 'showDropBoxFilter or showBarrier can\'t be false | null'), - super(key: key); - - static Key insideCloseButtonKey = const Key("InsideCloseButtonKey"); - static Key outsideCloseButtonKey = const Key("OutsideCloseButtonKey"); - static Key barrierKey = const Key("barrierKey"); - static Key bubbleKey = const Key("bubbleKey"); - - @override - State createState() => _SuperTooltipState(); -} - -class _SuperTooltipState extends State - with SingleTickerProviderStateMixin { - final LayerLink _layerLink = LayerLink(); - late AnimationController _animationController; - SuperTooltipController? _superTooltipController; - OverlayEntry? _entry; - OverlayEntry? _barrierEntry; - OverlayEntry? blur; - - bool showCloseButton = false; - CloseButtonType closeButtonType = CloseButtonType.inside; - Color? closeButtonColor; - double? closeButtonSize; - late bool showBarrier; - Color? barrierColor; - late bool hasShadow; - late Color shadowColor; - late double shadowBlurRadius; - late double shadowSpreadRadius; - late Offset shadowOffset; - late bool showBlur; - - @override - void initState() { - _animationController = AnimationController( - duration: widget.fadeInDuration, - reverseDuration: widget.fadeOutDuration, - vsync: this, - ); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - - // TD: Mouse stuff - super.initState(); - } - - @override - void didUpdateWidget(SuperTooltip oldWidget) { - if (_superTooltipController != widget.controller) { - _superTooltipController!.removeListener(_onChangeNotifier); - _superTooltipController = widget.controller ?? SuperTooltipController(); - _superTooltipController!.addListener(_onChangeNotifier); - } - super.didUpdateWidget(oldWidget); - } - - // @override - @override - void dispose() { - if (_entry != null) _removeEntries(); - _superTooltipController?.removeListener(_onChangeNotifier); - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - showCloseButton = widget.showCloseButton; - closeButtonType = widget.closeButtonType; - closeButtonColor = widget.closeButtonColor ?? Colors.black; - closeButtonSize = widget.closeButtonSize ?? 30.0; - showBarrier = widget.showBarrier ?? true; - barrierColor = widget.barrierColor ?? Colors.black54; - hasShadow = widget.hasShadow ?? true; - shadowColor = widget.shadowColor ?? Colors.black54; - shadowBlurRadius = widget.shadowBlurRadius ?? 10.0; - shadowSpreadRadius = widget.shadowSpreadRadius ?? 5.0; - shadowOffset = widget.shadowOffset ?? Offset.zero; - showBlur = widget.showDropBoxFilter; - - return CompositedTransformTarget( - link: _layerLink, - child: GestureDetector( - onTap: () { - if (widget.toggleOnTap && _superTooltipController!.isVisible) { - _superTooltipController!.hideTooltip(); - } else { - if (widget.showOnTap) { - _superTooltipController!.showTooltip(); - } - } - }, - onLongPress: widget.onLongPress, - child: widget.child, - ), - ); - } - - void _onChangeNotifier() { - switch (_superTooltipController!.event) { - case Event.show: - _showTooltip(); - break; - case Event.hide: - _hideTooltip(); - break; - } - } - - void _createOverlayEntries() { - final renderBox = context.findRenderObject() as RenderBox; - - final overlayState = Overlay.of(context); - RenderBox? overlay; - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlay = overlayState.context.findRenderObject() as RenderBox?; - } - - final size = renderBox.size; - final target = renderBox.localToGlobal(size.center(Offset.zero)); - final animation = CurvedAnimation( - parent: _animationController, - curve: Curves.fastOutSlowIn, - ); - final offsetToTarget = Offset( - -target.dx + size.width / 2, - -target.dy + size.height / 2, - ); - final backgroundColor = - widget.backgroundColor ?? Theme.of(context).cardColor; - - var constraints = widget.constraints; - var preferredDirection = widget.popupDirection; - var left = widget.left; - var right = widget.right; - var top = widget.top; - var bottom = widget.bottom; - - if (widget.snapsFarAwayVertically) { - constraints = constraints.copyWith(maxHeight: null); - left = right = 0.0; - - if (overlay != null) { - if (target.dy > overlay.size.center(Offset.zero).dy) { - preferredDirection = TooltipDirection.up; - top = 0.0; - } else { - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.down; - bottom = 0.0; - } - } else if (widget.snapsFarAwayHorizontally) { - constraints = constraints.copyWith(maxHeight: null); - top = bottom = 0.0; - - if (overlay != null) { - if (target.dx < overlay.size.center(Offset.zero).dx) { - preferredDirection = TooltipDirection.right; - right = 0.0; - } else { - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } else { - // overlay is null - set default values - preferredDirection = TooltipDirection.left; - left = 0.0; - } - } - - _barrierEntry = showBarrier - ? OverlayEntry( - builder: (context) => FadeTransition( - opacity: animation, - child: GestureDetector( - onTap: widget.hideTooltipOnBarrierTap - ? _superTooltipController!.hideTooltip - : null, - child: Container( - key: SuperTooltip.barrierKey, - decoration: ShapeDecoration( - shape: ShapeOverlay( - clipAreaCornerRadius: widget.touchThroughAreaCornerRadius, - clipAreaShape: widget.touchThroughAreaShape, - clipRect: widget.touchThroughArea, - barrierColor: barrierColor, - overlayDimensions: widget.overlayDimensions, - ), - ), - ), - ), - ), - ) - : null; - - blur = showBlur - ? OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.sigmaX, - sigmaY: widget.sigmaY, - ), - child: Container( - width: double.infinity, - height: double.infinity, - ), - ), - ), - ) - : null; - _entry = OverlayEntry( - builder: (BuildContext context) => FadeTransition( - opacity: animation, - child: Center( - child: CompositedTransformFollower( - link: _layerLink, - showWhenUnlinked: false, - offset: offsetToTarget, - child: CustomSingleChildLayout( - delegate: TooltipPositionDelegate( - preferredDirection: preferredDirection, - constraints: constraints, - top: top, - bottom: bottom, - left: left, - right: right, - target: target, - // verticalOffset: widget.verticalOffset, - overlay: overlay, - margin: widget.minimumOutsideMargin, - snapsFarAwayHorizontally: widget.snapsFarAwayHorizontally, - snapsFarAwayVertically: widget.snapsFarAwayVertically, - ), - // TD: Text fields and such will need a material ancestor - // In order to function properly. Need to find more elegant way - // to add this. - child: Stack( - fit: StackFit.passthrough, - children: [ - Material( - color: Colors.transparent, - child: GestureDetector( - onTap: () { - if (widget.hideTooltipOnTap) - _superTooltipController!.hideTooltip(); - }, - child: Container( - key: SuperTooltip.bubbleKey, - margin: SuperUtils.getTooltipMargin( - arrowLength: widget.arrowLength, - arrowTipDistance: widget.arrowTipDistance, - closeButtonSize: closeButtonSize, - preferredDirection: preferredDirection, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - padding: SuperUtils.getTooltipPadding( - closeButtonSize: closeButtonSize, - closeButtonType: closeButtonType, - showCloseButton: showCloseButton, - ), - decoration: widget.decorationBuilder != null - ? widget.decorationBuilder!(target) - : ShapeDecoration( - color: backgroundColor, - shadows: hasShadow - ? widget.boxShadows ?? - [ - BoxShadow( - blurRadius: shadowBlurRadius, - spreadRadius: shadowSpreadRadius, - color: shadowColor, - offset: shadowOffset, - ), - ] - : null, - shape: BubbleShape( - arrowBaseWidth: widget.arrowBaseWidth, - arrowTipDistance: widget.arrowTipDistance, - borderColor: widget.borderColor, - borderRadius: widget.borderRadius, - borderWidth: widget.borderWidth, - bottom: bottom, - left: left, - preferredDirection: preferredDirection, - right: right, - target: target, - top: top, - bubbleDimensions: widget.bubbleDimensions, - ), - ), - child: widget.content, - ), - ), - ), - _buildCloseButton(), - ], - ), - ), - ), - ), - ), - ); - - // ignore: unnecessary_null_comparison - if (overlayState != null) { - overlayState.insertAll([ - if (showBlur) blur!, - if (showBarrier) _barrierEntry!, - _entry!, - ]); - } - } - - _showTooltip() async { - widget.onShow?.call(); - - // Already visible. - if (_entry != null) return; - - _createOverlayEntries(); - - await _animationController - .forward() - .whenComplete(_superTooltipController!.complete); - } - - _removeEntries() { - _entry?.remove(); - _entry = null; - _barrierEntry?.remove(); - _entry = null; - blur?.remove(); - } - - _hideTooltip() async { - widget.onHide?.call(); - await _animationController - .reverse() - .whenComplete(_superTooltipController!.complete); - - _removeEntries(); - } - - Widget _buildCloseButton() { - /** - * return Sizebox.shrizk if showCloseButton is false - */ - if (!showCloseButton) { - return const SizedBox.shrink(); - } - - double right; - double top; - - switch (widget.popupDirection) { - // - // LEFT: ------------------------------------- - case TooltipDirection.left: - right = widget.arrowLength + widget.arrowTipDistance + 3.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // RIGHT/UP: --------------------------------- - case TooltipDirection.right: - case TooltipDirection.up: - right = 5.0; - if (closeButtonType == CloseButtonType.inside) { - top = 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // DOWN: ------------------------------------- - case TooltipDirection.down: - // If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance - // is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside. - right = 2.0; - if (closeButtonType == CloseButtonType.inside) { - top = widget.arrowLength + widget.arrowTipDistance + 2.0; - } else if (closeButtonType == CloseButtonType.outside) { - top = 0.0; - } else { - throw AssertionError(closeButtonType); - } - break; - - // --------------------------------------------- - - default: - throw AssertionError(widget.popupDirection); - } - - // --- - - return Positioned( - right: right, - top: top, - child: Material( - color: Colors.transparent, - child: IconButton( - key: closeButtonType == CloseButtonType.inside - ? SuperTooltip.insideCloseButtonKey - : SuperTooltip.outsideCloseButtonKey, - icon: Icon( - Icons.close_outlined, - size: closeButtonSize, - color: closeButtonColor, - ), - onPressed: () async => await widget.controller!.hideTooltip(), - ), - ), - ); - } -} diff --git a/.history/lib/src/super_tooltip_controller_20240830121146.dart b/.history/lib/src/super_tooltip_controller_20240830121146.dart deleted file mode 100644 index cea7355..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830121146.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - - late Event event; - - Future showTooltip() { - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} diff --git a/.history/lib/src/super_tooltip_controller_20240830122654.dart b/.history/lib/src/super_tooltip_controller_20240830122654.dart deleted file mode 100644 index bfa626b..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830122654.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = false; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830122709.dart b/.history/lib/src/super_tooltip_controller_20240830122709.dart deleted file mode 100644 index ec6378b..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830122709.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = true; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830122857.dart b/.history/lib/src/super_tooltip_controller_20240830122857.dart deleted file mode 100644 index eb9a8c8..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830122857.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = true; - - late Event event; - - Future showTooltip({bool external = true}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip({bool external = true}) { - externalControlOnly = external; - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830123422.dart b/.history/lib/src/super_tooltip_controller_20240830123422.dart deleted file mode 100644 index 65709e0..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830123422.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = true; - - late Event event; - - Future showTooltip({bool external = true}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830123454.dart b/.history/lib/src/super_tooltip_controller_20240830123454.dart deleted file mode 100644 index a7d3cae..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830123454.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = true; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830123526.dart b/.history/lib/src/super_tooltip_controller_20240830123526.dart deleted file mode 100644 index 8355ba4..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830123526.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = true; - - late Event event; - - Future showTooltip({bool external = true}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830123535.dart b/.history/lib/src/super_tooltip_controller_20240830123535.dart deleted file mode 100644 index a7d3cae..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830123535.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = true; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830123552.dart b/.history/lib/src/super_tooltip_controller_20240830123552.dart deleted file mode 100644 index 7fa56df..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830123552.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = false; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830125828.dart b/.history/lib/src/super_tooltip_controller_20240830125828.dart deleted file mode 100644 index 3dac059..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830125828.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - - // External control flag - bool externalControlOnly = false; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControlOnly = external; // Set the flag based on external trigger - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} diff --git a/.history/lib/src/super_tooltip_controller_20240830130556.dart b/.history/lib/src/super_tooltip_controller_20240830130556.dart deleted file mode 100644 index 7fa56df..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830130556.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = false; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830132235.dart b/.history/lib/src/super_tooltip_controller_20240830132235.dart deleted file mode 100644 index 1cbc7e3..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830132235.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = false; - - late Event event; - - Future showTooltip({bool external = true}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830132556.dart b/.history/lib/src/super_tooltip_controller_20240830132556.dart deleted file mode 100644 index 70b023a..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830132556.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControl = false; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControl = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip({bool external = false}) { - externalControl = external; - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830132612.dart b/.history/lib/src/super_tooltip_controller_20240830132612.dart deleted file mode 100644 index bfa626b..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830132612.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - // External control flag - bool externalControlOnly = false; - - late Event event; - - Future showTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip({bool external = false}) { - externalControlOnly = external; - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -} - diff --git a/.history/lib/src/super_tooltip_controller_20240830140105.dart b/.history/lib/src/super_tooltip_controller_20240830140105.dart deleted file mode 100644 index cea7355..0000000 --- a/.history/lib/src/super_tooltip_controller_20240830140105.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'enums.dart'; - -class SuperTooltipController extends ChangeNotifier { - late Completer _completer; - bool _isVisible = false; - bool get isVisible => _isVisible; - - late Event event; - - Future showTooltip() { - event = Event.show; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = true); - } - - Future hideTooltip() { - event = Event.hide; - _completer = Completer(); - notifyListeners(); - return _completer.future.whenComplete(() => _isVisible = false); - } - - void complete() { - if (!_completer.isCompleted) { - _completer.complete(); - } - } -}