Skip to content

Commit

Permalink
Refine IME composing range styling by applying underline as text style (
Browse files Browse the repository at this point in the history
  • Loading branch information
agata authored Sep 19, 2024
1 parent 9d87315 commit b4ae815
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 61 deletions.
5 changes: 2 additions & 3 deletions lib/src/editor/raw_editor/raw_editor_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}

Expand Down
3 changes: 1 addition & 2 deletions lib/src/editor/widgets/text/text_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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(
Expand Down
120 changes: 64 additions & 56 deletions lib/src/editor/widgets/text/text_line.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -49,6 +50,7 @@ class TextLine extends StatefulWidget {
final ValueChanged<String>? onLaunchUrl;
final LinkActionPicker linkActionPicker;
final List<String> customLinkPrefixes;
final TextRange composingRange;

@override
State<TextLine> createState() => _TextLineState();
Expand Down Expand Up @@ -267,14 +269,70 @@ class _TextLineState extends State<TextLine> {
if (nodes.isEmpty && kIsWeb) {
nodes = LinkedList<Node>()..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<InlineSpan> _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();

Expand Down Expand Up @@ -643,8 +701,6 @@ class EditableTextLine extends RenderObjectWidget {
this.devicePixelRatio,
this.cursorCont,
this.inlineCodeStyle,
this.composingRange,
this.composingColor,
{super.key});

final Line line;
Expand All @@ -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() {
Expand All @@ -680,9 +734,7 @@ class EditableTextLine extends RenderObjectWidget {
_getPadding(),
color,
cursorCont,
inlineCodeStyle,
composingRange,
composingColor);
inlineCodeStyle);
}

@override
Expand All @@ -698,8 +750,7 @@ class EditableTextLine extends RenderObjectWidget {
..hasFocus = hasFocus
..setDevicePixelRatio(devicePixelRatio)
..setCursorCont(cursorCont)
..setInlineCodeStyle(inlineCodeStyle)
..setComposingRange(composingRange);
..setInlineCodeStyle(inlineCodeStyle);
}

EdgeInsetsGeometry _getPadding() {
Expand All @@ -726,8 +777,6 @@ class RenderEditableTextLine extends RenderEditableBox {
this.color,
this.cursorCont,
this.inlineCodeStyle,
this.composingRange,
this.composingColor,
);

RenderBox? _leading;
Expand All @@ -746,8 +795,6 @@ class RenderEditableTextLine extends RenderEditableBox {
List<TextBox>? _selectedRects;
late Rect _caretPrototype;
InlineCodeStyle inlineCodeStyle;
TextRange composingRange;
Color composingColor;
final Map<TextLineSlot, RenderBox> children = <TextLineSlot, RenderBox>{};

Iterable<RenderBox> get _children sync* {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit b4ae815

Please sign in to comment.