Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1696] Refactored Tooltip implementation #1834

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions modules/ensemble/lib/framework/ensemble_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:ensemble/framework/error_handling.dart';
import 'package:ensemble/framework/scope.dart';
import 'package:ensemble/framework/studio/studio_debugger.dart';
import 'package:ensemble/framework/view/data_scope_widget.dart';
import 'package:ensemble/util/utils.dart';
import 'package:ensemble/widget/helpers/controllers.dart';
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
import 'package:flutter/cupertino.dart';
Expand Down Expand Up @@ -67,6 +68,18 @@ abstract class EnsembleWidgetState<W extends EnsembleWidget> extends State<W> {
child: rtn);
}


// add tooltip handling if tooltip message is specified
// add tooltip handling if tooltip message is specified
if (widgetController.toolTip != null) {
rtn = Utils.getTooltipWidget(
context,
rtn,
widgetController.toolTip,
widgetController
);
}

// in Web, capture the pointer if overlay on htmlelementview like Maps
if (widgetController.captureWebPointer == true) {
rtn = PointerInterceptor(child: rtn);
Expand Down
10 changes: 10 additions & 0 deletions modules/ensemble/lib/framework/widget/widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ abstract class EWidgetState<W extends HasController>
child: rtn);
}

// add tooltip handling if tooltip message is specified
if (widgetController.toolTip != null) {
rtn = Utils.getTooltipWidget(
context,
rtn,
widgetController.toolTip,
widgetController
);
}

// in Web, capture the pointer if overlay on htmlelementview like Maps
if (widgetController.captureWebPointer == true) {
rtn = PointerInterceptor(child: rtn);
Expand Down
78 changes: 78 additions & 0 deletions modules/ensemble/lib/util/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import 'dart:math';
import 'dart:ui';
import 'package:ensemble/ensemble.dart';
import 'package:ensemble/ensemble_app.dart';
import 'package:ensemble/framework/action.dart';
import 'package:ensemble/framework/stub/location_manager.dart';
import 'package:ensemble/framework/theme/theme_manager.dart';
import 'package:ensemble/screen_controller.dart';
import 'package:ensemble/widget/helpers/tooltip_composite.dart';
import 'package:ensemble_ts_interpreter/invokables/UserLocale.dart';
import 'package:path/path.dart' as p;

Expand Down Expand Up @@ -488,6 +491,81 @@ class Utils {
return null;
}

// Creates tooltip composite from inputs
static TooltipStyleComposite? getTooltipStyleComposite(
ChangeNotifier controller, dynamic inputs) {
if (inputs is Map) {
return TooltipStyleComposite(controller, inputs: inputs);
}
return null;
}

/// Creates tooltip widget with configured styles and behavior
static Widget getTooltipWidget(
BuildContext context,
Widget child,
Map<String, dynamic>? tooltipData,
ChangeNotifier controller
) {
if (tooltipData == null) return child;

final tooltip = TooltipData.from(tooltipData, controller);
if (tooltip == null) return child;

final tooltipKey = GlobalKey();
// Start with the original child
Widget tooltipChild = child;

if (kIsWeb && tooltip.styles?.triggerMode == null) {
tooltipChild = MouseRegion(
onEnter: (_) {
final dynamic tooltip = tooltipKey.currentState;
tooltip?.ensureTooltipVisible();
},
onExit: (_) {
final dynamic tooltip = tooltipKey.currentState;
tooltip?.deactivate();
},
child: tooltipChild,
);
}

return Tooltip(
key: tooltipKey,
message: tooltip.message,
textStyle: tooltip.styles?.textStyle,
padding: tooltip.styles?.padding,
margin: tooltip.styles?.margin,
verticalOffset: tooltip.styles?.verticalOffset,
preferBelow: tooltip.styles?.preferBelow,
waitDuration:
tooltip.styles?.waitDuration ?? const Duration(milliseconds: 0),
showDuration:
tooltip.styles?.showDuration ?? const Duration(milliseconds: 1500),
triggerMode: tooltip.styles?.triggerMode ?? TooltipTriggerMode.tap,
enableFeedback: true,
decoration: BoxDecoration(
color: tooltip.styles?.backgroundColor ?? Colors.grey[700],
borderRadius: tooltip.styles?.borderRadius,
border: (tooltip.styles?.borderColor != null ||
tooltip.styles?.borderWidth != null)
? Border.all(
color: tooltip.styles?.borderColor ??
ThemeManager().getBorderColor(context),
width: (tooltip.styles?.borderWidth ??
ThemeManager().getBorderThickness(context))
.toDouble(),
)
: null,
),
onTriggered: tooltip.onTriggered != null
? () =>
ScreenController().executeAction(context, tooltip.onTriggered!)
: null,
child: tooltipChild,
);
}

static BoxShadowComposite? getBoxShadowComposite(
ChangeNotifier widgetController, dynamic inputs) {
if (inputs is Map) {
Expand Down
17 changes: 14 additions & 3 deletions modules/ensemble/lib/widget/helpers/controllers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import 'package:ensemble/model/transform_matrix.dart';
import 'package:ensemble/page_model.dart';
import 'package:ensemble/util/utils.dart';
import 'package:ensemble/widget/helpers/box_animation_composite.dart';
import 'package:ensemble/widget/helpers/tooltip_composite.dart';
import 'package:ensemble_ts_interpreter/errors.dart';
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

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

// properties for tooltip
Map<String, dynamic>? toolTip;

// legacy used to show as the form label if used inside Form
@Deprecated("don't use anymore")
String? label;
Expand Down Expand Up @@ -279,7 +284,8 @@ abstract class WidgetController extends Controller with HasStyles {
'textDirection': (value) => textDirection = Utils.getTextDirection(value),
'label': (value) => label = Utils.optionalString(value),
'classList': (value) => classList = value,
'className': (value) => className = value
'className': (value) => className = value,
'tooltip': (value) => toolTip = Utils.getMap(value),
};
}

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

// properties for tooltip
Map<String, dynamic>? toolTip;


@override
Map<String, Function> getters() {
return {
Expand Down Expand Up @@ -499,8 +509,9 @@ abstract class EnsembleWidgetController extends EnsembleController
'captureWebPointer': (value) =>
captureWebPointer = Utils.optionalBool(value),
'classList': (value) => classList = value,
'className': (value) => className = value
};
'className': (value) => className = value,
'tooltip': (value) => toolTip = Utils.getMap(value),
};
}

bool hasPositions() {
Expand Down
98 changes: 98 additions & 0 deletions modules/ensemble/lib/widget/helpers/tooltip_composite.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/// This class contains helper controllers for our widgets.
import 'package:ensemble/framework/action.dart';
import 'package:ensemble/framework/extensions.dart';
import 'package:ensemble/util/utils.dart';
import 'package:ensemble/widget/helpers/controllers.dart';
import 'package:flutter/material.dart';

class TooltipData {
final String message;
final TooltipStyleComposite? styles;
final EnsembleAction? onTriggered;

TooltipData({
required this.message,
this.styles,
this.onTriggered,
});

static TooltipData? from(Map<String, dynamic>? data, ChangeNotifier controller) {
if (data == null) return null;

return TooltipData(
message: Utils.getString(data['message'], fallback: ''),
styles: data['styles'] != null ?
TooltipStyleComposite(controller, inputs: data['styles']) : null,
onTriggered: data['onTriggered'] != null ?
EnsembleAction.from(data['onTriggered']) : null,
);
}
}

// Composite class to handle tooltip styling and behavior
class TooltipStyleComposite extends WidgetCompositeProperty {
TooltipStyleComposite(super.widgetController, {required Map inputs}) {
textStyle = Utils.getTextStyle(inputs['textStyle']);
verticalOffset = Utils.optionalDouble(inputs['verticalOffset']);
preferBelow = Utils.optionalBool(inputs['preferBelow']);
waitDuration = Utils.getDuration(inputs['waitDuration']);
showDuration = Utils.getDuration(inputs['showDuration']);
triggerMode = TooltipTriggerMode.values.from(inputs['triggerMode']);
backgroundColor = Utils.getColor(inputs['backgroundColor']);
borderRadius = Utils.getBorderRadius(inputs['borderRadius'])?.getValue();
padding = Utils.optionalInsets(inputs['padding']);
margin = Utils.optionalInsets(inputs['margin']);
borderColor = Utils.getColor(inputs['borderColor']);
borderWidth = Utils.optionalInt(inputs['borderWidth']);
}

TextStyle? textStyle;
double? verticalOffset;
bool? preferBelow;
Duration? waitDuration;
Duration? showDuration;
TooltipTriggerMode? triggerMode;
Color? backgroundColor;
BorderRadius? borderRadius;
EdgeInsets? padding;
EdgeInsets? margin;
Color? borderColor;
int? borderWidth;

@override
Map<String, Function> setters() {
return {
'textStyle': (value) => textStyle = Utils.getTextStyle(value),
'verticalOffset': (value) => verticalOffset = Utils.optionalDouble(value),
'preferBelow': (value) => preferBelow = Utils.optionalBool(value),
'waitDuration': (value) => waitDuration = Utils.getDuration(value),
'showDuration': (value) => showDuration = Utils.getDuration(value),
'triggerMode': (value) => triggerMode = TooltipTriggerMode.values.from(value),
'backgroundColor': (value) => backgroundColor = Utils.getColor(value),
'borderRadius': (value) => borderRadius = Utils.getBorderRadius(value)?.getValue(),
'padding': (value) => padding = Utils.optionalInsets(value),
'margin': (value) => margin = Utils.optionalInsets(value),
'borderColor': (value) => borderColor = Utils.getColor(value),
'borderWidth': (value) => borderWidth = Utils.optionalInt(value),
};
}

@override
Map<String, Function> getters() => {
'textStyle': () => textStyle,
'verticalOffset': () => verticalOffset,
'preferBelow': () => preferBelow,
'waitDuration': () => waitDuration,
'showDuration': () => showDuration,
'triggerMode': () => triggerMode,
'backgroundColor': () => backgroundColor,
'borderRadius': () => borderRadius,
'padding': () => padding,
'margin': () => margin,
'borderColor': () => borderColor,
'borderWidth': () => borderWidth,
};

@override
Map<String, Function> methods() => {};
}
Loading