Skip to content

Commit

Permalink
Improve crayon UI to not be stuck in 1996 (space-wizards#33101)
Browse files Browse the repository at this point in the history
* Improve crayon UI to not be stuck in 1996

* Make a horrifying crayon spaghetti

* Crayon

* Undeprecate the crayon, describe the crayon
  • Loading branch information
SaphireLattice authored and sleepyyapril committed Jan 16, 2025
1 parent 882ce26 commit 26ffe94
Show file tree
Hide file tree
Showing 7 changed files with 449 additions and 317 deletions.
12 changes: 11 additions & 1 deletion Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected override void Open()
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)
Expand All @@ -44,6 +44,16 @@ public override void OnProtoReload(PrototypesReloadedEventArgs args)
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);
Expand Down
11 changes: 5 additions & 6 deletions Content.Client/Crayon/UI/CrayonWindow.xaml
Original file line number Diff line number Diff line change
@@ -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>
138 changes: 108 additions & 30 deletions Content.Client/Crayon/UI/CrayonWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand All @@ -28,8 +35,10 @@ public sealed partial class CrayonWindow : DefaultWindow
public CrayonWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();

Search.OnTextChanged += _ => RefreshList();
Search.OnTextChanged += SearchChanged;
ColorSelector.OnColorChanged += SelectColor;
}

Expand All @@ -44,51 +53,94 @@ private void SelectColor(Color color)
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))
{
_selected = first;
_autoSelected = _selected;
OnSelected?.Invoke(_selected);
}

foreach (var categoryName in names)
{
if (!decal.Contains(filter))
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();
}
Expand All @@ -107,12 +159,38 @@ public void UpdateState(CrayonBoundUserInterfaceState state)
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();
Expand Down
2 changes: 2 additions & 0 deletions Content.Server/Crayon/CrayonSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ private void OnCrayonAfterInteract(EntityUid uid, CrayonComponent component, Aft

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)
Expand Down
44 changes: 40 additions & 4 deletions Content.Shared/Crayon/SharedCrayonComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@

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
Expand All @@ -17,6 +28,9 @@ public enum CrayonUiKey : byte
}
}

/// <summary>
/// Used by the client to notify the server about the selected decal ID
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonSelectMessage : BoundUserInterfaceMessage
{
Expand All @@ -27,6 +41,9 @@ public CrayonSelectMessage(string selected)
}
}

/// <summary>
/// Sets the color of the crayon, used by Rainbow Crayon
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonColorMessage : BoundUserInterfaceMessage
{
Expand All @@ -37,13 +54,25 @@ public CrayonColorMessage(Color color)
}
}

/// <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
{
Expand All @@ -60,10 +89,17 @@ public CrayonComponentState(Color color, string state, int charges, int capacity
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;

Expand Down
7 changes: 7 additions & 0 deletions Resources/Locale/en-US/crayon/crayon-component.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 26ffe94

Please sign in to comment.