Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 45 additions & 13 deletions packages/fleather/lib/src/widgets/editor_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1274,14 +1274,32 @@ class SelectorScopeState extends State<SelectorScope> {
final RenderBox presenter = context.findRenderObject() as RenderBox;
final RenderBox overlayBox =
overlay.context.findRenderObject() as RenderBox;
final offset = Offset(0.0, presenter.size.height);
final presenterRect = Rect.fromPoints(
presenter.localToGlobal(Offset.zero, ancestor: overlayBox),
presenter.localToGlobal(
presenter.size.bottomRight(Offset.zero),
ancestor: overlayBox,
),
);
// Toolbar padding belongs to the area selectors must avoid as well.
// Keep horizontal alignment with the button, but clear the whole toolbar.
final toolbar = context.findAncestorStateOfType<_FleatherToolbarState>();
final toolbarBox = toolbar?.context.findRenderObject() as RenderBox?;
final toolbarRect = toolbarBox == null
? presenterRect
: Rect.fromPoints(
toolbarBox.localToGlobal(Offset.zero, ancestor: overlayBox),
toolbarBox.localToGlobal(
toolbarBox.size.bottomRight(Offset.zero),
ancestor: overlayBox,
),
);
final position = RelativeRect.fromSize(
Rect.fromPoints(
presenter.localToGlobal(offset, ancestor: overlayBox),
presenter.localToGlobal(
presenter.size.bottomRight(Offset.zero) + offset,
ancestor: overlayBox,
),
Rect.fromLTRB(
presenterRect.left,
toolbarRect.top,
presenterRect.right,
toolbarRect.bottom,
),
overlayBox.size,
);
Expand Down Expand Up @@ -1376,8 +1394,6 @@ class _SelectorLayout extends SingleChildLayoutDelegate {
// childSize: The size of the menu, when fully open, as determined by
// getConstraintsForChild.

final double y = position.top;

// Find the ideal horizontal position.
double x;
if (position.right > childSize.width) {
Expand All @@ -1395,13 +1411,29 @@ class _SelectorLayout extends SingleChildLayoutDelegate {
}
}

final Offset wantedPosition = Offset(x, y);
final Offset originCenter = position.toRect(Offset.zero & size).center;
final Rect presenter = position.toRect(Offset.zero & size);
final Offset originCenter = presenter.center;
final Iterable<Rect> subScreens =
DisplayFeatureSubScreen.subScreensInBounds(
Offset.zero & size, avoidBounds);
final Rect subScreen = _closestScreen(subScreens, originCenter);
return _fitInsideScreen(subScreen, childSize, wantedPosition);
final double spaceBelow = subScreen.bottom -
padding.bottom -
_selectorScreenPadding -
presenter.bottom -
_selectorScreenPadding;
final double spaceAbove = presenter.top -
_selectorScreenPadding -
subScreen.top -
padding.top -
_selectorScreenPadding;
// Prefer below the presenter, but flip above it when that offers more room.
final bool showAbove =
childSize.height > spaceBelow && spaceAbove > spaceBelow;
final double y = showAbove
? presenter.top - _selectorScreenPadding - childSize.height
: presenter.bottom + _selectorScreenPadding;
Comment on lines +1434 to +1435

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this and it works good except I believe there is extra padding now as you can see from the screenshots. I believe you shouldn't account for _selectorScreenPadding here as they would be added in _fitInsideScreen if needed (to push it away from screen edges).

Before:
Image
Image

After:
Image
Image

return _fitInsideScreen(subScreen, childSize, Offset(x, y));
}

Rect _closestScreen(Iterable<Rect> screens, Offset point) {
Expand Down Expand Up @@ -1430,7 +1462,7 @@ class _SelectorLayout extends SingleChildLayoutDelegate {
padding.right;
}
if (y < screen.top + _selectorScreenPadding + padding.top) {
y = _selectorScreenPadding + padding.top;
y = screen.top + _selectorScreenPadding + padding.top;
} else if (y + childSize.height >
screen.bottom - _selectorScreenPadding - padding.bottom) {
y = screen.bottom -
Expand Down
87 changes: 86 additions & 1 deletion packages/fleather/test/widgets/editor_toolbar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,91 @@ void main() {
});

group('SelectorScope', () {
for (final isAtBottom in [false, true]) {
for (final keyboardHeight in [0.0, 200.0]) {
for (final isBackground in [false, true]) {
testWidgets(
'${isBackground ? 'Background' : 'Text'} color selector opens '
'${isAtBottom ? 'above' : 'below'} the toolbar '
'with keyboard height $keyboardHeight',
(tester) async {
const padding = EdgeInsets.all(24);
final controller = FleatherController();
addTearDown(controller.dispose);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) => MediaQuery(
data: MediaQuery.of(context).copyWith(
padding: padding,
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
child: child!,
),
home: Scaffold(
body: SafeArea(
child: Column(
children: [
if (isAtBottom) const Spacer(),
FleatherToolbar(
children: [
ColorButton(
controller: controller,
attributeKey: isBackground
? ParchmentAttribute.backgroundColor
: ParchmentAttribute.foregroundColor,
nullColorLabel:
isBackground ? 'No color' : 'Automatic',
builder: (context, value) =>
const Icon(Icons.palette),
),
],
),
if (!isAtBottom) const Spacer(),
],
),
),
),
),
);
await tester.tap(find.byType(ColorButton));
await tester.pumpAndSettle();

final selector = find.byKey(const Key('color_selector'));
expect(selector, findsOneWidget);
final selectorRect = tester.getRect(selector);
final toolbarRect = tester.getRect(find.byType(FleatherToolbar));
if (isAtBottom) {
expect(selectorRect.bottom, lessThanOrEqualTo(toolbarRect.top));
} else {
expect(
selectorRect.top,
greaterThanOrEqualTo(toolbarRect.bottom),
);
}
expect(selectorRect.overlaps(toolbarRect), isFalse);

final screen = tester.getRect(find.byType(Scaffold));
expect(selectorRect.left, greaterThanOrEqualTo(padding.left));
expect(
selectorRect.right,
lessThanOrEqualTo(screen.right - padding.right),
);
expect(selectorRect.top, greaterThanOrEqualTo(padding.top));
expect(
selectorRect.bottom,
lessThanOrEqualTo(
screen.bottom - padding.bottom - keyboardHeight,
),
);
expect(tester.takeException(), isNull);
await tester.pumpWidget(const SizedBox());
await tester.pumpAndSettle(throttleDuration);
},
);
}
}
}

testWidgets('Correctly places the selector in a visible area of screen',
(WidgetTester tester) async {
const padding = EdgeInsets.all(32);
Expand Down Expand Up @@ -502,7 +587,7 @@ void main() {
);
expect(
tester.getRect(find.byKey(const Key('heading_selector'))).bottom,
tester.getRect(find.byType(Scaffold)).bottom - padding.bottom - 8,
lessThan(tester.getRect(find.byType(SelectHeadingButton)).top),
);
});
});
Expand Down
6 changes: 4 additions & 2 deletions packages/parchment/example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import 'package:parchment/parchment.dart';
void main() {
final doc = ParchmentDocument();
// Modify this document with insert, delete and format operations
doc.insert(0,
'Parchment package provides rich text document model for Fleather editor');
doc.insert(
0,
'Parchment package provides rich text document model for Fleather editor',
);
doc.format(0, 5, ParchmentAttribute.bold); // Makes first word bold.
doc.format(0, 0, ParchmentAttribute.h1); // Makes first line a heading.
doc.delete(23, 10); // Deletes "rich text " segment.
Expand Down
Loading
Loading