Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SuperTextField] [SuperDesktopTextField] Adds Page-Up/Down, Home/End selectors and handlers to SuperDesktopTextField (Resolves #1438) #1463

Merged
merged 54 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f339ed0
text field scrollable demo added
rutvik110 Sep 17, 2023
e3e20e1
added page-up/down, home/end selectors and keyboard action handlers t…
rutvik110 Sep 21, 2023
336606c
scrolling SuperDesktopField before ancestor scrollable if there's any…
rutvik110 Oct 3, 2023
1df8006
review updates
rutvik110 Oct 4, 2023
0764793
updated scroll keyboard handlers, added blockControlKeys to keyboard …
rutvik110 Oct 4, 2023
c73dbe3
textfield within scrollable demo update
rutvik110 Oct 4, 2023
789bac6
updated macOS selectors to scroll textfield content before scrolling …
rutvik110 Oct 15, 2023
ee053ca
added scrollOnHomeOnMacOrWeb and scrollOnEndOnMacOrWeb to SuperTextFi…
rutvik110 Oct 15, 2023
79564e3
reverted unwanted change while rebasing against main
rutvik110 Oct 18, 2023
f3d45cd
handler names update
rutvik110 Oct 19, 2023
fc78760
scroll handlers updated
rutvik110 Oct 19, 2023
717a467
comparing keys directly instead of keyId
rutvik110 Oct 19, 2023
dc4bef6
docs update
rutvik110 Oct 19, 2023
327d1e8
docs update
rutvik110 Oct 19, 2023
beb57de
moved ctrl/cmd+ home/end handlers to handle those on mac before event…
rutvik110 Oct 23, 2023
5d747e6
added tests for SuperTextField scrolling on different scroll shortcut…
rutvik110 Oct 23, 2023
a4a99b2
added tests for SuperTextField scrolling alongside ancestor scrollabl…
rutvik110 Oct 24, 2023
b9883f9
tests added when textfield is at center within the ancestor scrollable
rutvik110 Oct 24, 2023
13e7a32
tests refactor
rutvik110 Oct 25, 2023
89d27da
removed ios references from SuperDesktopField scroll shortcuts and tests
rutvik110 Oct 25, 2023
cfb019d
refactor
rutvik110 Oct 25, 2023
d69b6fc
refactor
rutvik110 Oct 25, 2023
f1d3f3d
reusing selector handlers within keyboard/ime handlers for performing…
rutvik110 Oct 26, 2023
31b524d
refactor
rutvik110 Oct 26, 2023
9b8b089
scroll key handlers updated
rutvik110 Oct 27, 2023
73427c3
doc updates
rutvik110 Oct 28, 2023
1be30b1
desktop textfield docs update
rutvik110 Oct 31, 2023
e649a95
desktop textfield scroll demo updates
rutvik110 Oct 31, 2023
3d3ee52
SuperDesktopTextField scroll tests moved to separate file, refactor
rutvik110 Oct 31, 2023
650503b
doc updates, refacoring
rutvik110 Nov 4, 2023
f36bf57
removed key usage to find ancestor scrollable in tests
rutvik110 Nov 4, 2023
98cf086
docs updates
rutvik110 Nov 4, 2023
d76f5d4
super text field scrolling demo updates
rutvik110 Nov 7, 2023
7d8d189
scroll handlers docs updates
rutvik110 Nov 7, 2023
155432d
added `placeCaretInSuperDesktopTextField` within `SuperTextFieldRobot…
rutvik110 Nov 7, 2023
550f88f
removed redundant parameter
rutvik110 Nov 7, 2023
3dca044
refactor
rutvik110 Nov 11, 2023
25b1b8e
test setup update
rutvik110 Nov 11, 2023
273f459
added new test group for running a test across mac, and all web deskt…
rutvik110 Nov 11, 2023
d65521b
using SuperTextField in the widget tree
rutvik110 Nov 12, 2023
9e20b32
setting platform configuration to desktop for SuperTextField
rutvik110 Nov 12, 2023
5de64d9
reverted unwanted changes
rutvik110 Nov 14, 2023
3cfea2e
renamed 'placement' to verticalAlignment
rutvik110 Nov 17, 2023
1ddb85b
test files renamed
rutvik110 Nov 17, 2023
46f2869
refactored scrolling variant
rutvik110 Nov 17, 2023
d0dc44d
scroll variant updated
rutvik110 Nov 17, 2023
91d0251
removed SuperDesktopTextField references, and using SuperTextField in…
rutvik110 Nov 18, 2023
13c9820
added extension to find ancestor scrollable within current context
rutvik110 Nov 18, 2023
800a4b6
docs updates
rutvik110 Nov 28, 2023
d792a97
docs updates
rutvik110 Nov 28, 2023
3176606
migrated usage of RawKeyDownEvent and other deprecated things related…
rutvik110 Dec 4, 2023
f34990b
renamed test file
rutvik110 Dec 8, 2023
45fc989
test descriptions update
rutvik110 Dec 8, 2023
565078e
test description updates, removed multiline descriptions
rutvik110 Dec 8, 2023
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
18 changes: 18 additions & 0 deletions super_editor/example/lib/demos/supertextfield/demo_textfield.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:example/demos/supertextfield/_emojis_demo.dart';
import 'package:example/demos/supertextfield/_expanding_multi_line_demo.dart';
import 'package:example/demos/supertextfield/_interactive_demo.dart';
import 'package:example/demos/supertextfield/textfield_inside_single_child_scroll_view_demo.dart';
import 'package:example/demos/supertextfield/textfield_inside_slivers_demo.dart';
import 'package:example/demos/supertextfield/_single_line_demo.dart';
import 'package:example/demos/supertextfield/_static_multi_line_demo.dart';
import 'package:example/demos/supertextfield/_textfield_demo_screen.dart';
Expand Down Expand Up @@ -91,6 +93,22 @@ class _TextFieldDemoState extends State<TextFieldDemo> {
});
},
),
DemoMenuItem(
label: 'TextField inside slivers',
onPressed: () {
setState(() {
_demoBuilder = (_) => const TextFieldInsideSliversDemo();
});
},
),
DemoMenuItem(
label: 'TextField inside SingleChildScrollView',
onPressed: () {
setState(() {
_demoBuilder = (_) => const TextFieldInsideSingleChildScrollViewDemo();
});
},
),
],
child: _demoBuilder(context),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:example/demos/supertextfield/demo_text_styles.dart';
import 'package:flutter/material.dart';
import 'package:super_editor/super_text_field.dart';

/// Demo of [SuperDesktopTextField] inside [SingleChildScrollView] with scrollable content.
class TextFieldInsideSingleChildScrollViewDemo extends StatefulWidget {
rutvik110 marked this conversation as resolved.
Show resolved Hide resolved
const TextFieldInsideSingleChildScrollViewDemo({super.key});

@override
State<TextFieldInsideSingleChildScrollViewDemo> createState() => _TextFieldInsideSingleChildScrollViewDemoState();
}

class _TextFieldInsideSingleChildScrollViewDemoState extends State<TextFieldInsideSingleChildScrollViewDemo> {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
SizedBox(
// Occupy 80% of the vertical space to avoid pushing text field off-screen
// and to provide a visual clue on text field's position within demo.
height: MediaQuery.of(context).size.height * 0.8,
rutvik110 marked this conversation as resolved.
Show resolved Hide resolved
width: double.infinity,
child: Placeholder(
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
child: Center(
child: Text("Content"),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
child: SuperDesktopTextField(
// This demo tests scrolling text field behavior. Force the text field to be tall
// enough to easily see content scrolling by, but short enough to ensure that
// the content is scrollable.
minLines: 5,
maxLines: 5,
textStyleBuilder: demoTextStyleBuilder,
decorationBuilder: (context, child) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.grey.shade300,
width: 1,
),
),
child: child,
);
},
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 2,
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
width: double.infinity,
child: Placeholder(
matthew-carroll marked this conversation as resolved.
Show resolved Hide resolved
child: Center(
child: Text("Content"),
),
),
),
],
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:example/demos/supertextfield/demo_text_styles.dart';
import 'package:flutter/material.dart';
import 'package:super_editor/super_text_field.dart';

/// Demo of [SuperDesktopTextField] inside Slivers with scrollable content.
class TextFieldInsideSliversDemo extends StatefulWidget {
rutvik110 marked this conversation as resolved.
Show resolved Hide resolved
const TextFieldInsideSliversDemo({super.key});

@override
State<TextFieldInsideSliversDemo> createState() => _TextFieldInsideSliversDemoState();
}

class _TextFieldInsideSliversDemoState extends State<TextFieldInsideSliversDemo> {
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: SizedBox(
// Occupy 80% of the vertical space to avoid pushing text field off-screen
// and to provide a visual clue on text field's position within demo.
height: MediaQuery.of(context).size.height * 0.8,
width: double.infinity,
child: Placeholder(
child: Center(
child: Text("Content"),
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SuperDesktopTextField(
// This demo tests scrolling text field behavior. Force the text field to be tall
// enough to easily see content scrolling by, but short enough to ensure that
// the content is scrollable.
minLines: 5,
maxLines: 5,
textStyleBuilder: demoTextStyleBuilder,
decorationBuilder: (context, child) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.grey.shade300,
width: 1,
),
),
child: child,
);
},
),
),
),
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: double.infinity,
child: Placeholder(
child: Center(
child: Text("Content"),
),
),
),
),
],
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:super_editor/src/default_editor/text_tools.dart';
import 'package:super_editor/src/document_operations/selection_operations.dart';
import 'package:super_editor/src/infrastructure/_logging.dart';
import 'package:super_editor/src/infrastructure/content_layers.dart';
import 'package:super_editor/src/infrastructure/flutter/build_context.dart';
import 'package:super_editor/src/infrastructure/flutter/flutter_scheduler.dart';
import 'package:super_editor/src/infrastructure/multi_tap_gesture.dart';
import 'package:super_editor/src/infrastructure/platforms/android/android_document_controls.dart';
Expand Down Expand Up @@ -486,7 +487,7 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt

_controlsController = SuperEditorAndroidControlsScope.rootOf(context);

_ancestorScrollPosition = _findAncestorScrollable(context)?.position;
_ancestorScrollPosition = context.findAncestorScrollableWithVerticalScroll?.position;

// On the next frame, check if our active scroll position changed to a
// different instance. If it did, move our listener to the new one.
Expand Down Expand Up @@ -571,7 +572,8 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
/// widget includes a `ScrollView` and this `State`'s render object
/// is the viewport `RenderBox`.
RenderBox get viewportBox =>
(_findAncestorScrollable(context)?.context.findRenderObject() ?? context.findRenderObject()) as RenderBox;
(context.findAncestorScrollableWithVerticalScroll?.context.findRenderObject() ?? context.findRenderObject())
as RenderBox;

Offset _getDocumentOffsetFromGlobalOffset(Offset globalOffset) {
return _docLayout.getDocumentOffsetFromAncestorOffset(globalOffset);
Expand Down Expand Up @@ -1214,22 +1216,6 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
]);
}

ScrollableState? _findAncestorScrollable(BuildContext context) {
final ancestorScrollable = Scrollable.maybeOf(context);
if (ancestorScrollable == null) {
return null;
}

final direction = ancestorScrollable.axisDirection;
// If the direction is horizontal, then we are inside a widget like a TabBar
// or a horizontal ListView, so we can't use the ancestor scrollable
if (direction == AxisDirection.left || direction == AxisDirection.right) {
return null;
}

return ancestorScrollable;
}

@override
Widget build(BuildContext context) {
final gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:super_editor/src/default_editor/text_tools.dart';
import 'package:super_editor/src/document_operations/selection_operations.dart';
import 'package:super_editor/src/infrastructure/_logging.dart';
import 'package:super_editor/src/infrastructure/content_layers.dart';
import 'package:super_editor/src/infrastructure/flutter/build_context.dart';
import 'package:super_editor/src/infrastructure/flutter/flutter_scheduler.dart';
import 'package:super_editor/src/infrastructure/multi_tap_gesture.dart';
import 'package:super_editor/src/infrastructure/platforms/ios/floating_cursor.dart';
Expand Down Expand Up @@ -333,7 +334,7 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
_controlsController!.floatingCursorController.addListener(_floatingCursorListener);
_controlsController!.floatingCursorController.cursorGeometryInViewport.addListener(_onFloatingCursorGeometryChange);

_ancestorScrollPosition = _findAncestorScrollable(context)?.position;
_ancestorScrollPosition = context.findAncestorScrollableWithVerticalScroll?.position;

// On the next frame, check if our active scroll position changed to a
// different instance. If it did, move our listener to the new one.
Expand Down Expand Up @@ -477,7 +478,8 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
/// widget includes a `ScrollView` and this `State`'s render object
/// is the viewport `RenderBox`.
RenderBox get viewportBox =>
(_findAncestorScrollable(context)?.context.findRenderObject() ?? context.findRenderObject()) as RenderBox;
(context.findAncestorScrollableWithVerticalScroll?.context.findRenderObject() ?? context.findRenderObject())
as RenderBox;

Offset _documentOffsetToViewportOffset(Offset documentOffset) {
final globalOffset = _docLayout.getGlobalOffsetFromDocumentOffset(documentOffset);
Expand Down Expand Up @@ -1219,22 +1221,6 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
]);
}

ScrollableState? _findAncestorScrollable(BuildContext context) {
final ancestorScrollable = Scrollable.maybeOf(context);
if (ancestorScrollable == null) {
return null;
}

final direction = ancestorScrollable.axisDirection;
// If the direction is horizontal, then we are inside a widget like a TabBar
// or a horizontal ListView, so we can't use the ancestor scrollable
if (direction == AxisDirection.left || direction == AxisDirection.right) {
return null;
}

return ancestorScrollable;
}

/// Starts a drag activity to scroll the document.
void _startDragScrolling(DragStartDetails details) {
_dragMode = DragMode.scroll;
Expand Down
22 changes: 4 additions & 18 deletions super_editor/lib/src/default_editor/document_scrollable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:super_editor/src/infrastructure/_logging.dart';
import 'package:super_editor/src/infrastructure/documents/document_scroller.dart';
import 'package:super_editor/src/infrastructure/flutter/build_context.dart';
import 'package:super_editor/src/infrastructure/flutter/flutter_scheduler.dart';
import 'package:super_editor/src/infrastructure/scrolling_diagnostics/_scrolling_minimap.dart';

Expand Down Expand Up @@ -159,7 +160,8 @@ class _DocumentScrollableState extends State<DocumentScrollable> with SingleTick
/// widget includes a `ScrollView` and this `State`'s render object
/// is the viewport `RenderBox`.
RenderBox get _viewport =>
(_findAncestorScrollable(context)?.context.findRenderObject() ?? context.findRenderObject()) as RenderBox;
(context.findAncestorScrollableWithVerticalScroll?.context.findRenderObject() ?? context.findRenderObject())
as RenderBox;

/// Returns the `ScrollPosition` that controls the scroll offset of
/// this widget.
Expand All @@ -173,25 +175,9 @@ class _DocumentScrollableState extends State<DocumentScrollable> with SingleTick
/// is returned.
ScrollPosition get _scrollPosition => _ancestorScrollPosition ?? _scrollController.position;

ScrollableState? _findAncestorScrollable(BuildContext context) {
final ancestorScrollable = Scrollable.maybeOf(context);
if (ancestorScrollable == null) {
return null;
}

final direction = ancestorScrollable.axisDirection;
// If the direction is horizontal, then we are inside a widget like a TabBar
// or a horizontal ListView, so we can't use the ancestor scrollable
if (direction == AxisDirection.left || direction == AxisDirection.right) {
return null;
}

return ancestorScrollable;
}

@override
Widget build(BuildContext context) {
final ancestorScrollable = _findAncestorScrollable(context);
final ancestorScrollable = context.findAncestorScrollableWithVerticalScroll;
_ancestorScrollPosition = ancestorScrollable?.position;

return Stack(
Expand Down
21 changes: 21 additions & 0 deletions super_editor/lib/src/infrastructure/flutter/build_context.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';

extension ScrollableFinder on BuildContext {
/// Finds the nearest ancestor [Scrollable] with a vertical scroll in the
/// widget tree.
ScrollableState? get findAncestorScrollableWithVerticalScroll {
final ancestorScrollable = Scrollable.maybeOf(this);
if (ancestorScrollable == null) {
return null;
}

final direction = ancestorScrollable.axisDirection;
// If the direction is horizontal, then we are inside a widget like a TabBar
// or a horizontal ListView, so we can't use the ancestor scrollable
if (direction == AxisDirection.left || direction == AxisDirection.right) {
return null;
}

return ancestorScrollable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:super_editor/src/infrastructure/blinking_caret.dart';
import 'package:super_editor/src/infrastructure/document_gestures.dart';
import 'package:super_editor/src/infrastructure/document_gestures_interaction_overrides.dart';
import 'package:super_editor/src/infrastructure/documents/selection_leader_document_layer.dart';
import 'package:super_editor/src/infrastructure/flutter/build_context.dart';
import 'package:super_editor/src/infrastructure/flutter/flutter_scheduler.dart';
import 'package:super_editor/src/infrastructure/flutter/overlay_with_groups.dart';
import 'package:super_editor/src/infrastructure/multi_tap_gesture.dart';
Expand Down Expand Up @@ -187,7 +188,7 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
void didChangeDependencies() {
super.didChangeDependencies();

_ancestorScrollPosition = _findAncestorScrollable(context)?.position;
_ancestorScrollPosition = context.findAncestorScrollableWithVerticalScroll?.position;

// On the next frame, check if our active scroll position changed to a
// different instance. If it did, move our listener to the new one.
Expand Down Expand Up @@ -443,7 +444,8 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
/// widget includes a `ScrollView` and this `State`'s render object
/// is the viewport `RenderBox`.
RenderBox get viewportBox =>
(_findAncestorScrollable(context)?.context.findRenderObject() ?? context.findRenderObject()) as RenderBox;
(context.findAncestorScrollableWithVerticalScroll?.context.findRenderObject() ?? context.findRenderObject())
as RenderBox;

Offset _getDocumentOffsetFromGlobalOffset(Offset globalOffset) {
return _docLayout.getDocumentOffsetFromAncestorOffset(globalOffset);
Expand Down Expand Up @@ -1033,22 +1035,6 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
);
}

ScrollableState? _findAncestorScrollable(BuildContext context) {
final ancestorScrollable = Scrollable.maybeOf(context);
if (ancestorScrollable == null) {
return null;
}

final direction = ancestorScrollable.axisDirection;
// If the direction is horizontal, then we are inside a widget like a TabBar
// or a horizontal ListView, so we can't use the ancestor scrollable
if (direction == AxisDirection.left || direction == AxisDirection.right) {
return null;
}

return ancestorScrollable;
}

@override
Widget build(BuildContext context) {
final gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;
Expand Down
Loading
Loading