Add quick interactions locally
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using EventInput;
|
||||
using System.Windows;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Text input
|
||||
/// </summary>
|
||||
public class CUITextInput : CUIComponent, IKeyboardSubscriber
|
||||
{
|
||||
|
||||
#region IKeyboardSubscriber
|
||||
|
||||
private Keys pressedKey;
|
||||
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
try
|
||||
{
|
||||
pressedKey = key;
|
||||
if (key == Keys.Back) Back();
|
||||
if (key == Keys.Delete) Delete();
|
||||
if (key == Keys.Left) MoveLeft();
|
||||
if (key == Keys.Right) MoveRight();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveTextInput(char inputChar) => ReceiveTextInput(inputChar.ToString());
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveTextInput(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
CutSelection();
|
||||
Text = Text.Insert(CaretPos, text);
|
||||
CaretPos = CaretPos + 1;
|
||||
OnTextAdded?.Invoke(text);
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||
//CUI.Log($"ReceiveTextInput {text}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Log(e);
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveCommandInput(char command)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pressedKey == Keys.A) SelectAll();
|
||||
if (pressedKey == Keys.C) Copy();
|
||||
if (pressedKey == Keys.V) Paste();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
}
|
||||
//CUI.Log($"ReceiveCommandInput {command}");
|
||||
}
|
||||
|
||||
//Alt+tab?
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public void ReceiveEditingInput(string text, int start, int length)
|
||||
{
|
||||
//CUI.Log($"ReceiveEditingInput {text} {start} {length}");
|
||||
}
|
||||
|
||||
//TODO mb should lose focus here
|
||||
/// <summary>
|
||||
/// From IKeyboardSubscriber, don't use it directly
|
||||
/// </summary>
|
||||
public bool Selected { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
public void SelectAll() => Select(0, Text.Length);
|
||||
|
||||
public void Copy()
|
||||
{
|
||||
if (Selection.IsEmpty) return;
|
||||
selectionHandle.Grabbed = false;
|
||||
Clipboard.SetText(Text.SubstringSafe(Selection.Start, Selection.End));
|
||||
}
|
||||
public void Paste()
|
||||
{
|
||||
ReceiveTextInput(Clipboard.GetText());
|
||||
}
|
||||
|
||||
public void AddText(string text) => ReceiveTextInput(text);
|
||||
public void MoveLeft()
|
||||
{
|
||||
CaretPos--;
|
||||
Selection = IntRange.Zero;
|
||||
}
|
||||
public void MoveRight()
|
||||
{
|
||||
CaretPos++;
|
||||
Selection = IntRange.Zero;
|
||||
}
|
||||
|
||||
// //TODO
|
||||
// public void SelectLeft()
|
||||
// {
|
||||
// if (Selection == IntRange.Zero) Selection = new IntRange(CaretPos - 1, CaretPos);
|
||||
// else Selection = new IntRange(Selection.Start - 1, Selection.End);
|
||||
// }
|
||||
// //TODO
|
||||
// public void SelectRight()
|
||||
// {
|
||||
// if (Selection == IntRange.Zero) Selection = new IntRange(CaretPos, CaretPos + 1);
|
||||
// else Selection = new IntRange(Selection.Start, Selection.End + 1);
|
||||
// }
|
||||
|
||||
public void Back()
|
||||
{
|
||||
TextInputState oldState = State;
|
||||
if (!Selection.IsEmpty) CutSelection();
|
||||
else
|
||||
{
|
||||
string s1 = oldState.Text.SubstringSafe(0, oldState.CaretPos - 1);
|
||||
string s2 = oldState.Text.SubstringSafe(oldState.CaretPos);
|
||||
Text = s1 + s2;
|
||||
CaretPos = oldState.CaretPos - 1;
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
TextInputState oldState = State;
|
||||
if (!Selection.IsEmpty) CutSelection();
|
||||
else
|
||||
{
|
||||
string s1 = oldState.Text.SubstringSafe(0, oldState.CaretPos);
|
||||
string s2 = oldState.Text.SubstringSafe(oldState.CaretPos + 1);
|
||||
Text = s1 + s2;
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||
//CaretPos = oldState.CaretPos;
|
||||
}
|
||||
}
|
||||
|
||||
public void CutSelection()
|
||||
{
|
||||
if (Selection.IsEmpty) return;
|
||||
selectionHandle.Grabbed = false;
|
||||
string s1 = Text.SubstringSafe(0, Selection.Start);
|
||||
string s2 = Text.SubstringSafe(Selection.End);
|
||||
Text = s1 + s2;
|
||||
CaretPos = Selection.Start;
|
||||
Selection = IntRange.Zero;
|
||||
if (Command != null) DispatchUp(new CUICommand(Command, State.Text));
|
||||
}
|
||||
|
||||
internal int SetCaretPos(Vector2 v)
|
||||
{
|
||||
int newCaretPos = TextComponent.CaretIndex(v.X);
|
||||
CaretPos = newCaretPos;
|
||||
return newCaretPos;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal class SelectionHandle
|
||||
{
|
||||
public bool Grabbed;
|
||||
public int lastSelectedPos;
|
||||
}
|
||||
internal SelectionHandle selectionHandle = new SelectionHandle();
|
||||
|
||||
internal record struct TextInputState(string Text, IntRange Selection, int CaretPos)
|
||||
{
|
||||
public string Text { get; init; } = Text ?? "";
|
||||
}
|
||||
private TextInputState state; internal TextInputState State
|
||||
{
|
||||
get => state;
|
||||
set
|
||||
{
|
||||
state = ValidateState(value);
|
||||
ApplyState(state);
|
||||
}
|
||||
}
|
||||
|
||||
internal TextInputState ValidateState(TextInputState state)
|
||||
{
|
||||
//return state with { CaretPos = state.CaretPos.Fit(0, state.Text.Length - 1) };
|
||||
|
||||
string newText = state.Text;
|
||||
|
||||
IntRange newSelection = new IntRange(
|
||||
state.Selection.Start.Fit(0, newText.Length),
|
||||
state.Selection.End.Fit(0, newText.Length)
|
||||
);
|
||||
|
||||
int newCaretPos = state.CaretPos.Fit(0, newText.Length);
|
||||
|
||||
return new TextInputState(newText, newSelection, newCaretPos);
|
||||
}
|
||||
|
||||
internal void ApplyState(TextInputState state)
|
||||
{
|
||||
TextComponent.Text = state.Text;
|
||||
|
||||
SelectionOverlay.Visible = !state.Selection.IsEmpty;
|
||||
CaretIndicatorVisible = Focused && !SelectionOverlay.Visible;
|
||||
|
||||
if (!state.Selection.IsEmpty)
|
||||
{
|
||||
SelectionOverlay.Absolute = SelectionOverlay.Absolute with
|
||||
{
|
||||
Left = TextComponent.CaretPos(state.Selection.Start),
|
||||
Width = TextComponent.CaretPos(state.Selection.End) - TextComponent.CaretPos(state.Selection.Start),
|
||||
};
|
||||
}
|
||||
|
||||
CaretIndicator.Absolute = CaretIndicator.Absolute with
|
||||
{
|
||||
Left = TextComponent.CaretPos(state.CaretPos),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool valid = true; public bool Valid
|
||||
{
|
||||
get => valid;
|
||||
set
|
||||
{
|
||||
if (valid == value) return;
|
||||
valid = value;
|
||||
UpdateBorderColor();
|
||||
}
|
||||
}
|
||||
|
||||
public Type VatidationType { get; set; }
|
||||
public bool IsValidText(string text)
|
||||
{
|
||||
if (VatidationType == null) return true;
|
||||
|
||||
if (VatidationType == typeof(string)) return true;
|
||||
if (VatidationType == typeof(Color)) return true;
|
||||
if (VatidationType == typeof(bool)) return bool.TryParse(text, out _);
|
||||
if (VatidationType == typeof(int)) return int.TryParse(text, out _);
|
||||
if (VatidationType == typeof(float)) return float.TryParse(text, out _);
|
||||
if (VatidationType == typeof(double)) return double.TryParse(text, out _);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO this is cringe
|
||||
// public override void Consume(object o)
|
||||
// {
|
||||
// string value = (string)o;
|
||||
// State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||
// Valid = IsValidText(value);
|
||||
// }
|
||||
|
||||
internal CUITextBlock TextComponent;
|
||||
public string Text
|
||||
{
|
||||
get => State.Text;
|
||||
set
|
||||
{
|
||||
if (Disabled) return;
|
||||
|
||||
State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||
|
||||
bool isvalid = IsValidText(value);
|
||||
if (isvalid)
|
||||
{
|
||||
OnTextChanged?.Invoke(State.Text);
|
||||
}
|
||||
Valid = isvalid;
|
||||
}
|
||||
}
|
||||
|
||||
internal CUIComponent SelectionOverlay;
|
||||
public IntRange Selection
|
||||
{
|
||||
get => State.Selection;
|
||||
set => State = new TextInputState(State.Text, value, State.CaretPos);
|
||||
}
|
||||
public void Select(int start, int end) => Selection = new IntRange(start, end);
|
||||
|
||||
|
||||
public bool CaretIndicatorVisible { get; set; }
|
||||
public double CaretBlinkInterval { get; set; } = 0.5;
|
||||
internal CUIComponent CaretIndicator;
|
||||
public int CaretPos
|
||||
{
|
||||
get => State.CaretPos;
|
||||
set => State = new TextInputState(State.Text, State.Selection, value);
|
||||
}
|
||||
|
||||
|
||||
//TODO
|
||||
//[CUISerializable] public bool PreventOverflow { get; set; } = false;
|
||||
|
||||
public void UpdateBorderColor()
|
||||
{
|
||||
if (Valid)
|
||||
{
|
||||
if (Focused)
|
||||
{
|
||||
Style["Border"] = "CUIPalette.Input.Focused";
|
||||
}
|
||||
else
|
||||
{
|
||||
Style["Border"] = "CUIPalette.Input.Border";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Style["Border"] = "CUIPalette.Input.Invalid";
|
||||
}
|
||||
}
|
||||
[CUISerializable]
|
||||
public float TextScale
|
||||
{
|
||||
get => TextComponent?.TextScale ?? 0;
|
||||
set => TextComponent.TextScale = value;
|
||||
}
|
||||
public Color TextColor
|
||||
{
|
||||
get => TextComponent?.TextColor ?? Color.White;
|
||||
set
|
||||
{
|
||||
if (TextComponent != null)
|
||||
{
|
||||
TextComponent.TextColor = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<string> OnTextChanged;
|
||||
public Action<string> AddOnTextChanged { set => OnTextChanged += value; }
|
||||
public event Action<string> OnTextAdded;
|
||||
public Action<string> AddOnTextAdded { set => OnTextAdded += value; }
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (Focused)
|
||||
{
|
||||
CaretIndicator.Visible = CaretIndicatorVisible && Timing.TotalTime % CaretBlinkInterval > CaretBlinkInterval / 2;
|
||||
}
|
||||
|
||||
|
||||
base.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CUITextInput(string text) : this()
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
public CUITextInput() : base()
|
||||
{
|
||||
AbsoluteMin = new CUINullRect(w: 50, h: 22);
|
||||
FitContent = new CUIBool2(true, true);
|
||||
Focusable = true;
|
||||
Border.Thickness = 2;
|
||||
HideChildrenOutsideFrame = true;
|
||||
ConsumeMouseClicks = true;
|
||||
ConsumeDragAndDrop = true;
|
||||
ConsumeSwipe = true;
|
||||
BreakSerialization = true;
|
||||
|
||||
this["TextComponent"] = TextComponent = new CUITextBlock()
|
||||
{
|
||||
Text = "",
|
||||
Relative = new CUINullRect(0, 0, 1, 1),
|
||||
TextAlign = CUIAnchor.CenterLeft,
|
||||
Style = new CUIStyle(){
|
||||
{"Padding", "[2,2]"},
|
||||
{"TextColor", "CUIPalette.Input.Text"},
|
||||
},
|
||||
};
|
||||
|
||||
this["SelectionOverlay"] = SelectionOverlay = new CUIComponent()
|
||||
{
|
||||
Style = new CUIStyle(){
|
||||
{"BackgroundColor", "CUIPalette.Input.Selection"},
|
||||
{"Border", "Transparent"},
|
||||
},
|
||||
Relative = new CUINullRect(h: 1),
|
||||
Ghost = new CUIBool2(true, true),
|
||||
IgnoreParentVisibility = true,
|
||||
};
|
||||
|
||||
this["CaretIndicator"] = CaretIndicator = new CUIComponent()
|
||||
{
|
||||
Style = new CUIStyle(){
|
||||
{"BackgroundColor", "CUIPalette.Input.Caret"},
|
||||
{"Border", "Transparent"},
|
||||
},
|
||||
Relative = new CUINullRect(y: 0.1f, h: 0.8f),
|
||||
Absolute = new CUINullRect(w: 1),
|
||||
Ghost = new CUIBool2(true, true),
|
||||
Visible = false,
|
||||
IgnoreParentVisibility = true,
|
||||
};
|
||||
|
||||
OnConsume += (o) =>
|
||||
{
|
||||
string value = o.ToString();
|
||||
State = new TextInputState(value, State.Selection, State.CaretPos);
|
||||
Valid = IsValidText(value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
OnFocus += () =>
|
||||
{
|
||||
UpdateBorderColor();
|
||||
CaretIndicator.Visible = true;
|
||||
};
|
||||
|
||||
OnFocusLost += () =>
|
||||
{
|
||||
UpdateBorderColor();
|
||||
Selection = IntRange.Zero;
|
||||
CaretIndicator.Visible = false;
|
||||
};
|
||||
|
||||
OnMouseDown += (e) =>
|
||||
{
|
||||
int newCaretPos = SetCaretPos(e.MousePosition - Real.Position);
|
||||
Selection = IntRange.Zero;
|
||||
selectionHandle.lastSelectedPos = newCaretPos;
|
||||
selectionHandle.Grabbed = true;
|
||||
};
|
||||
|
||||
OnMouseMove += (e) =>
|
||||
{
|
||||
if (selectionHandle.Grabbed)
|
||||
{
|
||||
int nextCaretPos = SetCaretPos(e.MousePosition - Real.Position);
|
||||
Selection = new IntRange(selectionHandle.lastSelectedPos, nextCaretPos);
|
||||
}
|
||||
};
|
||||
|
||||
OnDClick += (e) => SelectAll();
|
||||
|
||||
if (CUI.Main is not null)
|
||||
{
|
||||
CUI.Main.Global.OnMouseUp += (e) => selectionHandle.Grabbed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user