diff --git a/Chat/Ui/ChatPanel.cs b/Chat/Ui/ChatPanel.cs
index a0dd730..17a2bdb 100644
--- a/Chat/Ui/ChatPanel.cs
+++ b/Chat/Ui/ChatPanel.cs
@@ -75,6 +75,7 @@ public void Dispose()
ViewportResizeNotifier.Instance.OnViewportResized -= OnViewportResized;
_model.OnMessageAppended -= OnMessageAppended;
+ LocManager.Instance.UnsubscribeToLocaleChange(OnLocaleChanged);
_panelStyle.Dispose();
_inputStyle.Dispose();
_container.QueueFree();
@@ -461,6 +462,7 @@ private void CreateUi()
};
_messageBuffer.AddThemeColorOverride("default_color", Colors.White);
_messageBuffer.AddThemeFontSizeOverride("normal_font_size", ChatConfig.FontSize);
+ StsUiFonts.Apply(_messageBuffer);
_vboxLayout.AddChild(_messageBuffer);
_messageBuffer.MetaClicked += OnMetaClicked;
@@ -489,6 +491,7 @@ private void CreateUi()
_inputField.AddThemeColorOverride("font_placeholder_color", ChatConfig.PlaceholderColor);
_inputField.AddThemeColorOverride("caret_color", ChatConfig.CaretColor);
_inputField.AddThemeFontSizeOverride("font_size", ChatConfig.FontSize);
+ StsUiFonts.Apply(_inputField);
_inputStyle = new StyleBoxFlat { BgColor = ChatConfig.InputBgColor };
_inputField.AddThemeStyleboxOverride("normal", _inputStyle);
@@ -552,6 +555,7 @@ internal void Initialize()
// 订阅窗口大小变化事件
ViewportResizeNotifier.Instance.OnViewportResized += OnViewportResized;
+ LocManager.Instance.SubscribeToLocaleChange(OnLocaleChanged);
// 注册到 InputCapture,让其放过 ChatPanel 内部的 Alt+Click
ItemInputCapture.RegisterBlockingControl(_container);
@@ -563,6 +567,11 @@ private void OnViewportResized(Vector2 _)
RequestCompletionRefresh();
}
+ private void OnLocaleChanged()
+ {
+ StsUiFonts.Refresh(_container);
+ }
+
private void ShowWelcome()
{
var welcomeText = new LocString("gameplay_ui", "LEMONSPIRE.chat.welcome").GetFormattedText();
diff --git a/Chat/Ui/Completion/ChatCompletionPopupController.cs b/Chat/Ui/Completion/ChatCompletionPopupController.cs
index ba43004..cf5d0e3 100644
--- a/Chat/Ui/Completion/ChatCompletionPopupController.cs
+++ b/Chat/Ui/Completion/ChatCompletionPopupController.cs
@@ -1,5 +1,6 @@
using Godot;
using lemonSpire2.Chat.Input.Model;
+using lemonSpire2.util.Ui;
namespace lemonSpire2.Chat.Ui.Completion;
@@ -59,6 +60,7 @@ public ChatCompletionPopupController()
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill
};
_list.AddThemeFontSizeOverride("font_size", ChatConfig.FontSize);
+ StsUiFonts.Apply(_list);
_scroll.AddChild(_list);
_panel.AddChild(_scroll);
}
diff --git a/PlayerStateEx/OverlayPanel/PlayerOverlayPanel.cs b/PlayerStateEx/OverlayPanel/PlayerOverlayPanel.cs
index d5d8dd5..6160cc0 100644
--- a/PlayerStateEx/OverlayPanel/PlayerOverlayPanel.cs
+++ b/PlayerStateEx/OverlayPanel/PlayerOverlayPanel.cs
@@ -3,6 +3,8 @@
using lemonSpire2.util.Ui;
using MegaCrit.Sts2.Core.Combat;
using MegaCrit.Sts2.Core.Entities.Players;
+using MegaCrit.Sts2.Core.Localization;
+using MegaCrit.Sts2.Core.Localization.Fonts;
using MegaCrit.Sts2.Core.Nodes;
using MegaCrit.Sts2.Core.Platform;
using MegaCrit.Sts2.Core.Rooms;
@@ -55,6 +57,7 @@ public override void _Ready()
{
MouseFilter = MouseFilterEnum.Stop;
CreateUi();
+ LocManager.Instance.SubscribeToLocaleChange(OnLocaleChanged);
// 注册到 InputCapture,让其放过 Panel 内部的 Alt+Click
ItemInputCapture.RegisterBlockingControl(_panel);
@@ -87,6 +90,8 @@ public override void _ExitTree()
if (_onViewportResized != null)
ViewportResizeNotifier.Instance.OnViewportResized -= _onViewportResized;
+ LocManager.Instance.UnsubscribeToLocaleChange(OnLocaleChanged);
+
CombatManager.Instance.CombatSetUp -= OnCombatSetUp;
CombatManager.Instance.CombatEnded -= OnCombatEnded;
@@ -157,6 +162,7 @@ private void CreateProviderContents()
};
sectionTitle.AddThemeColorOverride("font_color", new Color(0.6f, 0.6f, 0.7f));
sectionTitle.AddThemeFontSizeOverride("font_size", 20);
+ StsUiFonts.Apply(sectionTitle, FontType.Bold);
sectionContainer.AddChild(sectionTitle);
_contentContainer.AddChild(sectionContainer);
@@ -354,6 +360,11 @@ private void OnCloseButtonPressed()
QueueFree();
}
+ private void OnLocaleChanged()
+ {
+ StsUiFonts.Refresh(this);
+ }
+
private void OnCombatSetUp(CombatState _)
{
Log.Debug("CombatSetUp, refreshing");
diff --git a/PlayerStateEx/PanelProvider/CardRewardProvider.cs b/PlayerStateEx/PanelProvider/CardRewardProvider.cs
index 2fece40..41db7f0 100644
--- a/PlayerStateEx/PanelProvider/CardRewardProvider.cs
+++ b/PlayerStateEx/PanelProvider/CardRewardProvider.cs
@@ -218,6 +218,7 @@ private static Control CreateCardControl(Player player, CardEntry cardEntry)
MouseFilter = Control.MouseFilterEnum.Ignore
};
brokenLabel.AddThemeColorOverride("font_color", new Color(0.8f, 0.2f, 0.2f));
+ StsUiFonts.Apply(brokenLabel);
return brokenLabel;
}
diff --git a/PlayerStateEx/PanelProvider/HandCardProvider.cs b/PlayerStateEx/PanelProvider/HandCardProvider.cs
index 36aa8d2..9b1ae40 100644
--- a/PlayerStateEx/PanelProvider/HandCardProvider.cs
+++ b/PlayerStateEx/PanelProvider/HandCardProvider.cs
@@ -105,6 +105,7 @@ public void UpdateContent(Player player, Control content)
};
countLabel.AddThemeColorOverride("font_color", new Color(0.7f, 0.7f, 0.75f));
countLabel.AddThemeFontSizeOverride("font_size", 16);
+ StsUiFonts.Apply(countLabel);
container.AddChild(countLabel);
var groupCards = LemonSpireConfig.GroupHandCards;
diff --git a/PlayerStateEx/PanelProvider/ShopProvider.cs b/PlayerStateEx/PanelProvider/ShopProvider.cs
index aa0e951..fb1d562 100644
--- a/PlayerStateEx/PanelProvider/ShopProvider.cs
+++ b/PlayerStateEx/PanelProvider/ShopProvider.cs
@@ -103,6 +103,7 @@ public void UpdateContent(Player player, Control content)
MouseFilter = Control.MouseFilterEnum.Ignore
};
emptyLabel.AddThemeColorOverride("font_color", new Color(0.5f, 0.5f, 0.5f));
+ StsUiFonts.Apply(emptyLabel);
container.AddChild(emptyLabel);
return;
}
@@ -195,6 +196,7 @@ private static HBoxContainer CreateGoldRow(Player player)
};
goldLabel.AddThemeColorOverride("font_color", StsColors.gold);
goldLabel.AddThemeFontSizeOverride("font_size", 16);
+ StsUiFonts.Apply(goldLabel);
row.AddChild(goldLabel);
var titleLabel = new Label
@@ -204,6 +206,7 @@ private static HBoxContainer CreateGoldRow(Player player)
};
titleLabel.AddThemeColorOverride("font_color", new Color(0.6f, 0.6f, 0.65f));
titleLabel.AddThemeFontSizeOverride("font_size", 16);
+ StsUiFonts.Apply(titleLabel);
row.AddChild(titleLabel);
return row;
@@ -338,6 +341,7 @@ private static Label CreatePriceLabel(Player player, int cost, bool isOnSale = f
priceLabel.AddThemeColorOverride("font_color", StsColors.cream);
priceLabel.AddThemeFontSizeOverride("font_size", 14);
+ StsUiFonts.Apply(priceLabel);
return priceLabel;
}
diff --git a/util/Ui/DraggableTitleBar.cs b/util/Ui/DraggableTitleBar.cs
index c3c01c5..127f210 100644
--- a/util/Ui/DraggableTitleBar.cs
+++ b/util/Ui/DraggableTitleBar.cs
@@ -1,4 +1,5 @@
using Godot;
+using MegaCrit.Sts2.Core.Localization.Fonts;
namespace lemonSpire2.util.Ui;
@@ -49,10 +50,12 @@ public override void _Ready()
// 内部 HBoxContainer 布局
_titleLabel.AddThemeColorOverride("font_color", new Color(0.9f, 0.9f, 0.95f));
_titleLabel.AddThemeFontSizeOverride("font_size", 20);
+ StsUiFonts.Apply(_titleLabel, FontType.Bold);
_hbox.AddChild(_titleLabel);
_closeButton.AddThemeColorOverride("font_color", new Color(0.7f, 0.7f, 0.7f));
_closeButton.AddThemeColorOverride("font_hover_color", new Color(1f, 0.3f, 0.3f));
+ StsUiFonts.Apply(_closeButton, FontType.Bold);
_hbox.AddChild(_closeButton);
AddChild(_hbox);
diff --git a/util/Ui/StsUiFonts.cs b/util/Ui/StsUiFonts.cs
new file mode 100644
index 0000000..ca2650a
--- /dev/null
+++ b/util/Ui/StsUiFonts.cs
@@ -0,0 +1,91 @@
+using Godot;
+using MegaCrit.Sts2.Core.Localization;
+using MegaCrit.Sts2.Core.Localization.Fonts;
+
+namespace lemonSpire2.util.Ui;
+
+///
+/// Applies the same font resources and locale substitutions used by the game's UI.
+///
+public static class StsUiFonts
+{
+ private const string RegularFontPath = "res://themes/kreon_regular_shared.tres";
+ private const string BoldFontPath = "res://themes/kreon_bold_shared.tres";
+ private const string ItalicFontPath = "res://themes/bitter_medium_italic_glyph_space_one.tres";
+
+ private static readonly StringName FontRoleMeta = "lemon_spire_font_role";
+ private static readonly StringName Font = "font";
+ private static readonly StringName NormalFont = "normal_font";
+ private static readonly StringName BoldFont = "bold_font";
+ private static readonly StringName ItalicsFont = "italics_font";
+
+ private static Font? _regularFont;
+ private static Font? _boldFont;
+ private static Font? _italicFont;
+
+ public static void Apply(Control control, FontType fontType = FontType.Regular)
+ {
+ ArgumentNullException.ThrowIfNull(control);
+
+ control.SetMeta(FontRoleMeta, (int)fontType);
+
+ if (control is RichTextLabel richTextLabel)
+ {
+ ApplyRichTextFonts(richTextLabel);
+ return;
+ }
+
+ control.AddThemeFontOverride(Font, GetFont(fontType));
+ }
+
+ ///
+ /// Reapplies fonts to controls previously registered through .
+ ///
+ public static void Refresh(Node root)
+ {
+ ArgumentNullException.ThrowIfNull(root);
+
+ if (root is Control control && control.HasMeta(FontRoleMeta))
+ {
+ var fontType = (FontType)control.GetMeta(FontRoleMeta).AsInt32();
+ Apply(control, fontType);
+ }
+
+ foreach (var child in root.GetChildren())
+ Refresh(child);
+ }
+
+ private static void ApplyRichTextFonts(RichTextLabel label)
+ {
+ label.AddThemeFontOverride(NormalFont, GetFont(FontType.Regular));
+ label.AddThemeFontOverride(BoldFont, GetFont(FontType.Bold));
+ label.AddThemeFontOverride(ItalicsFont, GetFont(FontType.Italic));
+ }
+
+ private static Font GetFont(FontType fontType)
+ {
+ var locManager = LocManager.Instance;
+ var substituteFont = locManager is null
+ ? null
+ : FontManager.GetSubstituteFont(locManager.Language, fontType);
+
+ return substituteFont ?? GetDefaultFont(fontType);
+ }
+
+ private static Font GetDefaultFont(FontType fontType)
+ {
+ return fontType switch
+ {
+ FontType.Regular => _regularFont ??= LoadFont(RegularFontPath),
+ FontType.Bold => _boldFont ??= LoadFont(BoldFontPath),
+ FontType.Italic => _italicFont ??= LoadFont(ItalicFontPath),
+ _ => throw new ArgumentOutOfRangeException(nameof(fontType), fontType, null)
+ };
+ }
+
+ private static Font LoadFont(string path)
+ {
+ return ResourceLoader.Load(path, null, ResourceLoader.CacheMode.Reuse)
+ ?? throw new InvalidOperationException($"Could not load game font resource: {path}");
+ }
+}