From 335f13c22a89da64eca9c0b69f5e415456d0b05c Mon Sep 17 00:00:00 2001
From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Date: Sat, 25 Jul 2026 18:35:19 +0200
Subject: [PATCH] Add ability to include "experimental" icon for labeled PRs
(#39686)
* Initial commit
* Include correct license
* Review changes
* And some stuff I missed
* Make entries have a separate control
* Remove readonly
---
Content.Client/Changelog/ChangelogEntry.xaml | 8 +++
.../Changelog/ChangelogEntry.xaml.cs | 66 ++++++++++++++++++
Content.Client/Changelog/ChangelogManager.cs | 12 +++-
Content.Client/Changelog/ChangelogTab.xaml.cs | 46 ++++--------
.../Interface/Changelog/attributions.yml | 4 ++
.../Interface/Changelog/test-tube.svg | 18 +++++
.../Changelog/test-tube.svg.192dpi.png | Bin 0 -> 396 bytes
.../Changelog/test-tube.svg.192dpi.png.yml | 2 +
Tools/actions_changelog_rss.py | 5 ++
Tools/actions_changelogs_since_last_run.py | 6 ++
10 files changed, 130 insertions(+), 37 deletions(-)
create mode 100644 Content.Client/Changelog/ChangelogEntry.xaml
create mode 100644 Content.Client/Changelog/ChangelogEntry.xaml.cs
create mode 100644 Resources/Textures/Interface/Changelog/attributions.yml
create mode 100644 Resources/Textures/Interface/Changelog/test-tube.svg
create mode 100644 Resources/Textures/Interface/Changelog/test-tube.svg.192dpi.png
create mode 100644 Resources/Textures/Interface/Changelog/test-tube.svg.192dpi.png.yml
diff --git a/Content.Client/Changelog/ChangelogEntry.xaml b/Content.Client/Changelog/ChangelogEntry.xaml
new file mode 100644
index 0000000000..3e1b2719d9
--- /dev/null
+++ b/Content.Client/Changelog/ChangelogEntry.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/Content.Client/Changelog/ChangelogEntry.xaml.cs b/Content.Client/Changelog/ChangelogEntry.xaml.cs
new file mode 100644
index 0000000000..67b533a513
--- /dev/null
+++ b/Content.Client/Changelog/ChangelogEntry.xaml.cs
@@ -0,0 +1,66 @@
+using System.Numerics;
+using Content.Client.Resources;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+using static Content.Client.Changelog.ChangelogManager;
+
+namespace Content.Client.Changelog;
+
+[GenerateTypedNameReferences]
+public sealed partial class ChangelogEntry : Control
+{
+ [Dependency] private IResourceCache _resourceCache = default!;
+
+ public ChangelogEntry()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ }
+
+ public void SetText(FormattedMessage text)
+ {
+ Text.SetMessage(text);
+ }
+
+ public void SetIcons(ChangelogLineType type, bool experimental)
+ {
+ var icon = GetIcon(type);
+ Icon.Texture = icon.Item1;
+ Icon.ModulateSelfOverride = icon.Item2;
+
+ if (experimental)
+ {
+ var expIcon = new TextureRect
+ {
+ Texture = _resourceCache.GetTexture(
+ new ResPath("/Textures/Interface/Changelog/test-tube.svg.192dpi.png")),
+ TextureScale = new Vector2(0.5f, 0.5f),
+ VerticalAlignment = VAlignment.Top,
+ Margin = new Thickness(2, 4, 0, 2),
+ ModulateSelfOverride = Color.FromHex("#A0d16E"),
+ };
+
+ Icon.Parent!.AddChild(expIcon);
+ expIcon.SetPositionInParent(1);
+ }
+ }
+
+ private (Texture, Color) GetIcon(ChangelogLineType type)
+ {
+ var (file, color) = type switch
+ {
+ ChangelogLineType.Add => ("plus.svg.192dpi.png", "#6ED18D"),
+ ChangelogLineType.Remove => ("minus.svg.192dpi.png", "#D16E6E"),
+ ChangelogLineType.Fix => ("bug.svg.192dpi.png", "#D1BA6E"),
+ ChangelogLineType.Tweak => ("wrench.svg.192dpi.png", "#6E96D1"),
+ _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
+ };
+
+ return (_resourceCache.GetTexture(new ResPath($"/Textures/Interface/Changelog/{file}")), Color.FromHex(color));
+ }
+}
diff --git a/Content.Client/Changelog/ChangelogManager.cs b/Content.Client/Changelog/ChangelogManager.cs
index 1bf99af19d..c233873ee3 100644
--- a/Content.Client/Changelog/ChangelogManager.cs
+++ b/Content.Client/Changelog/ChangelogManager.cs
@@ -195,17 +195,23 @@ namespace Content.Client.Changelog
[DataDefinition]
public sealed partial class ChangelogEntry
{
- [DataField("id")]
+ [DataField]
public int Id { get; private set; }
- [DataField("author")]
+ [DataField]
public string Author { get; private set; } = "";
[DataField]
public DateTime Time { get; private set; }
- [DataField("changes")]
+ [DataField]
public List Changes { get; private set; } = default!;
+
+ ///
+ /// Labels attached to the related PR, passed on via the SS14.Changelog parser.
+ ///
+ [DataField]
+ public List Labels { get; private set; } = [];
}
[DataDefinition]
diff --git a/Content.Client/Changelog/ChangelogTab.xaml.cs b/Content.Client/Changelog/ChangelogTab.xaml.cs
index 24d4f39036..3cbc2a4547 100644
--- a/Content.Client/Changelog/ChangelogTab.xaml.cs
+++ b/Content.Client/Changelog/ChangelogTab.xaml.cs
@@ -7,7 +7,6 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.ContentPack;
using Robust.Shared.Utility;
using static Content.Client.Changelog.ChangelogManager;
using static Robust.Client.UserInterface.Controls.BoxContainer;
@@ -22,6 +21,11 @@ public sealed partial class ChangelogTab : Control
public bool AdminOnly;
+ ///
+ /// Changelog is from a PR marked "Experimental" and should therefore have an additional icon.
+ ///
+ private static string _experimentalString = "Intent: Experimental";
+
public ChangelogTab()
{
RobustXamlLoader.Load(this);
@@ -134,43 +138,17 @@ public sealed partial class ChangelogTab : Control
FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", FormattedMessage.EscapeText(author)))));
ChangelogBody.AddChild(authorLabel);
- foreach (var change in groupedEntry.SelectMany(c => c.Changes))
+ foreach (var (labels, changes) in groupedEntry.Select(c => (c.Labels, c.Changes)))
{
- var text = new RichTextLabel();
- text.SetMessage(FormattedMessage.FromUnformatted(change.Message));
- ChangelogBody.AddChild(new BoxContainer
+ foreach (var change in changes)
{
- Orientation = LayoutOrientation.Horizontal,
- Margin = new Thickness(14, 1, 10, 2),
- Children =
- {
- GetIcon(change.Type),
- text
- }
- });
+ var entry = new ChangelogEntry();
+ entry.SetText(FormattedMessage.FromUnformatted(change.Message));
+ entry.SetIcons(change.Type, labels.Contains(_experimentalString));
+ ChangelogBody.AddChild(entry);
+ }
}
}
}
}
-
- private TextureRect GetIcon(ChangelogLineType type)
- {
- var (file, color) = type switch
- {
- ChangelogLineType.Add => ("plus.svg.192dpi.png", "#6ED18D"),
- ChangelogLineType.Remove => ("minus.svg.192dpi.png", "#D16E6E"),
- ChangelogLineType.Fix => ("bug.svg.192dpi.png", "#D1BA6E"),
- ChangelogLineType.Tweak => ("wrench.svg.192dpi.png", "#6E96D1"),
- _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
- };
-
- return new TextureRect
- {
- Texture = _resourceCache.GetTexture(new ResPath($"/Textures/Interface/Changelog/{file}")),
- VerticalAlignment = VAlignment.Top,
- TextureScale = new Vector2(0.5f, 0.5f),
- Margin = new Thickness(2, 4, 6, 2),
- ModulateSelfOverride = Color.FromHex(color)
- };
- }
}
diff --git a/Resources/Textures/Interface/Changelog/attributions.yml b/Resources/Textures/Interface/Changelog/attributions.yml
new file mode 100644
index 0000000000..da5a3dcee7
--- /dev/null
+++ b/Resources/Textures/Interface/Changelog/attributions.yml
@@ -0,0 +1,4 @@
+- files: ["test-tube.svg"]
+ license: "CC-BY-4.0"
+ copyright: "Solar Icons"
+ source: "https://www.svgrepo.com/svg/526351/test-tube-minimalistic"
diff --git a/Resources/Textures/Interface/Changelog/test-tube.svg b/Resources/Textures/Interface/Changelog/test-tube.svg
new file mode 100644
index 0000000000..54d968351a
--- /dev/null
+++ b/Resources/Textures/Interface/Changelog/test-tube.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/Resources/Textures/Interface/Changelog/test-tube.svg.192dpi.png b/Resources/Textures/Interface/Changelog/test-tube.svg.192dpi.png
new file mode 100644
index 0000000000000000000000000000000000000000..dd38ab7facc6e2378c76339f08472628a9336a3c
GIT binary patch
literal 396
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50
z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10!Z^Su#1%;YCj|i2Z`VFD
z0Z8$e1o;IsBpjH~u)yGhz=a1J3LFBWSl+z@N--vRySp%Su*!M>Ih+L^k;Oo31wokc
zl-g%|px|sz7sn8b({m@@7CNNB<1#%&acR)M|M{%PR!*5 str:
for entry in sorted(group, key=lambda x: x["time"]):
for change in entry["changes"]:
emoji = TYPES_TO_EMOJI.get(change["type"], "")
+ if EXPERIMENTAL_LABEL in entry["labels"]
+ emoji = f"{emoji}{EXPERIMENTAL_EMOJI}"
msg = change["message"]
desc.write(f"{emoji} {html.escape(msg)}")
diff --git a/Tools/actions_changelogs_since_last_run.py b/Tools/actions_changelogs_since_last_run.py
index 876fb65aa5..b80463fd21 100755
--- a/Tools/actions_changelogs_since_last_run.py
+++ b/Tools/actions_changelogs_since_last_run.py
@@ -31,6 +31,9 @@ CHANGELOG_FILE = "Resources/Changelog/Changelog.yml"
TYPES_TO_EMOJI = {"Fix": "๐", "Add": "๐", "Remove": "โ", "Tweak": "โ๏ธ"}
+EXPERIMENTAL_LABEL = "Intent: Experimental"
+EXPERIMENTAL_EMOJI = "๐งช"
+
ChangelogEntry = dict[str, Any]
@@ -239,6 +242,9 @@ def changelog_entries_to_message_lines(entries: Iterable[ChangelogEntry]) -> lis
emoji = TYPES_TO_EMOJI.get(change["type"], "โ")
message = change["message"]
+ if EXPERIMENTAL_LABEL in entry["labels"]
+ emoji = f"{emoji}{EXPERIMENTAL_EMOJI}"
+
message_lines.append(create_change_line(emoji, message, url))
return message_lines