Link to reagent ingredients on the same Guidebook page (#36700)
* Add in-page links for guidebook reagent recipes * Add links to microwave recipes * This function is too specific to be in Control extensions * Better naming * Wrap RichTextLabel instead of subclassing * "Activate" is ambiguous
This commit is contained in:
@@ -19,13 +19,15 @@ namespace Content.Client.Guidebook.Controls;
|
|||||||
/// Control for embedding a microwave recipe into a guidebook.
|
/// Control for embedding a microwave recipe into a guidebook.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UsedImplicitly, GenerateTypedNameReferences]
|
[UsedImplicitly, GenerateTypedNameReferences]
|
||||||
public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag, ISearchableControl
|
public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag, ISearchableControl, IPrototypeRepresentationControl
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
[Dependency] private readonly ILogManager _logManager = default!;
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
|
public IPrototype? RepresentedPrototype { get; private set; }
|
||||||
|
|
||||||
public GuideMicrowaveEmbed()
|
public GuideMicrowaveEmbed()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
@@ -80,6 +82,8 @@ public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag,
|
|||||||
{
|
{
|
||||||
var entity = _prototype.Index<EntityPrototype>(recipe.Result);
|
var entity = _prototype.Index<EntityPrototype>(recipe.Result);
|
||||||
|
|
||||||
|
RepresentedPrototype = entity;
|
||||||
|
|
||||||
IconContainer.AddChild(new GuideEntityEmbed(recipe.Result, false, false));
|
IconContainer.AddChild(new GuideEntityEmbed(recipe.Result, false, false));
|
||||||
ResultName.SetMarkup(entity.Name);
|
ResultName.SetMarkup(entity.Name);
|
||||||
ResultDescription.SetMarkup(entity.Description);
|
ResultDescription.SetMarkup(entity.Description);
|
||||||
@@ -99,8 +103,9 @@ public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag,
|
|||||||
solidNameMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-solid-name-display", ("ingredient", ingredient.Name)));
|
solidNameMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-solid-name-display", ("ingredient", ingredient.Name)));
|
||||||
solidNameMsg.Pop();
|
solidNameMsg.Pop();
|
||||||
|
|
||||||
var solidNameLabel = new RichTextLabel();
|
var solidNameLabel = new GuidebookRichPrototypeLink();
|
||||||
solidNameLabel.SetMessage(solidNameMsg);
|
solidNameLabel.SetMessage(solidNameMsg);
|
||||||
|
solidNameLabel.LinkedPrototype = ingredient;
|
||||||
|
|
||||||
IngredientsGrid.AddChild(solidNameLabel);
|
IngredientsGrid.AddChild(solidNameLabel);
|
||||||
|
|
||||||
@@ -129,9 +134,10 @@ public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag,
|
|||||||
liquidColorMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-color-display", ("color", reagent.SubstanceColor)));
|
liquidColorMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-color-display", ("color", reagent.SubstanceColor)));
|
||||||
liquidColorMsg.Pop();
|
liquidColorMsg.Pop();
|
||||||
|
|
||||||
var liquidColorLabel = new RichTextLabel();
|
var liquidColorLabel = new GuidebookRichPrototypeLink();
|
||||||
liquidColorLabel.SetMessage(liquidColorMsg);
|
liquidColorLabel.SetMessage(liquidColorMsg);
|
||||||
liquidColorLabel.HorizontalAlignment = Control.HAlignment.Center;
|
liquidColorLabel.HorizontalAlignment = Control.HAlignment.Center;
|
||||||
|
liquidColorLabel.LinkedPrototype = reagent;
|
||||||
|
|
||||||
IngredientsGrid.AddChild(liquidColorLabel);
|
IngredientsGrid.AddChild(liquidColorLabel);
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ namespace Content.Client.Guidebook.Controls;
|
|||||||
/// Control for embedding a reagent into a guidebook.
|
/// Control for embedding a reagent into a guidebook.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UsedImplicitly, GenerateTypedNameReferences]
|
[UsedImplicitly, GenerateTypedNameReferences]
|
||||||
public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISearchableControl
|
public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISearchableControl, IPrototypeRepresentationControl
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
|
|
||||||
private readonly ChemistryGuideDataSystem _chemistryGuideData;
|
private readonly ChemistryGuideDataSystem _chemistryGuideData;
|
||||||
|
|
||||||
|
public IPrototype? RepresentedPrototype { get; private set; }
|
||||||
|
|
||||||
public GuideReagentEmbed()
|
public GuideReagentEmbed()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
@@ -80,6 +82,8 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
|||||||
|
|
||||||
private void GenerateControl(ReagentPrototype reagent)
|
private void GenerateControl(ReagentPrototype reagent)
|
||||||
{
|
{
|
||||||
|
RepresentedPrototype = reagent;
|
||||||
|
|
||||||
NameBackground.PanelOverride = new StyleBoxFlat
|
NameBackground.PanelOverride = new StyleBoxFlat
|
||||||
{
|
{
|
||||||
BackgroundColor = reagent.SubstanceColor
|
BackgroundColor = reagent.SubstanceColor
|
||||||
|
|||||||
@@ -4,13 +4,11 @@
|
|||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
Margin="0 0 0 5">
|
Margin="0 0 0 5">
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<BoxContainer Name="ReactantsContainer" Orientation="Vertical" HorizontalExpand="True"
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
<RichTextLabel Name="ReactantsLabel"
|
<BoxContainer Name="ReactantsContainer" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical">
|
||||||
HorizontalAlignment="Center"
|
<!-- Reactants will be added as children here -->
|
||||||
VerticalAlignment="Center"
|
</BoxContainer>
|
||||||
Access="Public"
|
|
||||||
Visible="False" />
|
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
<TextureRect TexturePath="/Textures/Interface/Misc/beakerlarge.png"
|
<TextureRect TexturePath="/Textures/Interface/Misc/beakerlarge.png"
|
||||||
@@ -23,11 +21,9 @@
|
|||||||
Margin="2 0 0 0" />
|
Margin="2 0 0 0" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
|
||||||
<RichTextLabel Name="ProductsLabel"
|
<BoxContainer Name="ProductsContainer" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical">
|
||||||
HorizontalAlignment="Center"
|
<!-- Products will be added as children here -->
|
||||||
VerticalAlignment="Center"
|
</BoxContainer>
|
||||||
Access="Public"
|
|
||||||
Visible="False" />
|
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
||||||
|
|||||||
@@ -34,16 +34,16 @@ public sealed partial class GuideReagentReaction : BoxContainer, ISearchableCont
|
|||||||
|
|
||||||
public GuideReagentReaction(ReactionPrototype prototype, IPrototypeManager protoMan, IEntitySystemManager sysMan) : this(protoMan)
|
public GuideReagentReaction(ReactionPrototype prototype, IPrototypeManager protoMan, IEntitySystemManager sysMan) : this(protoMan)
|
||||||
{
|
{
|
||||||
var reactantsLabel = ReactantsLabel;
|
Container container = ReactantsContainer;
|
||||||
SetReagents(prototype.Reactants, ref reactantsLabel, protoMan);
|
SetReagents(prototype.Reactants, ref container, protoMan);
|
||||||
var productLabel = ProductsLabel;
|
Container productContainer = ProductsContainer;
|
||||||
var products = new Dictionary<string, FixedPoint2>(prototype.Products);
|
var products = new Dictionary<string, FixedPoint2>(prototype.Products);
|
||||||
foreach (var (reagent, reactantProto) in prototype.Reactants)
|
foreach (var (reagent, reactantProto) in prototype.Reactants)
|
||||||
{
|
{
|
||||||
if (reactantProto.Catalyst)
|
if (reactantProto.Catalyst)
|
||||||
products.Add(reagent, reactantProto.Amount);
|
products.Add(reagent, reactantProto.Amount);
|
||||||
}
|
}
|
||||||
SetReagents(products, ref productLabel, protoMan);
|
SetReagents(products, ref productContainer, protoMan, false);
|
||||||
|
|
||||||
var mixingCategories = new List<MixingCategoryPrototype>();
|
var mixingCategories = new List<MixingCategoryPrototype>();
|
||||||
if (prototype.MixingCategories != null)
|
if (prototype.MixingCategories != null)
|
||||||
@@ -85,8 +85,8 @@ public sealed partial class GuideReagentReaction : BoxContainer, ISearchableCont
|
|||||||
entContainer.AddChild(nameLabel);
|
entContainer.AddChild(nameLabel);
|
||||||
ReactantsContainer.AddChild(entContainer);
|
ReactantsContainer.AddChild(entContainer);
|
||||||
|
|
||||||
var productLabel = ProductsLabel;
|
Container productContainer = ProductsContainer;
|
||||||
SetReagents(solution.Contents, ref productLabel, protoMan);
|
SetReagents(solution.Contents, ref productContainer, protoMan, false);
|
||||||
SetMixingCategory(categories, null, sysMan);
|
SetMixingCategory(categories, null, sysMan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,75 +95,80 @@ public sealed partial class GuideReagentReaction : BoxContainer, ISearchableCont
|
|||||||
IPrototypeManager protoMan,
|
IPrototypeManager protoMan,
|
||||||
IEntitySystemManager sysMan) : this(protoMan)
|
IEntitySystemManager sysMan) : this(protoMan)
|
||||||
{
|
{
|
||||||
ReactantsLabel.Visible = true;
|
var label = new RichTextLabel();
|
||||||
ReactantsLabel.SetMarkup(Loc.GetString("guidebook-reagent-sources-gas-wrapper",
|
label.SetMarkup(Loc.GetString("guidebook-reagent-sources-gas-wrapper",
|
||||||
("name", Loc.GetString(prototype.Name).ToLower())));
|
("name", Loc.GetString(prototype.Name).ToLower())));
|
||||||
|
|
||||||
|
ReactantsContainer.Visible = true;
|
||||||
|
ReactantsContainer.AddChild(label);
|
||||||
|
|
||||||
if (prototype.Reagent != null)
|
if (prototype.Reagent != null)
|
||||||
{
|
{
|
||||||
var quantity = new Dictionary<string, FixedPoint2>
|
var quantity = new Dictionary<string, FixedPoint2>
|
||||||
{
|
{
|
||||||
{ prototype.Reagent, FixedPoint2.New(0.21f) }
|
{ prototype.Reagent, FixedPoint2.New(0.21f) }
|
||||||
};
|
};
|
||||||
var productLabel = ProductsLabel;
|
Container productContainer = ProductsContainer;
|
||||||
SetReagents(quantity, ref productLabel, protoMan);
|
SetReagents(quantity, ref productContainer, protoMan, false);
|
||||||
}
|
}
|
||||||
SetMixingCategory(categories, null, sysMan);
|
SetMixingCategory(categories, null, sysMan);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetReagents(List<ReagentQuantity> reagents, ref RichTextLabel label, IPrototypeManager protoMan)
|
private void SetReagents(List<ReagentQuantity> reagents, ref Container container, IPrototypeManager protoMan, bool addLinks = true)
|
||||||
{
|
{
|
||||||
var amounts = new Dictionary<string, FixedPoint2>();
|
var amounts = new Dictionary<string, FixedPoint2>();
|
||||||
foreach (var (reagent, quantity) in reagents)
|
foreach (var (reagent, quantity) in reagents)
|
||||||
{
|
{
|
||||||
amounts.Add(reagent.Prototype, quantity);
|
amounts.Add(reagent.Prototype, quantity);
|
||||||
}
|
}
|
||||||
SetReagents(amounts, ref label, protoMan);
|
SetReagents(amounts, ref container, protoMan, addLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetReagents(
|
private void SetReagents(
|
||||||
Dictionary<string, ReactantPrototype> reactants,
|
Dictionary<string, ReactantPrototype> reactants,
|
||||||
ref RichTextLabel label,
|
ref Container container,
|
||||||
IPrototypeManager protoMan)
|
IPrototypeManager protoMan,
|
||||||
|
bool addLinks = true)
|
||||||
{
|
{
|
||||||
var amounts = new Dictionary<string, FixedPoint2>();
|
var amounts = new Dictionary<string, FixedPoint2>();
|
||||||
foreach (var (reagent, reactantPrototype) in reactants)
|
foreach (var (reagent, reactantPrototype) in reactants)
|
||||||
{
|
{
|
||||||
amounts.Add(reagent, reactantPrototype.Amount);
|
amounts.Add(reagent, reactantPrototype.Amount);
|
||||||
}
|
}
|
||||||
SetReagents(amounts, ref label, protoMan);
|
SetReagents(amounts, ref container, protoMan, addLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
private void SetReagents(
|
private void SetReagents(
|
||||||
Dictionary<ProtoId<MixingCategoryPrototype>, ReactantPrototype> reactants,
|
Dictionary<ProtoId<MixingCategoryPrototype>, ReactantPrototype> reactants,
|
||||||
ref RichTextLabel label,
|
ref Container container,
|
||||||
IPrototypeManager protoMan)
|
IPrototypeManager protoMan,
|
||||||
|
bool addLinks = true)
|
||||||
{
|
{
|
||||||
var amounts = new Dictionary<string, FixedPoint2>();
|
var amounts = new Dictionary<string, FixedPoint2>();
|
||||||
foreach (var (reagent, reactantPrototype) in reactants)
|
foreach (var (reagent, reactantPrototype) in reactants)
|
||||||
{
|
{
|
||||||
amounts.Add(reagent, reactantPrototype.Amount);
|
amounts.Add(reagent, reactantPrototype.Amount);
|
||||||
}
|
}
|
||||||
SetReagents(amounts, ref label, protoMan);
|
SetReagents(amounts, ref container, protoMan, addLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetReagents(Dictionary<string, FixedPoint2> reagents, ref RichTextLabel label, IPrototypeManager protoMan)
|
private void SetReagents(Dictionary<string, FixedPoint2> reagents, ref Container container, IPrototypeManager protoMan, bool addLinks = true)
|
||||||
{
|
{
|
||||||
var msg = new FormattedMessage();
|
|
||||||
var reagentCount = reagents.Count;
|
|
||||||
var i = 0;
|
|
||||||
foreach (var (product, amount) in reagents.OrderByDescending(p => p.Value))
|
foreach (var (product, amount) in reagents.OrderByDescending(p => p.Value))
|
||||||
{
|
{
|
||||||
|
var productProto = protoMan.Index<ReagentPrototype>(product);
|
||||||
|
var msg = new FormattedMessage();
|
||||||
msg.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-recipes-reagent-display",
|
msg.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-recipes-reagent-display",
|
||||||
("reagent", protoMan.Index<ReagentPrototype>(product).LocalizedName), ("ratio", amount)));
|
("reagent", productProto.LocalizedName), ("ratio", amount)));
|
||||||
i++;
|
|
||||||
if (i < reagentCount)
|
var label = new GuidebookRichPrototypeLink();
|
||||||
msg.PushNewline();
|
if (addLinks)
|
||||||
|
label.LinkedPrototype = productProto;
|
||||||
|
label.SetMessage(msg);
|
||||||
|
container.AddChild(label);
|
||||||
}
|
}
|
||||||
msg.Pop();
|
container.Visible = true;
|
||||||
label.SetMessage(msg);
|
|
||||||
label.Visible = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetMixingCategory(IReadOnlyList<ProtoId<MixingCategoryPrototype>> mixingCategories, ReactionPrototype? prototype, IEntitySystemManager sysMan)
|
private void SetMixingCategory(IReadOnlyList<ProtoId<MixingCategoryPrototype>> mixingCategories, ReactionPrototype? prototype, IEntitySystemManager sysMan)
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using Content.Client.Guidebook.RichText;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Input;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Content.Client.UserInterface.ControlExtensions;
|
||||||
|
|
||||||
|
namespace Content.Client.Guidebook.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A RichTextLabel which is a link to a specified IPrototype.
|
||||||
|
/// The link is activated by the owner if the prototype is represented
|
||||||
|
/// somewhere in the same document.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GuidebookRichPrototypeLink : Control, IPrototypeLinkControl
|
||||||
|
{
|
||||||
|
private bool _linkActive = false;
|
||||||
|
private FormattedMessage? _message;
|
||||||
|
private readonly RichTextLabel _richTextLabel;
|
||||||
|
|
||||||
|
public void EnablePrototypeLink()
|
||||||
|
{
|
||||||
|
if (_message == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_linkActive = true;
|
||||||
|
|
||||||
|
DefaultCursorShape = CursorShape.Hand;
|
||||||
|
|
||||||
|
_richTextLabel.SetMessage(_message, null, TextLinkTag.LinkColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GuidebookRichPrototypeLink() : base()
|
||||||
|
{
|
||||||
|
MouseFilter = MouseFilterMode.Pass;
|
||||||
|
OnKeyBindDown += HandleClick;
|
||||||
|
_richTextLabel = new RichTextLabel();
|
||||||
|
AddChild(_richTextLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMessage(FormattedMessage message)
|
||||||
|
{
|
||||||
|
_message = message;
|
||||||
|
_richTextLabel.SetMessage(_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IPrototype? LinkedPrototype { get; set; }
|
||||||
|
|
||||||
|
private void HandleClick(GUIBoundKeyEventArgs args)
|
||||||
|
{
|
||||||
|
if (!_linkActive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.Function != EngineKeyFunctions.UIClick)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.TryGetParentHandler<IAnchorClickHandler>(out var handler))
|
||||||
|
{
|
||||||
|
handler.HandleAnchor(this);
|
||||||
|
args.Handle();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Logger.Warning("Warning! No valid IAnchorClickHandler found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAnchorClickHandler
|
||||||
|
{
|
||||||
|
public void HandleAnchor(IPrototypeLinkControl prototypeLinkControl);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.Guidebook.RichText;
|
using Content.Client.Guidebook.RichText;
|
||||||
using Content.Client.UserInterface.ControlExtensions;
|
using Content.Client.UserInterface.ControlExtensions;
|
||||||
@@ -6,6 +7,7 @@ using Content.Client.UserInterface.Controls.FancyTree;
|
|||||||
using Content.Client.UserInterface.Systems.Info;
|
using Content.Client.UserInterface.Systems.Info;
|
||||||
using Content.Shared.Guidebook;
|
using Content.Shared.Guidebook;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.ContentPack;
|
using Robust.Shared.ContentPack;
|
||||||
@@ -14,7 +16,7 @@ using Robust.Shared.Prototypes;
|
|||||||
namespace Content.Client.Guidebook.Controls;
|
namespace Content.Client.Guidebook.Controls;
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler, IAnchorClickHandler
|
||||||
{
|
{
|
||||||
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
|
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
|
||||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||||
@@ -53,6 +55,38 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
|||||||
ShowGuide(entry);
|
ShowGuide(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void HandleAnchor(IPrototypeLinkControl prototypeLinkControl)
|
||||||
|
{
|
||||||
|
var prototype = prototypeLinkControl.LinkedPrototype;
|
||||||
|
if (prototype == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var (linkableControls, _) = GetLinkableControlsAndLinks(EntryContainer);
|
||||||
|
foreach (var linkableControl in linkableControls)
|
||||||
|
{
|
||||||
|
if (linkableControl.RepresentedPrototype != prototype)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (linkableControl is not Control control)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if the target item is currently filtered out
|
||||||
|
if (!control.Visible)
|
||||||
|
control.Visible = true;
|
||||||
|
|
||||||
|
UserInterfaceManager.DeferAction(() =>
|
||||||
|
{
|
||||||
|
if (control.GetControlScrollPosition() is not {} position)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Scroll.HScrollTarget = position.X;
|
||||||
|
Scroll.VScrollTarget = position.Y;
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnSelectionChanged(TreeItem? item)
|
private void OnSelectionChanged(TreeItem? item)
|
||||||
{
|
{
|
||||||
if (item != null && item.Metadata is GuideEntry entry)
|
if (item != null && item.Metadata is GuideEntry entry)
|
||||||
@@ -94,6 +128,23 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
LastEntry = entry.Id;
|
LastEntry = entry.Id;
|
||||||
|
|
||||||
|
var (linkableControls, linkControls) = GetLinkableControlsAndLinks(EntryContainer);
|
||||||
|
|
||||||
|
HashSet<IPrototype> availablePrototypeLinks = new();
|
||||||
|
foreach (var linkableControl in linkableControls)
|
||||||
|
{
|
||||||
|
var prototype = linkableControl.RepresentedPrototype;
|
||||||
|
if (prototype != null)
|
||||||
|
availablePrototypeLinks.Add(prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var linkControl in linkControls)
|
||||||
|
{
|
||||||
|
var prototype = linkControl.LinkedPrototype;
|
||||||
|
if (prototype != null && availablePrototypeLinks.Contains(prototype))
|
||||||
|
linkControl.EnablePrototypeLink();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateGuides(
|
public void UpdateGuides(
|
||||||
@@ -209,4 +260,30 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (List<IPrototypeRepresentationControl>, List<IPrototypeLinkControl>) GetLinkableControlsAndLinks(Control parent)
|
||||||
|
{
|
||||||
|
List<IPrototypeRepresentationControl> linkableList = new();
|
||||||
|
List<IPrototypeLinkControl> linkList = new();
|
||||||
|
|
||||||
|
foreach (var child in parent.Children)
|
||||||
|
{
|
||||||
|
var hasChildren = child.ChildCount > 0;
|
||||||
|
|
||||||
|
if (child is IPrototypeLinkControl linkChild)
|
||||||
|
linkList.Add(linkChild);
|
||||||
|
else if (child is IPrototypeRepresentationControl linkableChild)
|
||||||
|
linkableList.Add(linkableChild);
|
||||||
|
|
||||||
|
if (!hasChildren)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var (childLinkableList, childLinkList) = GetLinkableControlsAndLinks(child);
|
||||||
|
|
||||||
|
linkableList.AddRange(childLinkableList);
|
||||||
|
linkList.AddRange(childLinkList);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (linkableList, linkList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
Content.Client/Guidebook/Controls/IPrototypeLinkControl.cs
Normal file
28
Content.Client/Guidebook/Controls/IPrototypeLinkControl.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.Guidebook.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for controls which represent a Prototype
|
||||||
|
/// These can be linked to from a IPrototypeLinkControl
|
||||||
|
/// </summary>
|
||||||
|
public interface IPrototypeRepresentationControl
|
||||||
|
{
|
||||||
|
// The prototype that this control represents
|
||||||
|
public IPrototype? RepresentedPrototype { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for controls which can be clicked to navigate
|
||||||
|
/// to a specified prototype representation on the same page.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPrototypeLinkControl
|
||||||
|
{
|
||||||
|
// This control is a link to the specified prototype
|
||||||
|
public IPrototype? LinkedPrototype { get; }
|
||||||
|
|
||||||
|
// Initially the link will not be enabled,
|
||||||
|
// the owner can enable the link once there is a valid target
|
||||||
|
// for the Prototype link.
|
||||||
|
public void EnablePrototypeLink();
|
||||||
|
}
|
||||||
@@ -5,12 +5,15 @@ using Robust.Client.UserInterface.Controls;
|
|||||||
using Robust.Client.UserInterface.RichText;
|
using Robust.Client.UserInterface.RichText;
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
using Content.Client.UserInterface.ControlExtensions;
|
||||||
|
|
||||||
namespace Content.Client.Guidebook.RichText;
|
namespace Content.Client.Guidebook.RichText;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class TextLinkTag : IMarkupTag
|
public sealed class TextLinkTag : IMarkupTag
|
||||||
{
|
{
|
||||||
|
public static Color LinkColor => Color.CornflowerBlue;
|
||||||
|
|
||||||
public string Name => "textlink";
|
public string Name => "textlink";
|
||||||
|
|
||||||
public Control? Control;
|
public Control? Control;
|
||||||
@@ -30,7 +33,7 @@ public sealed class TextLinkTag : IMarkupTag
|
|||||||
label.Text = text;
|
label.Text = text;
|
||||||
|
|
||||||
label.MouseFilter = Control.MouseFilterMode.Stop;
|
label.MouseFilter = Control.MouseFilterMode.Stop;
|
||||||
label.FontColorOverride = Color.CornflowerBlue;
|
label.FontColorOverride = LinkColor;
|
||||||
label.DefaultCursorShape = Control.CursorShape.Hand;
|
label.DefaultCursorShape = Control.CursorShape.Hand;
|
||||||
|
|
||||||
label.OnMouseEntered += _ => label.FontColorOverride = Color.LightSkyBlue;
|
label.OnMouseEntered += _ => label.FontColorOverride = Color.LightSkyBlue;
|
||||||
@@ -50,17 +53,10 @@ public sealed class TextLinkTag : IMarkupTag
|
|||||||
if (Control == null)
|
if (Control == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var current = Control;
|
if (Control.TryGetParentHandler<ILinkClickHandler>(out var handler))
|
||||||
while (current != null)
|
|
||||||
{
|
|
||||||
current = current.Parent;
|
|
||||||
|
|
||||||
if (current is not ILinkClickHandler handler)
|
|
||||||
continue;
|
|
||||||
handler.HandleClick(link);
|
handler.HandleClick(link);
|
||||||
return;
|
else
|
||||||
}
|
Logger.Warning("Warning! No valid ILinkClickHandler found.");
|
||||||
Logger.Warning($"Warning! No valid ILinkClickHandler found.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Numerics;
|
||||||
using Content.Client.Guidebook.Controls;
|
using Content.Client.Guidebook.Controls;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
@@ -68,6 +70,52 @@ public static class ControlExtension
|
|||||||
return controlList;
|
return controlList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search the control’s tree for a parent node of type T
|
||||||
|
/// E.g. to find the control implementing some event handling interface.
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryGetParentHandler<T>(this Control child, [NotNullWhen(true)] out T? result)
|
||||||
|
{
|
||||||
|
for (var control = child; control is not null; control = control.Parent)
|
||||||
|
{
|
||||||
|
if (control is not T handler)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result = handler;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the control’s offset relative to its closest ScrollContainer
|
||||||
|
/// Returns null if the control is not in the tree or not visible.
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2? GetControlScrollPosition(this Control child)
|
||||||
|
{
|
||||||
|
if (!child.VisibleInTree)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var position = new Vector2();
|
||||||
|
var control = child;
|
||||||
|
|
||||||
|
while (control is not null)
|
||||||
|
{
|
||||||
|
// The scroll container's direct child is re-positioned while scrolling,
|
||||||
|
// so we need to ignore its position.
|
||||||
|
if (control.Parent is ScrollContainer)
|
||||||
|
break;
|
||||||
|
|
||||||
|
position += control.Position;
|
||||||
|
|
||||||
|
control = control.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool ChildrenContainText(this Control parent, string search)
|
public static bool ChildrenContainText(this Control parent, string search)
|
||||||
{
|
{
|
||||||
var labels = parent.GetControlOfType<Label>();
|
var labels = parent.GetControlOfType<Label>();
|
||||||
|
|||||||
Reference in New Issue
Block a user