| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- import 'dart:math' as math;
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/rendering.dart';
- import '../model/document/node/line.dart';
- import '../model/document/node/container.dart' as container_node;
- import '../service/cursor.dart';
- import '../widget/selection.dart';
- import '../widget/text_line.dart';
- import 'box.dart';
- enum TextLineSlot {
- LEADING,
- BODY,
- }
- /* ------------------------------- Render Box ------------------------------- */
- class RenderEditableTextLine extends RenderEditableBox {
- RenderEditableTextLine(
- this._line,
- this._textDirection,
- this._textSelection,
- this._enableInteractiveSelection,
- this.hasFocus,
- this._devicePixelRatio,
- this._padding,
- this._color,
- this._cursorController,
- );
- RenderBox? _leading;
- RenderContentProxyBox? _body;
- Line _line;
- TextDirection _textDirection;
- TextSelection _textSelection;
- Color _color;
- bool _enableInteractiveSelection;
- bool hasFocus = false;
- double _devicePixelRatio;
- EdgeInsetsGeometry _padding;
- CursorController _cursorController;
- EdgeInsets? _resolvedPadding;
- bool? _containesCursor;
- List<TextBox>? _selectedRects;
- Rect? _caretPrototype;
- final Map<TextLineSlot, RenderBox> children = {};
- // Getter && Setter
- Iterable<RenderBox> get _children sync* {
- if (_leading != null) {
- yield _leading!;
- }
- if (_body != null) {
- yield _body!;
- }
- }
- CursorController get cursorController => _cursorController;
- set cursorController(CursorController value) {
- if (_cursorController == value) {
- return;
- }
- _cursorController = value;
- markNeedsLayout();
- }
- double get devicePixelRatio => _devicePixelRatio;
- set devicePixelRatio(double value) {
- if (_devicePixelRatio == value) {
- return;
- }
- _devicePixelRatio = value;
- markNeedsLayout();
- }
- bool get enableInteractiveSelection => _enableInteractiveSelection;
- set enableInteractiveSelection(bool value) {
- if (_enableInteractiveSelection == value) {
- return;
- }
- _enableInteractiveSelection = value;
- markNeedsLayout();
- markNeedsSemanticsUpdate();
- }
- Color get color => _color;
- set color(Color value) {
- if (_color == value) {
- return;
- }
- _color = value;
- if (containsTextSelection()) {
- markNeedsPaint();
- }
- }
- TextDirection get textDirection => _textDirection;
- set textDirection(TextDirection value) {
- if (_textDirection == value) {
- return;
- }
- _textDirection = value;
- _resolvedPadding = null;
- markNeedsLayout();
- }
- TextSelection get textSelection => _textSelection;
- set textSelection(TextSelection value) {
- if (_textSelection == value) {
- return;
- }
- final containsSelection = containsTextSelection();
- if (attached && containsCursor()) {
- cursorController.removeListener(markNeedsLayout);
- cursorController.color.removeListener(markNeedsPaint);
- }
- _textSelection = value;
- _selectedRects = null;
- _containesCursor = null;
- if (attached && containsCursor()) {
- cursorController.addListener(markNeedsLayout);
- cursorController.color.addListener(markNeedsPaint);
- }
- if (containsSelection || containsTextSelection()) {
- markNeedsPaint();
- }
- }
- Line get line => _line;
- set line(Line value) {
- if (_line == value) {
- return;
- }
- _line = value;
- _containesCursor = null;
- markNeedsLayout();
- }
- EdgeInsetsGeometry get padding => _padding;
- set padding(EdgeInsetsGeometry value) {
- assert(value.isNonNegative);
- if (_padding == value) {
- return;
- }
- _padding = value;
- _resolvedPadding = null;
- markNeedsLayout();
- }
- RenderBox? get leading => _leading;
- set leading(RenderBox? value) {
- _leading = _updateChild(_leading, value, TextLineSlot.LEADING);
- }
- RenderContentProxyBox? get body => _body;
- set body(RenderContentProxyBox? value) {
- _body =
- _updateChild(_body, value, TextLineSlot.BODY) as RenderContentProxyBox?;
- }
- // Util
- bool containsTextSelection() {
- return line.documentOffset <= textSelection.end &&
- textSelection.start <= line.documentOffset + line.length - 1;
- }
- bool containsCursor() {
- return _containesCursor ??= textSelection.isCollapsed &&
- line.containsOffset(textSelection.baseOffset);
- }
- RenderBox? _updateChild(
- RenderBox? oldChild, RenderBox? newChild, TextLineSlot slot) {
- if (oldChild != null) {
- dropChild(oldChild);
- children.remove(slot);
- }
- if (newChild != null) {
- children[slot] = newChild;
- adoptChild(newChild);
- }
- return newChild;
- }
- List<TextBox> _getBoxes(TextSelection textSelection) {
- final parentData = _body!.parentData as BoxParentData?;
- return _body!.getBoxesForSelection(textSelection).map((box) {
- return TextBox.fromLTRBD(
- box.left + parentData!.offset.dx,
- box.top + parentData.offset.dy,
- box.right + parentData.offset.dx,
- box.bottom + parentData.offset.dy,
- box.direction,
- );
- }).toList(growable: false);
- }
- void _resolvePadding() {
- if (_resolvedPadding != null) {
- return;
- }
- _resolvedPadding = padding.resolve(textDirection);
- assert(_resolvedPadding!.isNonNegative);
- }
- // System Override: Selection && Cursor
- @override
- TextSelectionPoint getBaseEndpointForSelection(TextSelection textSelection) {
- return _getEndpointForSelection(textSelection, true);
- }
- @override
- TextSelectionPoint getExtentEndpointForSelection(
- TextSelection textSelection) {
- return _getEndpointForSelection(textSelection, false);
- }
- TextSelectionPoint _getEndpointForSelection(
- TextSelection textSelection, bool first) {
- if (textSelection.isCollapsed) {
- return TextSelectionPoint(
- Offset(0, preferredLineHeight(textSelection.extent)) +
- getOffsetForCaret(textSelection.extent),
- null,
- );
- }
- final boxes = _getBoxes(textSelection);
- assert(boxes.isNotEmpty);
- final targetBox = first ? boxes.first : boxes.last;
- return TextSelectionPoint(
- Offset(first ? targetBox.start : targetBox.end, targetBox.bottom),
- targetBox.direction,
- );
- }
- @override
- TextRange getLineBoundary(TextPosition position) {
- final lineDy = getOffsetForCaret(position)
- .translate(0, 0.5 * preferredLineHeight(position))
- .dy;
- final lineBoxes = _getBoxes(TextSelection(
- baseOffset: 0,
- extentOffset: line.length - 1,
- ))
- .where((element) => element.top < lineDy && element.bottom > lineDy)
- .toList(growable: false);
- return TextRange(
- start: getPositionForOffset(Offset(lineBoxes.first.left, lineDy)).offset,
- end: getPositionForOffset(Offset(lineBoxes.last.right, lineDy)).offset,
- );
- }
- @override
- Offset getOffsetForCaret(TextPosition position) {
- return _body!.getOffsetForCaret(position, _caretPrototype) +
- (body!.parentData as BoxParentData).offset;
- }
- @override
- TextPosition? getPositionAbove(TextPosition position) {
- return _getPosition(position, -0.5);
- }
- @override
- TextPosition? getPositionBelow(TextPosition position) {
- return _getPosition(position, 1.5);
- }
- TextPosition? _getPosition(TextPosition textPosition, double dyScale) {
- assert(textPosition.offset < line.length);
- final offset = getOffsetForCaret(textPosition)
- .translate(0, dyScale * preferredLineHeight(textPosition));
- if (_body!.size
- .contains(offset - (_body!.parentData as BoxParentData).offset)) {
- return getPositionForOffset(offset);
- }
- return null;
- }
- @override
- TextPosition getPositionForOffset(Offset offset) {
- return _body!.getPositionForOffset(
- offset - (_body!.parentData as BoxParentData).offset);
- }
- @override
- TextRange getWordBoundary(TextPosition position) {
- return _body!.getWordBoundary(position);
- }
- @override
- double preferredLineHeight(TextPosition position) {
- return _body!.getPreferredLineHeight();
- }
- @override
- container_node.Container get container => line;
- double get cursorWidth => cursorController.style.width;
- double get cursorHeight =>
- cursorController.style.height ??
- preferredLineHeight(const TextPosition(offset: 0));
- void _computeCaretPrototype() {
- switch (defaultTargetPlatform) {
- case TargetPlatform.iOS:
- case TargetPlatform.macOS:
- _caretPrototype = Rect.fromLTWH(0, 0, cursorWidth, cursorHeight + 2);
- break;
- case TargetPlatform.android:
- case TargetPlatform.fuchsia:
- case TargetPlatform.linux:
- case TargetPlatform.windows:
- _caretPrototype = Rect.fromLTWH(0, 2, cursorWidth, cursorHeight - 4.0);
- break;
- default:
- throw 'Invalid platform';
- }
- }
- @override
- void attach(covariant PipelineOwner owner) {
- super.attach(owner);
- for (final child in _children) {
- child.attach(owner);
- }
- if (containsCursor()) {
- cursorController.addListener(markNeedsLayout);
- cursorController.cursorColor.addListener(markNeedsPaint);
- }
- }
- @override
- void detach() {
- super.detach();
- for (final child in _children) {
- child.detach();
- }
- if (containsCursor()) {
- cursorController.removeListener(markNeedsLayout);
- cursorController.cursorColor.removeListener(markNeedsPaint);
- }
- }
- @override
- void redepthChildren() {
- _children.forEach(redepthChild);
- }
- @override
- void visitChildren(RenderObjectVisitor visitor) {
- _children.forEach(visitor);
- }
- @override
- List<DiagnosticsNode> debugDescribeChildren() {
- final 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();
- final horizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
- final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
- final leadingWidth = _leading == null
- ? 0
- : _leading!.getMinIntrinsicWidth(height - verticalPadding) as int;
- final bodyWidth = _body == null
- ? 0
- : _body!.getMinIntrinsicWidth(math.max(0, height - verticalPadding))
- as int;
- return horizontalPadding + leadingWidth + bodyWidth;
- }
- @override
- double computeMaxIntrinsicWidth(double height) {
- _resolvePadding();
- final horizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
- final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
- final leadingWidth = _leading == null
- ? 0
- : _leading!.getMaxIntrinsicWidth(height - verticalPadding) as int;
- final bodyWidth = _body == null
- ? 0
- : _body!.getMaxIntrinsicWidth(math.max(0, height - verticalPadding))
- as int;
- return horizontalPadding + leadingWidth + bodyWidth;
- }
- @override
- double computeMinIntrinsicHeight(double width) {
- _resolvePadding();
- final horizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
- final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
- if (_body != null) {
- return _body!
- .getMinIntrinsicHeight(math.max(0, width - horizontalPadding)) +
- verticalPadding;
- }
- return verticalPadding;
- }
- @override
- double computeMaxIntrinsicHeight(double width) {
- _resolvePadding();
- final horizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
- final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
- if (_body != null) {
- return _body!
- .getMaxIntrinsicHeight(math.max(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);
- (_body!.parentData as BoxParentData).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);
- (_leading!.parentData as BoxParentData).offset =
- Offset(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,
- cursorController.style,
- _caretPrototype,
- cursorController.cursorColor.value,
- devicePixelRatio,
- );
- @override
- void 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 &&
- line.documentOffset <= textSelection.end &&
- textSelection.start <= line.documentOffset + line.length - 1) {
- final local = localSelection(line, textSelection, false);
- _selectedRects ??= _body!.getBoxesForSelection(local);
- _paintSelection(context, effectiveOffset);
- }
- if (hasFocus &&
- cursorController.show.value &&
- containsCursor() &&
- !cursorController.style.paintAboveText) {
- _paintCursor(context, effectiveOffset);
- }
- context.paintChild(_body!, effectiveOffset);
- if (hasFocus &&
- cursorController.show.value &&
- containsCursor() &&
- cursorController.style.paintAboveText) {
- _paintCursor(context, effectiveOffset);
- }
- }
- }
- void _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);
- }
- }
- void _paintCursor(PaintingContext context, Offset effectiveOffset) {
- final position = TextPosition(
- offset: textSelection.extentOffset - line.documentOffset,
- affinity: textSelection.base.affinity,
- );
- _cursorPainter.paint(context.canvas, effectiveOffset, position);
- }
- @override
- bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
- return _children.first.hitTest(result, position: position);
- }
- }
- /* --------------------------------- Element -------------------------------- */
- class TextLineElement extends RenderObjectElement {
- TextLineElement(EditableTextLine line) : super(line);
- final Map<TextLineSlot, Element> _slotToChildren = <TextLineSlot, Element>{};
- @override
- EditableTextLine get widget => super.widget as EditableTextLine;
- @override
- RenderEditableTextLine get renderObject =>
- super.renderObject as RenderEditableTextLine;
- @override
- void visitChildren(ElementVisitor visitor) {
- _slotToChildren.values.forEach(visitor);
- }
- @override
- void forgetChild(Element child) {
- assert(_slotToChildren.containsValue(child));
- assert(child.slot is TextLineSlot);
- assert(_slotToChildren.containsKey(child.slot));
- _slotToChildren.remove(child.slot);
- super.forgetChild(child);
- }
- @override
- void mount(Element? parent, dynamic newSlot) {
- super.mount(parent, newSlot);
- _mountChild(widget.leading, TextLineSlot.LEADING);
- _mountChild(widget.body, TextLineSlot.BODY);
- }
- @override
- void update(covariant EditableTextLine newWidget) {
- super.update(newWidget);
- assert(widget == newWidget);
- _updateChild(widget.leading, TextLineSlot.LEADING);
- _updateChild(widget.body, TextLineSlot.BODY);
- }
- @override
- void insertRenderObjectChild(RenderBox child, TextLineSlot? slot) {
- _updateRenderObject(child, slot);
- assert(renderObject.children.keys.contains(slot));
- }
- @override
- void removeRenderObjectChild(RenderObject child, TextLineSlot? slot) {
- assert(child is RenderBox);
- assert(renderObject.children[slot!] == child);
- _updateRenderObject(null, slot);
- assert(!renderObject.children.keys.contains(slot));
- }
- @override
- void moveRenderObjectChild(
- RenderObject child, dynamic oldSlot, dynamic newSlot) {
- throw UnimplementedError();
- }
- void _mountChild(Widget? widget, TextLineSlot slot) {
- final oldChild = _slotToChildren[slot];
- final newChild = updateChild(oldChild, widget, slot);
- if (oldChild != null) {
- _slotToChildren.remove(slot);
- }
- if (newChild != null) {
- _slotToChildren[slot] = newChild;
- }
- }
- void _updateRenderObject(RenderBox? child, TextLineSlot? slot) {
- switch (slot) {
- case TextLineSlot.LEADING:
- renderObject.leading = child;
- break;
- case TextLineSlot.BODY:
- renderObject.body = child as RenderContentProxyBox?;
- break;
- default:
- throw UnimplementedError();
- }
- }
- void _updateChild(Widget? widget, TextLineSlot slot) {
- final oldChild = _slotToChildren[slot];
- final newChild = updateChild(oldChild, widget, slot);
- if (oldChild != null) {
- _slotToChildren.remove(slot);
- }
- if (newChild != null) {
- _slotToChildren[slot] = newChild;
- }
- }
- }
|