From b5cc0b023703d7e8a848087fedc1198c9d5944a8 Mon Sep 17 00:00:00 2001 From: Xingwei Zhu Date: Mon, 13 Feb 2023 19:12:04 +0800 Subject: [PATCH 1/4] fix cursor height bug when text is empty --- .../Runtime/painting/text_painter.cs | 43 ++++++++++++++++++ .../Runtime/rendering/editable.cs | 44 ++++++++++++++----- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/com.unity.uiwidgets/Runtime/painting/text_painter.cs b/com.unity.uiwidgets/Runtime/painting/text_painter.cs index 1a48eedf0..479f8123e 100644 --- a/com.unity.uiwidgets/Runtime/painting/text_painter.cs +++ b/com.unity.uiwidgets/Runtime/painting/text_painter.cs @@ -56,6 +56,7 @@ public class TextPainter { TextDirection? _textDirection; float _textScaleFactor; Paragraph _layoutTemplate; + Paragraph _layoutCursorTemplate; Paragraph _paragraph; bool _needsLayout = true; int? _maxLines; @@ -106,6 +107,7 @@ public float textScaleFactor { _textScaleFactor = value; markNeedsLayout(); _layoutTemplate = null; + _layoutCursorTemplate = null; } } @@ -131,6 +133,7 @@ public InlineSpan text { if (!Equals(_text == null ? null : _text.style, value == null ? null : value.style)) { _layoutTemplate = null; + _layoutCursorTemplate = null; } _text = value; @@ -155,6 +158,7 @@ public TextDirection? textDirection { _textDirection = value; markNeedsLayout(); _layoutTemplate = null; + _layoutCursorTemplate = null; } } @@ -451,6 +455,45 @@ ParagraphStyle _createParagraphStyle(TextDirection defaultTextDirection = TextDi ); } + ParagraphStyle _createParagraphStyleWithNoStrutStyle(TextDirection defaultTextDirection = TextDirection.ltr) { + D.assert(textDirection != null, + () => "TextPainter.textDirection must be set to a non-null value before using the TextPainter."); + return _text.style?.getParagraphStyle( + textAlign: textAlign, + textDirection: textDirection ?? defaultTextDirection, + textScaleFactor: textScaleFactor, + maxLines: _maxLines, + textHeightBehavior: _textHeightBehavior, + ellipsis: _ellipsis, + locale: _locale + ) ?? new ParagraphStyle( + textAlign: textAlign, + textDirection: textDirection ?? defaultTextDirection, + maxLines: maxLines, + textHeightBehavior: _textHeightBehavior, + ellipsis: ellipsis, + locale: locale + ); + } + + public float preferredCursorHeight { + get { + if (_layoutCursorTemplate == null) { + var builder = new ParagraphBuilder(_createParagraphStyleWithNoStrutStyle(TextDirection.ltr) + ); // direction doesn't matter, text is just a space + if (text != null && text.style != null) { + builder.pushStyle(text.style.getTextStyle(textScaleFactor: textScaleFactor)); + } + + builder.addText(" "); + _layoutCursorTemplate = builder.build(); + _layoutCursorTemplate.layout(new ParagraphConstraints(float.PositiveInfinity)); + } + + return _layoutCursorTemplate.height(); + } + } + public float preferredLineHeight { get { if (_layoutTemplate == null) { diff --git a/com.unity.uiwidgets/Runtime/rendering/editable.cs b/com.unity.uiwidgets/Runtime/rendering/editable.cs index 8c755ae08..9f6b46be5 100644 --- a/com.unity.uiwidgets/Runtime/rendering/editable.cs +++ b/com.unity.uiwidgets/Runtime/rendering/editable.cs @@ -1290,6 +1290,10 @@ public float preferredLineHeight { get { return _textPainter.preferredLineHeight; } } + private float preferredCursorHeight { + get { return _textPainter.preferredCursorHeight; } + } + float _preferredHeight(float width) { bool lockedMax = maxLines != null && minLines == null; bool lockedBoth = maxLines != null && minLines == maxLines; @@ -1537,14 +1541,26 @@ Rect _getCaretPrototype { switch (Application.platform) { case RuntimePlatform.IPhonePlayer: return Rect.fromLTWH(0.0f, 0.0f, cursorWidth, - preferredLineHeight + 2.0f); + preferredCursorHeight + 2.0f); default: - return Rect.fromLTWH(0.0f, EditableUtils._kCaretHeightOffset, cursorWidth, - preferredLineHeight - 2.0f * EditableUtils._kCaretHeightOffset); + return Rect.fromLTWH(0.0f, 0.0f, cursorWidth, + preferredCursorHeight); + } + } + } + + Rect _getCaretPrototypeForEmptyLine { + get { + switch (Application.platform) { + case RuntimePlatform.IPhonePlayer: + return Rect.fromLTWH(0.0f, 0.0f, cursorWidth, + preferredCursorHeight + 2.0f); + default: + return Rect.fromLTWH(0.0f, (preferredLineHeight - preferredCursorHeight) / 2, cursorWidth, + preferredCursorHeight); } } } - protected override void performLayout() { BoxConstraints constraints = this.constraints; @@ -1577,13 +1593,21 @@ void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPositio _textLayoutLastMinWidth == constraints.minWidth, () => $"Last width ({_textLayoutLastMinWidth}, {_textLayoutLastMaxWidth}) not the same as max width constraint ({constraints.minWidth}, {constraints.maxWidth})."); var paint = new Paint() {color = _floatingCursorOn ? backgroundCursorColor : _cursorColor}; - var caretOffset = _textPainter.getOffsetForCaret(textPosition, _caretPrototype) + effectiveOffset; - Rect caretRect = _caretPrototype.shift(caretOffset); + + //if there is no contents in this editable yet, we use the _getCaretPrototypeForEmptyLine API to fetch the proper caret height + var currentCaretPrototype = _caretPrototype; + if (text.text == "") { + currentCaretPrototype = _getCaretPrototypeForEmptyLine; + } + + var caretOffset = _textPainter.getOffsetForCaret(textPosition, currentCaretPrototype) + effectiveOffset; + Rect caretRect = currentCaretPrototype.shift(caretOffset); + if (_cursorOffset != null) { caretRect = caretRect.shift(_cursorOffset); } - - float? caretHeight = _textPainter.getFullHeightForCaret(textPosition, _caretPrototype); + + float? caretHeight = _textPainter.getFullHeightForCaret(textPosition, currentCaretPrototype); if (caretHeight != null) { switch (Application.platform) { case RuntimePlatform.IPhonePlayer: @@ -1598,7 +1622,7 @@ void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPositio default: caretRect = Rect.fromLTWH( caretRect.left, - caretRect.top - EditableUtils._kCaretHeightOffset, + caretRect.top, caretRect.width, caretHeight.Value ); @@ -1607,7 +1631,7 @@ void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPositio } caretRect = caretRect.shift(_getPixelPerfectCursorOffset(caretRect)); - + if (cursorRadius == null) { canvas.drawRect(caretRect, paint); } From cc8c06f72e9670ffb38569371fd5406aee80277a Mon Sep 17 00:00:00 2001 From: Xingwei Zhu Date: Tue, 14 Feb 2023 10:46:15 +0800 Subject: [PATCH 2/4] try fix the issue for IOS --- com.unity.uiwidgets/Runtime/rendering/editable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.uiwidgets/Runtime/rendering/editable.cs b/com.unity.uiwidgets/Runtime/rendering/editable.cs index 9f6b46be5..d0585322c 100644 --- a/com.unity.uiwidgets/Runtime/rendering/editable.cs +++ b/com.unity.uiwidgets/Runtime/rendering/editable.cs @@ -1553,7 +1553,7 @@ Rect _getCaretPrototypeForEmptyLine { get { switch (Application.platform) { case RuntimePlatform.IPhonePlayer: - return Rect.fromLTWH(0.0f, 0.0f, cursorWidth, + return Rect.fromLTWH(0.0f, (preferredLineHeight - preferredCursorHeight) / 2, cursorWidth, preferredCursorHeight + 2.0f); default: return Rect.fromLTWH(0.0f, (preferredLineHeight - preferredCursorHeight) / 2, cursorWidth, From 62be003b112c208eff86a30fb610d5f4b32759a3 Mon Sep 17 00:00:00 2001 From: Xingwei Zhu Date: Wed, 22 Feb 2023 15:08:38 +0800 Subject: [PATCH 3/4] Update editable.cs fix issue --- com.unity.uiwidgets/Runtime/rendering/editable.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/com.unity.uiwidgets/Runtime/rendering/editable.cs b/com.unity.uiwidgets/Runtime/rendering/editable.cs index d0585322c..c146683e5 100644 --- a/com.unity.uiwidgets/Runtime/rendering/editable.cs +++ b/com.unity.uiwidgets/Runtime/rendering/editable.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Unity.UIWidgets.async; using Unity.UIWidgets.foundation; using Unity.UIWidgets.gestures; @@ -1596,7 +1597,9 @@ void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPositio //if there is no contents in this editable yet, we use the _getCaretPrototypeForEmptyLine API to fetch the proper caret height var currentCaretPrototype = _caretPrototype; - if (text.text == "") { + var content = new StringBuilder(); + text.computeToPlainText(content, false, false); + if (string.IsNullOrEmpty(content.ToString())) { currentCaretPrototype = _getCaretPrototypeForEmptyLine; } @@ -1606,7 +1609,6 @@ void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPositio if (_cursorOffset != null) { caretRect = caretRect.shift(_cursorOffset); } - float? caretHeight = _textPainter.getFullHeightForCaret(textPosition, currentCaretPrototype); if (caretHeight != null) { switch (Application.platform) { From 035ff04e10c453f3824f6f0bf7df0cc3041c7b03 Mon Sep 17 00:00:00 2001 From: Xingwei Zhu Date: Thu, 23 Feb 2023 22:38:49 +0800 Subject: [PATCH 4/4] stash a partial fix for further client validation --- .../Runtime/rendering/editable.cs | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/com.unity.uiwidgets/Runtime/rendering/editable.cs b/com.unity.uiwidgets/Runtime/rendering/editable.cs index c146683e5..8a7e6a6a4 100644 --- a/com.unity.uiwidgets/Runtime/rendering/editable.cs +++ b/com.unity.uiwidgets/Runtime/rendering/editable.cs @@ -386,16 +386,18 @@ void _handleSelectionChange( TextSelection nextSelection, SelectionChangedCause cause ) { - - bool focusingEmpty = nextSelection.baseOffset == 0 - && nextSelection.extentOffset == 0 - && !hasFocus; + //force to update selection no matter whether the selection really changes + //it should not affect the ui logics except some performance drop + /*bool focusingEmpty = nextSelection.baseOffset == 0 + && nextSelection.extentOffset == 0 + && !hasFocus; if (nextSelection == selection && cause != SelectionChangedCause.keyboard && !focusingEmpty) { + Debug.Log("[ZXW2] handle double tap6 <>>>"); return; - } - + }*/ + onSelectionChanged?.Invoke(nextSelection, this, cause); } @@ -1459,7 +1461,7 @@ public void selectWordsInRange(Offset from = null, Offset to = null, SelectionCh ? firstWord : _selectWordAtOffset( _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset))); - + _handleSelectionChange( new TextSelection( baseOffset: firstWord.baseOffset, @@ -1594,20 +1596,35 @@ void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPositio _textLayoutLastMinWidth == constraints.minWidth, () => $"Last width ({_textLayoutLastMinWidth}, {_textLayoutLastMaxWidth}) not the same as max width constraint ({constraints.minWidth}, {constraints.maxWidth})."); var paint = new Paint() {color = _floatingCursorOn ? backgroundCursorColor : _cursorColor}; - + + //force offset to 0 (at the beginning of the text if the text is currently focused + if (hasFocus && textPosition.offset == -1) + { + textPosition = new TextPosition(0, textPosition.affinity); + } //if there is no contents in this editable yet, we use the _getCaretPrototypeForEmptyLine API to fetch the proper caret height var currentCaretPrototype = _caretPrototype; var content = new StringBuilder(); text.computeToPlainText(content, false, false); + + //Debug.Log("[ZXW] text is " + content.ToString()); if (string.IsNullOrEmpty(content.ToString())) { currentCaretPrototype = _getCaretPrototypeForEmptyLine; } + //Debug.Log("[ZXW] caret offset is " + currentCaretPrototype.top + " " + _caretPrototype.top + " " + _getCaretPrototypeForEmptyLine.top); + + var caretOffset = _textPainter.getOffsetForCaret(textPosition, currentCaretPrototype) + effectiveOffset; + Rect caretRect = currentCaretPrototype.shift(caretOffset); + //Debug.Log("[ZXW] caretRect is " + effectiveOffset.dy + " " + caretOffset.dy + " " + caretRect.top); + + if (_cursorOffset != null) { caretRect = caretRect.shift(_cursorOffset); + //Debug.Log("[ZXW] caretRect shift " + _cursorOffset.dy + " " + caretRect.top); } float? caretHeight = _textPainter.getFullHeightForCaret(textPosition, currentCaretPrototype); if (caretHeight != null) { @@ -1628,12 +1645,16 @@ void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPositio caretRect.width, caretHeight.Value ); + + //Debug.Log("[ZXW] caretRect final " + caretRect.height + " " + caretRect.top); break; } } caretRect = caretRect.shift(_getPixelPerfectCursorOffset(caretRect)); + //Debug.Log("[ZXW] caretRect pixel perfect " + caretRect.height + " " + caretRect.top); + if (cursorRadius == null) { canvas.drawRect(caretRect, paint); }