Add UIComponentWrapper

This commit is contained in:
copecone committed 2026-10-22 11:46:42 +09:00
1 parent 4179ed6696
commit 7eaf4f48e6
3 files changed
+117 -144

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
<EnableSourceControlManagerQueries>false</EnableSourceControlManagerQueries>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<LangVersion>11</LangVersion>
<LangVersion>12</LangVersion>
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
</PropertyGroup>
+74 -143
View File
@@ -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
/// <summary>Preset selector: preset names, plus the Custom label at index 0 while the values differ from the preset.</summary>
private static O5Dropdown<string>? _presetDropdown;
private static O5DropdownWrapper<string>? _presetDropdown;
/// <summary>Name used by the Save button; set to the selected preset's name when a preset is loaded.</summary>
private static O5InputField? _presetNameInput;
/// <summary>Saves the current values under the name in <see cref="_presetNameInput"/>.</summary>
@@ -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<RenderConfig.RenderOutputMode>? _outputModeControl;
private static O5Dropdown<Codec>? _codecDropdown;
private static TextMeshProUGUI? _codecLabel;
private static ITweenHandle? _codecLabelTween;
private static O5DropdownWrapper<Codec>? _codecDropdown;
private static O5DropdownWrapper<RateControlMode>? _rateControlDropdown;
private static O5Dropdown<RateControlMode>? _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<int>? _sampleRateDropdown;
private static O5Dropdown<int>? _channelsDropdown;
private static O5ToggleWrapper? _renderAudioToggle;
private static O5DropdownWrapper<int>? _sampleRateDropdown;
private static O5DropdownWrapper<int>? _channelsDropdown;
private static O5Slider? _audioBitrateSlider;
/// <summary>
@@ -150,22 +134,22 @@ internal static class RenderConfigWindow
/// </summary>
private static O5InputField? _fileNameFormatInput;
private static O5Dropdown<string>? _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<string>? _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<RenderConfig.RenderOutputMode>[]
{
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>(RenderConfig.RenderOutputMode.Video, "ui.render_config.output_mode_video"),
new SegmentOption<RenderConfig.RenderOutputMode>(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;
}
/// <summary>Same as the CreateInputField overload with an <c>out</c> label parameter, without returning the label.</summary>
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<string>? onChanged = null)
{
return CreateInputField(ctx, colParent, labelKey, defaultValue, value, id, out _, characterValidation, onChanged);
}
/// <summary>
/// Creates a labeled text input in <paramref name="colParent"/> (normally a field slot): an 18-unit label row and an
/// O5Kit input below it. The label and the placeholder are both bound to <paramref name="labelKey"/>.
@@ -2009,24 +1980,22 @@ internal static class RenderConfigWindow
/// <param name="defaultValue">Value restored by middle click; <c>null</c> for none.</param>
/// <param name="value">Initial text.</param>
/// <param name="id">O5Kit control id.</param>
/// <param name="label">Receives the label text component (used to dim the label of a blocked field).</param>
/// <param name="characterValidation">TextMeshPro character filter for typed text, e.g. Integer.</param>
/// <param name="onChanged">Called with the raw text on every change; parsing and validation are up to the caller.</param>
/// <returns>The input field.</returns>
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<string>? 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<T>(
O5Dropdown<T>? dropdown,
TextMeshProUGUI? label,
ref ITweenHandle? tweenHandle,
O5DropdownWrapper<T>? 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;
}
/// <summary>Same as the CreateToggleField overload with an <c>out</c> label parameter, without returning the label.</summary>
private static O5Toggle CreateToggleField(
O5Context ctx,
Transform colParent,
string labelKey,
bool? defaultValue,
bool value,
string toggleTextKey,
string id,
Action<bool>? onChanged = null,
bool generateLabel = true)
{
return CreateToggleField(ctx, colParent, labelKey, defaultValue, value, toggleTextKey, id, out _, onChanged, generateLabel);
}
/// <summary>
/// Creates an O5Kit toggle whose text is localized with <paramref name="toggleTextKey"/>. With
/// <paramref name="generateLabel"/> an 18-unit label row is added above it (text from <paramref name="labelKey"/>,
/// empty when the key is empty), which keeps the toggle aligned with labeled fields in the neighboring column.
/// </summary>
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<bool>? 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;
}
/// <summary>Same as the CreateDropdownField overload with an <c>out</c> label parameter, without returning the label.</summary>
private static O5Dropdown<T> CreateDropdownField<T>(
O5Context ctx,
Transform colParent,
string labelKey,
T defaultValue,
T value,
IReadOnlyList<T> values,
Func<T, string> display,
string id,
Action<T>? onChanged = null)
{
return CreateDropdownField(ctx, colParent, labelKey, defaultValue, value, values, display, id, out _, onChanged);
return new O5ToggleWrapper(toggle, label);
}
/// <summary>
@@ -2299,7 +2231,7 @@ internal static class RenderConfigWindow
/// dropdown. <paramref name="display"/> converts values to their shown text. The change callback is not invoked for
/// a null value.
/// </summary>
private static O5Dropdown<T> CreateDropdownField<T>(
private static O5DropdownWrapper<T> CreateDropdownField<T>(
O5Context ctx,
Transform colParent,
string labelKey,
@@ -2308,10 +2240,9 @@ internal static class RenderConfigWindow
IReadOnlyList<T> values,
Func<T, string> display,
string id,
out TextMeshProUGUI label,
Action<T>? 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<T>(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);
}
/// <summary>
+42
View File
@@ -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<T>(O5Dropdown<T> component, TextMeshProUGUI? label) : UIWrapperBase(label)
{
public readonly O5Dropdown<T> component = component;
public T? Value => component.Value;
public IReadOnlyList<T> Values => component.Values;
public void Set(T? value, bool invoke = true) => component.Set(value, invoke);
public void SetValues(IReadOnlyList<T> values) => component.SetValues(values);
}