popover.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import 'package:flutter/gestures.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. class PopoverMutex {
  5. PopoverState? state;
  6. }
  7. class PopoverController {
  8. PopoverState? state;
  9. close() {
  10. state?.close();
  11. }
  12. show() {
  13. state?.showOverlay();
  14. }
  15. }
  16. class PopoverTriggerActionFlags {
  17. static int click = 0x01;
  18. static int hover = 0x02;
  19. }
  20. class Popover extends StatefulWidget {
  21. final Widget child;
  22. final PopoverController? controller;
  23. final Offset? offset;
  24. final Decoration? maskDecoration;
  25. final Alignment targetAnchor;
  26. final Alignment followerAnchor;
  27. final Widget Function(BuildContext context) popupBuilder;
  28. final int triggerActions;
  29. final PopoverMutex? mutex;
  30. final void Function()? onClose;
  31. const Popover({
  32. Key? key,
  33. required this.child,
  34. required this.popupBuilder,
  35. this.controller,
  36. this.offset,
  37. this.maskDecoration,
  38. this.targetAnchor = Alignment.topLeft,
  39. this.followerAnchor = Alignment.topLeft,
  40. this.triggerActions = 0,
  41. this.mutex,
  42. this.onClose,
  43. }) : super(key: key);
  44. @override
  45. State<Popover> createState() => PopoverState();
  46. }
  47. class PopoverState extends State<Popover> {
  48. final LayerLink layerLink = LayerLink();
  49. OverlayEntry? _overlayEntry;
  50. bool hasMask = true;
  51. static PopoverState? _popoverWithMask;
  52. @override
  53. void initState() {
  54. widget.controller?.state = this;
  55. super.initState();
  56. }
  57. showOverlay() {
  58. debugPrint("show overlay");
  59. close();
  60. if (widget.mutex != null) {
  61. if (widget.mutex!.state != null && widget.mutex!.state != this) {
  62. widget.mutex!.state!.close();
  63. }
  64. widget.mutex!.state = this;
  65. }
  66. if (_popoverWithMask == null) {
  67. _popoverWithMask = this;
  68. } else {
  69. hasMask = false;
  70. }
  71. final newEntry = OverlayEntry(builder: (context) {
  72. final children = <Widget>[];
  73. if (hasMask) {
  74. children.add(_PopoverMask(
  75. decoration: widget.maskDecoration,
  76. onTap: () => close(),
  77. onExit: () => close(),
  78. ));
  79. }
  80. children.add(CompositedTransformFollower(
  81. link: layerLink,
  82. showWhenUnlinked: false,
  83. offset: widget.offset ?? Offset.zero,
  84. targetAnchor: widget.targetAnchor,
  85. followerAnchor: widget.followerAnchor,
  86. child: widget.popupBuilder(context),
  87. ));
  88. return Stack(children: children);
  89. });
  90. _overlayEntry = newEntry;
  91. Overlay.of(context)?.insert(newEntry);
  92. }
  93. close() {
  94. if (_overlayEntry != null) {
  95. _overlayEntry!.remove();
  96. _overlayEntry = null;
  97. if (_popoverWithMask == this) {
  98. _popoverWithMask = null;
  99. }
  100. if (widget.onClose != null) {
  101. widget.onClose!();
  102. }
  103. }
  104. if (widget.mutex?.state == this) {
  105. widget.mutex!.state = null;
  106. }
  107. }
  108. @override
  109. void deactivate() {
  110. debugPrint("deactivate");
  111. close();
  112. super.deactivate();
  113. }
  114. _handleTargetPointerDown(PointerDownEvent event) {
  115. if (widget.triggerActions & PopoverTriggerActionFlags.click != 0) {
  116. showOverlay();
  117. }
  118. }
  119. _handleTargetPointerEnter(PointerEnterEvent event) {
  120. if (widget.triggerActions & PopoverTriggerActionFlags.hover != 0) {
  121. showOverlay();
  122. }
  123. }
  124. @override
  125. Widget build(BuildContext context) {
  126. return CompositedTransformTarget(
  127. link: layerLink,
  128. child: MouseRegion(
  129. onEnter: _handleTargetPointerEnter,
  130. child: Listener(
  131. onPointerDown: _handleTargetPointerDown,
  132. child: widget.child,
  133. ),
  134. ),
  135. );
  136. }
  137. }
  138. class _PopoverMask extends StatefulWidget {
  139. final void Function() onTap;
  140. final void Function()? onExit;
  141. final Decoration? decoration;
  142. const _PopoverMask(
  143. {Key? key, required this.onTap, this.onExit, this.decoration})
  144. : super(key: key);
  145. @override
  146. State<StatefulWidget> createState() => _PopoverMaskState();
  147. }
  148. class _PopoverMaskState extends State<_PopoverMask> {
  149. @override
  150. void initState() {
  151. HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
  152. super.initState();
  153. }
  154. bool _handleGlobalKeyEvent(KeyEvent event) {
  155. if (event.logicalKey == LogicalKeyboardKey.escape) {
  156. if (widget.onExit != null) {
  157. widget.onExit!();
  158. }
  159. return true;
  160. }
  161. return false;
  162. }
  163. @override
  164. void deactivate() {
  165. HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
  166. super.deactivate();
  167. }
  168. @override
  169. Widget build(BuildContext context) {
  170. return GestureDetector(
  171. onTap: widget.onTap,
  172. child: Container(
  173. // decoration: widget.decoration,
  174. decoration: widget.decoration ??
  175. const BoxDecoration(
  176. color: Color.fromARGB(0, 244, 67, 54),
  177. ),
  178. ),
  179. );
  180. }
  181. }