Skip to content

Commit df39208

Browse files
feat: ✨ Added left and right tooltip position with improved tooltip v2 (#511)
* fix: ⚡ updated tooltip implementation and also added support for left and right side tooltip
1 parent 9ed3999 commit df39208

19 files changed

+1799
-939
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
package.
88
- Fixed [#506](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/506) - Timer
99
was not canceling when tapped on `TooltipActionButton` which may cause issue when `autoPlay` is ON.
10+
- Improvement [#511](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/pull/511) -
11+
Improved Tooltip widget
12+
- Feature [#54](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/54) - Added
13+
Feasibility to position tooltip left and right to the target widget.
1014

1115
## [4.0.1]
1216
- Fixed [#493](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/493) - ShowCase.withWidget not showing issue

analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ linter:
2222
avoid_positional_boolean_parameters: false
2323
use_super_parameters: true
2424
prefer_relative_imports: true
25+
require_trailing_commas: true

lib/src/constants.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
class Constants {
2+
Constants._();
3+
4+
/// Arrow dimensions
5+
static const double arrowWidth = 18;
6+
static const double arrowHeight = 9;
7+
8+
static const double arrowStrokeWidth = 10;
9+
10+
/// Padding when arrow is visible
11+
static const double withArrowToolTipPadding = 7;
12+
13+
/// Padding when arrow is not visible
14+
static const double withOutArrowToolTipPadding = 0;
15+
16+
/// Distance between target and tooltip
17+
static const double tooltipOffset = 10;
18+
19+
/// Minimum tooltip dimensions to maintain usability
20+
static const double minimumToolTipWidth = 50;
21+
// Currently we are not constraining height but will do in future
22+
static const double minimumToolTipHeight = 50;
23+
24+
/// This is amount of extra offset scale alignment will have
25+
/// i.e if it is bottom position then centerBottom + [extraAlignmentOffset]
26+
/// in bottom
27+
static const double extraAlignmentOffset = 5;
28+
}

lib/src/enum.dart

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,56 @@
2020
* SOFTWARE.
2121
*/
2222

23+
import 'dart:math';
24+
25+
import 'package:flutter/widgets.dart';
26+
2327
import 'showcase_widget.dart';
28+
import 'tooltip/render_object_manager.dart';
29+
30+
enum TooltipPosition {
31+
top(rotationAngle: pi, scaleAlignment: Alignment.topCenter),
32+
bottom(rotationAngle: 0, scaleAlignment: Alignment.bottomCenter),
33+
left(rotationAngle: pi * 0.5, scaleAlignment: Alignment.centerLeft),
34+
right(rotationAngle: 3 * pi * 0.5, scaleAlignment: Alignment.centerRight);
35+
36+
const TooltipPosition({
37+
required this.rotationAngle,
38+
required this.scaleAlignment,
39+
});
40+
41+
/// Initial position of the arrow is pointing top so we need to rotate as per the position of the tooltip
42+
/// This will provide necessary rotation to properly point arrow
43+
final double rotationAngle;
44+
45+
/// Determines the default scale alignment based on tooltip position.
46+
final Alignment scaleAlignment;
47+
48+
/// Computes the offset movement animation based on tooltip position.
49+
Offset calculateMoveOffset(
50+
double animationValue,
51+
double toolTipSlideEndDistance,
52+
) {
53+
switch (this) {
54+
case TooltipPosition.top:
55+
return Offset(0, (1 - animationValue) * -toolTipSlideEndDistance);
56+
case TooltipPosition.bottom:
57+
return Offset(0, (1 - animationValue) * toolTipSlideEndDistance);
58+
case TooltipPosition.left:
59+
return Offset((1 - animationValue) * -toolTipSlideEndDistance, 0);
60+
case TooltipPosition.right:
61+
return Offset((1 - animationValue) * toolTipSlideEndDistance, 0);
62+
}
63+
}
64+
65+
bool get isRight => this == TooltipPosition.right;
66+
bool get isLeft => this == TooltipPosition.left;
67+
bool get isTop => this == TooltipPosition.top;
68+
bool get isBottom => this == TooltipPosition.bottom;
2469

25-
enum TooltipPosition { top, bottom }
70+
bool get isHorizontal => isRight || isLeft;
71+
bool get isVertical => isTop || isBottom;
72+
}
2673

2774
enum TooltipActionPosition {
2875
outside,
@@ -60,3 +107,14 @@ enum TooltipDefaultActionType {
60107
}
61108
}
62109
}
110+
111+
/// These are the ToolTip layout widget ids and will be used to identify
112+
/// the widget during layout and painting phase
113+
enum TooltipLayoutSlot {
114+
tooltipBox,
115+
actionBox,
116+
arrow;
117+
118+
RenderObjectManager? get getObjectManager =>
119+
RenderObjectManager.renderObjects[this];
120+
}

lib/src/get_position.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class GetPosition {
4444
late final RenderBox? _box;
4545
late final Offset? _boxOffset;
4646

47+
RenderBox? get box => _box;
48+
4749
void getRenderBox() {
4850
var renderBox = key.currentContext?.findRenderObject() as RenderBox?;
4951

lib/src/layout_overlays.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ import 'package:flutter/material.dart';
2525
import 'showcase_widget.dart';
2626

2727
typedef OverlayBuilderCallback = Widget Function(
28-
BuildContext, Rect anchorBounds, Offset anchor);
28+
BuildContext context,
29+
Rect anchorBounds,
30+
Offset anchor,
31+
);
2932

3033
/// Displays an overlay Widget anchored directly above the center of this
3134
/// [AnchoredOverlay].

lib/src/showcase.dart

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import 'models/tooltip_action_button.dart';
3333
import 'models/tooltip_action_config.dart';
3434
import 'shape_clipper.dart';
3535
import 'showcase_widget.dart';
36+
import 'tooltip/tooltip.dart';
3637
import 'tooltip_action_button_widget.dart';
37-
import 'tooltip_widget.dart';
3838
import 'widget/floating_action_widget.dart';
3939

4040
class Showcase extends StatefulWidget {
@@ -272,8 +272,8 @@ class Showcase extends StatefulWidget {
272272
/// To understand how text is aligned, check [TextAlign]
273273
final TextAlign descriptionTextAlign;
274274

275-
/// Defines the margin for the tooltip.
276-
/// Which is from 0 to [toolTipSlideEndDistance].
275+
/// Defines the margin from the screen edge for the tooltip.
276+
/// ToolTip will try to not cross this border around the screen
277277
///
278278
/// Defaults to 14.
279279
final double toolTipMargin;
@@ -357,7 +357,7 @@ class Showcase extends StatefulWidget {
357357
/// - `blurValue`: The amount of background blur applied during the showcase.
358358
/// - `tooltipPosition`: The position of the tooltip relative to the showcased widget.
359359
/// - `toolTipSlideEndDistance`: The distance the tooltip slides in from the edge of the screen (defaults to 7dp).
360-
/// - `toolTipMargin`: The margin around the tooltip (defaults to 14dp).
360+
/// - `toolTipMargin`: The margin around the screen which tooltip try not to cross (defaults to 14dp).
361361
/// - `tooltipActions`: A list of custom actions (widgets) to display within the tooltip.
362362
/// - `tooltipActionConfig`: Configuration options for custom tooltip actions.
363363
/// - `scrollAlignment`: Defines the alignment for the auto scroll function.
@@ -424,14 +424,22 @@ class Showcase extends StatefulWidget {
424424
}) : height = null,
425425
width = null,
426426
container = null,
427-
assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0,
428-
"overlay opacity must be between 0 and 1."),
429-
assert(onTargetClick == null || disposeOnTap != null,
430-
"disposeOnTap is required if you're using onTargetClick"),
431-
assert(disposeOnTap == null || onTargetClick != null,
432-
"onTargetClick is required if you're using disposeOnTap"),
433-
assert(onBarrierClick == null || disableBarrierInteraction == false,
434-
"can't use onBarrierClick & disableBarrierInteraction property at same time");
427+
assert(
428+
overlayOpacity >= 0.0 && overlayOpacity <= 1.0,
429+
"overlay opacity must be between 0 and 1.",
430+
),
431+
assert(
432+
onTargetClick == null || disposeOnTap != null,
433+
"disposeOnTap is required if you're using onTargetClick",
434+
),
435+
assert(
436+
disposeOnTap == null || onTargetClick != null,
437+
"onTargetClick is required if you're using disposeOnTap",
438+
),
439+
assert(
440+
onBarrierClick == null || disableBarrierInteraction == false,
441+
"can't use onBarrierClick & disableBarrierInteraction property at same time",
442+
);
435443

436444
/// Creates a Showcase widget with a custom tooltip widget.
437445
///
@@ -501,7 +509,8 @@ class Showcase extends StatefulWidget {
501509
this.targetBorderRadius,
502510
this.overlayOpacity = 0.75,
503511
this.scrollLoadingWidget = const CircularProgressIndicator(
504-
valueColor: AlwaysStoppedAnimation(Colors.white)),
512+
valueColor: AlwaysStoppedAnimation(Colors.white),
513+
),
505514
this.onTargetClick,
506515
this.disposeOnTap,
507516
this.movingAnimationDuration = const Duration(milliseconds: 2000),
@@ -542,10 +551,14 @@ class Showcase extends StatefulWidget {
542551
titleTextDirection = null,
543552
descriptionTextDirection = null,
544553
toolTipMargin = 14,
545-
assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0,
546-
"overlay opacity must be between 0 and 1."),
547-
assert(onBarrierClick == null || disableBarrierInteraction == false,
548-
"can't use onBarrierClick & disableBarrierInteraction property at same time");
554+
assert(
555+
overlayOpacity >= 0.0 && overlayOpacity <= 1.0,
556+
"overlay opacity must be between 0 and 1.",
557+
),
558+
assert(
559+
onBarrierClick == null || disableBarrierInteraction == false,
560+
"can't use onBarrierClick & disableBarrierInteraction property at same time",
561+
);
549562

550563
@override
551564
State<Showcase> createState() => _ShowcaseState();
@@ -606,8 +619,9 @@ class _ShowcaseState extends State<Showcase> {
606619

607620
if (showCaseWidgetState.autoPlay) {
608621
timer = Timer(
609-
Duration(seconds: showCaseWidgetState.autoPlayDelay.inSeconds),
610-
_nextIfAny);
622+
Duration(seconds: showCaseWidgetState.autoPlayDelay.inSeconds),
623+
_nextIfAny,
624+
);
611625
}
612626
} else if (timer?.isActive ?? false) {
613627
timer?.cancel();
@@ -770,6 +784,9 @@ class _ShowcaseState extends State<Showcase> {
770784
height: mediaQuerySize.height,
771785
decoration: BoxDecoration(
772786
color: widget.overlayColor
787+
788+
//TODO: Update when we remove support for older version
789+
//ignore: deprecated_member_use
773790
.withOpacity(widget.overlayOpacity),
774791
),
775792
),
@@ -779,6 +796,8 @@ class _ShowcaseState extends State<Showcase> {
779796
height: mediaQuerySize.height,
780797
decoration: BoxDecoration(
781798
color: widget.overlayColor
799+
//TODO: Update when we remove support for older version
800+
//ignore: deprecated_member_use
782801
.withOpacity(widget.overlayOpacity),
783802
),
784803
),
@@ -799,8 +818,6 @@ class _ShowcaseState extends State<Showcase> {
799818
),
800819
ToolTipWidget(
801820
position: position,
802-
offset: offset,
803-
screenSize: screenSize,
804821
title: widget.title,
805822
titleTextAlign: widget.titleTextAlign,
806823
description: widget.description,
@@ -810,22 +827,19 @@ class _ShowcaseState extends State<Showcase> {
810827
titleTextStyle: widget.titleTextStyle,
811828
descTextStyle: widget.descTextStyle,
812829
container: widget.container,
813-
floatingActionWidget:
814-
widget.floatingActionWidget ?? _globalFloatingActionWidget,
815830
tooltipBackgroundColor: widget.tooltipBackgroundColor,
816831
textColor: widget.textColor,
817832
showArrow: widget.showArrow,
818-
contentHeight: widget.height,
819-
contentWidth: widget.width,
820833
onTooltipTap:
821834
widget.disposeOnTap == true || widget.onToolTipClick != null
822835
? _getOnTooltipTap
823836
: null,
824837
tooltipPadding: widget.tooltipPadding,
825838
disableMovingAnimation: widget.disableMovingAnimation ??
826839
showCaseWidgetState.disableMovingAnimation,
827-
disableScaleAnimation: widget.disableScaleAnimation ??
828-
showCaseWidgetState.disableScaleAnimation,
840+
disableScaleAnimation: (widget.disableScaleAnimation ??
841+
showCaseWidgetState.disableScaleAnimation) ||
842+
widget.container != null,
829843
movingAnimationDuration: widget.movingAnimationDuration,
830844
tooltipBorderRadius: widget.tooltipBorderRadius,
831845
scaleAnimationDuration: widget.scaleAnimationDuration,
@@ -841,12 +855,17 @@ class _ShowcaseState extends State<Showcase> {
841855
toolTipMargin: widget.toolTipMargin,
842856
tooltipActionConfig: _getTooltipActionConfig(),
843857
tooltipActions: _getTooltipActions(),
858+
targetPadding: widget.targetPadding,
844859
),
860+
if (_getFloatingActionWidget != null) _getFloatingActionWidget!,
845861
],
846862
],
847863
);
848864
}
849865

866+
Widget? get _getFloatingActionWidget =>
867+
widget.floatingActionWidget ?? _globalFloatingActionWidget;
868+
850869
List<Widget> _getTooltipActions() {
851870
final actionData = (widget.tooltipActions?.isNotEmpty ?? false)
852871
? widget.tooltipActions!

lib/src/showcase_widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class ShowCaseWidgetState extends State<ShowCaseWidget> {
219219
/// This Stores keys of showcase for which we will hide the
220220
/// [globalFloatingActionWidget].
221221
late final _hideFloatingWidgetKeys = {
222-
for (final item in widget.hideFloatingActionWidgetForShowcase) item: true
222+
for (final item in widget.hideFloatingActionWidgetForShowcase) item: true,
223223
};
224224

225225
/// Returns value of [ShowCaseWidget.blurValue]

0 commit comments

Comments
 (0)