popover.dart 5.3 KB

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