Skip to content

Commit

Permalink
[SuperTextField] [SuperDesktopTextField] Adds Page-Up/Down, Home/End …
Browse files Browse the repository at this point in the history
…selectors and handlers to SuperDesktopTextField (Resolves #1438) (#1463)
  • Loading branch information
rutvik110 authored and matthew-carroll committed Dec 11, 2023
1 parent 8610aa5 commit 53b90ad
Show file tree
Hide file tree
Showing 15 changed files with 2,207 additions and 164 deletions.
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 {
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,
width: double.infinity,
child: Placeholder(
child: Center(
child: Text("Content"),
),
),
),
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,
);
},
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 2,
width: double.infinity,
child: Placeholder(
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 {
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 @@ -445,7 +446,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 @@ -1035,22 +1037,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

0 comments on commit 53b90ad

Please sign in to comment.