Skip to content

Commit

Permalink
Fix hint component and example app toolbar focus (Resolves #726) (#730)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-carroll committed Aug 13, 2022
1 parent c248522 commit 574bc21
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import 'dart:math';

import 'package:example/logging.dart';
import 'package:flutter/material.dart';
import 'package:super_editor/super_editor.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:super_editor/super_editor.dart';

/// Small toolbar that is intended to display near some selected
/// text and offer a few text formatting controls.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:example/demos/example_editor/_task.dart';
import 'package:flutter/foundation.dart';
import 'package:example/logging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:super_editor/super_editor.dart';

Expand Down
51 changes: 27 additions & 24 deletions super_editor/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@ import 'package:example/demos/components/demo_unselectable_hr.dart';
import 'package:example/demos/debugging/simple_deltas_input.dart';
import 'package:example/demos/demo_app_shortcuts.dart';
import 'package:example/demos/demo_empty_document.dart';
import 'package:example/demos/demo_rtl.dart';
import 'package:example/demos/demo_markdown_serialization.dart';
import 'package:example/demos/demo_paragraphs.dart';
import 'package:example/demos/demo_rtl.dart';
import 'package:example/demos/demo_selectable_text.dart';
import 'package:example/demos/editor_configs/demo_mobile_editing_android.dart';
import 'package:example/demos/editor_configs/demo_mobile_editing_ios.dart';
import 'package:example/demos/example_editor/example_editor.dart';
import 'package:example/demos/flutter_features/demo_inline_widgets.dart';
import 'package:example/demos/flutter_features/textinputclient/basic_text_input_client.dart';
import 'package:example/demos/scrolling/demo_task_and_chat_with_customscrollview.dart';
import 'package:example/demos/styles/demo_doc_styles.dart';
import 'package:example/demos/supertextfield/ios/demo_superiostextfield.dart';
import 'package:example/demos/flutter_features/textinputclient/textfield.dart';
import 'package:example/demos/scrolling/demo_task_and_chat_with_customscrollview.dart';
import 'package:example/demos/sliver_example_editor.dart';
import 'package:example/demos/styles/demo_doc_styles.dart';
import 'package:example/demos/supertextfield/demo_textfield.dart';
import 'package:example/logging.dart';
import 'package:example/demos/supertextfield/ios/demo_superiostextfield.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
Expand Down Expand Up @@ -117,25 +116,29 @@ class _HomeScreenState extends State<HomeScreen> {

@override
Widget build(BuildContext context) {
// We need our own [Overlay] instead of the one created by the navigator
// because overlay entries added to navigator's [Overlay] are always
// displayed above all routes.
//
// We display the editor's toolbar in an [OverlayEntry], so inserting it
// at the navigator's [Overlay] causes widgets that are displayed in routes,
// e.g. [DropdownButton] items, to be displayed beneath the toolbar.
return Overlay(
initialEntries: [
OverlayEntry(builder: (context) {
return Scaffold(
key: _scaffoldKey,
appBar: _buildAppBar(context),
extendBodyBehindAppBar: true,
body: _selectedMenuItem!.pageBuilder(context),
drawer: _buildDrawer(),
);
})
],
// We need a FocusScope above the Overlay so that focus can be shared between
// SuperEditor in one OverlayEntry, and the popover toolbar in another OverlayEntry.
return FocusScope(
// We need our own [Overlay] instead of the one created by the navigator
// because overlay entries added to navigator's [Overlay] are always
// displayed above all routes.
//
// We display the editor's toolbar in an [OverlayEntry], so inserting it
// at the navigator's [Overlay] causes widgets that are displayed in routes,
// e.g. [DropdownButton] items, to be displayed beneath the toolbar.
child: Overlay(
initialEntries: [
OverlayEntry(builder: (context) {
return Scaffold(
key: _scaffoldKey,
appBar: _buildAppBar(context),
extendBodyBehindAppBar: true,
body: _selectedMenuItem!.pageBuilder(context),
drawer: _buildDrawer(),
);
})
],
),
);
}

Expand Down
40 changes: 21 additions & 19 deletions super_editor/lib/src/core/document_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -269,97 +269,99 @@ mixin DocumentComponent<T extends StatefulWidget> on State<T> {
/// to provide is [childDocumentComponentKey], which is a `GlobalKey` that provides
/// access to the child [DocumentComponent].
mixin ProxyDocumentComponent<T extends StatefulWidget> implements DocumentComponent<T> {
DocumentComponent get childDocumentComponentKey;
GlobalKey get childDocumentComponentKey;

DocumentComponent get childDocumentComponent => childDocumentComponentKey.currentState as DocumentComponent;

@override
NodePosition? getPositionAtOffset(Offset localOffset) {
return childDocumentComponentKey.getPositionAtOffset(localOffset);
return childDocumentComponent.getPositionAtOffset(localOffset);
}

@override
Offset getOffsetForPosition(NodePosition nodePosition) {
return childDocumentComponentKey.getOffsetForPosition(nodePosition);
return childDocumentComponent.getOffsetForPosition(nodePosition);
}

@override
Rect getRectForPosition(NodePosition nodePosition) {
return childDocumentComponentKey.getRectForPosition(nodePosition);
return childDocumentComponent.getRectForPosition(nodePosition);
}

@override
Rect getRectForSelection(NodePosition baseNodePosition, NodePosition extentNodePosition) {
return childDocumentComponentKey.getRectForSelection(baseNodePosition, extentNodePosition);
return childDocumentComponent.getRectForSelection(baseNodePosition, extentNodePosition);
}

@override
NodePosition getBeginningPosition() {
return childDocumentComponentKey.getBeginningPosition();
return childDocumentComponent.getBeginningPosition();
}

@override
NodePosition getBeginningPositionNearX(double x) {
return childDocumentComponentKey.getBeginningPositionNearX(x);
return childDocumentComponent.getBeginningPositionNearX(x);
}

@override
NodePosition? movePositionLeft(NodePosition currentPosition, [MovementModifier? movementModifier]) {
return childDocumentComponentKey.movePositionLeft(currentPosition, movementModifier);
return childDocumentComponent.movePositionLeft(currentPosition, movementModifier);
}

@override
NodePosition? movePositionRight(NodePosition currentPosition, [MovementModifier? movementModifier]) {
return childDocumentComponentKey.movePositionRight(currentPosition, movementModifier);
return childDocumentComponent.movePositionRight(currentPosition, movementModifier);
}

@override
NodePosition? movePositionUp(NodePosition currentPosition) {
return childDocumentComponentKey.movePositionUp(currentPosition);
return childDocumentComponent.movePositionUp(currentPosition);
}

@override
NodePosition? movePositionDown(NodePosition currentPosition) {
return childDocumentComponentKey.movePositionDown(currentPosition);
return childDocumentComponent.movePositionDown(currentPosition);
}

@override
NodePosition getEndPosition() {
return childDocumentComponentKey.getEndPosition();
return childDocumentComponent.getEndPosition();
}

@override
NodePosition getEndPositionNearX(double x) {
return childDocumentComponentKey.getEndPositionNearX(x);
return childDocumentComponent.getEndPositionNearX(x);
}

@override
NodeSelection? getSelectionInRange(Offset localBaseOffset, Offset localExtentOffset) {
return childDocumentComponentKey.getSelectionInRange(localBaseOffset, localExtentOffset);
return childDocumentComponent.getSelectionInRange(localBaseOffset, localExtentOffset);
}

@override
NodeSelection getCollapsedSelectionAt(NodePosition nodePosition) {
return childDocumentComponentKey.getCollapsedSelectionAt(nodePosition);
return childDocumentComponent.getCollapsedSelectionAt(nodePosition);
}

@override
NodeSelection getSelectionBetween({
required NodePosition basePosition,
required NodePosition extentPosition,
}) {
return childDocumentComponentKey.getSelectionBetween(basePosition: basePosition, extentPosition: extentPosition);
return childDocumentComponent.getSelectionBetween(basePosition: basePosition, extentPosition: extentPosition);
}

@override
NodeSelection getSelectionOfEverything() {
return childDocumentComponentKey.getSelectionOfEverything();
return childDocumentComponent.getSelectionOfEverything();
}

@override
bool isVisualSelectionSupported() => childDocumentComponentKey.isVisualSelectionSupported();
bool isVisualSelectionSupported() => childDocumentComponent.isVisualSelectionSupported();

@override
MouseCursor? getDesiredCursorAtOffset(Offset localOffset) {
return childDocumentComponentKey.getDesiredCursorAtOffset(localOffset);
return childDocumentComponent.getDesiredCursorAtOffset(localOffset);
}
}

Expand Down
15 changes: 9 additions & 6 deletions super_editor/lib/src/default_editor/box_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,16 @@ class SelectableBox extends StatelessWidget {
Widget build(BuildContext context) {
final isSelected = selection != null && !selection!.isCollapsed;

return IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
color: isSelected ? selectionColor.withOpacity(0.5) : Colors.transparent,
return MouseRegion(
cursor: SystemMouseCursors.basic,
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
color: isSelected ? selectionColor.withOpacity(0.5) : Colors.transparent,
),
position: DecorationPosition.foreground,
child: child,
),
position: DecorationPosition.foreground,
child: child,
),
);
}
Expand Down
36 changes: 2 additions & 34 deletions super_editor/lib/src/default_editor/document_gestures_mouse.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ class _DocumentMouseInteractorState extends State<DocumentMouseInteractor> with
Offset? _dragEndGlobal;
bool _expandSelectionDuringDrag = false;

// Current mouse cursor style displayed on screen.
Offset? _cursorGlobalOffset;
final _cursorStyle = ValueNotifier<MouseCursor>(SystemMouseCursors.basic);

@override
void initState() {
super.initState();
Expand Down Expand Up @@ -329,7 +325,6 @@ class _DocumentMouseInteractorState extends State<DocumentMouseInteractor> with
editorGesturesLog.info("Pan start on document, global offset: ${details.globalPosition}");

_dragStartGlobal = details.globalPosition;
_cursorGlobalOffset = details.globalPosition;

widget.autoScroller.enableAutoScrolling();

Expand All @@ -352,9 +347,7 @@ class _DocumentMouseInteractorState extends State<DocumentMouseInteractor> with
editorGesturesLog.info("Pan update on document, global offset: ${details.globalPosition}");

_dragEndGlobal = details.globalPosition;
_cursorGlobalOffset = details.globalPosition;

_updateCursorStyle();
_updateDragSelection();

widget.autoScroller.setGlobalAutoScrollRegion(
Expand Down Expand Up @@ -394,22 +387,6 @@ class _DocumentMouseInteractorState extends State<DocumentMouseInteractor> with
}
}

void _onMouseMove(PointerEvent pointerEvent) {
_cursorGlobalOffset = pointerEvent.position;
_updateCursorStyle();
}

void _updateCursorStyle() {
final cursorOffsetInDocument = _getDocOffsetFromGlobalOffset(_cursorGlobalOffset!);
final desiredCursor = _docLayout.getDesiredCursorAtOffset(cursorOffsetInDocument);

if (desiredCursor != null && desiredCursor != _cursorStyle.value) {
_cursorStyle.value = desiredCursor;
} else if (desiredCursor == null && _cursorStyle.value != SystemMouseCursors.basic) {
_cursorStyle.value = SystemMouseCursors.basic;
}
}

void _updateDragSelection() {
if (_dragEndGlobal == null) {
// User isn't dragging. No need to update drag selection.
Expand Down Expand Up @@ -538,17 +515,8 @@ Updating drag selection:
Widget _buildCursorStyle({
required Widget child,
}) {
return AnimatedBuilder(
animation: _cursorStyle,
builder: (context, child) {
return Listener(
onPointerHover: _onMouseMove,
child: MouseRegion(
cursor: _cursorStyle.value,
child: child,
),
);
},
return MouseRegion(
cursor: SystemMouseCursors.text,
child: child,
);
}
Expand Down
4 changes: 2 additions & 2 deletions super_editor/lib/src/default_editor/document_input_ime.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class DocumentImeInteractor extends StatefulWidget {
final Widget child;

@override
_DocumentImeInteractorState createState() => _DocumentImeInteractorState();
State createState() => _DocumentImeInteractorState();
}

class _DocumentImeInteractorState extends State<DocumentImeInteractor> implements DeltaTextInputClient {
Expand Down Expand Up @@ -862,7 +862,7 @@ class SoftwareKeyboardHandler {
// On iOS, newlines are reported here and also to performAction().
// On Android and web, newlines are only reported here. So, on Android and web,
// we forward the newline action to performAction.
if (defaultTargetPlatform == TargetPlatform.android || kIsWeb) {
if (defaultTargetPlatform == TargetPlatform.android || kIsWeb) {
editorImeLog.fine("Received a newline insertion on Android. Forwarding to newline input action.");
performAction(TextInputAction.newline);
} else {
Expand Down
9 changes: 5 additions & 4 deletions super_editor/lib/src/default_editor/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'dart:collection';
import 'dart:math';

import 'package:attributed_text/attributed_text.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide SelectableText;
import 'package:flutter/services.dart';
import 'package:super_editor/src/core/document.dart';
Expand Down Expand Up @@ -329,7 +328,7 @@ class _TextWithHintComponentState extends State<TextWithHintComponent>
final _childTextComponentKey = GlobalKey<TextComponentState>();

@override
DocumentComponent<StatefulWidget> get childDocumentComponentKey => _childTextComponentKey.currentState!;
GlobalKey get childDocumentComponentKey => _childTextComponentKey;

@override
TextComposable get childTextComposable => _childTextComponentKey.currentState!;
Expand All @@ -351,8 +350,10 @@ class _TextWithHintComponentState extends State<TextWithHintComponent>
return Stack(
children: [
if (widget.text.text.isEmpty)
Text.rich(
widget.hintText?.computeTextSpan(_styleBuilder) ?? const TextSpan(text: ''),
IgnorePointer(
child: Text.rich(
widget.hintText?.computeTextSpan(_styleBuilder) ?? const TextSpan(text: ''),
),
),
TextComponent(
key: _childTextComponentKey,
Expand Down
Loading

0 comments on commit 574bc21

Please sign in to comment.