22 #region IKeyboardSubscriber
24 private Keys pressedKey;
34 if (key == Keys.Back) Back();
35 if (key == Keys.Delete) Delete();
36 if (key == Keys.Left) MoveLeft();
37 if (key == Keys.Right) MoveRight();
56 Text = Text.Insert(CaretPos, text);
57 CaretPos = CaretPos + 1;
58 OnTextAdded?.Invoke(text);
75 if (pressedKey == Keys.A) SelectAll();
76 if (pressedKey == Keys.C) Copy();
77 if (pressedKey == Keys.V) Paste();
103 public void SelectAll() => Select(0, Text.Length);
107 if (Selection.IsEmpty)
return;
108 selectionHandle.Grabbed =
false;
109 Clipboard.SetText(Text.SubstringSafe(Selection.Start, Selection.End));
117 public void MoveLeft()
120 Selection = IntRange.Zero;
122 public void MoveRight()
125 Selection = IntRange.Zero;
143 TextInputState oldState = State;
144 if (!Selection.IsEmpty) CutSelection();
147 string s1 = oldState.Text.SubstringSafe(0, oldState.CaretPos - 1);
148 string s2 = oldState.Text.SubstringSafe(oldState.CaretPos);
150 CaretPos = oldState.CaretPos - 1;
157 TextInputState oldState = State;
158 if (!Selection.IsEmpty) CutSelection();
161 string s1 = oldState.Text.SubstringSafe(0, oldState.CaretPos);
162 string s2 = oldState.Text.SubstringSafe(oldState.CaretPos + 1);
169 public void CutSelection()
171 if (Selection.IsEmpty)
return;
172 selectionHandle.Grabbed =
false;
173 string s1 = Text.SubstringSafe(0, Selection.Start);
174 string s2 = Text.SubstringSafe(Selection.End);
176 CaretPos = Selection.Start;
177 Selection = IntRange.Zero;
181 internal int SetCaretPos(Vector2 v)
183 int newCaretPos = TextComponent.
CaretIndex(v.X);
184 CaretPos = newCaretPos;
190 internal class SelectionHandle
193 public int lastSelectedPos;
195 internal SelectionHandle selectionHandle =
new SelectionHandle();
197 internal record
struct TextInputState(string Text, IntRange Selection, int CaretPos)
199 public string Text {
get; init; } = Text ??
"";
201 private TextInputState state;
internal TextInputState State
206 state = ValidateState(value);
211 internal TextInputState ValidateState(TextInputState state)
215 string newText = state.Text;
217 IntRange newSelection =
new IntRange(
218 state.Selection.Start.Fit(0, newText.Length),
219 state.Selection.End.Fit(0, newText.Length)
222 int newCaretPos = state.CaretPos.Fit(0, newText.Length);
224 return new TextInputState(newText, newSelection, newCaretPos);
227 internal void ApplyState(TextInputState state)
229 TextComponent.Text = state.Text;
231 SelectionOverlay.Visible = !state.Selection.IsEmpty;
232 CaretIndicatorVisible = Focused && !SelectionOverlay.
Visible;
234 if (!state.Selection.IsEmpty)
236 SelectionOverlay.Absolute = SelectionOverlay.Absolute with
238 Left = TextComponent.
CaretPos(state.Selection.Start),
239 Width = TextComponent.
CaretPos(state.Selection.End) - TextComponent.
CaretPos(state.Selection.Start),
243 CaretIndicator.Absolute = CaretIndicator.Absolute with
245 Left = TextComponent.
CaretPos(state.CaretPos),
251 private bool valid =
true;
public bool Valid
256 if (valid == value)
return;
262 public Type VatidationType {
get;
set; }
263 public bool IsValidText(
string text)
265 if (VatidationType ==
null)
return true;
267 if (VatidationType == typeof(
string))
return true;
268 if (VatidationType == typeof(Color))
return true;
269 if (VatidationType == typeof(
bool))
return bool.TryParse(text, out _);
270 if (VatidationType == typeof(
int))
return int.TryParse(text, out _);
271 if (VatidationType == typeof(
float))
return float.TryParse(text, out _);
272 if (VatidationType == typeof(
double))
return double.TryParse(text, out _);
285 internal CUITextBlock TextComponent;
293 State =
new TextInputState(value, State.Selection, State.CaretPos);
295 bool isvalid = IsValidText(value);
298 OnTextChanged?.Invoke(State.Text);
304 internal CUIComponent SelectionOverlay;
305 public IntRange Selection
307 get => State.Selection;
308 set => State =
new TextInputState(State.Text, value, State.CaretPos);
310 public void Select(
int start,
int end) => Selection =
new IntRange(start, end);
313 public bool CaretIndicatorVisible {
get;
set; }
314 public double CaretBlinkInterval {
get;
set; } = 0.5;
315 internal CUIComponent CaretIndicator;
318 get => State.CaretPos;
319 set => State =
new TextInputState(State.Text, State.Selection, value);
326 public void UpdateBorderColor()
332 Style[
"Border"] =
"CUIPalette.Input.Focused";
336 Style[
"Border"] =
"CUIPalette.Input.Border";
341 Style[
"Border"] =
"CUIPalette.Input.Invalid";
345 public float TextScale
347 get => TextComponent?.TextScale ?? 0;
348 set => TextComponent.TextScale = value;
350 public Color TextColor
352 get => TextComponent?.TextColor ?? Color.White;
355 if (TextComponent !=
null)
357 TextComponent.TextColor = value;
362 public event Action<string> OnTextChanged;
363 public Action<string> AddOnTextChanged {
set => OnTextChanged += value; }
364 public event Action<string> OnTextAdded;
365 public Action<string> AddOnTextAdded {
set => OnTextAdded += value; }
367 public override void Draw(SpriteBatch spriteBatch)
371 CaretIndicator.Visible = CaretIndicatorVisible && Timing.TotalTime % CaretBlinkInterval > CaretBlinkInterval / 2;
375 base.Draw(spriteBatch);
384 public CUITextInput() : base()
386 AbsoluteMin =
new CUINullRect(w: 50, h: 22);
389 Border.Thickness = 2;
391 ConsumeMouseClicks =
true;
392 ConsumeDragAndDrop =
true;
396 this[
"TextComponent"] = TextComponent =
new CUITextBlock()
399 Relative =
new CUINullRect(0, 0, 1, 1),
400 TextAlign = CUIAnchor.CenterLeft,
401 Style =
new CUIStyle(){
402 {
"Padding",
"[2,2]"},
403 {
"TextColor",
"CUIPalette.Input.Text"},
407 this[
"SelectionOverlay"] = SelectionOverlay =
new CUIComponent()
409 Style =
new CUIStyle(){
410 {
"BackgroundColor",
"CUIPalette.Input.Selection"},
411 {
"Border",
"Transparent"},
414 Ghost =
new CUIBool2(
true,
true),
418 this[
"CaretIndicator"] = CaretIndicator =
new CUIComponent()
420 Style =
new CUIStyle(){
421 {
"BackgroundColor",
"CUIPalette.Input.Caret"},
422 {
"Border",
"Transparent"},
424 Relative =
new CUINullRect(y: 0.1f, h: 0.8f),
426 Ghost =
new CUIBool2(
true,
true),
433 string value = o.ToString();
434 State =
new TextInputState(value, State.Selection, State.CaretPos);
435 Valid = IsValidText(value);
443 CaretIndicator.Visible =
true;
449 Selection = IntRange.Zero;
450 CaretIndicator.Visible =
false;
453 OnMouseDown += (e) =>
455 int newCaretPos = SetCaretPos(e.MousePosition -
Real.Position);
456 Selection = IntRange.Zero;
457 selectionHandle.lastSelectedPos = newCaretPos;
458 selectionHandle.Grabbed =
true;
461 OnMouseMove += (e) =>
463 if (selectionHandle.Grabbed)
465 int nextCaretPos = SetCaretPos(e.MousePosition -
Real.Position);
466 Selection =
new IntRange(selectionHandle.lastSelectedPos, nextCaretPos);
470 OnDClick += (e) => SelectAll();
472 if (CUI.Main is not
null)
474 CUI.Main.Global.OnMouseUp += (e) => selectionHandle.Grabbed =
false;
int CaretIndex(float x)
Tndex of caret if there was one Used in CUITextInput, you don't need this.
float CaretPos(int i)
X coordinate of caret if there was one Used in CUITextInput, you don't need this.