opt emoji tooltip

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-01-19 18:10:41 +08:00
parent 036dbcaf21
commit 5a61dbe30c

View File

@@ -3,6 +3,11 @@ import 'dart:ui' show clampDouble;
import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'
show
ContainerRenderObjectMixin,
RenderBoxContainerDefaultsMixin,
MultiChildLayoutParentData;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
enum TooltipType { top, right } enum TooltipType { top, right }
@@ -74,8 +79,10 @@ class _CustomTooltipState extends State<CustomTooltip> {
@protected @protected
@override @override
void dispose() { void dispose() {
_longPressRecognizer?.onLongPressCancel = null; _longPressRecognizer
_longPressRecognizer?.dispose(); ?..onLongPressCancel = null
..dispose();
_longPressRecognizer = null;
super.dispose(); super.dispose();
} }
@@ -128,38 +135,31 @@ class _CustomTooltipOverlay extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget child = CustomMultiChildLayout( return _ToolTip(
delegate: _CustomMultiTooltipPositionDelegate(
type: type, type: type,
target: target, target: target,
verticalOffset: verticalOffset, verticalOffset: verticalOffset,
horizontalOffset: horizontalOffset, horizontalOffset: horizontalOffset,
preferBelow: false, preferBelow: false,
), onTap: PlatformUtils.isMobile ? onDismiss : null,
children: [ children: [
LayoutId(
id: _ChildType.overlay,
child: overlayWidget(),
),
LayoutId( LayoutId(
id: _ChildType.indicator, id: _ChildType.indicator,
child: 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 { class _ToolTip extends MultiChildRenderObjectWidget {
_CustomMultiTooltipPositionDelegate({ const _ToolTip({
super.children,
this.onTap,
required this.type, required this.type,
required this.target, required this.target,
required this.verticalOffset, required this.verticalOffset,
@@ -167,98 +167,188 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
required this.preferBelow, required this.preferBelow,
}); });
final VoidCallback? onTap;
final TooltipType type; final TooltipType type;
final Offset target; final Offset target;
final double verticalOffset; final double verticalOffset;
final double horizontalOffset; final double horizontalOffset;
final bool preferBelow; final bool preferBelow;
@override @override
void performLayout(Size size) { RenderObject createRenderObject(BuildContext context) {
switch (type) { return _RenderToolTip(
case TooltipType.top: onTap: onTap,
Size? indicatorSize;
if (hasChild(_ChildType.indicator)) {
indicatorSize = layoutChild(
_ChildType.indicator,
BoxConstraints.loose(size),
);
}
if (hasChild(_ChildType.overlay)) {
final overlaySize = layoutChild(
_ChildType.overlay,
BoxConstraints.loose(size),
);
Offset offset = positionDependentBox(
type: type, type: type,
size: size,
childSize: overlaySize,
target: target, target: target,
verticalOffset: verticalOffset, verticalOffset: verticalOffset,
horizontalOffset: horizontalOffset, horizontalOffset: horizontalOffset,
preferBelow: preferBelow, 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),
);
} }
if (hasChild(_ChildType.overlay)) { @override
final overlaySize = layoutChild( void updateRenderObject(BuildContext context, _RenderToolTip renderObject) {
_ChildType.overlay, renderObject
BoxConstraints.loose(size), ..onTap = onTap
); ..target = target
Offset offset = positionDependentBox( ..verticalOffset = verticalOffset
type: type, ..horizontalOffset = horizontalOffset
size: size, ..preferBelow = preferBelow;
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<RenderBox, MultiChildLayoutParentData>,
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,
_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<HitTestTarget> 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 @override
bool shouldRelayout(_CustomMultiTooltipPositionDelegate oldDelegate) { void performLayout() {
return target != oldDelegate.target || size = constraints.constrain(constraints.biggest);
verticalOffset != oldDelegate.verticalOffset ||
preferBelow != oldDelegate.preferBelow; 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 { class Triangle extends LeafRenderObjectWidget {
@@ -354,7 +444,7 @@ class RenderTriangle extends RenderBox {
bool get isRepaintBoundary => true; bool get isRepaintBoundary => true;
} }
Offset positionDependentBox({ Offset _positionDependentBox({
required TooltipType type, required TooltipType type,
required Size size, required Size size,
required Size childSize, required Size childSize,