123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import 'dart:async';
- import 'package:appflowy_popover/src/layout.dart';
- import 'package:flutter/material.dart';
- import 'mask.dart';
- import 'mutex.dart';
- class PopoverController {
- PopoverState? _state;
- close() {
- _state?.close();
- }
- show() {
- _state?.showOverlay();
- }
- }
- class PopoverTriggerFlags {
- static const int none = 0x00;
- static const int click = 0x01;
- static const int hover = 0x02;
- }
- 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,
- }
- class Popover extends StatefulWidget {
- final PopoverController? controller;
- final Offset? offset;
- final Decoration? maskDecoration;
- /// The function used to build the popover.
- final Widget? Function(BuildContext context) popupBuilder;
- final int triggerActions;
- /// If multiple popovers are exclusive,
- /// pass the same mutex to them.
- final PopoverMutex? mutex;
- /// The direction of the popover
- final PopoverDirection direction;
- final void Function()? onClose;
- final bool asBarrier;
- /// The content area of the popover.
- final Widget child;
- const Popover({
- Key? key,
- required this.child,
- required this.popupBuilder,
- this.controller,
- this.offset,
- this.maskDecoration = const BoxDecoration(
- color: Color.fromARGB(0, 244, 67, 54),
- ),
- this.triggerActions = 0,
- this.direction = PopoverDirection.rightWithTopAligned,
- this.mutex,
- this.onClose,
- this.asBarrier = false,
- }) : super(key: key);
- @override
- State<Popover> createState() => PopoverState();
- }
- class PopoverState extends State<Popover> {
- static final RootOverlayEntry _rootEntry = RootOverlayEntry();
- final PopoverLink popoverLink = PopoverLink();
- Timer? _debounceEnterRegionAction;
- @override
- void initState() {
- widget.controller?._state = this;
- super.initState();
- }
- void showOverlay() {
- close();
- if (widget.mutex != null) {
- widget.mutex?.state = this;
- }
- final shouldAddMask = _rootEntry.isEmpty;
- final newEntry = OverlayEntry(builder: (context) {
- final children = <Widget>[];
- if (shouldAddMask) {
- children.add(
- PopoverMask(
- decoration: widget.maskDecoration,
- onTap: () => _removeRootOverlay(),
- onExit: () => _removeRootOverlay(),
- ),
- );
- }
- children.add(
- PopoverContainer(
- direction: widget.direction,
- popoverLink: popoverLink,
- offset: widget.offset ?? Offset.zero,
- popupBuilder: widget.popupBuilder,
- onClose: () => close(),
- onCloseAll: () => _removeRootOverlay(),
- ),
- );
- return Stack(children: children);
- });
- _rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
- }
- void close() {
- if (_rootEntry.contains(this)) {
- _rootEntry.removeEntry(this);
- widget.onClose?.call();
- }
- }
- void _removeRootOverlay() {
- _rootEntry.popEntry();
- if (widget.mutex?.state == this) {
- widget.mutex?.removeState();
- }
- }
- @override
- void deactivate() {
- close();
- super.deactivate();
- }
- @override
- Widget build(BuildContext context) {
- return PopoverTarget(
- link: popoverLink,
- child: _buildChild(context),
- );
- }
- Widget _buildChild(BuildContext context) {
- if (widget.triggerActions == 0) {
- return widget.child;
- }
- return MouseRegion(
- onEnter: (event) {
- _debounceEnterRegionAction =
- Timer(const Duration(milliseconds: 200), () {
- if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
- showOverlay();
- }
- });
- },
- onExit: (event) {
- _debounceEnterRegionAction?.cancel();
- _debounceEnterRegionAction = null;
- },
- child: Listener(
- child: widget.child,
- onPointerDown: (PointerDownEvent event) {
- if (widget.triggerActions & PopoverTriggerFlags.click != 0) {
- showOverlay();
- }
- },
- ),
- );
- }
- }
- class PopoverContainer extends StatefulWidget {
- final Widget? Function(BuildContext context) popupBuilder;
- final PopoverDirection direction;
- final PopoverLink popoverLink;
- final Offset offset;
- final void Function() onClose;
- final void Function() onCloseAll;
- const PopoverContainer({
- Key? key,
- required this.popupBuilder,
- required this.direction,
- required this.popoverLink,
- required this.offset,
- required this.onClose,
- required this.onCloseAll,
- }) : 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!;
- }
- }
- 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),
- );
- }
- close() => widget.onClose();
- closeAll() => widget.onCloseAll();
- }
|