Skip to content

Commit

Permalink
Merge pull request AppFlowy-IO#141 from LucasXu0/auto_scroll
Browse files Browse the repository at this point in the history
feat: support auto scroll after selection updated
  • Loading branch information
LucasXu0 authored May 29, 2023
2 parents 0f9c453 + d49bfea commit 29ee7a3
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ class DeltaTextInputService extends TextInputService with DeltaTextInputClient {
@override
void performSelector(String selectorName) {}

@override
void insertContent(KeyboardInsertedContent content) {}

void _updateComposing(TextEditingDelta delta) {
if (delta is! TextEditingDeltaNonTextUpdate) {
if (composingTextRange != null &&
Expand All @@ -181,11 +184,6 @@ class DeltaTextInputService extends TextInputService with DeltaTextInputClient {
}
}
}

@override
void insertContent(KeyboardInsertedContent content) {
// TODO: implement insertContent
}
}

const String _whitespace = ' ';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,16 @@ class KeyboardServiceWidgetState extends State<KeyboardServiceWidget>
if (selection == null) {
textInputService.close();
} else {
// For the deletion, we should attach the text input service immediately.
_attachTextInputService(selection);

// debounce the attachTextInputService function to avoid
// the text input service being attached too frequently.
Debounce.debounce(
'attachTextInputService',
const Duration(milliseconds: 200),
() => _attachTextInputService(selection),
);
// Debounce.debounce(
// 'attachTextInputService',
// const Duration(milliseconds: 200),
// () => _attachTextInputService(selection),
// );

if (editorState.selectionUpdateReason == SelectionUpdateReason.uiEvent) {
focusNode.requestFocus();
Expand Down
30 changes: 30 additions & 0 deletions lib/src/editor/editor_component/service/scroll_service_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:appflowy_editor/src/editor/editor_component/service/scroll/deskt
import 'package:appflowy_editor/src/editor/editor_component/service/scroll/mobile_scroll_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ScrollServiceWidget extends StatefulWidget {
const ScrollServiceWidget({
Expand All @@ -29,6 +30,8 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>
AppFlowyScrollService get forward =>
_forwardKey.currentState as AppFlowyScrollService;

late EditorState editorState = context.read<EditorState>();

@override
late ScrollController scrollController;

Expand All @@ -37,6 +40,13 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>
super.initState();

scrollController = widget.scrollController ?? ScrollController();
editorState.selectionNotifier.addListener(_onSelectionChanged);
}

@override
void dispose() {
editorState.selectionNotifier.removeListener(_onSelectionChanged);
super.dispose();
}

@override
Expand Down Expand Up @@ -93,6 +103,26 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>
);
}

void _onSelectionChanged() {
// should auto scroll after the cursor or selection updated.
final selection = editorState.selection;
if (selection == null) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final selectionRect = editorState.service.selectionService.selectionRects;
if (selectionRect.isEmpty) {
return;
}
final endTouchPoint = selectionRect.last.centerRight;
if (selection.isCollapsed) {
startAutoScroll(endTouchPoint, edgeOffset: 50);
} else {
startAutoScroll(endTouchPoint);
}
});
}

@override
void disable() => forward.disable();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,6 @@ class _DesktopSelectionServiceWidgetState
}

_showDebugLayerIfNeeded(offset: panEndOffset);

editorState.service.scrollService?.startAutoScroll(
details.globalPosition,
);
}

void _onPanEnd(DragEndDetails details) {
Expand Down Expand Up @@ -443,7 +439,7 @@ class _DesktopSelectionServiceWidgetState
const baseToolbarOffset = Offset(0, 35.0);
final rects = selectable.getRectsInSelection(newSelection);
for (final rect in rects) {
final selectionRect = _transformRectToGlobal(selectable, rect);
final selectionRect = selectable.transformRectToGlobal(rect);
selectionRects.add(selectionRect);

// TODO: Need to compute more precise location.
Expand Down Expand Up @@ -522,7 +518,7 @@ class _DesktopSelectionServiceWidgetState
);

_cursorAreas.add(cursorArea);
selectionRects.add(_transformRectToGlobal(selectable, cursorRect));
selectionRects.add(selectable.transformRectToGlobal(cursorRect));
Overlay.of(context)?.insertAll(_cursorAreas);

_forceShowCursor();
Expand Down Expand Up @@ -591,11 +587,6 @@ class _DesktopSelectionServiceWidgetState
return node;
}

Rect _transformRectToGlobal(SelectableMixin selectable, Rect r) {
final Offset topLeft = selectable.localToGlobal(Offset(r.left, r.top));
return Rect.fromLTWH(topLeft.dx, topLeft.dy, r.width, r.height);
}

void _showDebugLayerIfNeeded({Offset? offset}) {
// remove false to show debug overlay.
// if (kDebugMode && false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,6 @@ class _MobileSelectionServiceWidgetState
updateSelection(selection);

_showDebugLayerIfNeeded(offset: details.globalPosition);

editorState.service.scrollService?.startAutoScroll(
details.globalPosition,
edgeOffset: 300,
direction: AxisDirection.up,
);
}

void _onDoubleTapDown(TapDownDetails details) {
Expand Down Expand Up @@ -335,7 +329,7 @@ class _MobileSelectionServiceWidgetState
const baseToolbarOffset = Offset(0, 35.0);
final rects = selectable.getRectsInSelection(newSelection);
for (final rect in rects) {
final selectionRect = _transformRectToGlobal(selectable, rect);
final selectionRect = selectable.transformRectToGlobal(rect);
selectionRects.add(selectionRect);

// TODO: Need to compute more precise location.
Expand Down Expand Up @@ -413,7 +407,7 @@ class _MobileSelectionServiceWidgetState
);

_cursorAreas.add(cursorArea);
selectionRects.add(_transformRectToGlobal(selectable, cursorRect));
selectionRects.add(selectable.transformRectToGlobal(cursorRect));
Overlay.of(context)?.insertAll(_cursorAreas);

_forceShowCursor();
Expand Down Expand Up @@ -482,11 +476,6 @@ class _MobileSelectionServiceWidgetState
return node;
}

Rect _transformRectToGlobal(SelectableMixin selectable, Rect r) {
final Offset topLeft = selectable.localToGlobal(Offset(r.left, r.top));
return Rect.fromLTWH(topLeft.dx, topLeft.dy, r.width, r.height);
}

void _showDebugLayerIfNeeded({Offset? offset}) {
// remove false to show debug overlay.
// if (kDebugMode && false) {
Expand Down
3 changes: 3 additions & 0 deletions lib/src/editor/toolbar/desktop/floating_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class _FloatingToolbarState extends State<FloatingToolbar>
}

void _showToolbar() {
if (editorState.selection?.isCollapsed ?? true) {
return;
}
final rects = editorState.selectionRects();
if (rects.isEmpty) {
return;
Expand Down
43 changes: 27 additions & 16 deletions lib/src/editor_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,28 +260,39 @@ class EditorState {
/// The current selection areas's rect in editor.
List<Rect> selectionRects() {
final selection = this.selection;
if (selection == null || selection.isCollapsed) {
if (selection == null) {
return [];
}

final nodes = getNodesInSelection(selection);
final rects = <Rect>[];
for (final node in nodes) {
final selectable = node.selectable;
if (selectable == null) {
continue;
}
final nodeRects = selectable.getRectsInSelection(selection);
if (nodeRects.isEmpty) {
continue;
}
final renderBox = node.renderBox;
if (renderBox == null) {
continue;

if (selection.isCollapsed && nodes.length == 1) {
final selectable = nodes.first.selectable;
if (selectable != null) {
final rect = selectable.getCursorRectInPosition(selection.end);
if (rect != null) {
rects.add(selectable.transformRectToGlobal(rect));
}
}
for (final rect in nodeRects) {
final globalOffset = renderBox.localToGlobal(rect.topLeft);
rects.add(globalOffset & rect.size);
} else {
for (final node in nodes) {
final selectable = node.selectable;
if (selectable == null) {
continue;
}
final nodeRects = selectable.getRectsInSelection(selection);
if (nodeRects.isEmpty) {
continue;
}
final renderBox = node.renderBox;
if (renderBox == null) {
continue;
}
for (final rect in nodeRects) {
final globalOffset = renderBox.localToGlobal(rect.topLeft);
rects.add(globalOffset & rect.size);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/extensions/position_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ extension PositionExtension on Position {
bool upwards = true,
}) {
final selection = editorState.selection;
final rects = editorState.service.selectionService.selectionRects;
final rects = editorState.selectionRects();
if (rects.isEmpty || selection == null) {
return null;
}
Expand Down
5 changes: 5 additions & 0 deletions lib/src/render/selection/selectable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,9 @@ mixin SelectableMixin<T extends StatefulWidget> on State<T> {
bool get shouldCursorBlink => true;

CursorStyle get cursorStyle => CursorStyle.verticalLine;

Rect transformRectToGlobal(Rect r) {
final topLeft = localToGlobal(r.topLeft);
return Rect.fromLTWH(topLeft.dx, topLeft.dy, r.width, r.height);
}
}

0 comments on commit 29ee7a3

Please sign in to comment.