using System.Linq; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Timing; using Robust.Shared.Utility; 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. ///
[GenerateTypedNameReferences] public sealed partial class NeoTabContainer : BoxContainer { private readonly Dictionary _tabs = new(); private readonly List _controls = new(); private readonly ButtonGroup _tabGroup = new(false); /// All children within the public OrderedChildCollection Contents => ContentContainer.Children; /// All children within the that are visible public List VisibleContents => Contents.Where(c => c == CurrentControl).ToList(); /// 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(); public Control? CurrentControl { get; private set; } public int? CurrentTab => _controls.FirstOrDefault(control => control == CurrentControl) switch { { } control => _controls.IndexOf(control), _ => null, }; /// public NeoTabContainer() { RobustXamlLoader.Load(this); LayoutChanged(TabLocation); ScrollingChanged(HScrollEnabled, VScrollEnabled); } protected override void ChildRemoved(Control child) { if (_tabs.Remove(child, out var button)) button.Dispose(); // Set the current tab to a different control if (CurrentControl == child) { var previous = _controls.IndexOf(child) - 1; if (previous > -1) SelectTab(_controls[previous]); else CurrentControl = null; } _controls.Remove(child); base.ChildRemoved(child); UpdateTabMerging(); } // A fun display of every location for the tabs if you want it // private TimeSpan _lastLayoutChange = TimeSpan.Zero; // private TimeSpan _nextLayoutChange = TimeSpan.Zero; // protected override void FrameUpdate(FrameEventArgs args) // { // base.FrameUpdate(args); // // _lastLayoutChange += TimeSpan.FromSeconds(args.DeltaSeconds); // // Change the layout every second such that the tabs go in a circle // if (_lastLayoutChange.TotalSeconds < _nextLayoutChange.TotalSeconds) // return; // // _lastLayoutChange = _nextLayoutChange; // _nextLayoutChange = _lastLayoutChange + TimeSpan.FromSeconds(2); // // LayoutChanged(TabLocation switch // { // North => East, // East => South, // South => West, // West => North, // _ => North, // }); // } /// /// 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 int AddTab(Control control, string? title, bool updateTabMerging = true) { var button = new Button { Group = _tabGroup, MinHeight = 32, MaxHeight = 32, HorizontalExpand = true, }; button.OnPressed += _ => SelectTab(control); if (!string.IsNullOrEmpty(title)) button.Text = title; TabContainer.AddChild(button); ContentContainer.AddChild(control); _controls.Add(control); _tabs.Add(control, button); // Show it if it has content if (ContentContainer.ChildCount > 1) control.Visible = false; else // Select it if it's the only tab SelectTab(control); if (updateTabMerging) UpdateTabMerging(); return ChildCount - 1; } /// /// 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(int index, bool updateTabMerging = true) { if (index < 0 || index >= _controls.Count) return false; var control = _controls[index]; RemoveTab(control, updateTabMerging); return true; } /// /// Removes/Disposes the tab associated with the given control /// /// The control 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(Control control, bool updateTabMerging = true) { if (!_tabs.TryGetValue(control, out var button)) return false; button.Dispose(); control.Dispose(); if (updateTabMerging) UpdateTabMerging(); return true; } /// Sets the title of the tab associated with the given index public void SetTabTitle(int index, string title) { if (index < 0 || index >= _controls.Count) return; var control = _controls[index]; SetTabTitle(control, title); } /// Sets the title of the tab associated with the given control public void SetTabTitle(Control control, string title) { if (!_tabs.TryGetValue(control, out var button)) return; if (button is Button b) b.Text = title; } /// Shows or hides the tab associated with the given index public void SetTabVisible(int index, bool visible) { if (index < 0 || index >= _controls.Count) return; var control = _controls[index]; SetTabVisible(control, visible); } /// Shows or hides the tab associated with the given control public void SetTabVisible(Control control, bool visible) { if (!_tabs.TryGetValue(control, out var button)) return; button.Visible = visible; UpdateTabMerging(); } /// Selects the tab associated with the control public void SelectTab(Control control) { if (CurrentControl != null) CurrentControl.Visible = false; var button = _tabs[control]; button.Pressed = true; control.Visible = true; CurrentControl = control; } /// 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)); } } }