using System.Collections.Immutable; using System.Linq; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Toolshed.TypeParsers; using static Content.Client.Stylesheets.StyleBase; using static Robust.Shared.Maths.Direction; namespace Content.Client.UserInterface.Controls; /// /// A simple yet good-looking tab container using normal UI elements with multiple styles ///
/// Because nobody else could do it better. ///
/// Yeah, it could be better if you don't shit with linkq ///
/// WD EDIT [GenerateTypedNameReferences] public sealed partial class NeoTabContainer : BoxContainer { private readonly Dictionary _tabs = new(); private readonly Dictionary _controls = new(); private readonly Dictionary _tabAliases = new(); private readonly HashSet _idTaken = new(); private readonly ButtonGroup _tabGroup = new(false); private TabId? _selectedTabId; private readonly TabIdPool _pool = new(); public ImmutableHashSet TakenIds => _idTaken.ToImmutableHashSet(); public ImmutableDictionary TabAliases => _tabAliases.ToImmutableDictionary(); /// All children within the public OrderedChildCollection Tabs => TabContainer.Children; /// All children within the that are visible public List VisibleTabs => Tabs.Where(c => c.Visible).ToList(); private Control? _currentControl; public Control? CurrentControl { get => _currentControl; private set { if (_currentControl != null) ContentScrollContainer.RemoveChild(_currentControl); _currentControl = value; if (value != null) ContentScrollContainer.AddChild(value); } } public TabId? SelectedTabId { get => _selectedTabId; set { if (value.HasValue) { if (_controls.TryGetValue(value.Value, out var control)) { CurrentControl = control; _selectedTabId = value; } } else { CurrentControl = null; _selectedTabId = value; } } } /// public NeoTabContainer() { RobustXamlLoader.Load(this); LayoutContainer.SetAnchorPreset(ContentScrollContainer, LayoutContainer.LayoutPreset.Wide); LayoutChanged(TabLocation); ScrollingChanged(HScrollEnabled, VScrollEnabled); } /// /// Adds a tab to this container /// /// The tab contents /// The title of the tab /// Whether the tabs should fix their styling automatically. Useful if you're doing tons of updates at once /// The index of the new tab public TabId AddTab(Control control, string? title, bool updateTabMerging = true) { var button = new Button { Group = _tabGroup, MinHeight = 32, MaxHeight = 32, HorizontalExpand = true, }; var id = _pool.Take(); _idTaken.Add(id); button.OnPressed += _ => SelectTab(id); if (!string.IsNullOrEmpty(title)) button.Text = title; TabContainer.AddChild(button); _controls.Add(id, control); _tabs.Add(id, button); SelectedTabId ??= id; if (updateTabMerging) UpdateTabMerging(); return id; } /// /// Removes/Disposes the tab associated with the given index /// /// The index of the tab to remove /// Whether the tabs should fix their styling automatically. Useful if you're doing tons of updates at once /// True if the tab was removed, false otherwise public bool RemoveTab(TabId index, bool updateTabMerging = true) { if (!_controls.ContainsKey(index)) return false; if (SelectedTabId == index) { if (_tabs.Count > 0) SelectedTabId = _tabs.Keys.First(); else SelectedTabId = null; } _controls[index].Dispose(); _controls.Remove(index); _tabs[index].Dispose(); _tabs.Remove(index); var alias = _tabAliases .Where(p => p.Value == index) .Select(p => p.Key) .FirstOrDefault(); if (alias is not null) _tabAliases.Remove(alias); if (updateTabMerging) UpdateTabMerging(); _idTaken.Remove(index); _pool.Free(index); return true; } public T? GetControl(TabId tabId) where T : Control { if (!_controls.TryGetValue(tabId, out var control)) return null; return control as T; } public IEnumerable GetControls() where T : Control { foreach (var control in _controls) { if(control.Value is T t) yield return t; } } public bool TryFindTabByAlias(string alias, out TabId tabId) { tabId = default; if (string.IsNullOrEmpty(alias)) return false; return _tabAliases.TryGetValue(alias, out tabId); } public void SetTabAlias(TabId tabId, string name) { if(!_controls.ContainsKey(tabId)) return; _tabAliases[name] = tabId; } /// Sets the title of the tab associated with the given index public void SetTabTitle(TabId index, string title) { if(!_tabs.TryGetValue(index, out var tab)) return; if (tab is Button button) button.Text = title; } /// Shows or hides the tab associated with the given index public void SetTabVisible(TabId index, bool visible) { if (!_tabs.TryGetValue(index, out var button)) return; button.Visible = visible; UpdateTabMerging(); } /// Selects the tab associated with the control public void SelectTab(TabId tabId) => SelectedTabId = tabId; /// Sets the style of every visible tab's Button to be Open to Right, Both, or Left depending on position public void UpdateTabMerging() { var visibleTabs = VisibleTabs; if (visibleTabs.Count == 0) return; if (visibleTabs.Count == 1) { var button = visibleTabs[0]; button.RemoveStyleClass(ButtonOpenRight); button.RemoveStyleClass(ButtonOpenBoth); button.RemoveStyleClass(ButtonOpenLeft); if (FirstTabOpenBoth) button.AddStyleClass(ButtonOpenBoth); return; } string GetDirection(Direction direction, int position) { return position switch { // First 0 => direction switch { North => ButtonOpenRight, South => ButtonOpenRight, East => ButtonOpenLeft, West => ButtonOpenLeft, _ => ButtonOpenRight, }, // Middle 1 => ButtonOpenBoth, // Last 2 => direction switch { North => ButtonOpenLeft, South => ButtonOpenLeft, East => ButtonOpenRight, West => ButtonOpenRight, _ => ButtonOpenLeft, }, _ => ButtonOpenBoth, }; } for (var i = 0; i < visibleTabs.Count; i++) { var button = visibleTabs[i]; button.RemoveStyleClass(ButtonOpenRight); button.RemoveStyleClass(ButtonOpenBoth); button.RemoveStyleClass(ButtonOpenLeft); if (FirstTabOpenBoth && i == 0) { button.AddStyleClass(ButtonOpenBoth); continue; } if (LastTabOpenBoth && i == visibleTabs.Count - 1) { button.AddStyleClass(ButtonOpenBoth); continue; } var position = i switch { 0 => 0, _ when i == visibleTabs.Count - 1 => 2, _ => 1, }; button.AddStyleClass(GetDirection(TabLocation, position)); } } } // For nice think of NeoTabContrainer public sealed class TabIdPool { private readonly Queue _freeIds = new(); private int _nextId = 1; public TabId Take() { if (_freeIds.Count > 0) { return _freeIds.Dequeue(); } return new TabId(_nextId++); } public void Free(TabId id) { if (!_freeIds.Contains(id)) { _freeIds.Enqueue(id); } } } public record struct TabId(int Id): IEquatable, IComparable, IAsType { public static implicit operator TabId(int id) => new TabId(id); public static implicit operator int(TabId id) => id.Id; public bool Equals(int other) => Id == other; public int CompareTo(TabId other) => Id.CompareTo(other.Id); public int AsType() => Id; };