From 80cb0f9fa104a5b5ce729c25ca7212dfc9362240 Mon Sep 17 00:00:00 2001 From: Jaylen Bian Date: Sun, 1 Aug 2021 10:53:58 +0800 Subject: [PATCH 1/6] [infra_ui][overlar] Implement overlay anchor - widget part --- .../example/lib/overlay/overlay_screen.dart | 6 +- .../lib/src/flowy_overlay/flowy_overlay.dart | 92 ++++++++++++++++++- .../overlay_layout_delegate.dart | 67 +++++++------- 3 files changed, 122 insertions(+), 43 deletions(-) diff --git a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart index d2f12ecec8..b6a62f64cd 100644 --- a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart +++ b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart @@ -45,11 +45,11 @@ class OverlayScreen extends StatelessWidget { ElevatedButton( onPressed: () { FlowyOverlay.of(context).insert( - const FlutterLogo( + widget: const FlutterLogo( size: 200, ), - 'overlay_flutter_logo', - null, + identifier: 'overlay_flutter_logo', + delegate: null, ); }, child: const Text('Show Overlay'), diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart index 237b6bea2f..cc3c16a7d1 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart @@ -1,4 +1,5 @@ import 'package:dartz/dartz.dart' show Tuple3; +import 'package:flowy_infra_ui/src/flowy_overlay/overlay_layout_delegate.dart'; import 'package:flutter/material.dart'; /// Specifies how overlay are anchored to the SourceWidget @@ -110,14 +111,55 @@ class FlowyOverlay extends StatefulWidget { class FlowyOverlayState extends State { List> _overlayList = []; - void insert({ + /// Insert a overlay widget which frame is set by the widget, not the component. + /// Be sure to specify the offset and size using the `Postition` widget. + void insertCustom({ required Widget widget, required String identifier, FlowyOverlayDelegate? delegate, }) { - setState(() { - _overlayList.add(Tuple3(widget, identifier, delegate)); - }); + _showOverlay( + widget: widget, + identifier: identifier, + shouldAnchor: false, + delegate: delegate, + ); + } + + void insertWithRect({ + required Widget widget, + required String identifier, + required Offset anchorPosition, + required Size anchorSize, + AnchorDirection? anchorDirection, + FlowyOverlayDelegate? delegate, + }) { + _showOverlay( + widget: widget, + identifier: identifier, + shouldAnchor: true, + delegate: delegate, + anchorPosition: anchorPosition, + anchorSize: anchorSize, + anchorDirection: anchorDirection, + ); + } + + void insertWithAnchor({ + required Widget widget, + required String identifier, + required BuildContext anchorContext, + AnchorDirection? anchorDirection, + FlowyOverlayDelegate? delegate, + }) { + _showOverlay( + widget: widget, + identifier: identifier, + shouldAnchor: true, + delegate: delegate, + anchorContext: anchorContext, + anchorDirection: anchorDirection, + ); } void remove(String identifier) { @@ -142,6 +184,48 @@ class FlowyOverlayState extends State { } } + void _showOverlay({ + required Widget widget, + required String identifier, + required bool shouldAnchor, + Offset? anchorPosition, + Size? anchorSize, + AnchorDirection? anchorDirection, + BuildContext? anchorContext, + FlowyOverlayDelegate? delegate, + }) { + Widget overlay = widget; + + if (shouldAnchor) { + assert( + anchorPosition != null || anchorContext != null, + 'Must provide `anchorPosition` or `anchorContext` to locating overlay.', + ); + var targetAnchorPosition = anchorPosition; + if (anchorContext != null) { + RenderObject renderObject = anchorContext.findRenderObject()!; + assert( + renderObject is RenderBox, + 'Unexpect non-RenderBox render object caught.', + ); + final localOffset = (renderObject as RenderBox).localToGlobal(Offset.zero); + targetAnchorPosition ??= localOffset; + } + final anchorRect = targetAnchorPosition! & (anchorSize ?? Size.zero); + overlay = CustomSingleChildLayout( + delegate: OverlayLayoutDelegate( + anchorRect: anchorRect, + anchorDirection: anchorDirection ?? AnchorDirection.rightWithTopAligned, + ), + child: widget, + ); + } + + setState(() { + _overlayList.add(Tuple3(overlay, identifier, delegate)); + }); + } + @override Widget build(BuildContext context) { final overlays = _overlayList.map((ele) => ele.value1); diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart index bfec790c73..34f63d7060 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart @@ -1,43 +1,38 @@ -// import 'dart:math' as math; -// import 'dart:ui'; +import 'dart:math' as math; +import 'dart:ui'; -// import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; -// import 'flowy_overlay.dart'; +import 'flowy_overlay.dart'; -// class OverlayLayoutDelegate extends SingleChildLayoutDelegate { -// OverlayLayoutDelegate({ -// required this.route, -// required this.padding, -// required this.anchorPosition, -// required this.anchorDirection, -// }); +class OverlayLayoutDelegate extends SingleChildLayoutDelegate { + OverlayLayoutDelegate({ + required this.anchorRect, + required this.anchorDirection, + }); -// final OverlayPannelRoute route; -// final EdgeInsets padding; -// final AnchorDirection anchorDirection; -// final Offset anchorPosition; + final Rect anchorRect; + final AnchorDirection anchorDirection; -// @override -// bool shouldRelayout(OverlayLayoutDelegate oldDelegate) { -// return anchorPosition != oldDelegate.anchorPosition || anchorDirection != oldDelegate.anchorDirection; -// } + @override + bool shouldRelayout(OverlayLayoutDelegate oldDelegate) { + return anchorRect != oldDelegate.anchorRect || anchorDirection != oldDelegate.anchorDirection; + } -// @override -// Offset getPositionForChild(Size size, Size childSize) { -// // TODO: junlin - calculate child position -// return Offset.zero; -// } + @override + Size getSize(BoxConstraints constraints) { + return super.getSize(constraints); + } -// @override -// BoxConstraints getConstraintsForChild(BoxConstraints constraints) { -// double maxHeight = math.max(0.0, constraints.maxHeight - padding.top - padding.bottom); -// double width = constraints.maxWidth; -// return BoxConstraints( -// minHeight: 0.0, -// maxHeight: maxHeight, -// minWidth: width, -// maxWidth: width, -// ); -// } -// } + @override + BoxConstraints getConstraintsForChild(BoxConstraints constraints) { + // TODO: junlin - calculate child constaints + return super.getConstraintsForChild(constraints); + } + + @override + Offset getPositionForChild(Size size, Size childSize) { + // TODO: junlin - calculate child position + return Offset(size.width / 2, size.height / 2); + } +} From a6a350a8313c6596ef7886a62780c3e31fc6ce70 Mon Sep 17 00:00:00 2001 From: Jaylen Bian Date: Sun, 1 Aug 2021 11:11:00 +0800 Subject: [PATCH 2/6] [infra_ui][overlar] Update overlay example with anchored and positioned mode --- .../example/lib/overlay/overlay_screen.dart | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart index b6a62f64cd..2b83902246 100644 --- a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart +++ b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart @@ -44,7 +44,7 @@ class OverlayScreen extends StatelessWidget { ), ElevatedButton( onPressed: () { - FlowyOverlay.of(context).insert( + FlowyOverlay.of(context).insertCustom( widget: const FlutterLogo( size: 200, ), @@ -54,6 +54,38 @@ class OverlayScreen extends StatelessWidget { }, child: const Text('Show Overlay'), ), + const SizedBox(height: 12.0), + ElevatedButton( + onPressed: () { + FlowyOverlay.of(context).insertWithAnchor( + widget: const FlutterLogo( + size: 200, + textColor: Colors.orange, + ), + identifier: 'overlay_flutter_logo', + delegate: null, + anchorContext: context, + ); + }, + child: const Text('Show Anchored Overlay'), + ), + const SizedBox(height: 12.0), + ElevatedButton( + onPressed: () { + final windowSize = MediaQuery.of(context).size; + FlowyOverlay.of(context).insertWithRect( + widget: const FlutterLogo( + size: 200, + textColor: Colors.orange, + ), + identifier: 'overlay_flutter_logo', + delegate: null, + anchorPosition: Offset(0, windowSize.height - 200), + anchorSize: Size.zero, + ); + }, + child: const Text('Show Positioned Overlay'), + ), ], )); } From 58e4a4d5f175f90b54f72590daf1ce15bbeaac1a Mon Sep 17 00:00:00 2001 From: Jaylen Bian Date: Sun, 1 Aug 2021 18:03:24 +0800 Subject: [PATCH 3/6] [infra_ui][overlar] Implement overlay insertion interface --- .../example/lib/overlay/overlay_screen.dart | 30 ++++++++++--------- .../lib/src/flowy_overlay/flowy_overlay.dart | 15 +++++++--- .../overlay_layout_delegate.dart | 10 ++----- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart index 2b83902246..5c5738dc11 100644 --- a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart +++ b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart @@ -55,20 +55,22 @@ class OverlayScreen extends StatelessWidget { child: const Text('Show Overlay'), ), const SizedBox(height: 12.0), - ElevatedButton( - onPressed: () { - FlowyOverlay.of(context).insertWithAnchor( - widget: const FlutterLogo( - size: 200, - textColor: Colors.orange, - ), - identifier: 'overlay_flutter_logo', - delegate: null, - anchorContext: context, - ); - }, - child: const Text('Show Anchored Overlay'), - ), + Builder(builder: (buttonContext) { + return ElevatedButton( + onPressed: () { + FlowyOverlay.of(context).insertWithAnchor( + widget: const FlutterLogo( + size: 200, + textColor: Colors.orange, + ), + identifier: 'overlay_flutter_logo', + delegate: null, + anchorContext: buttonContext, + ); + }, + child: const Text('Show Anchored Overlay'), + ); + }), const SizedBox(height: 12.0), ElevatedButton( onPressed: () { diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart index cc3c16a7d1..269eb8aa1f 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart @@ -201,17 +201,24 @@ class FlowyOverlayState extends State { anchorPosition != null || anchorContext != null, 'Must provide `anchorPosition` or `anchorContext` to locating overlay.', ); - var targetAnchorPosition = anchorPosition; + Offset targetAnchorPosition = anchorPosition ?? Offset.zero; + Size targetAnchorSize = anchorSize ?? Size.zero; if (anchorContext != null) { RenderObject renderObject = anchorContext.findRenderObject()!; assert( renderObject is RenderBox, 'Unexpect non-RenderBox render object caught.', ); - final localOffset = (renderObject as RenderBox).localToGlobal(Offset.zero); - targetAnchorPosition ??= localOffset; + final renderBox = renderObject as RenderBox; + targetAnchorPosition = renderBox.localToGlobal(Offset.zero); + targetAnchorSize = renderBox.size; } - final anchorRect = targetAnchorPosition! & (anchorSize ?? Size.zero); + final anchorRect = Rect.fromLTWH( + targetAnchorPosition.dx, + targetAnchorPosition.dy, + targetAnchorSize.width, + targetAnchorSize.height, + ); overlay = CustomSingleChildLayout( delegate: OverlayLayoutDelegate( anchorRect: anchorRect, diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart index 34f63d7060..eaea7c17f8 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart @@ -19,20 +19,14 @@ class OverlayLayoutDelegate extends SingleChildLayoutDelegate { return anchorRect != oldDelegate.anchorRect || anchorDirection != oldDelegate.anchorDirection; } - @override - Size getSize(BoxConstraints constraints) { - return super.getSize(constraints); - } - @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { - // TODO: junlin - calculate child constaints - return super.getConstraintsForChild(constraints); + return constraints.loosen(); } @override Offset getPositionForChild(Size size, Size childSize) { // TODO: junlin - calculate child position - return Offset(size.width / 2, size.height / 2); + return Offset(anchorRect.width / 2, anchorRect.height / 2); } } From ada5ab737e9c353a106dc87a5bd1060c78fd34d7 Mon Sep 17 00:00:00 2001 From: Jaylen Bian Date: Sun, 1 Aug 2021 18:53:49 +0800 Subject: [PATCH 4/6] [infra_ui][overlar] Add overlap behaviour interface and implement part of anchor type --- .../example/lib/overlay/overlay_screen.dart | 32 ++++++++++++++----- .../lib/src/flowy_overlay/flowy_overlay.dart | 18 +++++++++-- .../overlay_layout_delegate.dart | 31 +++++++++++++++--- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart index 5c5738dc11..ffb702816d 100644 --- a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart +++ b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart @@ -59,13 +59,21 @@ class OverlayScreen extends StatelessWidget { return ElevatedButton( onPressed: () { FlowyOverlay.of(context).insertWithAnchor( - widget: const FlutterLogo( - size: 200, - textColor: Colors.orange, + widget: SizedBox( + width: 200, + height: 100, + child: Card( + color: Colors.grey[200], + child: GestureDetector( + onTapDown: (_) => print('Hello Flutter'), + child: const Center(child: FlutterLogo(size: 100)), + ), + ), ), - identifier: 'overlay_flutter_logo', + identifier: 'overlay_card', delegate: null, anchorContext: buttonContext, + anchorDirection: AnchorDirection.topLeft, ); }, child: const Text('Show Anchored Overlay'), @@ -76,14 +84,22 @@ class OverlayScreen extends StatelessWidget { onPressed: () { final windowSize = MediaQuery.of(context).size; FlowyOverlay.of(context).insertWithRect( - widget: const FlutterLogo( - size: 200, - textColor: Colors.orange, + widget: SizedBox( + width: 200, + height: 100, + child: Card( + color: Colors.orange[200], + child: GestureDetector( + onTapDown: (_) => print('Hello Flutter'), + child: const Center(child: FlutterLogo(size: 100)), + ), + ), ), - identifier: 'overlay_flutter_logo', + identifier: 'overlay_card', delegate: null, anchorPosition: Offset(0, windowSize.height - 200), anchorSize: Size.zero, + anchorDirection: AnchorDirection.topLeft, ); }, child: const Text('Show Positioned Overlay'), diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart index 269eb8aa1f..a12988f0a5 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart @@ -28,7 +28,18 @@ enum AnchorDirection { custom, } -/// The behavior of overlay when user tapping system back button +// TODO: junlin - support overlap behaviour +/// [Unsupported] The behaviour of overlay when overlap with anchor widget +enum OverlapBehaviour { + /// Maintain overlay size, which may cover the anchor widget. + none, + + /// Resize overlay to avoid overlaping the anchor widget. + stretch, +} + +// TODO: junlin - support route pop handler +/// [Unsupported] The behavior of overlay when user tapping system back button enum OnBackBehavior { /// Won't handle the back action none, @@ -192,6 +203,7 @@ class FlowyOverlayState extends State { Size? anchorSize, AnchorDirection? anchorDirection, BuildContext? anchorContext, + OverlapBehaviour? overlapBehaviour, FlowyOverlayDelegate? delegate, }) { Widget overlay = widget; @@ -222,7 +234,9 @@ class FlowyOverlayState extends State { overlay = CustomSingleChildLayout( delegate: OverlayLayoutDelegate( anchorRect: anchorRect, - anchorDirection: anchorDirection ?? AnchorDirection.rightWithTopAligned, + anchorDirection: + shouldAnchor ? anchorDirection ?? AnchorDirection.rightWithTopAligned : AnchorDirection.custom, + overlapBehaviour: overlapBehaviour ?? OverlapBehaviour.stretch, ), child: widget, ); diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart index eaea7c17f8..b09eb3a28e 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart @@ -9,24 +9,47 @@ class OverlayLayoutDelegate extends SingleChildLayoutDelegate { OverlayLayoutDelegate({ required this.anchorRect, required this.anchorDirection, + required this.overlapBehaviour, }); final Rect anchorRect; final AnchorDirection anchorDirection; + final OverlapBehaviour overlapBehaviour; @override bool shouldRelayout(OverlayLayoutDelegate oldDelegate) { - return anchorRect != oldDelegate.anchorRect || anchorDirection != oldDelegate.anchorDirection; + return anchorRect != oldDelegate.anchorRect || + anchorDirection != oldDelegate.anchorDirection || + overlapBehaviour != oldDelegate.overlapBehaviour; } @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { - return constraints.loosen(); + switch (overlapBehaviour) { + case OverlapBehaviour.none: + return constraints.loosen(); + case OverlapBehaviour.stretch: + // TODO: junlin - resize when overlapBehaviour == .stretch + return constraints.loosen(); + } } @override Offset getPositionForChild(Size size, Size childSize) { - // TODO: junlin - calculate child position - return Offset(anchorRect.width / 2, anchorRect.height / 2); + Offset position; + switch (anchorDirection) { + case AnchorDirection.topLeft: + position = Offset( + anchorRect.left - childSize.width, + anchorRect.top - childSize.height, + ); + break; + default: + throw UnimplementedError(); + } + return Offset( + math.max(0.0, math.min(size.width, position.dx)), + math.max(0.0, math.min(size.height, position.dy)), + ); } } From dffb56d1a93ef9b03217eb13493a04314a0b8f20 Mon Sep 17 00:00:00 2001 From: Jaylen Bian Date: Sun, 1 Aug 2021 19:17:24 +0800 Subject: [PATCH 5/6] [infra_ui][overlar] Implement corner aligned anchor mode and update example project --- .../example/lib/overlay/overlay_screen.dart | 176 ++++++++++-------- .../overlay_layout_delegate.dart | 18 +- 2 files changed, 115 insertions(+), 79 deletions(-) diff --git a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart index ffb702816d..be1779b146 100644 --- a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart +++ b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart @@ -1,5 +1,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import '../home/demo_item.dart'; @@ -19,6 +20,19 @@ class OverlayItem extends DemoItem { } } +class OverlayDemoAnchorDirection extends ChangeNotifier { + OverlayDemoAnchorDirection(this._anchorDirection); + + AnchorDirection _anchorDirection; + + AnchorDirection get anchorDirection => _anchorDirection; + + set anchorDirection(AnchorDirection value) { + _anchorDirection = value; + notifyListeners(); + } +} + class OverlayScreen extends StatelessWidget { const OverlayScreen({Key? key}) : super(key: key); @@ -28,83 +42,99 @@ class OverlayScreen extends StatelessWidget { appBar: AppBar( title: const Text('Overlay Demo'), ), - body: Column( - children: [ - Flexible( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Container( - height: 300.0, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15.0), - color: Colors.grey[200], + body: ChangeNotifierProvider( + create: (context) => OverlayDemoAnchorDirection(AnchorDirection.topLeft), + child: Builder(builder: (context) { + return Column( + children: [ + DropdownButton( + value: context.watch().anchorDirection, + onChanged: (AnchorDirection? newValue) { + if (newValue != null) { + context.read().anchorDirection = newValue; + } + }, + items: AnchorDirection.values.map((AnchorDirection classType) { + return DropdownMenuItem(value: classType, child: Text(classType.toString())); + }).toList(), + ), + Flexible( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Container( + height: 300.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15.0), + color: Colors.grey[200], + ), + ), ), ), - ), - ), - ElevatedButton( - onPressed: () { - FlowyOverlay.of(context).insertCustom( - widget: const FlutterLogo( - size: 200, - ), - identifier: 'overlay_flutter_logo', - delegate: null, - ); - }, - child: const Text('Show Overlay'), - ), - const SizedBox(height: 12.0), - Builder(builder: (buttonContext) { - return ElevatedButton( - onPressed: () { - FlowyOverlay.of(context).insertWithAnchor( - widget: SizedBox( - width: 200, - height: 100, - child: Card( - color: Colors.grey[200], - child: GestureDetector( - onTapDown: (_) => print('Hello Flutter'), - child: const Center(child: FlutterLogo(size: 100)), + ElevatedButton( + onPressed: () { + FlowyOverlay.of(context).insertCustom( + widget: const FlutterLogo( + size: 200, + ), + identifier: 'overlay_flutter_logo', + delegate: null, + ); + }, + child: const Text('Show Overlay'), + ), + const SizedBox(height: 12.0), + Builder(builder: (buttonContext) { + return ElevatedButton( + onPressed: () { + FlowyOverlay.of(context).insertWithAnchor( + widget: SizedBox( + width: 200, + height: 100, + child: Card( + color: Colors.grey[200], + child: GestureDetector( + onTapDown: (_) => print('Hello Flutter'), + child: const Center(child: FlutterLogo(size: 100)), + ), + ), + ), + identifier: 'overlay_card', + delegate: null, + anchorContext: buttonContext, + anchorDirection: context.read().anchorDirection, + ); + }, + child: const Text('Show Anchored Overlay'), + ); + }), + const SizedBox(height: 12.0), + ElevatedButton( + onPressed: () { + final windowSize = MediaQuery.of(context).size; + FlowyOverlay.of(context).insertWithRect( + widget: SizedBox( + width: 200, + height: 100, + child: Card( + color: Colors.orange[200], + child: GestureDetector( + onTapDown: (_) => print('Hello Flutter'), + child: const Center(child: FlutterLogo(size: 100)), + ), ), ), - ), - identifier: 'overlay_card', - delegate: null, - anchorContext: buttonContext, - anchorDirection: AnchorDirection.topLeft, - ); - }, - child: const Text('Show Anchored Overlay'), - ); - }), - const SizedBox(height: 12.0), - ElevatedButton( - onPressed: () { - final windowSize = MediaQuery.of(context).size; - FlowyOverlay.of(context).insertWithRect( - widget: SizedBox( - width: 200, - height: 100, - child: Card( - color: Colors.orange[200], - child: GestureDetector( - onTapDown: (_) => print('Hello Flutter'), - child: const Center(child: FlutterLogo(size: 100)), - ), - ), - ), - identifier: 'overlay_card', - delegate: null, - anchorPosition: Offset(0, windowSize.height - 200), - anchorSize: Size.zero, - anchorDirection: AnchorDirection.topLeft, - ); - }, - child: const Text('Show Positioned Overlay'), - ), - ], + identifier: 'overlay_card', + delegate: null, + anchorPosition: Offset(0, windowSize.height - 200), + anchorSize: Size.zero, + anchorDirection: context.read().anchorDirection, + ); + }, + child: const Text('Show Positioned Overlay'), + ), + ], + ); + }), )); } } diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart index b09eb3a28e..350a87d4f7 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart @@ -39,17 +39,23 @@ class OverlayLayoutDelegate extends SingleChildLayoutDelegate { Offset position; switch (anchorDirection) { case AnchorDirection.topLeft: - position = Offset( - anchorRect.left - childSize.width, - anchorRect.top - childSize.height, - ); + 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; default: throw UnimplementedError(); } return Offset( - math.max(0.0, math.min(size.width, position.dx)), - math.max(0.0, math.min(size.height, position.dy)), + math.max(0.0, math.min(size.width - childSize.width, position.dx)), + math.max(0.0, math.min(size.height - childSize.height, position.dy)), ); } } From 5c4df3c6f2dadad7b001b2e4c74672747315261a Mon Sep 17 00:00:00 2001 From: Jaylen Bian Date: Sun, 1 Aug 2021 21:34:45 +0800 Subject: [PATCH 6/6] [infra_ui][overlar] Implement edge aligned anchor mode and update example project --- .../example/lib/overlay/overlay_screen.dart | 185 ++++++++++-------- .../overlay_layout_delegate.dart | 89 ++++++++- 2 files changed, 185 insertions(+), 89 deletions(-) diff --git a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart index be1779b146..ae1b955f01 100644 --- a/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart +++ b/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart @@ -43,96 +43,111 @@ class OverlayScreen extends StatelessWidget { title: const Text('Overlay Demo'), ), body: ChangeNotifierProvider( - create: (context) => OverlayDemoAnchorDirection(AnchorDirection.topLeft), - child: Builder(builder: (context) { - return Column( - children: [ - DropdownButton( - value: context.watch().anchorDirection, - onChanged: (AnchorDirection? newValue) { - if (newValue != null) { - context.read().anchorDirection = newValue; - } - }, - items: AnchorDirection.values.map((AnchorDirection classType) { - return DropdownMenuItem(value: classType, child: Text(classType.toString())); - }).toList(), - ), - Flexible( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Container( - height: 300.0, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15.0), - color: Colors.grey[200], - ), - ), - ), - ), - ElevatedButton( - onPressed: () { - FlowyOverlay.of(context).insertCustom( - widget: const FlutterLogo( - size: 200, - ), - identifier: 'overlay_flutter_logo', - delegate: null, - ); - }, - child: const Text('Show Overlay'), - ), - const SizedBox(height: 12.0), - Builder(builder: (buttonContext) { - return ElevatedButton( - onPressed: () { - FlowyOverlay.of(context).insertWithAnchor( - widget: SizedBox( - width: 200, - height: 100, - child: Card( - color: Colors.grey[200], - child: GestureDetector( - onTapDown: (_) => print('Hello Flutter'), - child: const Center(child: FlutterLogo(size: 100)), + create: (context) => OverlayDemoAnchorDirection(AnchorDirection.rightWithTopAligned), + child: Builder(builder: (providerContext) { + return Center( + child: ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: 500), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 48.0), + ElevatedButton( + onPressed: () { + final windowSize = MediaQuery.of(context).size; + FlowyOverlay.of(context).insertCustom( + widget: Positioned( + left: windowSize.width / 2.0 - 100, + top: 200, + child: SizedBox( + width: 200, + height: 100, + child: Card( + color: Colors.green[200], + child: GestureDetector( + // ignore: avoid_print + onTapDown: (_) => print('Hello Flutter'), + child: const Center(child: FlutterLogo(size: 100)), + ), + ), ), ), - ), - identifier: 'overlay_card', - delegate: null, - anchorContext: buttonContext, - anchorDirection: context.read().anchorDirection, - ); - }, - child: const Text('Show Anchored Overlay'), - ); - }), - const SizedBox(height: 12.0), - ElevatedButton( - onPressed: () { - final windowSize = MediaQuery.of(context).size; - FlowyOverlay.of(context).insertWithRect( - widget: SizedBox( - width: 200, + identifier: 'overlay_flutter_logo', + delegate: null, + ); + }, + child: const Text('Show Overlay'), + ), + const SizedBox(height: 24.0), + DropdownButton( + value: providerContext.watch().anchorDirection, + onChanged: (AnchorDirection? newValue) { + if (newValue != null) { + providerContext.read().anchorDirection = newValue; + } + }, + items: AnchorDirection.values.map((AnchorDirection classType) { + return DropdownMenuItem(value: classType, child: Text(classType.toString())); + }).toList(), + ), + const SizedBox(height: 24.0), + Builder(builder: (buttonContext) { + return SizedBox( height: 100, - child: Card( - color: Colors.orange[200], - child: GestureDetector( - onTapDown: (_) => print('Hello Flutter'), - child: const Center(child: FlutterLogo(size: 100)), - ), + child: ElevatedButton( + onPressed: () { + FlowyOverlay.of(context).insertWithAnchor( + widget: SizedBox( + width: 100, + height: 50, + child: Card( + color: Colors.grey[200], + child: GestureDetector( + // ignore: avoid_print + onTapDown: (_) => print('Hello Flutter'), + child: const Center(child: FlutterLogo(size: 50)), + ), + ), + ), + identifier: 'overlay_anchored_card', + delegate: null, + anchorContext: buttonContext, + anchorDirection: providerContext.read().anchorDirection, + ); + }, + child: const Text('Show Anchored Overlay'), ), - ), - identifier: 'overlay_card', - delegate: null, - anchorPosition: Offset(0, windowSize.height - 200), - anchorSize: Size.zero, - anchorDirection: context.read().anchorDirection, - ); - }, - child: const Text('Show Positioned Overlay'), + ); + }), + const SizedBox(height: 24.0), + ElevatedButton( + onPressed: () { + final windowSize = MediaQuery.of(context).size; + FlowyOverlay.of(context).insertWithRect( + widget: SizedBox( + width: 200, + height: 100, + child: Card( + color: Colors.orange[200], + child: GestureDetector( + // ignore: avoid_print + onTapDown: (_) => print('Hello Flutter'), + child: const Center(child: FlutterLogo(size: 100)), + ), + ), + ), + identifier: 'overlay_positioned_card', + delegate: null, + anchorPosition: Offset(0, windowSize.height - 200), + anchorSize: Size.zero, + anchorDirection: providerContext.read().anchorDirection, + ); + }, + child: const Text('Show Positioned Overlay'), + ), + ], ), - ], + ), ); }), )); diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart index 350a87d4f7..fbf5bca6f7 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_layout_delegate.dart @@ -39,16 +39,97 @@ class OverlayLayoutDelegate extends SingleChildLayoutDelegate { Offset position; switch (anchorDirection) { case AnchorDirection.topLeft: - position = Offset(anchorRect.left - childSize.width, anchorRect.top - childSize.height); + position = Offset( + anchorRect.left - childSize.width, + anchorRect.top - childSize.height, + ); break; case AnchorDirection.topRight: - position = Offset(anchorRect.right, anchorRect.top - childSize.height); + position = Offset( + anchorRect.right, + anchorRect.top - childSize.height, + ); break; case AnchorDirection.bottomLeft: - position = Offset(anchorRect.left - childSize.width, anchorRect.bottom); + position = Offset( + anchorRect.left - childSize.width, + anchorRect.bottom, + ); break; case AnchorDirection.bottomRight: - position = Offset(anchorRect.right, anchorRect.bottom); + position = Offset( + anchorRect.right, + anchorRect.bottom, + ); + 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();