Skip to content

Commit 4ba57cb

Browse files
authored
Merge pull request #1834 from EnsembleUI/tooltip0x45
[1696] Refactored Tooltip implementation
2 parents 4ceaa1c + c7e8e84 commit 4ba57cb

File tree

7 files changed

+213
-168
lines changed

7 files changed

+213
-168
lines changed

modules/ensemble/lib/framework/ensemble_widget.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:ensemble/framework/error_handling.dart';
55
import 'package:ensemble/framework/scope.dart';
66
import 'package:ensemble/framework/studio/studio_debugger.dart';
77
import 'package:ensemble/framework/view/data_scope_widget.dart';
8+
import 'package:ensemble/util/utils.dart';
89
import 'package:ensemble/widget/helpers/controllers.dart';
910
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
1011
import 'package:flutter/cupertino.dart';
@@ -67,6 +68,18 @@ abstract class EnsembleWidgetState<W extends EnsembleWidget> extends State<W> {
6768
child: rtn);
6869
}
6970

71+
72+
// add tooltip handling if tooltip message is specified
73+
// add tooltip handling if tooltip message is specified
74+
if (widgetController.toolTip != null) {
75+
rtn = Utils.getTooltipWidget(
76+
context,
77+
rtn,
78+
widgetController.toolTip,
79+
widgetController
80+
);
81+
}
82+
7083
// in Web, capture the pointer if overlay on htmlelementview like Maps
7184
if (widgetController.captureWebPointer == true) {
7285
rtn = PointerInterceptor(child: rtn);

modules/ensemble/lib/framework/widget/widget.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ abstract class EWidgetState<W extends HasController>
7575
child: rtn);
7676
}
7777

78+
// add tooltip handling if tooltip message is specified
79+
if (widgetController.toolTip != null) {
80+
rtn = Utils.getTooltipWidget(
81+
context,
82+
rtn,
83+
widgetController.toolTip,
84+
widgetController
85+
);
86+
}
87+
7888
// in Web, capture the pointer if overlay on htmlelementview like Maps
7989
if (widgetController.captureWebPointer == true) {
8090
rtn = PointerInterceptor(child: rtn);

modules/ensemble/lib/util/utils.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ import 'dart:math';
33
import 'dart:ui';
44
import 'package:ensemble/ensemble.dart';
55
import 'package:ensemble/ensemble_app.dart';
6+
import 'package:ensemble/framework/action.dart';
67
import 'package:ensemble/framework/ensemble_config_service.dart';
78
import 'package:ensemble/framework/stub/location_manager.dart';
89
import 'package:ensemble/framework/theme/theme_manager.dart';
10+
import 'package:ensemble/screen_controller.dart';
11+
import 'package:ensemble/widget/helpers/tooltip_composite.dart';
912
import 'package:ensemble_ts_interpreter/invokables/UserLocale.dart';
1013
import 'package:path/path.dart' as p;
1114

@@ -489,6 +492,81 @@ class Utils {
489492
return null;
490493
}
491494

495+
// Creates tooltip composite from inputs
496+
static TooltipStyleComposite? getTooltipStyleComposite(
497+
ChangeNotifier controller, dynamic inputs) {
498+
if (inputs is Map) {
499+
return TooltipStyleComposite(controller, inputs: inputs);
500+
}
501+
return null;
502+
}
503+
504+
/// Creates tooltip widget with configured styles and behavior
505+
static Widget getTooltipWidget(
506+
BuildContext context,
507+
Widget child,
508+
Map<String, dynamic>? tooltipData,
509+
ChangeNotifier controller
510+
) {
511+
if (tooltipData == null) return child;
512+
513+
final tooltip = TooltipData.from(tooltipData, controller);
514+
if (tooltip == null) return child;
515+
516+
final tooltipKey = GlobalKey();
517+
// Start with the original child
518+
Widget tooltipChild = child;
519+
520+
if (kIsWeb && tooltip.styles?.triggerMode == null) {
521+
tooltipChild = MouseRegion(
522+
onEnter: (_) {
523+
final dynamic tooltip = tooltipKey.currentState;
524+
tooltip?.ensureTooltipVisible();
525+
},
526+
onExit: (_) {
527+
final dynamic tooltip = tooltipKey.currentState;
528+
tooltip?.deactivate();
529+
},
530+
child: tooltipChild,
531+
);
532+
}
533+
534+
return Tooltip(
535+
key: tooltipKey,
536+
message: tooltip.message,
537+
textStyle: tooltip.styles?.textStyle,
538+
padding: tooltip.styles?.padding,
539+
margin: tooltip.styles?.margin,
540+
verticalOffset: tooltip.styles?.verticalOffset,
541+
preferBelow: tooltip.styles?.preferBelow,
542+
waitDuration:
543+
tooltip.styles?.waitDuration ?? const Duration(milliseconds: 0),
544+
showDuration:
545+
tooltip.styles?.showDuration ?? const Duration(milliseconds: 1500),
546+
triggerMode: tooltip.styles?.triggerMode ?? TooltipTriggerMode.tap,
547+
enableFeedback: true,
548+
decoration: BoxDecoration(
549+
color: tooltip.styles?.backgroundColor ?? Colors.grey[700],
550+
borderRadius: tooltip.styles?.borderRadius,
551+
border: (tooltip.styles?.borderColor != null ||
552+
tooltip.styles?.borderWidth != null)
553+
? Border.all(
554+
color: tooltip.styles?.borderColor ??
555+
ThemeManager().getBorderColor(context),
556+
width: (tooltip.styles?.borderWidth ??
557+
ThemeManager().getBorderThickness(context))
558+
.toDouble(),
559+
)
560+
: null,
561+
),
562+
onTriggered: tooltip.onTriggered != null
563+
? () =>
564+
ScreenController().executeAction(context, tooltip.onTriggered!)
565+
: null,
566+
child: tooltipChild,
567+
);
568+
}
569+
492570
static BoxShadowComposite? getBoxShadowComposite(
493571
ChangeNotifier widgetController, dynamic inputs) {
494572
if (inputs is Map) {

modules/ensemble/lib/widget/helpers/controllers.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import 'package:ensemble/model/transform_matrix.dart';
88
import 'package:ensemble/page_model.dart';
99
import 'package:ensemble/util/utils.dart';
1010
import 'package:ensemble/widget/helpers/box_animation_composite.dart';
11+
import 'package:ensemble/widget/helpers/tooltip_composite.dart';
1112
import 'package:ensemble_ts_interpreter/errors.dart';
1213
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
1314
import 'package:flutter/cupertino.dart';
15+
import 'package:flutter/material.dart';
1416

1517
import '../../model/capabilities.dart';
1618

@@ -223,6 +225,9 @@ abstract class WidgetController extends Controller with HasStyles {
223225
// https://pub.dev/packages/pointer_interceptor
224226
bool? captureWebPointer;
225227

228+
// properties for tooltip
229+
Map<String, dynamic>? toolTip;
230+
226231
// legacy used to show as the form label if used inside Form
227232
@Deprecated("don't use anymore")
228233
String? label;
@@ -279,7 +284,8 @@ abstract class WidgetController extends Controller with HasStyles {
279284
'textDirection': (value) => textDirection = Utils.getTextDirection(value),
280285
'label': (value) => label = Utils.optionalString(value),
281286
'classList': (value) => classList = value,
282-
'className': (value) => className = value
287+
'className': (value) => className = value,
288+
'tooltip': (value) => toolTip = Utils.getMap(value),
283289
};
284290
}
285291

@@ -458,6 +464,10 @@ abstract class EnsembleWidgetController extends EnsembleController
458464
// https://pub.dev/packages/pointer_interceptor
459465
bool? captureWebPointer;
460466

467+
// properties for tooltip
468+
Map<String, dynamic>? toolTip;
469+
470+
461471
@override
462472
Map<String, Function> getters() {
463473
return {
@@ -499,8 +509,9 @@ abstract class EnsembleWidgetController extends EnsembleController
499509
'captureWebPointer': (value) =>
500510
captureWebPointer = Utils.optionalBool(value),
501511
'classList': (value) => classList = value,
502-
'className': (value) => className = value
503-
};
512+
'className': (value) => className = value,
513+
'tooltip': (value) => toolTip = Utils.getMap(value),
514+
};
504515
}
505516

506517
bool hasPositions() {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/// This class contains helper controllers for our widgets.
2+
import 'package:ensemble/framework/action.dart';
3+
import 'package:ensemble/framework/extensions.dart';
4+
import 'package:ensemble/util/utils.dart';
5+
import 'package:ensemble/widget/helpers/controllers.dart';
6+
import 'package:flutter/material.dart';
7+
8+
class TooltipData {
9+
final String message;
10+
final TooltipStyleComposite? styles;
11+
final EnsembleAction? onTriggered;
12+
13+
TooltipData({
14+
required this.message,
15+
this.styles,
16+
this.onTriggered,
17+
});
18+
19+
static TooltipData? from(Map<String, dynamic>? data, ChangeNotifier controller) {
20+
if (data == null) return null;
21+
22+
return TooltipData(
23+
message: Utils.getString(data['message'], fallback: ''),
24+
styles: data['styles'] != null ?
25+
TooltipStyleComposite(controller, inputs: data['styles']) : null,
26+
onTriggered: data['onTriggered'] != null ?
27+
EnsembleAction.from(data['onTriggered']) : null,
28+
);
29+
}
30+
}
31+
32+
// Composite class to handle tooltip styling and behavior
33+
class TooltipStyleComposite extends WidgetCompositeProperty {
34+
TooltipStyleComposite(super.widgetController, {required Map inputs}) {
35+
textStyle = Utils.getTextStyle(inputs['textStyle']);
36+
verticalOffset = Utils.optionalDouble(inputs['verticalOffset']);
37+
preferBelow = Utils.optionalBool(inputs['preferBelow']);
38+
waitDuration = Utils.getDuration(inputs['waitDuration']);
39+
showDuration = Utils.getDuration(inputs['showDuration']);
40+
triggerMode = TooltipTriggerMode.values.from(inputs['triggerMode']);
41+
backgroundColor = Utils.getColor(inputs['backgroundColor']);
42+
borderRadius = Utils.getBorderRadius(inputs['borderRadius'])?.getValue();
43+
padding = Utils.optionalInsets(inputs['padding']);
44+
margin = Utils.optionalInsets(inputs['margin']);
45+
borderColor = Utils.getColor(inputs['borderColor']);
46+
borderWidth = Utils.optionalInt(inputs['borderWidth']);
47+
}
48+
49+
TextStyle? textStyle;
50+
double? verticalOffset;
51+
bool? preferBelow;
52+
Duration? waitDuration;
53+
Duration? showDuration;
54+
TooltipTriggerMode? triggerMode;
55+
Color? backgroundColor;
56+
BorderRadius? borderRadius;
57+
EdgeInsets? padding;
58+
EdgeInsets? margin;
59+
Color? borderColor;
60+
int? borderWidth;
61+
62+
@override
63+
Map<String, Function> setters() {
64+
return {
65+
'textStyle': (value) => textStyle = Utils.getTextStyle(value),
66+
'verticalOffset': (value) => verticalOffset = Utils.optionalDouble(value),
67+
'preferBelow': (value) => preferBelow = Utils.optionalBool(value),
68+
'waitDuration': (value) => waitDuration = Utils.getDuration(value),
69+
'showDuration': (value) => showDuration = Utils.getDuration(value),
70+
'triggerMode': (value) => triggerMode = TooltipTriggerMode.values.from(value),
71+
'backgroundColor': (value) => backgroundColor = Utils.getColor(value),
72+
'borderRadius': (value) => borderRadius = Utils.getBorderRadius(value)?.getValue(),
73+
'padding': (value) => padding = Utils.optionalInsets(value),
74+
'margin': (value) => margin = Utils.optionalInsets(value),
75+
'borderColor': (value) => borderColor = Utils.getColor(value),
76+
'borderWidth': (value) => borderWidth = Utils.optionalInt(value),
77+
};
78+
}
79+
80+
@override
81+
Map<String, Function> getters() => {
82+
'textStyle': () => textStyle,
83+
'verticalOffset': () => verticalOffset,
84+
'preferBelow': () => preferBelow,
85+
'waitDuration': () => waitDuration,
86+
'showDuration': () => showDuration,
87+
'triggerMode': () => triggerMode,
88+
'backgroundColor': () => backgroundColor,
89+
'borderRadius': () => borderRadius,
90+
'padding': () => padding,
91+
'margin': () => margin,
92+
'borderColor': () => borderColor,
93+
'borderWidth': () => borderWidth,
94+
};
95+
96+
@override
97+
Map<String, Function> methods() => {};
98+
}

0 commit comments

Comments
 (0)