--- /dev/null
+using Content.Client.Labels.UI;
+using Content.Shared.Labels;
+using Content.Shared.Labels.Components;
+using Content.Shared.Labels.EntitySystems;
+
+namespace Content.Client.Labels.EntitySystems;
+
+public sealed class HandLabelerSystem : SharedHandLabelerSystem
+{
+ protected override void UpdateUI(Entity<HandLabelerComponent> ent)
+ {
+ if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, HandLabelerUiKey.Key, out var bui)
+ && bui is HandLabelerBoundUserInterface cBui)
+ {
+ cBui.Reload();
+ }
+ }
+}
using Content.Shared.Labels;
+using Content.Shared.Labels.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Labels.UI
/// </summary>
public sealed class HandLabelerBoundUserInterface : BoundUserInterface
{
+ [Dependency] private readonly IEntityManager _entManager = default!;
+
[ViewVariables]
private HandLabelerWindow? _window;
public HandLabelerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
+ IoCManager.InjectDependencies(this);
}
protected override void Open()
_window.OnClose += Close;
_window.OnLabelChanged += OnLabelChanged;
+ Reload();
}
private void OnLabelChanged(string newLabel)
{
- SendMessage(new HandLabelerLabelChangedMessage(newLabel));
+ // Focus moment
+ if (_entManager.TryGetComponent(Owner, out HandLabelerComponent? labeler) &&
+ labeler.AssignedLabel.Equals(newLabel))
+ return;
+
+ SendPredictedMessage(new HandLabelerLabelChangedMessage(newLabel));
}
- /// <summary>
- /// Update the UI state based on server-sent info
- /// </summary>
- /// <param name="state"></param>
- protected override void UpdateState(BoundUserInterfaceState state)
+ public void Reload()
{
- base.UpdateState(state);
- if (_window == null || state is not HandLabelerBoundUserInterfaceState cast)
+ if (_window == null || !_entManager.TryGetComponent(Owner, out HandLabelerComponent? component))
return;
- _window.SetCurrentLabel(cast.CurrentLabel);
+ _window.SetCurrentLabel(component.AssignedLabel);
}
protected override void Dispose(bool disposing)
{
public event Action<string>? OnLabelChanged;
+ /// <summary>
+ /// Is the user currently entering text into the control?
+ /// </summary>
+ private bool _focused;
+ // TODO LineEdit Make this a bool on the LineEdit control
+
+ private string _label = string.Empty;
+
public HandLabelerWindow()
{
RobustXamlLoader.Load(this);
- LabelLineEdit.OnTextEntered += e => OnLabelChanged?.Invoke(e.Text);
- LabelLineEdit.OnFocusExit += e => OnLabelChanged?.Invoke(e.Text);
+ LabelLineEdit.OnTextEntered += e =>
+ {
+ _label = e.Text;
+ OnLabelChanged?.Invoke(_label);
+ };
+
+ LabelLineEdit.OnFocusEnter += _ => _focused = true;
+ LabelLineEdit.OnFocusExit += _ =>
+ {
+ _focused = false;
+ LabelLineEdit.Text = _label;
+ };
}
public void SetCurrentLabel(string label)
{
- LabelLineEdit.Text = label;
+ if (label == _label)
+ return;
+
+ _label = label;
+ if (!_focused)
+ LabelLineEdit.Text = label;
}
}
}
+++ /dev/null
-using Content.Shared.Whitelist;
-
-namespace Content.Server.Labels.Components
-{
- [RegisterComponent]
- public sealed partial class HandLabelerComponent : Component
- {
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("assignedLabel")]
- public string AssignedLabel { get; set; } = string.Empty;
-
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("maxLabelChars")]
- public int MaxLabelChars { get; set; } = 50;
-
- [DataField("whitelist")]
- public EntityWhitelist Whitelist = new();
- }
-}
-using Content.Server.Labels.Components;
-using Content.Server.UserInterface;
-using Content.Server.Popups;
-using Content.Shared.Administration.Logs;
-using Content.Shared.Database;
-using Content.Shared.Interaction;
-using Content.Shared.Labels;
-using Content.Shared.Verbs;
-using JetBrains.Annotations;
-using Robust.Server.GameObjects;
-using Robust.Shared.Player;
+using Content.Shared.Labels.EntitySystems;
-namespace Content.Server.Labels
-{
- /// <summary>
- /// A hand labeler system that lets an object apply labels to objects with the <see cref="LabelComponent"/> .
- /// </summary>
- [UsedImplicitly]
- public sealed class HandLabelerSystem : EntitySystem
- {
- [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly LabelSystem _labelSystem = default!;
- [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<HandLabelerComponent, AfterInteractEvent>(AfterInteractOn);
- SubscribeLocalEvent<HandLabelerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
- // Bound UI subscriptions
- SubscribeLocalEvent<HandLabelerComponent, HandLabelerLabelChangedMessage>(OnHandLabelerLabelChanged);
- }
-
- private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent<UtilityVerb> args)
- {
- if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanAccess)
- return;
-
- string labelerText = handLabeler.AssignedLabel == string.Empty ? Loc.GetString("hand-labeler-remove-label-text") : Loc.GetString("hand-labeler-add-label-text");
-
- var verb = new UtilityVerb()
- {
- Act = () =>
- {
- AddLabelTo(uid, handLabeler, target, out var result);
- if (result != null)
- _popupSystem.PopupEntity(result, args.User, args.User);
- },
- Text = labelerText
- };
-
- args.Verbs.Add(verb);
- }
-
- private void AfterInteractOn(EntityUid uid, HandLabelerComponent handLabeler, AfterInteractEvent args)
- {
- if (args.Target is not {Valid: true} target || !handLabeler.Whitelist.IsValid(target) || !args.CanReach)
- return;
-
- AddLabelTo(uid, handLabeler, target, out string? result);
- if (result == null)
- return;
- _popupSystem.PopupEntity(result, args.User, args.User);
+namespace Content.Server.Labels.Label;
- // Log labeling
- _adminLogger.Add(LogType.Action, LogImpact.Low,
- $"{ToPrettyString(args.User):user} labeled {ToPrettyString(target):target} with {ToPrettyString(uid):labeler}");
- }
-
- private void AddLabelTo(EntityUid uid, HandLabelerComponent? handLabeler, EntityUid target, out string? result)
- {
- if (!Resolve(uid, ref handLabeler))
- {
- result = null;
- return;
- }
-
- if (handLabeler.AssignedLabel == string.Empty)
- {
- _labelSystem.Label(target, null);
- result = Loc.GetString("hand-labeler-successfully-removed");
- return;
- }
-
- _labelSystem.Label(target, handLabeler.AssignedLabel);
- result = Loc.GetString("hand-labeler-successfully-applied");
- }
-
- private void OnHandLabelerLabelChanged(EntityUid uid, HandLabelerComponent handLabeler, HandLabelerLabelChangedMessage args)
- {
- if (args.Actor is not {Valid: true} player)
- return;
-
- var label = args.Label.Trim();
- handLabeler.AssignedLabel = label.Substring(0, Math.Min(handLabeler.MaxLabelChars, label.Length));
- DirtyUI(uid, handLabeler);
-
- // Log label change
- _adminLogger.Add(LogType.Action, LogImpact.Low,
- $"{ToPrettyString(player):user} set {ToPrettyString(uid):labeler} to apply label \"{handLabeler.AssignedLabel}\"");
-
- }
-
- private void DirtyUI(EntityUid uid,
- HandLabelerComponent? handLabeler = null)
- {
- if (!Resolve(uid, ref handLabeler))
- return;
+public sealed class HandLabelerSystem : SharedHandLabelerSystem
+{
- _userInterfaceSystem.SetUiState(uid, HandLabelerUiKey.Key,
- new HandLabelerBoundUserInterfaceState(handLabeler.AssignedLabel));
- }
- }
}
/// <param name="text">intended label text (null to remove)</param>
/// <param name="label">label component for resolve</param>
/// <param name="metadata">metadata component for resolve</param>
- public void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null)
+ public override void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null)
{
if (!Resolve(uid, ref metadata))
return;
--- /dev/null
+using Content.Shared.Labels.EntitySystems;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Labels.Components;
+
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedHandLabelerSystem))]
+public sealed partial class HandLabelerComponent : Component
+{
+ [ViewVariables(VVAccess.ReadWrite), Access(Other = AccessPermissions.ReadWriteExecute)]
+ [DataField]
+ public string AssignedLabel = string.Empty;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
+ public int MaxLabelChars = 50;
+
+ [DataField]
+ public EntityWhitelist Whitelist = new();
+}
+
+[Serializable, NetSerializable]
+public sealed class HandLabelerComponentState(string assignedLabel) : IComponentState
+{
+ public string AssignedLabel = assignedLabel;
+
+ public int MaxLabelChars;
+}
--- /dev/null
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Interaction;
+using Content.Shared.Labels.Components;
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Robust.Shared.GameStates;
+using Robust.Shared.Network;
+
+namespace Content.Shared.Labels.EntitySystems;
+
+public abstract class SharedHandLabelerSystem : EntitySystem
+{
+ [Dependency] protected readonly SharedUserInterfaceSystem UserInterfaceSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedLabelSystem _labelSystem = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly INetManager _netManager = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<HandLabelerComponent, AfterInteractEvent>(AfterInteractOn);
+ SubscribeLocalEvent<HandLabelerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
+ // Bound UI subscriptions
+ SubscribeLocalEvent<HandLabelerComponent, HandLabelerLabelChangedMessage>(OnHandLabelerLabelChanged);
+ SubscribeLocalEvent<HandLabelerComponent, ComponentGetState>(OnGetState);
+ SubscribeLocalEvent<HandLabelerComponent, ComponentHandleState>(OnHandleState);
+ }
+
+ private void OnGetState(Entity<HandLabelerComponent> ent, ref ComponentGetState args)
+ {
+ args.State = new HandLabelerComponentState(ent.Comp.AssignedLabel)
+ {
+ MaxLabelChars = ent.Comp.MaxLabelChars,
+ };
+ }
+
+ private void OnHandleState(Entity<HandLabelerComponent> ent, ref ComponentHandleState args)
+ {
+ if (args.Current is not HandLabelerComponentState state)
+ return;
+
+ ent.Comp.MaxLabelChars = state.MaxLabelChars;
+
+ if (ent.Comp.AssignedLabel == state.AssignedLabel)
+ return;
+
+ ent.Comp.AssignedLabel = state.AssignedLabel;
+ UpdateUI(ent);
+ }
+
+ protected virtual void UpdateUI(Entity<HandLabelerComponent> ent)
+ {
+ }
+
+ private void AddLabelTo(EntityUid uid, HandLabelerComponent? handLabeler, EntityUid target, out string? result)
+ {
+ if (!Resolve(uid, ref handLabeler))
+ {
+ result = null;
+ return;
+ }
+
+ if (handLabeler.AssignedLabel == string.Empty)
+ {
+ if (_netManager.IsServer)
+ _labelSystem.Label(target, null);
+ result = Loc.GetString("hand-labeler-successfully-removed");
+ return;
+ }
+ if (_netManager.IsServer)
+ _labelSystem.Label(target, handLabeler.AssignedLabel);
+ result = Loc.GetString("hand-labeler-successfully-applied");
+ }
+
+ private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent<UtilityVerb> args)
+ {
+ if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanAccess)
+ return;
+
+ var labelerText = handLabeler.AssignedLabel == string.Empty ? Loc.GetString("hand-labeler-remove-label-text") : Loc.GetString("hand-labeler-add-label-text");
+
+ var verb = new UtilityVerb()
+ {
+ Act = () =>
+ {
+ Labeling(uid, target, args.User, handLabeler);
+ },
+ Text = labelerText
+ };
+
+ args.Verbs.Add(verb);
+ }
+
+ private void AfterInteractOn(EntityUid uid, HandLabelerComponent handLabeler, AfterInteractEvent args)
+ {
+ if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanReach)
+ return;
+
+ Labeling(uid, target, args.User, handLabeler);
+ }
+
+ private void Labeling(EntityUid uid, EntityUid target, EntityUid User, HandLabelerComponent handLabeler)
+ {
+ AddLabelTo(uid, handLabeler, target, out var result);
+ if (result == null)
+ return;
+
+ _popupSystem.PopupClient(result, User, User);
+
+ // Log labeling
+ _adminLogger.Add(LogType.Action, LogImpact.Low,
+ $"{ToPrettyString(User):user} labeled {ToPrettyString(target):target} with {ToPrettyString(uid):labeler}");
+ }
+
+ private void OnHandLabelerLabelChanged(EntityUid uid, HandLabelerComponent handLabeler, HandLabelerLabelChangedMessage args)
+ {
+ var label = args.Label.Trim();
+ handLabeler.AssignedLabel = label[..Math.Min(handLabeler.MaxLabelChars, label.Length)];
+ UpdateUI((uid, handLabeler));
+ Dirty(uid, handLabeler);
+
+ // Log label change
+ _adminLogger.Add(LogType.Action, LogImpact.Low,
+ $"{ToPrettyString(args.Actor):user} set {ToPrettyString(uid):labeler} to apply label \"{handLabeler.AssignedLabel}\"");
+ }
+}
SubscribeLocalEvent<LabelComponent, ExaminedEvent>(OnExamine);
}
+ public virtual void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null){}
+
private void OnExamine(EntityUid uid, LabelComponent? label, ExaminedEvent args)
{
if (!Resolve(uid, ref label))
using Robust.Shared.Serialization;
-namespace Content.Shared.Labels
-{
- /// <summary>
- /// Key representing which <see cref="PlayerBoundUserInterface"/> is currently open.
- /// Useful when there are multiple UI for an object. Here it's future-proofing only.
- /// </summary>
- [Serializable, NetSerializable]
- public enum HandLabelerUiKey
- {
- Key,
- }
-
- [Serializable, NetSerializable]
- public enum PaperLabelVisuals : byte
- {
- Layer,
- HasLabel,
- LabelType
- }
+namespace Content.Shared.Labels;
- /// <summary>
- /// Represents a <see cref="HandLabelerComponent"/> state that can be sent to the client
- /// </summary>
- [Serializable, NetSerializable]
- public sealed class HandLabelerBoundUserInterfaceState : BoundUserInterfaceState
- {
- public string CurrentLabel { get; }
-
- public HandLabelerBoundUserInterfaceState(string currentLabel)
- {
- CurrentLabel = currentLabel;
- }
- }
+/// <summary>
+/// Key representing which <see cref="PlayerBoundUserInterface"/> is currently open.
+/// Useful when there are multiple UI for an object. Here it's future-proofing only.
+/// </summary>
+[Serializable, NetSerializable]
+public enum HandLabelerUiKey
+{
+ Key,
+}
- [Serializable, NetSerializable]
- public sealed class HandLabelerLabelChangedMessage : BoundUserInterfaceMessage
- {
- public string Label { get; }
+[Serializable, NetSerializable]
+public enum PaperLabelVisuals : byte
+{
+ Layer,
+ HasLabel,
+ LabelType
+}
- public HandLabelerLabelChangedMessage(string label)
- {
- Label = label;
- }
- }
+[Serializable, NetSerializable]
+public sealed class HandLabelerLabelChangedMessage(string label) : BoundUserInterfaceMessage
+{
+ public string Label { get; } = label;
}