From c8f57ea82bdc8a8894eb4067b0377aec1cca2825 Mon Sep 17 00:00:00 2001 From: Amir Alizadeh Date: Thu, 3 Oct 2024 21:46:59 +0330 Subject: [PATCH] Fix RichTextBox selection bug --- .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index df5582cc3..17dc25e16 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -60,6 +60,51 @@ namespace FlaxEngine.GUI return false; } + /// + /// Gets the text block or the nearest text block of the character at the given index. + /// + /// The character index. + /// The result text block descriptor. + /// If true, the when the index is between two text blocks, it will return the next block. + /// True if a text block is found, otherwise false. + public bool GetNearestTextBlock(int index, out TextBlock result, bool snapToNext = false) + { + var textBlocksSpan = CollectionsMarshal.AsSpan(_textBlocks); + int blockCount = _textBlocks.Count; + + for (int i = 0; i < blockCount; i++) + { + ref TextBlock currentBlock = ref textBlocksSpan[i]; + + if (currentBlock.Range.Contains(index)) + { + result = currentBlock; + return true; + } + + if (i < blockCount - 1) + { + ref TextBlock nextBlock = ref textBlocksSpan[i + 1]; + if (index >= currentBlock.Range.EndIndex && index < nextBlock.Range.StartIndex) + { + result = snapToNext ? nextBlock : currentBlock; + return true; + } + } + } + + // Handle case when index is outside all text ranges + if (index >= 0 && blockCount > 0) + { + result = (index <= textBlocksSpan[0].Range.StartIndex) ? textBlocksSpan[0] : textBlocksSpan[blockCount - 1]; + return true; + } + + // If no text block is found + result = new TextBlock(); + return false; + } + /// /// Updates the text blocks. /// @@ -221,6 +266,30 @@ namespace FlaxEngine.GUI return base.OnMouseDoubleClick(location, button); } + /// + protected override void SetSelection(int start, int end, bool withScroll = true) + { + int snappedStart = start; + int snappedEnd = end; + + if (start != -1 && end != -1) + { + bool movingBack = (_selectionStart != -1 && _selectionEnd != -1) && (end < _selectionEnd || start < _selectionStart); + + GetNearestTextBlock(start, out TextBlock startTextBlock, !movingBack); + GetNearestTextBlock(end, out TextBlock endTextBlock, !movingBack); + + snappedStart = startTextBlock.Range.Contains(start) ? start : (movingBack ? startTextBlock.Range.EndIndex - 1 : startTextBlock.Range.StartIndex); + + snappedEnd = endTextBlock.Range.Contains(end) ? end : (movingBack ? endTextBlock.Range.EndIndex - 1 : endTextBlock.Range.StartIndex); + + snappedStart = movingBack ? Math.Min(start, snappedStart) : Math.Max(start, snappedStart); + snappedEnd = movingBack ? Math.Min(end, snappedEnd) : Math.Max(end, snappedEnd); + } + + base.SetSelection(snappedStart, snappedEnd, withScroll); + } + /// public override void DrawSelf() { @@ -289,8 +358,8 @@ namespace FlaxEngine.GUI // Selection if (hasSelection && textBlock.Style.BackgroundSelectedBrush != null && textBlock.Range.Intersect(ref selection)) { - var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); - var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex); + var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : GetCharPosition(selection.StartIndex, out _); + var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : GetCharPosition(selection.EndIndex, out _); float height = font.Height; float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha;