185 lines
7.6 KiB
C#
185 lines
7.6 KiB
C#
// Assets/_Project/Scripts/Dev/DebugConsoleUI.cs
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace TD.Dev
|
|
{
|
|
/// <summary>
|
|
/// Builds and draws <see cref="DebugConsole"/>'s VisualElement tree: the input bar and
|
|
/// its autocomplete list. Owns presentation only — element construction, styling, and
|
|
/// row rendering — no command grammar, keybinding, or suggestion-selection logic; that
|
|
/// stays in <see cref="DebugConsole"/>, which drives this class through its public
|
|
/// members and reads/writes <see cref="CommandInput"/> directly for focus and value.
|
|
/// </summary>
|
|
internal sealed class DebugConsoleUI
|
|
{
|
|
// Beyond this many candidates the list is truncated with a "+N more" row so a
|
|
// large DraftPool can't grow the console bar off the top of the screen.
|
|
private const int MaxVisibleSuggestions = 12;
|
|
|
|
/// <summary>One row of the autocomplete list: a candidate token plus an optional dim hint.</summary>
|
|
public readonly struct SuggestionRow
|
|
{
|
|
public readonly string Text;
|
|
public readonly string Hint;
|
|
|
|
public SuggestionRow(string text, string hint)
|
|
{
|
|
Text = text;
|
|
Hint = hint;
|
|
}
|
|
}
|
|
|
|
public TextField CommandInput { get; }
|
|
|
|
private readonly VisualElement consoleContainer;
|
|
private readonly VisualElement suggestionList;
|
|
|
|
public DebugConsoleUI(VisualElement uiRoot)
|
|
{
|
|
// Top-anchored bar, closed by default. Distinct position from the HUD's
|
|
// bottom-left chat panel so the two never overlap.
|
|
consoleContainer = new VisualElement();
|
|
consoleContainer.pickingMode = PickingMode.Ignore;
|
|
consoleContainer.style.position = Position.Absolute;
|
|
consoleContainer.style.left = 0;
|
|
consoleContainer.style.right = 0;
|
|
consoleContainer.style.top = 0;
|
|
consoleContainer.style.paddingTop = 8;
|
|
consoleContainer.style.paddingBottom = 8;
|
|
consoleContainer.style.paddingLeft = 12;
|
|
consoleContainer.style.paddingRight = 12;
|
|
// Fully opaque: the console is a text-entry overlay that has to stay readable
|
|
// over the level, the HUD, and whatever else is on screen.
|
|
consoleContainer.style.backgroundColor = new Color(0f, 0f, 0f, 1f);
|
|
consoleContainer.style.display = DisplayStyle.None;
|
|
|
|
CommandInput = new TextField();
|
|
CommandInput.style.minHeight = 28;
|
|
CommandInput.style.width = Length.Percent(100);
|
|
CommandInput.maxLength = 200;
|
|
CommandInput.isDelayed = false;
|
|
StyleConsoleInput(CommandInput);
|
|
|
|
suggestionList = new VisualElement();
|
|
suggestionList.pickingMode = PickingMode.Ignore;
|
|
suggestionList.style.flexDirection = FlexDirection.Column;
|
|
suggestionList.style.marginTop = 4;
|
|
suggestionList.style.display = DisplayStyle.None;
|
|
|
|
consoleContainer.Add(CommandInput);
|
|
consoleContainer.Add(suggestionList);
|
|
uiRoot.Add(consoleContainer);
|
|
}
|
|
|
|
// Mirrors HUDController.StyleChatInputDark — dark background, white text,
|
|
// a thin border, and inner padding so ascenders/descenders don't clip.
|
|
// Green border (vs. chat's neutral gray) as a quick visual "this is the
|
|
// debug console, not chat" cue.
|
|
private static void StyleConsoleInput(TextField field)
|
|
{
|
|
var darkBg = new Color(0.05f, 0.05f, 0.05f, 1f);
|
|
var borderClr = new Color(0.35f, 0.75f, 0.35f);
|
|
|
|
field.style.backgroundColor = darkBg;
|
|
field.style.color = Color.white;
|
|
field.style.borderTopWidth = field.style.borderBottomWidth =
|
|
field.style.borderLeftWidth = field.style.borderRightWidth = 1;
|
|
field.style.borderTopColor = field.style.borderBottomColor =
|
|
field.style.borderLeftColor = field.style.borderRightColor = borderClr;
|
|
|
|
var inner = field.Q("unity-text-input");
|
|
if (inner != null)
|
|
{
|
|
inner.style.backgroundColor = darkBg;
|
|
inner.style.color = Color.white;
|
|
inner.style.paddingTop = 4;
|
|
inner.style.paddingBottom = 4;
|
|
inner.style.paddingLeft = 6;
|
|
inner.style.paddingRight = 6;
|
|
}
|
|
}
|
|
|
|
// One autocomplete row: the candidate token plus an optional dim hint. Rows are
|
|
// pickingMode Ignore and keyboard-driven only — nothing here is clickable, which
|
|
// avoids the recursive picking-mode problem documented in HUDController's chat
|
|
// panel (clicks falling through to the game world via a ScrollView's internal
|
|
// wrapper element).
|
|
private static VisualElement BuildSuggestionRow(string text, string hint, bool selected)
|
|
{
|
|
var row = new VisualElement();
|
|
row.pickingMode = PickingMode.Ignore;
|
|
row.style.flexDirection = FlexDirection.Row;
|
|
row.style.minHeight = 20;
|
|
row.style.paddingLeft = 6;
|
|
row.style.paddingRight = 6;
|
|
row.style.backgroundColor = selected
|
|
? new Color(0.16f, 0.38f, 0.16f, 1f) // matches the green console cue
|
|
: new Color(0.05f, 0.05f, 0.05f, 1f);
|
|
|
|
var label = new Label(text);
|
|
label.pickingMode = PickingMode.Ignore;
|
|
label.style.color = Color.white;
|
|
label.style.unityFontStyleAndWeight = selected ? FontStyle.Bold : FontStyle.Normal;
|
|
row.Add(label);
|
|
|
|
if (!string.IsNullOrEmpty(hint))
|
|
{
|
|
var hintLabel = new Label(hint);
|
|
hintLabel.pickingMode = PickingMode.Ignore;
|
|
hintLabel.style.color = new Color(0.6f, 0.6f, 0.6f);
|
|
hintLabel.style.marginLeft = 12;
|
|
row.Add(hintLabel);
|
|
}
|
|
|
|
return row;
|
|
}
|
|
|
|
public void Show()
|
|
{
|
|
consoleContainer.style.display = DisplayStyle.Flex;
|
|
consoleContainer.pickingMode = PickingMode.Position;
|
|
}
|
|
|
|
public void Hide()
|
|
{
|
|
consoleContainer.style.display = DisplayStyle.None;
|
|
consoleContainer.pickingMode = PickingMode.Ignore;
|
|
}
|
|
|
|
// Windows the list around selectedIndex so arrowing past MaxVisibleSuggestions
|
|
// keeps the selected row on screen instead of scrolling it out of the truncated view.
|
|
public void RenderSuggestions(List<SuggestionRow> rows, int selectedIndex)
|
|
{
|
|
suggestionList.Clear();
|
|
|
|
if (rows == null || rows.Count == 0)
|
|
{
|
|
suggestionList.style.display = DisplayStyle.None;
|
|
return;
|
|
}
|
|
|
|
suggestionList.style.display = DisplayStyle.Flex;
|
|
|
|
int start = rows.Count > MaxVisibleSuggestions
|
|
? Mathf.Clamp(selectedIndex - MaxVisibleSuggestions / 2, 0,
|
|
rows.Count - MaxVisibleSuggestions)
|
|
: 0;
|
|
int end = Mathf.Min(start + MaxVisibleSuggestions, rows.Count);
|
|
|
|
for (int i = start; i < end; i++)
|
|
suggestionList.Add(BuildSuggestionRow(rows[i].Text, rows[i].Hint, i == selectedIndex));
|
|
|
|
int hidden = rows.Count - (end - start);
|
|
if (hidden > 0)
|
|
suggestionList.Add(BuildSuggestionRow($"+{hidden} more", null, false));
|
|
}
|
|
|
|
public void ClearSuggestions()
|
|
{
|
|
suggestionList.Clear();
|
|
suggestionList.style.display = DisplayStyle.None;
|
|
}
|
|
}
|
|
}
|