Implement class RenderEditableTextLine

pull/13/head
singerdmx 4 years ago
parent 617acaa363
commit 5dcada3307
  1. 1
      lib/models/documents/nodes/leaf.dart
  2. 270
      lib/widgets/editor.dart
  3. 2
      lib/widgets/text_block.dart
  4. 255
      lib/widgets/text_line.dart
  5. 135
      lib/widgets/text_selection.dart

@ -201,7 +201,6 @@ class Embed extends Leaf {
@override @override
Node newInstance() { Node newInstance() {
// TODO: implement newInstance
throw UnimplementedError(); throw UnimplementedError();
} }

@ -2,12 +2,17 @@ import 'dart:math' as math;
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_quill/models/documents/attribute.dart';
import 'package:flutter_quill/models/documents/document.dart'; import 'package:flutter_quill/models/documents/document.dart';
import 'package:flutter_quill/models/documents/nodes/container.dart' import 'package:flutter_quill/models/documents/nodes/container.dart'
as containerNode; as containerNode;
import 'package:flutter_quill/models/documents/nodes/leaf.dart';
import 'package:flutter_quill/models/documents/nodes/line.dart';
import 'package:flutter_quill/models/documents/nodes/node.dart'; import 'package:flutter_quill/models/documents/nodes/node.dart';
import 'package:flutter_quill/utils/diff_delta.dart'; import 'package:flutter_quill/utils/diff_delta.dart';
import 'package:flutter_quill/widgets/default_styles.dart'; import 'package:flutter_quill/widgets/default_styles.dart';
@ -224,6 +229,10 @@ class _QuillEditorState extends State<QuillEditor>
bool getSelectionEnabled() { bool getSelectionEnabled() {
return widget.enableInteractiveSelection; return widget.enableInteractiveSelection;
} }
_requestKeyboard() {
_editorKey.currentState.requestKeyboard();
}
} }
class _QuillEditorSelectionGestureDetectorBuilder class _QuillEditorSelectionGestureDetectorBuilder
@ -235,6 +244,124 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override @override
onForcePressStart(ForcePressDetails details) { onForcePressStart(ForcePressDetails details) {
super.onForcePressStart(details); super.onForcePressStart(details);
if (delegate.getSelectionEnabled() && shouldShowSelectionToolbar) {
getEditor().showToolbar();
}
}
@override
onForcePressEnd(ForcePressDetails details) {}
@override
void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
if (!delegate.getSelectionEnabled()) {
return;
}
switch (Theme.of(_state.context).platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
getRenderEditor().selectPositionAt(
details.globalPosition,
null,
SelectionChangedCause.longPress,
);
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
getRenderEditor().selectWordsInRange(
details.globalPosition - details.offsetFromOrigin,
details.globalPosition,
SelectionChangedCause.longPress,
);
break;
default:
throw ('Invalid platform');
}
}
_launchUrlIfNeeded(TapUpDetails details) {
TextPosition pos =
getRenderEditor().getPositionForOffset(details.globalPosition);
containerNode.ChildQuery result =
getEditor().widget.controller.document.queryChild(pos.offset);
if (result.node == null) {
return;
}
Line line = result.node as Line;
containerNode.ChildQuery segmentResult =
line.queryChild(result.offset, false);
if (segmentResult.node == null) {
return;
}
Leaf segment = segmentResult.node as Leaf;
if (segment.style.containsKey(Attribute.link.key) &&
getEditor().widget.onLaunchUrl != null) {
if (getEditor().widget.readOnly) {
getEditor()
.widget
.onLaunchUrl(segment.style.attributes[Attribute.link.key].value);
}
}
}
@override
onSingleTapUp(TapUpDetails details) {
getEditor().hideToolbar();
_launchUrlIfNeeded(details);
if (delegate.getSelectionEnabled()) {
switch (Theme.of(_state.context).platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
switch (details.kind) {
case PointerDeviceKind.mouse:
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
getRenderEditor().selectPosition(SelectionChangedCause.tap);
break;
case PointerDeviceKind.touch:
case PointerDeviceKind.unknown:
getRenderEditor().selectWordEdge(SelectionChangedCause.tap);
break;
}
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
getRenderEditor().selectPosition(SelectionChangedCause.tap);
break;
}
}
_state._requestKeyboard();
}
@override
void onSingleLongTapStart(LongPressStartDetails details) {
if (delegate.getSelectionEnabled()) {
switch (Theme.of(_state.context).platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
getRenderEditor().selectPositionAt(
details.globalPosition,
null,
SelectionChangedCause.longPress,
);
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
getRenderEditor().selectWord(SelectionChangedCause.longPress);
Feedback.forLongPress(_state.context);
break;
default:
throw ('Invalid platform');
}
}
} }
} }
@ -329,6 +456,9 @@ class RawEditorState extends EditorState
bool _didAutoFocus = false; bool _didAutoFocus = false;
DefaultStyles _styles; DefaultStyles _styles;
final ClipboardStatusNotifier _clipboardStatus = ClipboardStatusNotifier(); final ClipboardStatusNotifier _clipboardStatus = ClipboardStatusNotifier();
final LayerLink _toolbarLayerLink = LayerLink();
final LayerLink _startHandleLayerLink = LayerLink();
final LayerLink _endHandleLayerLink = LayerLink();
bool get _hasFocus => widget.focusNode.hasFocus; bool get _hasFocus => widget.focusNode.hasFocus;
@ -702,6 +832,7 @@ class RawEditorState extends EditorState
_focusAttachment.reparent(); _focusAttachment.reparent();
super.build(context); super.build(context);
// TODO
throw UnimplementedError(); throw UnimplementedError();
} }
@ -856,7 +987,7 @@ class RawEditorState extends EditorState
textEditingValue = TextEditingValue( textEditingValue = TextEditingValue(
text: text:
selection.textBefore(plainText) + selection.textAfter(plainText), selection.textBefore(plainText) + selection.textAfter(plainText),
selection: TextSelection.collapsed(offset: selection.start), selection: TextSelection.collapsed(offset: selection.start),
); );
} }
@ -906,18 +1037,102 @@ class RawEditorState extends EditorState
} }
_didChangeTextEditingValue() { _didChangeTextEditingValue() {
// TODO requestKeyboard();
_showCaretOnScreen();
updateRemoteValueIfNeeded();
_cursorCont.startOrStopCursorTimerIfNeeded(
_hasFocus, widget.controller.selection);
if (hasConnection) {
_cursorCont.stopCursorTimer(resetCharTicks: false);
_cursorCont.startCursorTimer();
}
SchedulerBinding.instance.addPostFrameCallback(
(Duration _) => _updateOrDisposeSelectionOverlayIfNeeded());
} }
_handleFocusChanged() { _updateOrDisposeSelectionOverlayIfNeeded() {
// TODO if (_selectionOverlay != null) {
if (_hasFocus) {
_selectionOverlay.update(textEditingValue);
} else {
_selectionOverlay.dispose();
_selectionOverlay = null;
}
} else if (_hasFocus) {
_selectionOverlay?.hide();
_selectionOverlay = null;
if (widget.selectionCtrls != null) {
_selectionOverlay = EditorTextSelectionOverlay(
textEditingValue,
false,
context,
widget,
_toolbarLayerLink,
_startHandleLayerLink,
_endHandleLayerLink,
getRenderEditor(),
widget.selectionCtrls,
this,
DragStartBehavior.start,
null,
_clipboardStatus);
_selectionOverlay.handlesVisible = _shouldShowSelectionHandles();
_selectionOverlay.showHandles();
}
}
} }
_onChangedClipboardStatus() { _handleFocusChanged() {
// TODO openOrCloseConnection();
_cursorCont.startOrStopCursorTimerIfNeeded(
_hasFocus, widget.controller.selection);
_updateOrDisposeSelectionOverlayIfNeeded();
if (_hasFocus) {
WidgetsBinding.instance.addObserver(this);
_showCaretOnScreen();
} else {
WidgetsBinding.instance.removeObserver(this);
}
updateKeepAlive();
} }
_showCaretOnScreen() {} _onChangedClipboardStatus() {}
bool _showCaretOnScreenScheduled = false;
_showCaretOnScreen() {
if (!widget.showCursor || _showCaretOnScreenScheduled) {
return;
}
_showCaretOnScreenScheduled = true;
SchedulerBinding.instance.addPostFrameCallback((Duration _) {
_showCaretOnScreenScheduled = false;
final viewport = RenderAbstractViewport.of(getRenderEditor());
assert(viewport != null);
final editorOffset =
getRenderEditor().localToGlobal(Offset(0.0, 0.0), ancestor: viewport);
final offsetInViewport = _scrollController.offset + editorOffset.dy;
final offset = getRenderEditor().getOffsetToRevealCursor(
_scrollController.position.viewportDimension,
_scrollController.offset,
offsetInViewport,
);
if (offset != null) {
_scrollController.animateTo(
offset,
duration: Duration(milliseconds: 100),
curve: Curves.fastOutSlowIn,
);
}
});
}
@override @override
RenderEditor getRenderEditor() { RenderEditor getRenderEditor() {
@ -979,6 +1194,14 @@ class RawEditorState extends EditorState
@override @override
bool get wantKeepAlive => widget.focusNode.hasFocus; bool get wantKeepAlive => widget.focusNode.hasFocus;
openOrCloseConnection() {
if (widget.focusNode.hasFocus && widget.focusNode.consumeKeyboardToken()) {
openConnectionIfNeeded();
} else if (!widget.focusNode.hasFocus) {
closeConnectionIfNeeded();
}
}
} }
typedef TextSelectionChangedHandler = void Function( typedef TextSelectionChangedHandler = void Function(
@ -993,7 +1216,7 @@ class RenderEditor extends RenderEditableContainerBox
LayerLink _endHandleLayerLink; LayerLink _endHandleLayerLink;
TextSelectionChangedHandler onSelectionChanged; TextSelectionChangedHandler onSelectionChanged;
final ValueNotifier<bool> _selectionStartInViewport = final ValueNotifier<bool> _selectionStartInViewport =
ValueNotifier<bool>(true); ValueNotifier<bool>(true);
ValueListenable<bool> get selectionStartInViewport => ValueListenable<bool> get selectionStartInViewport =>
_selectionStartInViewport; _selectionStartInViewport;
@ -1001,7 +1224,8 @@ class RenderEditor extends RenderEditableContainerBox
ValueListenable<bool> get selectionEndInViewport => _selectionEndInViewport; ValueListenable<bool> get selectionEndInViewport => _selectionEndInViewport;
final ValueNotifier<bool> _selectionEndInViewport = ValueNotifier<bool>(true); final ValueNotifier<bool> _selectionEndInViewport = ValueNotifier<bool>(true);
RenderEditor(List<RenderEditableBox> children, RenderEditor(
List<RenderEditableBox> children,
TextDirection textDirection, TextDirection textDirection,
hasFocus, hasFocus,
EdgeInsetsGeometry padding, EdgeInsetsGeometry padding,
@ -1126,6 +1350,34 @@ class RenderEditor extends RenderEditableContainerBox
assert(cause != null && from != null); assert(cause != null && from != null);
// TODO: implement selectWordsInRange // TODO: implement selectWordsInRange
} }
getOffsetToRevealCursor(
double viewportHeight, double scrollOffset, double offsetInViewport) {
List<TextSelectionPoint> endpoints = getEndpointsForSelection(selection);
if (endpoints.length != 1) {
return null;
}
RenderEditableBox child = childAtPosition(selection.extent);
const kMargin = 8.0;
double caretTop = endpoints.single.point.dy -
child.preferredLineHeight(TextPosition(
offset:
selection.extentOffset - child.getContainer().getOffset())) -
kMargin +
offsetInViewport;
final caretBottom = endpoints.single.point.dy + kMargin + offsetInViewport;
double dy;
if (caretTop < scrollOffset) {
dy = caretTop;
} else if (caretBottom > scrollOffset + viewportHeight) {
dy = caretBottom - viewportHeight;
}
if (dy == null) {
return null;
}
return math.max(dy, 0.0);
}
} }
class EditableContainerParentData class EditableContainerParentData

@ -171,6 +171,8 @@ class EditableTextBlock extends StatelessWidget {
} else if (attrs.containsKey(Attribute.codeBlock.key)) { } else if (attrs.containsKey(Attribute.codeBlock.key)) {
lineSpacing = defaultStyles.code.lineSpacing; lineSpacing = defaultStyles.code.lineSpacing;
} }
top = lineSpacing.item1;
bottom = lineSpacing.item2;
} }
if (index == 1) { if (index == 1) {

@ -1,3 +1,6 @@
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_quill/models/documents/attribute.dart'; import 'package:flutter_quill/models/documents/attribute.dart';
@ -10,6 +13,7 @@ import 'package:flutter_quill/models/documents/nodes/node.dart';
import 'package:flutter_quill/models/documents/style.dart'; import 'package:flutter_quill/models/documents/style.dart';
import 'package:flutter_quill/widgets/cursor.dart'; import 'package:flutter_quill/widgets/cursor.dart';
import 'package:flutter_quill/widgets/proxy.dart'; import 'package:flutter_quill/widgets/proxy.dart';
import 'package:flutter_quill/widgets/text_selection.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'box.dart'; import 'box.dart';
@ -229,6 +233,15 @@ class RenderEditableTextLine extends RenderEditableBox {
assert(hasFocus != null), assert(hasFocus != null),
assert(cursorCont != null); assert(cursorCont != null);
Iterable<RenderBox> get _children sync* {
if (leading != null) {
yield leading;
}
if (body != null) {
yield body;
}
}
setCursorCont(CursorCont c) { setCursorCont(CursorCont c) {
assert(c != null); assert(c != null);
if (cursorCont == c) { if (cursorCont == c) {
@ -459,6 +472,248 @@ class RenderEditableTextLine extends RenderEditableBox {
container.Container getContainer() { container.Container getContainer() {
return line; return line;
} }
double get cursorWidth => cursorCont.style.width;
double get cursorHeight =>
cursorCont.style.height ?? preferredLineHeight(TextPosition(offset: 0));
_computeCaretPrototype() {
assert(defaultTargetPlatform != null);
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
_caretPrototype =
Rect.fromLTWH(0.0, 0.0, cursorWidth, cursorHeight + 2);
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
_caretPrototype =
Rect.fromLTWH(0.0, 2.0, cursorWidth, cursorHeight - 4.0);
break;
default:
throw ('Invalid platform');
}
}
@override
attach(covariant PipelineOwner owner) {
super.attach(owner);
for (final child in _children) {
child.attach(owner);
}
if (containsCursor()) {
cursorCont.addListener(markNeedsLayout);
cursorCont.cursorColor.addListener(markNeedsPaint);
}
}
@override
detach() {
super.detach();
for (RenderBox child in _children) {
child.detach();
}
if (containsCursor()) {
cursorCont.removeListener(markNeedsLayout);
cursorCont.cursorColor.removeListener(markNeedsPaint);
}
}
@override
redepthChildren() {
_children.forEach(redepthChild);
}
@override
visitChildren(RenderObjectVisitor visitor) {
_children.forEach(visitor);
}
@override
List<DiagnosticsNode> debugDescribeChildren() {
var value = <DiagnosticsNode>[];
void add(RenderBox child, String name) {
if (child != null) {
value.add(child.toDiagnosticsNode(name: name));
}
}
add(leading, 'leading');
add(body, 'body');
return value;
}
@override
bool get sizedByParent => false;
@override
double computeMinIntrinsicWidth(double height) {
_resolvePadding();
double horizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
double verticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
int leadingWidth = leading == null
? 0
: leading.getMinIntrinsicWidth(height - verticalPadding);
int bodyWidth = body == null
? 0
: body.getMinIntrinsicWidth(math.max(0.0, height - verticalPadding));
return horizontalPadding + leadingWidth + bodyWidth;
}
@override
double computeMaxIntrinsicWidth(double height) {
_resolvePadding();
double horizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
double verticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
int leadingWidth = leading == null
? 0
: leading.getMaxIntrinsicWidth(height - verticalPadding);
int bodyWidth = body == null
? 0
: body.getMaxIntrinsicWidth(math.max(0.0, height - verticalPadding));
return horizontalPadding + leadingWidth + bodyWidth;
}
@override
double computeMinIntrinsicHeight(double width) {
_resolvePadding();
double horizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
double verticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (body != null) {
return body
.getMinIntrinsicHeight(math.max(0.0, width - horizontalPadding)) +
verticalPadding;
}
return verticalPadding;
}
@override
double computeMaxIntrinsicHeight(double width) {
_resolvePadding();
double horizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
double verticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (body != null) {
return body
.getMaxIntrinsicHeight(math.max(0.0, width - horizontalPadding)) +
verticalPadding;
}
return verticalPadding;
}
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
_resolvePadding();
return body.getDistanceToActualBaseline(baseline) + _resolvedPadding.top;
}
@override
void performLayout() {
final constraints = this.constraints;
_selectedRects = null;
_resolvePadding();
assert(_resolvedPadding != null);
if (body == null && leading == null) {
size = constraints.constrain(Size(
_resolvedPadding.left + _resolvedPadding.right,
_resolvedPadding.top + _resolvedPadding.bottom,
));
return;
}
final innerConstraints = constraints.deflate(_resolvedPadding);
final indentWidth = textDirection == TextDirection.ltr
? _resolvedPadding.left
: _resolvedPadding.right;
body.layout(innerConstraints, parentUsesSize: true);
final bodyParentData = body.parentData as BoxParentData;
bodyParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);
if (leading != null) {
final leadingConstraints = innerConstraints.copyWith(
minWidth: indentWidth,
maxWidth: indentWidth,
maxHeight: body.size.height);
leading.layout(leadingConstraints, parentUsesSize: true);
final parentData = leading.parentData as BoxParentData;
parentData.offset = Offset(0.0, _resolvedPadding.top);
}
size = constraints.constrain(Size(
_resolvedPadding.left + body.size.width + _resolvedPadding.right,
_resolvedPadding.top + body.size.height + _resolvedPadding.bottom,
));
_computeCaretPrototype();
}
CursorPainter get _cursorPainter => CursorPainter(
body,
cursorCont.style,
_caretPrototype,
cursorCont.cursorColor.value,
devicePixelRatio,
);
@override
paint(PaintingContext context, Offset offset) {
if (leading != null) {
final parentData = leading.parentData as BoxParentData;
final effectiveOffset = offset + parentData.offset;
context.paintChild(leading, effectiveOffset);
}
if (body != null) {
final parentData = body.parentData as BoxParentData;
final effectiveOffset = offset + parentData.offset;
if ((enableInteractiveSelection ?? true) &&
line.getDocumentOffset() <= textSelection.end &&
textSelection.start <= line.getDocumentOffset() + line.length - 1) {
final local = localSelection(line, textSelection, false);
_selectedRects ??= body.getBoxesForSelection(
local,
);
_paintSelection(context, effectiveOffset);
}
if (hasFocus &&
cursorCont.show.value &&
containsCursor() &&
!cursorCont.style.paintAboveText) {
_paintCursor(context, effectiveOffset);
}
context.paintChild(body, effectiveOffset);
if (hasFocus &&
cursorCont.show.value &&
containsCursor() &&
cursorCont.style.paintAboveText) {
_paintCursor(context, effectiveOffset);
}
}
}
_paintSelection(PaintingContext context, Offset effectiveOffset) {
assert(_selectedRects != null);
final paint = Paint()..color = color;
for (final box in _selectedRects) {
context.canvas.drawRect(box.toRect().shift(effectiveOffset), paint);
}
}
_paintCursor(PaintingContext context, Offset effectiveOffset) {
final position = TextPosition(
offset: textSelection.extentOffset - line.getDocumentOffset(),
affinity: textSelection.base.affinity,
);
_cursorPainter.paint(context.canvas, effectiveOffset, position);
}
} }
class _TextLineElement extends RenderObjectElement { class _TextLineElement extends RenderObjectElement {

@ -41,7 +41,8 @@ class EditorTextSelectionOverlay {
List<OverlayEntry> _handles; List<OverlayEntry> _handles;
OverlayEntry toolbar; OverlayEntry toolbar;
EditorTextSelectionOverlay(this.value, EditorTextSelectionOverlay(
this.value,
this.handlesVisible, this.handlesVisible,
this.context, this.context,
this.debugRequiredFor, this.debugRequiredFor,
@ -107,10 +108,10 @@ class EditorTextSelectionOverlay {
_toolbarController.forward(from: 0.0); _toolbarController.forward(from: 0.0);
} }
Widget _buildHandle(BuildContext context, Widget _buildHandle(
_TextSelectionHandlePosition position) { BuildContext context, _TextSelectionHandlePosition position) {
if ((_selection.isCollapsed && if ((_selection.isCollapsed &&
position == _TextSelectionHandlePosition.END) || position == _TextSelectionHandlePosition.END) ||
selectionCtrls == null) { selectionCtrls == null) {
return Container(); return Container();
} }
@ -144,8 +145,8 @@ class EditorTextSelectionOverlay {
} }
} }
_handleSelectionHandleChanged(TextSelection newSelection, _handleSelectionHandleChanged(
_TextSelectionHandlePosition position) { TextSelection newSelection, _TextSelectionHandlePosition position) {
TextPosition textPosition; TextPosition textPosition;
switch (position) { switch (position) {
case _TextSelectionHandlePosition.START: case _TextSelectionHandlePosition.START:
@ -168,7 +169,7 @@ class EditorTextSelectionOverlay {
} }
List<TextSelectionPoint> endpoints = List<TextSelectionPoint> endpoints =
renderObject.getEndpointsForSelection(_selection); renderObject.getEndpointsForSelection(_selection);
Rect editingRegion = Rect.fromPoints( Rect editingRegion = Rect.fromPoints(
renderObject.localToGlobal(Offset.zero), renderObject.localToGlobal(Offset.zero),
@ -177,7 +178,7 @@ class EditorTextSelectionOverlay {
double baseLineHeight = renderObject.preferredLineHeight(_selection.base); double baseLineHeight = renderObject.preferredLineHeight(_selection.base);
double extentLineHeight = double extentLineHeight =
renderObject.preferredLineHeight(_selection.extent); renderObject.preferredLineHeight(_selection.extent);
double smallestLineHeight = math.min(baseLineHeight, extentLineHeight); double smallestLineHeight = math.min(baseLineHeight, extentLineHeight);
bool isMultiline = endpoints.last.point.dy - endpoints.first.point.dy > bool isMultiline = endpoints.last.point.dy - endpoints.first.point.dy >
smallestLineHeight / 2; smallestLineHeight / 2;
@ -233,6 +234,21 @@ class EditorTextSelectionOverlay {
hide(); hide();
_toolbarController.dispose(); _toolbarController.dispose();
} }
void showHandles() {
assert(_handles == null);
_handles = <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) =>
_buildHandle(context, _TextSelectionHandlePosition.START)),
OverlayEntry(
builder: (BuildContext context) =>
_buildHandle(context, _TextSelectionHandlePosition.END)),
];
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)
.insertAll(_handles);
}
} }
class _TextSelectionHandleOverlay extends StatefulWidget { class _TextSelectionHandleOverlay extends StatefulWidget {
@ -319,7 +335,7 @@ class _TextSelectionHandleOverlayState
_handleDragUpdate(DragUpdateDetails details) { _handleDragUpdate(DragUpdateDetails details) {
TextPosition position = TextPosition position =
widget.renderObject.getPositionForOffset(details.globalPosition); widget.renderObject.getPositionForOffset(details.globalPosition);
if (widget.selection.isCollapsed) { if (widget.selection.isCollapsed) {
widget.onSelectionHandleChanged(TextSelection.fromPosition(position)); widget.onSelectionHandleChanged(TextSelection.fromPosition(position));
return; return;
@ -332,17 +348,17 @@ class _TextSelectionHandleOverlayState
case _TextSelectionHandlePosition.START: case _TextSelectionHandlePosition.START:
newSelection = TextSelection( newSelection = TextSelection(
baseOffset: baseOffset:
isNormalized ? position.offset : widget.selection.baseOffset, isNormalized ? position.offset : widget.selection.baseOffset,
extentOffset: extentOffset:
isNormalized ? widget.selection.extentOffset : position.offset, isNormalized ? widget.selection.extentOffset : position.offset,
); );
break; break;
case _TextSelectionHandlePosition.END: case _TextSelectionHandlePosition.END:
newSelection = TextSelection( newSelection = TextSelection(
baseOffset: baseOffset:
isNormalized ? widget.selection.baseOffset : position.offset, isNormalized ? widget.selection.baseOffset : position.offset,
extentOffset: extentOffset:
isNormalized ? position.offset : widget.selection.extentOffset, isNormalized ? position.offset : widget.selection.extentOffset,
); );
break; break;
} }
@ -381,12 +397,12 @@ class _TextSelectionHandleOverlayState
} }
TextPosition textPosition = TextPosition textPosition =
widget.position == _TextSelectionHandlePosition.START widget.position == _TextSelectionHandlePosition.START
? widget.selection.base ? widget.selection.base
: widget.selection.extent; : widget.selection.extent;
double lineHeight = widget.renderObject.preferredLineHeight(textPosition); double lineHeight = widget.renderObject.preferredLineHeight(textPosition);
Offset handleAnchor = Offset handleAnchor =
widget.selectionControls.getHandleAnchor(type, lineHeight); widget.selectionControls.getHandleAnchor(type, lineHeight);
Size handleSize = widget.selectionControls.getHandleSize(lineHeight); Size handleSize = widget.selectionControls.getHandleSize(lineHeight);
Rect handleRect = Rect.fromLTWH( Rect handleRect = Rect.fromLTWH(
@ -442,9 +458,11 @@ class _TextSelectionHandleOverlayState
); );
} }
TextSelectionHandleType _chooseType(TextDirection textDirection, TextSelectionHandleType _chooseType(
TextSelectionHandleType ltrType, TextDirection textDirection,
TextSelectionHandleType rtlType,) { TextSelectionHandleType ltrType,
TextSelectionHandleType rtlType,
) {
if (widget.selection.isCollapsed) return TextSelectionHandleType.collapsed; if (widget.selection.isCollapsed) return TextSelectionHandleType.collapsed;
assert(textDirection != null); assert(textDirection != null);
@ -475,8 +493,7 @@ class EditorTextSelectionGestureDetector extends StatefulWidget {
this.onDragSelectionEnd, this.onDragSelectionEnd,
this.behavior, this.behavior,
@required this.child, @required this.child,
}) }) : assert(child != null),
: assert(child != null),
super(key: key); super(key: key);
final GestureTapDownCallback onTapDown; final GestureTapDownCallback onTapDown;
@ -651,34 +668,33 @@ class _EditorTextSelectionGestureDetectorState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = final Map<Type, GestureRecognizerFactory> gestures =
<Type, GestureRecognizerFactory>{}; <Type, GestureRecognizerFactory>{};
gestures[_TransparentTapGestureRecognizer] = gestures[_TransparentTapGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<_TransparentTapGestureRecognizer>( GestureRecognizerFactoryWithHandlers<_TransparentTapGestureRecognizer>(
() => _TransparentTapGestureRecognizer(debugOwner: this), () => _TransparentTapGestureRecognizer(debugOwner: this),
(_TransparentTapGestureRecognizer instance) { (_TransparentTapGestureRecognizer instance) {
instance instance
..onTapDown = _handleTapDown ..onTapDown = _handleTapDown
..onTapUp = _handleTapUp ..onTapUp = _handleTapUp
..onTapCancel = _handleTapCancel; ..onTapCancel = _handleTapCancel;
}, },
); );
if (widget.onSingleLongTapStart != null || if (widget.onSingleLongTapStart != null ||
widget.onSingleLongTapMoveUpdate != null || widget.onSingleLongTapMoveUpdate != null ||
widget.onSingleLongTapEnd != null) { widget.onSingleLongTapEnd != null) {
gestures[LongPressGestureRecognizer] = gestures[LongPressGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => () => LongPressGestureRecognizer(
LongPressGestureRecognizer( debugOwner: this, kind: PointerDeviceKind.touch),
debugOwner: this, kind: PointerDeviceKind.touch), (LongPressGestureRecognizer instance) {
(LongPressGestureRecognizer instance) { instance
instance ..onLongPressStart = _handleLongPressStart
..onLongPressStart = _handleLongPressStart ..onLongPressMoveUpdate = _handleLongPressMoveUpdate
..onLongPressMoveUpdate = _handleLongPressMoveUpdate ..onLongPressEnd = _handleLongPressEnd;
..onLongPressEnd = _handleLongPressEnd; },
}, );
);
} }
if (widget.onDragSelectionStart != null || if (widget.onDragSelectionStart != null ||
@ -686,32 +702,29 @@ class _EditorTextSelectionGestureDetectorState
widget.onDragSelectionEnd != null) { widget.onDragSelectionEnd != null) {
gestures[HorizontalDragGestureRecognizer] = gestures[HorizontalDragGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>( GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
() => () => HorizontalDragGestureRecognizer(
HorizontalDragGestureRecognizer( debugOwner: this, kind: PointerDeviceKind.mouse),
debugOwner: this, kind: PointerDeviceKind.mouse), (HorizontalDragGestureRecognizer instance) {
(HorizontalDragGestureRecognizer instance) { instance
instance ..dragStartBehavior = DragStartBehavior.down
..dragStartBehavior = DragStartBehavior.down ..onStart = _handleDragStart
..onStart = _handleDragStart ..onUpdate = _handleDragUpdate
..onUpdate = _handleDragUpdate ..onEnd = _handleDragEnd;
..onEnd = _handleDragEnd; },
}, );
);
} }
if (widget.onForcePressStart != null || widget.onForcePressEnd != null) { if (widget.onForcePressStart != null || widget.onForcePressEnd != null) {
gestures[ForcePressGestureRecognizer] = gestures[ForcePressGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>( GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
() => ForcePressGestureRecognizer(debugOwner: this), () => ForcePressGestureRecognizer(debugOwner: this),
(ForcePressGestureRecognizer instance) { (ForcePressGestureRecognizer instance) {
instance instance
..onStart = ..onStart =
widget.onForcePressStart != null ? _forcePressStarted : null widget.onForcePressStart != null ? _forcePressStarted : null
..onEnd = widget.onForcePressEnd != null ..onEnd = widget.onForcePressEnd != null ? _forcePressEnded : null;
? _forcePressEnded },
: null; );
},
);
} }
return RawGestureDetector( return RawGestureDetector(

Loading…
Cancel
Save