From b4ae8151f8cba4e7111080578d0db557071a9dff Mon Sep 17 00:00:00 2001 From: agata Date: Thu, 19 Sep 2024 13:03:37 +0900 Subject: [PATCH] Refine IME composing range styling by applying underline as text style (#2244) --- .../editor/raw_editor/raw_editor_state.dart | 5 +- lib/src/editor/widgets/text/text_block.dart | 3 +- lib/src/editor/widgets/text/text_line.dart | 120 ++++++++++-------- 3 files changed, 67 insertions(+), 61 deletions(-) diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index 0e98d25f7..f9c071a4d 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -707,6 +707,7 @@ class QuillRawEditorState extends EditorState linkActionPicker: _linkActionPicker, onLaunchUrl: widget.configurations.onLaunchUrl, customLinkPrefixes: widget.configurations.customLinkPrefixes, + composingRange: composingRange.value, ); final editableTextLine = EditableTextLine( node, @@ -721,9 +722,7 @@ class QuillRawEditorState extends EditorState _hasFocus, MediaQuery.devicePixelRatioOf(context), _cursorCont, - _styles!.inlineCode!, - composingRange.value, - _styles!.paragraph!.style.color!); + _styles!.inlineCode!); return editableTextLine; } diff --git a/lib/src/editor/widgets/text/text_block.dart b/lib/src/editor/widgets/text/text_block.dart index 292db507e..9f619b634 100644 --- a/lib/src/editor/widgets/text/text_block.dart +++ b/lib/src/editor/widgets/text/text_block.dart @@ -195,6 +195,7 @@ class EditableTextBlock extends StatelessWidget { onLaunchUrl: onLaunchUrl, customLinkPrefixes: customLinkPrefixes, customRecognizerBuilder: customRecognizerBuilder, + composingRange: composingRange, ), indentWidthBuilder(block, context, count, numberPointWidthBuilder), _getSpacingForLine(line, index, count, defaultStyles), @@ -206,8 +207,6 @@ class EditableTextBlock extends StatelessWidget { MediaQuery.devicePixelRatioOf(context), cursorCont, styles!.inlineCode!, - composingRange, - styles!.paragraph!.style.color!, ); final nodeTextDirection = getDirectionOfNode(line, textDirection); children.add( diff --git a/lib/src/editor/widgets/text/text_line.dart b/lib/src/editor/widgets/text/text_line.dart index daa402e9e..b0c42f4b8 100644 --- a/lib/src/editor/widgets/text/text_line.dart +++ b/lib/src/editor/widgets/text/text_line.dart @@ -31,6 +31,7 @@ class TextLine extends StatefulWidget { required this.controller, required this.onLaunchUrl, required this.linkActionPicker, + required this.composingRange, this.textDirection, this.customStyleBuilder, this.customRecognizerBuilder, @@ -49,6 +50,7 @@ class TextLine extends StatefulWidget { final ValueChanged? onLaunchUrl; final LinkActionPicker linkActionPicker; final List customLinkPrefixes; + final TextRange composingRange; @override State createState() => _TextLineState(); @@ -267,14 +269,70 @@ class _TextLineState extends State { if (nodes.isEmpty && kIsWeb) { nodes = LinkedList()..add(leaf.QuillText('\u{200B}')); } - final children = nodes - .map((node) => - _getTextSpanFromNode(defaultStyles, node, widget.line.style)) - .toList(growable: false); + + final isComposingRangeOutOfLine = !widget.composingRange.isValid || + widget.composingRange.isCollapsed || + (widget.composingRange.start < widget.line.documentOffset || + widget.composingRange.end > + widget.line.documentOffset + widget.line.length); + + if (isComposingRangeOutOfLine) { + final children = nodes + .map((node) => + _getTextSpanFromNode(defaultStyles, node, widget.line.style)) + .toList(growable: false); + return TextSpan(children: children, style: lineStyle); + } + + final children = nodes.expand((node) { + final child = + _getTextSpanFromNode(defaultStyles, node, widget.line.style); + final isNodeInComposingRange = + node.documentOffset <= widget.composingRange.start && + widget.composingRange.end <= node.documentOffset + node.length; + if (isNodeInComposingRange) { + return _splitAndApplyComposingStyle(node, child); + } else { + return [child]; + } + }).toList(growable: false); return TextSpan(children: children, style: lineStyle); } + // split the text nodes into composing and non-composing nodes + // and apply the composing style to the composing nodes + List _splitAndApplyComposingStyle(Node node, InlineSpan child) { + assert(widget.composingRange.isValid && !widget.composingRange.isCollapsed); + + final composingStart = widget.composingRange.start - node.documentOffset; + final composingEnd = widget.composingRange.end - node.documentOffset; + final text = child.toPlainText(); + + final textBefore = text.substring(0, composingStart); + final textComposing = text.substring(composingStart, composingEnd); + final textAfter = text.substring(composingEnd); + + final composingStyle = child.style + ?.merge(const TextStyle(decoration: TextDecoration.underline)) ?? + const TextStyle(decoration: TextDecoration.underline); + + return [ + TextSpan( + text: textBefore, + style: child.style, + ), + TextSpan( + text: textComposing, + style: composingStyle, + ), + TextSpan( + text: textAfter, + style: child.style, + ), + ]; + } + TextStyle _getLineStyle(DefaultStyles defaultStyles) { var textStyle = const TextStyle(); @@ -643,8 +701,6 @@ class EditableTextLine extends RenderObjectWidget { this.devicePixelRatio, this.cursorCont, this.inlineCodeStyle, - this.composingRange, - this.composingColor, {super.key}); final Line line; @@ -660,8 +716,6 @@ class EditableTextLine extends RenderObjectWidget { final double devicePixelRatio; final CursorCont cursorCont; final InlineCodeStyle inlineCodeStyle; - final TextRange composingRange; - final Color composingColor; @override RenderObjectElement createElement() { @@ -680,9 +734,7 @@ class EditableTextLine extends RenderObjectWidget { _getPadding(), color, cursorCont, - inlineCodeStyle, - composingRange, - composingColor); + inlineCodeStyle); } @override @@ -698,8 +750,7 @@ class EditableTextLine extends RenderObjectWidget { ..hasFocus = hasFocus ..setDevicePixelRatio(devicePixelRatio) ..setCursorCont(cursorCont) - ..setInlineCodeStyle(inlineCodeStyle) - ..setComposingRange(composingRange); + ..setInlineCodeStyle(inlineCodeStyle); } EdgeInsetsGeometry _getPadding() { @@ -726,8 +777,6 @@ class RenderEditableTextLine extends RenderEditableBox { this.color, this.cursorCont, this.inlineCodeStyle, - this.composingRange, - this.composingColor, ); RenderBox? _leading; @@ -746,8 +795,6 @@ class RenderEditableTextLine extends RenderEditableBox { List? _selectedRects; late Rect _caretPrototype; InlineCodeStyle inlineCodeStyle; - TextRange composingRange; - Color composingColor; final Map children = {}; Iterable get _children sync* { @@ -863,12 +910,6 @@ class RenderEditableTextLine extends RenderEditableBox { markNeedsLayout(); } - void setComposingRange(TextRange newComposingRange) { - if (composingRange == newComposingRange) return; - composingRange = newComposingRange; - markNeedsLayout(); - } - // Start selection implementation bool containsTextSelection() { @@ -1351,11 +1392,6 @@ class RenderEditableTextLine extends RenderEditableBox { _paintSelection(context, effectiveOffset); } - - // Paints an underline to indicate the text being composed by the IME. - if (composingRange.isValid) { - _paintComposing(context); - } } } @@ -1387,34 +1423,6 @@ class RenderEditableTextLine extends RenderEditableBox { ); } - // Paints a line below the composing text. - void _paintComposing(PaintingContext context) { - assert(composingRange.isValid); - final composingStart = composingRange.start - line.documentOffset; - final composingEnd = composingRange.end - line.documentOffset; - if (composingStart < 0 || composingEnd < 0) { - return; - } - final composingRects = _body!.getBoxesForSelection( - TextSelection( - baseOffset: composingStart, - extentOffset: composingEnd, - ), - ); - final paint = Paint() - ..color = composingColor - ..style = PaintingStyle.stroke - ..strokeWidth = 1; - for (final box in composingRects) { - final rect = box.toRect(); - context.canvas.drawLine( - rect.bottomLeft.translate(0, -5), - rect.bottomRight.translate(0, -5), - paint, - ); - } - } - @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { if (_leading != null) {