253 lines
5.4 KiB
Dart
Raw Normal View History

2022-09-19 18:12:41 +08:00
import 'package:appflowy_popover/src/layout.dart';
2022-08-30 12:51:59 +08:00
import 'package:flutter/gestures.dart';
2022-08-29 14:00:27 +08:00
import 'package:flutter/material.dart';
2022-08-29 13:56:16 +08:00
2022-09-19 18:12:41 +08:00
import 'mask.dart';
import 'mutex.dart';
2022-08-29 15:29:39 +08:00
2022-08-30 12:51:59 +08:00
class PopoverController {
2022-09-19 18:12:41 +08:00
PopoverState? _state;
2022-08-29 14:00:27 +08:00
close() {
2022-09-19 18:12:41 +08:00
_state?.close();
2022-08-29 14:00:27 +08:00
}
show() {
2022-09-19 18:12:41 +08:00
_state?.showOverlay();
2022-08-29 14:00:27 +08:00
}
}
2022-09-19 18:12:41 +08:00
class PopoverTriggerFlags {
2022-08-30 18:26:44 +08:00
static int click = 0x01;
static int hover = 0x02;
}
2022-08-31 15:36:47 +08:00
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,
}
2022-08-30 12:51:59 +08:00
class Popover extends StatefulWidget {
final PopoverController? controller;
2022-09-01 15:28:16 +08:00
2022-08-29 14:00:27 +08:00
final Offset? offset;
2022-09-01 15:28:16 +08:00
2022-08-29 14:00:27 +08:00
final Decoration? maskDecoration;
2022-09-01 15:28:16 +08:00
/// The function used to build the popover.
2022-09-15 16:05:55 +08:00
final Widget? Function(BuildContext context) popupBuilder;
2022-09-01 15:28:16 +08:00
2022-08-30 18:26:44 +08:00
final int triggerActions;
2022-09-01 15:28:16 +08:00
/// If multiple popovers are exclusive,
/// pass the same mutex to them.
2022-08-30 18:26:44 +08:00
final PopoverMutex? mutex;
2022-09-01 15:28:16 +08:00
/// The direction of the popover
2022-08-31 15:36:47 +08:00
final PopoverDirection direction;
2022-09-01 15:28:16 +08:00
2022-08-30 15:29:37 +08:00
final void Function()? onClose;
2022-08-29 14:00:27 +08:00
2022-09-01 15:28:16 +08:00
/// The content area of the popover.
final Widget child;
2022-08-30 12:51:59 +08:00
const Popover({
2022-08-29 14:00:27 +08:00
Key? key,
required this.child,
required this.popupBuilder,
this.controller,
this.offset,
this.maskDecoration,
2022-08-30 18:26:44 +08:00
this.triggerActions = 0,
2022-08-31 15:36:47 +08:00
this.direction = PopoverDirection.rightWithTopAligned,
2022-08-30 18:26:44 +08:00
this.mutex,
2022-08-30 15:29:37 +08:00
this.onClose,
2022-08-29 14:00:27 +08:00
}) : super(key: key);
@override
2022-08-30 12:51:59 +08:00
State<Popover> createState() => PopoverState();
2022-08-29 14:00:27 +08:00
}
2022-08-30 12:51:59 +08:00
class PopoverState extends State<Popover> {
2022-08-31 15:36:47 +08:00
final PopoverLink popoverLink = PopoverLink();
2022-08-29 14:00:27 +08:00
OverlayEntry? _overlayEntry;
bool hasMask = true;
2022-08-30 12:51:59 +08:00
static PopoverState? _popoverWithMask;
2022-08-29 16:07:54 +08:00
2022-08-29 14:00:27 +08:00
@override
void initState() {
2022-09-19 18:12:41 +08:00
widget.controller?._state = this;
2022-08-29 14:00:27 +08:00
super.initState();
}
2022-09-19 18:12:41 +08:00
void showOverlay() {
2022-08-29 16:07:54 +08:00
close();
2022-08-29 14:00:27 +08:00
2022-08-30 18:26:44 +08:00
if (widget.mutex != null) {
widget.mutex?.state = this;
2022-08-30 18:26:44 +08:00
}
2022-08-29 16:07:54 +08:00
if (_popoverWithMask == null) {
_popoverWithMask = this;
} else {
2022-09-19 18:12:41 +08:00
// hasMask = false;
2022-08-29 14:00:27 +08:00
}
final newEntry = OverlayEntry(builder: (context) {
final children = <Widget>[];
if (hasMask) {
2022-09-19 18:12:41 +08:00
children.add(PopoverMask(
2022-08-29 14:00:27 +08:00
decoration: widget.maskDecoration,
2022-08-29 16:07:54 +08:00
onTap: () => close(),
onExit: () => close(),
2022-08-29 14:00:27 +08:00
));
}
2022-08-31 18:06:41 +08:00
children.add(PopoverContainer(
direction: widget.direction,
popoverLink: popoverLink,
offset: widget.offset ?? Offset.zero,
popupBuilder: widget.popupBuilder,
onClose: () => close(),
2022-08-31 19:15:20 +08:00
onCloseAll: () => closeAll(),
2022-08-31 18:06:41 +08:00
));
2022-08-29 14:00:27 +08:00
return Stack(children: children);
});
_overlayEntry = newEntry;
2022-09-19 18:12:41 +08:00
final OverlayState s = Overlay.of(context)!;
2022-08-29 14:00:27 +08:00
Overlay.of(context)?.insert(newEntry);
}
2022-09-19 18:12:41 +08:00
void close() {
2022-08-29 16:07:54 +08:00
if (_overlayEntry != null) {
_overlayEntry!.remove();
_overlayEntry = null;
2022-09-19 18:12:41 +08:00
2022-08-29 16:07:54 +08:00
if (_popoverWithMask == this) {
_popoverWithMask = null;
}
2022-09-19 18:12:41 +08:00
widget.onClose?.call();
2022-08-29 14:00:27 +08:00
}
2022-08-30 18:26:44 +08:00
if (widget.mutex?.state == this) {
2022-09-19 18:12:41 +08:00
widget.mutex?.removeState();
2022-08-30 18:26:44 +08:00
}
2022-08-29 14:00:27 +08:00
}
2022-09-19 18:12:41 +08:00
void closeAll() {
2022-08-31 19:15:20 +08:00
_popoverWithMask?.close();
}
2022-08-29 14:00:27 +08:00
@override
2022-08-29 16:07:54 +08:00
void deactivate() {
close();
super.deactivate();
2022-08-29 14:00:27 +08:00
}
@override
Widget build(BuildContext context) {
2022-08-31 15:36:47 +08:00
return PopoverTarget(
link: popoverLink,
2022-09-19 18:12:41 +08:00
child: _buildChild(context),
2022-08-30 18:26:44 +08:00
);
2022-08-29 14:00:27 +08:00
}
2022-09-19 18:12:41 +08:00
Widget _buildChild(BuildContext context) {
if (widget.triggerActions == 0) {
return widget.child;
2022-08-29 14:00:27 +08:00
}
2022-09-19 18:12:41 +08:00
return MouseRegion(
onEnter: (PointerEnterEvent event) {
if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
showOverlay();
}
},
child: Listener(
child: widget.child,
onPointerDown: (PointerDownEvent event) {
if (widget.triggerActions & PopoverTriggerFlags.click != 0) {
showOverlay();
}
},
2022-08-29 14:00:27 +08:00
),
);
}
2022-08-29 13:56:16 +08:00
}
2022-08-31 18:06:41 +08:00
class PopoverContainer extends StatefulWidget {
2022-09-15 16:05:55 +08:00
final Widget? Function(BuildContext context) popupBuilder;
2022-08-31 18:06:41 +08:00
final PopoverDirection direction;
final PopoverLink popoverLink;
final Offset offset;
final void Function() onClose;
2022-08-31 19:15:20 +08:00
final void Function() onCloseAll;
2022-08-31 18:06:41 +08:00
const PopoverContainer({
Key? key,
required this.popupBuilder,
required this.direction,
required this.popoverLink,
required this.offset,
required this.onClose,
2022-08-31 19:15:20 +08:00
required this.onCloseAll,
2022-08-31 18:06:41 +08:00
}) : super(key: key);
@override
State<StatefulWidget> createState() => PopoverContainerState();
static PopoverContainerState of(BuildContext context) {
if (context is StatefulElement && context.state is PopoverContainerState) {
return context.state as PopoverContainerState;
}
final PopoverContainerState? result =
context.findAncestorStateOfType<PopoverContainerState>();
return result!;
}
2022-08-31 18:06:41 +08:00
}
class PopoverContainerState extends State<PopoverContainer> {
@override
Widget build(BuildContext context) {
return CustomSingleChildLayout(
delegate: PopoverLayoutDelegate(
direction: widget.direction,
link: widget.popoverLink,
offset: widget.offset,
),
child: widget.popupBuilder(context),
);
}
2022-08-31 19:15:20 +08:00
close() => widget.onClose();
closeAll() => widget.onCloseAll();
2022-08-31 18:06:41 +08:00
}