Improve crayon UI to not be stuck in 1996 (#33101)

* Improve crayon UI to not be stuck in 1996

* Make a horrifying crayon spaghetti

* Crayon

* Undeprecate the crayon, describe the crayon
This commit is contained in:
Saphire Lattice
2024-11-16 10:25:06 +07:00
committed by GitHub
parent abdefbd622
commit e7e1d96051
7 changed files with 466 additions and 334 deletions

View File

@@ -31,7 +31,7 @@ namespace Content.Client.Crayon.UI
private void PopulateCrayons()
{
var crayonDecals = _protoManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("crayon"));
_menu?.Populate(crayonDecals);
_menu?.Populate(crayonDecals.ToList());
}
public override void OnProtoReload(PrototypesReloadedEventArgs args)
@@ -44,6 +44,16 @@ namespace Content.Client.Crayon.UI
PopulateCrayons();
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
base.ReceiveMessage(message);
if (_menu is null || message is not CrayonUsedMessage crayonMessage)
return;
_menu.AdvanceState(crayonMessage.DrawnDecal);
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);

View File

@@ -1,14 +1,13 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'crayon-window-title'}"
MinSize="250 300"
SetSize="250 300">
MinSize="450 500"
SetSize="450 500">
<BoxContainer Orientation="Vertical">
<ColorSelectorSliders Name="ColorSelector" Visible="False" />
<LineEdit Name="Search" />
<LineEdit Name="Search" Margin="0 0 0 8" PlaceHolder="{Loc 'crayon-window-placeholder'}" />
<ScrollContainer VerticalExpand="True">
<GridContainer Name="Grid" Columns="6">
<!-- Crayon decals get added here by code -->
</GridContainer>
<BoxContainer Name="Grids" Orientation="Vertical">
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Content.Client.Stylesheets;
using Content.Shared.Crayon;
using Content.Shared.Decals;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
@@ -18,7 +20,12 @@ namespace Content.Client.Crayon.UI
[GenerateTypedNameReferences]
public sealed partial class CrayonWindow : DefaultWindow
{
private Dictionary<string, Texture>? _decals;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly SpriteSystem _spriteSystem = default!;
private Dictionary<string, List<(string Name, Texture Texture)>>? _decals;
private List<string>? _allDecals;
private string? _autoSelected;
private string? _selected;
private Color _color;
@@ -28,8 +35,10 @@ namespace Content.Client.Crayon.UI
public CrayonWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
Search.OnTextChanged += _ => RefreshList();
Search.OnTextChanged += SearchChanged;
ColorSelector.OnColorChanged += SelectColor;
}
@@ -44,51 +53,94 @@ namespace Content.Client.Crayon.UI
private void RefreshList()
{
// Clear
Grid.DisposeAllChildren();
if (_decals == null)
Grids.DisposeAllChildren();
if (_decals == null || _allDecals == null)
return;
var filter = Search.Text;
foreach (var (decal, tex) in _decals)
var comma = filter.IndexOf(',');
var first = (comma == -1 ? filter : filter[..comma]).Trim();
var names = _decals.Keys.ToList();
names.Sort((a, b) => a == "random" ? 1 : b == "random" ? -1 : a.CompareTo(b));
if (_autoSelected != null && first != _autoSelected && _allDecals.Contains(first))
{
if (!decal.Contains(filter))
_selected = first;
_autoSelected = _selected;
OnSelected?.Invoke(_selected);
}
foreach (var categoryName in names)
{
var locName = Loc.GetString("crayon-category-" + categoryName);
var category = _decals[categoryName].Where(d => locName.Contains(first) || d.Name.Contains(first)).ToList();
if (category.Count == 0)
continue;
var button = new TextureButton()
var label = new Label
{
TextureNormal = tex,
Name = decal,
ToolTip = decal,
Modulate = _color,
Text = locName
};
button.OnPressed += ButtonOnPressed;
if (_selected == decal)
var grid = new GridContainer
{
var panelContainer = new PanelContainer()
Columns = 6,
Margin = new Thickness(0, 0, 0, 16)
};
Grids.AddChild(label);
Grids.AddChild(grid);
foreach (var (name, texture) in category)
{
var button = new TextureButton()
{
PanelOverride = new StyleBoxFlat()
{
BackgroundColor = StyleNano.ButtonColorDefault,
},
Children =
{
button,
},
TextureNormal = texture,
Name = name,
ToolTip = name,
Modulate = _color,
Scale = new System.Numerics.Vector2(2, 2)
};
Grid.AddChild(panelContainer);
}
else
{
Grid.AddChild(button);
button.OnPressed += ButtonOnPressed;
if (_selected == name)
{
var panelContainer = new PanelContainer()
{
PanelOverride = new StyleBoxFlat()
{
BackgroundColor = StyleNano.ButtonColorDefault,
},
Children =
{
button,
},
};
grid.AddChild(panelContainer);
}
else
{
grid.AddChild(button);
}
}
}
}
private void SearchChanged(LineEdit.LineEditEventArgs obj)
{
_autoSelected = ""; // Placeholder to kick off the auto-select in refreshlist()
RefreshList();
}
private void ButtonOnPressed(ButtonEventArgs obj)
{
if (obj.Button.Name == null) return;
_selected = obj.Button.Name;
_autoSelected = null;
OnSelected?.Invoke(_selected);
RefreshList();
}
@@ -107,12 +159,38 @@ namespace Content.Client.Crayon.UI
RefreshList();
}
public void Populate(IEnumerable<DecalPrototype> prototypes)
public void AdvanceState(string drawnDecal)
{
_decals = new Dictionary<string, Texture>();
var filter = Search.Text;
if (!filter.Contains(',') || !filter.Contains(drawnDecal))
return;
var first = filter[..filter.IndexOf(',')].Trim();
if (first.Equals(drawnDecal, StringComparison.InvariantCultureIgnoreCase))
{
Search.Text = filter[(filter.IndexOf(',') + 1)..].Trim();
_autoSelected = first;
}
RefreshList();
}
public void Populate(List<DecalPrototype> prototypes)
{
_decals = [];
_allDecals = [];
prototypes.Sort((a, b) => a.ID.CompareTo(b.ID));
foreach (var decalPrototype in prototypes)
{
_decals.Add(decalPrototype.ID, decalPrototype.Sprite.Frame0());
var category = "random";
if (decalPrototype.Tags.Count > 1 && decalPrototype.Tags[1].StartsWith("crayon-"))
category = decalPrototype.Tags[1].Replace("crayon-", "");
var list = _decals.GetOrNew(category);
list.Add((decalPrototype.ID, _spriteSystem.Frame0(decalPrototype.Sprite)));
_allDecals.Add(decalPrototype.ID);
}
RefreshList();

View File

@@ -82,6 +82,8 @@ public sealed class CrayonSystem : SharedCrayonSystem
if (component.DeleteEmpty && component.Charges <= 0)
UseUpCrayon(uid, args.User);
else
_uiSystem.ServerSendUiMessage(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonUsedMessage(component.SelectedState));
}
private void OnCrayonUse(EntityUid uid, CrayonComponent component, UseInHandEvent args)

View File

@@ -3,12 +3,23 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Crayon
{
/// <summary>
/// Component holding the state of a crayon-like component
/// </summary>
[NetworkedComponent, ComponentProtoName("Crayon"), Access(typeof(SharedCrayonSystem))]
public abstract partial class SharedCrayonComponent : Component
{
/// <summary>
/// The ID of currently selected decal prototype that will be placed when the crayon is used
/// </summary>
public string SelectedState { get; set; } = string.Empty;
[DataField("color")] public Color Color;
/// <summary>
/// Color with which the crayon will draw
/// </summary>
[DataField("color")]
public Color Color;
[Serializable, NetSerializable]
public enum CrayonUiKey : byte
@@ -17,6 +28,9 @@ namespace Content.Shared.Crayon
}
}
/// <summary>
/// Used by the client to notify the server about the selected decal ID
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonSelectMessage : BoundUserInterfaceMessage
{
@@ -27,6 +41,9 @@ namespace Content.Shared.Crayon
}
}
/// <summary>
/// Sets the color of the crayon, used by Rainbow Crayon
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonColorMessage : BoundUserInterfaceMessage
{
@@ -37,13 +54,25 @@ namespace Content.Shared.Crayon
}
}
/// <summary>
/// Server to CLIENT. Notifies the BUI that a decal with given ID has been drawn.
/// Allows the client UI to advance forward in the client-only ephemeral queue,
/// preventing the crayon from becoming a magic text storage device.
/// </summary>
[Serializable, NetSerializable]
public enum CrayonVisuals
public sealed class CrayonUsedMessage : BoundUserInterfaceMessage
{
State,
Color
public readonly string DrawnDecal;
public CrayonUsedMessage(string drawn)
{
DrawnDecal = drawn;
}
}
/// <summary>
/// Component state, describes how many charges are left in the crayon in the near-hand UI
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonComponentState : ComponentState
{
@@ -60,10 +89,17 @@ namespace Content.Shared.Crayon
Capacity = capacity;
}
}
/// <summary>
/// The state of the crayon UI as sent by the server
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonBoundUserInterfaceState : BoundUserInterfaceState
{
public string Selected;
/// <summary>
/// Whether or not the color can be selected
/// </summary>
public bool SelectableColor;
public Color Color;

View File

@@ -8,3 +8,10 @@ crayon-interact-invalid-location = Can't reach there!
## UI
crayon-window-title = Crayon
crayon-window-placeholder = Search, or queue a comma-separated list of names
crayon-category-1-brushes = Brushes
crayon-category-2-alphanum = Numbers and letters
crayon-category-3-symbols = Symbols
crayon-category-4-info = Signs
crayon-category-5-graffiti = Graffiti
crayon-category-random = Random

File diff suppressed because it is too large Load Diff