using System.Globalization;
namespace Sandbox.UI;
public partial class Label
{
///
/// Enables multi-line support for editing purposes.
///
public bool Multiline { get; set; } = true;
// Sol: TODO: unlink this from wrapping, add css text-wrap or something
private Vector2 caretScroll;
///
/// Replace the currently selected text with given text.
///
public void ReplaceSelection( string str )
{
var s = Math.Min( SelectionStart, SelectionEnd );
var e = Math.Max( SelectionStart, SelectionEnd );
var len = e - s;
if ( CaretPosition > e ) CaretPosition -= len;
else if ( CaretPosition > s ) CaretPosition = s;
CaretPosition += new StringInfo( str ).LengthInTextElements;
InsertText( str, s, e );
SelectionStart = 0;
SelectionEnd = 0;
}
///
/// Sets the text selection.
///
public void SetSelection( int start, int end )
{
var s = Math.Min( start, end ).Clamp( 0, TextLength );
var e = Math.Max( start, end ).Clamp( 0, TextLength );
if ( s == e )
{
s = 0;
e = 0;
}
SelectionStart = s;
SelectionEnd = e;
}
///
/// Set the text caret position to the given index.
///
/// Where to move the text caret to within the text.
/// Whether to also add the characters we passed by to the selection.
public void SetCaretPosition( int pos, bool select = false )
{
if ( SelectionEnd == 0 && SelectionStart == 0 && select )
{
SelectionStart = CaretPosition.Clamp( 0, TextLength );
}
CaretPosition = pos.Clamp( 0, TextLength );
if ( select )
{
SelectionEnd = CaretPosition;
}
else
{
SelectionEnd = 0;
SelectionStart = 0;
}
ScrollToCaret();
}
///
/// Put the caret within the visible region.
///
public void ScrollToCaret()
{
// Sol: not supported right now. think would work totally differently with "proper" scrollbars etc?
if ( Multiline ) return;
if ( _textBlock is null ) return;
_textBlock.ScrollToCaret( CaretPosition, ref caretScroll, Box.RectInner.Size );
}
///
/// Move the text caret to the closest word start or end to the left of current position.
/// This simulates holding Control key while pressing left arrow key.
///
/// Whether to also add the characters we passed by to the selection.
public void MoveToWordBoundaryLeft( bool select )
{
var boundaries = GetWordBoundaryIndices();
var left = boundaries.LastOrDefault( x => x < CaretPosition );
MoveCaretPos( left - CaretPosition, select );
}
///
/// Move the text caret to the closest word start or end to the right of current position.
/// This simulates holding Control key while pressing right arrow key.
///
/// Whether to also add the characters we passed by to the selection.
public void MoveToWordBoundaryRight( bool select )
{
var boundaries = GetWordBoundaryIndices();
var right = boundaries.FirstOrDefault( x => x >= CaretPosition );
if ( right == 0 ) return;
MoveCaretPos( right - CaretPosition, select );
}
///
/// Move the text caret by given amount.
///
/// How many characters to the right to move. Negative values move left.
/// Whether to also add the characters we passed by to the selection.
public void MoveCaretPos( int delta, bool select = false )
{
SetCaretPosition( CaretPosition + delta, select );
}
///
/// Insert given text at given position.
///
/// Text to insert.
/// Position to insert the text at.
/// If set, the end position in the current ,
/// which will be used to replace portion of the existing text with the given .
public void InsertText( string text, int pos, int? endpos = null )
{
CaretSantity();
pos = Math.Clamp( pos, 0, TextLength );
if ( endpos.HasValue ) endpos = Math.Clamp( endpos.Value, 0, TextLength );
var a = pos > 0 ? StringInfo.SubstringByTextElements( 0, pos ) : "";
var b = "";
if ( endpos.HasValue )
{
if ( endpos < TextLength ) b = StringInfo.SubstringByTextElements( endpos.Value );
}
else
{
if ( pos < TextLength ) b = StringInfo.SubstringByTextElements( pos );
}
Text = $"{a}{text}{b}";
}
///
/// Remove given amount of characters from the label at given position.
///
public virtual void RemoveText( int start, int count )
{
var a = start > 0 ? StringInfo.SubstringByTextElements( 0, start ) : "";
var b = (start + count < TextLength) ? StringInfo.SubstringByTextElements( start + count ) : "";
Text = a + b;
}
///
/// Move the text caret to the start of the current line.
///
/// Whether to also add the characters we passed by to the selection.
public void MoveToLineStart( bool select = false )
{
if ( !Multiline )
{
SetCaretPosition( 0, select );
return;
}
int iNewline = 0;
var e = StringInfo.GetTextElementEnumerator( Text );
while ( e.MoveNext() )
{
if ( e.ElementIndex >= CaretPosition )
break;
if ( IsNewline( e.GetTextElement() ) )
iNewline = e.ElementIndex + 1;
}
SetCaretPosition( iNewline, select );
}
///
/// Move the text caret to the end of the current line.
///
/// Whether to also add the characters we passed by to the selection.
public void MoveToLineEnd( bool select = false )
{
if ( !Multiline )
{
SetCaretPosition( TextLength, select );
return;
}
var e = StringInfo.GetTextElementEnumerator( Text );
while ( e.MoveNext() )
{
if ( e.ElementIndex < CaretPosition )
continue;
if ( IsNewline( e.GetTextElement() ) )
{
SetCaretPosition( e.ElementIndex, select );
return;
}
}
SetCaretPosition( TextLength, select );
}
///
/// Move the text caret to next or previous line.
///
/// How many lines to offset. Negative values move up.
/// Whether to also add the characters we passed by to the selection.
public void MoveCaretLine( int offset_line, bool select )
{
if ( !Multiline )
{
if ( offset_line < 0 ) SetCaretPosition( 0, select );
if ( offset_line > 0 ) SetCaretPosition( TextLength, select );
return;
}
var caret = GetCaretRect( CaretPosition );
var height = caret.Size;
height.x = 0;
var click = caret.Position + caret.Size * 0.5f + height * offset_line * 1.2f;
var pos = GetLetterAtScreenPosition( click );
SetCaretPosition( pos, select );
}
///
/// Select a work at given word position.
///
public void SelectWord( int wordPos )
{
if ( TextLength == 0 )
return;
var boundaries = GetWordBoundaryIndices();
SelectionStart = boundaries.LastOrDefault( x => x < wordPos );
SelectionEnd = boundaries.FirstOrDefault( x => x >= wordPos );
CaretPosition = SelectionEnd;
}
///
/// Returns a list of positions in the text of each side of each word within the .
/// This is used for Control + Arrow Key navigation.
///
public List GetWordBoundaryIndices()
{
var result = new List() { 0, StringInfo.LengthInTextElements };
var e = StringInfo.GetTextElementEnumerator( Text );
var input = string.Empty;
// make it work with graphemes by assuming everything is 1 char long
while ( e.MoveNext() )
{
input += e.GetTextElement()[0];
}
var match = System.Text.RegularExpressions.Regex.Match( input, @"\b" );
while ( match.Success )
{
result.Add( match.Index );
match = match.NextMatch();
}
result = result.Distinct().ToList();
result.Sort();
return result;
}
///
/// Returns true if the input string is a 1 or 2 (\r\n) character newline symbol.
///
private bool IsNewline( string str )
{
if ( str == "\n" ) return true;
if ( str == "\r\n" ) return true;
if ( str == "\r" ) return true;
return false;
}
}