SHA256
1677 lines
63 KiB
C#
1677 lines
63 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using ADOFAIRenderer.API;
|
|
using ADOFAIRenderer.Core;
|
|
using ADOFAIRenderer.Core.Data;
|
|
using ADOFAIRenderer.Data;
|
|
using ADOFAIRenderer.Localization;
|
|
using ADOFAIRenderer.Util.OS;
|
|
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;
|
|
|
|
// Presets controls
|
|
private static O5Dropdown<string>? _presetDropdown;
|
|
private static O5InputField? _presetNameInput;
|
|
private static O5Button? _savePresetButton;
|
|
private static O5Button? _deletePresetButton;
|
|
// ReSharper disable once InconsistentNaming
|
|
private static LayoutElement? _deletePresetButtonLE;
|
|
private static bool _isDeletePending;
|
|
private static Coroutine? _deleteResetCoroutine;
|
|
private static string? _lastLoadedPresetName;
|
|
|
|
// Controls cache for validation & sync
|
|
private static O5Slider? _renderFpsSlider;
|
|
private static O5Slider? _videoFPSSlider;
|
|
private static O5Slider? _renderWidthSlider;
|
|
private static O5Slider? _renderHeightSlider;
|
|
private static O5Slider? _delayAfterEndSlider;
|
|
private static O5Toggle? _showJudgementToggle;
|
|
|
|
private static O5Dropdown<Codec>? _codecDropdown;
|
|
private static O5Dropdown<RateControlMode>? _rateControlDropdown;
|
|
private static O5InputField? _bitrateInput;
|
|
private static TMPro.TextMeshProUGUI? _bitrateLabel;
|
|
private static ITweenHandle? _bitrateLabelTween;
|
|
|
|
private static O5InputField? _maxBitrateInput;
|
|
private static TMPro.TextMeshProUGUI? _maxBitrateLabel;
|
|
private static ITweenHandle? _maxBitrateLabelTween;
|
|
|
|
private static O5InputField? _qpInput;
|
|
private static TMPro.TextMeshProUGUI? _qpLabel;
|
|
private static ITweenHandle? _qpLabelTween;
|
|
|
|
private static O5Slider? _keyframeIntervalSlider;
|
|
private static O5Toggle? _forceSoftwareToggle;
|
|
|
|
private static O5Toggle? _renderAudioToggle;
|
|
private static O5Dropdown<int>? _sampleRateDropdown;
|
|
private static O5Dropdown<int>? _channelsDropdown;
|
|
private static O5Slider? _audioBitrateSlider;
|
|
|
|
private static O5InputField? _outputDirInput;
|
|
|
|
private static O5Dropdown<string>? _fileExtensionDropdown;
|
|
private static O5InputField? _randomSeedInput;
|
|
private static O5Toggle? _processAudioToggle;
|
|
private static O5Toggle? _nonTileToggle;
|
|
|
|
private static O5Toggle? _firePlanetToggle;
|
|
private static O5ColorPicker? _firePlanetPicker;
|
|
private static O5Toggle? _icePlanetToggle;
|
|
private static O5ColorPicker? _icePlanetPicker;
|
|
private static O5Toggle? _windPlanetToggle;
|
|
private static O5ColorPicker? _windPlanetPicker;
|
|
|
|
private static readonly Color DefaultFirePlanetColor = new(1f, 0f, 0f, 1f); // #FF0000
|
|
private static readonly Color DefaultIcePlanetColor = new(0f, 0f, 1f, 1f); // #0000FF
|
|
private static readonly Color DefaultWindPlanetColor = new(0.3f, 0.7f, 0f, 1f); // #4DB300
|
|
|
|
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(900, 600), Draggable = true, Resizable = true,
|
|
ShowCloseButton = true
|
|
});
|
|
if (_configWindow.TitleText != null)
|
|
{
|
|
LocalizedText.Bind(_configWindow.TitleText, "ui.render_config.window_title");
|
|
}
|
|
|
|
_configWindow.CloseRequested += win =>
|
|
{
|
|
CloseAllPickers();
|
|
ResetDeletePendingState();
|
|
win.Rect.gameObject.SetActive(false);
|
|
};
|
|
|
|
BuildUI(_configWindow.Content);
|
|
LocalizationManager.OnLanguageChanged += OnLanguageChanged;
|
|
}
|
|
else
|
|
{
|
|
ReloadPresetDropdown(GlobalSettings.Current.LastPresetName);
|
|
}
|
|
|
|
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
|
var config = RenderManager.Instance.Configuration ??= new RenderConfig();
|
|
ApplyConfigToUI(config);
|
|
|
|
SetWindowActive(true);
|
|
_configWindow.BringToFront();
|
|
}
|
|
|
|
private static void BuildUI(RectTransform content)
|
|
{
|
|
var ctx = RendererUIManager.Context;
|
|
var (_, scrollContent, _) =
|
|
O5Factory.ScrollView(ctx, content, spacing: 10f, padding: 8f);
|
|
|
|
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
|
var config = RenderManager.Instance.Configuration ??= new RenderConfig();
|
|
EnsureDefaultConfig(config);
|
|
|
|
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,
|
|
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[] 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 = {
|
|
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
|
|
};
|
|
|
|
// Presets (Collapsible Card)
|
|
var (_, presetContent) = CreateCard(
|
|
ctx, scrollContent,
|
|
"ui.render_config.presets"
|
|
);
|
|
|
|
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,
|
|
"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"
|
|
);
|
|
if (_presetNameInput.Placeholder != null)
|
|
{
|
|
LocalizedText.Bind(_presetNameInput.Placeholder, "ui.render_config.preset_name_placeholder");
|
|
}
|
|
// ReSharper disable once InconsistentNaming
|
|
var inputLE = _presetNameInput.Rect.GetComponent<LayoutElement>() ?? _presetNameInput.Rect.gameObject.AddComponent<LayoutElement>();
|
|
inputLE.flexibleWidth = 1f;
|
|
inputLE.preferredHeight = 40f;
|
|
|
|
_savePresetButton = CreateButton(
|
|
ctx: ctx,
|
|
parent: actionsRowRect,
|
|
textKey: "ui.render_config.preset_save",
|
|
id: "btn_preset_save",
|
|
onClick: () => OnSavePresetClicked(config),
|
|
height: 40f
|
|
);
|
|
// ReSharper disable once InconsistentNaming
|
|
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 = CreateButton(
|
|
ctx: redCtx,
|
|
parent: actionsRowRect,
|
|
textKey: "ui.render_config.preset_delete",
|
|
id: "btn_preset_delete",
|
|
onClick: () => OnDeletePresetClicked(config),
|
|
height: 40f
|
|
);
|
|
if (_deletePresetButton.Label != null)
|
|
{
|
|
LocalizedText.Bind(_deletePresetButton.Label, () =>
|
|
_isDeletePending
|
|
? LocalizationManager.Get("ui.render_config.preset_delete_confirm")
|
|
: LocalizationManager.Get("ui.render_config.preset_delete"));
|
|
}
|
|
_deletePresetButtonLE = _deletePresetButton.Rect.GetComponent<LayoutElement>() ?? _deletePresetButton.Rect.gameObject.AddComponent<LayoutElement>();
|
|
_deletePresetButtonLE.preferredWidth = 90f;
|
|
_deletePresetButtonLE.flexibleWidth = 0f;
|
|
_deletePresetButtonLE.preferredHeight = 40f;
|
|
|
|
UpdateDeleteButtonState();
|
|
|
|
// 1-3. Top section: 좌/우 컨테이너 1개씩으로 3행을 쌓는다 (중앙선 1개)
|
|
var (topLeft, topRight) = CreateTwoColumnPanel(scrollContent, spacing: 8f, columnSpacing: 10f);
|
|
|
|
_renderFpsSlider = CreateSlider(
|
|
ctx: ctx, parent: CreateFieldSlot(topLeft),
|
|
labelKey: "ui.render_config.render_fps", id: "slider_render_fps",
|
|
defaultValue: 60, min: 0.01, max: 3000, value: 60,
|
|
format: "F3", clampMode: ClampMode.All,
|
|
sliderFilter: value => Snap(renderFpsSnap, value),
|
|
onChanged: val => { config.RenderFPS = val; MarkConfigModified(); }
|
|
);
|
|
|
|
_videoFPSSlider = CreateSlider(
|
|
ctx: ctx, parent: CreateFieldSlot(topRight),
|
|
labelKey: "ui.render_config.video_fps", id: "slider_video_fps",
|
|
defaultValue: 60, min: 0.01, max: 600, value: 60,
|
|
format: "F3", clampMode: ClampMode.All,
|
|
sliderFilter: value => Snap(videoFpsSnap, value),
|
|
onChanged: val => { config.VideoFPS = val; MarkConfigModified(); }
|
|
);
|
|
|
|
_renderWidthSlider = CreateSlider(
|
|
ctx: ctx, parent: CreateFieldSlot(topLeft),
|
|
labelKey: "ui.render_config.render_width", id: "slider_render_width",
|
|
defaultValue: 2560, min: 0, max: 15360, value: 2560,
|
|
format: "F0", clampMode: ClampMode.All,
|
|
sliderFilter: value => Snap(renderWidthSnap, value), inputFilter: value => Math.Max(value, 2),
|
|
onChanged: val => { config.RenderWidth = (int)Math.Round(val); MarkConfigModified(); }
|
|
);
|
|
|
|
_renderHeightSlider = CreateSlider(
|
|
ctx: ctx, parent: CreateFieldSlot(topRight),
|
|
labelKey: "ui.render_config.render_height", id: "slider_render_height",
|
|
defaultValue: 1440, min: 0, max: 8640, value: 1440,
|
|
format: "F0", clampMode: ClampMode.All,
|
|
sliderFilter: value => Snap(renderHeightSnap, value), inputFilter: value => Math.Max(value, 2),
|
|
onChanged: val => { config.RenderHeight = (int)Math.Round(val); MarkConfigModified(); }
|
|
);
|
|
|
|
_delayAfterEndSlider = CreateSlider(
|
|
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,
|
|
onChanged: val => { config.DelayAfterEnd = val; MarkConfigModified(); }
|
|
);
|
|
|
|
_showJudgementToggle = CreateToggleField(
|
|
ctx, CreateFieldSlot(topRight),
|
|
labelKey: "",
|
|
defaultValue: false,
|
|
value: config.ShowJudgementText,
|
|
toggleTextKey: "ui.render_config.show_judgement",
|
|
id: "toggle_show_judgement",
|
|
onChanged: val => { config.ShowJudgementText = val; MarkConfigModified(); },
|
|
generateLabel: false
|
|
);
|
|
|
|
// 4. Video Encoding Config (Collapsible Card)
|
|
var (_, videoContent) = CreateCard(
|
|
ctx, scrollContent,
|
|
"ui.render_config.video_encoding"
|
|
);
|
|
|
|
var (videoLeft, videoRight) = CreateTwoColumnPanel(videoContent);
|
|
var codecs = (Codec[])Enum.GetValues(typeof(Codec));
|
|
_codecDropdown = CreateDropdownField(
|
|
ctx, CreateFieldSlot(videoLeft),
|
|
"ui.render_config.codec",
|
|
Codec.H264, config.VideoConfig.Codec,
|
|
codecs, c => c.ToString(),
|
|
"dd_video_codec",
|
|
c =>
|
|
{
|
|
config.VideoConfig.Codec = c;
|
|
UpdateVideoCodecUI(c, config);
|
|
MarkConfigModified();
|
|
});
|
|
|
|
_bitrateInput = CreateInputField(
|
|
ctx, CreateFieldSlot(videoLeft),
|
|
"ui.render_config.bitrate",
|
|
"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; MarkConfigModified(); }
|
|
);
|
|
|
|
_qpInput = CreateInputField(
|
|
ctx, CreateFieldSlot(videoLeft),
|
|
"ui.render_config.qp",
|
|
"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; MarkConfigModified(); }
|
|
);
|
|
|
|
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
|
|
_rateControlDropdown = CreateDropdownField(
|
|
ctx, CreateFieldSlot(videoRight),
|
|
"ui.render_config.rate_control",
|
|
RateControlMode.CBR, config.VideoConfig.RateControlMode,
|
|
rateModes, m => m.ToString(),
|
|
"dd_rate_control",
|
|
m =>
|
|
{
|
|
config.VideoConfig.RateControlMode = m;
|
|
UpdateRateControlUI(m);
|
|
MarkConfigModified();
|
|
}
|
|
);
|
|
|
|
_maxBitrateInput = CreateInputField(
|
|
ctx, CreateFieldSlot(videoRight),
|
|
"ui.render_config.max_bitrate",
|
|
"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; MarkConfigModified(); }
|
|
);
|
|
|
|
_forceSoftwareToggle = CreateToggleField(
|
|
ctx, CreateFieldSlot(videoRight),
|
|
labelKey: "",
|
|
defaultValue: false,
|
|
value: config.VideoConfig.ForceSoftwareEncoder,
|
|
toggleTextKey: "ui.render_config.force_software",
|
|
id: "toggle_force_software",
|
|
onChanged: val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); },
|
|
generateLabel: true
|
|
);
|
|
|
|
_keyframeIntervalSlider = CreateSlider(
|
|
ctx: ctx, parent: videoContent,
|
|
labelKey: "ui.render_config.keyframe_interval", id: "slider_keyframe_interval",
|
|
defaultValue: 2, min: 0.1, max: 10, value: 2,
|
|
format: "F1", clampMode: ClampMode.All,
|
|
onChanged: val => { config.VideoConfig.KeyframeInterval = val; MarkConfigModified(); }
|
|
);
|
|
|
|
// 5. Audio Encoding Config (Collapsible Card)
|
|
var (_, audioContent) = CreateCard(
|
|
ctx, scrollContent,
|
|
"ui.render_config.audio_encoding"
|
|
);
|
|
|
|
var (audioLeft, audioRight) = CreateTwoColumnPanel(audioContent);
|
|
_renderAudioToggle = CreateToggleField(
|
|
ctx, CreateFieldSlot(audioLeft),
|
|
labelKey: "",
|
|
defaultValue: true,
|
|
value: config.AudioConfig.RenderAudio,
|
|
toggleTextKey: "ui.render_config.render_audio",
|
|
id: "toggle_render_audio",
|
|
onChanged: val => { config.AudioConfig.RenderAudio = val; MarkConfigModified(); },
|
|
generateLabel: false
|
|
);
|
|
|
|
_audioBitrateSlider = CreateSlider(
|
|
ctx: ctx, parent: CreateFieldSlot(audioRight),
|
|
labelKey: "ui.render_config.audio_bitrate", id: "slider_audio_bitrate",
|
|
defaultValue: 256, min: 64, max: 384, value: 256,
|
|
format: "F0", clampMode: ClampMode.All,
|
|
sliderFilter: value => Snap(audioBitrateSnap, value),
|
|
onChanged: val => { config.AudioConfig.AudioBitrate = (int)Math.Round(val) * 1000; MarkConfigModified(); }
|
|
);
|
|
|
|
var channels = new[] { 1, 2 };
|
|
_channelsDropdown = CreateDropdownField(
|
|
ctx, CreateFieldSlot(audioLeft),
|
|
"ui.render_config.channels",
|
|
2, config.AudioConfig.AudioChannels,
|
|
channels, c => c == 1 ? "1 (Mono)" : "2 (Stereo)",
|
|
"dd_channels",
|
|
c => { config.AudioConfig.AudioChannels = c; MarkConfigModified(); }
|
|
);
|
|
|
|
_sampleRateDropdown = CreateDropdownField(
|
|
ctx, CreateFieldSlot(audioRight),
|
|
"ui.render_config.sample_rate",
|
|
48000, config.AudioConfig.AudioSampleRate,
|
|
CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[config.VideoConfig.Codec]], r => $"{r/1000.0} kHz",
|
|
"dd_sample_rate",
|
|
r => { config.AudioConfig.AudioSampleRate = r; MarkConfigModified(); }
|
|
);
|
|
|
|
// 6. Global Settings (Collapsible Card)
|
|
var (_, globalContent) = CreateCard(
|
|
ctx, scrollContent,
|
|
"ui.render_config.global_settings"
|
|
);
|
|
|
|
_outputDirInput = CreateFullWidthInputField(
|
|
ctx, globalContent,
|
|
"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);
|
|
CreateButton(
|
|
ctx, globalLeft,
|
|
"ui.render_config.browse",
|
|
"btn_browse_output",
|
|
() =>
|
|
{
|
|
var initial = string.IsNullOrWhiteSpace(_outputDirInput.Value)
|
|
? GlobalSettings.DefaultOutputDirectory
|
|
: _outputDirInput.Value;
|
|
FolderDialogUtil.OpenFolderBrowser(initial, selected =>
|
|
{
|
|
Melon<RenderMod>.Logger.Msg("Output Directory Selected: " + selected);
|
|
if (!string.IsNullOrWhiteSpace(selected))
|
|
{
|
|
_outputDirInput.Set(selected);
|
|
GlobalSettings.Current.OutputDirectory = selected;
|
|
GlobalSettings.Save();
|
|
}
|
|
});
|
|
}
|
|
);
|
|
|
|
CreateButton(
|
|
ctx, globalRight,
|
|
"ui.render_config.reset_default",
|
|
"btn_reset_output",
|
|
() =>
|
|
{
|
|
_outputDirInput.Set(GlobalSettings.DefaultOutputDirectory);
|
|
GlobalSettings.Current.OutputDirectory = null;
|
|
GlobalSettings.Save();
|
|
}
|
|
);
|
|
|
|
// 7. Advanced Settings (Collapsible Card)
|
|
var (_, advContent) = CreateCard(
|
|
ctx, scrollContent,
|
|
"ui.render_config.advanced_settings"
|
|
);
|
|
|
|
var (advLeft, advRight) = CreateTwoColumnPanel(advContent);
|
|
var extensions = new[] { "mkv", "mp4", "mov", "webm", "avi" };
|
|
_fileExtensionDropdown = CreateDropdownField(
|
|
ctx, CreateFieldSlot(advLeft),
|
|
"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;
|
|
MarkConfigModified();
|
|
}
|
|
);
|
|
|
|
_processAudioToggle = CreateToggleField(
|
|
ctx, CreateFieldSlot(advLeft),
|
|
labelKey: "",
|
|
defaultValue: true,
|
|
value: config.AdvancedConfig.ProcessAudio,
|
|
toggleTextKey: "ui.render_config.process_audio",
|
|
id: "toggle_process_audio",
|
|
onChanged: val =>
|
|
{
|
|
config.AdvancedConfig.ProcessAudio = val;
|
|
MarkConfigModified();
|
|
},
|
|
generateLabel: false
|
|
);
|
|
|
|
_randomSeedInput = CreateInputField(
|
|
ctx, CreateFieldSlot(advRight),
|
|
"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;
|
|
MarkConfigModified();
|
|
}
|
|
);
|
|
|
|
_nonTileToggle = CreateToggleField(
|
|
ctx, CreateFieldSlot(advRight),
|
|
labelKey: "",
|
|
defaultValue: false,
|
|
value: config.AdvancedConfig.NonTile,
|
|
toggleTextKey: "ui.render_config.non_tile",
|
|
id: "toggle_non_tile",
|
|
onChanged: val =>
|
|
{
|
|
config.AdvancedConfig.NonTile = val;
|
|
MarkConfigModified();
|
|
},
|
|
generateLabel: false
|
|
);
|
|
|
|
// 7-1. Planet Color Settings (Optional)
|
|
var planetColorsLabel = CreateLabel(ctx, advContent, "ui.render_config.planet_colors");
|
|
// ReSharper disable once InconsistentNaming
|
|
var planetLabelLE = planetColorsLabel.gameObject.AddComponent<LayoutElement>();
|
|
planetLabelLE.preferredHeight = 18f;
|
|
planetLabelLE.minHeight = 18f;
|
|
|
|
var planetCols = CreateMultiColumnPanel(advContent, 3);
|
|
var (fireCol, iceCol, windCol) = (planetCols[0], planetCols[1], planetCols[2]);
|
|
|
|
var fireSlot = CreateFieldSlot(fireCol);
|
|
_firePlanetToggle = CreateToggleField(
|
|
ctx, fireSlot,
|
|
labelKey: "",
|
|
defaultValue: false,
|
|
value: config.AdvancedConfig.firePlanetColor.HasValue,
|
|
toggleTextKey: "ui.render_config.fire_planet",
|
|
id: "toggle_fire_planet",
|
|
onChanged: val =>
|
|
{
|
|
_firePlanetPicker?.SetBlocked(!val);
|
|
config.AdvancedConfig.firePlanetColor = val ? _firePlanetPicker?.Value : null;
|
|
if (!val) _firePlanetPicker?.SetExpanded(false);
|
|
MarkConfigModified();
|
|
},
|
|
generateLabel: false
|
|
);
|
|
_firePlanetPicker = CreateColorPickerField(
|
|
ctx, fireSlot,
|
|
DefaultFirePlanetColor,
|
|
config.AdvancedConfig.firePlanetColor ?? DefaultFirePlanetColor,
|
|
"cp_fire_planet",
|
|
val =>
|
|
{
|
|
if (_firePlanetToggle?.Value == true)
|
|
{
|
|
config.AdvancedConfig.firePlanetColor = val;
|
|
MarkConfigModified();
|
|
}
|
|
}
|
|
);
|
|
if (!config.AdvancedConfig.firePlanetColor.HasValue)
|
|
{
|
|
_firePlanetPicker.SetBlocked(true, true);
|
|
}
|
|
|
|
var iceSlot = CreateFieldSlot(iceCol);
|
|
_icePlanetToggle = CreateToggleField(
|
|
ctx, iceSlot,
|
|
labelKey: "",
|
|
defaultValue: false,
|
|
value: config.AdvancedConfig.icePlanetColor.HasValue,
|
|
toggleTextKey: "ui.render_config.ice_planet",
|
|
id: "toggle_ice_planet",
|
|
onChanged: val =>
|
|
{
|
|
_icePlanetPicker?.SetBlocked(!val);
|
|
config.AdvancedConfig.icePlanetColor = val ? _icePlanetPicker?.Value : null;
|
|
if (!val) _icePlanetPicker?.SetExpanded(false);
|
|
MarkConfigModified();
|
|
},
|
|
generateLabel: false
|
|
);
|
|
_icePlanetPicker = CreateColorPickerField(
|
|
ctx, iceSlot,
|
|
DefaultIcePlanetColor,
|
|
config.AdvancedConfig.icePlanetColor ?? DefaultIcePlanetColor,
|
|
"cp_ice_planet",
|
|
val =>
|
|
{
|
|
if (_icePlanetToggle?.Value == true)
|
|
{
|
|
config.AdvancedConfig.icePlanetColor = val;
|
|
MarkConfigModified();
|
|
}
|
|
}
|
|
);
|
|
if (!config.AdvancedConfig.icePlanetColor.HasValue)
|
|
{
|
|
_icePlanetPicker.SetBlocked(true, true);
|
|
}
|
|
|
|
var windSlot = CreateFieldSlot(windCol);
|
|
_windPlanetToggle = CreateToggleField(
|
|
ctx, windSlot,
|
|
labelKey: "",
|
|
defaultValue: false,
|
|
value: config.AdvancedConfig.windPlanetColor.HasValue,
|
|
toggleTextKey: "ui.render_config.wind_planet",
|
|
id: "toggle_wind_planet",
|
|
onChanged: val =>
|
|
{
|
|
_windPlanetPicker?.SetBlocked(!val);
|
|
config.AdvancedConfig.windPlanetColor = val ? _windPlanetPicker?.Value : null;
|
|
if (!val) _windPlanetPicker?.SetExpanded(false);
|
|
MarkConfigModified();
|
|
},
|
|
generateLabel: false
|
|
);
|
|
_windPlanetPicker = CreateColorPickerField(
|
|
ctx, windSlot,
|
|
DefaultWindPlanetColor,
|
|
config.AdvancedConfig.windPlanetColor ?? DefaultWindPlanetColor,
|
|
"cp_wind_planet",
|
|
val =>
|
|
{
|
|
if (_windPlanetToggle?.Value == true)
|
|
{
|
|
config.AdvancedConfig.windPlanetColor = val;
|
|
MarkConfigModified();
|
|
}
|
|
}
|
|
);
|
|
if (!config.AdvancedConfig.windPlanetColor.HasValue)
|
|
{
|
|
_windPlanetPicker.SetBlocked(true, true);
|
|
}
|
|
|
|
// 8. Go Render Button
|
|
CreateButton(
|
|
ctx, scrollContent,
|
|
"ui.render_config.go_render",
|
|
"btn_go_render",
|
|
onClick: TryGoRender,
|
|
height: 48f
|
|
);
|
|
|
|
// 9. Version Label
|
|
var versionLabel = O5Factory.ControlText(ctx, scrollContent, 12f, noPad: true);
|
|
LocalizedText.Bind(versionLabel, GetVersionText);
|
|
versionLabel.alignment = TMPro.TextAlignmentOptions.Center;
|
|
versionLabel.color = ctx.Theme.TextDim;
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
var versionLE = versionLabel.gameObject.AddComponent<LayoutElement>();
|
|
versionLE.preferredHeight = 18f;
|
|
versionLE.minHeight = 18f;
|
|
|
|
return;
|
|
|
|
double Snap(double[] array, double value) =>
|
|
array.OrderBy(n => Math.Abs(n - value)).First();
|
|
}
|
|
|
|
private static void EnsureDefaultConfig(RenderConfig config)
|
|
{
|
|
if (config.RenderWidth <= 0) config.RenderWidth = 2560;
|
|
if (config.RenderHeight <= 0) config.RenderHeight = 1440;
|
|
if (config.RenderFPS <= 0) config.RenderFPS = 60.0;
|
|
if (config.VideoFPS <= 0) config.VideoFPS = 60.0;
|
|
if (config.DelayAfterEnd < 0) config.DelayAfterEnd = 15.0;
|
|
}
|
|
|
|
private static void ApplyConfigToUI(RenderConfig config)
|
|
{
|
|
EnsureDefaultConfig(config);
|
|
|
|
_renderFpsSlider?.Set((float) config.RenderFPS, false);
|
|
_videoFPSSlider?.Set((float) config.VideoFPS, false);
|
|
_renderWidthSlider?.Set(config.RenderWidth, false);
|
|
_renderHeightSlider?.Set(config.RenderHeight, false);
|
|
_delayAfterEndSlider?.Set((float) config.DelayAfterEnd, 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);
|
|
_keyframeIntervalSlider?.Set(config.VideoConfig.KeyframeInterval, 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);
|
|
_audioBitrateSlider?.Set(config.AudioConfig.AudioBitrate / 1000.0, 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);
|
|
|
|
var fireColor = config.AdvancedConfig.firePlanetColor;
|
|
_firePlanetToggle?.Set(fireColor.HasValue, false);
|
|
_firePlanetPicker?.Set(fireColor ?? DefaultFirePlanetColor, false);
|
|
_firePlanetPicker?.SetBlocked(!fireColor.HasValue, true);
|
|
|
|
var iceColor = config.AdvancedConfig.icePlanetColor;
|
|
_icePlanetToggle?.Set(iceColor.HasValue, false);
|
|
_icePlanetPicker?.Set(iceColor ?? DefaultIcePlanetColor, false);
|
|
_icePlanetPicker?.SetBlocked(!iceColor.HasValue, true);
|
|
|
|
var windColor = config.AdvancedConfig.windPlanetColor;
|
|
_windPlanetToggle?.Set(windColor.HasValue, false);
|
|
_windPlanetPicker?.Set(windColor ?? DefaultWindPlanetColor, false);
|
|
_windPlanetPicker?.SetBlocked(!windColor.HasValue, true);
|
|
|
|
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;
|
|
SyncUItoConfig(config);
|
|
|
|
// 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 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)
|
|
{
|
|
ResetDeletePendingState();
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!_isDeletePending)
|
|
{
|
|
_isDeletePending = true;
|
|
if (_deletePresetButtonLE != null) _deletePresetButtonLE.preferredWidth = 110f;
|
|
if (_deletePresetButton != null)
|
|
{
|
|
if (_deletePresetButton.Label != null)
|
|
{
|
|
_deletePresetButton.Label.text = LocalizationManager.Get("ui.render_config.preset_delete_confirm");
|
|
}
|
|
_deletePresetButton.NormalColor = new Color(0.95f, 0.25f, 0.25f, 1f);
|
|
_deletePresetButton.UpdateVisual();
|
|
}
|
|
|
|
if (_deleteResetCoroutine != null)
|
|
{
|
|
RendererUIManager.Instance.StopCoroutine(_deleteResetCoroutine);
|
|
}
|
|
_deleteResetCoroutine = RendererUIManager.Instance.StartCoroutine(ResetDeletePendingAfterDelay());
|
|
return;
|
|
}
|
|
|
|
ResetDeletePendingState();
|
|
DoDeletePreset(targetPresetName, config);
|
|
}
|
|
|
|
private static System.Collections.IEnumerator ResetDeletePendingAfterDelay()
|
|
{
|
|
yield return new WaitForSeconds(3f);
|
|
ResetDeletePendingState();
|
|
}
|
|
|
|
private static void ResetDeletePendingState()
|
|
{
|
|
if (_deleteResetCoroutine != null)
|
|
{
|
|
RendererUIManager.Instance.StopCoroutine(_deleteResetCoroutine);
|
|
_deleteResetCoroutine = null;
|
|
}
|
|
|
|
if (!_isDeletePending && (_deletePresetButton == null || _deletePresetButton.Label?.text == LocalizationManager.Get("ui.render_config.preset_delete")))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_isDeletePending = false;
|
|
if (_deletePresetButtonLE) _deletePresetButtonLE.preferredWidth = 90f;
|
|
if (_deletePresetButton != null)
|
|
{
|
|
if (_deletePresetButton.Label != null)
|
|
{
|
|
_deletePresetButton.Label.text = LocalizationManager.Get("ui.render_config.preset_delete");
|
|
}
|
|
_deletePresetButton.NormalColor = RendererUIManager.Context.Theme.SoftRed;
|
|
_deletePresetButton.UpdateVisual();
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
ResetDeletePendingState();
|
|
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 OnLanguageChanged(SystemLanguage language)
|
|
{
|
|
if (_configWindow == null) return;
|
|
|
|
if (_presetDropdown != null)
|
|
{
|
|
var cur = _presetDropdown.Value;
|
|
var isCustom = cur == "(Modified)" || cur == "(수정됨)" || cur == CustomPresetLabel;
|
|
ReloadPresetDropdown(isCustom ? CustomPresetLabel : cur);
|
|
}
|
|
}
|
|
|
|
private static string GetVersionText()
|
|
{
|
|
var versionText = $"ADOFAI Renderer v{Melon<RenderMod>.Instance.Info.Version}";
|
|
if (DateTime.UtcNow >= RenderMod.ExpirationTime)
|
|
versionText += " " + LocalizationManager.Get("ui.render_config.expired");
|
|
return versionText;
|
|
}
|
|
|
|
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개 = 균등 분할 (행마다 HLG를 만들던 기존 방식의 어긋남 방지)
|
|
// - 각 컬럼은 preferredWidth=0 + flexibleWidth=1 로 항상 1:1:...:1 균등 분할
|
|
// - 각 필드는 Field 슬롯(label 18 + 간격 3 + 컨트롤 50)으로 높이를 고정해
|
|
// 좌/우 스택을 써도 행 단위 Y 정렬이 유지되도록 한다.
|
|
private static RectTransform[] CreateMultiColumnPanel(
|
|
Transform parent, int columnCount, float spacing = 8f, float columnSpacing = 8f)
|
|
{
|
|
if (columnCount < 1) throw new ArgumentOutOfRangeException(nameof(columnCount), "Column count must be at least 1.");
|
|
|
|
var panelObj = new GameObject($"{columnCount}ColPanel");
|
|
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;
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
var panelLE = panelObj.AddComponent<LayoutElement>();
|
|
panelLE.flexibleWidth = 1f;
|
|
|
|
var columns = new RectTransform[columnCount];
|
|
for (var i = 0; i < columnCount; i++)
|
|
{
|
|
columns[i] = CreateColumn(panelObj.transform, $"Col{i + 1}", columnSpacing);
|
|
}
|
|
|
|
return columns;
|
|
}
|
|
|
|
private static (RectTransform left, RectTransform right) CreateTwoColumnPanel(
|
|
Transform parent, float spacing = 8f, float columnSpacing = 8f)
|
|
{
|
|
var cols = CreateMultiColumnPanel(parent, 2, spacing, columnSpacing);
|
|
return (cols[0], cols[1]);
|
|
}
|
|
|
|
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;
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
// 핵심: 좌/우 선호 너비를 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;
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
var slotLE = slotObj.AddComponent<LayoutElement>();
|
|
slotLE.flexibleWidth = 1f;
|
|
|
|
return slotRect;
|
|
}
|
|
|
|
// ReSharper disable once UnusedTupleComponentInReturnValue
|
|
private static (RectTransform card, RectTransform content) CreateCard(
|
|
O5Context ctx,
|
|
Transform parent,
|
|
string titleKey,
|
|
bool activeValue = true,
|
|
Action<bool>? onActiveChanged = null,
|
|
Action? onDeleteClick = null,
|
|
bool showDeleteButton = false,
|
|
bool showActiveToggle = false)
|
|
{
|
|
var (card, content) = O5Factory.Card(
|
|
ctx,
|
|
parent,
|
|
LocalizationManager.Get(titleKey),
|
|
activeValue,
|
|
onActiveChanged,
|
|
onDeleteClick,
|
|
showDeleteButton,
|
|
showActiveToggle
|
|
);
|
|
|
|
var titleTmp = card.Find("Header/TitleTrigger/TitleText")?.GetComponent<TMPro.TextMeshProUGUI>()
|
|
?? card.GetComponentsInChildren<TMPro.TextMeshProUGUI>(true).FirstOrDefault(t => t.gameObject.name == "TitleText");
|
|
if (titleTmp)
|
|
{
|
|
titleTmp.fontSize = 18f;
|
|
LocalizedText.Bind(titleTmp, titleKey);
|
|
}
|
|
|
|
return (card, content);
|
|
}
|
|
|
|
private static TMPro.TextMeshProUGUI CreateLabel(
|
|
O5Context ctx,
|
|
Transform parent,
|
|
string textKey,
|
|
float fontSize = 13f,
|
|
bool noPad = true,
|
|
Color? color = null)
|
|
{
|
|
var label = O5Factory.ControlText(ctx, parent, fontSize, noPad: noPad);
|
|
label.text = LocalizationManager.Get(textKey);
|
|
label.color = color ?? ctx.Theme.TextDim;
|
|
LocalizedText.Bind(label, textKey);
|
|
return label;
|
|
}
|
|
|
|
private static O5Slider CreateSlider(
|
|
O5Context ctx,
|
|
Transform parent,
|
|
string labelKey,
|
|
string id,
|
|
double defaultValue,
|
|
double min,
|
|
double max,
|
|
double value,
|
|
string format = "F0",
|
|
ClampMode clampMode = ClampMode.All,
|
|
Func<double, double>? sliderFilter = null,
|
|
Func<double, double>? inputFilter = null,
|
|
Action<double>? onChanged = null,
|
|
Action<double>? onComplete = null,
|
|
bool showFill = true)
|
|
{
|
|
var slider = O5Factory.Slider(
|
|
ctx: ctx,
|
|
parent: parent,
|
|
defaultValue: defaultValue,
|
|
min: min,
|
|
max: max,
|
|
value: value,
|
|
format: format,
|
|
clampMode: clampMode,
|
|
sliderFilter: sliderFilter,
|
|
inputFilter: inputFilter,
|
|
onComplete: onComplete,
|
|
onChanged: onChanged,
|
|
text: LocalizationManager.Get(labelKey),
|
|
id: id,
|
|
showFill: showFill
|
|
);
|
|
|
|
if (slider.Label != null)
|
|
{
|
|
LocalizedText.Bind(slider.Label, labelKey);
|
|
}
|
|
|
|
return slider;
|
|
}
|
|
|
|
private static O5Button CreateButton(
|
|
O5Context ctx,
|
|
Transform parent,
|
|
string textKey,
|
|
string id,
|
|
Action onClick,
|
|
float? height = null)
|
|
{
|
|
var button = O5Factory.Button(
|
|
ctx: ctx,
|
|
parent: parent,
|
|
onClick: onClick,
|
|
text: LocalizationManager.Get(textKey),
|
|
id: id,
|
|
height: height
|
|
);
|
|
|
|
if (button.Label != null)
|
|
{
|
|
LocalizedText.Bind(button.Label, textKey);
|
|
}
|
|
|
|
return button;
|
|
}
|
|
|
|
private static O5InputField CreateInputField(
|
|
O5Context ctx,
|
|
Transform colParent,
|
|
string labelKey,
|
|
string? defaultValue,
|
|
string value,
|
|
string id,
|
|
TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None,
|
|
Action<string>? onChanged = null)
|
|
{
|
|
return CreateInputField(ctx, colParent, labelKey, defaultValue, value, id, out _, characterValidation, onChanged);
|
|
}
|
|
|
|
private static O5InputField CreateInputField(
|
|
O5Context ctx,
|
|
Transform colParent,
|
|
string labelKey,
|
|
string? defaultValue,
|
|
string value,
|
|
string id,
|
|
out TMPro.TextMeshProUGUI label,
|
|
TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None,
|
|
Action<string>? onChanged = null)
|
|
{
|
|
label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
|
label.text = LocalizationManager.Get(labelKey);
|
|
label.color = ctx.Theme.TextDim;
|
|
LocalizedText.Bind(label, labelKey);
|
|
|
|
// 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: LocalizationManager.Get(labelKey),
|
|
icon: null,
|
|
id: id
|
|
);
|
|
|
|
if (inputField.Placeholder != null)
|
|
{
|
|
LocalizedText.Bind(inputField.Placeholder, labelKey);
|
|
}
|
|
|
|
inputField.InputField.characterValidation = characterValidation;
|
|
return inputField;
|
|
}
|
|
|
|
private static void SetFieldBlocked(
|
|
O5InputField? input,
|
|
TMPro.TextMeshProUGUI? label,
|
|
ref ITweenHandle? tweenHandle,
|
|
bool blocked,
|
|
bool noAnimate = false)
|
|
{
|
|
input?.SetBlocked(blocked, noAnimate);
|
|
|
|
if (label == null) return;
|
|
|
|
tweenHandle?.Kill();
|
|
var ctx = RendererUIManager.Context;
|
|
var baseColor = ctx.Theme.TextDim;
|
|
var targetColor = blocked
|
|
? new Color(baseColor.r, baseColor.g, baseColor.b, baseColor.a * 0.4f)
|
|
: baseColor;
|
|
|
|
if (noAnimate)
|
|
{
|
|
label.color = targetColor;
|
|
}
|
|
else
|
|
{
|
|
tweenHandle = ctx.Tween.TweenColor(label, targetColor, 0.2f);
|
|
}
|
|
}
|
|
|
|
private static void UpdateRateControlUI(RateControlMode mode, bool noAnimate = false)
|
|
{
|
|
bool blockBitrate = mode is RateControlMode.CQP;
|
|
bool blockMaxBitrate = mode is RateControlMode.CBR or RateControlMode.CQP;
|
|
bool blockQp = mode is RateControlMode.CBR or RateControlMode.VBR;
|
|
|
|
SetFieldBlocked(_bitrateInput, _bitrateLabel, ref _bitrateLabelTween, blockBitrate, noAnimate);
|
|
SetFieldBlocked(_maxBitrateInput, _maxBitrateLabel, ref _maxBitrateLabelTween, blockMaxBitrate, noAnimate);
|
|
SetFieldBlocked(_qpInput, _qpLabel, ref _qpLabelTween, blockQp, noAnimate);
|
|
}
|
|
|
|
private static void UpdateVideoCodecUI(Codec codec, RenderConfig? config = null)
|
|
{
|
|
if (_sampleRateDropdown == null) return;
|
|
|
|
_sampleRateDropdown.SetValues(CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[codec]]);
|
|
|
|
if (config != null && _sampleRateDropdown.Value is var currentRate)
|
|
{
|
|
config.AudioConfig.AudioSampleRate = currentRate;
|
|
}
|
|
}
|
|
|
|
private static O5InputField CreateFullWidthInputField(
|
|
O5Context ctx,
|
|
Transform parent,
|
|
string labelKey,
|
|
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 = LocalizationManager.Get(labelKey);
|
|
label.color = ctx.Theme.TextDim;
|
|
LocalizedText.Bind(label, labelKey);
|
|
|
|
// 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: LocalizationManager.Get(labelKey),
|
|
icon: null,
|
|
id: id
|
|
);
|
|
|
|
if (input.Placeholder != null)
|
|
{
|
|
LocalizedText.Bind(input.Placeholder, labelKey);
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (generateLabel)
|
|
{
|
|
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
|
label.text = string.IsNullOrEmpty(labelKey) ? "" : LocalizationManager.Get(labelKey);
|
|
label.color = ctx.Theme.TextDim;
|
|
LocalizedText.Bind(label, labelKey);
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
|
labelLE.preferredHeight = 18f;
|
|
labelLE.minHeight = 18f;
|
|
}
|
|
|
|
var toggle = O5Factory.Toggle(
|
|
ctx: ctx,
|
|
parent: colParent,
|
|
defaultValue: defaultValue,
|
|
value: value,
|
|
onChanged: onChanged,
|
|
text: !string.IsNullOrEmpty(toggleTextKey) ? LocalizationManager.Get(toggleTextKey) : "",
|
|
id: id
|
|
);
|
|
|
|
if (toggle.Label != null && !string.IsNullOrEmpty(toggleTextKey))
|
|
{
|
|
LocalizedText.Bind(toggle.Label, toggleTextKey);
|
|
}
|
|
|
|
return toggle;
|
|
}
|
|
|
|
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)
|
|
{
|
|
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
|
label.text = LocalizationManager.Get(labelKey);
|
|
label.color = ctx.Theme.TextDim;
|
|
LocalizedText.Bind(label, labelKey);
|
|
|
|
// 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);
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
var ddLE = ddContainer.AddComponent<LayoutElement>();
|
|
ddLE.preferredHeight = 50f;
|
|
ddLE.minHeight = 0f;
|
|
|
|
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 O5ColorPicker CreateColorPickerField(
|
|
O5Context ctx,
|
|
Transform colParent,
|
|
Color? defaultValue,
|
|
Color value,
|
|
string id,
|
|
Action<Color>? onChanged = null)
|
|
{
|
|
var cpContainer = new GameObject($"CP_{id}");
|
|
cpContainer.transform.SetParent(colParent, false);
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
var cpLE = cpContainer.AddComponent<LayoutElement>();
|
|
var height = ctx.Theme.ControlHeightFor(ctx.Theme.FontSizeBody);
|
|
cpLE.preferredHeight = height;
|
|
cpLE.minHeight = height;
|
|
|
|
var canvasRect = RendererUIManager.Instance.WindowManager.Canvas.Root.GetComponent<RectTransform>();
|
|
|
|
var picker = O5Factory.ColorPicker(
|
|
ctx: ctx,
|
|
parent: cpContainer.transform,
|
|
canvasRect: canvasRect,
|
|
canvasCamera: null,
|
|
defaultValue: defaultValue,
|
|
value: value,
|
|
onChanged: onChanged,
|
|
onComplete: onChanged,
|
|
id: id
|
|
);
|
|
|
|
// 3열 등 다열 레이아웃 환경에 맞춰 O5ColorPicker 헤더 내부 비율 및 폰트 크기 조정
|
|
// O5Kit 기본값(Preview: 0~0.5, HexInput: 0.62~1.0, fontSize: 19)은 너비가 좁을 때
|
|
// Hex 문자열(8자리 RGBA) 앞부분이 잘려 보이는 현상이 발생하므로 공간을 재분배합니다.
|
|
if (picker.Rect.Find("Header") is RectTransform header)
|
|
{
|
|
if (header.Find("Preview") is RectTransform preview)
|
|
{
|
|
preview.anchorMax = new Vector2(0.28f, 1f);
|
|
}
|
|
|
|
if (header.Find("ToggleArea") is RectTransform toggleArea)
|
|
{
|
|
toggleArea.anchorMax = new Vector2(0.30f, 1f);
|
|
}
|
|
|
|
if (header.Find("HexInput") is RectTransform hexInput)
|
|
{
|
|
hexInput.anchorMin = new Vector2(0.32f, 0f);
|
|
var tmpInput = hexInput.GetComponentInChildren<TMPro.TMP_InputField>();
|
|
if (tmpInput != null && tmpInput.textComponent != null)
|
|
{
|
|
tmpInput.textComponent.fontSize = 15f;
|
|
}
|
|
}
|
|
}
|
|
|
|
return picker;
|
|
}
|
|
|
|
private static void SetWindowActive(bool enabled)
|
|
{
|
|
if (!enabled)
|
|
{
|
|
CloseAllPickers();
|
|
}
|
|
_configWindow?.Rect.gameObject.SetActive(enabled);
|
|
}
|
|
|
|
private static void CloseAllPickers()
|
|
{
|
|
_firePlanetPicker?.SetExpanded(false, true);
|
|
_icePlanetPicker?.SetExpanded(false, true);
|
|
_windPlanetPicker?.SetExpanded(false, true);
|
|
}
|
|
} |