import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import '../model/document/node/block.dart'; import '../widget/selection.dart'; import 'box.dart'; import 'editor.dart'; class RenderEditableTextBlock extends RenderEditableContainerBox implements RenderEditableBox { RenderEditableTextBlock({ required Block block, required TextDirection textDirection, required EdgeInsetsGeometry padding, required double scrollBottomInset, required Decoration decoration, List? children, ImageConfiguration configuration = ImageConfiguration.empty, EdgeInsets contentPadding = EdgeInsets.zero, }) : _decoration = decoration, _configuration = configuration, _savePadding = padding, _contentPadding = contentPadding, super(children, textDirection, scrollBottomInset, block, padding.add(contentPadding)); EdgeInsetsGeometry _savePadding; EdgeInsets _contentPadding; set contentPadding(EdgeInsets value) { if (_contentPadding == value) { return; } _contentPadding = value; super.padding = _savePadding.add(_contentPadding); } @override set padding(EdgeInsetsGeometry value) { super.padding = value.add(_contentPadding); _savePadding = value; } BoxPainter? _painter; Decoration _decoration; Decoration get decoration => _decoration; set decoration(Decoration value) { if (_decoration == value) { return; } _painter?.dispose(); _painter = null; _decoration = value; markNeedsPaint(); } ImageConfiguration _configuration; ImageConfiguration get configuration => _configuration; set configuration(ImageConfiguration value) { if (_configuration == value) { return; } _configuration = value; markNeedsPaint(); } @override TextRange getLineBoundary(TextPosition position) { final child = childAtPosition(position); final rangeInChild = child.getLineBoundary(TextPosition( offset: position.offset - child.container.offset, affinity: position.affinity, )); return TextRange( start: rangeInChild.start + child.container.offset, end: rangeInChild.end + child.container.offset, ); } @override Offset getOffsetForCaret(TextPosition position) { final child = childAtPosition(position); return child.getOffsetForCaret(TextPosition( offset: position.offset - child.container.offset, affinity: position.affinity, )) + (child.parentData as BoxParentData).offset; } @override TextPosition getPositionForOffset(Offset offset) { final child = childAtOffset(offset)!; final parentData = child.parentData as BoxParentData; final localPosition = child.getPositionForOffset(offset - parentData.offset); return TextPosition( offset: localPosition.offset + child.container.offset, affinity: localPosition.affinity, ); } @override TextRange getWordBoundary(TextPosition position) { final child = childAtPosition(position); final nodeOffset = child.container.offset; final childWord = child .getWordBoundary(TextPosition(offset: position.offset - nodeOffset)); return TextRange( start: childWord.start + nodeOffset, end: childWord.end + nodeOffset, ); } @override TextPosition? getPositionAbove(TextPosition position) { assert(position.offset < container.length); final child = childAtPosition(position); final childLocalPosition = TextPosition(offset: position.offset - child.container.offset); final result = child.getPositionAbove(childLocalPosition); if (result != null) { return TextPosition(offset: result.offset + child.container.offset); } final sibling = childBefore(child); if (sibling == null) { return null; } final caretOffset = child.getOffsetForCaret(childLocalPosition); final testPosition = TextPosition(offset: sibling.container.length - 1); final testOffset = sibling.getOffsetForCaret(testPosition); final finalOffset = Offset(caretOffset.dx, testOffset.dy); return TextPosition( offset: sibling.container.offset + sibling.getPositionForOffset(finalOffset).offset, ); } @override TextPosition? getPositionBelow(TextPosition position) { assert(position.offset < container.length); final child = childAtPosition(position); final childLocalPosition = TextPosition(offset: position.offset - child.container.offset); final result = child.getPositionBelow(childLocalPosition); if (result != null) { return TextPosition(offset: result.offset + child.container.offset); } final sibling = childAfter(child); if (sibling == null) { return null; } final caretOffset = child.getOffsetForCaret(childLocalPosition); final testOffset = sibling.getOffsetForCaret(const TextPosition(offset: 0)); final finalOffset = Offset(caretOffset.dx, testOffset.dy); return TextPosition( offset: sibling.container.offset + sibling.getPositionForOffset(finalOffset).offset, ); } @override double preferredLineHeight(TextPosition position) { final child = childAtPosition(position); return child.preferredLineHeight( TextPosition(offset: position.offset - child.container.offset), ); } @override TextSelectionPoint getBaseEndpointForSelection(TextSelection selection) { if (selection.isCollapsed) { return TextSelectionPoint( Offset(0, preferredLineHeight(selection.extent)) + getOffsetForCaret(selection.extent), null, ); } final baseNode = container.queryChild(selection.start, false).node; var baseChild = firstChild; while (baseChild != null) { if (baseChild.container == baseNode) { break; } baseChild = childAfter(baseChild); } assert(baseChild != null); final basePoint = baseChild!.getBaseEndpointForSelection(localSelection( baseChild.container, selection, true, )); return TextSelectionPoint( basePoint.point + (baseChild.parentData as BoxParentData).offset, basePoint.direction, ); } @override TextSelectionPoint getExtentEndpointForSelection(TextSelection selection) { if (selection.isCollapsed) { return TextSelectionPoint( Offset(0, preferredLineHeight(selection.extent)) + getOffsetForCaret(selection.extent), null, ); } final extentNode = container.queryChild(selection.end, false).node; var extentChild = firstChild; while (extentChild != null) { if (extentChild.container == extentNode) { break; } extentChild = childAfter(extentChild); } assert(extentChild != null); final extentPoint = extentChild!.getExtentEndpointForSelection( localSelection(extentChild.container, selection, true), ); return TextSelectionPoint( extentPoint.point + (extentChild.parentData as BoxParentData).offset, extentPoint.direction, ); } @override void detach() { _painter?.dispose(); _painter = null; super.detach(); markNeedsPaint(); } @override void paint(PaintingContext context, Offset offset) { _paintDecoration(context, offset); defaultPaint(context, offset); } void _paintDecoration(PaintingContext context, Offset offset) { _painter ??= _decoration.createBoxPainter(markNeedsPaint); final decorationPadding = resolvedPadding! - _contentPadding; final filledConfiguration = configuration.copyWith(size: decorationPadding.deflateSize(size)); final debugSaveCount = context.canvas.getSaveCount(); final decorationOffset = offset.translate(decorationPadding.left, decorationPadding.top); _painter!.paint(context.canvas, decorationOffset, filledConfiguration); if (debugSaveCount != context.canvas.getSaveCount()) { throw '${_decoration.runtimeType} painter had mismatching save and restore calls.'; } if (decoration.isComplex) { context.setIsComplexHint(); } } @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { return defaultHitTestChildren(result, position: position); } }