diff --git a/ADOFAIRenderer.csproj b/ADOFAIRenderer.csproj
index 8bbe202..ea0e20b 100644
--- a/ADOFAIRenderer.csproj
+++ b/ADOFAIRenderer.csproj
@@ -8,7 +8,7 @@
false
true
true
- 11
+ 12
GPL-3.0-or-later
diff --git a/UI/Window/RenderConfigWindow.cs b/UI/Window/RenderConfigWindow.cs
index c48a3ac..4971b11 100644
--- a/UI/Window/RenderConfigWindow.cs
+++ b/UI/Window/RenderConfigWindow.cs
@@ -8,6 +8,7 @@ using ADOFAIRenderer.Core.Data;
using ADOFAIRenderer.Data;
using ADOFAIRenderer.Localization;
using ADOFAIRenderer.Util.OS;
+using ADOFAIRenderer.Util.UI;
using MelonLoader;
using Newtonsoft.Json;
using O5Kit.Control;
@@ -62,7 +63,7 @@ internal static class RenderConfigWindow
// Presets controls
/// Preset selector: preset names, plus the Custom label at index 0 while the values differ from the preset.
- private static O5Dropdown? _presetDropdown;
+ private static O5DropdownWrapper? _presetDropdown;
/// Name used by the Save button; set to the selected preset's name when a preset is loaded.
private static O5InputField? _presetNameInput;
/// Saves the current values under the name in .
@@ -105,39 +106,22 @@ internal static class RenderConfigWindow
private static O5Slider? _renderWidthSlider;
private static O5Slider? _renderHeightSlider;
private static O5Slider? _delayAfterEndSlider;
- private static O5Toggle? _showJudgementToggle;
+ private static O5ToggleWrapper? _showJudgementToggle;
private static SegmentedControl? _outputModeControl;
- private static O5Dropdown? _codecDropdown;
- private static TextMeshProUGUI? _codecLabel;
- private static ITweenHandle? _codecLabelTween;
+ private static O5DropdownWrapper? _codecDropdown;
+ private static O5DropdownWrapper? _rateControlDropdown;
- private static O5Dropdown? _rateControlDropdown;
- private static TextMeshProUGUI? _rateControlLabel;
- private static ITweenHandle? _rateControlLabelTween;
- // Bitrate / max bitrate / QP inputs keep their label and the running label color tween, so UpdateRateControlUI
- // can dim the label together with the blocked input (see SetFieldBlocked).
- private static O5InputField? _bitrateInput;
- private static TextMeshProUGUI? _bitrateLabel;
- private static ITweenHandle? _bitrateLabelTween;
-
- private static O5InputField? _maxBitrateInput;
- private static TextMeshProUGUI? _maxBitrateLabel;
- private static ITweenHandle? _maxBitrateLabelTween;
-
- private static O5InputField? _qpInput;
- private static TextMeshProUGUI? _qpLabel;
- private static ITweenHandle? _qpLabelTween;
-
- private static O5Toggle? _forceSoftwareToggle;
- private static TextMeshProUGUI? _forceSoftwareLabel;
- private static ITweenHandle? _forceSoftwareLabelTween;
+ private static O5InputFieldWrapper? _bitrateInput;
+ private static O5InputFieldWrapper? _maxBitrateInput;
+ private static O5InputFieldWrapper? _qpInput;
+ private static O5ToggleWrapper? _forceSoftwareToggle;
private static O5Slider? _keyframeIntervalSlider;
- private static O5Toggle? _renderAudioToggle;
- private static O5Dropdown? _sampleRateDropdown;
- private static O5Dropdown? _channelsDropdown;
+ private static O5ToggleWrapper? _renderAudioToggle;
+ private static O5DropdownWrapper? _sampleRateDropdown;
+ private static O5DropdownWrapper? _channelsDropdown;
private static O5Slider? _audioBitrateSlider;
///
@@ -150,22 +134,22 @@ internal static class RenderConfigWindow
///
private static O5InputField? _fileNameFormatInput;
- private static O5Dropdown? _fileExtensionDropdown;
- private static O5InputField? _randomSeedInput;
- private static O5Toggle? _processAudioToggle;
- private static O5Toggle? _nonTileToggle;
- private static O5Toggle? _highResBloomCorrectionToggle;
- private static O5Toggle? _fixCustomFrameRateToggle;
- private static O5Toggle? _disableTileCullingToggle;
- private static O5Toggle? _hideFailedDecorationsToggle;
+ private static O5DropdownWrapper? _fileExtensionDropdown;
+ private static O5InputFieldWrapper? _randomSeedInput;
+ private static O5ToggleWrapper? _processAudioToggle;
+ private static O5ToggleWrapper? _nonTileToggle;
+ private static O5ToggleWrapper? _highResBloomCorrectionToggle;
+ private static O5ToggleWrapper? _fixCustomFrameRateToggle;
+ private static O5ToggleWrapper? _disableTileCullingToggle;
+ private static O5ToggleWrapper? _hideFailedDecorationsToggle;
// Planet color overrides: the toggle enables the override, the picker holds the color. A disabled toggle stores
// null in AdvancedConfig (no override is applied by PersistencePatch) and blocks the picker.
- private static O5Toggle? _firePlanetToggle;
+ private static O5ToggleWrapper? _firePlanetToggle;
private static O5ColorPicker? _firePlanetPicker;
- private static O5Toggle? _icePlanetToggle;
+ private static O5ToggleWrapper? _icePlanetToggle;
private static O5ColorPicker? _icePlanetPicker;
- private static O5Toggle? _windPlanetToggle;
+ private static O5ToggleWrapper? _windPlanetToggle;
private static O5ColorPicker? _windPlanetPicker;
// Colors shown in a planet picker when its override is off (and its middle-click default value)
@@ -257,30 +241,31 @@ internal static class RenderConfigWindow
// Render FPS: standard video rates, then multiples of 120 and of 288 up to the 3000 maximum.
// Video FPS: standard video rates, then multiples of 60 and of 72 up to the 600 maximum.
// Audio bitrate: kbps values. Width/height: common resolutions grouped by aspect ratio.
- double[] renderFpsSnap = { 23.976, 24, 29.97, 30, 59.94, 60, 144,
+ double[] renderFpsSnap = [ 23.976, 24, 29.97, 30, 59.94, 60, 144,
120, 240, 360, 480, 600, 720, // +120
288, 576, 864, 1152, 1440, 1728, // +288
3000 // MAX
- };
- double[] videoFpsSnap = { 23.976, 24, 29.97, 30, 59.94, 60, 72,
+ ];
+
+ double[] videoFpsSnap = [ 23.976, 24, 29.97, 30, 59.94, 60, 72,
120, 180, 240, 300, 360, 420, 480, 540, // +60
144, 216, 288, 432, 504, 576, // +72
600 // MAX
- };
+ ];
- double[] audioBitrateSnap = { 64, 96, 128, 192, 256, 320, 384 };
+ double[] audioBitrateSnap = [64, 96, 128, 192, 256, 320, 384];
- double[] renderWidthSnap = {
+ double[] renderWidthSnap = [
256, 426, 640, 854, 960, 1280, 1366, 1600, 1920, 2048, 2560, 3200, 3840, 4096, 5120, 5760, 6016, 7680, 10240, 15360, // 16:9
230, 384, 576, 768, 1152, 1440, 1680, 2880, 3072, // 16:10
192, 320, 480, 800, 1024, 1400, 2732, 6400, 11520 // 4:3
- };
+ ];
- double[] renderHeightSnap = {
+ double[] renderHeightSnap = [
144, 240, 360, 480, 540, 720, 768, 900, 1080, 1152, 1440, 1800, 2160, 2304, 2880, 3240, 3384, 4320, 5760, 8640, // 16:9
400, 600, 800, 1050, 1200, 1600, 1920, 2400, 3200, 4800, 9600, // 16:10
864, 960, 1536, 2048, 3072 // 4:3
- };
+ ];
// ---------------------------------------------------------------------------------------------
// Section 1: Presets card - preset dropdown, then a row with name input, Save and Delete buttons
@@ -474,7 +459,7 @@ internal static class RenderConfigWindow
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,
+ format: "F3", clampMode: ClampMode.Slider,
onChanged: val => { config.DelayAfterEnd = val; MarkConfigModified(); }
);
@@ -497,11 +482,11 @@ internal static class RenderConfigWindow
labelKey: "ui.render_config.output_mode",
defaultValue: RenderConfig.RenderOutputMode.Video,
value: config.OutputMode,
- options: new SegmentOption[]
- {
- new(RenderConfig.RenderOutputMode.Video, "ui.render_config.output_mode_video"),
- new(RenderConfig.RenderOutputMode.PNGSequence, "ui.render_config.output_mode_png_sequence")
- },
+ options:
+ [
+ new SegmentOption(RenderConfig.RenderOutputMode.Video, "ui.render_config.output_mode_video"),
+ new SegmentOption(RenderConfig.RenderOutputMode.PNGSequence, "ui.render_config.output_mode_png_sequence")
+ ],
id: "seg_output_mode",
onChanged: val =>
{
@@ -535,7 +520,7 @@ internal static class RenderConfigWindow
"ui.render_config.codec",
Codec.H264, config.VideoConfig.Codec,
codecs, c => c.ToString(),
- "dd_video_codec", out _codecLabel,
+ "dd_video_codec",
c =>
{
config.VideoConfig.Codec = c;
@@ -549,7 +534,7 @@ internal static class RenderConfigWindow
ctx, CreateFieldSlot(videoLeft),
"ui.render_config.bitrate",
"30000", config.VideoConfig.Bitrate.ToString(),
- "input_bitrate", out _bitrateLabel,
+ "input_bitrate",
TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var b) && b > 0) config.VideoConfig.Bitrate = b; MarkConfigModified(); }
);
@@ -558,7 +543,7 @@ internal static class RenderConfigWindow
ctx, CreateFieldSlot(videoLeft),
"ui.render_config.qp",
"18", config.VideoConfig.QP.ToString(),
- "input_qp", out _qpLabel,
+ "input_qp",
TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var qp) && qp >= 0) config.VideoConfig.QP = qp; MarkConfigModified(); }
);
@@ -569,7 +554,7 @@ internal static class RenderConfigWindow
"ui.render_config.rate_control",
RateControlMode.CBR, config.VideoConfig.RateControlMode,
rateModes, m => m.ToString(),
- "dd_rate_control", out _rateControlLabel,
+ "dd_rate_control",
m =>
{
config.VideoConfig.RateControlMode = m;
@@ -582,7 +567,7 @@ internal static class RenderConfigWindow
ctx, CreateFieldSlot(videoRight),
"ui.render_config.max_bitrate",
"50000", config.VideoConfig.MaxBitrate.ToString(),
- "input_max_bitrate", out _maxBitrateLabel,
+ "input_max_bitrate",
TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var mb) && mb > 0) config.VideoConfig.MaxBitrate = mb; MarkConfigModified(); }
);
@@ -593,7 +578,7 @@ internal static class RenderConfigWindow
defaultValue: false,
value: config.VideoConfig.ForceSoftwareEncoder,
toggleTextKey: "ui.render_config.force_software",
- id: "toggle_force_software", out _forceSoftwareLabel,
+ id: "toggle_force_software",
onChanged: val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); },
generateLabel: true
);
@@ -1108,7 +1093,7 @@ internal static class RenderConfigWindow
_codecDropdown?.Set(config.VideoConfig.Codec, false);
_rateControlDropdown?.Set(config.VideoConfig.RateControlMode, false);
- _bitrateInput?.Set(config.VideoConfig.Bitrate.ToString(), false);
+ _bitrateInput?.component.Set(config.VideoConfig.Bitrate.ToString(), false);
_maxBitrateInput?.Set(config.VideoConfig.MaxBitrate.ToString(), false);
_qpInput?.Set(config.VideoConfig.QP.ToString(), false);
_keyframeIntervalSlider?.Set(config.VideoConfig.KeyframeInterval, false, noFilter: true);
@@ -1985,20 +1970,6 @@ internal static class RenderConfigWindow
return button;
}
- /// Same as the CreateInputField overload with an out label parameter, without returning the label.
- private static O5InputField CreateInputField(
- O5Context ctx,
- Transform colParent,
- string labelKey,
- string? defaultValue,
- string value,
- string id,
- TMP_InputField.CharacterValidation characterValidation = TMP_InputField.CharacterValidation.None,
- Action? onChanged = null)
- {
- return CreateInputField(ctx, colParent, labelKey, defaultValue, value, id, out _, characterValidation, onChanged);
- }
-
///
/// Creates a labeled text input in (normally a field slot): an 18-unit label row and an
/// O5Kit input below it. The label and the placeholder are both bound to .
@@ -2009,24 +1980,22 @@ internal static class RenderConfigWindow
/// Value restored by middle click; null for none.
/// Initial text.
/// O5Kit control id.
- /// Receives the label text component (used to dim the label of a blocked field).
/// TextMeshPro character filter for typed text, e.g. Integer.
/// Called with the raw text on every change; parsing and validation are up to the caller.
/// The input field.
- private static O5InputField CreateInputField(
+ private static O5InputFieldWrapper CreateInputField(
O5Context ctx,
Transform colParent,
string labelKey,
string? defaultValue,
string value,
string id,
- out TextMeshProUGUI label,
TMP_InputField.CharacterValidation characterValidation = TMP_InputField.CharacterValidation.None,
Action? onChanged = null)
{
// Labeled text input: an 18-unit label row (13 pt, dim) above an O5Kit input. The label key also serves as the
// placeholder. characterValidation restricts typed characters (e.g. Integer). onChanged receives the raw text.
- label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
+ var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim;
LocalizedText.Bind(label, labelKey);
@@ -2053,7 +2022,7 @@ internal static class RenderConfigWindow
}
inputField.InputField.characterValidation = characterValidation;
- return inputField;
+ return new O5InputFieldWrapper(inputField, label);
}
// ===================================================================================================
@@ -2061,42 +2030,36 @@ internal static class RenderConfigWindow
// ===================================================================================================
private static void SetFieldBlocked(
- O5InputField? input,
- TextMeshProUGUI? label,
- ref ITweenHandle? tweenHandle,
+ O5InputFieldWrapper? input,
bool blocked,
bool noAnimate = false)
{
- input?.SetBlocked(blocked, noAnimate);
+ input?.component.SetBlocked(blocked, noAnimate);
- if (label == null) return;
- UpdateLabelDim(label, ref tweenHandle, blocked, noAnimate);
+ if (input?.label == null) return;
+ UpdateLabelDim(input.label, ref input.tween, blocked, noAnimate);
}
private static void SetToggleBlocked(
- O5Toggle? toggle,
- TextMeshProUGUI? label,
- ref ITweenHandle? tweenHandle,
+ O5ToggleWrapper? toggle,
bool blocked,
bool noAnimate = false)
{
- toggle?.SetBlocked(blocked, noAnimate);
+ toggle?.component.SetBlocked(blocked, noAnimate);
- if (label == null) return;
- UpdateLabelDim(label, ref tweenHandle, blocked, noAnimate);
+ if (toggle?.label == null) return;
+ UpdateLabelDim(toggle.label, ref toggle.tween, blocked, noAnimate);
}
private static void SetDropdownBlocked(
- O5Dropdown? dropdown,
- TextMeshProUGUI? label,
- ref ITweenHandle? tweenHandle,
+ O5DropdownWrapper? dropdown,
bool blocked,
bool noAnimate = false)
{
- dropdown?.SetBlocked(blocked, noAnimate);
+ dropdown?.component.SetBlocked(blocked, noAnimate);
- if (label == null) return;
- UpdateLabelDim(label, ref tweenHandle, blocked, noAnimate);
+ if (dropdown?.label == null) return;
+ UpdateLabelDim(dropdown.label, ref dropdown.tween, blocked, noAnimate);
}
private static void UpdateLabelDim(TextMeshProUGUI label, ref ITweenHandle? tweenHandle, bool blocked, bool noAnimate = false)
@@ -2149,12 +2112,12 @@ internal static class RenderConfigWindow
blockForceSoftwareToggle |= blockVideoEncodingSettings;
blockKeyframeIntervalSlider |= blockVideoEncodingSettings;
- SetDropdownBlocked(_codecDropdown, _codecLabel, ref _codecLabelTween, blockCodecDropdown, noAnimate);
- SetDropdownBlocked(_rateControlDropdown, _rateControlLabel, ref _rateControlLabelTween, blockRateControlModeDropdown, noAnimate);
- SetFieldBlocked(_bitrateInput, _bitrateLabel, ref _bitrateLabelTween, blockBitrateInput, noAnimate);
- SetFieldBlocked(_maxBitrateInput, _maxBitrateLabel, ref _maxBitrateLabelTween, blockMaxBitrateInput, noAnimate);
- SetFieldBlocked(_qpInput, _qpLabel, ref _qpLabelTween, blockQPInput, noAnimate);
- SetToggleBlocked(_forceSoftwareToggle, _forceSoftwareLabel, ref _forceSoftwareLabelTween, blockForceSoftwareToggle, noAnimate);
+ SetDropdownBlocked(_codecDropdown, blockCodecDropdown, noAnimate);
+ SetDropdownBlocked(_rateControlDropdown, blockRateControlModeDropdown, noAnimate);
+ SetFieldBlocked(_bitrateInput, blockBitrateInput, noAnimate);
+ SetFieldBlocked(_maxBitrateInput, blockMaxBitrateInput, noAnimate);
+ SetFieldBlocked(_qpInput, blockQPInput, noAnimate);
+ SetToggleBlocked(_forceSoftwareToggle, blockForceSoftwareToggle, noAnimate);
_keyframeIntervalSlider?.SetBlocked(blockKeyframeIntervalSlider, noAnimate);
}
@@ -2214,28 +2177,13 @@ internal static class RenderConfigWindow
return input;
}
-
- /// Same as the CreateToggleField overload with an out label parameter, without returning the label.
- private static O5Toggle CreateToggleField(
- O5Context ctx,
- Transform colParent,
- string labelKey,
- bool? defaultValue,
- bool value,
- string toggleTextKey,
- string id,
- Action? onChanged = null,
- bool generateLabel = true)
- {
- return CreateToggleField(ctx, colParent, labelKey, defaultValue, value, toggleTextKey, id, out _, onChanged, generateLabel);
- }
///
/// Creates an O5Kit toggle whose text is localized with . With
/// an 18-unit label row is added above it (text from ,
/// empty when the key is empty), which keeps the toggle aligned with labeled fields in the neighboring column.
///
- private static O5Toggle CreateToggleField(
+ private static O5ToggleWrapper CreateToggleField(
O5Context ctx,
Transform colParent,
string labelKey,
@@ -2243,11 +2191,10 @@ internal static class RenderConfigWindow
bool value,
string toggleTextKey,
string id,
- out TextMeshProUGUI? label,
Action? onChanged = null,
bool generateLabel = true)
{
- label = null;
+ TextMeshProUGUI? label = null;
if (generateLabel)
{
label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
@@ -2276,22 +2223,7 @@ internal static class RenderConfigWindow
LocalizedText.Bind(toggle.Label, toggleTextKey);
}
- return toggle;
- }
-
- /// Same as the CreateDropdownField overload with an out label parameter, without returning the label.
- private static O5Dropdown CreateDropdownField(
- O5Context ctx,
- Transform colParent,
- string labelKey,
- T defaultValue,
- T value,
- IReadOnlyList values,
- Func display,
- string id,
- Action? onChanged = null)
- {
- return CreateDropdownField(ctx, colParent, labelKey, defaultValue, value, values, display, id, out _, onChanged);
+ return new O5ToggleWrapper(toggle, label);
}
///
@@ -2299,7 +2231,7 @@ internal static class RenderConfigWindow
/// dropdown. converts values to their shown text. The change callback is not invoked for
/// a null value.
///
- private static O5Dropdown CreateDropdownField(
+ private static O5DropdownWrapper CreateDropdownField(
O5Context ctx,
Transform colParent,
string labelKey,
@@ -2308,10 +2240,9 @@ internal static class RenderConfigWindow
IReadOnlyList values,
Func display,
string id,
- out TextMeshProUGUI label,
Action? onChanged = null)
{
- label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
+ var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim;
LocalizedText.Bind(label, labelKey);
@@ -2329,7 +2260,7 @@ internal static class RenderConfigWindow
ddLE.preferredHeight = 50f;
ddLE.minHeight = 0f;
- return O5Factory.DropDown(
+ return new O5DropdownWrapper(O5Factory.DropDown(
ctx: ctx,
parent: ddContainer.transform,
defaultValue: defaultValue,
@@ -2338,7 +2269,7 @@ internal static class RenderConfigWindow
display: display,
onChanged: val => { if (val != null) onChanged?.Invoke(val); },
id: id
- );
+ ), label);
}
///
diff --git a/Util/UI/UIComponentWrapper.cs b/Util/UI/UIComponentWrapper.cs
new file mode 100644
index 0000000..4ca94c4
--- /dev/null
+++ b/Util/UI/UIComponentWrapper.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using O5Kit.Control;
+using O5Kit.Core;
+using TMPro;
+using UnityEngine;
+
+namespace ADOFAIRenderer.Util.UI;
+
+internal abstract record UIWrapperBase(TextMeshProUGUI? label)
+{
+ public readonly TextMeshProUGUI? label = label;
+
+ #pragma warning disable CS0649
+ public ITweenHandle? tween;
+ #pragma warning restore CS0649
+}
+
+internal record O5InputFieldWrapper(O5InputField component, TextMeshProUGUI? label) : UIWrapperBase(label)
+{
+ public readonly O5InputField component = component;
+
+ public string Value => component.Value;
+ public void Set(string value, bool invoke = true) => component.Set(value, invoke);
+}
+
+internal record O5ToggleWrapper(O5Toggle component, TextMeshProUGUI? label) : UIWrapperBase(label)
+{
+ public readonly O5Toggle component = component;
+
+ public bool Value => component.Value;
+ public void Set(bool value, bool invoke = true) => component.Set(value, invoke);
+}
+
+internal record O5DropdownWrapper(O5Dropdown component, TextMeshProUGUI? label) : UIWrapperBase(label)
+{
+ public readonly O5Dropdown component = component;
+
+ public T? Value => component.Value;
+ public IReadOnlyList Values => component.Values;
+ public void Set(T? value, bool invoke = true) => component.Set(value, invoke);
+ public void SetValues(IReadOnlyList values) => component.SetValues(values);
+}
\ No newline at end of file