acdream/src/AcDream.App/UI/UiMarkupList.cs

96 lines
3.7 KiB
C#

using System.Numerics;
namespace AcDream.App.UI;
/// <summary>
/// Lightweight, data-bound string list for plugin markup. It owns only row
/// selection and scroll position; the plugin binding remains the sole owner of
/// rows and selected index. This deliberately avoids exposing App widget types
/// through the BCL plugin contract.
/// </summary>
public sealed class UiMarkupList : UiElement
{
public Func<IReadOnlyList<string>> ItemsSource { get; set; } =
static () => Array.Empty<string>();
public Func<IReadOnlyList<uint>> ItemColorsSource { get; set; } =
static () => Array.Empty<uint>();
public Func<int> SelectedIndexSource { get; set; } = static () => -1;
public Action<int>? SelectionChanged { get; set; }
public UiDatFont? DatFont { get; set; }
public float RowHeight { get; set; } = 18f;
public float Padding { get; set; } = 3f;
public Vector4 BackgroundColor { get; set; } = new(0f, 0f, 0f, 0.92f);
public Vector4 BorderColor { get; set; } = new(0.46f, 0.37f, 0.16f, 1f);
public Vector4 TextColor { get; set; } = new(0.91f, 0.87f, 0.76f, 1f);
public Vector4 SelectedColor { get; set; } = new(0.28f, 0.23f, 0.08f, 0.95f);
private int _topRow;
public override bool HandlesClick => true;
protected override void OnDraw(UiRenderContext context)
{
IReadOnlyList<string> items = ItemsSource();
IReadOnlyList<uint> itemColors = ItemColorsSource();
int visibleRows = VisibleRows;
int selected = SelectedIndexSource();
if (selected >= 0 && selected < items.Count)
{
if (selected < _topRow)
_topRow = selected;
else if (selected >= _topRow + visibleRows)
_topRow = selected - visibleRows + 1;
}
ClampTop(items.Count, visibleRows);
context.DrawFill(0f, 0f, Width, Height, BackgroundColor);
context.DrawRectOutline(0f, 0f, Width, Height, BorderColor, 1f);
int end = Math.Min(items.Count, _topRow + visibleRows);
for (int index = _topRow; index < end; index++)
{
float y = (index - _topRow) * RowHeight;
if (index == selected)
context.DrawFill(1f, y + 1f, Width - 2f, RowHeight - 1f, SelectedColor);
string text = items[index];
Vector4 textColor = index < itemColors.Count
? Rgb(itemColors[index])
: TextColor;
float textY = y + MathF.Max(0f,
(RowHeight - (DatFont?.LineHeight ?? 14f)) * 0.5f);
if (DatFont is { } font)
context.DrawStringDat(font, text, Padding, textY, textColor, true);
else
context.DrawString(text, Padding, textY, textColor);
}
}
public override bool OnEvent(in UiEvent e)
{
IReadOnlyList<string> items = ItemsSource();
if (e.Type == UiEventType.Scroll)
{
_topRow -= Math.Sign(e.Data0);
ClampTop(items.Count, VisibleRows);
return true;
}
if (e.Type != UiEventType.MouseDown || !Enabled)
return false;
int row = (int)MathF.Floor(e.Data2 / MathF.Max(1f, RowHeight));
int index = _topRow + row;
if (row >= 0 && row < VisibleRows && index >= 0 && index < items.Count)
SelectionChanged?.Invoke(index);
return true;
}
private int VisibleRows => Math.Max(1, (int)MathF.Floor(
Height / MathF.Max(1f, RowHeight)));
private void ClampTop(int count, int visibleRows) =>
_topRow = Math.Clamp(_topRow, 0, Math.Max(0, count - visibleRows));
private static Vector4 Rgb(uint value) => new(
((value >> 16) & 0xFFu) / 255f,
((value >> 8) & 0xFFu) / 255f,
(value & 0xFFu) / 255f,
1f);
}