using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Shared.Decals;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.SprayPainter.UI;
///
/// A window to select spray painter settings by object type, as well as pipe colours and decals.
///
[GenerateTypedNameReferences]
public sealed partial class SprayPainterWindow : DefaultWindow
{
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
private readonly SpriteSystem _spriteSystem;
// Events
public event Action? OnSpritePicked;
public event Action? OnTabChanged;
public event Action>? OnDecalChanged;
public event Action? OnSetPipeColor;
public event Action? OnDecalColorChanged;
public event Action? OnDecalAngleChanged;
public event Action? OnDecalSnapChanged;
// Pipe color data
private ItemList _colorList = default!;
public Dictionary ItemColorIndex = new();
private Dictionary _currentPalette = new();
private const string ColorLocKeyPrefix = "pipe-painter-color-";
// Paintable objects
private Dictionary> _currentStylesByGroup = new();
private Dictionary> _currentGroupsByCategory = new();
// Tab controls
private Dictionary _paintableControls = new();
private BoxContainer? _pipeControl;
// Decals
private List _currentDecals = [];
private SprayPainterDecals? _sprayPainterDecals;
private readonly SpriteSpecifier _colorEntryIconTexture = new SpriteSpecifier.Rsi(
new ResPath("Structures/Piping/Atmospherics/pipe.rsi"),
"pipeStraight");
public SprayPainterWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _sysMan.GetEntitySystem();
Tabs.OnTabChanged += (index) => OnTabChanged?.Invoke(index, _sprayPainterDecals?.GetPositionInParent() == index);
}
private string GetColorLocString(string? colorKey)
{
if (string.IsNullOrEmpty(colorKey))
return Loc.GetString("pipe-painter-no-color-selected");
var locKey = ColorLocKeyPrefix + colorKey;
if (!_loc.TryGetString(locKey, out var locString))
locString = colorKey;
return locString;
}
public string? IndexToColorKey(int index)
{
return _colorList[index].Text;
}
private void OnStyleSelected(ListData data)
{
if (data is SpriteListData listData)
OnSpritePicked?.Invoke(listData.Group, listData.Style);
}
///
/// Wrapper to allow for selecting/deselecting the event to avoid loops
///
private void OnColorPicked(ItemList.ItemListSelectedEventArgs args)
{
OnSetPipeColor?.Invoke(args);
}
///
/// Setup function for the window.
///
/// Each group, mapped by name to the set of named styles by their associated entity prototype.
/// The set of categories and the groups associated with them.
/// A list of each decal.
public void PopulateCategories(Dictionary> stylesByGroup, Dictionary> groupsByCategory, List decals)
{
bool tabsCleared = false;
var lastTab = Tabs.CurrentTab;
if (!_currentGroupsByCategory.Equals(groupsByCategory))
{
// Destroy all existing tabs
tabsCleared = true;
_paintableControls.Clear();
_pipeControl = null;
_sprayPainterDecals = null;
Tabs.RemoveAllChildren();
}
// Only clear if the entries change. Otherwise the list would "jump" after selecting an item
if (tabsCleared || !_currentStylesByGroup.Equals(stylesByGroup))
{
_currentStylesByGroup = stylesByGroup;
var tabIndex = 0;
foreach (var (categoryName, categoryGroups) in groupsByCategory.OrderBy(c => c.Key))
{
if (categoryGroups.Count <= 0)
continue;
// Repopulating controls:
// ensure that categories with multiple groups have separate subtabs
// but single-group categories do not.
if (tabsCleared)
{
TabContainer? subTabs = null;
if (categoryGroups.Count > 1)
subTabs = new();
foreach (var group in categoryGroups)
{
if (!stylesByGroup.TryGetValue(group, out var styles))
continue;
var groupControl = new SprayPainterGroup();
groupControl.OnButtonPressed += OnStyleSelected;
_paintableControls[group] = groupControl;
if (categoryGroups.Count > 1)
{
if (subTabs != null)
{
subTabs?.AddChild(groupControl);
var subTabLocalization = Loc.GetString("spray-painter-tab-group-" + group.ToLower());
TabContainer.SetTabTitle(groupControl, subTabLocalization);
}
}
else
{
Tabs.AddChild(groupControl);
}
}
if (subTabs != null)
Tabs.AddChild(subTabs);
var tabLocalization = Loc.GetString("spray-painter-tab-category-" + categoryName.ToLower());
Tabs.SetTabTitle(tabIndex, tabLocalization);
tabIndex++;
}
// Finally, populate all groups with new data.
foreach (var group in categoryGroups)
{
if (!stylesByGroup.TryGetValue(group, out var styles) ||
!_paintableControls.TryGetValue(group, out var control))
continue;
var dataList = styles
.Select(e => new SpriteListData(group, e.Key, e.Value, 0))
.OrderBy(d => Loc.GetString($"spray-painter-style-{group.ToLower()}-{d.Style.ToLower()}"))
.ToList();
control.PopulateList(dataList);
}
}
}
PopulateColors(_currentPalette);
if (!_currentDecals.Equals(decals))
{
_currentDecals = decals;
if (_sprayPainterDecals is null)
{
_sprayPainterDecals = new SprayPainterDecals();
_sprayPainterDecals.OnDecalSelected += id => OnDecalChanged?.Invoke(id);
_sprayPainterDecals.OnColorChanged += color => OnDecalColorChanged?.Invoke(color);
_sprayPainterDecals.OnAngleChanged += angle => OnDecalAngleChanged?.Invoke(angle);
_sprayPainterDecals.OnSnapChanged += snap => OnDecalSnapChanged?.Invoke(snap);
Tabs.AddChild(_sprayPainterDecals);
TabContainer.SetTabTitle(_sprayPainterDecals, Loc.GetString("spray-painter-tab-category-decals"));
}
_sprayPainterDecals.PopulateDecals(decals, _spriteSystem);
}
if (tabsCleared)
SetSelectedTab(lastTab);
}
public void PopulateColors(Dictionary palette)
{
// Create pipe tab controls if they don't exist
bool tabCreated = false;
if (_pipeControl == null)
{
_pipeControl = new BoxContainer() { Orientation = BoxContainer.LayoutOrientation.Vertical };
var label = new Label() { Text = Loc.GetString("spray-painter-selected-color") };
_colorList = new ItemList() { VerticalExpand = true };
_colorList.OnItemSelected += OnColorPicked;
_pipeControl.AddChild(label);
_pipeControl.AddChild(_colorList);
Tabs.AddChild(_pipeControl);
TabContainer.SetTabTitle(_pipeControl, Loc.GetString("spray-painter-tab-category-pipes"));
tabCreated = true;
}
// Populate the tab if needed (new tab/new data)
if (tabCreated || !_currentPalette.Equals(palette))
{
_currentPalette = palette;
ItemColorIndex.Clear();
_colorList.Clear();
int index = 0;
foreach (var color in palette)
{
var locString = GetColorLocString(color.Key);
var item = _colorList.AddItem(locString, _spriteSystem.Frame0(_colorEntryIconTexture), metadata: color.Key);
item.IconModulate = color.Value;
ItemColorIndex.Add(color.Key, index);
index++;
}
}
}
# region Setters
public void SetSelectedStyles(Dictionary selectedStyles)
{
foreach (var (group, style) in selectedStyles)
{
if (!_paintableControls.TryGetValue(group, out var control))
continue;
control.SelectItemByStyle(style);
}
}
public void SelectColor(string color)
{
if (_colorList != null && ItemColorIndex.TryGetValue(color, out var colorIdx))
{
_colorList.OnItemSelected -= OnColorPicked;
_colorList[colorIdx].Selected = true;
_colorList.OnItemSelected += OnColorPicked;
}
}
public void SetSelectedTab(int tab)
{
Tabs.CurrentTab = int.Min(tab, Tabs.ChildCount - 1);
}
public void SetSelectedDecal(string decal)
{
if (_sprayPainterDecals != null)
_sprayPainterDecals.SetSelectedDecal(decal);
}
public void SetDecalAngle(int angle)
{
if (_sprayPainterDecals != null)
_sprayPainterDecals.SetAngle(angle);
}
public void SetDecalColor(Color? color)
{
if (_sprayPainterDecals != null)
_sprayPainterDecals.SetColor(color);
}
public void SetDecalSnap(bool snap)
{
if (_sprayPainterDecals != null)
_sprayPainterDecals.SetSnap(snap);
}
# endregion
}
public record SpriteListData(string Group, string Style, EntProtoId Prototype, int SelectedIndex) : ListData;