mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 11:08:03 +08:00
@@ -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<CustomTooltip> {
|
||||
@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<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
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user