diff --git a/lib/common/widgets/custom_tooltip.dart b/lib/common/widgets/custom_tooltip.dart index b33058edd..eea160d69 100644 --- a/lib/common/widgets/custom_tooltip.dart +++ b/lib/common/widgets/custom_tooltip.dart @@ -3,6 +3,11 @@ import 'dart:ui' show clampDouble; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart' + show + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin, + MultiChildLayoutParentData; import 'package:flutter/widgets.dart'; enum TooltipType { top, right } @@ -74,8 +79,10 @@ class _CustomTooltipState extends State { @protected @override void dispose() { - _longPressRecognizer?.onLongPressCancel = null; - _longPressRecognizer?.dispose(); + _longPressRecognizer + ?..onLongPressCancel = null + ..dispose(); + _longPressRecognizer = null; super.dispose(); } @@ -128,38 +135,31 @@ class _CustomTooltipOverlay extends StatelessWidget { @override Widget build(BuildContext context) { - Widget child = CustomMultiChildLayout( - delegate: _CustomMultiTooltipPositionDelegate( - type: type, - target: target, - verticalOffset: verticalOffset, - horizontalOffset: horizontalOffset, - preferBelow: false, - ), + return _ToolTip( + type: type, + target: target, + verticalOffset: verticalOffset, + horizontalOffset: horizontalOffset, + preferBelow: false, + onTap: PlatformUtils.isMobile ? onDismiss : null, children: [ - LayoutId( - id: _ChildType.overlay, - child: overlayWidget(), - ), LayoutId( id: _ChildType.indicator, child: indicator(), ), + LayoutId( + id: _ChildType.overlay, + child: overlayWidget(), + ), ], ); - if (PlatformUtils.isMobile) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: onDismiss, - child: child, - ); - } - return child; } } -class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate { - _CustomMultiTooltipPositionDelegate({ +class _ToolTip extends MultiChildRenderObjectWidget { + const _ToolTip({ + super.children, + this.onTap, required this.type, required this.target, required this.verticalOffset, @@ -167,98 +167,188 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate { required this.preferBelow, }); + final VoidCallback? onTap; final TooltipType type; - final Offset target; - final double verticalOffset; - final double horizontalOffset; - final bool preferBelow; @override - void performLayout(Size size) { - switch (type) { - case TooltipType.top: - Size? indicatorSize; - if (hasChild(_ChildType.indicator)) { - indicatorSize = layoutChild( - _ChildType.indicator, - BoxConstraints.loose(size), - ); - } + RenderObject createRenderObject(BuildContext context) { + return _RenderToolTip( + onTap: onTap, + type: type, + target: target, + verticalOffset: verticalOffset, + horizontalOffset: horizontalOffset, + preferBelow: preferBelow, + ); + } - if (hasChild(_ChildType.overlay)) { - final overlaySize = layoutChild( - _ChildType.overlay, - BoxConstraints.loose(size), - ); - Offset offset = positionDependentBox( - type: type, - size: size, - childSize: overlaySize, - target: target, - verticalOffset: verticalOffset, - horizontalOffset: horizontalOffset, - preferBelow: preferBelow, - ); - if (indicatorSize != null) { - offset = Offset(offset.dx, offset.dy - indicatorSize.height + 1); - positionChild( - _ChildType.indicator, - Offset( - target.dx - indicatorSize.width / 2, - offset.dy + overlaySize.height - 1, - ), - ); - } - positionChild(_ChildType.overlay, offset); - } - case TooltipType.right: - Size? indicatorSize; - if (hasChild(_ChildType.indicator)) { - indicatorSize = layoutChild( - _ChildType.indicator, - BoxConstraints.loose(size), - ); - } + @override + void updateRenderObject(BuildContext context, _RenderToolTip renderObject) { + renderObject + ..onTap = onTap + ..target = target + ..verticalOffset = verticalOffset + ..horizontalOffset = horizontalOffset + ..preferBelow = preferBelow; + } +} - if (hasChild(_ChildType.overlay)) { - final overlaySize = layoutChild( - _ChildType.overlay, - BoxConstraints.loose(size), - ); - Offset offset = positionDependentBox( - type: type, - size: size, - childSize: overlaySize, - target: target, - verticalOffset: verticalOffset, - horizontalOffset: horizontalOffset, - preferBelow: preferBelow, - ); - if (indicatorSize != null) { - offset = Offset(offset.dx + indicatorSize.height - 1, offset.dy); - positionChild( - _ChildType.indicator, - Offset( - offset.dx - indicatorSize.width + 1, - target.dy - indicatorSize.height / 2, - ), - ); - } - positionChild(_ChildType.overlay, offset); - } +class _RenderToolTip extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { + _RenderToolTip({ + VoidCallback? onTap, + required TooltipType type, + required Offset target, + required double verticalOffset, + required double horizontalOffset, + required bool preferBelow, + }) : _type = type, + _target = target, + _verticalOffset = verticalOffset, + _horizontalOffset = horizontalOffset, + _preferBelow = preferBelow, + _hitTestSelf = onTap != null { + if (onTap != null) { + _tapGestureRecognizer = TapGestureRecognizer()..onTap = onTap; + } + } + + TapGestureRecognizer? _tapGestureRecognizer; + + set onTap(VoidCallback? value) { + _tapGestureRecognizer?.onTap = value; + } + + @override + void dispose() { + _tapGestureRecognizer + ?..onTap = null + ..dispose(); + _tapGestureRecognizer = null; + super.dispose(); + } + + final bool _hitTestSelf; + @override + bool hitTestSelf(Offset position) => _hitTestSelf; + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerDownEvent) { + _tapGestureRecognizer?.addPointer(event); + } + } + + final TooltipType _type; + + Offset _target; + Offset get target => _target; + set target(Offset value) { + if (_target == value) return; + _target = value; + markNeedsPaint(); + } + + double _verticalOffset; + double get verticalOffset => _verticalOffset; + set verticalOffset(double value) { + if (_verticalOffset == value) return; + _verticalOffset = value; + markNeedsPaint(); + } + + double _horizontalOffset; + double get horizontalOffset => _horizontalOffset; + set horizontalOffset(double value) { + if (_horizontalOffset == value) return; + _horizontalOffset = value; + markNeedsPaint(); + } + + bool _preferBelow; + bool get preferBelow => _preferBelow; + set preferBelow(bool value) { + if (_preferBelow == value) return; + _preferBelow = value; + markNeedsPaint(); + } + + @override + void setupParentData(RenderBox child) { + if (child.parentData is! MultiChildLayoutParentData) { + child.parentData = MultiChildLayoutParentData(); } } @override - bool shouldRelayout(_CustomMultiTooltipPositionDelegate oldDelegate) { - return target != oldDelegate.target || - verticalOffset != oldDelegate.verticalOffset || - preferBelow != oldDelegate.preferBelow; + void performLayout() { + size = constraints.constrain(constraints.biggest); + + final c = BoxConstraints.loose(size); + RenderBox indicator = firstChild!..layout(c, parentUsesSize: true); + RenderBox overlay = lastChild!..layout(c, parentUsesSize: true); + + final indicatorSize = indicator.size; + final overlaySize = overlay.size; + + final indicatorParentData = + indicator.parentData as MultiChildLayoutParentData; + final overlayParentData = overlay.parentData as MultiChildLayoutParentData; + + switch (_type) { + case TooltipType.top: + Offset offset = _positionDependentBox( + type: _type, + size: size, + childSize: overlaySize, + target: target, + verticalOffset: verticalOffset, + horizontalOffset: horizontalOffset, + preferBelow: preferBelow, + ); + offset = Offset(offset.dx, offset.dy - indicatorSize.height + 1); + overlayParentData.offset = offset; + indicatorParentData.offset = Offset( + target.dx - indicatorSize.width / 2, + offset.dy + overlaySize.height - 1, + ); + case TooltipType.right: + Offset offset = _positionDependentBox( + type: _type, + size: size, + childSize: overlaySize, + target: target, + verticalOffset: verticalOffset, + horizontalOffset: horizontalOffset, + preferBelow: preferBelow, + ); + offset = Offset(offset.dx + indicatorSize.height - 1, offset.dy); + overlayParentData.offset = offset; + Offset( + offset.dx - indicatorSize.width + 1, + target.dy - indicatorSize.height / 2, + ); + } } + + @override + void paint(PaintingContext context, Offset offset) { + RenderBox? child = firstChild; + while (child != null) { + final childParentData = child.parentData as MultiChildLayoutParentData; + context.paintChild(child, childParentData.offset + offset); + child = childParentData.nextSibling; + } + } + + @override + bool get isRepaintBoundary => true; } class Triangle extends LeafRenderObjectWidget { @@ -354,7 +444,7 @@ class RenderTriangle extends RenderBox { bool get isRepaintBoundary => true; } -Offset positionDependentBox({ +Offset _positionDependentBox({ required TooltipType type, required Size size, required Size childSize,