Files
ADOFAIRenderer/UI/Window/RenderConfigWindow.cs
T

782 lines
30 KiB
C#

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)
{
_configWindow = RendererUIManager.Instance.WindowManager.Create(new O5WindowOptions
{
Id = "RendererMod_RenderConfig",
Title = LocalizationManager.Get("ui.render_config.window_title"),
Size = new Vector2(580f, 480f),
Draggable = true,
Resizable = true,
ShowCloseButton = true
});
_configWindow.CloseRequested += win =>
{
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 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-3. Top section: 좌/우 컨테이너 1개씩으로 3행을 쌓는다 (중앙선 1개)
var (topLeft, topRight) = CreateTwoColumnPanel(scrollContent, spacing: 8f, columnSpacing: 10f);
_renderFpsInput = CreateInputField(
ctx, CreateFieldSlot(topLeft),
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; }
);
_renderWidthInput = CreateInputField(
ctx, CreateFieldSlot(topLeft),
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; }
);
_delayAfterEndInput = CreateInputField(
ctx, CreateFieldSlot(topLeft),
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; }
);
_videoFpsInput = CreateInputField(
ctx, CreateFieldSlot(topRight),
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; }
);
_renderHeightInput = CreateInputField(
ctx, CreateFieldSlot(topRight),
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; }
);
_showJudgementToggle = CreateToggleField(
ctx, CreateFieldSlot(topRight),
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 (videoLeft, videoRight) = CreateTwoColumnPanel(videoContent);
var codecs = (Codec[])Enum.GetValues(typeof(Codec));
_codecDropdown = CreateDropdownField(
ctx, CreateFieldSlot(videoLeft),
LocalizationManager.Get("ui.render_config.codec"),
Codec.H264, config.VideoConfig.Codec,
codecs, c => c.ToString(),
"dd_video_codec",
c => config.VideoConfig.Codec = c
);
_bitrateInput = CreateInputField(
ctx, CreateFieldSlot(videoLeft),
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; }
);
_qpInput = CreateInputField(
ctx, CreateFieldSlot(videoLeft),
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; }
);
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
_rateControlDropdown = CreateDropdownField(
ctx, CreateFieldSlot(videoRight),
LocalizationManager.Get("ui.render_config.rate_control"),
RateControlMode.CBR, config.VideoConfig.RateControlMode,
rateModes, m => m.ToString(),
"dd_rate_control",
m => config.VideoConfig.RateControlMode = m
);
_maxBitrateInput = CreateInputField(
ctx, CreateFieldSlot(videoRight),
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; }
);
_keyframeIntervalInput = CreateInputField(
ctx, CreateFieldSlot(videoRight),
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 (audioLeft, audioRight) = CreateTwoColumnPanel(audioContent);
_renderAudioToggle = CreateToggleField(
ctx, CreateFieldSlot(audioLeft),
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 channels = new[] { 1, 2 };
_channelsDropdown = CreateDropdownField(
ctx, CreateFieldSlot(audioLeft),
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
);
var sampleRates = new[] { 44100, 48000 };
_sampleRateDropdown = CreateDropdownField(
ctx, CreateFieldSlot(audioRight),
LocalizationManager.Get("ui.render_config.sample_rate"),
44100, config.AudioConfig.AudioSampleRate,
sampleRates, r => $"{r} Hz",
"dd_sample_rate",
r => config.AudioConfig.AudioSampleRate = r
);
_audioBitrateInput = CreateInputField(
ctx, CreateFieldSlot(audioRight),
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 (globalLeft, globalRight) = CreateTwoColumnPanel(globalContent);
O5Factory.Button(
ctx, globalLeft,
() =>
{
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, globalRight,
() =>
{
_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 (advLeft, advRight) = CreateTwoColumnPanel(advContent);
var extensions = new[] { "mkv", "mp4", "mov", "webm", "avi" };
_fileExtensionDropdown = CreateDropdownField(
ctx, CreateFieldSlot(advLeft),
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; }
);
_processAudioToggle = CreateToggleField(
ctx, CreateFieldSlot(advLeft),
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
);
_randomSeedInput = CreateInputField(
ctx, CreateFieldSlot(advRight),
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;
}
);
_nonTileToggle = CreateToggleField(
ctx, CreateFieldSlot(advRight),
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", () => { })
);
}
// 카드(또는 상단 섹션)당 하나의 좌/우 컨테이너를 만들어
// 가로 한 줄 두 필드 사이 중앙이 행마다 어긋나지 않도록 한다.
// - 패널 1개 = 중앙선 1개 (행마다 HLG를 만들던 기존 방식의 P1-P2 어긋남 방지)
// - 좌/우 컬럼은 preferredWidth=0 + flexibleWidth=1 로 항상 50:50
// - 각 필드는 Field 슬롯(label 18 + 간격 3 + 컨트롤 50)으로 높이를 고정해
// 좌/우 스택을 써도 행 단위 Y 정렬이 유지되도록 한다.
private static (RectTransform left, RectTransform right) CreateTwoColumnPanel(
Transform parent, float spacing = 8f, float columnSpacing = 8f)
{
var panelObj = new GameObject("TwoColPanel");
panelObj.transform.SetParent(parent, false);
panelObj.AddComponent<RectTransform>();
var hLayout = panelObj.AddComponent<HorizontalLayoutGroup>();
hLayout.spacing = spacing;
hLayout.padding = new RectOffset(0, 0, 0, 0);
hLayout.childAlignment = TextAnchor.UpperCenter;
hLayout.childControlWidth = true;
hLayout.childControlHeight = true;
hLayout.childForceExpandWidth = true;
hLayout.childForceExpandHeight = false;
var panelLE = panelObj.AddComponent<LayoutElement>();
panelLE.flexibleWidth = 1f;
var left = CreateColumn(panelObj.transform, "LeftCol", columnSpacing);
var right = CreateColumn(panelObj.transform, "RightCol", columnSpacing);
return (left, right);
}
private static RectTransform CreateColumn(Transform parent, string name, float spacing)
{
var colObj = new GameObject(name);
colObj.transform.SetParent(parent, false);
var colRect = colObj.AddComponent<RectTransform>();
var colLayout = colObj.AddComponent<VerticalLayoutGroup>();
colLayout.spacing = spacing;
colLayout.padding = new RectOffset(0, 0, 0, 0);
colLayout.childControlWidth = true;
colLayout.childControlHeight = true;
colLayout.childForceExpandWidth = true;
colLayout.childForceExpandHeight = false;
// 핵심: 좌/우 선호 너비를 0으로 강제해 항상 정확히 절반씩 나누도록 한다.
// (기존 행마다 HLG 방식은 라벨/값 텍스트 길이에 따라 preferred가 달라져
// 중앙이 (W + P1 - P2) / 2 로 행마다 틀어졌음)
var colLE = colObj.AddComponent<LayoutElement>();
colLE.minWidth = 0f;
colLE.preferredWidth = 0f;
colLE.flexibleWidth = 1f;
return colRect;
}
private static RectTransform CreateFieldSlot(Transform column)
{
var slotObj = new GameObject("Field");
slotObj.transform.SetParent(column, false);
var slotRect = slotObj.AddComponent<RectTransform>();
var slotLayout = slotObj.AddComponent<VerticalLayoutGroup>();
slotLayout.spacing = 3f;
slotLayout.padding = new RectOffset(0, 0, 0, 0);
slotLayout.childControlWidth = true;
slotLayout.childControlHeight = true;
slotLayout.childForceExpandWidth = true;
slotLayout.childForceExpandHeight = false;
var slotLE = slotObj.AddComponent<LayoutElement>();
slotLE.flexibleWidth = 1f;
return slotRect;
}
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;
// 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)
{
_configWindow?.Rect.gameObject.SetActive(enabled);
}
}