diff --git a/scribe/lib/scribe/ai/presentation/model/modal/anchored_suggestion_layout_result.dart b/scribe/lib/scribe/ai/presentation/model/modal/anchored_suggestion_layout_result.dart index 0101928d4..134b02574 100644 --- a/scribe/lib/scribe/ai/presentation/model/modal/anchored_suggestion_layout_result.dart +++ b/scribe/lib/scribe/ai/presentation/model/modal/anchored_suggestion_layout_result.dart @@ -1,15 +1,11 @@ -import 'package:flutter/cupertino.dart'; - class AnchoredSuggestionLayoutResult { - final Offset offset; final double availableHeight; - final double? top; - final double? bottom; + final double left; + final double bottom; const AnchoredSuggestionLayoutResult({ - required this.offset, + required this.left, + required this.bottom, required this.availableHeight, - this.top, - this.bottom, }); } diff --git a/scribe/lib/scribe/ai/presentation/utils/modal/anchored_modal_layout_calculator.dart b/scribe/lib/scribe/ai/presentation/utils/modal/anchored_modal_layout_calculator.dart index ec85d4e5a..a968977ec 100644 --- a/scribe/lib/scribe/ai/presentation/utils/modal/anchored_modal_layout_calculator.dart +++ b/scribe/lib/scribe/ai/presentation/utils/modal/anchored_modal_layout_calculator.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/animation.dart'; import 'package:scribe/scribe.dart'; @@ -145,10 +143,11 @@ class AnchoredModalLayoutCalculator { required Size screenSize, required Offset anchorPosition, required Size anchorSize, - required double modalMaxHeight, + required Size menuSize, ModalPlacement? preferredPlacement, double gap = 8, - double padding = AIScribeSizes.screenEdgePadding, + double verticalOffset = 6, + double padding = 8, }) { final isTop = preferredPlacement == ModalPlacement.top; @@ -157,25 +156,79 @@ class AnchoredModalLayoutCalculator { final positionBottom = screenSize.height - anchorPosition.dy + gap; return AnchoredSuggestionLayoutResult( - offset: Offset( - anchorPosition.dx, - positionBottom, - ), availableHeight: availableHeight, + left: anchorPosition.dx, bottom: positionBottom, ); } - final spaceBelow = screenSize.height - anchorPosition.dy - padding - gap; - final positionTop = anchorPosition.dx + anchorSize.width + gap; + late ModalPlacement placement; + + if (preferredPlacement != null && + _canPlace( + placement: preferredPlacement, + screen: screenSize, + anchor: anchorPosition, + anchorSize: anchorSize, + menu: menuSize, + gap: gap, + )) { + placement = preferredPlacement; + } else { + final fallbackOrder = [ + ModalPlacement.right, + ModalPlacement.bottom, + ModalPlacement.top, + ModalPlacement.left, + ]; + + placement = fallbackOrder.firstWhere( + (p) => _canPlace( + placement: p, + screen: screenSize, + anchor: anchorPosition, + anchorSize: anchorSize, + menu: menuSize, + gap: gap, + ), + orElse: () => ModalPlacement.right, + ); + } + + late double left; + late double bottom; + + switch (placement) { + case ModalPlacement.right: + left = anchorPosition.dx + anchorSize.width + gap; + bottom = anchorPosition.dy + verticalOffset; + break; + + case ModalPlacement.bottom: + left = anchorPosition.dx; + bottom = anchorPosition.dy + anchorSize.height + gap + verticalOffset; + break; + + case ModalPlacement.top: + left = anchorPosition.dx; + bottom = anchorPosition.dy - menuSize.height - gap + verticalOffset; + break; + case ModalPlacement.left: + left = anchorPosition.dx - menuSize.width - gap; + bottom = anchorPosition.dy + verticalOffset; + break; + } + + left = left.clamp(padding, screenSize.width - menuSize.width - padding); + bottom = bottom.clamp( + padding, + screenSize.height - menuSize.height - padding, + ); return AnchoredSuggestionLayoutResult( - offset: Offset( - positionTop, - anchorPosition.dy, - ), - availableHeight: min(spaceBelow, modalMaxHeight), - top: anchorPosition.dy, + availableHeight: menuSize.height, + left: left, + bottom: bottom, ); } } diff --git a/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart b/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart index 3c8a9f99e..bb359365c 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart @@ -104,8 +104,9 @@ class _AiScribeSuggestionWidgetState extends State { screenSize: screenSize, anchorPosition: widget.buttonPosition!, anchorSize: widget.buttonSize!, - modalMaxHeight: modalMaxHeight, + menuSize: Size(modalWidth, modalMaxHeight), preferredPlacement: widget.preferredPlacement, + padding: AIScribeSizes.screenEdgePadding, ); return PointerInterceptor( @@ -118,8 +119,7 @@ class _AiScribeSuggestionWidgetState extends State { ), ), PositionedDirectional( - start: layout.offset.dx, - top: layout.top, + start: layout.left, bottom: layout.bottom, child: _buildModalContainer( width: modalWidth,