feat: enable remove one layer when click the empty space

This commit is contained in:
appflowy 2022-09-20 10:53:47 +08:00
parent d1737d35fe
commit 723b34a736
3 changed files with 120 additions and 60 deletions

View File

@ -341,6 +341,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
List<Widget> children = [ List<Widget> children = [
Popover( Popover(
mutex: _popoverMutex, mutex: _popoverMutex,
asBarrier: true,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0), offset: const Offset(20, 0),
popupBuilder: (BuildContext context) { popupBuilder: (BuildContext context) {
@ -357,6 +358,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
), ),
Popover( Popover(
mutex: _popoverMutex, mutex: _popoverMutex,
asBarrier: true,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0), offset: const Offset(20, 0),
popupBuilder: (BuildContext context) { popupBuilder: (BuildContext context) {

View File

@ -1,24 +1,86 @@
import 'dart:collection';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class _PopoverMask extends StatefulWidget { typedef EntryMap = LinkedHashMap<PopoverState, OverlayEntryContext>;
class RootOverlayEntry {
final EntryMap _entries = EntryMap();
RootOverlayEntry();
void addEntry(
BuildContext context,
PopoverState newState,
OverlayEntry entry,
bool asBarrier,
) {
_entries[newState] = OverlayEntryContext(entry, newState, asBarrier);
Overlay.of(context)?.insert(entry);
}
bool contains(PopoverState oldState) {
return _entries.containsKey(oldState);
}
void removeEntry(PopoverState oldState) {
if (_entries.isEmpty) return;
final removedEntry = _entries.remove(oldState);
removedEntry?.overlayEntry.remove();
}
bool get isEmpty => _entries.isEmpty;
bool get isNotEmpty => _entries.isNotEmpty;
bool hasEntry() {
return _entries.isNotEmpty;
}
PopoverState? popEntry() {
if (_entries.isEmpty) return null;
final lastEntry = _entries.values.last;
_entries.remove(lastEntry.popoverState);
lastEntry.overlayEntry.remove();
lastEntry.popoverState.widget.onClose?.call();
if (lastEntry.asBarrier) {
return lastEntry.popoverState;
} else {
return popEntry();
}
}
}
class OverlayEntryContext {
final bool asBarrier;
final PopoverState popoverState;
final OverlayEntry overlayEntry;
OverlayEntryContext(
this.overlayEntry,
this.popoverState,
this.asBarrier,
);
}
class PopoverMask extends StatefulWidget {
final void Function() onTap; final void Function() onTap;
final void Function()? onExit; final void Function()? onExit;
final Decoration? decoration; final Decoration? decoration;
const _PopoverMask( const PopoverMask(
{Key? key, {Key? key, required this.onTap, this.onExit, this.decoration})
required this.onTap,
this.onExit,
this.decoration =
const BoxDecoration(color: Color.fromARGB(0, 244, 67, 54))})
: super(key: key); : super(key: key);
@override @override
State<StatefulWidget> createState() => _PopoverMaskState(); State<StatefulWidget> createState() => _PopoverMaskState();
} }
class _PopoverMaskState extends State<_PopoverMask> { class _PopoverMaskState extends State<PopoverMask> {
@override @override
void initState() { void initState() {
HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent); HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
@ -30,11 +92,11 @@ class _PopoverMaskState extends State<_PopoverMask> {
if (widget.onExit != null) { if (widget.onExit != null) {
widget.onExit!(); widget.onExit!();
} }
return true; return true;
} } else {
return false; return false;
} }
}
@override @override
void deactivate() { void deactivate() {

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:appflowy_popover/src/layout.dart'; import 'package:appflowy_popover/src/layout.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'mask.dart'; import 'mask.dart';
import 'mutex.dart'; import 'mutex.dart';
@ -68,6 +67,8 @@ class Popover extends StatefulWidget {
final void Function()? onClose; final void Function()? onClose;
final bool asBarrier;
/// The content area of the popover. /// The content area of the popover.
final Widget child; final Widget child;
@ -77,11 +78,14 @@ class Popover extends StatefulWidget {
required this.popupBuilder, required this.popupBuilder,
this.controller, this.controller,
this.offset, this.offset,
this.maskDecoration, this.maskDecoration = const BoxDecoration(
color: Color.fromARGB(0, 244, 67, 54),
),
this.triggerActions = 0, this.triggerActions = 0,
this.direction = PopoverDirection.rightWithTopAligned, this.direction = PopoverDirection.rightWithTopAligned,
this.mutex, this.mutex,
this.onClose, this.onClose,
this.asBarrier = false,
}) : super(key: key); }) : super(key: key);
@override @override
@ -89,11 +93,9 @@ class Popover extends StatefulWidget {
} }
class PopoverState extends State<Popover> { class PopoverState extends State<Popover> {
static final RootOverlayEntry _rootEntry = RootOverlayEntry();
final PopoverLink popoverLink = PopoverLink(); final PopoverLink popoverLink = PopoverLink();
OverlayEntry? _overlayEntry; Timer? _debounceEnterRegionAction;
bool hasMask = true;
static PopoverState? _popoverWithMask;
@override @override
void initState() { void initState() {
@ -107,64 +109,51 @@ class PopoverState extends State<Popover> {
if (widget.mutex != null) { if (widget.mutex != null) {
widget.mutex?.state = this; widget.mutex?.state = this;
} }
final shouldAddMask = _rootEntry.isEmpty;
if (_popoverWithMask == null) {
_popoverWithMask = this;
} else {
// hasMask = false;
}
final newEntry = OverlayEntry(builder: (context) { final newEntry = OverlayEntry(builder: (context) {
final children = <Widget>[]; final children = <Widget>[];
if (shouldAddMask) {
if (hasMask) { children.add(
children.add(PopoverMask( PopoverMask(
decoration: widget.maskDecoration, decoration: widget.maskDecoration,
onTap: () => close(), onTap: () => _removeRootOverlay(),
onExit: () => close(), onExit: () => _removeRootOverlay(),
)); ),
);
} }
children.add(PopoverContainer( children.add(
PopoverContainer(
direction: widget.direction, direction: widget.direction,
popoverLink: popoverLink, popoverLink: popoverLink,
offset: widget.offset ?? Offset.zero, offset: widget.offset ?? Offset.zero,
popupBuilder: widget.popupBuilder, popupBuilder: widget.popupBuilder,
onClose: () => close(), onClose: () => close(),
onCloseAll: () => closeAll(), onCloseAll: () => _removeRootOverlay(),
)); ),
);
return Stack(children: children); return Stack(children: children);
}); });
_overlayEntry = newEntry; _rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
final OverlayState s = Overlay.of(context)!;
Overlay.of(context)?.insert(newEntry);
} }
void close() { void close() {
if (_overlayEntry != null) { if (_rootEntry.contains(this)) {
_overlayEntry!.remove(); _rootEntry.removeEntry(this);
_overlayEntry = null;
if (_popoverWithMask == this) {
_popoverWithMask = null;
}
widget.onClose?.call(); widget.onClose?.call();
} }
}
void _removeRootOverlay() {
_rootEntry.popEntry();
if (widget.mutex?.state == this) { if (widget.mutex?.state == this) {
widget.mutex?.removeState(); widget.mutex?.removeState();
} }
} }
void closeAll() {
_popoverWithMask?.close();
}
@override @override
void deactivate() { void deactivate() {
close(); close();
@ -185,10 +174,17 @@ class PopoverState extends State<Popover> {
} }
return MouseRegion( return MouseRegion(
onEnter: (PointerEnterEvent event) { onEnter: (event) {
_debounceEnterRegionAction =
Timer(const Duration(milliseconds: 200), () {
if (widget.triggerActions & PopoverTriggerFlags.hover != 0) { if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
showOverlay(); showOverlay();
} }
});
},
onExit: (event) {
_debounceEnterRegionAction?.cancel();
_debounceEnterRegionAction = null;
}, },
child: Listener( child: Listener(
child: widget.child, child: widget.child,