Files
RobustToolbox/Robust.Client/UserInterface/Controls/Tree.cs
2023-07-09 12:19:42 +10:00

350 lines
9.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls
{
[Virtual]
public class Tree : Control
{
public const string StylePropertyItemBoxSelected = "item-selected";
public const string StylePropertyBackground = "background";
private readonly List<Item> _itemList = new();
private Item? _root;
private int? _selectedIndex;
private VScrollBar _scrollBar;
public bool HideRoot { get; set; }
public Item? TreeRoot => _root;
public Item? Selected => _selectedIndex == null ? null : _itemList[_selectedIndex.Value];
public event Action? OnItemSelected;
public Tree()
{
MouseFilter = MouseFilterMode.Pass;
RectClipContent = true;
_scrollBar = new VScrollBar
{
Name = "_v_scroll",
VerticalAlignment = VAlignment.Stretch,
HorizontalAlignment = HAlignment.Right
};
AddChild(_scrollBar);
}
public void Clear()
{
foreach (var item in _itemList)
{
item.Dispose();
}
_itemList.Clear();
_selectedIndex = null;
_root = null;
_updateScrollbar();
}
public Item CreateItem(Item? parent = null, int idx = -1)
{
if (parent != null)
{
if (parent.Parent != this)
{
throw new ArgumentException("Parent must be owned by this tree.", nameof(parent));
}
if (parent.Disposed)
{
throw new ArgumentException("Parent is disposed", nameof(parent));
}
}
var item = new Item(this, _itemList.Count);
_itemList.Add(item);
if (_root == null)
{
_root = item;
}
else
{
parent = parent ?? _root;
parent.Children.Add(item);
}
_updateScrollbar();
return item;
}
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (args.Function != EngineKeyFunctions.UIClick)
{
return;
}
var item = _tryFindItemAtPosition(args.RelativePixelPosition);
if (item != null && item.Selectable)
{
_selectedIndex = item.Index;
OnItemSelected?.Invoke();
args.Handle();
}
}
private Item? _tryFindItemAtPosition(Vector2 position)
{
var font = _getFont();
if (font == null || _root == null)
{
return null;
}
var background = _getBackground();
if (background != null)
{
position -= background.GetContentOffset(Vector2.Zero, UIScale);
}
var vOffset = -_scrollBar.Value;
Item? final = null;
bool DoSearch(Item item, float hOffset)
{
var itemHeight = font.GetHeight(UIScale);
var itemBox = UIBox2.FromDimensions(new(hOffset, vOffset), new(PixelWidth - hOffset, itemHeight));
if (itemBox.Contains(position))
{
final = item;
return true;
}
vOffset += itemHeight;
hOffset += 5;
foreach (var child in item.Children)
{
if (DoSearch(child, hOffset))
{
return true;
}
}
return false;
}
if (HideRoot)
{
foreach (var child in _root.Children)
{
if (DoSearch(child, 0))
{
break;
}
}
}
else
{
DoSearch(_root, 0);
}
return final;
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
var font = _getFont();
if (font == null || _root == null)
{
return;
}
var background = _getBackground();
var itemSelected = _getItemSelectedStyle();
var vOffset = -_scrollBar.Value;
var hOffset = 0f;
if (itemSelected == null)
{
return;
}
if (background != null)
{
background.Draw(handle, PixelSizeBox, UIScale);
var (bho, bvo) = background.GetContentOffset(Vector2.Zero, UIScale);
vOffset += bvo;
hOffset += bho;
}
if (HideRoot)
{
foreach (var child in _root.Children)
{
_drawItem(handle, ref vOffset, hOffset, child, font, itemSelected);
}
}
else
{
_drawItem(handle, ref vOffset, hOffset, _root, font, itemSelected);
}
}
private void _drawItem(
DrawingHandleScreen handle, ref float vOffset, float hOffset, Item item,
Font font, StyleBox itemSelected)
{
var itemHeight = font.GetHeight(UIScale);
var selected = item.Index == _selectedIndex;
if (selected)
{
var imageTarget = UIBox2.FromDimensions(hOffset, vOffset, PixelWidth - hOffset, itemHeight);
itemSelected.Draw(handle, imageTarget, UIScale);
}
if (!string.IsNullOrWhiteSpace(item.Text))
{
var offset = itemSelected.GetContentOffset(Vector2.Zero, UIScale);
var baseLine = offset + new Vector2(hOffset, vOffset + font.GetAscent(UIScale));
foreach (var rune in item.Text.EnumerateRunes())
{
baseLine += new Vector2(font.DrawChar(handle, rune, baseLine, UIScale, Color.White), 0);
}
}
vOffset += itemHeight;
hOffset += 5;
foreach (var child in item.Children)
{
_drawItem(handle, ref vOffset, hOffset, child, font, itemSelected);
}
}
private float _getInternalHeight()
{
var font = _getFont();
if (font == null || _root == null)
{
return 0;
}
if (HideRoot)
{
var sum = 0f;
foreach (var child in _root.Children)
{
sum += _getItemHeight(child, font);
}
return sum;
}
return _getItemHeight(_root, font);
}
private float _getItemHeight(Item item, Font font)
{
float sum = font.GetHeight(UIScale);
foreach (var child in item.Children)
{
sum += _getItemHeight(child, font);
}
return sum;
}
private void _updateScrollbar()
{
var internalHeight = _getInternalHeight();
_scrollBar.MaxValue = Math.Max(internalHeight, PixelHeight);
_scrollBar.Page = PixelHeight;
_scrollBar.Visible = internalHeight > PixelHeight;
}
protected override void Resized()
{
base.Resized();
_updateScrollbar();
}
private Font? _getFont()
{
if (TryGetStyleProperty<Font>("font", out var font))
{
return font;
}
return null;
}
private StyleBox? _getBackground()
{
if (TryGetStyleProperty<StyleBox>(StylePropertyBackground, out var box))
{
return box;
}
return null;
}
private StyleBox? _getItemSelectedStyle()
{
if (TryGetStyleProperty<StyleBox>(StylePropertyItemBoxSelected, out var box))
{
return box;
}
return null;
}
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
{
base.MouseWheel(args);
_scrollBar.ValueTarget -= args.Delta.Y * 50;
}
public sealed class Item : IDisposable
{
internal readonly List<Item> Children = new();
internal readonly int Index;
public readonly Tree Parent;
public object? Metadata { get; set; }
public bool Disposed { get; private set; }
public string? Text { get; set; }
public bool Selectable { get; set; } = true;
internal Item(Tree parent, int index)
{
Parent = parent;
Index = index;
}
public void Dispose()
{
Disposed = true;
}
}
}
}