From 4fb760e44c12d739bd72c1ab5c575c0e7b6c5448 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Fri, 26 Aug 2022 13:47:49 +0800 Subject: [PATCH] feat: add popover overlay --- .../widgets/header/field_editor.dart | 18 +- .../lib/src/flowy_overlay/flowy_popover.dart | 56 +++- .../flowy_overlay/flowy_popover_layout.dart | 251 ++++++++++++++++++ 3 files changed, 303 insertions(+), 22 deletions(-) create mode 100644 frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover_layout.dart diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart index 655975d2f7..5289fe6d89 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart @@ -114,16 +114,14 @@ class FieldEditorPopOver extends StatelessWidget { @override Widget build(BuildContext context) { return FlowyPopover( - child: Container( - constraints: BoxConstraints.loose(const Size(280, 400)), - width: 280, - height: 400, - child: FieldEditor( - gridId: gridId, - fieldName: fieldName, - typeOptionLoader: typeOptionLoader, - key: key), - )); + builder: (BuildContext context) { + return FieldEditor( + gridId: gridId, + fieldName: fieldName, + typeOptionLoader: typeOptionLoader, + key: key); + }, + ); } static show( diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover.dart index f5bee6b492..4d512fda40 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover.dart @@ -1,21 +1,23 @@ +import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; +import 'package:flowy_infra_ui/style_widget/decoration.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; +import './flowy_popover_layout.dart'; const _overlayContainerPadding = EdgeInsets.all(12); -class FlowyPopover extends StatelessWidget { - final Widget child; +class FlowyPopover extends StatefulWidget { + final Widget Function(BuildContext context) builder; final ShapeBorder? shape; + final EdgeInsets padding; - FlowyPopover({Key? key, required this.child, this.shape}) : super(key: key); - - @override - Widget build(BuildContext context) { - return SimpleDialog( - shape: shape ?? - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - children: [Container(padding: _overlayContainerPadding, child: child)], - ); - } + FlowyPopover({ + Key? key, + required this.builder, + this.shape, + this.padding = _overlayContainerPadding, + }) : super(key: key); static show( BuildContext context, { @@ -24,4 +26,34 @@ class FlowyPopover extends StatelessWidget { showDialog( barrierColor: Colors.transparent, context: context, builder: builder); } + + @override + State createState() => _FlowyPopoverState(); +} + +class _FlowyPopoverState extends State { + final preRenderKey = GlobalKey(); + Size? size; + + @override + Widget build(BuildContext context) { + final theme = + context.watch() ?? AppTheme.fromType(ThemeType.light); + return Material( + type: MaterialType.transparency, + child: CustomSingleChildLayout( + delegate: PopoverLayoutDelegate( + anchorRect: const Rect.fromLTWH(0, 0, 280, 400), + anchorDirection: AnchorDirection.rightWithTopAligned, + overlapBehaviour: OverlapBehaviour.stretch, + ), + child: Container( + padding: widget.padding, + constraints: BoxConstraints.loose(const Size(280, 400)), + decoration: FlowyDecoration.decoration( + theme.surface, theme.shadowColor.withOpacity(0.15)), + key: preRenderKey, + child: widget.builder(context), + ))); + } } diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover_layout.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover_layout.dart new file mode 100644 index 0000000000..0d4bacde52 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover_layout.dart @@ -0,0 +1,251 @@ +import 'dart:math' as math; +import 'package:flutter/material.dart'; +import 'flowy_overlay.dart'; + +class PopoverLayoutDelegate extends SingleChildLayoutDelegate { + PopoverLayoutDelegate({ + required this.anchorRect, + required this.anchorDirection, + required this.overlapBehaviour, + }); + + final Rect anchorRect; + final AnchorDirection anchorDirection; + final OverlapBehaviour overlapBehaviour; + + @override + bool shouldRelayout(PopoverLayoutDelegate oldDelegate) { + return anchorRect != oldDelegate.anchorRect || + anchorDirection != oldDelegate.anchorDirection || + overlapBehaviour != oldDelegate.overlapBehaviour; + } + + @override + BoxConstraints getConstraintsForChild(BoxConstraints constraints) { + switch (overlapBehaviour) { + case OverlapBehaviour.none: + return constraints.loosen(); + case OverlapBehaviour.stretch: + BoxConstraints childConstraints; + switch (anchorDirection) { + case AnchorDirection.topLeft: + childConstraints = BoxConstraints.loose(Size( + anchorRect.left, + anchorRect.top, + )); + break; + case AnchorDirection.topRight: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth - anchorRect.right, + anchorRect.top, + )); + break; + case AnchorDirection.bottomLeft: + childConstraints = BoxConstraints.loose(Size( + anchorRect.left, + constraints.maxHeight - anchorRect.bottom, + )); + break; + case AnchorDirection.bottomRight: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth - anchorRect.right, + constraints.maxHeight - anchorRect.bottom, + )); + break; + case AnchorDirection.center: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth, + constraints.maxHeight, + )); + break; + case AnchorDirection.topWithLeftAligned: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth - anchorRect.left, + anchorRect.top, + )); + break; + case AnchorDirection.topWithCenterAligned: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth, + anchorRect.top, + )); + break; + case AnchorDirection.topWithRightAligned: + childConstraints = BoxConstraints.loose(Size( + anchorRect.right, + anchorRect.top, + )); + break; + case AnchorDirection.rightWithTopAligned: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth - anchorRect.right, + constraints.maxHeight - anchorRect.top, + )); + break; + case AnchorDirection.rightWithCenterAligned: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth - anchorRect.right, + constraints.maxHeight, + )); + break; + case AnchorDirection.rightWithBottomAligned: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth - anchorRect.right, + anchorRect.bottom, + )); + break; + case AnchorDirection.bottomWithLeftAligned: + childConstraints = BoxConstraints.loose(Size( + anchorRect.left, + constraints.maxHeight - anchorRect.bottom, + )); + break; + case AnchorDirection.bottomWithCenterAligned: + childConstraints = BoxConstraints.loose(Size( + constraints.maxWidth, + constraints.maxHeight - anchorRect.bottom, + )); + break; + case AnchorDirection.bottomWithRightAligned: + childConstraints = BoxConstraints.loose(Size( + anchorRect.right, + constraints.maxHeight - anchorRect.bottom, + )); + break; + case AnchorDirection.leftWithTopAligned: + childConstraints = BoxConstraints.loose(Size( + anchorRect.left, + constraints.maxHeight - anchorRect.top, + )); + break; + case AnchorDirection.leftWithCenterAligned: + childConstraints = BoxConstraints.loose(Size( + anchorRect.left, + constraints.maxHeight, + )); + break; + case AnchorDirection.leftWithBottomAligned: + childConstraints = BoxConstraints.loose(Size( + anchorRect.left, + anchorRect.bottom, + )); + break; + case AnchorDirection.custom: + childConstraints = constraints.loosen(); + break; + default: + throw UnimplementedError(); + } + return childConstraints; + } + } + + @override + Offset getPositionForChild(Size size, Size childSize) { + Offset position; + switch (anchorDirection) { + case AnchorDirection.topLeft: + position = Offset( + anchorRect.left - childSize.width, + anchorRect.top - childSize.height, + ); + break; + case AnchorDirection.topRight: + position = Offset( + anchorRect.right, + anchorRect.top - childSize.height, + ); + break; + case AnchorDirection.bottomLeft: + position = Offset( + anchorRect.left - childSize.width, + anchorRect.bottom, + ); + break; + case AnchorDirection.bottomRight: + position = Offset( + anchorRect.right, + anchorRect.bottom, + ); + break; + case AnchorDirection.center: + position = anchorRect.center; + break; + case AnchorDirection.topWithLeftAligned: + position = Offset( + anchorRect.left, + anchorRect.top - childSize.height, + ); + break; + case AnchorDirection.topWithCenterAligned: + position = Offset( + anchorRect.left + anchorRect.width / 2.0 - childSize.width / 2.0, + anchorRect.top - childSize.height, + ); + break; + case AnchorDirection.topWithRightAligned: + position = Offset( + anchorRect.right - childSize.width, + anchorRect.top - childSize.height, + ); + break; + case AnchorDirection.rightWithTopAligned: + position = Offset(anchorRect.right, anchorRect.top); + break; + case AnchorDirection.rightWithCenterAligned: + position = Offset( + anchorRect.right, + anchorRect.top + anchorRect.height / 2.0 - childSize.height / 2.0, + ); + break; + case AnchorDirection.rightWithBottomAligned: + position = Offset( + anchorRect.right, + anchorRect.bottom - childSize.height, + ); + break; + case AnchorDirection.bottomWithLeftAligned: + position = Offset( + anchorRect.left, + anchorRect.bottom, + ); + break; + case AnchorDirection.bottomWithCenterAligned: + position = Offset( + anchorRect.left + anchorRect.width / 2.0 - childSize.width / 2.0, + anchorRect.bottom, + ); + break; + case AnchorDirection.bottomWithRightAligned: + position = Offset( + anchorRect.right - childSize.width, + anchorRect.bottom, + ); + break; + case AnchorDirection.leftWithTopAligned: + position = Offset( + anchorRect.left - childSize.width, + anchorRect.top, + ); + break; + case AnchorDirection.leftWithCenterAligned: + position = Offset( + anchorRect.left - childSize.width, + anchorRect.top + anchorRect.height / 2.0 - childSize.height / 2.0, + ); + break; + case AnchorDirection.leftWithBottomAligned: + position = Offset( + anchorRect.left - childSize.width, + anchorRect.bottom - childSize.height, + ); + break; + default: + throw UnimplementedError(); + } + return Offset( + math.max(0.0, math.min(size.width - childSize.width, position.dx)), + math.max(0.0, math.min(size.height - childSize.height, position.dy)), + ); + } +}