|
|
|
@ -12,9 +12,37 @@ import 'link.dart'; |
|
|
|
|
import 'text_line.dart'; |
|
|
|
|
import 'text_selection.dart'; |
|
|
|
|
|
|
|
|
|
const List<int> arabianRomanNumbers = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]; |
|
|
|
|
|
|
|
|
|
const List<String> romanNumbers = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']; |
|
|
|
|
const List<int> arabianRomanNumbers = [ |
|
|
|
|
1000, |
|
|
|
|
900, |
|
|
|
|
500, |
|
|
|
|
400, |
|
|
|
|
100, |
|
|
|
|
90, |
|
|
|
|
50, |
|
|
|
|
40, |
|
|
|
|
10, |
|
|
|
|
9, |
|
|
|
|
5, |
|
|
|
|
4, |
|
|
|
|
1 |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
const List<String> romanNumbers = [ |
|
|
|
|
'M', |
|
|
|
|
'CM', |
|
|
|
|
'D', |
|
|
|
|
'CD', |
|
|
|
|
'C', |
|
|
|
|
'XC', |
|
|
|
|
'L', |
|
|
|
|
'XL', |
|
|
|
|
'X', |
|
|
|
|
'IX', |
|
|
|
|
'V', |
|
|
|
|
'IV', |
|
|
|
|
'I' |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
class EditableTextBlock extends StatelessWidget { |
|
|
|
|
const EditableTextBlock( |
|
|
|
@ -74,7 +102,8 @@ class EditableTextBlock extends StatelessWidget { |
|
|
|
|
_buildChildren(context, indentLevelCounts)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
BoxDecoration? _getDecorationForBlock(Block node, DefaultStyles? defaultStyles) { |
|
|
|
|
BoxDecoration? _getDecorationForBlock( |
|
|
|
|
Block node, DefaultStyles? defaultStyles) { |
|
|
|
|
final attrs = block.style.attributes; |
|
|
|
|
if (attrs.containsKey(Attribute.blockQuote.key)) { |
|
|
|
|
return defaultStyles!.quote!.decoration; |
|
|
|
@ -85,7 +114,8 @@ class EditableTextBlock extends StatelessWidget { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
List<Widget> _buildChildren(BuildContext context, Map<int, int> indentLevelCounts) { |
|
|
|
|
List<Widget> _buildChildren( |
|
|
|
|
BuildContext context, Map<int, int> indentLevelCounts) { |
|
|
|
|
final defaultStyles = QuillStyles.getStyles(context, false); |
|
|
|
|
final count = block.children.length; |
|
|
|
|
final children = <Widget>[]; |
|
|
|
@ -120,7 +150,8 @@ class EditableTextBlock extends StatelessWidget { |
|
|
|
|
return children.toList(growable: false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Widget? _buildLeading(BuildContext context, Line line, int index, Map<int, int> indentLevelCounts, int count) { |
|
|
|
|
Widget? _buildLeading(BuildContext context, Line line, int index, |
|
|
|
|
Map<int, int> indentLevelCounts, int count) { |
|
|
|
|
final defaultStyles = QuillStyles.getStyles(context, false); |
|
|
|
|
final attrs = line.style.attributes; |
|
|
|
|
if (attrs[Attribute.list.key] == Attribute.ol) { |
|
|
|
@ -137,7 +168,8 @@ class EditableTextBlock extends StatelessWidget { |
|
|
|
|
|
|
|
|
|
if (attrs[Attribute.list.key] == Attribute.ul) { |
|
|
|
|
return QuillBulletPoint( |
|
|
|
|
style: defaultStyles!.leading!.style.copyWith(fontWeight: FontWeight.bold), |
|
|
|
|
style: |
|
|
|
|
defaultStyles!.leading!.style.copyWith(fontWeight: FontWeight.bold), |
|
|
|
|
width: 32, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
@ -167,7 +199,8 @@ class EditableTextBlock extends StatelessWidget { |
|
|
|
|
index: index, |
|
|
|
|
indentLevelCounts: indentLevelCounts, |
|
|
|
|
count: count, |
|
|
|
|
style: defaultStyles!.code!.style.copyWith(color: defaultStyles.code!.style.color!.withOpacity(0.4)), |
|
|
|
|
style: defaultStyles!.code!.style |
|
|
|
|
.copyWith(color: defaultStyles.code!.style.color!.withOpacity(0.4)), |
|
|
|
|
width: 32, |
|
|
|
|
attrs: attrs, |
|
|
|
|
padding: 16, |
|
|
|
@ -192,14 +225,16 @@ class EditableTextBlock extends StatelessWidget { |
|
|
|
|
|
|
|
|
|
var baseIndent = 0.0; |
|
|
|
|
|
|
|
|
|
if (attrs.containsKey(Attribute.list.key) || attrs.containsKey(Attribute.codeBlock.key)) { |
|
|
|
|
if (attrs.containsKey(Attribute.list.key) || |
|
|
|
|
attrs.containsKey(Attribute.codeBlock.key)) { |
|
|
|
|
baseIndent = 32.0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return baseIndent + extraIndent; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Tuple2 _getSpacingForLine(Line node, int index, int count, DefaultStyles? defaultStyles) { |
|
|
|
|
Tuple2 _getSpacingForLine( |
|
|
|
|
Line node, int index, int count, DefaultStyles? defaultStyles) { |
|
|
|
|
var top = 0.0, bottom = 0.0; |
|
|
|
|
|
|
|
|
|
final attrs = block.style.attributes; |
|
|
|
@ -250,7 +285,8 @@ class EditableTextBlock extends StatelessWidget { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class RenderEditableTextBlock extends RenderEditableContainerBox implements RenderEditableBox { |
|
|
|
|
class RenderEditableTextBlock extends RenderEditableContainerBox |
|
|
|
|
implements RenderEditableBox { |
|
|
|
|
RenderEditableTextBlock({ |
|
|
|
|
required Block block, |
|
|
|
|
required TextDirection textDirection, |
|
|
|
@ -336,7 +372,8 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
TextPosition getPositionForOffset(Offset offset) { |
|
|
|
|
final child = childAtOffset(offset)!; |
|
|
|
|
final parentData = child.parentData as BoxParentData; |
|
|
|
|
final localPosition = child.getPositionForOffset(offset - parentData.offset); |
|
|
|
|
final localPosition = |
|
|
|
|
child.getPositionForOffset(offset - parentData.offset); |
|
|
|
|
return TextPosition( |
|
|
|
|
offset: localPosition.offset + child.getContainer().offset, |
|
|
|
|
affinity: localPosition.affinity, |
|
|
|
@ -347,7 +384,8 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
TextRange getWordBoundary(TextPosition position) { |
|
|
|
|
final child = childAtPosition(position); |
|
|
|
|
final nodeOffset = child.getContainer().offset; |
|
|
|
|
final childWord = child.getWordBoundary(TextPosition(offset: position.offset - nodeOffset)); |
|
|
|
|
final childWord = child |
|
|
|
|
.getWordBoundary(TextPosition(offset: position.offset - nodeOffset)); |
|
|
|
|
return TextRange( |
|
|
|
|
start: childWord.start + nodeOffset, |
|
|
|
|
end: childWord.end + nodeOffset, |
|
|
|
@ -359,7 +397,8 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
assert(position.offset < getContainer().length); |
|
|
|
|
|
|
|
|
|
final child = childAtPosition(position); |
|
|
|
|
final childLocalPosition = TextPosition(offset: position.offset - child.getContainer().offset); |
|
|
|
|
final childLocalPosition = |
|
|
|
|
TextPosition(offset: position.offset - child.getContainer().offset); |
|
|
|
|
final result = child.getPositionAbove(childLocalPosition); |
|
|
|
|
if (result != null) { |
|
|
|
|
return TextPosition(offset: result.offset + child.getContainer().offset); |
|
|
|
@ -371,10 +410,13 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
final caretOffset = child.getOffsetForCaret(childLocalPosition); |
|
|
|
|
final testPosition = TextPosition(offset: sibling.getContainer().length - 1); |
|
|
|
|
final testPosition = |
|
|
|
|
TextPosition(offset: sibling.getContainer().length - 1); |
|
|
|
|
final testOffset = sibling.getOffsetForCaret(testPosition); |
|
|
|
|
final finalOffset = Offset(caretOffset.dx, testOffset.dy); |
|
|
|
|
return TextPosition(offset: sibling.getContainer().offset + sibling.getPositionForOffset(finalOffset).offset); |
|
|
|
|
return TextPosition( |
|
|
|
|
offset: sibling.getContainer().offset + |
|
|
|
|
sibling.getPositionForOffset(finalOffset).offset); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@ -382,7 +424,8 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
assert(position.offset < getContainer().length); |
|
|
|
|
|
|
|
|
|
final child = childAtPosition(position); |
|
|
|
|
final childLocalPosition = TextPosition(offset: position.offset - child.getContainer().offset); |
|
|
|
|
final childLocalPosition = |
|
|
|
|
TextPosition(offset: position.offset - child.getContainer().offset); |
|
|
|
|
final result = child.getPositionBelow(childLocalPosition); |
|
|
|
|
if (result != null) { |
|
|
|
|
return TextPosition(offset: result.offset + child.getContainer().offset); |
|
|
|
@ -396,20 +439,25 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
final caretOffset = child.getOffsetForCaret(childLocalPosition); |
|
|
|
|
final testOffset = sibling.getOffsetForCaret(const TextPosition(offset: 0)); |
|
|
|
|
final finalOffset = Offset(caretOffset.dx, testOffset.dy); |
|
|
|
|
return TextPosition(offset: sibling.getContainer().offset + sibling.getPositionForOffset(finalOffset).offset); |
|
|
|
|
return TextPosition( |
|
|
|
|
offset: sibling.getContainer().offset + |
|
|
|
|
sibling.getPositionForOffset(finalOffset).offset); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
double preferredLineHeight(TextPosition position) { |
|
|
|
|
final child = childAtPosition(position); |
|
|
|
|
return child.preferredLineHeight(TextPosition(offset: position.offset - child.getContainer().offset)); |
|
|
|
|
return child.preferredLineHeight( |
|
|
|
|
TextPosition(offset: position.offset - child.getContainer().offset)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
TextSelectionPoint getBaseEndpointForSelection(TextSelection selection) { |
|
|
|
|
if (selection.isCollapsed) { |
|
|
|
|
return TextSelectionPoint( |
|
|
|
|
Offset(0, preferredLineHeight(selection.extent)) + getOffsetForCaret(selection.extent), null); |
|
|
|
|
Offset(0, preferredLineHeight(selection.extent)) + |
|
|
|
|
getOffsetForCaret(selection.extent), |
|
|
|
|
null); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
final baseNode = getContainer().queryChild(selection.start, false).node; |
|
|
|
@ -422,15 +470,20 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
} |
|
|
|
|
assert(baseChild != null); |
|
|
|
|
|
|
|
|
|
final basePoint = baseChild!.getBaseEndpointForSelection(localSelection(baseChild.getContainer(), selection, true)); |
|
|
|
|
return TextSelectionPoint(basePoint.point + (baseChild.parentData as BoxParentData).offset, basePoint.direction); |
|
|
|
|
final basePoint = baseChild!.getBaseEndpointForSelection( |
|
|
|
|
localSelection(baseChild.getContainer(), 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); |
|
|
|
|
Offset(0, preferredLineHeight(selection.extent)) + |
|
|
|
|
getOffsetForCaret(selection.extent), |
|
|
|
|
null); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
final extentNode = getContainer().queryChild(selection.end, false).node; |
|
|
|
@ -444,10 +497,11 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
} |
|
|
|
|
assert(extentChild != null); |
|
|
|
|
|
|
|
|
|
final extentPoint = |
|
|
|
|
extentChild!.getExtentEndpointForSelection(localSelection(extentChild.getContainer(), selection, true)); |
|
|
|
|
final extentPoint = extentChild!.getExtentEndpointForSelection( |
|
|
|
|
localSelection(extentChild.getContainer(), selection, true)); |
|
|
|
|
return TextSelectionPoint( |
|
|
|
|
extentPoint.point + (extentChild.parentData as BoxParentData).offset, extentPoint.direction); |
|
|
|
|
extentPoint.point + (extentChild.parentData as BoxParentData).offset, |
|
|
|
|
extentPoint.direction); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@ -469,10 +523,12 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
|
|
|
|
|
final decorationPadding = resolvedPadding! - _contentPadding; |
|
|
|
|
|
|
|
|
|
final filledConfiguration = configuration.copyWith(size: decorationPadding.deflateSize(size)); |
|
|
|
|
final filledConfiguration = |
|
|
|
|
configuration.copyWith(size: decorationPadding.deflateSize(size)); |
|
|
|
|
final debugSaveCount = context.canvas.getSaveCount(); |
|
|
|
|
|
|
|
|
|
final decorationOffset = offset.translate(decorationPadding.left, decorationPadding.top); |
|
|
|
|
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 ' |
|
|
|
@ -501,7 +557,8 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
TextPosition globalToLocalPosition(TextPosition position) { |
|
|
|
|
assert(getContainer().containsOffset(position.offset), 'The provided text position is not in the current node'); |
|
|
|
|
assert(getContainer().containsOffset(position.offset), |
|
|
|
|
'The provided text position is not in the current node'); |
|
|
|
|
return TextPosition( |
|
|
|
|
offset: position.offset - getContainer().documentOffset, |
|
|
|
|
affinity: position.affinity, |
|
|
|
@ -520,8 +577,14 @@ class RenderEditableTextBlock extends RenderEditableContainerBox implements Rend |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class _EditableBlock extends MultiChildRenderObjectWidget { |
|
|
|
|
_EditableBlock(this.block, this.textDirection, this.padding, this.scrollBottomInset, this.decoration, |
|
|
|
|
this.contentPadding, List<Widget> children) |
|
|
|
|
_EditableBlock( |
|
|
|
|
this.block, |
|
|
|
|
this.textDirection, |
|
|
|
|
this.padding, |
|
|
|
|
this.scrollBottomInset, |
|
|
|
|
this.decoration, |
|
|
|
|
this.contentPadding, |
|
|
|
|
List<Widget> children) |
|
|
|
|
: super(children: children); |
|
|
|
|
|
|
|
|
|
final Block block; |
|
|
|
@ -531,7 +594,8 @@ class _EditableBlock extends MultiChildRenderObjectWidget { |
|
|
|
|
final Decoration decoration; |
|
|
|
|
final EdgeInsets? contentPadding; |
|
|
|
|
|
|
|
|
|
EdgeInsets get _padding => EdgeInsets.only(top: padding.item1, bottom: padding.item2); |
|
|
|
|
EdgeInsets get _padding => |
|
|
|
|
EdgeInsets.only(top: padding.item1, bottom: padding.item2); |
|
|
|
|
|
|
|
|
|
EdgeInsets get _contentPadding => contentPadding ?? EdgeInsets.zero; |
|
|
|
|
|
|
|
|
@ -548,7 +612,8 @@ class _EditableBlock extends MultiChildRenderObjectWidget { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
void updateRenderObject(BuildContext context, covariant RenderEditableTextBlock renderObject) { |
|
|
|
|
void updateRenderObject( |
|
|
|
|
BuildContext context, covariant RenderEditableTextBlock renderObject) { |
|
|
|
|
renderObject |
|
|
|
|
..setContainer(block) |
|
|
|
|
..textDirection = textDirection |
|
|
|
|