using System; using System.Drawing; using Gwen.Input; namespace Gwen.Control { /// /// Text box (editable). /// public class TextBox : Label { private bool m_SelectAll; private int m_CursorPos; private int m_CursorEnd; private Rectangle m_SelectionBounds; private Rectangle m_CaretBounds; private float m_LastInputTime; protected override bool AccelOnlyFocus { get { return true; } } protected override bool NeedsInputChars { get { return true; } } /// /// Determines whether text should be selected when the control is focused. /// public bool SelectAllOnFocus { get { return m_SelectAll; } set { m_SelectAll = value; if (value) OnSelectAll(this); } } /// /// Indicates whether the text has active selection. /// public bool HasSelection { get { return m_CursorPos != m_CursorEnd; } } /// /// Invoked when the text has changed. /// public event GwenEventHandler TextChanged; /// /// Invoked when the submit key has been pressed. /// public event GwenEventHandler SubmitPressed; /// /// Current cursor position (character index). /// public int CursorPos { get { return m_CursorPos; } set { if (m_CursorPos == value) return; m_CursorPos = value; RefreshCursorBounds(); } } public int CursorEnd { get { return m_CursorEnd; } set { if (m_CursorEnd == value) return; m_CursorEnd = value; RefreshCursorBounds(); } } /// /// Determines whether the control can insert text at a given cursor position. /// /// Text to check. /// Cursor position. /// True if allowed. protected virtual bool IsTextAllowed(String text, int position) { return true; } /// /// Initializes a new instance of the class. /// /// Parent control. public TextBox(Base parent) : base(parent) { SetSize(200, 20); MouseInputEnabled = true; KeyboardInputEnabled = true; Alignment = Pos.Left | Pos.CenterV; TextPadding = new Padding(4, 2, 4, 2); m_CursorPos = 0; m_CursorEnd = 0; m_SelectAll = false; TextColor = Color.FromArgb(255, 50, 50, 50); // TODO: From Skin IsTabable = true; AddAccelerator("Ctrl + C", OnCopy); AddAccelerator("Ctrl + X", OnCut); AddAccelerator("Ctrl + V", OnPaste); AddAccelerator("Ctrl + A", OnSelectAll); } /// /// Renders the focus overlay. /// /// Skin to use. protected override void RenderFocus(Skin.Base skin) { // nothing } /// /// Handler for text changed event. /// protected override void OnTextChanged() { base.OnTextChanged(); if (m_CursorPos > TextLength) m_CursorPos = TextLength; if (m_CursorEnd > TextLength) m_CursorEnd = TextLength; if (TextChanged != null) TextChanged.Invoke(this); } /// /// Handler for character input event. /// /// Character typed. /// /// True if handled. /// protected override bool OnChar(char chr) { base.OnChar(chr); if (chr == '\t') return false; InsertText(chr.ToString()); return true; } /// /// Inserts text at current cursor position, erasing selection if any. /// /// Text to insert. void InsertText(String text) { // TODO: Make sure fits (implement maxlength) if (HasSelection) { EraseSelection(); } if (m_CursorPos > TextLength) m_CursorPos = TextLength; if (!IsTextAllowed(text, m_CursorPos)) return; String str = Text; str = str.Insert(m_CursorPos, text); SetText(str); m_CursorPos += text.Length; m_CursorEnd = m_CursorPos; RefreshCursorBounds(); } /// /// Renders the control using specified skin. /// /// Skin to use. protected override void Render(Skin.Base skin) { base.Render(skin); if (ShouldDrawBackground) skin.DrawTextBox(this); if (!HasFocus) return; // Draw selection.. if selected.. if (m_CursorPos != m_CursorEnd) { skin.Renderer.DrawColor = Color.FromArgb(200, 50, 170, 255); skin.Renderer.DrawFilledRect(m_SelectionBounds); } // Draw caret float time = Platform.Neutral.GetTimeInSeconds() - m_LastInputTime; if ((time % 1.0f) <= 0.5f) { skin.Renderer.DrawColor = Color.Black; skin.Renderer.DrawFilledRect(m_CaretBounds); } } protected virtual void RefreshCursorBounds() { m_LastInputTime = Platform.Neutral.GetTimeInSeconds(); MakeCaretVisible(); Point pA = GetCharacterPosition(m_CursorPos); Point pB = GetCharacterPosition(m_CursorEnd); m_SelectionBounds.X = Math.Min(pA.X, pB.X); m_SelectionBounds.Y = TextY - 1; m_SelectionBounds.Width = Math.Max(pA.X, pB.X) - m_SelectionBounds.X; m_SelectionBounds.Height = TextHeight + 2; m_CaretBounds.X = pA.X; m_CaretBounds.Y = TextY - 1; m_CaretBounds.Width = 1; m_CaretBounds.Height = TextHeight + 2; Redraw(); } /// /// Handler for Paste event. /// /// Source control. protected override void OnPaste(Base from) { base.OnPaste(from); InsertText(Platform.Neutral.GetClipboardText()); } /// /// Handler for Copy event. /// /// Source control. protected override void OnCopy(Base from) { if (!HasSelection) return; base.OnCopy(from); Platform.Neutral.SetClipboardText(GetSelection()); } /// /// Handler for Cut event. /// /// Source control. protected override void OnCut(Base from) { if (!HasSelection) return; base.OnCut(from); Platform.Neutral.SetClipboardText(GetSelection()); EraseSelection(); } /// /// Handler for Select All event. /// /// Source control. protected override void OnSelectAll(Base from) { //base.OnSelectAll(from); m_CursorEnd = 0; m_CursorPos = TextLength; RefreshCursorBounds(); } /// /// Handler invoked on mouse double click (left) event. /// /// X coordinate. /// Y coordinate. protected override void OnMouseDoubleClickedLeft(int x, int y) { //base.OnMouseDoubleClickedLeft(x, y); OnSelectAll(this); } /// /// Handler for Return keyboard event. /// /// Indicates whether the key was pressed or released. /// /// True if handled. /// protected override bool OnKeyReturn(bool down) { base.OnKeyReturn(down); if (down) return true; OnReturn(); // Try to move to the next control, as if tab had been pressed OnKeyTab(true); // If we still have focus, blur it. if (HasFocus) { Blur(); } return true; } /// /// Handler for Backspace keyboard event. /// /// Indicates whether the key was pressed or released. /// /// True if handled. /// protected override bool OnKeyBackspace(bool down) { base.OnKeyBackspace(down); if (!down) return true; if (HasSelection) { EraseSelection(); return true; } if (m_CursorPos == 0) return true; DeleteText(m_CursorPos - 1, 1); return true; } /// /// Handler for Delete keyboard event. /// /// Indicates whether the key was pressed or released. /// /// True if handled. /// protected override bool OnKeyDelete(bool down) { base.OnKeyDelete(down); if (!down) return true; if (HasSelection) { EraseSelection(); return true; } if (m_CursorPos >= TextLength) return true; DeleteText(m_CursorPos, 1); return true; } /// /// Handler for Left Arrow keyboard event. /// /// Indicates whether the key was pressed or released. /// /// True if handled. /// protected override bool OnKeyLeft(bool down) { base.OnKeyLeft(down); if (!down) return true; if (m_CursorPos > 0) m_CursorPos--; if (!Input.InputHandler.IsShiftDown) { m_CursorEnd = m_CursorPos; } RefreshCursorBounds(); return true; } /// /// Handler for Right Arrow keyboard event. /// /// Indicates whether the key was pressed or released. /// /// True if handled. /// protected override bool OnKeyRight(bool down) { base.OnKeyRight(down); if (!down) return true; if (m_CursorPos < TextLength) m_CursorPos++; if (!Input.InputHandler.IsShiftDown) { m_CursorEnd = m_CursorPos; } RefreshCursorBounds(); return true; } /// /// Handler for Home keyboard event. /// /// Indicates whether the key was pressed or released. /// /// True if handled. /// protected override bool OnKeyHome(bool down) { base.OnKeyHome(down); if (!down) return true; m_CursorPos = 0; if (!Input.InputHandler.IsShiftDown) { m_CursorEnd = m_CursorPos; } RefreshCursorBounds(); return true; } /// /// Handler for End keyboard event. /// /// Indicates whether the key was pressed or released. /// /// True if handled. /// protected override bool OnKeyEnd(bool down) { base.OnKeyEnd(down); m_CursorPos = TextLength; if (!Input.InputHandler.IsShiftDown) { m_CursorEnd = m_CursorPos; } RefreshCursorBounds(); return true; } /// /// Returns currently selected text. /// /// Current selection. public String GetSelection() { if (!HasSelection) return String.Empty; int start = Math.Min(m_CursorPos, m_CursorEnd); int end = Math.Max(m_CursorPos, m_CursorEnd); String str = Text; return str.Substring(start, end - start); } /// /// Deletes text. /// /// Starting cursor position. /// Length in characters. public virtual void DeleteText(int startPos, int length) { String str = Text; str = str.Remove(startPos, length); SetText(str); if (m_CursorPos > startPos) { CursorPos = m_CursorPos - length; } CursorEnd = m_CursorPos; } /// /// Deletes selected text. /// public virtual void EraseSelection() { int start = Math.Min(m_CursorPos, m_CursorEnd); int end = Math.Max(m_CursorPos, m_CursorEnd); DeleteText(start, end - start); // Move the cursor to the start of the selection, // since the end is probably outside of the string now. m_CursorPos = start; m_CursorEnd = start; } /// /// Handler invoked on mouse click (left) event. /// /// X coordinate. /// Y coordinate. /// If set to true mouse button is down. protected override void OnMouseClickedLeft(int x, int y, bool down) { base.OnMouseClickedLeft(x, y, down); if (m_SelectAll) { OnSelectAll(this); //m_SelectAll = false; return; } int c = GetClosestCharacter(x, y); if (down) { CursorPos = c; if (!Input.InputHandler.IsShiftDown) CursorEnd = c; InputHandler.MouseFocus = this; } else { if (InputHandler.MouseFocus == this) { CursorPos = c; InputHandler.MouseFocus = null; } } } /// /// Handler invoked on mouse moved event. /// /// X coordinate. /// Y coordinate. /// X change. /// Y change. protected override void OnMouseMoved(int x, int y, int dx, int dy) { base.OnMouseMoved(x, y, dx, dy); if (InputHandler.MouseFocus != this) return; int c = GetClosestCharacter(x, y); CursorPos = c; } protected virtual void MakeCaretVisible() { int caretPos = GetCharacterPosition(m_CursorPos).X - TextX; // If the caret is already in a semi-good position, leave it. { int realCaretPos = caretPos + TextX; if (realCaretPos > Width*0.1f && realCaretPos < Width*0.9f) return; } // The ideal position is for the caret to be right in the middle int idealx = (int)(-caretPos + Width * 0.5f); // Don't show too much whitespace to the right if (idealx + TextWidth < Width - TextPadding.Right) idealx = -TextWidth + (Width - TextPadding.Right); // Or the left if (idealx > TextPadding.Left) idealx = TextPadding.Left; SetTextPosition(idealx, TextY); } /// /// Lays out the control's interior according to alignment, padding, dock etc. /// /// Skin to use. protected override void Layout(Skin.Base skin) { base.Layout(skin); RefreshCursorBounds(); } /// /// Handler for the return key. /// protected virtual void OnReturn() { if (SubmitPressed != null) SubmitPressed.Invoke(this); } } }