diff --git a/Content.Client/UserInterface/Controls/TextLinkLabel.cs b/Content.Client/UserInterface/Controls/TextLinkLabel.cs index 1600e979b8..afd4fa48a1 100644 --- a/Content.Client/UserInterface/Controls/TextLinkLabel.cs +++ b/Content.Client/UserInterface/Controls/TextLinkLabel.cs @@ -1,13 +1,85 @@ +using Content.Client.UserInterface.ControlExtensions; +using Content.Client.UserInterface.RichText; +using Content.Shared.Chat; +using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; +using Robust.Shared.Input; +using Robust.Shared.Utility; + namespace Content.Client.UserInterface.Controls; /// /// Carries link data parsed and resolved from a TextLink , -/// only one field per link should be populated at a time. +/// only one LinkType field per link should be populated at a time. /// -public sealed class TextLinkLabel : Label +public sealed partial class TextLinkLabel : Label { - public string? LinkString { get; init; } - public NetEntity? LinkEntity { get; init; } + public string? LinkString { get; init; } // default links + public NetEntity? LinkEntity { get; init; } // entity links + public Color LinkColor { get; set; } + + [Dependency] private IEntityManager _entity = default!; + private bool canClickLink; + + public TextLinkLabel() + { + IoCManager.InjectDependencies(this); + OnKeyBindDown += OnKeybindDown; + OnMouseEntered += _ => OnHoverChanged(true); + OnMouseExited += _ => OnHoverChanged(false); + } + + /// + /// Refreshes the label's properties based on the current link target + /// and the viewer's permission to follow it.
Call after construction, + /// and whenever that permission could have changed. + ///
+ /// Whether the label should be shown at all. Defaults to true. + /// Additional override to force the label non-clickable. Defaults to true. + public void UpdateLabelProperties(SharedChatSystem chatSystem, bool? visible = null, bool? clickable = null) + { + visible ??= true; + Visible = visible.Value; + clickable ??= true; + + canClickLink = (LinkString != null || (LinkEntity is { } netEntity && chatSystem.CanClickMessageSender(netEntity))) && (bool)clickable; + + MouseFilter = canClickLink ? MouseFilterMode.Stop : MouseFilterMode.Ignore; + DefaultCursorShape = canClickLink ? CursorShape.Hand : CursorShape.Arrow; + + OnHoverChanged(false); + } + + private void OnHoverChanged(bool hovering) + { + FontColorOverride = (canClickLink, hovering) switch + { + (true, true) => Color.LightSkyBlue, // clickable and currently hovered + _ => LinkColor, // not clickable, or not hovered + }; + } + + /// + /// Delegates click to the nearest ancestor ILinkClickHandler or IEntityLinkClickHandler; + /// TextLinkLabel has no idea what a click actually does. + /// + private void OnKeybindDown(GUIBoundKeyEventArgs args) + { + if (args.Function != EngineKeyFunctions.UIClick) + return; + + if (LinkString is null && LinkEntity is null) + return; + + if (LinkEntity is { } entity && + this.TryGetParentHandler(out var entityLinkClickHandler)) + { + entityLinkClickHandler.HandleClick(entity); + } + else if (LinkString != null && this.TryGetParentHandler(out var linkClickHandler)) + { + linkClickHandler.HandleClick(LinkString); + } + } } diff --git a/Content.Client/UserInterface/RichText/TextLinkTag.Entity.cs b/Content.Client/UserInterface/RichText/TextLinkTag.Entity.cs index c68b375a0e..3b1b94f1e1 100644 --- a/Content.Client/UserInterface/RichText/TextLinkTag.Entity.cs +++ b/Content.Client/UserInterface/RichText/TextLinkTag.Entity.cs @@ -24,12 +24,9 @@ public sealed partial class TextLinkTag if (!NetEntity.TryParse(entStr, out var netEntity)) return false; - var chat = _entity.System(); - var clickable = chat.CanClickMessageSender(netEntity); - var color = GetLinkColor(node, netEntity); - data = new LinkData(LinkString: null, LinkEntity: netEntity, Color: color, Clickable: clickable); + data = new LinkData(LinkString: null, LinkEntity: netEntity, Color: color); return true; } diff --git a/Content.Client/UserInterface/RichText/TextLinkTag.Plain.cs b/Content.Client/UserInterface/RichText/TextLinkTag.Plain.cs index d18e21116e..9defaf0253 100644 --- a/Content.Client/UserInterface/RichText/TextLinkTag.Plain.cs +++ b/Content.Client/UserInterface/RichText/TextLinkTag.Plain.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Utility; +using Robust.Shared.Utility; namespace Content.Client.UserInterface.RichText; @@ -15,7 +15,7 @@ public sealed partial class TextLinkTag return false; } - data = new LinkData(LinkString: linkStr, LinkEntity: null, Color: null, Clickable: true); + data = new LinkData(LinkString: linkStr, LinkEntity: null, Color: null); return true; } } diff --git a/Content.Client/UserInterface/RichText/TextLinkTag.cs b/Content.Client/UserInterface/RichText/TextLinkTag.cs index a9f78f10f5..98fde7e8ac 100644 --- a/Content.Client/UserInterface/RichText/TextLinkTag.cs +++ b/Content.Client/UserInterface/RichText/TextLinkTag.cs @@ -4,20 +4,25 @@ using JetBrains.Annotations; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.RichText; -using Robust.Shared.Input; using Robust.Shared.Utility; -using Content.Client.UserInterface.ControlExtensions; using Content.Client.UserInterface.Controls; +using Content.Shared.Chat; using Robust.Client.ResourceManagement; namespace Content.Client.UserInterface.RichText; /// /// Markup tag handler for [textlink="LinkText"] nodes. Renders a link -/// in rich text, covering two types: -/// plain links (link=) and entity links (entity=). -/// optional color= and entitynamecolor= parameters -/// allow setting a color override and opting into using entity name colors for entity links +/// in rich text, covering two link types: +/// +/// link="GuideEntryPrototypeID" — a plain link. +/// entity="NetEntity" — an entity link. +/// +/// Optional parameters: +/// +/// color="HexColor" — color override. +/// entitynamecolor="Bool" — entity links only; opt into using the entity's name color. +/// /// [UsedImplicitly] public sealed partial class TextLinkTag : IMarkupTagHandler @@ -25,6 +30,7 @@ public sealed partial class TextLinkTag : IMarkupTagHandler [Dependency] private IEntityManager _entity = default!; [Dependency] private IUserInterfaceManager _ui = default!; [Dependency] private IResourceCache _cache = default!; + private SharedChatSystem? _chat; public string Name => "textlink"; public static Color DefaultLinkColor => Color.CornflowerBlue; @@ -38,7 +44,7 @@ public sealed partial class TextLinkTag : IMarkupTagHandler /// /// Resolved Link Data, LinkString and LinkEntity should not be populated at the same time /// - private readonly record struct LinkData(string? LinkString, NetEntity? LinkEntity, Color? Color, bool Clickable); + private readonly record struct LinkData(string? LinkString, NetEntity? LinkEntity, Color? Color); public TextLinkTag() { @@ -67,25 +73,27 @@ public sealed partial class TextLinkTag : IMarkupTagHandler foreach (var (attrname, resolver) in _resolvers) { - if (node.Attributes.ContainsKey(attrname)) + if (!node.Attributes.ContainsKey(attrname)) + continue; + + if(!resolver(node, out linkData)) { - if(!resolver(node, out linkData)) - { - return false; - } - linkTypeResolved = true; - break; + return false; } + + linkTypeResolved = true; + break; } + if (!linkTypeResolved) { return false; } + // color= > resolver-supplied color > default var linkColor = ResolveColorOverride(node) ?? linkData.Color ?? DefaultLinkColor; - var linkLabel = new TextLinkLabel() { Text = text, LinkString = linkData.LinkString, LinkEntity = linkData.LinkEntity }; - linkLabel.FontColorOverride = linkColor; + var linkLabel = new TextLinkLabel() { Text = text, LinkString = linkData.LinkString, LinkEntity = linkData.LinkEntity, LinkColor = linkColor}; // eat my ass about where this magic number comes from // our UI stack is awful. Finding this magic number was awful. @@ -96,14 +104,8 @@ public sealed partial class TextLinkTag : IMarkupTagHandler linkLabel.FontOverride = boldFont; } - if (linkData.Clickable) - { - linkLabel.MouseFilter = Control.MouseFilterMode.Stop; - linkLabel.DefaultCursorShape = Control.CursorShape.Hand; - linkLabel.OnMouseEntered += _ => linkLabel.FontColorOverride = Color.LightSkyBlue; - linkLabel.OnMouseExited += _ => linkLabel.FontColorOverride = linkColor; - linkLabel.OnKeyBindDown += args => OnKeybindDown(args, linkLabel); - } + _chat ??= _entity.System(); + linkLabel.UpdateLabelProperties(_chat); control = linkLabel; return true; @@ -119,28 +121,6 @@ public sealed partial class TextLinkTag : IMarkupTagHandler return Color.TryFromHex(colorStr, out var color) ? color : null; } - - /// - /// Delegates to the nearest ancestor ILinkClickHandler or IEntityLinkClickHandler; - /// TextLinkTag has no idea what a click actually does. - /// - private void OnKeybindDown(GUIBoundKeyEventArgs args, TextLinkLabel? control) - { - if (args.Function != EngineKeyFunctions.UIClick) - return; - - if (control == null) - return; - - if (control.LinkEntity is { } entity && control.TryGetParentHandler(out var entityLinkClickHandler)) - { - entityLinkClickHandler.HandleClick(entity); - } - else if (control.LinkString != null && control.TryGetParentHandler(out var linkClickHandler)) - { - linkClickHandler.HandleClick(control.LinkString); - } - } } /// diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index 4f95bb9021..2140879aa9 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -67,6 +67,7 @@ public sealed partial class ChatUIController : UIController [UISystemDependency] private readonly MindSystem? _mindSystem = default!; [UISystemDependency] private readonly RoleCodewordSystem? _roleCodewordSystem = default!; + private SharedChatSystem? _sharedChatSys; private static readonly ProtoId ChatNamePalette = "ChatNames"; private string[] _chatNameColors = default!; private bool _chatNameColorsEnabled; @@ -429,7 +430,7 @@ public sealed partial class ChatUIController : UIController { UpdateChannelPermissions(); UpdateAutoFillHighlights(); - Repopulate(); + UpdateLinkLabels(); } private void AddSpeechBubble(ChatMessage msg, SpeechBubble.SpeechType speechType) @@ -942,6 +943,16 @@ public sealed partial class ChatUIController : UIController } } + private void UpdateLinkLabels() + { + _sharedChatSys ??= _ent.System(); + + foreach (var chat in _chats) + { + chat.UpdateTextLinkLabelProperties(_sharedChatSys); + } + } + /// /// Returns the chat name color for a mob /// diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs index 8a8b6750da..f5545a496f 100644 --- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs @@ -1,3 +1,4 @@ +using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.RichText; using Content.Client.UserInterface.Systems.Chat.Controls; using Content.Shared.Chat; @@ -91,6 +92,20 @@ public partial class ChatBox : UIWidget, IEntityLinkClickHandler } } + /// + /// Calls update function on every child in the ChatBox's OutputPanel "Contents" + /// + public void UpdateTextLinkLabelProperties(SharedChatSystem _sharedChatSys) + { + foreach (var contentsChild in Contents.Children) + { + if (contentsChild is TextLinkLabel textLinkLabel) + { + textLinkLabel.UpdateLabelProperties(_sharedChatSys); + } + } + } + private void OnChannelFilter(ChatChannel channel, bool active) { Contents.Clear();