popover.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import 'package:appflowy_popover/layout.dart';
  2. import 'package:flutter/gestures.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. class PopoverMutex {
  6. PopoverState? state;
  7. }
  8. class PopoverController {
  9. PopoverState? state;
  10. close() {
  11. state?.close();
  12. }
  13. show() {
  14. state?.showOverlay();
  15. }
  16. }
  17. class PopoverTriggerActionFlags {
  18. static int click = 0x01;
  19. static int hover = 0x02;
  20. }
  21. enum PopoverDirection {
  22. // Corner aligned with a corner of the SourceWidget
  23. topLeft,
  24. topRight,
  25. bottomLeft,
  26. bottomRight,
  27. center,
  28. // Edge aligned with a edge of the SourceWidget
  29. topWithLeftAligned,
  30. topWithCenterAligned,
  31. topWithRightAligned,
  32. rightWithTopAligned,
  33. rightWithCenterAligned,
  34. rightWithBottomAligned,
  35. bottomWithLeftAligned,
  36. bottomWithCenterAligned,
  37. bottomWithRightAligned,
  38. leftWithTopAligned,
  39. leftWithCenterAligned,
  40. leftWithBottomAligned,
  41. custom,
  42. }
  43. class Popover extends StatefulWidget {
  44. final Widget child;
  45. final PopoverController? controller;
  46. final Offset? offset;
  47. final Decoration? maskDecoration;
  48. final Widget Function(BuildContext context) popupBuilder;
  49. final int triggerActions;
  50. final PopoverMutex? mutex;
  51. final PopoverDirection direction;
  52. final void Function()? onClose;
  53. const Popover({
  54. Key? key,
  55. required this.child,
  56. required this.popupBuilder,
  57. this.controller,
  58. this.offset,
  59. this.maskDecoration,
  60. this.triggerActions = 0,
  61. this.direction = PopoverDirection.rightWithTopAligned,
  62. this.mutex,
  63. this.onClose,
  64. }) : super(key: key);
  65. @override
  66. State<Popover> createState() => PopoverState();
  67. }
  68. class PopoverState extends State<Popover> {
  69. final PopoverLink popoverLink = PopoverLink();
  70. OverlayEntry? _overlayEntry;
  71. bool hasMask = true;
  72. static PopoverState? _popoverWithMask;
  73. @override
  74. void initState() {
  75. widget.controller?.state = this;
  76. super.initState();
  77. }
  78. showOverlay() {
  79. close();
  80. if (widget.mutex != null) {
  81. if (widget.mutex!.state != null && widget.mutex!.state != this) {
  82. widget.mutex!.state!.close();
  83. }
  84. widget.mutex!.state = this;
  85. }
  86. if (_popoverWithMask == null) {
  87. _popoverWithMask = this;
  88. } else {
  89. hasMask = false;
  90. }
  91. final newEntry = OverlayEntry(builder: (context) {
  92. final children = <Widget>[];
  93. if (hasMask) {
  94. children.add(_PopoverMask(
  95. decoration: widget.maskDecoration,
  96. onTap: () => close(),
  97. onExit: () => close(),
  98. ));
  99. }
  100. children.add(PopoverContainer(
  101. direction: widget.direction,
  102. popoverLink: popoverLink,
  103. offset: widget.offset ?? Offset.zero,
  104. popupBuilder: widget.popupBuilder,
  105. onClose: () => close(),
  106. onCloseAll: () => closeAll(),
  107. ));
  108. return Stack(children: children);
  109. });
  110. _overlayEntry = newEntry;
  111. Overlay.of(context)?.insert(newEntry);
  112. }
  113. close() {
  114. if (_overlayEntry != null) {
  115. _overlayEntry!.remove();
  116. _overlayEntry = null;
  117. if (_popoverWithMask == this) {
  118. _popoverWithMask = null;
  119. }
  120. if (widget.onClose != null) {
  121. widget.onClose!();
  122. }
  123. }
  124. if (widget.mutex?.state == this) {
  125. widget.mutex!.state = null;
  126. }
  127. }
  128. closeAll() {
  129. _popoverWithMask?.close();
  130. }
  131. @override
  132. void deactivate() {
  133. close();
  134. super.deactivate();
  135. }
  136. _handleTargetPointerDown(PointerDownEvent event) {
  137. if (widget.triggerActions & PopoverTriggerActionFlags.click != 0) {
  138. showOverlay();
  139. }
  140. }
  141. _handleTargetPointerEnter(PointerEnterEvent event) {
  142. if (widget.triggerActions & PopoverTriggerActionFlags.hover != 0) {
  143. showOverlay();
  144. }
  145. }
  146. @override
  147. Widget build(BuildContext context) {
  148. return PopoverTarget(
  149. link: popoverLink,
  150. child: MouseRegion(
  151. onEnter: _handleTargetPointerEnter,
  152. child: Listener(
  153. onPointerDown: _handleTargetPointerDown,
  154. child: widget.child,
  155. ),
  156. ),
  157. );
  158. }
  159. }
  160. class _PopoverMask extends StatefulWidget {
  161. final void Function() onTap;
  162. final void Function()? onExit;
  163. final Decoration? decoration;
  164. const _PopoverMask(
  165. {Key? key, required this.onTap, this.onExit, this.decoration})
  166. : super(key: key);
  167. @override
  168. State<StatefulWidget> createState() => _PopoverMaskState();
  169. }
  170. class _PopoverMaskState extends State<_PopoverMask> {
  171. @override
  172. void initState() {
  173. HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
  174. super.initState();
  175. }
  176. bool _handleGlobalKeyEvent(KeyEvent event) {
  177. if (event.logicalKey == LogicalKeyboardKey.escape) {
  178. if (widget.onExit != null) {
  179. widget.onExit!();
  180. }
  181. return true;
  182. }
  183. return false;
  184. }
  185. @override
  186. void deactivate() {
  187. HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
  188. super.deactivate();
  189. }
  190. @override
  191. Widget build(BuildContext context) {
  192. return GestureDetector(
  193. onTap: widget.onTap,
  194. child: Container(
  195. // decoration: widget.decoration,
  196. decoration: widget.decoration ??
  197. const BoxDecoration(
  198. color: Color.fromARGB(0, 244, 67, 54),
  199. ),
  200. ),
  201. );
  202. }
  203. }
  204. class PopoverContainer extends StatefulWidget {
  205. final Widget Function(BuildContext context) popupBuilder;
  206. final PopoverDirection direction;
  207. final PopoverLink popoverLink;
  208. final Offset offset;
  209. final void Function() onClose;
  210. final void Function() onCloseAll;
  211. const PopoverContainer({
  212. Key? key,
  213. required this.popupBuilder,
  214. required this.direction,
  215. required this.popoverLink,
  216. required this.offset,
  217. required this.onClose,
  218. required this.onCloseAll,
  219. }) : super(key: key);
  220. @override
  221. State<StatefulWidget> createState() => PopoverContainerState();
  222. }
  223. class PopoverContainerState extends State<PopoverContainer> {
  224. @override
  225. Widget build(BuildContext context) {
  226. return CustomSingleChildLayout(
  227. delegate: PopoverLayoutDelegate(
  228. direction: widget.direction,
  229. link: widget.popoverLink,
  230. offset: widget.offset,
  231. ),
  232. child: widget.popupBuilder(context),
  233. );
  234. }
  235. close() => widget.onClose();
  236. closeAll() => widget.onCloseAll();
  237. static PopoverContainerState of(BuildContext context) {
  238. if (context is StatefulElement && context.state is PopoverContainerState) {
  239. return context.state as PopoverContainerState;
  240. }
  241. final PopoverContainerState? result =
  242. context.findAncestorStateOfType<PopoverContainerState>();
  243. return result!;
  244. }
  245. }