mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: use custom follower
This commit is contained in:
parent
d79a7cb194
commit
14c1959d63
@ -0,0 +1,84 @@
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class PopoverCompositedTransformFollower extends CompositedTransformFollower {
|
||||
const PopoverCompositedTransformFollower({
|
||||
super.key,
|
||||
required super.link,
|
||||
super.showWhenUnlinked = true,
|
||||
super.offset = Offset.zero,
|
||||
super.targetAnchor = Alignment.topLeft,
|
||||
super.followerAnchor = Alignment.topLeft,
|
||||
super.child,
|
||||
});
|
||||
|
||||
@override
|
||||
PopoverRenderFollowerLayer createRenderObject(BuildContext context) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
return PopoverRenderFollowerLayer(
|
||||
screenSize: screenSize,
|
||||
link: link,
|
||||
showWhenUnlinked: showWhenUnlinked,
|
||||
offset: offset,
|
||||
leaderAnchor: targetAnchor,
|
||||
followerAnchor: followerAnchor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context, PopoverRenderFollowerLayer renderObject) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
renderObject
|
||||
..screenSize = screenSize
|
||||
..link = link
|
||||
..showWhenUnlinked = showWhenUnlinked
|
||||
..offset = offset
|
||||
..leaderAnchor = targetAnchor
|
||||
..followerAnchor = followerAnchor;
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverRenderFollowerLayer extends RenderFollowerLayer {
|
||||
Size screenSize;
|
||||
|
||||
PopoverRenderFollowerLayer({
|
||||
required super.link,
|
||||
super.showWhenUnlinked = true,
|
||||
super.offset = Offset.zero,
|
||||
super.leaderAnchor = Alignment.topLeft,
|
||||
super.followerAnchor = Alignment.topLeft,
|
||||
super.child,
|
||||
required this.screenSize,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
super.paint(context, offset);
|
||||
|
||||
final global = localToGlobal(offset);
|
||||
|
||||
if (link.leader == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (link.leader!.offset.dx + link.leaderSize!.width + size.width >
|
||||
screenSize.width) {
|
||||
debugPrint("over flow");
|
||||
}
|
||||
debugPrint(
|
||||
"right: ${link.leader!.offset.dx + link.leaderSize!.width + size.width}, screen with: ${screenSize.width}");
|
||||
// debugPrint(
|
||||
// "offset: $offset, global: $global, link: ${link.leader?.offset}, link size: ${link.leaderSize}");
|
||||
// debugPrint("follower size: ${this.size}, screen size: ${this.screenSize}");
|
||||
}
|
||||
}
|
||||
|
||||
class EdgeFollowerLayer extends FollowerLayer {
|
||||
EdgeFollowerLayer({
|
||||
required super.link,
|
||||
super.showWhenUnlinked = true,
|
||||
super.unlinkedOffset = Offset.zero,
|
||||
super.linkedOffset = Offset.zero,
|
||||
});
|
||||
}
|
339
frontend/app_flowy/packages/appflowy_popover/lib/layout.dart
Normal file
339
frontend/app_flowy/packages/appflowy_popover/lib/layout.dart
Normal file
@ -0,0 +1,339 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import './popover.dart';
|
||||
|
||||
class PopoverLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
PopoverLink link;
|
||||
PopoverDirection direction;
|
||||
|
||||
PopoverLayoutDelegate({
|
||||
required this.link,
|
||||
required this.direction,
|
||||
});
|
||||
|
||||
@override
|
||||
bool shouldRelayout(PopoverLayoutDelegate oldDelegate) {
|
||||
if (direction != oldDelegate.direction) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (link != oldDelegate.link) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (link.leaderOffset != oldDelegate.link.leaderOffset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (link.leaderSize != oldDelegate.link.leaderSize) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||
return constraints.loosen();
|
||||
// assert(link.leaderSize != null);
|
||||
// // if (link.leaderSize == null) {
|
||||
// // return constraints.loosen();
|
||||
// // }
|
||||
// final anchorRect = Rect.fromLTWH(
|
||||
// link.leaderOffset!.dx,
|
||||
// link.leaderOffset!.dy,
|
||||
// link.leaderSize!.width,
|
||||
// link.leaderSize!.height,
|
||||
// );
|
||||
// BoxConstraints childConstraints;
|
||||
// switch (direction) {
|
||||
// case PopoverDirection.topLeft:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.topRight:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomLeft:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomRight:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.center:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth,
|
||||
// constraints.maxHeight,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.topWithLeftAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.left,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.topWithCenterAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.topWithRightAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.right,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.rightWithTopAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// constraints.maxHeight - anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.rightWithCenterAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// constraints.maxHeight,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.rightWithBottomAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomWithLeftAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomWithCenterAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomWithRightAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.right,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.leftWithTopAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// constraints.maxHeight - anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.leftWithCenterAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// constraints.maxHeight,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.leftWithBottomAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.custom:
|
||||
// childConstraints = constraints.loosen();
|
||||
// break;
|
||||
// default:
|
||||
// throw UnimplementedError();
|
||||
// }
|
||||
// return childConstraints;
|
||||
}
|
||||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
if (link.leaderSize == null) {
|
||||
return Offset.zero;
|
||||
}
|
||||
final anchorRect = Rect.fromLTWH(
|
||||
link.leaderOffset!.dx,
|
||||
link.leaderOffset!.dy,
|
||||
link.leaderSize!.width,
|
||||
link.leaderSize!.height,
|
||||
);
|
||||
Offset position;
|
||||
switch (direction) {
|
||||
case PopoverDirection.topLeft:
|
||||
position = Offset(
|
||||
anchorRect.left - childSize.width,
|
||||
anchorRect.top - childSize.height,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.topRight:
|
||||
position = Offset(
|
||||
anchorRect.right,
|
||||
anchorRect.top - childSize.height,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.bottomLeft:
|
||||
position = Offset(
|
||||
anchorRect.left - childSize.width,
|
||||
anchorRect.bottom,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.bottomRight:
|
||||
position = Offset(
|
||||
anchorRect.right,
|
||||
anchorRect.bottom,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.center:
|
||||
position = anchorRect.center;
|
||||
break;
|
||||
case PopoverDirection.topWithLeftAligned:
|
||||
position = Offset(
|
||||
anchorRect.left,
|
||||
anchorRect.top - childSize.height,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.topWithCenterAligned:
|
||||
position = Offset(
|
||||
anchorRect.left + anchorRect.width / 2.0 - childSize.width / 2.0,
|
||||
anchorRect.top - childSize.height,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.topWithRightAligned:
|
||||
position = Offset(
|
||||
anchorRect.right - childSize.width,
|
||||
anchorRect.top - childSize.height,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.rightWithTopAligned:
|
||||
position = Offset(anchorRect.right, anchorRect.top);
|
||||
break;
|
||||
case PopoverDirection.rightWithCenterAligned:
|
||||
position = Offset(
|
||||
anchorRect.right,
|
||||
anchorRect.top + anchorRect.height / 2.0 - childSize.height / 2.0,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.rightWithBottomAligned:
|
||||
position = Offset(
|
||||
anchorRect.right,
|
||||
anchorRect.bottom - childSize.height,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.bottomWithLeftAligned:
|
||||
position = Offset(
|
||||
anchorRect.left,
|
||||
anchorRect.bottom,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.bottomWithCenterAligned:
|
||||
position = Offset(
|
||||
anchorRect.left + anchorRect.width / 2.0 - childSize.width / 2.0,
|
||||
anchorRect.bottom,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.bottomWithRightAligned:
|
||||
position = Offset(
|
||||
anchorRect.right - childSize.width,
|
||||
anchorRect.bottom,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.leftWithTopAligned:
|
||||
position = Offset(
|
||||
anchorRect.left - childSize.width,
|
||||
anchorRect.top,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.leftWithCenterAligned:
|
||||
position = Offset(
|
||||
anchorRect.left - childSize.width,
|
||||
anchorRect.top + anchorRect.height / 2.0 - childSize.height / 2.0,
|
||||
);
|
||||
break;
|
||||
case PopoverDirection.leftWithBottomAligned:
|
||||
position = Offset(
|
||||
anchorRect.left - childSize.width,
|
||||
anchorRect.bottom - childSize.height,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
return Offset(
|
||||
math.max(0.0, math.min(size.width - childSize.width, position.dx)),
|
||||
math.max(0.0, math.min(size.height - childSize.height, position.dy)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverTarget extends SingleChildRenderObjectWidget {
|
||||
final PopoverLink link;
|
||||
const PopoverTarget({
|
||||
super.key,
|
||||
super.child,
|
||||
required this.link,
|
||||
});
|
||||
|
||||
@override
|
||||
PopoverTargetRenderBox createRenderObject(BuildContext context) {
|
||||
return PopoverTargetRenderBox(
|
||||
link: link,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context, PopoverTargetRenderBox renderObject) {
|
||||
renderObject.link = link;
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverTargetRenderBox extends RenderProxyBox {
|
||||
PopoverLink link;
|
||||
PopoverTargetRenderBox({required this.link, RenderBox? child}) : super(child);
|
||||
|
||||
@override
|
||||
bool get alwaysNeedsCompositing => true;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
super.performLayout();
|
||||
link.leaderSize = size;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
link.leaderOffset = localToGlobal(Offset.zero);
|
||||
super.paint(context, offset);
|
||||
}
|
||||
|
||||
@override
|
||||
void detach() {
|
||||
link.leaderOffset = null;
|
||||
link.leaderSize = null;
|
||||
super.detach();
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<PopoverLink>('link', link));
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverLink {
|
||||
Offset? leaderOffset;
|
||||
Size? leaderSize;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import 'package:appflowy_popover/layout.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import './follower.dart';
|
||||
|
||||
class PopoverMutex {
|
||||
PopoverState? state;
|
||||
@ -23,6 +25,31 @@ class PopoverTriggerActionFlags {
|
||||
static int hover = 0x02;
|
||||
}
|
||||
|
||||
enum PopoverDirection {
|
||||
// Corner aligned with a corner of the SourceWidget
|
||||
topLeft,
|
||||
topRight,
|
||||
bottomLeft,
|
||||
bottomRight,
|
||||
center,
|
||||
|
||||
// Edge aligned with a edge of the SourceWidget
|
||||
topWithLeftAligned,
|
||||
topWithCenterAligned,
|
||||
topWithRightAligned,
|
||||
rightWithTopAligned,
|
||||
rightWithCenterAligned,
|
||||
rightWithBottomAligned,
|
||||
bottomWithLeftAligned,
|
||||
bottomWithCenterAligned,
|
||||
bottomWithRightAligned,
|
||||
leftWithTopAligned,
|
||||
leftWithCenterAligned,
|
||||
leftWithBottomAligned,
|
||||
|
||||
custom,
|
||||
}
|
||||
|
||||
class Popover extends StatefulWidget {
|
||||
final Widget child;
|
||||
final PopoverController? controller;
|
||||
@ -33,6 +60,7 @@ class Popover extends StatefulWidget {
|
||||
final Widget Function(BuildContext context) popupBuilder;
|
||||
final int triggerActions;
|
||||
final PopoverMutex? mutex;
|
||||
final PopoverDirection direction;
|
||||
final void Function()? onClose;
|
||||
|
||||
const Popover({
|
||||
@ -45,6 +73,7 @@ class Popover extends StatefulWidget {
|
||||
this.targetAnchor = Alignment.topLeft,
|
||||
this.followerAnchor = Alignment.topLeft,
|
||||
this.triggerActions = 0,
|
||||
this.direction = PopoverDirection.rightWithTopAligned,
|
||||
this.mutex,
|
||||
this.onClose,
|
||||
}) : super(key: key);
|
||||
@ -54,7 +83,7 @@ class Popover extends StatefulWidget {
|
||||
}
|
||||
|
||||
class PopoverState extends State<Popover> {
|
||||
final LayerLink layerLink = LayerLink();
|
||||
final PopoverLink popoverLink = PopoverLink();
|
||||
OverlayEntry? _overlayEntry;
|
||||
bool hasMask = true;
|
||||
|
||||
@ -95,14 +124,15 @@ class PopoverState extends State<Popover> {
|
||||
));
|
||||
}
|
||||
|
||||
children.add(CompositedTransformFollower(
|
||||
link: layerLink,
|
||||
showWhenUnlinked: false,
|
||||
offset: widget.offset ?? Offset.zero,
|
||||
targetAnchor: widget.targetAnchor,
|
||||
followerAnchor: widget.followerAnchor,
|
||||
child: widget.popupBuilder(context),
|
||||
));
|
||||
children.add(
|
||||
CustomSingleChildLayout(
|
||||
delegate: PopoverLayoutDelegate(
|
||||
direction: widget.direction,
|
||||
link: popoverLink,
|
||||
),
|
||||
child: widget.popupBuilder(context),
|
||||
),
|
||||
);
|
||||
|
||||
return Stack(children: children);
|
||||
});
|
||||
@ -150,8 +180,8 @@ class PopoverState extends State<Popover> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CompositedTransformTarget(
|
||||
link: layerLink,
|
||||
return PopoverTarget(
|
||||
link: popoverLink,
|
||||
child: MouseRegion(
|
||||
onEnter: _handleTargetPointerEnter,
|
||||
child: Listener(
|
||||
|
Loading…
x
Reference in New Issue
Block a user