diff --git a/Localization/Locale/ko.json b/Localization/Locale/ko.json
index 6d8563a..1296232 100644
--- a/Localization/Locale/ko.json
+++ b/Localization/Locale/ko.json
@@ -23,7 +23,7 @@
"ui.render_config.audio_encoding": "오디오 인코딩 설정",
"ui.render_config.global_settings": "전역 설정",
"ui.render_config.advanced_settings": "고급 설정",
- "ui.render_config.go_render": "렌더 시작",
+ "ui.render_config.go_render": "렌더링하러 가다",
"ui.render_config.codec": "코덱",
"ui.render_config.rate_control": "비트레이트 제어 모드",
"ui.render_config.bitrate": "비트레이트 (kbps)",
diff --git a/UI/LocalizedText.cs b/UI/LocalizedText.cs
new file mode 100644
index 0000000..96bf389
--- /dev/null
+++ b/UI/LocalizedText.cs
@@ -0,0 +1,112 @@
+using System;
+using ADOFAIRenderer.Localization;
+using TMPro;
+using UnityEngine;
+
+namespace ADOFAIRenderer.UI;
+
+///
+/// Component that binds a TextMeshProUGUI component to a localization key or dynamic string provider,
+/// automatically updating the displayed text whenever the active language changes.
+///
+internal class LocalizedText : MonoBehaviour
+{
+ private TextMeshProUGUI? _textComponent;
+ private string? _key;
+ private Func? _dynamicProvider;
+
+ ///
+ /// Gets or sets the localization key. Setting this will clear any dynamic provider.
+ ///
+ public string? Key
+ {
+ get => _key;
+ set
+ {
+ _key = value;
+ _dynamicProvider = null;
+ Refresh();
+ }
+ }
+
+ ///
+ /// Sets a dynamic string provider function.
+ ///
+ public void SetDynamic(Func provider)
+ {
+ _dynamicProvider = provider;
+ _key = null;
+ Refresh();
+ }
+
+ private void Awake()
+ {
+ EnsureComponent();
+ LocalizationManager.OnLanguageChanged += HandleLanguageChanged;
+ }
+
+ private void OnEnable()
+ {
+ Refresh();
+ }
+
+ private void OnDestroy()
+ {
+ LocalizationManager.OnLanguageChanged -= HandleLanguageChanged;
+ }
+
+ private void HandleLanguageChanged(SystemLanguage language)
+ {
+ Refresh();
+ }
+
+ private void EnsureComponent()
+ {
+ if (_textComponent == null)
+ {
+ _textComponent = GetComponent();
+ }
+ }
+
+ ///
+ /// Refreshes the text using either the dynamic provider or the static localization key.
+ ///
+ public void Refresh()
+ {
+ EnsureComponent();
+ if (_textComponent == null) return;
+
+ if (_dynamicProvider != null)
+ {
+ _textComponent.text = _dynamicProvider();
+ }
+ else if (!string.IsNullOrEmpty(_key))
+ {
+ _textComponent.text = LocalizationManager.Get(_key);
+ }
+ }
+
+ ///
+ /// Binds a TextMeshProUGUI component to a localization key.
+ ///
+ public static LocalizedText Bind(TextMeshProUGUI target, string key)
+ {
+ if (target == null) throw new ArgumentNullException(nameof(target));
+ var component = target.GetComponent() ?? target.gameObject.AddComponent();
+ component._textComponent = target;
+ component.Key = key;
+ return component;
+ }
+
+ ///
+ /// Binds a TextMeshProUGUI component to a dynamic string provider function.
+ ///
+ public static LocalizedText Bind(TextMeshProUGUI target, Func provider)
+ {
+ if (target == null) throw new ArgumentNullException(nameof(target));
+ var component = target.GetComponent() ?? target.gameObject.AddComponent();
+ component._textComponent = target;
+ component.SetDynamic(provider);
+ return component;
+ }
+}
diff --git a/UI/Window/RenderConfigWindow.cs b/UI/Window/RenderConfigWindow.cs
index 9963feb..edede6e 100644
--- a/UI/Window/RenderConfigWindow.cs
+++ b/UI/Window/RenderConfigWindow.cs
@@ -87,6 +87,10 @@ internal static class RenderConfigWindow
Size = new Vector2(900, 600), Draggable = true, Resizable = true,
ShowCloseButton = true
});
+ if (_configWindow.TitleText != null)
+ {
+ LocalizedText.Bind(_configWindow.TitleText, "ui.render_config.window_title");
+ }
_configWindow.CloseRequested += win =>
{
@@ -95,6 +99,7 @@ internal static class RenderConfigWindow
};
BuildUI(_configWindow.Content);
+ LocalizationManager.OnLanguageChanged += OnLanguageChanged;
}
else
{
@@ -145,14 +150,9 @@ internal static class RenderConfigWindow
};
// Presets (Collapsible Card)
- var (_, presetContent) = O5Factory.Card(
+ var (_, presetContent) = CreateCard(
ctx, scrollContent,
- LocalizationManager.Get("ui.render_config.presets"),
- activeValue: true,
- onActiveChanged: null,
- onDeleteClick: null,
- showDeleteButton: false,
- showActiveToggle: false
+ "ui.render_config.presets"
);
var presets = PresetManager.GetPresets();
@@ -177,7 +177,7 @@ internal static class RenderConfigWindow
_presetDropdown = CreateDropdownField(
ctx, presetContent,
- LocalizationManager.Get("ui.render_config.preset_select"),
+ "ui.render_config.preset_select",
initialPresetName, initialPresetName,
presetNames, p => p,
"dd_presets",
@@ -213,18 +213,24 @@ internal static class RenderConfigWindow
icon: null,
id: "input_preset_name"
);
+ if (_presetNameInput.Placeholder != null)
+ {
+ LocalizedText.Bind(_presetNameInput.Placeholder, "ui.render_config.preset_name_placeholder");
+ }
+ // ReSharper disable once InconsistentNaming
var inputLE = _presetNameInput.Rect.GetComponent() ?? _presetNameInput.Rect.gameObject.AddComponent();
inputLE.flexibleWidth = 1f;
inputLE.preferredHeight = 40f;
- _savePresetButton = O5Factory.Button(
+ _savePresetButton = CreateButton(
ctx: ctx,
parent: actionsRowRect,
- onClick: () => OnSavePresetClicked(config),
- text: LocalizationManager.Get("ui.render_config.preset_save"),
+ textKey: "ui.render_config.preset_save",
id: "btn_preset_save",
+ onClick: () => OnSavePresetClicked(config),
height: 40f
);
+ // ReSharper disable once InconsistentNaming
var saveLE = _savePresetButton.Rect.GetComponent() ?? _savePresetButton.Rect.gameObject.AddComponent();
saveLE.preferredWidth = 90f;
saveLE.flexibleWidth = 0f;
@@ -241,12 +247,12 @@ internal static class RenderConfigWindow
fonts: ctx.Fonts,
tween: ctx.Tween
);
- _deletePresetButton = O5Factory.Button(
+ _deletePresetButton = CreateButton(
ctx: redCtx,
parent: actionsRowRect,
- onClick: () => OnDeletePresetClicked(config),
- text: LocalizationManager.Get("ui.render_config.preset_delete"),
+ textKey: "ui.render_config.preset_delete",
id: "btn_preset_delete",
+ onClick: () => OnDeletePresetClicked(config),
height: 40f
);
var deleteLE = _deletePresetButton.Rect.GetComponent() ?? _deletePresetButton.Rect.gameObject.AddComponent();
@@ -259,86 +265,72 @@ internal static class RenderConfigWindow
// 1-3. Top section: 좌/우 컨테이너 1개씩으로 3행을 쌓는다 (중앙선 1개)
var (topLeft, topRight) = CreateTwoColumnPanel(scrollContent, spacing: 8f, columnSpacing: 10f);
- _renderFpsSlider = O5Factory.Slider(
+ _renderFpsSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topLeft),
+ labelKey: "ui.render_config.render_fps", id: "slider_render_fps",
defaultValue: 60, min: 0.01, max: 3000, value: 60,
format: "F3", clampMode: ClampMode.All,
-
sliderFilter: value => Snap(renderFpsSnap, value),
- inputFilter: null, onComplete: null, onChanged: val => { config.RenderFPS = val; MarkConfigModified(); },
- text: LocalizationManager.Get("ui.render_config.render_fps"),
- id: "slider_render_fps", showFill: true
+ onChanged: val => { config.RenderFPS = val; MarkConfigModified(); }
);
- _videoFPSSlider = O5Factory.Slider(
+ _videoFPSSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topRight),
+ labelKey: "ui.render_config.video_fps", id: "slider_video_fps",
defaultValue: 60, min: 0.01, max: 600, value: 60,
format: "F3", clampMode: ClampMode.All,
-
sliderFilter: value => Snap(videoFpsSnap, value),
- inputFilter: null, onComplete: null, onChanged: val => { config.VideoFPS = val; MarkConfigModified(); },
- text: LocalizationManager.Get("ui.render_config.video_fps"),
- id: "slider_video_fps", showFill: true
+ onChanged: val => { config.VideoFPS = val; MarkConfigModified(); }
);
- _renderWidthSlider = O5Factory.Slider(
+ _renderWidthSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topLeft),
+ labelKey: "ui.render_config.render_width", id: "slider_render_width",
defaultValue: 2560, min: 0, max: 15360, value: 2560,
format: "F0", clampMode: ClampMode.All,
-
sliderFilter: value => Snap(renderWidthSnap, value), inputFilter: value => Math.Max(value, 2),
- onComplete: null, onChanged: val => { config.RenderWidth = (int)Math.Round(val); MarkConfigModified(); },
- text: LocalizationManager.Get("ui.render_config.render_width"),
- id: "slider_render_width", showFill: true
+ onChanged: val => { config.RenderWidth = (int)Math.Round(val); MarkConfigModified(); }
);
- _renderHeightSlider = O5Factory.Slider(
+ _renderHeightSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topRight),
+ labelKey: "ui.render_config.render_height", id: "slider_render_height",
defaultValue: 1440, min: 0, max: 8640, value: 1440,
format: "F0", clampMode: ClampMode.All,
-
sliderFilter: value => Snap(renderHeightSnap, value), inputFilter: value => Math.Max(value, 2),
- onComplete: null, onChanged: val => { config.RenderHeight = (int)Math.Round(val); MarkConfigModified(); },
- text: LocalizationManager.Get("ui.render_config.render_height"),
- id: "slider_render_height", showFill: true
+ onChanged: val => { config.RenderHeight = (int)Math.Round(val); MarkConfigModified(); }
);
- _delayAfterEndSlider = O5Factory.Slider(
+ _delayAfterEndSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topLeft),
+ labelKey: "ui.render_config.delay_after_end", id: "slider_delay_after_end",
defaultValue: 15, min: 0, max: 9999.999, value: 15,
format: "F3", clampMode: ClampMode.All,
-
- sliderFilter: null, inputFilter: null,
- onComplete: null, onChanged: val => { config.DelayAfterEnd = val; MarkConfigModified(); },
- text: LocalizationManager.Get("ui.render_config.delay_after_end"),
- id: "slider_delay_after_end", showFill: true
+ onChanged: val => { config.DelayAfterEnd = val; MarkConfigModified(); }
);
_showJudgementToggle = CreateToggleField(
- ctx, CreateFieldSlot(topRight), "",
- false, config.ShowJudgementText,
- LocalizationManager.Get("ui.render_config.show_judgement"),
- "toggle_show_judgement",
- val => { config.ShowJudgementText = val; MarkConfigModified(); },
+ ctx, CreateFieldSlot(topRight),
+ labelKey: "",
+ defaultValue: false,
+ value: config.ShowJudgementText,
+ toggleTextKey: "ui.render_config.show_judgement",
+ id: "toggle_show_judgement",
+ onChanged: val => { config.ShowJudgementText = val; MarkConfigModified(); },
generateLabel: false
);
// 4. Video Encoding Config (Collapsible Card)
- var (_, videoContent) = O5Factory.Card(
+ var (_, videoContent) = CreateCard(
ctx, scrollContent,
- LocalizationManager.Get("ui.render_config.video_encoding"),
- activeValue: true,
- onActiveChanged: null,
- onDeleteClick: null,
- showDeleteButton: false,
- showActiveToggle: false
+ "ui.render_config.video_encoding"
);
var (videoLeft, videoRight) = CreateTwoColumnPanel(videoContent);
var codecs = (Codec[])Enum.GetValues(typeof(Codec));
_codecDropdown = CreateDropdownField(
ctx, CreateFieldSlot(videoLeft),
- LocalizationManager.Get("ui.render_config.codec"),
+ "ui.render_config.codec",
Codec.H264, config.VideoConfig.Codec,
codecs, c => c.ToString(),
"dd_video_codec",
@@ -351,7 +343,7 @@ internal static class RenderConfigWindow
_bitrateInput = CreateInputField(
ctx, CreateFieldSlot(videoLeft),
- LocalizationManager.Get("ui.render_config.bitrate"),
+ "ui.render_config.bitrate",
"30000", config.VideoConfig.Bitrate.ToString(),
"input_bitrate", out _bitrateLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
@@ -360,7 +352,7 @@ internal static class RenderConfigWindow
_qpInput = CreateInputField(
ctx, CreateFieldSlot(videoLeft),
- LocalizationManager.Get("ui.render_config.qp"),
+ "ui.render_config.qp",
"18", config.VideoConfig.QP.ToString(),
"input_qp", out _qpLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
@@ -370,7 +362,7 @@ internal static class RenderConfigWindow
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
_rateControlDropdown = CreateDropdownField(
ctx, CreateFieldSlot(videoRight),
- LocalizationManager.Get("ui.render_config.rate_control"),
+ "ui.render_config.rate_control",
RateControlMode.CBR, config.VideoConfig.RateControlMode,
rateModes, m => m.ToString(),
"dd_rate_control",
@@ -384,7 +376,7 @@ internal static class RenderConfigWindow
_maxBitrateInput = CreateInputField(
ctx, CreateFieldSlot(videoRight),
- LocalizationManager.Get("ui.render_config.max_bitrate"),
+ "ui.render_config.max_bitrate",
"50000", config.VideoConfig.MaxBitrate.ToString(),
"input_max_bitrate", out _maxBitrateLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
@@ -392,61 +384,55 @@ internal static class RenderConfigWindow
);
_forceSoftwareToggle = CreateToggleField(
- ctx, CreateFieldSlot(videoRight), "",
- false, config.VideoConfig.ForceSoftwareEncoder,
- LocalizationManager.Get("ui.render_config.force_software"),
- "toggle_force_software",
- val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); },
+ ctx, CreateFieldSlot(videoRight),
+ labelKey: "",
+ defaultValue: false,
+ value: config.VideoConfig.ForceSoftwareEncoder,
+ toggleTextKey: "ui.render_config.force_software",
+ id: "toggle_force_software",
+ onChanged: val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); },
generateLabel: true
);
- _keyframeIntervalSlider = O5Factory.Slider(
+ _keyframeIntervalSlider = CreateSlider(
ctx: ctx, parent: videoContent,
+ labelKey: "ui.render_config.keyframe_interval", id: "slider_keyframe_interval",
defaultValue: 2, min: 0.1, max: 10, value: 2,
format: "F1", clampMode: ClampMode.All,
-
- sliderFilter: null, inputFilter: null,
- onComplete: null, onChanged: val => { config.VideoConfig.KeyframeInterval = val; MarkConfigModified(); },
- text: LocalizationManager.Get("ui.render_config.keyframe_interval"),
- id: "slider_keyframe_interval", showFill: true
+ onChanged: val => { config.VideoConfig.KeyframeInterval = val; MarkConfigModified(); }
);
// 5. Audio Encoding Config (Collapsible Card)
- var (_, audioContent) = O5Factory.Card(
+ var (_, audioContent) = CreateCard(
ctx, scrollContent,
- LocalizationManager.Get("ui.render_config.audio_encoding"),
- activeValue: true,
- onActiveChanged: null,
- onDeleteClick: null,
- showDeleteButton: false,
- showActiveToggle: false
+ "ui.render_config.audio_encoding"
);
var (audioLeft, audioRight) = CreateTwoColumnPanel(audioContent);
_renderAudioToggle = CreateToggleField(
- ctx, CreateFieldSlot(audioLeft), "",
- true, config.AudioConfig.RenderAudio,
- LocalizationManager.Get("ui.render_config.render_audio"),
- "toggle_render_audio",
- val => { config.AudioConfig.RenderAudio = val; MarkConfigModified(); },
+ ctx, CreateFieldSlot(audioLeft),
+ labelKey: "",
+ defaultValue: true,
+ value: config.AudioConfig.RenderAudio,
+ toggleTextKey: "ui.render_config.render_audio",
+ id: "toggle_render_audio",
+ onChanged: val => { config.AudioConfig.RenderAudio = val; MarkConfigModified(); },
generateLabel: false
);
- _audioBitrateSlider = O5Factory.Slider(
+ _audioBitrateSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(audioRight),
+ labelKey: "ui.render_config.audio_bitrate", id: "slider_audio_bitrate",
defaultValue: 256, min: 64, max: 384, value: 256,
format: "F0", clampMode: ClampMode.All,
-
sliderFilter: value => Snap(audioBitrateSnap, value),
- inputFilter: null, onComplete: null, onChanged: val => { config.AudioConfig.AudioBitrate = (int)Math.Round(val) * 1000; MarkConfigModified(); },
- text: LocalizationManager.Get("ui.render_config.audio_bitrate"),
- id: "slider_audio_bitrate", showFill: true
+ onChanged: val => { config.AudioConfig.AudioBitrate = (int)Math.Round(val) * 1000; MarkConfigModified(); }
);
var channels = new[] { 1, 2 };
_channelsDropdown = CreateDropdownField(
ctx, CreateFieldSlot(audioLeft),
- LocalizationManager.Get("ui.render_config.channels"),
+ "ui.render_config.channels",
2, config.AudioConfig.AudioChannels,
channels, c => c == 1 ? "1 (Mono)" : "2 (Stereo)",
"dd_channels",
@@ -455,7 +441,7 @@ internal static class RenderConfigWindow
_sampleRateDropdown = CreateDropdownField(
ctx, CreateFieldSlot(audioRight),
- LocalizationManager.Get("ui.render_config.sample_rate"),
+ "ui.render_config.sample_rate",
48000, config.AudioConfig.AudioSampleRate,
CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[config.VideoConfig.Codec]], r => $"{r/1000.0} kHz",
"dd_sample_rate",
@@ -463,19 +449,14 @@ internal static class RenderConfigWindow
);
// 6. Global Settings (Collapsible Card)
- var (_, globalContent) = O5Factory.Card(
+ var (_, globalContent) = CreateCard(
ctx, scrollContent,
- LocalizationManager.Get("ui.render_config.global_settings"),
- activeValue: true,
- onActiveChanged: null,
- onDeleteClick: null,
- showDeleteButton: false,
- showActiveToggle: false
+ "ui.render_config.global_settings"
);
_outputDirInput = CreateFullWidthInputField(
ctx, globalContent,
- LocalizationManager.Get("ui.render_config.output_dir"),
+ "ui.render_config.output_dir",
GlobalSettings.DefaultOutputDirectory,
GlobalSettings.EffectiveOutputDirectory,
"input_output_dir",
@@ -495,8 +476,10 @@ internal static class RenderConfigWindow
);
var (globalLeft, globalRight) = CreateTwoColumnPanel(globalContent);
- O5Factory.Button(
+ CreateButton(
ctx, globalLeft,
+ "ui.render_config.browse",
+ "btn_browse_output",
() =>
{
var initial = string.IsNullOrWhiteSpace(_outputDirInput.Value)
@@ -512,39 +495,32 @@ internal static class RenderConfigWindow
GlobalSettings.Save();
}
});
- },
- LocalizationManager.Get("ui.render_config.browse"),
- "btn_browse_output"
+ }
);
- O5Factory.Button(
+ CreateButton(
ctx, globalRight,
+ "ui.render_config.reset_default",
+ "btn_reset_output",
() =>
{
_outputDirInput.Set(GlobalSettings.DefaultOutputDirectory);
GlobalSettings.Current.OutputDirectory = null;
GlobalSettings.Save();
- },
- LocalizationManager.Get("ui.render_config.reset_default"),
- "btn_reset_output"
+ }
);
// 7. Advanced Settings (Collapsible Card)
- var (_, advContent) = O5Factory.Card(
+ var (_, advContent) = CreateCard(
ctx, scrollContent,
- LocalizationManager.Get("ui.render_config.advanced_settings"),
- activeValue: true,
- onActiveChanged: null,
- onDeleteClick: null,
- showDeleteButton: false,
- showActiveToggle: false
+ "ui.render_config.advanced_settings"
);
var (advLeft, advRight) = CreateTwoColumnPanel(advContent);
var extensions = new[] { "mkv", "mp4", "mov", "webm", "avi" };
_fileExtensionDropdown = CreateDropdownField(
ctx, CreateFieldSlot(advLeft),
- LocalizationManager.Get("ui.render_config.file_extension"),
+ "ui.render_config.file_extension",
"mkv", config.AdvancedConfig.FileExtension,
extensions, e => e,
"dd_adv_file_extension",
@@ -556,11 +532,13 @@ internal static class RenderConfigWindow
);
_processAudioToggle = CreateToggleField(
- ctx, CreateFieldSlot(advLeft), "",
- true, config.AdvancedConfig.ProcessAudio,
- LocalizationManager.Get("ui.render_config.process_audio"),
- "toggle_process_audio",
- val =>
+ ctx, CreateFieldSlot(advLeft),
+ labelKey: "",
+ defaultValue: true,
+ value: config.AdvancedConfig.ProcessAudio,
+ toggleTextKey: "ui.render_config.process_audio",
+ id: "toggle_process_audio",
+ onChanged: val =>
{
config.AdvancedConfig.ProcessAudio = val;
MarkConfigModified();
@@ -570,7 +548,7 @@ internal static class RenderConfigWindow
_randomSeedInput = CreateInputField(
ctx, CreateFieldSlot(advRight),
- LocalizationManager.Get("ui.render_config.random_seed"),
+ "ui.render_config.random_seed",
"", config.AdvancedConfig.RandomSeed?.ToString() ?? "",
"input_random_seed",
TMPro.TMP_InputField.CharacterValidation.Integer,
@@ -583,11 +561,13 @@ internal static class RenderConfigWindow
);
_nonTileToggle = CreateToggleField(
- ctx, CreateFieldSlot(advRight), "",
- false, config.AdvancedConfig.NonTile,
- LocalizationManager.Get("ui.render_config.non_tile"),
- "toggle_non_tile",
- val =>
+ ctx, CreateFieldSlot(advRight),
+ labelKey: "",
+ defaultValue: false,
+ value: config.AdvancedConfig.NonTile,
+ toggleTextKey: "ui.render_config.non_tile",
+ id: "toggle_non_tile",
+ onChanged: val =>
{
config.AdvancedConfig.NonTile = val;
MarkConfigModified();
@@ -596,9 +576,7 @@ internal static class RenderConfigWindow
);
// 7-1. Planet Color Settings (Optional)
- var planetColorsLabel = O5Factory.ControlText(ctx, advContent, 13f, noPad: true);
- planetColorsLabel.text = LocalizationManager.Get("ui.render_config.planet_colors");
- planetColorsLabel.color = ctx.Theme.TextDim;
+ var planetColorsLabel = CreateLabel(ctx, advContent, "ui.render_config.planet_colors");
// ReSharper disable once InconsistentNaming
var planetLabelLE = planetColorsLabel.gameObject.AddComponent();
planetLabelLE.preferredHeight = 18f;
@@ -609,11 +587,13 @@ internal static class RenderConfigWindow
var fireSlot = CreateFieldSlot(fireCol);
_firePlanetToggle = CreateToggleField(
- ctx, fireSlot, "",
- false, config.AdvancedConfig.firePlanetColor.HasValue,
- LocalizationManager.Get("ui.render_config.fire_planet"),
- "toggle_fire_planet",
- val =>
+ ctx, fireSlot,
+ labelKey: "",
+ defaultValue: false,
+ value: config.AdvancedConfig.firePlanetColor.HasValue,
+ toggleTextKey: "ui.render_config.fire_planet",
+ id: "toggle_fire_planet",
+ onChanged: val =>
{
_firePlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.firePlanetColor = val ? _firePlanetPicker?.Value : null;
@@ -643,11 +623,13 @@ internal static class RenderConfigWindow
var iceSlot = CreateFieldSlot(iceCol);
_icePlanetToggle = CreateToggleField(
- ctx, iceSlot, "",
- false, config.AdvancedConfig.icePlanetColor.HasValue,
- LocalizationManager.Get("ui.render_config.ice_planet"),
- "toggle_ice_planet",
- val =>
+ ctx, iceSlot,
+ labelKey: "",
+ defaultValue: false,
+ value: config.AdvancedConfig.icePlanetColor.HasValue,
+ toggleTextKey: "ui.render_config.ice_planet",
+ id: "toggle_ice_planet",
+ onChanged: val =>
{
_icePlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.icePlanetColor = val ? _icePlanetPicker?.Value : null;
@@ -677,11 +659,13 @@ internal static class RenderConfigWindow
var windSlot = CreateFieldSlot(windCol);
_windPlanetToggle = CreateToggleField(
- ctx, windSlot, "",
- false, config.AdvancedConfig.windPlanetColor.HasValue,
- LocalizationManager.Get("ui.render_config.wind_planet"),
- "toggle_wind_planet",
- val =>
+ ctx, windSlot,
+ labelKey: "",
+ defaultValue: false,
+ value: config.AdvancedConfig.windPlanetColor.HasValue,
+ toggleTextKey: "ui.render_config.wind_planet",
+ id: "toggle_wind_planet",
+ onChanged: val =>
{
_windPlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.windPlanetColor = val ? _windPlanetPicker?.Value : null;
@@ -710,20 +694,17 @@ internal static class RenderConfigWindow
}
// 8. Go Render Button
- O5Factory.Button(
+ CreateButton(
ctx, scrollContent,
- TryGoRender,
- LocalizationManager.Get("ui.render_config.go_render"),
+ "ui.render_config.go_render",
"btn_go_render",
+ onClick: TryGoRender,
height: 48f
);
// 9. Version Label
var versionLabel = O5Factory.ControlText(ctx, scrollContent, 12f, noPad: true);
- var versionText = $"ADOFAI Renderer v{Melon.Instance.Info.Version}";
- if (DateTime.UtcNow >= RenderMod.ExpirationTime) versionText += " " + LocalizationManager.Get("ui.render_config.expired");
-
- versionLabel.text = versionText;
+ LocalizedText.Bind(versionLabel, GetVersionText);
versionLabel.alignment = TMPro.TextAlignmentOptions.Center;
versionLabel.color = ctx.Theme.TextDim;
@@ -953,7 +934,7 @@ internal static class RenderConfigWindow
{
SyncUItoConfig(config);
- var name = _presetNameInput?.Value?.Trim();
+ var name = _presetNameInput?.Value.Trim();
if (string.IsNullOrWhiteSpace(name) || name == CustomPresetLabel)
{
CommonWarningDialogUI.ShowDialog(
@@ -1087,6 +1068,26 @@ internal static class RenderConfigWindow
_deletePresetButton?.SetBlocked(!canDelete);
}
+ private static void OnLanguageChanged(SystemLanguage language)
+ {
+ if (_configWindow == null) return;
+
+ if (_presetDropdown != null)
+ {
+ var cur = _presetDropdown.Value;
+ var isCustom = cur == "(Modified)" || cur == "(수정됨)" || cur == CustomPresetLabel;
+ ReloadPresetDropdown(isCustom ? CustomPresetLabel : cur);
+ }
+ }
+
+ private static string GetVersionText()
+ {
+ var versionText = $"ADOFAI Renderer v{Melon.Instance.Info.Version}";
+ if (DateTime.UtcNow >= RenderMod.ExpirationTime)
+ versionText += " " + LocalizationManager.Get("ui.render_config.expired");
+ return versionText;
+ }
+
private static void ShowValidationError(string message)
{
Melon.Logger.Error(message);
@@ -1189,23 +1190,139 @@ internal static class RenderConfigWindow
return slotRect;
}
+ // ReSharper disable once UnusedTupleComponentInReturnValue
+ private static (RectTransform card, RectTransform content) CreateCard(
+ O5Context ctx,
+ Transform parent,
+ string titleKey,
+ bool activeValue = true,
+ Action? onActiveChanged = null,
+ Action? onDeleteClick = null,
+ bool showDeleteButton = false,
+ bool showActiveToggle = false)
+ {
+ var (card, content) = O5Factory.Card(
+ ctx,
+ parent,
+ LocalizationManager.Get(titleKey),
+ activeValue,
+ onActiveChanged,
+ onDeleteClick,
+ showDeleteButton,
+ showActiveToggle
+ );
+
+ var titleTmp = card.Find("Header/TitleTrigger/TitleText")?.GetComponent()
+ ?? card.GetComponentsInChildren(true).FirstOrDefault(t => t.gameObject.name == "TitleText");
+ if (titleTmp)
+ {
+ titleTmp.fontSize = 18f;
+ LocalizedText.Bind(titleTmp, titleKey);
+ }
+
+ return (card, content);
+ }
+
+ private static TMPro.TextMeshProUGUI CreateLabel(
+ O5Context ctx,
+ Transform parent,
+ string textKey,
+ float fontSize = 13f,
+ bool noPad = true,
+ Color? color = null)
+ {
+ var label = O5Factory.ControlText(ctx, parent, fontSize, noPad: noPad);
+ label.text = LocalizationManager.Get(textKey);
+ label.color = color ?? ctx.Theme.TextDim;
+ LocalizedText.Bind(label, textKey);
+ return label;
+ }
+
+ private static O5Slider CreateSlider(
+ O5Context ctx,
+ Transform parent,
+ string labelKey,
+ string id,
+ double defaultValue,
+ double min,
+ double max,
+ double value,
+ string format = "F0",
+ ClampMode clampMode = ClampMode.All,
+ Func? sliderFilter = null,
+ Func? inputFilter = null,
+ Action? onChanged = null,
+ Action? onComplete = null,
+ bool showFill = true)
+ {
+ var slider = O5Factory.Slider(
+ ctx: ctx,
+ parent: parent,
+ defaultValue: defaultValue,
+ min: min,
+ max: max,
+ value: value,
+ format: format,
+ clampMode: clampMode,
+ sliderFilter: sliderFilter,
+ inputFilter: inputFilter,
+ onComplete: onComplete,
+ onChanged: onChanged,
+ text: LocalizationManager.Get(labelKey),
+ id: id,
+ showFill: showFill
+ );
+
+ if (slider.Label != null)
+ {
+ LocalizedText.Bind(slider.Label, labelKey);
+ }
+
+ return slider;
+ }
+
+ private static O5Button CreateButton(
+ O5Context ctx,
+ Transform parent,
+ string textKey,
+ string id,
+ Action onClick,
+ float? height = null)
+ {
+ var button = O5Factory.Button(
+ ctx: ctx,
+ parent: parent,
+ onClick: onClick,
+ text: LocalizationManager.Get(textKey),
+ id: id,
+ height: height
+ );
+
+ if (button.Label != null)
+ {
+ LocalizedText.Bind(button.Label, textKey);
+ }
+
+ return button;
+ }
+
private static O5InputField CreateInputField(
O5Context ctx,
Transform colParent,
- string labelText,
+ string labelKey,
string? defaultValue,
string value,
string id,
TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None,
Action? onChanged = null)
{
- return CreateInputField(ctx, colParent, labelText, defaultValue, value, id, out _, characterValidation, onChanged);
+ return CreateInputField(ctx, colParent, labelKey, defaultValue, value, id, out _, characterValidation, onChanged);
}
private static O5InputField CreateInputField(
O5Context ctx,
Transform colParent,
- string labelText,
+ string labelKey,
string? defaultValue,
string value,
string id,
@@ -1214,8 +1331,9 @@ internal static class RenderConfigWindow
Action? onChanged = null)
{
label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
- label.text = labelText;
+ label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim;
+ LocalizedText.Bind(label, labelKey);
// ReSharper disable once InconsistentNaming
var labelLE = label.gameObject.AddComponent();
@@ -1228,11 +1346,16 @@ internal static class RenderConfigWindow
defaultValue: defaultValue,
value: value,
onChanged: onChanged,
- placeholder: labelText,
+ placeholder: LocalizationManager.Get(labelKey),
icon: null,
id: id
);
+ if (inputField.Placeholder != null)
+ {
+ LocalizedText.Bind(inputField.Placeholder, labelKey);
+ }
+
inputField.InputField.characterValidation = characterValidation;
return inputField;
}
@@ -1291,7 +1414,7 @@ internal static class RenderConfigWindow
private static O5InputField CreateFullWidthInputField(
O5Context ctx,
Transform parent,
- string labelText,
+ string labelKey,
string? defaultValue,
string value,
string id,
@@ -1309,8 +1432,9 @@ internal static class RenderConfigWindow
layout.childForceExpandHeight = false;
var label = O5Factory.ControlText(ctx, containerRect, 13f, noPad: true);
- label.text = labelText;
+ label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim;
+ LocalizedText.Bind(label, labelKey);
// ReSharper disable once InconsistentNaming
var labelLE = label.gameObject.AddComponent();
@@ -1323,30 +1447,36 @@ internal static class RenderConfigWindow
defaultValue: defaultValue,
value: value,
onChanged: onChanged,
- placeholder: labelText,
+ placeholder: LocalizationManager.Get(labelKey),
icon: null,
id: id
);
+ if (input.Placeholder != null)
+ {
+ LocalizedText.Bind(input.Placeholder, labelKey);
+ }
+
return input;
}
private static O5Toggle CreateToggleField(
O5Context ctx,
Transform colParent,
- string labelText,
+ string labelKey,
bool? defaultValue,
bool value,
- string toggleText,
+ string toggleTextKey,
string id,
Action? onChanged = null,
bool generateLabel = true)
{
- if (generateLabel)
+ if (generateLabel && !string.IsNullOrEmpty(labelKey))
{
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
- label.text = labelText;
+ label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim;
+ LocalizedText.Bind(label, labelKey);
// ReSharper disable once InconsistentNaming
var labelLE = label.gameObject.AddComponent();
@@ -1354,21 +1484,28 @@ internal static class RenderConfigWindow
labelLE.minHeight = 18f;
}
- return O5Factory.Toggle(
+ var toggle = O5Factory.Toggle(
ctx: ctx,
parent: colParent,
defaultValue: defaultValue,
value: value,
onChanged: onChanged,
- text: toggleText,
+ text: !string.IsNullOrEmpty(toggleTextKey) ? LocalizationManager.Get(toggleTextKey) : "",
id: id
);
+
+ if (toggle.Label != null && !string.IsNullOrEmpty(toggleTextKey))
+ {
+ LocalizedText.Bind(toggle.Label, toggleTextKey);
+ }
+
+ return toggle;
}
private static O5Dropdown CreateDropdownField(
O5Context ctx,
Transform colParent,
- string labelText,
+ string labelKey,
T defaultValue,
T value,
IReadOnlyList values,
@@ -1377,8 +1514,9 @@ internal static class RenderConfigWindow
Action? onChanged = null)
{
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
- label.text = labelText;
+ label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim;
+ LocalizedText.Bind(label, labelKey);
// ReSharper disable once InconsistentNaming
var labelLE = label.gameObject.AddComponent();