Add Preset System

This commit is contained in:
copecone committed 2026-10-10 01:51:38 +09:00
1 parent 34d766cb4d
commit d218bb835d
7 files changed
+931 -151

No files matched your search

+419 -57
View File
@@ -21,6 +21,13 @@ internal static class RenderConfigWindow
{
private static O5Window? _configWindow;
// Presets controls
private static O5Dropdown<string>? _presetDropdown;
private static O5InputField? _presetNameInput;
private static O5Button? _savePresetButton;
private static O5Button? _deletePresetButton;
private static string? _lastLoadedPresetName;
// Controls cache for validation & sync
private static O5Slider? _renderFpsSlider;
private static O5Slider? _videoFPSSlider;
@@ -89,6 +96,10 @@ internal static class RenderConfigWindow
BuildUI(_configWindow.Content);
}
else
{
ReloadPresetDropdown(GlobalSettings.Current.LastPresetName);
}
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
var config = RenderManager.Instance.Configuration ??= new RenderConfig();
@@ -133,7 +144,117 @@ internal static class RenderConfigWindow
864, 960, 1536, 2048, 3072 // 4:3
};
// TODO: Presets
// Presets (Collapsible Card)
var (_, presetContent) = O5Factory.Card(
ctx, scrollContent,
LocalizationManager.Get("ui.render_config.presets"),
activeValue: true,
onActiveChanged: null,
onDeleteClick: null,
showDeleteButton: false,
showActiveToggle: false
);
var presets = PresetManager.GetPresets();
var presetNames = presets.Select(p => p.Name).ToList();
var initialPresetName = GlobalSettings.Current.LastPresetName;
if (string.IsNullOrEmpty(initialPresetName) || !presetNames.Contains(initialPresetName))
{
initialPresetName = presetNames.FirstOrDefault(n => n.StartsWith("QHD", StringComparison.OrdinalIgnoreCase))
?? (presetNames.Count > 0 ? presetNames[0] : "");
}
_lastLoadedPresetName = initialPresetName;
if (config.RenderFPS <= 0)
{
var initialPreset = presets.FirstOrDefault(p => string.Equals(p.Name, initialPresetName, StringComparison.OrdinalIgnoreCase)) ?? presets.FirstOrDefault();
if (initialPreset != null)
{
config.CopyFrom(initialPreset.Config);
}
}
EnsureDefaultConfig(config);
_presetDropdown = CreateDropdownField(
ctx, presetContent,
LocalizationManager.Get("ui.render_config.preset_select"),
initialPresetName, initialPresetName,
presetNames, p => p,
"dd_presets",
selectedName =>
{
OnPresetSelected(selectedName, config);
}
);
var actionsRowObj = new GameObject("PresetActionsRow");
actionsRowObj.transform.SetParent(presetContent, false);
var actionsRowRect = actionsRowObj.AddComponent<RectTransform>();
var actionsHLayout = actionsRowObj.AddComponent<HorizontalLayoutGroup>();
actionsHLayout.spacing = 8f;
actionsHLayout.childAlignment = TextAnchor.MiddleCenter;
actionsHLayout.childControlWidth = true;
actionsHLayout.childControlHeight = true;
actionsHLayout.childForceExpandWidth = false;
actionsHLayout.childForceExpandHeight = false;
// ReSharper disable once InconsistentNaming
var actionsRowLE = actionsRowObj.AddComponent<LayoutElement>();
actionsRowLE.preferredHeight = 40f;
actionsRowLE.flexibleWidth = 1f;
_presetNameInput = O5Factory.Input(
ctx: ctx,
parent: actionsRowRect,
defaultValue: "",
value: initialPresetName,
onChanged: null,
placeholder: LocalizationManager.Get("ui.render_config.preset_name_placeholder"),
icon: null,
id: "input_preset_name"
);
var inputLE = _presetNameInput.Rect.GetComponent<LayoutElement>() ?? _presetNameInput.Rect.gameObject.AddComponent<LayoutElement>();
inputLE.flexibleWidth = 1f;
inputLE.preferredHeight = 40f;
_savePresetButton = O5Factory.Button(
ctx: ctx,
parent: actionsRowRect,
onClick: () => OnSavePresetClicked(config),
text: LocalizationManager.Get("ui.render_config.preset_save"),
id: "btn_preset_save",
height: 40f
);
var saveLE = _savePresetButton.Rect.GetComponent<LayoutElement>() ?? _savePresetButton.Rect.gameObject.AddComponent<LayoutElement>();
saveLE.preferredWidth = 90f;
saveLE.flexibleWidth = 0f;
saveLE.preferredHeight = 40f;
var redTheme = ctx.Theme with
{
ObjectButton = ctx.Theme.SoftRed,
ButtonHover = ctx.Theme.SoftRed * 0.8f
};
var redCtx = new O5Context(
config: ctx.Config,
theme: redTheme,
fonts: ctx.Fonts,
tween: ctx.Tween
);
_deletePresetButton = O5Factory.Button(
ctx: redCtx,
parent: actionsRowRect,
onClick: () => OnDeletePresetClicked(config),
text: LocalizationManager.Get("ui.render_config.preset_delete"),
id: "btn_preset_delete",
height: 40f
);
var deleteLE = _deletePresetButton.Rect.GetComponent<LayoutElement>() ?? _deletePresetButton.Rect.gameObject.AddComponent<LayoutElement>();
deleteLE.preferredWidth = 90f;
deleteLE.flexibleWidth = 0f;
deleteLE.preferredHeight = 40f;
UpdateDeleteButtonState();
// 1-3. Top section: 좌/우 컨테이너 1개씩으로 3행을 쌓는다 (중앙선 1개)
var (topLeft, topRight) = CreateTwoColumnPanel(scrollContent, spacing: 8f, columnSpacing: 10f);
@@ -144,7 +265,7 @@ internal static class RenderConfigWindow
format: "F3", clampMode: ClampMode.All,
sliderFilter: value => Snap(renderFpsSnap, value),
inputFilter: null, onComplete: null, onChanged: val => { config.RenderFPS = val; },
inputFilter: null, onComplete: null, onChanged: val => { config.RenderFPS = val; MarkConfigModified(); },
text: LocalizationManager.Get("ui.render_config.render_fps"),
id: "slider_render_fps", showFill: true
);
@@ -155,7 +276,7 @@ internal static class RenderConfigWindow
format: "F3", clampMode: ClampMode.All,
sliderFilter: value => Snap(videoFpsSnap, value),
inputFilter: null, onComplete: null, onChanged: val => { config.VideoFPS = val; },
inputFilter: null, onComplete: null, onChanged: val => { config.VideoFPS = val; MarkConfigModified(); },
text: LocalizationManager.Get("ui.render_config.video_fps"),
id: "slider_video_fps", showFill: true
);
@@ -166,7 +287,7 @@ internal static class RenderConfigWindow
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); },
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
);
@@ -177,7 +298,7 @@ internal static class RenderConfigWindow
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); },
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
);
@@ -188,7 +309,7 @@ internal static class RenderConfigWindow
format: "F3", clampMode: ClampMode.All,
sliderFilter: null, inputFilter: null,
onComplete: null, onChanged: val => { config.DelayAfterEnd = val; },
onComplete: null, onChanged: val => { config.DelayAfterEnd = val; MarkConfigModified(); },
text: LocalizationManager.Get("ui.render_config.delay_after_end"),
id: "slider_delay_after_end", showFill: true
);
@@ -198,7 +319,7 @@ internal static class RenderConfigWindow
false, config.ShowJudgementText,
LocalizationManager.Get("ui.render_config.show_judgement"),
"toggle_show_judgement",
val => config.ShowJudgementText = val,
val => { config.ShowJudgementText = val; MarkConfigModified(); },
generateLabel: false
);
@@ -225,6 +346,7 @@ internal static class RenderConfigWindow
{
config.VideoConfig.Codec = c;
UpdateVideoCodecUI(c, config);
MarkConfigModified();
});
_bitrateInput = CreateInputField(
@@ -233,7 +355,7 @@ internal static class RenderConfigWindow
"30000", config.VideoConfig.Bitrate.ToString(),
"input_bitrate", out _bitrateLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var b) && b > 0) config.VideoConfig.Bitrate = b; }
val => { if (int.TryParse(val, out var b) && b > 0) config.VideoConfig.Bitrate = b; MarkConfigModified(); }
);
_qpInput = CreateInputField(
@@ -242,7 +364,7 @@ internal static class RenderConfigWindow
"18", config.VideoConfig.QP.ToString(),
"input_qp", out _qpLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var qp) && qp >= 0) config.VideoConfig.QP = qp; }
val => { if (int.TryParse(val, out var qp) && qp >= 0) config.VideoConfig.QP = qp; MarkConfigModified(); }
);
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
@@ -256,6 +378,7 @@ internal static class RenderConfigWindow
{
config.VideoConfig.RateControlMode = m;
UpdateRateControlUI(m);
MarkConfigModified();
}
);
@@ -265,7 +388,7 @@ internal static class RenderConfigWindow
"50000", config.VideoConfig.MaxBitrate.ToString(),
"input_max_bitrate", out _maxBitrateLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var mb) && mb > 0) config.VideoConfig.MaxBitrate = mb; }
val => { if (int.TryParse(val, out var mb) && mb > 0) config.VideoConfig.MaxBitrate = mb; MarkConfigModified(); }
);
_forceSoftwareToggle = CreateToggleField(
@@ -273,7 +396,7 @@ internal static class RenderConfigWindow
false, config.VideoConfig.ForceSoftwareEncoder,
LocalizationManager.Get("ui.render_config.force_software"),
"toggle_force_software",
val => config.VideoConfig.ForceSoftwareEncoder = val,
val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); },
generateLabel: true
);
@@ -283,7 +406,7 @@ internal static class RenderConfigWindow
format: "F1", clampMode: ClampMode.All,
sliderFilter: null, inputFilter: null,
onComplete: null, onChanged: val => { config.VideoConfig.KeyframeInterval = val; },
onComplete: null, onChanged: val => { config.VideoConfig.KeyframeInterval = val; MarkConfigModified(); },
text: LocalizationManager.Get("ui.render_config.keyframe_interval"),
id: "slider_keyframe_interval", showFill: true
);
@@ -305,7 +428,7 @@ internal static class RenderConfigWindow
true, config.AudioConfig.RenderAudio,
LocalizationManager.Get("ui.render_config.render_audio"),
"toggle_render_audio",
val => config.AudioConfig.RenderAudio = val,
val => { config.AudioConfig.RenderAudio = val; MarkConfigModified(); },
generateLabel: false
);
@@ -315,7 +438,7 @@ internal static class RenderConfigWindow
format: "F0", clampMode: ClampMode.All,
sliderFilter: value => Snap(audioBitrateSnap, value),
inputFilter: null, onComplete: null, onChanged: val => { config.AudioConfig.AudioBitrate = (int)Math.Round(val) * 1000; },
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
);
@@ -327,7 +450,7 @@ internal static class RenderConfigWindow
2, config.AudioConfig.AudioChannels,
channels, c => c == 1 ? "1 (Mono)" : "2 (Stereo)",
"dd_channels",
c => config.AudioConfig.AudioChannels = c
c => { config.AudioConfig.AudioChannels = c; MarkConfigModified(); }
);
_sampleRateDropdown = CreateDropdownField(
@@ -336,7 +459,7 @@ internal static class RenderConfigWindow
48000, config.AudioConfig.AudioSampleRate,
CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[config.VideoConfig.Codec]], r => $"{r/1000.0} kHz",
"dd_sample_rate",
r => config.AudioConfig.AudioSampleRate = r
r => { config.AudioConfig.AudioSampleRate = r; MarkConfigModified(); }
);
// 6. Global Settings (Collapsible Card)
@@ -425,7 +548,11 @@ internal static class RenderConfigWindow
"mkv", config.AdvancedConfig.FileExtension,
extensions, e => e,
"dd_adv_file_extension",
e => { if (!string.IsNullOrEmpty(e)) config.AdvancedConfig.FileExtension = e; }
e =>
{
if (!string.IsNullOrEmpty(e)) config.AdvancedConfig.FileExtension = e;
MarkConfigModified();
}
);
_processAudioToggle = CreateToggleField(
@@ -433,7 +560,11 @@ internal static class RenderConfigWindow
true, config.AdvancedConfig.ProcessAudio,
LocalizationManager.Get("ui.render_config.process_audio"),
"toggle_process_audio",
val => config.AdvancedConfig.ProcessAudio = val,
val =>
{
config.AdvancedConfig.ProcessAudio = val;
MarkConfigModified();
},
generateLabel: false
);
@@ -447,6 +578,7 @@ internal static class RenderConfigWindow
{
if (int.TryParse(val, out var s)) config.AdvancedConfig.RandomSeed = s;
else if (string.IsNullOrWhiteSpace(val)) config.AdvancedConfig.RandomSeed = null;
MarkConfigModified();
}
);
@@ -455,7 +587,11 @@ internal static class RenderConfigWindow
false, config.AdvancedConfig.NonTile,
LocalizationManager.Get("ui.render_config.non_tile"),
"toggle_non_tile",
val => config.AdvancedConfig.NonTile = val,
val =>
{
config.AdvancedConfig.NonTile = val;
MarkConfigModified();
},
generateLabel: false
);
@@ -482,6 +618,7 @@ internal static class RenderConfigWindow
_firePlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.firePlanetColor = val ? _firePlanetPicker?.Value : null;
if (!val) _firePlanetPicker?.SetExpanded(false);
MarkConfigModified();
},
generateLabel: false
);
@@ -493,7 +630,10 @@ internal static class RenderConfigWindow
val =>
{
if (_firePlanetToggle?.Value == true)
{
config.AdvancedConfig.firePlanetColor = val;
MarkConfigModified();
}
}
);
if (!config.AdvancedConfig.firePlanetColor.HasValue)
@@ -512,6 +652,7 @@ internal static class RenderConfigWindow
_icePlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.icePlanetColor = val ? _icePlanetPicker?.Value : null;
if (!val) _icePlanetPicker?.SetExpanded(false);
MarkConfigModified();
},
generateLabel: false
);
@@ -523,7 +664,10 @@ internal static class RenderConfigWindow
val =>
{
if (_icePlanetToggle?.Value == true)
{
config.AdvancedConfig.icePlanetColor = val;
MarkConfigModified();
}
}
);
if (!config.AdvancedConfig.icePlanetColor.HasValue)
@@ -542,6 +686,7 @@ internal static class RenderConfigWindow
_windPlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.windPlanetColor = val ? _windPlanetPicker?.Value : null;
if (!val) _windPlanetPicker?.SetExpanded(false);
MarkConfigModified();
},
generateLabel: false
);
@@ -553,7 +698,10 @@ internal static class RenderConfigWindow
val =>
{
if (_windPlanetToggle?.Value == true)
{
config.AdvancedConfig.windPlanetColor = val;
MarkConfigModified();
}
}
);
if (!config.AdvancedConfig.windPlanetColor.HasValue)
@@ -647,49 +795,19 @@ internal static class RenderConfigWindow
UpdateVideoCodecUI(config.VideoConfig.Codec, config);
UpdateRateControlUI(config.VideoConfig.RateControlMode, noAnimate: true);
if (!string.IsNullOrEmpty(GlobalSettings.Current.LastPresetName))
{
_presetDropdown?.Set(GlobalSettings.Current.LastPresetName, false);
_lastLoadedPresetName = GlobalSettings.Current.LastPresetName;
UpdateDeleteButtonState();
}
}
private static void TryGoRender()
{
var config = RenderManager.Instance.Configuration;
// Sync Video Config
if (_bitrateInput != null && int.TryParse(_bitrateInput.Value, out var bitrate) && bitrate > 0)
{
config.VideoConfig.Bitrate = bitrate;
}
if (_maxBitrateInput != null && int.TryParse(_maxBitrateInput.Value, out var maxBitrate) && maxBitrate > 0)
{
config.VideoConfig.MaxBitrate = maxBitrate;
}
if (_qpInput != null && int.TryParse(_qpInput.Value, out var qp) && qp >= 0)
{
config.VideoConfig.QP = qp;
}
// Sync Advanced Config
if (_randomSeedInput != null)
{
if (int.TryParse(_randomSeedInput.Value, out var seed))
{
config.AdvancedConfig.RandomSeed = seed;
}
else if (string.IsNullOrWhiteSpace(_randomSeedInput.Value))
{
config.AdvancedConfig.RandomSeed = null;
}
}
// Sync Planet Colors
config.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)
? _firePlanetPicker.Value
: null;
config.AdvancedConfig.icePlanetColor = (_icePlanetToggle?.Value == true && _icePlanetPicker != null)
? _icePlanetPicker.Value
: null;
config.AdvancedConfig.windPlanetColor = (_windPlanetToggle?.Value == true && _windPlanetPicker != null)
? _windPlanetPicker.Value
: null;
SyncUItoConfig(config);
// Sync Output Directory
if (_outputDirInput != null)
@@ -725,6 +843,250 @@ internal static class RenderConfigWindow
new RenderSession().Start(config, RenderManager.Instance.levelPath);
}
private static void SyncUItoConfig(RenderConfig target)
{
if (_renderFpsSlider != null) target.RenderFPS = _renderFpsSlider.Value;
if (_videoFPSSlider != null) target.VideoFPS = _videoFPSSlider.Value;
if (_renderWidthSlider != null) target.RenderWidth = (int)Math.Round(_renderWidthSlider.Value);
if (_renderHeightSlider != null) target.RenderHeight = (int)Math.Round(_renderHeightSlider.Value);
if (_delayAfterEndSlider != null) target.DelayAfterEnd = _delayAfterEndSlider.Value;
if (_showJudgementToggle != null) target.ShowJudgementText = _showJudgementToggle.Value;
if (_codecDropdown != null) target.VideoConfig.Codec = _codecDropdown.Value;
if (_rateControlDropdown != null) target.VideoConfig.RateControlMode = _rateControlDropdown.Value;
if (_bitrateInput != null && int.TryParse(_bitrateInput.Value, out var bitrate) && bitrate > 0)
{
target.VideoConfig.Bitrate = bitrate;
}
if (_maxBitrateInput != null && int.TryParse(_maxBitrateInput.Value, out var maxBitrate) && maxBitrate > 0)
{
target.VideoConfig.MaxBitrate = maxBitrate;
}
if (_qpInput != null && int.TryParse(_qpInput.Value, out var qp) && qp >= 0)
{
target.VideoConfig.QP = qp;
}
if (_keyframeIntervalSlider != null) target.VideoConfig.KeyframeInterval = _keyframeIntervalSlider.Value;
if (_forceSoftwareToggle != null) target.VideoConfig.ForceSoftwareEncoder = _forceSoftwareToggle.Value;
if (_renderAudioToggle != null) target.AudioConfig.RenderAudio = _renderAudioToggle.Value;
if (_sampleRateDropdown != null) target.AudioConfig.AudioSampleRate = _sampleRateDropdown.Value;
if (_channelsDropdown != null) target.AudioConfig.AudioChannels = _channelsDropdown.Value;
if (_audioBitrateSlider != null) target.AudioConfig.AudioBitrate = (int)Math.Round(_audioBitrateSlider.Value) * 1000;
if (_fileExtensionDropdown != null && !string.IsNullOrEmpty(_fileExtensionDropdown.Value))
target.AdvancedConfig.FileExtension = _fileExtensionDropdown.Value;
if (_randomSeedInput != null)
{
if (int.TryParse(_randomSeedInput.Value, out var seed))
{
target.AdvancedConfig.RandomSeed = seed;
}
else if (string.IsNullOrWhiteSpace(_randomSeedInput.Value))
{
target.AdvancedConfig.RandomSeed = null;
}
}
if (_processAudioToggle != null) target.AdvancedConfig.ProcessAudio = _processAudioToggle.Value;
if (_nonTileToggle != null) target.AdvancedConfig.NonTile = _nonTileToggle.Value;
// Planet Colors
target.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)
? _firePlanetPicker.Value
: null;
target.AdvancedConfig.icePlanetColor = (_icePlanetToggle?.Value == true && _icePlanetPicker != null)
? _icePlanetPicker.Value
: null;
target.AdvancedConfig.windPlanetColor = (_windPlanetToggle?.Value == true && _windPlanetPicker != null)
? _windPlanetPicker.Value
: null;
}
private static string CustomPresetLabel => LocalizationManager.Get("ui.render_config.preset_custom");
private static void MarkConfigModified()
{
if (_presetDropdown == null) return;
var customLabel = CustomPresetLabel;
if (_presetDropdown.Value == customLabel) return;
var currentValues = _presetDropdown.Values;
if (!currentValues.Contains(customLabel))
{
var list = new List<string>(currentValues);
list.Insert(0, customLabel);
_presetDropdown.SetValues(list);
}
_presetDropdown.Set(customLabel, false);
UpdateDeleteButtonState();
}
private static void OnPresetSelected(string? selectedName, RenderConfig config)
{
if (string.IsNullOrEmpty(selectedName) || selectedName == CustomPresetLabel) return;
var preset = PresetManager.GetPresets().FirstOrDefault(p => string.Equals(p.Name, selectedName, StringComparison.OrdinalIgnoreCase));
if (preset == null) return;
GlobalSettings.Current.LastPresetName = preset.Name;
GlobalSettings.Save();
_lastLoadedPresetName = preset.Name;
config.CopyFrom(preset.Config);
ApplyConfigToUI(config);
_presetNameInput?.Set(preset.Name, false);
if (_presetDropdown != null && _presetDropdown.Values.Contains(CustomPresetLabel))
{
var cleaned = _presetDropdown.Values.Where(v => v != CustomPresetLabel).ToList();
_presetDropdown.SetValues(cleaned);
_presetDropdown.Set(preset.Name, false);
}
UpdateDeleteButtonState();
}
private static void OnSavePresetClicked(RenderConfig config)
{
SyncUItoConfig(config);
var name = _presetNameInput?.Value?.Trim();
if (string.IsNullOrWhiteSpace(name) || name == CustomPresetLabel)
{
CommonWarningDialogUI.ShowDialog(
LocalizationManager.Get("ui.render_config.validation_title"),
LocalizationManager.Get("ui.render_config.preset_name_empty"),
new DialogButton(LocalizationManager.Get("ui.render_config.confirm"), () => { })
);
return;
}
bool isCurrentPreset = !string.IsNullOrEmpty(_lastLoadedPresetName) &&
string.Equals(_lastLoadedPresetName, name, StringComparison.OrdinalIgnoreCase);
var existing = PresetManager.GetPresets().FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase));
if (existing != null && !isCurrentPreset)
{
CommonWarningDialogUI.ShowDialog(
LocalizationManager.Get("ui.render_config.preset_overwrite_title"),
LocalizationManager.Get("ui.render_config.preset_overwrite_desc", name),
new DialogButton(
LocalizationManager.Get("ui.render_config.confirm"),
() => DoSavePreset(name, config)
),
new DialogButton(
LocalizationManager.Get("ui.render_config.cancel"),
() => { }
)
);
}
else
{
DoSavePreset(name, config);
}
}
private static void DoSavePreset(string name, RenderConfig config)
{
PresetManager.SavePreset(name, config);
GlobalSettings.Current.LastPresetName = name;
GlobalSettings.Save();
_lastLoadedPresetName = name;
ReloadPresetDropdown(selectName: name);
}
private static void OnDeletePresetClicked(RenderConfig config)
{
var targetPresetName = _presetDropdown?.Value;
if (string.IsNullOrWhiteSpace(targetPresetName) || targetPresetName == CustomPresetLabel)
{
targetPresetName = _lastLoadedPresetName;
if (string.IsNullOrWhiteSpace(targetPresetName) || targetPresetName == CustomPresetLabel)
{
return;
}
}
var ctx = RendererUIManager.Context;
CommonWarningDialogUI.ShowDialog(
LocalizationManager.Get("ui.render_config.preset_delete_title"),
LocalizationManager.Get("ui.render_config.preset_delete_desc", targetPresetName),
new DialogButton(
LocalizationManager.Get("ui.render_config.confirm"),
() => DoDeletePreset(targetPresetName, config),
ctx.Theme.SoftRed
),
new DialogButton(
LocalizationManager.Get("ui.render_config.cancel"),
() => { }
)
);
}
private static void DoDeletePreset(string name, RenderConfig config)
{
PresetManager.DeletePreset(name);
var presets = PresetManager.GetPresets();
var fallbackName = presets.Count > 0 ? presets[0].Name : "";
GlobalSettings.Current.LastPresetName = fallbackName;
GlobalSettings.Save();
_lastLoadedPresetName = fallbackName;
ReloadPresetDropdown(selectName: fallbackName);
var fallbackPreset = presets.FirstOrDefault(p => string.Equals(p.Name, fallbackName, StringComparison.OrdinalIgnoreCase));
if (fallbackPreset != null)
{
config.CopyFrom(fallbackPreset.Config);
ApplyConfigToUI(config);
_presetNameInput?.Set(fallbackPreset.Name, false);
}
}
private static void ReloadPresetDropdown(string? selectName = null)
{
var presets = PresetManager.GetPresets();
var names = presets.Select(p => p.Name).ToList();
if (names.Count == 0)
{
_presetDropdown?.SetValues(new List<string> { CustomPresetLabel });
_presetDropdown?.Set(CustomPresetLabel, false);
_lastLoadedPresetName = null;
UpdateDeleteButtonState();
return;
}
_presetDropdown?.SetValues(names);
var targetName = selectName;
if (string.IsNullOrEmpty(targetName) || !names.Contains(targetName))
{
targetName = names[0];
}
_presetDropdown?.Set(targetName, false);
_lastLoadedPresetName = targetName;
UpdateDeleteButtonState();
}
private static void UpdateDeleteButtonState(string? presetName = null)
{
var targetName = presetName ?? _presetDropdown?.Value;
if (targetName == CustomPresetLabel)
{
targetName = _lastLoadedPresetName;
}
bool canDelete = !string.IsNullOrEmpty(targetName) &&
targetName != CustomPresetLabel &&
PresetManager.GetPresets().Any(p => string.Equals(p.Name, targetName, StringComparison.OrdinalIgnoreCase));
_deletePresetButton?.SetBlocked(!canDelete);
}
private static void ShowValidationError(string message)
{
Melon<RenderMod>.Logger.Error(message);