Files
wwdpublic/Content.Client/UserInterface/Controls/NeoTabContainer.xaml.cs
Cinkafox 916d889fc4 - add: Bark (#827)
* - add: bark

* - tweak: Bark now in client side

* - add: bark config in options

* - add: migration prepare

* - add: Migrations

* - add: more barks

* - add: bark preference in character profile

* - add: knob

* - add: change value by mouse wheel

* - tweak: optimise WWDP thinks

* - tweak: improve NeoTabContainer optimisation

* - add: limit of barks

* - fix: кролькины фиксы

* - fix: change things in tab id

* Update Content.Client/_White/TTS/TTSSystem.cs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* - fix: спатисон дурак блин ты сломал что то..

* - fix: спатисонов фиксы

* Update Content.Server/_White/Bark/BarkSystem.cs

* - fix: буковки

* Apply suggestions from code review

---------

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-06 03:37:39 +10:00

340 lines
9.5 KiB
C#

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;
/// <summary>
/// A simple yet good-looking tab container using normal UI elements with multiple styles
/// <br />
/// Because nobody else could do it better.
/// <br />
/// Yeah, it could be better if you don't shit with linkq
/// </summary>
/// WD EDIT
[GenerateTypedNameReferences]
public sealed partial class NeoTabContainer : BoxContainer
{
private readonly Dictionary<TabId, BaseButton> _tabs = new();
private readonly Dictionary<TabId, Control> _controls = new();
private readonly Dictionary<string, TabId> _tabAliases = new();
private readonly HashSet<TabId> _idTaken = new();
private readonly ButtonGroup _tabGroup = new(false);
private TabId? _selectedTabId;
private readonly TabIdPool _pool = new();
public ImmutableHashSet<TabId> TakenIds => _idTaken.ToImmutableHashSet();
public ImmutableDictionary<string, TabId> TabAliases => _tabAliases.ToImmutableDictionary();
/// All children within the <see cref="TabContainer"/>
public OrderedChildCollection Tabs => TabContainer.Children;
/// All children within the <see cref="TabContainer"/> that are visible
public List<Control> 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;
}
}
}
/// <inheritdoc cref="NeoTabContainer"/>
public NeoTabContainer()
{
RobustXamlLoader.Load(this);
LayoutContainer.SetAnchorPreset(ContentScrollContainer, LayoutContainer.LayoutPreset.Wide);
LayoutChanged(TabLocation);
ScrollingChanged(HScrollEnabled, VScrollEnabled);
}
/// <summary>
/// Adds a tab to this container
/// </summary>
/// <param name="control">The tab contents</param>
/// <param name="title">The title of the tab</param>
/// <param name="updateTabMerging">Whether the tabs should fix their styling automatically. Useful if you're doing tons of updates at once</param>
/// <returns>The index of the new tab</returns>
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;
}
/// <summary>
/// Removes/Disposes the tab associated with the given index
/// </summary>
/// <param name="index">The index of the tab to remove</param>
/// <param name="updateTabMerging">Whether the tabs should fix their styling automatically. Useful if you're doing tons of updates at once</param>
/// <returns>True if the tab was removed, false otherwise</returns>
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<T>(TabId tabId) where T : Control
{
if (!_controls.TryGetValue(tabId, out var control))
return null;
return control as T;
}
public IEnumerable<T> GetControls<T>() 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<TabId> _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<int>,
IComparable<TabId>,
IAsType<int>
{
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;
};