SHA256
forked from ADOFAIEditorForum/ADOFAIRenderer
Add Config UI Prototype
This commit is contained in:
1 file changed
+714
-9
@@ -1,15 +1,53 @@
|
||||
using ADOFAIRenderer.Localization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using ADOFAIRenderer.API;
|
||||
using ADOFAIRenderer.Core;
|
||||
using ADOFAIRenderer.Data;
|
||||
using ADOFAIRenderer.Localization;
|
||||
using ADOFAIRenderer.Util;
|
||||
using ADOFAIRenderer.Util.FFmpeg;
|
||||
using MelonLoader;
|
||||
using O5Kit.Control;
|
||||
using O5Kit.Core;
|
||||
using O5Kit.Factory;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ADOFAIRenderer.UI.Window;
|
||||
|
||||
internal static class RenderConfigWindow
|
||||
{
|
||||
private static O5Window? _configWindow;
|
||||
|
||||
// 윈도우 만드는 메서드...
|
||||
|
||||
// Controls cache for validation & sync
|
||||
private static O5InputField? _renderFpsInput;
|
||||
private static O5InputField? _videoFpsInput;
|
||||
private static O5InputField? _renderWidthInput;
|
||||
private static O5InputField? _renderHeightInput;
|
||||
private static O5InputField? _delayAfterEndInput;
|
||||
private static O5Toggle? _showJudgementToggle;
|
||||
|
||||
private static O5Dropdown<Codec>? _codecDropdown;
|
||||
private static O5Dropdown<RateControlMode>? _rateControlDropdown;
|
||||
private static O5InputField? _bitrateInput;
|
||||
private static O5InputField? _maxBitrateInput;
|
||||
private static O5InputField? _qpInput;
|
||||
private static O5InputField? _keyframeIntervalInput;
|
||||
private static O5Toggle? _forceSoftwareToggle;
|
||||
|
||||
private static O5Toggle? _renderAudioToggle;
|
||||
private static O5Dropdown<int>? _sampleRateDropdown;
|
||||
private static O5Dropdown<int>? _channelsDropdown;
|
||||
private static O5InputField? _audioBitrateInput;
|
||||
|
||||
private static O5InputField? _outputDirInput;
|
||||
|
||||
private static O5Dropdown<string>? _fileExtensionDropdown;
|
||||
private static O5InputField? _randomSeedInput;
|
||||
private static O5Toggle? _processAudioToggle;
|
||||
private static O5Toggle? _nonTileToggle;
|
||||
|
||||
public static void CreateWindow()
|
||||
{
|
||||
if (_configWindow == null)
|
||||
@@ -18,7 +56,7 @@ internal static class RenderConfigWindow
|
||||
{
|
||||
Id = "RendererMod_RenderConfig",
|
||||
Title = LocalizationManager.Get("ui.render_config.window_title"),
|
||||
Size = new Vector2(580f, 420f),
|
||||
Size = new Vector2(580f, 480f),
|
||||
Draggable = true,
|
||||
Resizable = true,
|
||||
ShowCloseButton = true
|
||||
@@ -28,20 +66,687 @@ internal static class RenderConfigWindow
|
||||
{
|
||||
win.Rect.gameObject.SetActive(false);
|
||||
};
|
||||
|
||||
|
||||
BuildUI(_configWindow.Content);
|
||||
}
|
||||
|
||||
var config = RenderManager.Instance.Configuration ??= new RenderConfig();
|
||||
ApplyConfigToUI(config);
|
||||
|
||||
SetWindowActive(true);
|
||||
_configWindow.BringToFront();
|
||||
}
|
||||
|
||||
private static void BuildUI(RectTransform content)
|
||||
{
|
||||
var (viewport, scrollContent, scroller) =
|
||||
O5Factory.ScrollView(RendererUIManager.Context, content, spacing: 10f);
|
||||
var ctx = RendererUIManager.Context;
|
||||
var (viewport, scrollContent, scroller) =
|
||||
O5Factory.ScrollView(ctx, content, spacing: 10f, padding: 8f);
|
||||
|
||||
var config = RenderManager.Instance.Configuration ??= new RenderConfig();
|
||||
EnsureDefaultConfig(config);
|
||||
|
||||
// 1. RenderFPS & VideoFPS
|
||||
var (row1Left, row1Right) = CreateTwoColumnRow(scrollContent);
|
||||
_renderFpsInput = CreateInputField(
|
||||
ctx, row1Left,
|
||||
LocalizationManager.Get("ui.render_config.render_fps"),
|
||||
"60", config.RenderFPS.ToString("0.##"),
|
||||
"input_render_fps",
|
||||
TMPro.TMP_InputField.CharacterValidation.Decimal,
|
||||
val => { if (double.TryParse(val, out var d) && d > 0) config.RenderFPS = d; }
|
||||
);
|
||||
_videoFpsInput = CreateInputField(
|
||||
ctx, row1Right,
|
||||
LocalizationManager.Get("ui.render_config.video_fps"),
|
||||
"60", config.VideoFPS.ToString("0.##"),
|
||||
"input_video_fps",
|
||||
TMPro.TMP_InputField.CharacterValidation.Decimal,
|
||||
val => { if (double.TryParse(val, out var d) && d > 0) config.VideoFPS = d; }
|
||||
);
|
||||
|
||||
// 2. RenderWidth & RenderHeight
|
||||
var (row2Left, row2Right) = CreateTwoColumnRow(scrollContent);
|
||||
_renderWidthInput = CreateInputField(
|
||||
ctx, row2Left,
|
||||
LocalizationManager.Get("ui.render_config.render_width"),
|
||||
"1920", config.RenderWidth.ToString(),
|
||||
"input_render_width",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var i) && i > 0) config.RenderWidth = i; }
|
||||
);
|
||||
_renderHeightInput = CreateInputField(
|
||||
ctx, row2Right,
|
||||
LocalizationManager.Get("ui.render_config.render_height"),
|
||||
"1080", config.RenderHeight.ToString(),
|
||||
"input_render_height",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var i) && i > 0) config.RenderHeight = i; }
|
||||
);
|
||||
|
||||
// 3. Delay After Level End & Show Judgement
|
||||
var (row3Left, row3Right) = CreateTwoColumnRow(scrollContent);
|
||||
_delayAfterEndInput = CreateInputField(
|
||||
ctx, row3Left,
|
||||
LocalizationManager.Get("ui.render_config.delay_after_end"),
|
||||
"1.0", config.DelayAfterEnd.ToString("0.##"),
|
||||
"input_delay_after_end",
|
||||
TMPro.TMP_InputField.CharacterValidation.Decimal,
|
||||
val => { if (double.TryParse(val, out var d) && d >= 0) config.DelayAfterEnd = d; }
|
||||
);
|
||||
_showJudgementToggle = CreateToggleField(
|
||||
ctx, row3Right,
|
||||
LocalizationManager.Get("ui.render_config.show_judgement"),
|
||||
false, config.ShowJudgementText,
|
||||
LocalizationManager.Get("ui.render_config.show_judgement"),
|
||||
"toggle_show_judgement",
|
||||
val => config.ShowJudgementText = val
|
||||
);
|
||||
|
||||
// 4. Video Encoding Config (Collapsible Card)
|
||||
var (_, videoContent) = O5Factory.Card(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.video_encoding"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
);
|
||||
|
||||
var (vRow1Left, vRow1Right) = CreateTwoColumnRow(videoContent);
|
||||
var codecs = (Codec[])Enum.GetValues(typeof(Codec));
|
||||
_codecDropdown = CreateDropdownField(
|
||||
ctx, vRow1Left,
|
||||
LocalizationManager.Get("ui.render_config.codec"),
|
||||
Codec.H264, config.VideoConfig.Codec,
|
||||
codecs, c => c.ToString(),
|
||||
"dd_video_codec",
|
||||
c => config.VideoConfig.Codec = c
|
||||
);
|
||||
|
||||
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
|
||||
_rateControlDropdown = CreateDropdownField(
|
||||
ctx, vRow1Right,
|
||||
LocalizationManager.Get("ui.render_config.rate_control"),
|
||||
RateControlMode.CBR, config.VideoConfig.RateControlMode,
|
||||
rateModes, m => m.ToString(),
|
||||
"dd_rate_control",
|
||||
m => config.VideoConfig.RateControlMode = m
|
||||
);
|
||||
|
||||
var (vRow2Left, vRow2Right) = CreateTwoColumnRow(videoContent);
|
||||
_bitrateInput = CreateInputField(
|
||||
ctx, vRow2Left,
|
||||
LocalizationManager.Get("ui.render_config.bitrate"),
|
||||
"30000", config.VideoConfig.Bitrate.ToString(),
|
||||
"input_bitrate",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var b) && b > 0) config.VideoConfig.Bitrate = b; }
|
||||
);
|
||||
_maxBitrateInput = CreateInputField(
|
||||
ctx, vRow2Right,
|
||||
LocalizationManager.Get("ui.render_config.max_bitrate"),
|
||||
"50000", config.VideoConfig.MaxBitrate.ToString(),
|
||||
"input_max_bitrate",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var mb) && mb > 0) config.VideoConfig.MaxBitrate = mb; }
|
||||
);
|
||||
|
||||
var (vRow3Left, vRow3Right) = CreateTwoColumnRow(videoContent);
|
||||
_qpInput = CreateInputField(
|
||||
ctx, vRow3Left,
|
||||
LocalizationManager.Get("ui.render_config.qp"),
|
||||
"18", config.VideoConfig.QP.ToString(),
|
||||
"input_qp",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var qp) && qp >= 0) config.VideoConfig.QP = qp; }
|
||||
);
|
||||
_keyframeIntervalInput = CreateInputField(
|
||||
ctx, vRow3Right,
|
||||
LocalizationManager.Get("ui.render_config.keyframe_interval"),
|
||||
"2.0", config.VideoConfig.KeyframeInterval.ToString("0.##"),
|
||||
"input_keyframe_interval",
|
||||
TMPro.TMP_InputField.CharacterValidation.Decimal,
|
||||
val => { if (double.TryParse(val, out var kf) && kf > 0) config.VideoConfig.KeyframeInterval = kf; }
|
||||
);
|
||||
|
||||
_forceSoftwareToggle = O5Factory.Toggle(
|
||||
ctx, videoContent,
|
||||
false, config.VideoConfig.ForceSoftwareEncoder,
|
||||
val => config.VideoConfig.ForceSoftwareEncoder = val,
|
||||
LocalizationManager.Get("ui.render_config.force_software"),
|
||||
"toggle_force_software"
|
||||
);
|
||||
|
||||
// 5. Audio Encoding Config (Collapsible Card)
|
||||
var (_, audioContent) = O5Factory.Card(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.audio_encoding"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
);
|
||||
|
||||
var (aRow1Left, aRow1Right) = CreateTwoColumnRow(audioContent);
|
||||
_renderAudioToggle = CreateToggleField(
|
||||
ctx, aRow1Left,
|
||||
LocalizationManager.Get("ui.render_config.render_audio"),
|
||||
true, config.AudioConfig.RenderAudio,
|
||||
LocalizationManager.Get("ui.render_config.render_audio"),
|
||||
"toggle_render_audio",
|
||||
val => config.AudioConfig.RenderAudio = val
|
||||
);
|
||||
|
||||
var sampleRates = new[] { 44100, 48000 };
|
||||
_sampleRateDropdown = CreateDropdownField(
|
||||
ctx, aRow1Right,
|
||||
LocalizationManager.Get("ui.render_config.sample_rate"),
|
||||
44100, config.AudioConfig.AudioSampleRate,
|
||||
sampleRates, r => $"{r} Hz",
|
||||
"dd_sample_rate",
|
||||
r => config.AudioConfig.AudioSampleRate = r
|
||||
);
|
||||
|
||||
var (aRow2Left, aRow2Right) = CreateTwoColumnRow(audioContent);
|
||||
var channels = new[] { 1, 2 };
|
||||
_channelsDropdown = CreateDropdownField(
|
||||
ctx, aRow2Left,
|
||||
LocalizationManager.Get("ui.render_config.channels"),
|
||||
2, config.AudioConfig.AudioChannels,
|
||||
channels, c => c == 1 ? "1 (Mono)" : "2 (Stereo)",
|
||||
"dd_channels",
|
||||
c => config.AudioConfig.AudioChannels = c
|
||||
);
|
||||
_audioBitrateInput = CreateInputField(
|
||||
ctx, aRow2Right,
|
||||
LocalizationManager.Get("ui.render_config.audio_bitrate"),
|
||||
"256", (config.AudioConfig.AudioBitrate / 1000).ToString(),
|
||||
"input_audio_bitrate",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var ab) && ab > 0) config.AudioConfig.AudioBitrate = ab * 1000; }
|
||||
);
|
||||
|
||||
// 6. Global Settings (Collapsible Card)
|
||||
var (_, globalContent) = O5Factory.Card(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.global_settings"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
);
|
||||
|
||||
_outputDirInput = CreateFullWidthInputField(
|
||||
ctx, globalContent,
|
||||
LocalizationManager.Get("ui.render_config.output_dir"),
|
||||
GlobalSettings.DefaultOutputDirectory,
|
||||
GlobalSettings.EffectiveOutputDirectory,
|
||||
"input_output_dir",
|
||||
val =>
|
||||
{
|
||||
var trimmed = val?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(trimmed) ||
|
||||
string.Equals(trimmed, GlobalSettings.DefaultOutputDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
GlobalSettings.Current.OutputDirectory = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalSettings.Current.OutputDirectory = trimmed;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
var (gBtnLeft, gBtnRight) = CreateTwoColumnRow(globalContent);
|
||||
O5Factory.Button(
|
||||
ctx, gBtnLeft,
|
||||
() =>
|
||||
{
|
||||
var initial = string.IsNullOrWhiteSpace(_outputDirInput.Value)
|
||||
? GlobalSettings.DefaultOutputDirectory
|
||||
: _outputDirInput.Value;
|
||||
FolderDialogUtil.OpenFolderBrowser(initial, selected =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(selected))
|
||||
{
|
||||
_outputDirInput.Set(selected);
|
||||
GlobalSettings.Current.OutputDirectory = selected;
|
||||
GlobalSettings.Save();
|
||||
}
|
||||
});
|
||||
},
|
||||
LocalizationManager.Get("ui.render_config.browse"),
|
||||
"btn_browse_output"
|
||||
);
|
||||
|
||||
O5Factory.Button(
|
||||
ctx, gBtnRight,
|
||||
() =>
|
||||
{
|
||||
_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(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.advanced_settings"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
);
|
||||
|
||||
var (adRow1Left, adRow1Right) = CreateTwoColumnRow(advContent);
|
||||
var extensions = new[] { "mkv", "mp4", "mov", "webm", "avi" };
|
||||
_fileExtensionDropdown = CreateDropdownField(
|
||||
ctx, adRow1Left,
|
||||
LocalizationManager.Get("ui.render_config.file_extension"),
|
||||
"mkv", config.AdvancedConfig.FileExtension,
|
||||
extensions, e => e,
|
||||
"dd_adv_file_extension",
|
||||
e => { if (!string.IsNullOrEmpty(e)) config.AdvancedConfig.FileExtension = e; }
|
||||
);
|
||||
|
||||
_randomSeedInput = CreateInputField(
|
||||
ctx, adRow1Right,
|
||||
LocalizationManager.Get("ui.render_config.random_seed"),
|
||||
"", config.AdvancedConfig.RandomSeed?.ToString() ?? "",
|
||||
"input_random_seed",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val =>
|
||||
{
|
||||
if (int.TryParse(val, out var s)) config.AdvancedConfig.RandomSeed = s;
|
||||
else if (string.IsNullOrWhiteSpace(val)) config.AdvancedConfig.RandomSeed = null;
|
||||
}
|
||||
);
|
||||
|
||||
var (adRow2Left, adRow2Right) = CreateTwoColumnRow(advContent);
|
||||
_processAudioToggle = CreateToggleField(
|
||||
ctx, adRow2Left,
|
||||
LocalizationManager.Get("ui.render_config.process_audio"),
|
||||
true, config.AdvancedConfig.ProcessAudio,
|
||||
LocalizationManager.Get("ui.render_config.process_audio"),
|
||||
"toggle_process_audio",
|
||||
val => config.AdvancedConfig.ProcessAudio = val
|
||||
);
|
||||
_nonTileToggle = CreateToggleField(
|
||||
ctx, adRow2Right,
|
||||
LocalizationManager.Get("ui.render_config.non_tile"),
|
||||
false, config.AdvancedConfig.NonTile,
|
||||
LocalizationManager.Get("ui.render_config.non_tile"),
|
||||
"toggle_non_tile",
|
||||
val => config.AdvancedConfig.NonTile = val
|
||||
);
|
||||
|
||||
// 8. Go Render Button
|
||||
O5Factory.Button(
|
||||
ctx, scrollContent,
|
||||
TryGoRender,
|
||||
LocalizationManager.Get("ui.render_config.go_render"),
|
||||
"btn_go_render",
|
||||
height: 48f
|
||||
);
|
||||
}
|
||||
|
||||
private static void EnsureDefaultConfig(RenderConfig config)
|
||||
{
|
||||
if (config.RenderWidth <= 0) config.RenderWidth = Screen.width > 0 ? Screen.width : 1920;
|
||||
if (config.RenderHeight <= 0) config.RenderHeight = Screen.height > 0 ? Screen.height : 1080;
|
||||
if (config.RenderFPS <= 0) config.RenderFPS = 60.0;
|
||||
if (config.VideoFPS <= 0) config.VideoFPS = 60.0;
|
||||
if (config.DelayAfterEnd < 0) config.DelayAfterEnd = 1.0;
|
||||
}
|
||||
|
||||
private static void ApplyConfigToUI(RenderConfig config)
|
||||
{
|
||||
EnsureDefaultConfig(config);
|
||||
|
||||
_renderFpsInput?.Set(config.RenderFPS.ToString("0.##"), false);
|
||||
_videoFpsInput?.Set(config.VideoFPS.ToString("0.##"), false);
|
||||
_renderWidthInput?.Set(config.RenderWidth.ToString(), false);
|
||||
_renderHeightInput?.Set(config.RenderHeight.ToString(), false);
|
||||
_delayAfterEndInput?.Set(config.DelayAfterEnd.ToString("0.##"), false);
|
||||
_showJudgementToggle?.Set(config.ShowJudgementText, false);
|
||||
|
||||
_codecDropdown?.Set(config.VideoConfig.Codec, false);
|
||||
_rateControlDropdown?.Set(config.VideoConfig.RateControlMode, false);
|
||||
_bitrateInput?.Set(config.VideoConfig.Bitrate.ToString(), false);
|
||||
_maxBitrateInput?.Set(config.VideoConfig.MaxBitrate.ToString(), false);
|
||||
_qpInput?.Set(config.VideoConfig.QP.ToString(), false);
|
||||
_keyframeIntervalInput?.Set(config.VideoConfig.KeyframeInterval.ToString("0.##"), false);
|
||||
_forceSoftwareToggle?.Set(config.VideoConfig.ForceSoftwareEncoder, false);
|
||||
|
||||
_renderAudioToggle?.Set(config.AudioConfig.RenderAudio, false);
|
||||
_sampleRateDropdown?.Set(config.AudioConfig.AudioSampleRate, false);
|
||||
_channelsDropdown?.Set(config.AudioConfig.AudioChannels, false);
|
||||
_audioBitrateInput?.Set((config.AudioConfig.AudioBitrate / 1000).ToString(), false);
|
||||
|
||||
_outputDirInput?.Set(GlobalSettings.EffectiveOutputDirectory, false);
|
||||
|
||||
_fileExtensionDropdown?.Set(config.AdvancedConfig.FileExtension, false);
|
||||
_randomSeedInput?.Set(config.AdvancedConfig.RandomSeed?.ToString() ?? "", false);
|
||||
_processAudioToggle?.Set(config.AdvancedConfig.ProcessAudio, false);
|
||||
_nonTileToggle?.Set(config.AdvancedConfig.NonTile, false);
|
||||
}
|
||||
|
||||
private static void TryGoRender()
|
||||
{
|
||||
var config = RenderManager.Instance.Configuration;
|
||||
|
||||
// Validate RenderFPS
|
||||
if (!double.TryParse(_renderFpsInput?.Value, out var renderFps) || renderFps <= 0)
|
||||
{
|
||||
ShowValidationError("Render FPS must be a positive number.");
|
||||
return;
|
||||
}
|
||||
config.RenderFPS = renderFps;
|
||||
|
||||
// Validate VideoFPS
|
||||
if (!double.TryParse(_videoFpsInput?.Value, out var videoFps) || videoFps <= 0)
|
||||
{
|
||||
ShowValidationError("Video FPS must be a positive number.");
|
||||
return;
|
||||
}
|
||||
config.VideoFPS = videoFps;
|
||||
|
||||
// Validate RenderWidth
|
||||
if (!int.TryParse(_renderWidthInput?.Value, out var renderWidth) || renderWidth <= 0)
|
||||
{
|
||||
ShowValidationError("Render Width must be an integer greater than 0.");
|
||||
return;
|
||||
}
|
||||
config.RenderWidth = renderWidth;
|
||||
|
||||
// Validate RenderHeight
|
||||
if (!int.TryParse(_renderHeightInput?.Value, out var renderHeight) || renderHeight <= 0)
|
||||
{
|
||||
ShowValidationError("Render Height must be an integer greater than 0.");
|
||||
return;
|
||||
}
|
||||
config.RenderHeight = renderHeight;
|
||||
|
||||
// Validate DelayAfterEnd
|
||||
if (!double.TryParse(_delayAfterEndInput?.Value, out var delay) || delay < 0)
|
||||
{
|
||||
ShowValidationError("Delay After End must be a non-negative number.");
|
||||
return;
|
||||
}
|
||||
config.DelayAfterEnd = delay;
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (_keyframeIntervalInput != null && double.TryParse(_keyframeIntervalInput.Value, out var kf) && kf > 0)
|
||||
{
|
||||
config.VideoConfig.KeyframeInterval = kf;
|
||||
}
|
||||
|
||||
// Sync Audio Config
|
||||
if (_audioBitrateInput != null && int.TryParse(_audioBitrateInput.Value, out var ab) && ab > 0)
|
||||
{
|
||||
config.AudioConfig.AudioBitrate = ab * 1000;
|
||||
}
|
||||
|
||||
// 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 Output Directory
|
||||
if (_outputDirInput != null)
|
||||
{
|
||||
var path = _outputDirInput.Value.Trim();
|
||||
if (string.IsNullOrWhiteSpace(path) ||
|
||||
string.Equals(path, GlobalSettings.DefaultOutputDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
GlobalSettings.Current.OutputDirectory = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
if (!Directory.Exists(fullPath))
|
||||
{
|
||||
Directory.CreateDirectory(fullPath);
|
||||
}
|
||||
GlobalSettings.Current.OutputDirectory = fullPath;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowValidationError($"Output directory path is invalid:\n{ex.Message}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalSettings.Save();
|
||||
SetWindowActive(false);
|
||||
|
||||
new RenderSession().Start(config, RenderManager.Instance.levelPath);
|
||||
}
|
||||
|
||||
private static void ShowValidationError(string message)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error(message);
|
||||
CommonWarningDialogUI.ShowDialog(
|
||||
LocalizationManager.Get("ui.render_config.validation_title"),
|
||||
message,
|
||||
new DialogButton("OK", () => { })
|
||||
);
|
||||
}
|
||||
|
||||
private static (RectTransform left, RectTransform right) CreateTwoColumnRow(Transform parent, float spacing = 8f)
|
||||
{
|
||||
var rowObj = new GameObject("TwoColRow");
|
||||
rowObj.transform.SetParent(parent, false);
|
||||
|
||||
var rowRect = rowObj.AddComponent<RectTransform>();
|
||||
|
||||
var hLayout = rowObj.AddComponent<HorizontalLayoutGroup>();
|
||||
hLayout.spacing = spacing;
|
||||
hLayout.childControlWidth = true;
|
||||
hLayout.childControlHeight = true;
|
||||
hLayout.childForceExpandWidth = true;
|
||||
hLayout.childForceExpandHeight = false;
|
||||
|
||||
var col1Obj = new GameObject("Col1");
|
||||
col1Obj.transform.SetParent(rowObj.transform, false);
|
||||
var col1Rect = col1Obj.AddComponent<RectTransform>();
|
||||
var col1Layout = col1Obj.AddComponent<VerticalLayoutGroup>();
|
||||
col1Layout.spacing = 3f;
|
||||
col1Layout.childControlWidth = true;
|
||||
col1Layout.childControlHeight = true;
|
||||
col1Layout.childForceExpandWidth = true;
|
||||
col1Layout.childForceExpandHeight = false;
|
||||
|
||||
var col2Obj = new GameObject("Col2");
|
||||
col2Obj.transform.SetParent(rowObj.transform, false);
|
||||
var col2Rect = col2Obj.AddComponent<RectTransform>();
|
||||
var col2Layout = col2Obj.AddComponent<VerticalLayoutGroup>();
|
||||
col2Layout.spacing = 3f;
|
||||
col2Layout.childControlWidth = true;
|
||||
col2Layout.childControlHeight = true;
|
||||
col2Layout.childForceExpandWidth = true;
|
||||
col2Layout.childForceExpandHeight = false;
|
||||
|
||||
return (col1Rect, col2Rect);
|
||||
}
|
||||
|
||||
private static O5InputField CreateInputField(
|
||||
O5Context ctx,
|
||||
Transform colParent,
|
||||
string labelText,
|
||||
string? defaultValue,
|
||||
string value,
|
||||
string id,
|
||||
TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None,
|
||||
Action<string>? onChanged = null)
|
||||
{
|
||||
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.color = ctx.Theme.TextDim;
|
||||
|
||||
// TODO: RenderFPS double 입력칸, VideoFPS double 입력칸, RenderWidth int (>0) 입력칸, RenderHeight int (>0) 입력칸, Video Encoding Config, Audio Encoding Config, Delay After Level End double (>=0) 입력칸, Show Judgement Toggle, Global Settings, Advanced Settings, Go Render 버튼
|
||||
// NOTE: 한 가로줄에 두개씩
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
labelLE.preferredHeight = 18f;
|
||||
labelLE.minHeight = 18f;
|
||||
|
||||
var inputField = O5Factory.Input(
|
||||
ctx: ctx,
|
||||
parent: colParent,
|
||||
defaultValue: defaultValue,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
placeholder: labelText,
|
||||
icon: null,
|
||||
id: id
|
||||
);
|
||||
|
||||
inputField.InputField.characterValidation = characterValidation;
|
||||
return inputField;
|
||||
}
|
||||
|
||||
private static O5InputField CreateFullWidthInputField(
|
||||
O5Context ctx,
|
||||
Transform parent,
|
||||
string labelText,
|
||||
string? defaultValue,
|
||||
string value,
|
||||
string id,
|
||||
Action<string>? onChanged = null)
|
||||
{
|
||||
var container = new GameObject($"FullWidth_{id}");
|
||||
container.transform.SetParent(parent, false);
|
||||
|
||||
var containerRect = container.AddComponent<RectTransform>();
|
||||
var layout = container.AddComponent<VerticalLayoutGroup>();
|
||||
layout.spacing = 3f;
|
||||
layout.childControlWidth = true;
|
||||
layout.childControlHeight = true;
|
||||
layout.childForceExpandWidth = true;
|
||||
layout.childForceExpandHeight = false;
|
||||
|
||||
var label = O5Factory.ControlText(ctx, containerRect, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.color = ctx.Theme.TextDim;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
labelLE.preferredHeight = 18f;
|
||||
labelLE.minHeight = 18f;
|
||||
|
||||
var input = O5Factory.Input(
|
||||
ctx: ctx,
|
||||
parent: containerRect,
|
||||
defaultValue: defaultValue,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
placeholder: labelText,
|
||||
icon: null,
|
||||
id: id
|
||||
);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static O5Toggle CreateToggleField(
|
||||
O5Context ctx,
|
||||
Transform colParent,
|
||||
string labelText,
|
||||
bool? defaultValue,
|
||||
bool value,
|
||||
string toggleText,
|
||||
string id,
|
||||
Action<bool>? onChanged = null)
|
||||
{
|
||||
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.color = ctx.Theme.TextDim;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
labelLE.preferredHeight = 18f;
|
||||
labelLE.minHeight = 18f;
|
||||
|
||||
return O5Factory.Toggle(
|
||||
ctx: ctx,
|
||||
parent: colParent,
|
||||
defaultValue: defaultValue,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
text: toggleText,
|
||||
id: id
|
||||
);
|
||||
}
|
||||
|
||||
private static O5Dropdown<T> CreateDropdownField<T>(
|
||||
O5Context ctx,
|
||||
Transform colParent,
|
||||
string labelText,
|
||||
T defaultValue,
|
||||
T value,
|
||||
IReadOnlyList<T> values,
|
||||
Func<T, string> display,
|
||||
string id,
|
||||
Action<T>? onChanged = null)
|
||||
{
|
||||
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.color = ctx.Theme.TextDim;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
labelLE.preferredHeight = 18f;
|
||||
labelLE.minHeight = 18f;
|
||||
|
||||
var ddContainer = new GameObject($"DD_{id}");
|
||||
ddContainer.transform.SetParent(colParent, false);
|
||||
var ddRect = ddContainer.AddComponent<RectTransform>();
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var ddLE = ddContainer.AddComponent<LayoutElement>();
|
||||
ddLE.preferredHeight = 50f;
|
||||
ddLE.minHeight = 50f;
|
||||
|
||||
return O5Factory.DropDown(
|
||||
ctx: ctx,
|
||||
parent: ddContainer.transform,
|
||||
defaultValue: defaultValue,
|
||||
value: value,
|
||||
values: values,
|
||||
display: display,
|
||||
onChanged: val => { if (val != null) onChanged?.Invoke(val); },
|
||||
id: id
|
||||
);
|
||||
}
|
||||
|
||||
private static void SetWindowActive(bool enabled)
|
||||
|
||||
Reference in new issue
Block a user