Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-01-25 17:30:25 +08:00
parent 7ace981f24
commit 361eb4c614
5 changed files with 94 additions and 232 deletions

View File

@@ -1,6 +1,3 @@
import 'dart:math' as math;
import 'dart:ui' show clampDouble;
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'
@@ -10,18 +7,14 @@ import 'package:flutter/rendering.dart'
MultiChildLayoutParentData;
import 'package:flutter/widgets.dart';
enum TooltipType { top, right }
class CustomTooltip extends StatefulWidget {
const CustomTooltip({
super.key,
this.type = TooltipType.top,
required this.overlayWidget,
required this.child,
required this.indicator,
});
final TooltipType type;
final Widget child;
final ValueGetter<Widget> overlayWidget;
final ValueGetter<Widget> indicator;
@@ -51,27 +44,20 @@ class _CustomTooltipState extends State<CustomTooltip> {
longPressRecognizer.addPointer(event);
}
Widget _buildCustomTooltipOverlay(BuildContext context) {
final OverlayState overlayState = Overlay.of(
context,
debugRequiredFor: widget,
Widget _buildCustomTooltipOverlay(
BuildContext context,
OverlayChildLayoutInfo layoutInfo,
) {
final target = MatrixUtils.transformPoint(
layoutInfo.childPaintTransform,
layoutInfo.childSize.topCenter(Offset.zero),
);
final RenderBox box = this.context.findRenderObject()! as RenderBox;
final Offset target = box.localToGlobal(
box.size.center(Offset.zero),
ancestor: overlayState.context.findRenderObject(),
);
final _CustomTooltipOverlay overlayChild = _CustomTooltipOverlay(
verticalOffset: box.size.height / 2,
horizontalOffset: box.size.width / 2,
type: widget.type,
target: target,
onDismiss: _scheduleDismissTooltip,
overlayWidget: widget.overlayWidget,
indicator: widget.indicator,
);
return SelectionContainer.maybeOf(context) == null
? overlayChild
: SelectionContainer.disabled(child: overlayChild);
@@ -105,7 +91,7 @@ class _CustomTooltipState extends State<CustomTooltip> {
child: widget.child,
);
}
return OverlayPortal(
return OverlayPortal.overlayChildLayoutBuilder(
controller: _overlayController,
overlayChildBuilder: _buildCustomTooltipOverlay,
child: result,
@@ -117,18 +103,12 @@ enum _ChildType { overlay, indicator }
class _CustomTooltipOverlay extends StatelessWidget {
const _CustomTooltipOverlay({
required this.verticalOffset,
required this.horizontalOffset,
required this.type,
required this.target,
required this.onDismiss,
required this.overlayWidget,
required this.indicator,
});
final double verticalOffset;
final double horizontalOffset;
final TooltipType type;
final Offset target;
final VoidCallback onDismiss;
final ValueGetter<Widget> overlayWidget;
@@ -137,10 +117,7 @@ class _CustomTooltipOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _ToolTip(
type: type,
target: target,
verticalOffset: verticalOffset,
horizontalOffset: horizontalOffset,
preferBelow: false,
onTap: PlatformUtils.isMobile ? onDismiss : null,
children: [
@@ -161,28 +138,19 @@ class _ToolTip extends MultiChildRenderObjectWidget {
const _ToolTip({
super.children,
this.onTap,
required this.type,
required this.target,
required this.verticalOffset,
required this.horizontalOffset,
required this.preferBelow,
});
final VoidCallback? onTap;
final TooltipType type;
final Offset target;
final double verticalOffset;
final double horizontalOffset;
final bool preferBelow;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderToolTip(
onTap: onTap,
type: type,
target: target,
verticalOffset: verticalOffset,
horizontalOffset: horizontalOffset,
preferBelow: preferBelow,
);
}
@@ -192,8 +160,6 @@ class _ToolTip extends MultiChildRenderObjectWidget {
renderObject
..onTap = onTap
..target = target
..verticalOffset = verticalOffset
..horizontalOffset = horizontalOffset
..preferBelow = preferBelow;
}
}
@@ -204,15 +170,9 @@ class _RenderToolTip extends RenderBox
RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
_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,
}) : _target = target,
_preferBelow = preferBelow,
_hitTestSelf = onTap != null {
if (onTap != null) {
@@ -246,8 +206,6 @@ class _RenderToolTip extends RenderBox
}
}
final TooltipType _type;
Offset _target;
Offset get target => _target;
set target(Offset value) {
@@ -256,22 +214,6 @@ class _RenderToolTip extends RenderBox
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) {
@@ -302,40 +244,18 @@ class _RenderToolTip extends RenderBox
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,
);
}
Offset offset = positionDependentBox(
size: size,
childSize: overlaySize,
target: target,
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,
);
}
@override
@@ -357,19 +277,16 @@ class Triangle extends LeafRenderObjectWidget {
super.key,
required this.color,
required this.size,
this.type = .top,
});
final Color color;
final Size size;
final TooltipType type;
@override
RenderObject createRenderObject(BuildContext context) {
return RenderTriangle(
color: color,
preferredSize: size,
type: type,
);
}
@@ -388,10 +305,8 @@ class RenderTriangle extends RenderBox {
RenderTriangle({
required Color color,
required Size preferredSize,
required TooltipType type,
}) : _color = color,
_preferredSize = preferredSize,
_type = type;
_preferredSize = preferredSize;
Color _color;
Color get color => _color;
@@ -408,8 +323,6 @@ class RenderTriangle extends RenderBox {
markNeedsLayout();
}
final TooltipType _type;
@override
void performLayout() {
size = constraints.constrain(_preferredSize);
@@ -422,21 +335,11 @@ class RenderTriangle extends RenderBox {
..color = color
..style = PaintingStyle.fill;
Path path;
switch (_type) {
case TooltipType.top:
path = Path()
..moveTo(0, 0)
..lineTo(size.width, 0)
..lineTo(size.width / 2, size.height)
..close();
case TooltipType.right:
path = Path()
..moveTo(0, size.height / 2)
..lineTo(size.width, 0)
..lineTo(size.width, size.height)
..close();
}
final path = Path()
..moveTo(0, 0)
..lineTo(size.width, 0)
..lineTo(size.width / 2, size.height)
..close();
context.canvas.drawPath(path, paint);
}
@@ -444,50 +347,3 @@ class RenderTriangle extends RenderBox {
@override
bool get isRepaintBoundary => true;
}
Offset _positionDependentBox({
required TooltipType type,
required Size size,
required Size childSize,
required Offset target,
required bool preferBelow,
double verticalOffset = 0.0,
double horizontalOffset = 0.0,
double margin = 10.0,
}) {
switch (type) {
case TooltipType.top:
// VERTICAL DIRECTION
final bool fitsBelow =
target.dy + verticalOffset + childSize.height <= size.height - margin;
final bool fitsAbove =
target.dy - verticalOffset - childSize.height >= margin;
final bool tooltipBelow = fitsAbove == fitsBelow
? preferBelow
: fitsBelow;
final double y;
if (tooltipBelow) {
y = math.min(target.dy + verticalOffset, size.height - margin);
} else {
y = math.max(target.dy - verticalOffset - childSize.height, margin);
} // HORIZONTAL DIRECTION
final double flexibleSpace = size.width - childSize.width;
final double x = flexibleSpace <= 2 * margin
// If there's not enough horizontal space for margin + child, center the
// child.
? flexibleSpace / 2.0
: clampDouble(
target.dx - childSize.width / 2,
margin,
flexibleSpace - margin,
);
return Offset(x, y);
case TooltipType.right:
final double dy = math.max(margin, target.dy - childSize.height / 2);
final double dx = math.min(
target.dx + horizontalOffset,
size.width - childSize.width - margin,
);
return Offset(dx, dy);
}
}