diff --git a/Content.Client/Labels/EntitySystems/HandLabelerSystem.cs b/Content.Client/Labels/EntitySystems/HandLabelerSystem.cs new file mode 100644 index 0000000000..e956014b2e --- /dev/null +++ b/Content.Client/Labels/EntitySystems/HandLabelerSystem.cs @@ -0,0 +1,18 @@ +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 ent) + { + if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, HandLabelerUiKey.Key, out var bui) + && bui is HandLabelerBoundUserInterface cBui) + { + cBui.Reload(); + } + } +} diff --git a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs index d393c43f93..555f1ff09e 100644 --- a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs +++ b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs @@ -1,4 +1,5 @@ using Content.Shared.Labels; +using Content.Shared.Labels.Components; using Robust.Client.GameObjects; namespace Content.Client.Labels.UI @@ -8,11 +9,14 @@ namespace Content.Client.Labels.UI /// 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() @@ -27,24 +31,25 @@ namespace Content.Client.Labels.UI _window.OnClose += Close; _window.OnLabelChanged += OnLabelChanged; + Reload(); } private void OnLabelChanged(string newLabel) { - SendMessage(new HandLabelerLabelChangedMessage(newLabel)); - } - - /// - /// Update the UI state based on server-sent info - /// - /// - protected override void UpdateState(BoundUserInterfaceState state) - { - base.UpdateState(state); - if (_window == null || state is not HandLabelerBoundUserInterfaceState cast) + // Focus moment + if (_entManager.TryGetComponent(Owner, out HandLabelerComponent? labeler) && + labeler.AssignedLabel.Equals(newLabel)) return; - _window.SetCurrentLabel(cast.CurrentLabel); + SendPredictedMessage(new HandLabelerLabelChangedMessage(newLabel)); + } + + public void Reload() + { + if (_window == null || !_entManager.TryGetComponent(Owner, out HandLabelerComponent? component)) + return; + + _window.SetCurrentLabel(component.AssignedLabel); } protected override void Dispose(bool disposing) diff --git a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs index 7706c31f85..6482cdc1cc 100644 --- a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs +++ b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs @@ -9,17 +9,40 @@ namespace Content.Client.Labels.UI { public event Action? OnLabelChanged; + /// + /// Is the user currently entering text into the control? + /// + 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; } } } diff --git a/Content.Server/Labels/Label/Components/HandLabelerComponent.cs b/Content.Server/Labels/Label/Components/HandLabelerComponent.cs deleted file mode 100644 index 6c96cada9e..0000000000 --- a/Content.Server/Labels/Label/Components/HandLabelerComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -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(); - } -} diff --git a/Content.Server/Labels/Label/HandLabelerSystem.cs b/Content.Server/Labels/Label/HandLabelerSystem.cs index 84c41b0db5..d52bf26046 100644 --- a/Content.Server/Labels/Label/HandLabelerSystem.cs +++ b/Content.Server/Labels/Label/HandLabelerSystem.cs @@ -1,116 +1,8 @@ -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 +namespace Content.Server.Labels.Label; + +public sealed class HandLabelerSystem : SharedHandLabelerSystem { - /// - /// A hand labeler system that lets an object apply labels to objects with the . - /// - [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(AfterInteractOn); - SubscribeLocalEvent>(OnUtilityVerb); - // Bound UI subscriptions - SubscribeLocalEvent(OnHandLabelerLabelChanged); - } - - private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent 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); - - // 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; - - _userInterfaceSystem.SetUiState(uid, HandLabelerUiKey.Key, - new HandLabelerBoundUserInterfaceState(handLabeler.AssignedLabel)); - } - } } diff --git a/Content.Server/Labels/Label/LabelSystem.cs b/Content.Server/Labels/Label/LabelSystem.cs index dd4ba1f718..aee2abe7ab 100644 --- a/Content.Server/Labels/Label/LabelSystem.cs +++ b/Content.Server/Labels/Label/LabelSystem.cs @@ -50,7 +50,7 @@ namespace Content.Server.Labels /// intended label text (null to remove) /// label component for resolve /// metadata component for resolve - 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; diff --git a/Content.Shared/Labels/Components/HandLabelerComponent.cs b/Content.Shared/Labels/Components/HandLabelerComponent.cs new file mode 100644 index 0000000000..8e2cb7b067 --- /dev/null +++ b/Content.Shared/Labels/Components/HandLabelerComponent.cs @@ -0,0 +1,30 @@ +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; +} diff --git a/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs b/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs new file mode 100644 index 0000000000..7dbeee3e77 --- /dev/null +++ b/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs @@ -0,0 +1,129 @@ +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(AfterInteractOn); + SubscribeLocalEvent>(OnUtilityVerb); + // Bound UI subscriptions + SubscribeLocalEvent(OnHandLabelerLabelChanged); + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + } + + private void OnGetState(Entity ent, ref ComponentGetState args) + { + args.State = new HandLabelerComponentState(ent.Comp.AssignedLabel) + { + MaxLabelChars = ent.Comp.MaxLabelChars, + }; + } + + private void OnHandleState(Entity 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 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 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}\""); + } +} diff --git a/Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs b/Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs index a8239e7867..1189bb46d0 100644 --- a/Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs +++ b/Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs @@ -13,6 +13,8 @@ public abstract partial class SharedLabelSystem : EntitySystem SubscribeLocalEvent(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)) diff --git a/Content.Shared/Labels/LabelEvents.cs b/Content.Shared/Labels/LabelEvents.cs index 9f00354af2..62e9c15c85 100644 --- a/Content.Shared/Labels/LabelEvents.cs +++ b/Content.Shared/Labels/LabelEvents.cs @@ -1,47 +1,27 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Labels +namespace Content.Shared.Labels; + +/// +/// Key representing which is currently open. +/// Useful when there are multiple UI for an object. Here it's future-proofing only. +/// +[Serializable, NetSerializable] +public enum HandLabelerUiKey { - /// - /// Key representing which is currently open. - /// Useful when there are multiple UI for an object. Here it's future-proofing only. - /// - [Serializable, NetSerializable] - public enum HandLabelerUiKey - { - Key, - } - - [Serializable, NetSerializable] - public enum PaperLabelVisuals : byte - { - Layer, - HasLabel, - LabelType - } - - /// - /// Represents a state that can be sent to the client - /// - [Serializable, NetSerializable] - public sealed class HandLabelerBoundUserInterfaceState : BoundUserInterfaceState - { - public string CurrentLabel { get; } - - public HandLabelerBoundUserInterfaceState(string currentLabel) - { - CurrentLabel = currentLabel; - } - } - - [Serializable, NetSerializable] - public sealed class HandLabelerLabelChangedMessage : BoundUserInterfaceMessage - { - public string Label { get; } - - public HandLabelerLabelChangedMessage(string label) - { - Label = label; - } - } + Key, +} + +[Serializable, NetSerializable] +public enum PaperLabelVisuals : byte +{ + Layer, + HasLabel, + LabelType +} + +[Serializable, NetSerializable] +public sealed class HandLabelerLabelChangedMessage(string label) : BoundUserInterfaceMessage +{ + public string Label { get; } = label; }