Skip to content

Commit

Permalink
Mobile mounted toolbar now appears above Android overlay controls, mi…
Browse files Browse the repository at this point in the history
…grated mounted toolbar and Android controls to OverlayPortal (Resolves #893)(Resolves #1510) (#1524)
  • Loading branch information
matthew-carroll committed Oct 17, 2023
1 parent 30f271f commit ce11f26
Show file tree
Hide file tree
Showing 29 changed files with 842 additions and 580 deletions.
175 changes: 76 additions & 99 deletions super_editor/example/lib/demos/example_editor/example_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ class _ExampleEditorState extends State<ExampleEditor> {

SuperEditorDebugVisualsConfig? _debugConfig;

OverlayEntry? _textFormatBarOverlayEntry;
final _textFormatBarOverlayController = OverlayPortalController();
final _textSelectionAnchor = ValueNotifier<Offset?>(null);

OverlayEntry? _imageFormatBarOverlayEntry;
final _imageFormatBarOverlayController = OverlayPortalController();
final _imageSelectionAnchor = ValueNotifier<Offset?>(null);

final _overlayController = MagnifierAndToolbarController() //
Expand All @@ -65,10 +65,6 @@ class _ExampleEditorState extends State<ExampleEditor> {

@override
void dispose() {
if (_textFormatBarOverlayEntry != null) {
_textFormatBarOverlayEntry!.remove();
}

_scrollController.dispose();
_editorFocusNode.dispose();
_composer.dispose();
Expand Down Expand Up @@ -139,35 +135,13 @@ class _ExampleEditorState extends State<ExampleEditor> {
}

void _showEditorToolbar() {
if (_textFormatBarOverlayEntry == null) {
// Create an overlay entry to build the editor toolbar.
// TODO: add an overlay to the Editor widget to avoid using the
// application overlay
_textFormatBarOverlayEntry ??= OverlayEntry(builder: (context) {
return EditorToolbar(
editorViewportKey: _viewportKey,
anchor: _selectionLayerLinks.expandedSelectionBoundsLink,
editorFocusNode: _editorFocusNode,
editor: _docEditor,
document: _doc,
composer: _composer,
closeToolbar: _hideEditorToolbar,
);
});

// Display the toolbar in the application overlay.
final overlay = Overlay.of(context);
overlay.insert(_textFormatBarOverlayEntry!);
}
_textFormatBarOverlayController.show();

// Schedule a callback after this frame to locate the selection
// bounds on the screen and display the toolbar near the selected
// text.
// TODO: switch this to use a Leader and Follower
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (_textFormatBarOverlayEntry == null) {
return;
}

final docBoundingBox = (_docLayoutKey.currentState as DocumentLayout)
.getRectForSelection(_composer.selection!.base, _composer.selection!.extent)!;
final docBox = _docLayoutKey.currentContext!.findRenderObject() as RenderBox;
Expand All @@ -185,22 +159,14 @@ class _ExampleEditorState extends State<ExampleEditor> {
// the bar doesn't momentarily "flash" at its old anchor position.
_textSelectionAnchor.value = null;

if (_textFormatBarOverlayEntry != null) {
// Remove the toolbar overlay and null-out the entry.
// We null out the entry because we can't query whether
// or not the entry exists in the overlay, so in our
// case, null implies the entry is not in the overlay,
// and non-null implies the entry is in the overlay.
_textFormatBarOverlayEntry!.remove();
_textFormatBarOverlayEntry = null;

// Ensure that focus returns to the editor.
//
// I tried explicitly unfocus()'ing the URL textfield
// in the toolbar but it didn't return focus to the
// editor. I'm not sure why.
_editorFocusNode.requestFocus();
}
_textFormatBarOverlayController.hide();

// Ensure that focus returns to the editor.
//
// I tried explicitly unfocus()'ing the URL textfield
// in the toolbar but it didn't return focus to the
// editor. I'm not sure why.
_editorFocusNode.requestFocus();
}

DocumentGestureMode get _gestureMode {
Expand Down Expand Up @@ -249,37 +215,11 @@ class _ExampleEditorState extends State<ExampleEditor> {
void _selectAll() => _docOps.selectAll();

void _showImageToolbar() {
if (_imageFormatBarOverlayEntry == null) {
// Create an overlay entry to build the image toolbar.
_imageFormatBarOverlayEntry ??= OverlayEntry(builder: (context) {
return ImageFormatToolbar(
anchor: _imageSelectionAnchor,
composer: _composer,
setWidth: (nodeId, width) {
final node = _doc.getNodeById(nodeId)!;
final currentStyles = SingleColumnLayoutComponentStyles.fromMetadata(node);
SingleColumnLayoutComponentStyles(
width: width,
padding: currentStyles.padding,
).applyTo(node);
},
closeToolbar: _hideImageToolbar,
);
});

// Display the toolbar in the application overlay.
final overlay = Overlay.of(context);
overlay.insert(_imageFormatBarOverlayEntry!);
}

// Schedule a callback after this frame to locate the selection
// bounds on the screen and display the toolbar near the selected
// text.
// TODO: switch to a Leader and Follower for this
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (_imageFormatBarOverlayEntry == null) {
return;
}

final docBoundingBox = (_docLayoutKey.currentState as DocumentLayout)
.getRectForSelection(_composer.selection!.base, _composer.selection!.extent)!;
final docBox = _docLayoutKey.currentContext!.findRenderObject() as RenderBox;
Expand All @@ -290,57 +230,61 @@ class _ExampleEditorState extends State<ExampleEditor> {

_imageSelectionAnchor.value = overlayBoundingBox.center;
});

_imageFormatBarOverlayController.show();
}

void _hideImageToolbar() {
// Null out the selection anchor so that when the bar re-appears,
// it doesn't momentarily "flash" at its old anchor position.
_imageSelectionAnchor.value = null;

if (_imageFormatBarOverlayEntry != null) {
// Remove the image toolbar overlay and null-out the entry.
// We null out the entry because we can't query whether
// or not the entry exists in the overlay, so in our
// case, null implies the entry is not in the overlay,
// and non-null implies the entry is in the overlay.
_imageFormatBarOverlayEntry!.remove();
_imageFormatBarOverlayEntry = null;

// Ensure that focus returns to the editor.
_editorFocusNode.requestFocus();
}
_imageFormatBarOverlayController.hide();

// Ensure that focus returns to the editor.
_editorFocusNode.requestFocus();
}

@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: _brightness,
builder: (context, _) {
return ValueListenableBuilder(
valueListenable: _brightness,
builder: (context, brightness, child) {
return Theme(
data: ThemeData(brightness: _brightness.value),
child: Builder(
builder: (themedContext) {
// This builder captures the new theme
return Stack(
data: ThemeData(brightness: brightness),
child: child!,
);
},
child: Builder(
// This builder captures the new theme
builder: (themedContext) {
return OverlayPortal(
controller: _textFormatBarOverlayController,
overlayChildBuilder: _buildFloatingToolbar,
child: OverlayPortal(
controller: _imageFormatBarOverlayController,
overlayChildBuilder: _buildImageToolbar,
child: Stack(
children: [
Column(
children: [
Expanded(
child: _buildEditor(themedContext),
),
if (_isMobile) _buildMountedToolbar(),
if (_isMobile) //
_buildMountedToolbar(),
],
),
Align(
alignment: Alignment.bottomRight,
child: _buildCornerFabs(),
),
],
);
},
),
);
},
),
),
);
},
),
);
}

Expand Down Expand Up @@ -495,6 +439,39 @@ class _ExampleEditorState extends State<ExampleEditor> {
},
);
}

Widget _buildFloatingToolbar(BuildContext context) {
return EditorToolbar(
editorViewportKey: _viewportKey,
anchor: _selectionLayerLinks.expandedSelectionBoundsLink,
editorFocusNode: _editorFocusNode,
editor: _docEditor,
document: _doc,
composer: _composer,
closeToolbar: _hideEditorToolbar,
);
}

Widget _buildImageToolbar(BuildContext context) {
return ImageFormatToolbar(
anchor: _imageSelectionAnchor,
composer: _composer,
setWidth: (nodeId, width) {
print("Applying width $width to node $nodeId");
final node = _doc.getNodeById(nodeId)!;
final currentStyles = SingleColumnLayoutComponentStyles.fromMetadata(node);
SingleColumnLayoutComponentStyles(
width: width,
padding: currentStyles.padding,
).applyTo(node);

// TODO: schedule a presentation reflow so that the image changes size immediately (https://github.com/superlistapp/super_editor/issues/1529)
// Right now, nothing happens when pressing the button, unless we force a
// rebuild/reflow.
},
closeToolbar: _hideImageToolbar,
);
}
}

// Makes text light, for use during dark mode styling.
Expand Down
Loading

0 comments on commit ce11f26

Please sign in to comment.