using System; using System.Collections.Generic; using System.Numerics; namespace AcDream.App.UI; /// /// Editable one-line chat input. Port of retail UIElement_Text editable /// one-line mode + ChatInterface's 100-entry command history. Caret is a /// glyph index; the caret pixel-X is Σ glyph advances (UiDatFont) to the caret. /// Submit (Enter / Send) fires , clears, and pushes history. /// Decomp: UIElement_Text MoveCursor @0x468d00, FindPixelsFromPos @0x472b40; /// ChatInterface ProcessCommand @0x4f5100 (history cap 100, sentinel 0xFFFFFFFF). /// public sealed class UiChatInput : UiElement { public UiDatFont? DatFont { get; set; } public AcDream.App.Rendering.BitmapFont? Font { get; set; } public Vector4 TextColor { get; set; } = new(1f, 1f, 1f, 1f); public Vector4 BackgroundColor { get; set; } = new(0f, 0f, 0f, 0.35f); public float Padding { get; set; } = 4f; public int MaxCharacters { get; set; } = 0xFFFF; public Action? OnSubmit { get; set; } private string _text = ""; private int _caret; public string Text => _text; public int CaretPos => _caret; private readonly List _history = new(); private int _historyIndex = -1; public int HistoryCount => _history.Count; public UiChatInput() { AcceptsFocus = true; IsEditControl = true; CapturesPointerDrag = true; } public void InsertChar(char c) { if (c < 0x20 || c == 0x7F) return; if (_text.Length >= MaxCharacters) return; _text = _text.Insert(_caret, c.ToString()); _caret++; _historyIndex = -1; } public void Backspace() { if (_caret == 0) return; _text = _text.Remove(_caret - 1, 1); _caret--; } public void DeleteForward() { if (_caret >= _text.Length) return; _text = _text.Remove(_caret, 1); } public void MoveCaret(int delta) => _caret = Math.Clamp(_caret + delta, 0, _text.Length); public void CaretHome() => _caret = 0; public void CaretEnd() => _caret = _text.Length; public void Submit() { var t = _text; if (t.Trim().Length == 0) { Clear(); return; } OnSubmit?.Invoke(t); PushHistory(t); Clear(); } private void Clear() { _text = ""; _caret = 0; _historyIndex = -1; } private void PushHistory(string t) { _history.Add(t); if (_history.Count > 100) _history.RemoveAt(0); _historyIndex = -1; } public void HistoryPrev() { if (_history.Count == 0) return; _historyIndex = _historyIndex < 0 ? _history.Count - 1 : Math.Max(0, _historyIndex - 1); SetTextFromHistory(); } public void HistoryNext() { if (_historyIndex < 0) return; _historyIndex++; if (_historyIndex >= _history.Count) { _historyIndex = -1; Clear(); return; } SetTextFromHistory(); } private void SetTextFromHistory() { _text = _history[_historyIndex]; _caret = _text.Length; } public float CaretPixelX() => DatFont is { } df ? df.MeasureWidth(_text.Substring(0, _caret)) : Font is { } bf ? bf.MeasureWidth(_text.Substring(0, _caret)) : 0f; private bool _focused; protected override void OnDraw(UiRenderContext ctx) { ctx.DrawRect(0, 0, Width, Height, BackgroundColor); float lh = DatFont?.LineHeight ?? Font?.LineHeight ?? 14f; float ty = (Height - lh) * 0.5f; if (DatFont is { } df) ctx.DrawStringDat(df, _text, Padding, ty, TextColor); else ctx.DrawString(_text, Padding, ty, TextColor, Font); if (_focused) { float cx = Padding + CaretPixelX(); ctx.DrawRect(cx, ty, 1f, lh, TextColor); } } public override bool OnEvent(in UiEvent e) { switch (e.Type) { case UiEventType.FocusGained: _focused = true; return true; case UiEventType.FocusLost: _focused = false; _historyIndex = -1; return true; case UiEventType.Char: InsertChar((char)e.Data0); return true; case UiEventType.KeyDown: { var key = (Silk.NET.Input.Key)e.Data0; switch (key) { case Silk.NET.Input.Key.Enter: case Silk.NET.Input.Key.KeypadEnter: Submit(); return true; case Silk.NET.Input.Key.Backspace: Backspace(); return true; case Silk.NET.Input.Key.Delete: DeleteForward(); return true; case Silk.NET.Input.Key.Left: MoveCaret(-1); return true; case Silk.NET.Input.Key.Right: MoveCaret(1); return true; case Silk.NET.Input.Key.Home: CaretHome(); return true; case Silk.NET.Input.Key.End: CaretEnd(); return true; case Silk.NET.Input.Key.Up: HistoryPrev(); return true; case Silk.NET.Input.Key.Down: HistoryNext(); return true; } return false; } } return false; } }