// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine.Assertions;
using FlaxEngine.Utilities;
namespace FlaxEngine.GUI
{
///
/// Base class for all text box controls which can gather text input from the user.
///
public abstract class TextBoxBase : ContainerControl
{
///
/// The delete control character (used for text filtering).
///
protected const char DelChar = (char)0x7F;
///
/// The text separators (used for words skipping).
///
protected static readonly char[] Separators =
{
' ',
'.',
',',
'\t',
'\r',
'\n',
':',
';',
'\'',
'\"',
')',
'(',
'/',
'\\',
'>',
'<',
};
///
/// Default height of the text box
///
public static float DefaultHeight = 18;
///
/// Left and right margin for text inside the text box bounds rectangle
///
public static float DefaultMargin = 4;
///
/// The current text value.
///
protected string _text = string.Empty;
///
/// The text value captured when user started editing text. Used to detect content modification.
///
protected string _onStartEditValue;
///
/// Flag used to indicate whenever user is editing the text.
///
protected bool _isEditing;
///
/// The view offset
///
protected Float2 _viewOffset;
///
/// The target view offset.
///
protected Float2 _targetViewOffset;
///
/// The text size calculated from font.
///
protected Float2 _textSize;
///
/// Flag used to indicate whenever text can contain multiple lines.
///
protected bool _isMultiline;
///
/// Flag used to indicate whenever text is read-only and cannot be modified by the user.
///
protected bool _isReadOnly;
///
/// Flag used to indicate whenever text is selectable.
///
protected bool _isSelectable = true;
///
/// The maximum length of the text.
///
protected int _maxLength;
///
/// Flag used to indicate whenever user is selecting text.
///
protected bool _isSelecting;
///
/// The selection start position (character index).
///
protected int _selectionStart;
///
/// The selection end position (character index).
///
protected int _selectionEnd;
///
/// The animate time for selection.
///
protected float _animateTime;
///
/// If the cursor should change to an IBeam
///
protected bool _changeCursor = true;
///
/// Event fired when text gets changed
///
public event Action TextChanged;
///
/// Event fired when text gets changed after editing (user accepted entered value).
///
public event Action EditEnd;
///
/// Event fired when text gets changed after editing (user accepted entered value).
///
public event Action TextBoxEditEnd;
///
/// Event fired when a key is down.
///
public event Action KeyDown;
///
/// Event fired when a key is up.
///
public event Action KeyUp;
///
/// Gets or sets a value indicating whether the text box can end edit via left click outside of the control
///
[HideInEditor]
public bool EndEditOnClick { get; set; } = true;
///
/// Gets or sets a value indicating whether this is a multiline text box control.
///
[EditorOrder(40), Tooltip("If checked, the textbox will support multiline text input.")]
public bool IsMultiline
{
get => _isMultiline;
set
{
if (_isMultiline != value)
{
_isMultiline = value;
OnIsMultilineChanged();
Deselect();
if (!_isMultiline)
{
var lines = _text.Split('\n');
_text = lines[0];
}
}
}
}
///
/// Gets or sets the maximum number of characters the user can type into the text box control.
///
[EditorOrder(50), Tooltip("The maximum number of characters the user can type into the text box control.")]
public int MaxLength
{
get => _maxLength;
set
{
if (_maxLength <= 0)
throw new ArgumentOutOfRangeException(nameof(MaxLength));
if (_maxLength != value)
{
_maxLength = value;
// Cut too long text
if (_text.Length > _maxLength)
{
Text = _text.Substring(0, _maxLength);
}
}
}
}
///
/// Gets or sets a value indicating whether text in the text box is read-only.
///
[EditorOrder(60), Tooltip("If checked, text in the text box is read-only.")]
public bool IsReadOnly
{
get => _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
OnIsReadOnlyChanged();
}
}
}
///
/// Gets or sets a value indicating whether text can be selected in text box.
///
[EditorOrder(62), Tooltip("If checked, text can be selected in text box.")]
public bool IsSelectable
{
get => _isSelectable;
set
{
if (_isSelectable != value)
{
_isSelectable = value;
OnIsSelectableChanged();
}
}
}
///
/// Gets or sets a value indicating whether apply clipping mask on text during rendering.
///
[EditorOrder(529)]
public bool ClipText { get; set; } = true;
///
/// Gets or sets a value indicating whether you can scroll the text in the text box (eg. with a mouse wheel).
///
[EditorOrder(41)]
public bool IsMultilineScrollable { get; set; } = true;
///
/// Gets or sets textbox background color when the control is selected (has focus).
///
[EditorDisplay("Background Style"), EditorOrder(2001), Tooltip("The textbox background color when the control is selected (has focus)."), ExpandGroups]
public Color BackgroundSelectedColor { get; set; }
///
/// Gets or sets the color of the caret (Transparent if not used).
///
[EditorDisplay("Caret Style"), EditorOrder(2020), Tooltip("The color of the caret (Transparent if not used)."), ExpandGroups]
public Color CaretColor { get; set; }
///
/// Gets or sets the speed of the caret flashing animation.
///
[EditorDisplay("Caret Style"), EditorOrder(2021), Tooltip("The speed of the caret flashing animation.")]
public float CaretFlashSpeed { get; set; } = 6.0f;
///
/// Gets or sets the speed of the selection background flashing animation.
///
[EditorDisplay("Background Style"), EditorOrder(2002), Tooltip("The speed of the selection background flashing animation.")]
public float BackgroundSelectedFlashSpeed { get; set; } = 6.0f;
///
/// Gets or sets whether to have a border.
///
[EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("Whether to have a border."), ExpandGroups]
public bool HasBorder { get; set; } = true;
///
/// Gets or sets the border thickness.
///
[EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The thickness of the border."), Limit(0)]
public float BorderThickness { get; set; } = 1.0f;
///
/// Gets or sets the color of the border (Transparent if not used).
///
[EditorDisplay("Border Style"), EditorOrder(2012), Tooltip("The color of the border (Transparent if not used).")]
public Color BorderColor { get; set; }
///
/// Gets or sets the color of the border when control is focused (Transparent if not used).
///
[EditorDisplay("Border Style"), EditorOrder(2013), Tooltip("The color of the border when control is focused (Transparent if not used)")]
public Color BorderSelectedColor { get; set; }
///
/// Gets the size of the text (cached).
///
public Float2 TextSize => _textSize;
///
/// Occurs when target view offset gets changed.
///
public event Action TargetViewOffsetChanged;
///
/// Gets the current view offset (text scrolling offset). Includes the smoothing.
///
public Float2 ViewOffset => _viewOffset;
///
/// Gets or sets the target view offset (text scrolling offset).
///
[NoAnimate, NoSerialize, HideInEditor]
public Float2 TargetViewOffset
{
get => _targetViewOffset;
set
{
value = Float2.Round(value);
if (Float2.NearEqual(ref value, ref _targetViewOffset))
return;
_targetViewOffset = _viewOffset = value;
OnTargetViewOffsetChanged();
}
}
///
/// Gets a value indicating whether user is editing the text.
///
public bool IsEditing => _isEditing;
///
/// Gets or sets text property.
///
[EditorOrder(0), MultilineText, Tooltip("The entered text.")]
public string Text
{
get => _text;
set
{
// Skip set if user is editing value
if (_isEditing)
return;
SetText(value);
}
}
///
/// Sets the text (forced, even if user is editing it).
///
/// The value.
[NoAnimate]
public void SetText(string value)
{
// Prevent from null problems
if (value == null)
value = string.Empty;
// Filter text
if (value.IndexOf('\r') != -1)
value = value.Replace("\r", "");
// Filter text (handle backspace control character)
if (value.IndexOf(DelChar) != -1)
value = value.Replace(DelChar.ToString(), "");
// Clamp length
if (value.Length > MaxLength)
value = value.Substring(0, MaxLength);
// Ensure to use only single line
if (_isMultiline == false && value.Length > 0)
{
// Extract only the first line
value = value.GetLines()[0];
}
if (_text != value)
{
Deselect();
ResetViewOffset();
_text = value;
OnTextChanged();
}
}
///
/// Sets the text as it was entered by user (focus, change value, defocus).
///
/// The value.
[NoAnimate]
public void SetTextAsUser(string value)
{
Focus();
SetText(value);
RemoveFocus();
}
///
/// Gets length of the text
///
public int TextLength => _text.Length;
///
/// Gets the currently selected text in the control.
///
public string SelectedText
{
get
{
int length = SelectionLength;
return length > 0 ? _text.Substring(SelectionLeft, length) : string.Empty;
}
}
///
/// Gets the number of characters selected in the text box.
///
public int SelectionLength => Mathf.Abs(_selectionEnd - _selectionStart);
///
/// Gets or sets the selection range.
///
[EditorOrder(50)]
public TextRange SelectionRange
{
get => new TextRange(SelectionLeft, SelectionRight);
set => SetSelection(value.StartIndex, value.EndIndex, false);
}
///
/// Returns true if any text is selected, otherwise false
///
public bool HasSelection => SelectionLength > 0;
///
/// Index of the character on left edge of the selection
///
protected int SelectionLeft => Mathf.Min(_selectionStart, _selectionEnd);
///
/// Index of the character on right edge of the selection
///
protected int SelectionRight => Mathf.Max(_selectionStart, _selectionEnd);
///
/// Gets current caret position (index of the character)
///
protected int CaretPosition => _selectionEnd;
///
/// Calculates the caret rectangle.
///
protected Rectangle CaretBounds
{
get
{
const float caretWidth = 1.2f;
Float2 caretPos = GetCharPosition(CaretPosition, out var height);
return new Rectangle(
caretPos.X - (caretWidth * 0.5f),
caretPos.Y,
caretWidth,
height);
}
}
///
/// Gets rectangle with area for text
///
protected virtual Rectangle TextRectangle => new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2);
///
/// Gets rectangle used to clip text
///
protected virtual Rectangle TextClipRectangle => new Rectangle(1, 1, Width - 2, Height - 2);
///
/// Initializes a new instance of the class.
///
protected TextBoxBase()
: this(false, 0, 0)
{
}
///
/// Initializes a new instance of the class.
///
/// Enable/disable multiline text input support.
/// The control position X coordinate.
/// The control position Y coordinate.
/// The control width.
protected TextBoxBase(bool isMultiline, float x, float y, float width = 120)
: base(x, y, width, DefaultHeight)
{
_isMultiline = isMultiline;
_maxLength = 2147483646;
_selectionStart = _selectionEnd = -1;
var style = Style.Current;
CaretColor = style.Foreground;
BorderColor = Color.Transparent;
BorderSelectedColor = style.BackgroundSelected;
BackgroundColor = style.TextBoxBackground;
BackgroundSelectedColor = style.TextBoxBackgroundSelected;
}
///
/// Clears all text from the text box control.
///
public virtual void Clear()
{
Text = string.Empty;
}
///
/// Clear selection range
///
public virtual void ClearSelection()
{
OnSelectingEnd();
SetSelection(-1);
}
///
/// Resets the view offset (text scroll view).
///
public virtual void ResetViewOffset()
{
TargetViewOffset = Float2.Zero;
}
///
/// Called when target view offset gets changed.
///
protected virtual void OnTargetViewOffsetChanged()
{
TargetViewOffsetChanged?.Invoke();
}
///
/// Copies the current selection in the text box to the Clipboard.
///
public virtual void Copy()
{
var selectedText = SelectedText;
if (selectedText.Length > 0)
{
// Copy selected text
Clipboard.Text = selectedText;
}
}
///
/// Moves the current selection in the text box to the Clipboard.
///
public virtual void Cut()
{
var selectedText = SelectedText;
if (selectedText.Length > 0)
{
// Copy selected text
Clipboard.Text = selectedText;
if (IsReadOnly)
return;
// Remove selection
int left = SelectionLeft;
_text = _text.Remove(left, SelectionLength);
SetSelection(left);
OnTextChanged();
}
}
///
/// Replaces the current selection in the text box with the contents of the Clipboard.
///
public virtual void Paste()
{
if (IsReadOnly)
return;
// Get clipboard data
var clipboardText = Clipboard.Text;
if (string.IsNullOrEmpty(clipboardText))
return;
Insert(clipboardText);
}
///
/// Duplicates the current selection in the text box.
///
public virtual void Duplicate()
{
if (IsReadOnly)
return;
var selectedText = SelectedText;
if (selectedText.Length > 0)
{
var right = SelectionRight;
SetSelection(right);
Insert(selectedText);
SetSelection(right, right + selectedText.Length);
}
}
///
/// Ensures that the caret is visible in the TextBox window, by scrolling the TextBox control surface if necessary.
///
public virtual void ScrollToCaret()
{
// If it's empty
if (_text.Length == 0)
{
TargetViewOffset = Float2.Zero;
return;
}
// If it's not selected
if (_selectionStart == -1 && _selectionEnd == -1)
{
return;
}
Rectangle caretBounds = CaretBounds;
Rectangle textArea = TextRectangle;
// Update view offset (caret needs to be in a view)
var caretInView = caretBounds.Location - _targetViewOffset;
var clampedCaretInView = Float2.Clamp(caretInView, textArea.UpperLeft, textArea.BottomRight);
TargetViewOffset += caretInView - clampedCaretInView;
}
///
/// Selects all text in the text box.
///
public virtual void SelectAll()
{
if (TextLength > 0)
{
SetSelection(0, TextLength);
}
}
///
/// Sets the selection to empty value.
///
public virtual void Deselect()
{
SetSelection(-1);
}
///
/// Gets the character the index at point (eg. mouse location in control-space).
///
/// The location (in control-space).
/// The character index under the location
public virtual int CharIndexAtPoint(ref Float2 location)
{
return HitTestText(location + _viewOffset);
}
///
/// Inserts the specified character (at the current selection).
///
/// The character.
public virtual void Insert(char c)
{
Insert(c.ToString());
}
///
/// Inserts the specified text (at the current selection).
///
/// The string.
public virtual void Insert(string str)
{
if (IsReadOnly)
return;
// Filter text
if (str.IndexOf('\r') != -1)
str = str.Replace("\r", "");
if (str.IndexOf(DelChar) != -1)
str = str.Replace(DelChar.ToString(), "");
if (!IsMultiline && str.IndexOf('\n') != -1)
str = str.Replace("\n", "");
int selectionLength = SelectionLength;
int charactersLeft = MaxLength - _text.Length + selectionLength;
Assert.IsTrue(charactersLeft >= 0);
if (charactersLeft == 0)
return;
if (charactersLeft < str.Length)
str = str.Substring(0, charactersLeft);
if (TextLength == 0)
{
_text = str;
SetSelection(TextLength);
}
else
{
var left = SelectionLeft >= 0 ? SelectionLeft : 0;
if (HasSelection)
{
_text = _text.Remove(left, selectionLength);
SetSelection(left);
}
_text = _text.Insert(left, str);
SetSelection(left + str.Length);
}
OnTextChanged();
}
///
/// Moves the caret right.
///
/// Shift is held.
/// Control is held.
protected virtual void MoveRight(bool shift, bool ctrl)
{
if (HasSelection && !shift)
{
SetSelection(SelectionRight);
}
else if (SelectionRight < TextLength)
{
int position;
if (ctrl)
position = FindNextWordBegin();
else
position = _selectionEnd + 1;
if (shift)
{
SetSelection(_selectionStart, position);
}
else
{
SetSelection(position);
}
}
}
///
/// Moves the caret left.
///
/// Shift is held.
/// Control is held.
protected virtual void MoveLeft(bool shift, bool ctrl)
{
if (HasSelection && !shift)
{
SetSelection(SelectionLeft);
}
else if (SelectionLeft >= 0)
{
int position;
if (ctrl)
position = FindPrevWordBegin();
else
position = Mathf.Max(_selectionEnd - 1, 0);
if (shift)
{
SetSelection(_selectionStart, position);
}
else
{
SetSelection(position);
}
}
}
///
/// Moves the caret down.
///
/// Shift is held.
/// Control is held.
protected virtual void MoveDown(bool shift, bool ctrl)
{
if (HasSelection && !shift)
{
SetSelection(SelectionRight);
}
else
{
int position = FindLineDownChar(CaretPosition);
if (shift)
{
SetSelection(_selectionStart, position);
}
else
{
SetSelection(position);
}
}
}
///
/// Moves the caret up.
///
/// Shift is held.
/// Control is held.
protected virtual void MoveUp(bool shift, bool ctrl)
{
if (HasSelection && !shift)
{
SetSelection(SelectionLeft);
}
else
{
int position = FindLineUpChar(CaretPosition);
if (shift)
{
SetSelection(_selectionStart, position);
}
else
{
SetSelection(position);
}
}
}
///
/// Sets the caret position.
///
/// The caret position.
/// If set to true with auto-scroll.
protected virtual void SetSelection(int caret, bool withScroll = true)
{
SetSelection(caret, caret);
}
///
/// Sets the selection.
///
/// The selection start character.
/// The selection end character.
/// If set to true with auto-scroll.
protected virtual void SetSelection(int start, int end, bool withScroll = true)
{
// Update parameters
int textLength = _text.Length;
_selectionStart = Mathf.Clamp(start, -1, textLength);
_selectionEnd = Mathf.Clamp(end, -1, textLength);
if (withScroll)
{
// Update view on caret modified
ScrollToCaret();
// Reset caret and selection animation
_animateTime = 0.0f;
}
}
private int FindNextWordBegin()
{
int textLength = TextLength;
int caretPos = CaretPosition;
if (caretPos + 1 >= textLength)
return textLength;
int spaceLoc = _text.IndexOfAny(Separators, caretPos + 1);
if (spaceLoc == -1)
spaceLoc = textLength;
else
spaceLoc++;
return spaceLoc;
}
private int FindPrevWordBegin()
{
int caretPos = CaretPosition;
if (caretPos - 2 < 0)
return 0;
int spaceLoc = _text.LastIndexOfAny(Separators, caretPos - 2);
if (spaceLoc == -1)
spaceLoc = 0;
else
spaceLoc++;
return spaceLoc;
}
private int FindPrevLineBegin()
{
int caretPos = CaretPosition;
if (caretPos - 2 < 0)
return 0;
int newLineLoc = _text.LastIndexOf('\n', caretPos - 2);
if (newLineLoc == -1)
newLineLoc = 0;
else
newLineLoc++;
return newLineLoc;
}
private int FindLineDownChar(int index)
{
if (!IsMultiline)
return 0;
var location = GetCharPosition(index, out var height);
location.Y += height;
return HitTestText(location);
}
private int FindLineUpChar(int index)
{
if (!IsMultiline)
return _text.Length;
var location = GetCharPosition(index, out var height);
location.Y -= height;
return HitTestText(location);
}
private void RemoveFocus()
{
if (Parent != null)
Parent.Focus();
else
Defocus();
}
///
/// Calculates total text size. Called by to cache the text size.
///
/// The total text size.
public abstract Float2 GetTextSize();
///
/// Calculates character position for given character index.
///
/// The text position to get it's coordinates.
/// The character height (at the given character position).
/// The character position (upper left corner which can be used for a caret position).
public abstract Float2 GetCharPosition(int index, out float height);
///
/// Calculates hit character index at given location.
///
/// The location to test.
/// The selected character position index (can be equal to text length if location is outside of the layout rectangle).
public abstract int HitTestText(Float2 location);
///
/// Called when is multiline gets changed.
///
protected virtual void OnIsMultilineChanged()
{
}
///
/// Called when is read only gets changed.
///
protected virtual void OnIsReadOnlyChanged()
{
}
///
/// Called when is selectable flag gets changed.
///
protected virtual void OnIsSelectableChanged()
{
}
///
/// Action called when user starts text selecting
///
protected virtual void OnSelectingBegin()
{
if (!_isSelecting)
{
// Set flag
_isSelecting = true;
// Start tracking mouse
StartMouseCapture();
}
}
///
/// Action called when user ends text selecting
///
protected virtual void OnSelectingEnd()
{
if (_isSelecting)
{
// Clear flag
_isSelecting = false;
// Stop tracking mouse
EndMouseCapture();
}
}
///
/// Action called when user starts text editing
///
protected virtual void OnEditBegin()
{
if (_isEditing)
return;
_isEditing = true;
_onStartEditValue = _text;
// Reset caret visibility
_animateTime = 0;
}
///
/// Action called when user ends text editing.
///
protected virtual void OnEditEnd()
{
if (!_isEditing)
return;
_isEditing = false;
if (_onStartEditValue != _text)
{
_onStartEditValue = _text;
EditEnd?.Invoke();
TextBoxEditEnd?.Invoke(this);
}
_onStartEditValue = string.Empty;
ClearSelection();
ResetViewOffset();
}
///
/// Action called when text gets modified.
///
protected virtual void OnTextChanged()
{
_textSize = GetTextSize();
TextChanged?.Invoke();
}
///
public override void Update(float deltaTime)
{
bool isDeltaSlow = deltaTime > (1 / 20.0f);
_animateTime += deltaTime;
// Animate view offset
_viewOffset = isDeltaSlow ? _targetViewOffset : Float2.Lerp(_viewOffset, _targetViewOffset, deltaTime * 20.0f);
// Clicking outside of the text box will end text editing. Left will keep the value, right will restore original value
if (_isEditing && EndEditOnClick)
{
if (!IsMouseOver && RootWindow.ContainsFocus)
{
if (Input.GetMouseButtonDown(MouseButton.Left))
{
RemoveFocus();
}
else if (Input.GetMouseButtonDown(MouseButton.Right))
{
RestoreTextFromStart();
RemoveFocus();
}
}
}
base.Update(deltaTime);
}
///
/// Restores the Text from the start.
///
public void RestoreTextFromStart()
{
// Restore text from start
SetSelection(-1);
_text = _onStartEditValue;
OnTextChanged();
}
///
public override void OnGotFocus()
{
base.OnGotFocus();
if (IsReadOnly)
return;
OnEditBegin();
}
///
public override void OnLostFocus()
{
base.OnLostFocus();
if (IsReadOnly)
return;
OnEditEnd();
}
///
public override void OnEndMouseCapture()
{
// Clear flag
_isSelecting = false;
}
///
public override void NavigationFocus()
{
base.NavigationFocus();
if (IsNavFocused)
SelectAll();
}
///
public override void OnSubmit()
{
OnEditEnd();
if (IsNavFocused)
{
OnEditBegin();
SelectAll();
}
base.OnSubmit();
}
///
public override void OnMouseEnter(Float2 location)
{
if (_isEditing && _changeCursor)
Cursor = CursorType.IBeam;
base.OnMouseEnter(location);
}
///
public override void OnMouseLeave()
{
if (Cursor == CursorType.IBeam)
Cursor = CursorType.Default;
base.OnMouseLeave();
}
///
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
if (_isSelecting)
{
// Find char index at current mouse location
int currentIndex = CharIndexAtPoint(ref location);
// Modify selection end
SetSelection(_selectionStart, currentIndex);
}
if (Cursor == CursorType.Default && _isEditing && _changeCursor)
Cursor = CursorType.IBeam;
}
///
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && _text.Length > 0 && _isSelectable)
{
Focus();
OnSelectingBegin();
// Calculate char index under the mouse location
var hitPos = CharIndexAtPoint(ref location);
// Select range with shift
if (_selectionStart != -1 && RootWindow.GetKey(KeyboardKeys.Shift) && SelectionLength == 0)
{
if (hitPos < _selectionStart)
SetSelection(hitPos, _selectionStart);
else
SetSelection(_selectionStart, hitPos);
}
else
{
SetSelection(hitPos);
}
if (Cursor == CursorType.Default && _changeCursor)
Cursor = CursorType.IBeam;
return true;
}
if (button == MouseButton.Left && !IsFocused)
{
Focus();
if (_changeCursor)
Cursor = CursorType.IBeam;
return true;
}
return false;
}
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
return true;
if (button == MouseButton.Left && _isSelectable)
{
OnSelectingEnd();
return true;
}
return false;
}
///
public override bool OnMouseWheel(Float2 location, float delta)
{
if (base.OnMouseWheel(location, delta))
return true;
// Multiline scroll
if (IsMultiline && _text.Length != 0 && IsMultilineScrollable)
{
TargetViewOffset = Float2.Clamp(_targetViewOffset - new Float2(0, delta * 10.0f), Float2.Zero, new Float2(_targetViewOffset.X, _textSize.Y));
return true;
}
// No event handled
return false;
}
///
public override bool OnCharInput(char c)
{
if (base.OnCharInput(c))
return true;
if (IsReadOnly)
return false;
Insert(c);
return true;
}
///
public override void OnKeyUp(KeyboardKeys key)
{
base.OnKeyUp(key);
KeyUp?.Invoke(key);
}
///
public override bool OnKeyDown(KeyboardKeys key)
{
var window = Root;
bool shiftDown = window.GetKey(KeyboardKeys.Shift);
bool ctrDown = window.GetKey(KeyboardKeys.Control);
KeyDown?.Invoke(key);
switch (key)
{
case KeyboardKeys.ArrowRight:
MoveRight(shiftDown, ctrDown);
return true;
case KeyboardKeys.ArrowLeft:
MoveLeft(shiftDown, ctrDown);
return true;
case KeyboardKeys.ArrowUp:
MoveUp(shiftDown, ctrDown);
return true;
case KeyboardKeys.ArrowDown:
MoveDown(shiftDown, ctrDown);
return true;
case KeyboardKeys.C:
if (ctrDown)
{
Copy();
return true;
}
break;
case KeyboardKeys.V:
if (ctrDown)
{
Paste();
return true;
}
break;
case KeyboardKeys.D:
if (ctrDown)
{
Duplicate();
return true;
}
break;
case KeyboardKeys.X:
if (ctrDown)
{
Cut();
return true;
}
break;
case KeyboardKeys.A:
if (ctrDown)
{
SelectAll();
return true;
}
break;
case KeyboardKeys.Backspace:
{
if (IsReadOnly)
return true;
if (ctrDown)
{
int prevWordBegin = FindPrevWordBegin();
_text = _text.Remove(prevWordBegin, CaretPosition - prevWordBegin);
SetSelection(prevWordBegin);
OnTextChanged();
return true;
}
int left = SelectionLeft;
if (HasSelection)
{
_text = _text.Remove(left, SelectionLength);
SetSelection(left);
OnTextChanged();
}
else if (CaretPosition > 0)
{
left -= 1;
_text = _text.Remove(left, 1);
SetSelection(left);
OnTextChanged();
}
return true;
}
case KeyboardKeys.Delete:
{
if (IsReadOnly)
return true;
int left = SelectionLeft;
if (HasSelection)
{
_text = _text.Remove(left, SelectionLength);
SetSelection(left);
OnTextChanged();
}
else if (TextLength > 0 && left < TextLength)
{
_text = _text.Remove(left, 1);
SetSelection(left);
OnTextChanged();
}
return true;
}
case KeyboardKeys.Escape:
{
RestoreTextFromStart();
if (!IsNavFocused)
RemoveFocus();
return true;
}
case KeyboardKeys.Return:
if (IsMultiline)
{
// Insert new line
Insert('\n');
}
else if (!IsNavFocused)
{
// End editing
RemoveFocus();
}
else
return false;
return true;
case KeyboardKeys.Home:
if (shiftDown)
{
// Select text from the current cursor point back to the beginning of the line
if (_selectionStart != -1)
{
SetSelection(FindPrevLineBegin(), _selectionStart);
}
}
else
{
// Move caret to the first character
SetSelection(0);
}
return true;
case KeyboardKeys.End:
{
// Move caret after last character
SetSelection(TextLength);
return true;
}
case KeyboardKeys.Tab:
// Don't process
return false;
}
return true;
}
}
}