iOS - floating cursor

pull/519/head
X Code 3 years ago
parent 2b8fc44a5d
commit 13d542680e
  1. 4
      lib/src/widgets/box.dart
  2. 11
      lib/src/widgets/cursor.dart
  3. 196
      lib/src/widgets/editor.dart
  4. 31
      lib/src/widgets/float_cursor.dart
  5. 43
      lib/src/widgets/raw_editor.dart
  6. 115
      lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart
  7. 48
      lib/src/widgets/simple_viewer.dart
  8. 10
      lib/src/widgets/text_block.dart
  9. 55
      lib/src/widgets/text_line.dart

@ -119,4 +119,8 @@ abstract class RenderEditableBox extends RenderBox {
/// Returns the [Rect] in local coordinates for the caret at the given text
/// position.
Rect getLocalRectForCaret(TextPosition position);
/// Returns the [Rect] of the caret prototype at the given text
/// position. [Rect] starts at origin.
Rect getCaretPrototype(TextPosition position);
}

@ -131,6 +131,17 @@ class CursorCont extends ChangeNotifier {
Timer? _cursorTimer;
bool _targetCursorVisibility = false;
final ValueNotifier<TextPosition?> _floatingCursorTextPosition =
ValueNotifier(null);
ValueNotifier<TextPosition?> get floatingCursorTextPosition =>
_floatingCursorTextPosition;
void setFloatingCursorTextPosition(TextPosition? position) =>
_floatingCursorTextPosition.value = position;
bool get isFloatingCursorActive => floatingCursorTextPosition.value != null;
CursorStyle _style;
CursorStyle get style => _style;
set style(CursorStyle value) {

@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:string_validator/string_validator.dart';
import 'package:url_launcher/url_launcher.dart';
@ -22,6 +23,7 @@ import 'controller.dart';
import 'cursor.dart';
import 'default_styles.dart';
import 'delegate.dart';
import 'float_cursor.dart';
import 'image.dart';
import 'raw_editor.dart';
import 'text_selection.dart';
@ -56,6 +58,10 @@ abstract class EditorState extends State<RawEditor> {
EditorTextSelectionOverlay? getSelectionOverlay();
/// Controls the floating cursor animation when it is released.
/// The floating cursor is animated to merge with the regular cursor.
AnimationController get floatingCursorResetController;
bool showToolbar();
void hideToolbar();
@ -88,9 +94,24 @@ abstract class RenderAbstractEditor {
/// selection that contains some text but whose ends meet in the middle).
TextPosition getPositionForOffset(Offset offset);
/// Returns the local coordinates of the endpoints of the given selection.
///
/// If the selection is collapsed (and therefore occupies a single point), the
/// returned list is of length one. Otherwise, the selection is not collapsed
/// and the returned list is of length two. In this case, however, the two
/// points might actually be co-located (e.g., because of a bidirectional
/// selection that contains some text but whose ends meet in the middle).
List<TextSelectionPoint> getEndpointsForSelection(
TextSelection textSelection);
/// Sets the screen position of the floating cursor and the text position
/// closest to the cursor.
/// `resetLerpValue` drives the size of the floating cursor.
/// See [EditorState.floatingCursorResetController].
void setFloatingCursor(FloatingCursorDragState dragState,
Offset lastBoundedOffset, TextPosition lastTextPosition,
{double? resetLerpValue});
/// If [ignorePointer] is false (the default) then this method is called by
/// the internal gesture recognizer's [TapGestureRecognizer.onTapDown]
/// callback.
@ -646,25 +667,39 @@ class _QuillEditorSelectionGestureDetectorBuilder
}
}
/// Signature for the callback that reports when the user changes the selection
/// (including the cursor location).
///
/// Used by [RenderEditor.onSelectionChanged].
typedef TextSelectionChangedHandler = void Function(
TextSelection selection, SelectionChangedCause cause);
// The padding applied to text field. Used to determine the bounds when
// moving the floating cursor.
const EdgeInsets _kFloatingCursorAddedMargin = EdgeInsets.fromLTRB(4, 4, 4, 5);
// The additional size on the x and y axis with which to expand the prototype
// cursor to render the floating cursor in pixels.
const EdgeInsets _kFloatingCaretSizeIncrease =
EdgeInsets.symmetric(horizontal: 0.5, vertical: 1);
class RenderEditor extends RenderEditableContainerBox
implements RenderAbstractEditor {
RenderEditor(
ViewportOffset? offset,
List<RenderEditableBox>? children,
TextDirection textDirection,
double scrollBottomInset,
EdgeInsetsGeometry padding,
this.document,
this.selection,
this._hasFocus,
this.onSelectionChanged,
this._startHandleLayerLink,
this._endHandleLayerLink,
EdgeInsets floatingCursorAddedMargin,
) : super(
ViewportOffset? offset,
List<RenderEditableBox>? children,
TextDirection textDirection,
double scrollBottomInset,
EdgeInsetsGeometry padding,
this.document,
this.selection,
this._hasFocus,
this.onSelectionChanged,
this._startHandleLayerLink,
this._endHandleLayerLink,
EdgeInsets floatingCursorAddedMargin,
this._cursorController)
: super(
children,
document.root,
textDirection,
@ -672,6 +707,8 @@ class RenderEditor extends RenderEditableContainerBox
padding,
);
final CursorCont _cursorController;
Document document;
TextSelection selection;
bool _hasFocus = false;
@ -983,9 +1020,20 @@ class RenderEditor extends RenderEditableContainerBox
@override
void paint(PaintingContext context, Offset offset) {
if (_hasFocus &&
_cursorController.show.value &&
!_cursorController.style.paintAboveText) {
_paintFloatingCursor(context, offset);
}
defaultPaint(context, offset);
_updateSelectionExtentsVisibility(offset + _paintOffset);
_paintHandleLayers(context, getEndpointsForSelection(selection));
if (_hasFocus &&
_cursorController.show.value &&
_cursorController.style.paintAboveText) {
_paintFloatingCursor(context, offset);
}
}
@override
@ -1097,6 +1145,128 @@ class RenderEditor extends RenderEditableContainerBox
final boxParentData = targetChild.parentData as BoxParentData;
return childLocalRect.shift(Offset(0, boxParentData.offset.dy));
}
// Start floating cursor
FloatingCursorPainter get _floatingCursorPainter => FloatingCursorPainter(
floatingCursorRect: _floatingCursorRect,
style: _cursorController.style,
);
bool _floatingCursorOn = false;
Rect? _floatingCursorRect;
TextPosition get floatingCursorTextPosition => _floatingCursorTextPosition;
late TextPosition _floatingCursorTextPosition;
// The relative origin in relation to the distance the user has theoretically
// dragged the floating cursor offscreen.
// This value is used to account for the difference
// in the rendering position and the raw offset value.
Offset _relativeOrigin = Offset.zero;
Offset? _previousOffset;
bool _resetOriginOnLeft = false;
bool _resetOriginOnRight = false;
bool _resetOriginOnTop = false;
bool _resetOriginOnBottom = false;
/// Returns the position within the editor closest to the raw cursor offset.
Offset calculateBoundedFloatingCursorOffset(
Offset rawCursorOffset, double preferredLineHeight) {
var deltaPosition = Offset.zero;
final topBound = _kFloatingCursorAddedMargin.top;
final bottomBound =
size.height - preferredLineHeight + _kFloatingCursorAddedMargin.bottom;
final leftBound = _kFloatingCursorAddedMargin.left;
final rightBound = size.width - _kFloatingCursorAddedMargin.right;
if (_previousOffset != null) {
deltaPosition = rawCursorOffset - _previousOffset!;
}
// If the raw cursor offset has gone off an edge,
// we want to reset the relative origin of
// the dragging when the user drags back into the field.
if (_resetOriginOnLeft && deltaPosition.dx > 0) {
_relativeOrigin =
Offset(rawCursorOffset.dx - leftBound, _relativeOrigin.dy);
_resetOriginOnLeft = false;
} else if (_resetOriginOnRight && deltaPosition.dx < 0) {
_relativeOrigin =
Offset(rawCursorOffset.dx - rightBound, _relativeOrigin.dy);
_resetOriginOnRight = false;
}
if (_resetOriginOnTop && deltaPosition.dy > 0) {
_relativeOrigin =
Offset(_relativeOrigin.dx, rawCursorOffset.dy - topBound);
_resetOriginOnTop = false;
} else if (_resetOriginOnBottom && deltaPosition.dy < 0) {
_relativeOrigin =
Offset(_relativeOrigin.dx, rawCursorOffset.dy - bottomBound);
_resetOriginOnBottom = false;
}
final currentX = rawCursorOffset.dx - _relativeOrigin.dx;
final currentY = rawCursorOffset.dy - _relativeOrigin.dy;
final double adjustedX =
math.min(math.max(currentX, leftBound), rightBound);
final double adjustedY =
math.min(math.max(currentY, topBound), bottomBound);
final adjustedOffset = Offset(adjustedX, adjustedY);
if (currentX < leftBound && deltaPosition.dx < 0) {
_resetOriginOnLeft = true;
} else if (currentX > rightBound && deltaPosition.dx > 0) {
_resetOriginOnRight = true;
}
if (currentY < topBound && deltaPosition.dy < 0) {
_resetOriginOnTop = true;
} else if (currentY > bottomBound && deltaPosition.dy > 0) {
_resetOriginOnBottom = true;
}
_previousOffset = rawCursorOffset;
return adjustedOffset;
}
@override
void setFloatingCursor(FloatingCursorDragState dragState,
Offset boundedOffset, TextPosition textPosition,
{double? resetLerpValue}) {
if (dragState == FloatingCursorDragState.Start) {
_relativeOrigin = Offset.zero;
_previousOffset = null;
_resetOriginOnBottom = false;
_resetOriginOnTop = false;
_resetOriginOnRight = false;
_resetOriginOnBottom = false;
}
_floatingCursorOn = dragState != FloatingCursorDragState.End;
if (_floatingCursorOn) {
_floatingCursorTextPosition = textPosition;
final sizeAdjustment = resetLerpValue != null
? EdgeInsets.lerp(
_kFloatingCaretSizeIncrease, EdgeInsets.zero, resetLerpValue)!
: _kFloatingCaretSizeIncrease;
final child = childAtPosition(textPosition);
final caretPrototype =
child.getCaretPrototype(child.globalToLocalPosition(textPosition));
_floatingCursorRect =
sizeAdjustment.inflateRect(caretPrototype).shift(boundedOffset);
_cursorController
.setFloatingCursorTextPosition(_floatingCursorTextPosition);
} else {
_floatingCursorRect = null;
_cursorController.setFloatingCursorTextPosition(null);
}
}
void _paintFloatingCursor(PaintingContext context, Offset offset) {
_floatingCursorPainter.paint(context.canvas);
}
// End floating cursor
}
class EditableContainerParentData

@ -0,0 +1,31 @@
// The corner radius of the floating cursor in pixels.
import 'dart:ui';
import '../../widgets/cursor.dart';
const Radius _kFloatingCaretRadius = Radius.circular(1);
/// Floating painter responsible for painting the floating cursor when
/// floating mode is activated
class FloatingCursorPainter {
FloatingCursorPainter({
required this.floatingCursorRect,
required this.style,
});
CursorStyle style;
Rect? floatingCursorRect;
final Paint floatingCursorPaint = Paint();
void paint(Canvas canvas) {
final floatingCursorRect = this.floatingCursorRect;
final floatingCursorColor = style.color.withOpacity(0.75);
if (floatingCursorRect == null) return;
canvas.drawRRect(
RRect.fromRectAndRadius(floatingCursorRect, _kFloatingCaretRadius),
floatingCursorPaint..color = floatingCursorColor,
);
}
}

@ -126,6 +126,7 @@ class RawEditorState extends EditorState
ScrollController get scrollController => _scrollController;
late ScrollController _scrollController;
// Cursors
late CursorCont _cursorCont;
// Focus
@ -133,6 +134,7 @@ class RawEditorState extends EditorState
FocusAttachment? _focusAttachment;
bool get _hasFocus => widget.focusNode.hasFocus;
// Theme
DefaultStyles? _styles;
final ClipboardStatusNotifier _clipboardStatus = ClipboardStatusNotifier();
@ -162,6 +164,7 @@ class RawEditorState extends EditorState
document: _doc,
selection: widget.controller.selection,
hasFocus: _hasFocus,
cursorController: _cursorCont,
textDirection: _textDirection,
startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink,
@ -196,6 +199,7 @@ class RawEditorState extends EditorState
onSelectionChanged: _handleSelectionChanged,
scrollBottomInset: widget.scrollBottomInset,
padding: widget.padding,
cursorController: _cursorCont,
children: _buildChildren(_doc, context),
),
),
@ -362,6 +366,11 @@ class RawEditorState extends EditorState
tickerProvider: this,
);
// Floating cursor
_floatingCursorResetController = AnimationController(vsync: this);
_floatingCursorResetController.addListener(onFloatingCursorResetTick);
// Keyboard
_keyboardListener = KeyboardEventHandler(
handleCursorMovement,
handleShortcut,
@ -727,6 +736,12 @@ class RawEditorState extends EditorState
@override
bool get readOnly => widget.readOnly;
@override
AnimationController get floatingCursorResetController =>
_floatingCursorResetController;
late AnimationController _floatingCursorResetController;
}
class _Editor extends MultiChildRenderObjectWidget {
@ -741,6 +756,7 @@ class _Editor extends MultiChildRenderObjectWidget {
required this.endHandleLayerLink,
required this.onSelectionChanged,
required this.scrollBottomInset,
required this.cursorController,
this.padding = EdgeInsets.zero,
this.offset,
}) : super(key: key, children: children);
@ -755,23 +771,24 @@ class _Editor extends MultiChildRenderObjectWidget {
final TextSelectionChangedHandler onSelectionChanged;
final double scrollBottomInset;
final EdgeInsetsGeometry padding;
final CursorCont cursorController;
@override
RenderEditor createRenderObject(BuildContext context) {
return RenderEditor(
offset,
null,
textDirection,
scrollBottomInset,
padding,
document,
selection,
hasFocus,
onSelectionChanged,
startHandleLayerLink,
endHandleLayerLink,
const EdgeInsets.fromLTRB(4, 4, 4, 5),
);
offset,
null,
textDirection,
scrollBottomInset,
padding,
document,
selection,
hasFocus,
onSelectionChanged,
startHandleLayerLink,
endHandleLayerLink,
const EdgeInsets.fromLTRB(4, 4, 4, 5),
cursorController);
}
@override

@ -1,3 +1,6 @@
import 'dart:ui';
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@ -185,9 +188,119 @@ mixin RawEditorStateTextInputClientMixin on EditorState
// no-op
}
// The time it takes for the floating cursor to snap to the text aligned
// cursor position after the user has finished placing it.
static const Duration _floatingCursorResetTime = Duration(milliseconds: 125);
// The original position of the caret on FloatingCursorDragState.start.
Rect? _startCaretRect;
// The most recent text position as determined by the location of the floating
// cursor.
TextPosition? _lastTextPosition;
// The offset of the floating cursor as determined from the start call.
Offset? _pointOffsetOrigin;
// The most recent position of the floating cursor.
Offset? _lastBoundedOffset;
// Because the center of the cursor is preferredLineHeight / 2 below the touch
// origin, but the touch origin is used to determine which line the cursor is
// on, we need this offset to correctly render and move the cursor.
Offset _floatingCursorOffset(TextPosition textPosition) =>
Offset(0, getRenderEditor()!.preferredLineHeight(textPosition) / 2);
@override
void updateFloatingCursor(RawFloatingCursorPoint point) {
throw UnimplementedError();
switch (point.state) {
case FloatingCursorDragState.Start:
if (floatingCursorResetController.isAnimating) {
floatingCursorResetController.stop();
onFloatingCursorResetTick();
}
// We want to send in points that are centered around a (0,0) origin, so
// we cache the position.
_pointOffsetOrigin = point.offset;
final currentTextPosition =
TextPosition(offset: getRenderEditor()!.selection.baseOffset);
_startCaretRect =
getRenderEditor()!.getLocalRectForCaret(currentTextPosition);
_lastBoundedOffset = _startCaretRect!.center -
_floatingCursorOffset(currentTextPosition);
_lastTextPosition = currentTextPosition;
getRenderEditor()!.setFloatingCursor(
point.state, _lastBoundedOffset!, _lastTextPosition!);
break;
case FloatingCursorDragState.Update:
assert(_lastTextPosition != null, 'Last text position was not set');
final floatingCursorOffset = _floatingCursorOffset(_lastTextPosition!);
final centeredPoint = point.offset! - _pointOffsetOrigin!;
final rawCursorOffset =
_startCaretRect!.center + centeredPoint - floatingCursorOffset;
final preferredLineHeight =
getRenderEditor()!.preferredLineHeight(_lastTextPosition!);
_lastBoundedOffset =
getRenderEditor()!.calculateBoundedFloatingCursorOffset(
rawCursorOffset,
preferredLineHeight,
);
_lastTextPosition = getRenderEditor()!.getPositionForOffset(
getRenderEditor()!
.localToGlobal(_lastBoundedOffset! + floatingCursorOffset));
getRenderEditor()!.setFloatingCursor(
point.state, _lastBoundedOffset!, _lastTextPosition!);
final newSelection = TextSelection.collapsed(
offset: _lastTextPosition!.offset,
affinity: _lastTextPosition!.affinity);
// Setting selection as floating cursor moves will have scroll view
// bring background cursor into view
getRenderEditor()!
.onSelectionChanged(newSelection, SelectionChangedCause.forcePress);
break;
case FloatingCursorDragState.End:
// We skip animation if no update has happened.
if (_lastTextPosition != null && _lastBoundedOffset != null) {
floatingCursorResetController
..value = 0.0
..animateTo(1,
duration: _floatingCursorResetTime, curve: Curves.decelerate);
}
break;
}
}
/// Specifies the floating cursor dimensions and position based
/// the animation controller value.
/// The floating cursor is resized
/// (see [RenderAbstractEditor.setFloatingCursor])
/// and repositioned (linear interpolation between position of floating cursor
/// and current position of background cursor)
void onFloatingCursorResetTick() {
final finalPosition =
getRenderEditor()!.getLocalRectForCaret(_lastTextPosition!).centerLeft -
_floatingCursorOffset(_lastTextPosition!);
if (floatingCursorResetController.isCompleted) {
getRenderEditor()!.setFloatingCursor(
FloatingCursorDragState.End, finalPosition, _lastTextPosition!);
_startCaretRect = null;
_lastTextPosition = null;
_pointOffsetOrigin = null;
_lastBoundedOffset = null;
} else {
final lerpValue = floatingCursorResetController.value;
final lerpX =
lerpDouble(_lastBoundedOffset!.dx, finalPosition.dx, lerpValue)!;
final lerpY =
lerpDouble(_lastBoundedOffset!.dy, finalPosition.dy, lerpValue)!;
getRenderEditor()!.setFloatingCursor(FloatingCursorDragState.Update,
Offset(lerpX, lerpY), _lastTextPosition!,
resetLerpValue: lerpValue);
}
}
@override

@ -150,15 +150,15 @@ class _QuillSimpleViewerState extends State<QuillSimpleViewer>
link: _toolbarLayerLink,
child: Semantics(
child: _SimpleViewer(
document: _doc,
textDirection: _textDirection,
startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _nullSelectionChanged,
scrollBottomInset: widget.scrollBottomInset,
padding: widget.padding,
children: _buildChildren(_doc, context),
),
document: _doc,
textDirection: _textDirection,
startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _nullSelectionChanged,
scrollBottomInset: widget.scrollBottomInset,
padding: widget.padding,
cursorController: _cursorCont,
children: _buildChildren(_doc, context)),
),
);
@ -315,6 +315,7 @@ class _SimpleViewer extends MultiChildRenderObjectWidget {
required this.endHandleLayerLink,
required this.onSelectionChanged,
required this.scrollBottomInset,
required this.cursorController,
this.offset,
this.padding = EdgeInsets.zero,
Key? key,
@ -328,24 +329,25 @@ class _SimpleViewer extends MultiChildRenderObjectWidget {
final TextSelectionChangedHandler onSelectionChanged;
final double scrollBottomInset;
final EdgeInsetsGeometry padding;
final CursorCont cursorController;
@override
RenderEditor createRenderObject(BuildContext context) {
return RenderEditor(
offset,
null,
textDirection,
scrollBottomInset,
padding,
document,
const TextSelection(baseOffset: 0, extentOffset: 0),
false,
// hasFocus,
onSelectionChanged,
startHandleLayerLink,
endHandleLayerLink,
const EdgeInsets.fromLTRB(4, 4, 4, 5),
);
offset,
null,
textDirection,
scrollBottomInset,
padding,
document,
const TextSelection(baseOffset: 0, extentOffset: 0),
false,
// hasFocus,
onSelectionChanged,
startHandleLayerLink,
endHandleLayerLink,
const EdgeInsets.fromLTRB(4, 4, 4, 5),
cursorController);
}
@override

@ -560,6 +560,16 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
affinity: position.affinity,
);
}
@override
Rect getCaretPrototype(TextPosition position) {
final child = childAtPosition(position);
final localPosition = TextPosition(
offset: position.offset - child.getContainer().offset,
affinity: position.affinity,
);
return child.getCaretPrototype(localPosition);
}
}
class _EditableBlock extends MultiChildRenderObjectWidget {

@ -387,7 +387,7 @@ class RenderEditableTextLine extends RenderEditableBox {
EdgeInsets? _resolvedPadding;
bool? _containsCursor;
List<TextBox>? _selectedRects;
Rect? _caretPrototype;
late Rect _caretPrototype;
final Map<TextLineSlot, RenderBox> children = <TextLineSlot, RenderBox>{};
Iterable<RenderBox> get _children sync* {
@ -501,8 +501,11 @@ class RenderEditableTextLine extends RenderEditableBox {
}
bool containsCursor() {
return _containsCursor ??= textSelection.isCollapsed &&
line.containsOffset(textSelection.baseOffset);
return _containsCursor ??= cursorCont.isFloatingCursorActive
? line
.containsOffset(cursorCont.floatingCursorTextPosition.value!.offset)
: textSelection.isCollapsed &&
line.containsOffset(textSelection.baseOffset);
}
RenderBox? _updateChild(
@ -638,6 +641,17 @@ class RenderEditableTextLine extends RenderEditableBox {
cursorCont.style.height ??
preferredLineHeight(const TextPosition(offset: 0));
// TODO: This is no longer producing the highest-fidelity caret
// heights for Android, especially when non-alphabetic languages
// are involved. The current implementation overrides the height set
// here with the full measured height of the text on Android which looks
// superior (subjectively and in terms of fidelity) in _paintCaret. We
// should rework this properly to once again match the platform. The constant
// _kCaretHeightOffset scales poorly for small font sizes.
//
/// On iOS, the cursor is taller than the cursor on Android. The height
/// of the cursor for iOS is approximate and obtained through an eyeball
/// comparison.
void _computeCaretPrototype() {
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
@ -655,12 +669,24 @@ class RenderEditableTextLine extends RenderEditableBox {
}
}
void _onFloatingCursorChange() {
_containsCursor = null;
markNeedsPaint();
}
// End caret implementation
//
// Start render box overrides
@override
void attach(covariant PipelineOwner owner) {
super.attach(owner);
for (final child in _children) {
child.attach(owner);
}
cursorCont.floatingCursorTextPosition.addListener(_onFloatingCursorChange);
if (containsCursor()) {
cursorCont.addListener(markNeedsLayout);
cursorCont.color.addListener(safeMarkNeedsPaint);
@ -673,6 +699,8 @@ class RenderEditableTextLine extends RenderEditableBox {
for (final child in _children) {
child.detach();
}
cursorCont.floatingCursorTextPosition
.removeListener(_onFloatingCursorChange);
if (containsCursor()) {
cursorCont.removeListener(markNeedsLayout);
cursorCont.color.removeListener(safeMarkNeedsPaint);
@ -817,8 +845,10 @@ class RenderEditableTextLine extends RenderEditableBox {
CursorPainter get _cursorPainter => CursorPainter(
editable: _body,
style: cursorCont.style,
prototype: _caretPrototype!,
color: cursorCont.color.value,
prototype: _caretPrototype,
color: cursorCont.isFloatingCursorActive
? cursorCont.style.backgroundColor
: cursorCont.color.value,
devicePixelRatio: devicePixelRatio,
);
@ -873,10 +903,14 @@ class RenderEditableTextLine extends RenderEditableBox {
void _paintCursor(
PaintingContext context, Offset effectiveOffset, bool lineHasEmbed) {
final position = TextPosition(
offset: textSelection.extentOffset - line.documentOffset,
affinity: textSelection.base.affinity,
);
final position = cursorCont.isFloatingCursorActive
? TextPosition(
offset: cursorCont.floatingCursorTextPosition.value!.offset -
line.documentOffset,
affinity: cursorCont.floatingCursorTextPosition.value!.affinity)
: TextPosition(
offset: textSelection.extentOffset - line.documentOffset,
affinity: textSelection.base.affinity);
_cursorPainter.paint(
context.canvas, effectiveOffset, position, lineHasEmbed);
}
@ -921,6 +955,9 @@ class RenderEditableTextLine extends RenderEditableBox {
}
markNeedsPaint();
}
@override
Rect getCaretPrototype(TextPosition position) => _caretPrototype;
}
class _TextLineElement extends RenderObjectElement {

Loading…
Cancel
Save