popover.dart 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import 'package:appflowy_popover/src/layout.dart';
  2. import 'package:flutter/material.dart';
  3. import 'mask.dart';
  4. import 'mutex.dart';
  5. class PopoverController {
  6. PopoverState? _state;
  7. close() {
  8. _state?.close();
  9. }
  10. show() {
  11. _state?.showOverlay();
  12. }
  13. }
  14. class PopoverTriggerFlags {
  15. static const int none = 0x00;
  16. static const int click = 0x01;
  17. static const int hover = 0x02;
  18. }
  19. enum PopoverDirection {
  20. // Corner aligned with a corner of the SourceWidget
  21. topLeft,
  22. topRight,
  23. bottomLeft,
  24. bottomRight,
  25. center,
  26. // Edge aligned with a edge of the SourceWidget
  27. topWithLeftAligned,
  28. topWithCenterAligned,
  29. topWithRightAligned,
  30. rightWithTopAligned,
  31. rightWithCenterAligned,
  32. rightWithBottomAligned,
  33. bottomWithLeftAligned,
  34. bottomWithCenterAligned,
  35. bottomWithRightAligned,
  36. leftWithTopAligned,
  37. leftWithCenterAligned,
  38. leftWithBottomAligned,
  39. custom,
  40. }
  41. class Popover extends StatefulWidget {
  42. final PopoverController? controller;
  43. final Offset? offset;
  44. final Decoration? maskDecoration;
  45. /// The function used to build the popover.
  46. final Widget? Function(BuildContext context) popupBuilder;
  47. final int triggerActions;
  48. /// If multiple popovers are exclusive,
  49. /// pass the same mutex to them.
  50. final PopoverMutex? mutex;
  51. /// The direction of the popover
  52. final PopoverDirection direction;
  53. final void Function()? onClose;
  54. final bool asBarrier;
  55. /// The content area of the popover.
  56. final Widget child;
  57. const Popover({
  58. Key? key,
  59. required this.child,
  60. required this.popupBuilder,
  61. this.controller,
  62. this.offset,
  63. this.maskDecoration = const BoxDecoration(
  64. color: Color.fromARGB(0, 244, 67, 54),
  65. ),
  66. this.triggerActions = 0,
  67. this.direction = PopoverDirection.rightWithTopAligned,
  68. this.mutex,
  69. this.onClose,
  70. this.asBarrier = false,
  71. }) : super(key: key);
  72. @override
  73. State<Popover> createState() => PopoverState();
  74. }
  75. class PopoverState extends State<Popover> {
  76. static final RootOverlayEntry _rootEntry = RootOverlayEntry();
  77. final PopoverLink popoverLink = PopoverLink();
  78. @override
  79. void initState() {
  80. widget.controller?._state = this;
  81. super.initState();
  82. }
  83. void showOverlay() {
  84. close();
  85. if (widget.mutex != null) {
  86. widget.mutex?.state = this;
  87. }
  88. final shouldAddMask = _rootEntry.isEmpty;
  89. final newEntry = OverlayEntry(builder: (context) {
  90. final children = <Widget>[];
  91. if (shouldAddMask) {
  92. children.add(
  93. PopoverMask(
  94. decoration: widget.maskDecoration,
  95. onTap: () => _removeRootOverlay(),
  96. onExit: () => _removeRootOverlay(),
  97. ),
  98. );
  99. }
  100. children.add(
  101. PopoverContainer(
  102. direction: widget.direction,
  103. popoverLink: popoverLink,
  104. offset: widget.offset ?? Offset.zero,
  105. popupBuilder: widget.popupBuilder,
  106. onClose: () => close(),
  107. onCloseAll: () => _removeRootOverlay(),
  108. ),
  109. );
  110. return Stack(children: children);
  111. });
  112. _rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
  113. }
  114. void close() {
  115. if (_rootEntry.contains(this)) {
  116. _rootEntry.removeEntry(this);
  117. widget.onClose?.call();
  118. }
  119. }
  120. void _removeRootOverlay() {
  121. _rootEntry.popEntry();
  122. if (widget.mutex?.state == this) {
  123. widget.mutex?.removeState();
  124. }
  125. }
  126. @override
  127. void deactivate() {
  128. close();
  129. super.deactivate();
  130. }
  131. @override
  132. Widget build(BuildContext context) {
  133. return PopoverTarget(
  134. link: popoverLink,
  135. child: _buildChild(context),
  136. );
  137. }
  138. Widget _buildChild(BuildContext context) {
  139. if (widget.triggerActions == 0) {
  140. return widget.child;
  141. }
  142. return MouseRegion(
  143. onEnter: (event) {
  144. if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
  145. showOverlay();
  146. }
  147. },
  148. child: Listener(
  149. child: widget.child,
  150. onPointerDown: (PointerDownEvent event) {
  151. if (widget.triggerActions & PopoverTriggerFlags.click != 0) {
  152. showOverlay();
  153. }
  154. },
  155. ),
  156. );
  157. }
  158. }
  159. class PopoverContainer extends StatefulWidget {
  160. final Widget? Function(BuildContext context) popupBuilder;
  161. final PopoverDirection direction;
  162. final PopoverLink popoverLink;
  163. final Offset offset;
  164. final void Function() onClose;
  165. final void Function() onCloseAll;
  166. const PopoverContainer({
  167. Key? key,
  168. required this.popupBuilder,
  169. required this.direction,
  170. required this.popoverLink,
  171. required this.offset,
  172. required this.onClose,
  173. required this.onCloseAll,
  174. }) : super(key: key);
  175. @override
  176. State<StatefulWidget> createState() => PopoverContainerState();
  177. static PopoverContainerState of(BuildContext context) {
  178. if (context is StatefulElement && context.state is PopoverContainerState) {
  179. return context.state as PopoverContainerState;
  180. }
  181. final PopoverContainerState? result =
  182. context.findAncestorStateOfType<PopoverContainerState>();
  183. return result!;
  184. }
  185. }
  186. class PopoverContainerState extends State<PopoverContainer> {
  187. @override
  188. Widget build(BuildContext context) {
  189. return CustomSingleChildLayout(
  190. delegate: PopoverLayoutDelegate(
  191. direction: widget.direction,
  192. link: widget.popoverLink,
  193. offset: widget.offset,
  194. ),
  195. child: widget.popupBuilder(context),
  196. );
  197. }
  198. void close() => widget.onClose();
  199. void closeAll() => widget.onCloseAll();
  200. }