SHA256
1411 lines
55 KiB
C#
1411 lines
55 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using ADOFAIRenderer.API;
|
|
using ADOFAIRenderer.Core;
|
|
using ADOFAIRenderer.Data;
|
|
using ADOFAIRenderer.Data.Input;
|
|
using ADOFAIRenderer.Util;
|
|
using ADOFAIRenderer.Util.FFmpeg;
|
|
using MelonLoader;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
using UnityEngine.UIElements.Experimental;
|
|
|
|
namespace ADOFAIRenderer.UI;
|
|
|
|
internal class RenderConfigUIManager : MonoBehaviour
|
|
{
|
|
[SerializeField]
|
|
private UIDocument uiDocument = null!;
|
|
|
|
private VisualElement _root = null!;
|
|
private IntegerInputValidator _intValidator = null!;
|
|
private DoubleInputValidator _doubleValidator = null!;
|
|
|
|
private Button _goRenderButton = null!;
|
|
private Button _videoEncodingConfigButton = null!;
|
|
private VisualElement _videoEncodingConfigContainer = null!;
|
|
|
|
private Button _audioEncodingConfigButton = null!;
|
|
private VisualElement _audioEncodingConfigContainer = null!;
|
|
|
|
private Button _globalSettingsButton = null!;
|
|
private VisualElement? _globalConfigContainer;
|
|
private Button _advancedSettingsButton = null!;
|
|
private VisualElement? _advancedConfigContainer;
|
|
private Label _versionLabel = null!;
|
|
|
|
private void Awake()
|
|
{
|
|
_root = uiDocument.rootVisualElement;
|
|
RenderManager.Instance.Configuration = new RenderConfig();
|
|
|
|
_intValidator = new IntegerInputValidator(_root);
|
|
_doubleValidator = new DoubleInputValidator(_root);
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
// Init Validators
|
|
_intValidator
|
|
.AddLimits("RenderWidth", new PositiveOnlyIntegerLimit(), new RangeIntegerLimit(1, 7680))
|
|
.AddLimits("RenderHeight", new PositiveOnlyIntegerLimit(), new RangeIntegerLimit(1, 4320));
|
|
|
|
_doubleValidator
|
|
.AddLimits("RenderFPS", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0, 9600))
|
|
.AddLimits("VideoFPS", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0, 4800))
|
|
.AddLimits("DelayAfterEnd", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0, 86400));
|
|
|
|
_intValidator.SetupValidation();
|
|
_doubleValidator.SetupValidation();
|
|
|
|
// Attach Button Event Listener
|
|
_goRenderButton = _root.Q<Button>("GoRenderButton");
|
|
_goRenderButton?.RegisterCallback<ClickEvent>(_ => TryGoRender());
|
|
|
|
_videoEncodingConfigButton = _root.Q<Button>("VideoEncodingSettingsButton");
|
|
if (_videoEncodingConfigButton != null)
|
|
{
|
|
Melon<RenderMod>.Logger.Msg($"[UI] VideoEncodingSettingsButton found: pickingMode={_videoEncodingConfigButton.pickingMode}, enabledSelf={_videoEncodingConfigButton.enabledSelf}");
|
|
_videoEncodingConfigButton.RegisterCallback<ClickEvent>(_ => OpenVideoEncodingConfig());
|
|
}
|
|
else
|
|
{
|
|
Melon<RenderMod>.Logger.Error("Button 'VideoEncodingSettingsButton' not found.");
|
|
}
|
|
|
|
_audioEncodingConfigButton = _root.Q<Button>("AudioEncodingSettingsButton");
|
|
if (_audioEncodingConfigButton != null)
|
|
{
|
|
Melon<RenderMod>.Logger.Msg($"[UI] AudioEncodingSettingsButton found: pickingMode={_audioEncodingConfigButton.pickingMode}, enabledSelf={_audioEncodingConfigButton.enabledSelf}");
|
|
_audioEncodingConfigButton.RegisterCallback<ClickEvent>(_ => OpenAudioEncodingConfig());
|
|
}
|
|
else
|
|
{
|
|
Melon<RenderMod>.Logger.Error("Button 'AudioEncodingSettingsButton' not found.");
|
|
}
|
|
|
|
var presetSettingsButton = _root.Q<Button>("PresetSettingsButton");
|
|
if (presetSettingsButton != null)
|
|
{
|
|
Melon<RenderMod>.Logger.Msg($"[UI] PresetSettingsButton found: pickingMode={presetSettingsButton.pickingMode}, enabledSelf={presetSettingsButton.enabledSelf}");
|
|
presetSettingsButton.RegisterCallback<ClickEvent>(_ => OpenPresetConfig());
|
|
}
|
|
else
|
|
{
|
|
Melon<RenderMod>.Logger.Error("Button 'PresetSettingsButton' not found.");
|
|
}
|
|
|
|
var rightBottomContainer = new VisualElement
|
|
{
|
|
name = "RightBottomButtonContainer",
|
|
pickingMode = PickingMode.Ignore,
|
|
style =
|
|
{
|
|
position = Position.Absolute,
|
|
right = 0f,
|
|
bottom = 0f,
|
|
flexDirection = FlexDirection.Column,
|
|
alignItems = Align.FlexEnd
|
|
}
|
|
};
|
|
|
|
var globalRow = CreateRow();
|
|
globalRow.pickingMode = PickingMode.Ignore;
|
|
globalRow.style.justifyContent = Justify.FlexEnd;
|
|
_globalSettingsButton = new Button { name = "GlobalSettingsButton", text = "Global Settings..." };
|
|
globalRow.Add(_globalSettingsButton);
|
|
rightBottomContainer.Add(globalRow);
|
|
|
|
var advancedRow = CreateRow();
|
|
advancedRow.pickingMode = PickingMode.Ignore;
|
|
advancedRow.style.justifyContent = Justify.FlexEnd;
|
|
_advancedSettingsButton = new Button { name = "AdvancedSettingsButton", text = "Advanced Settings..." };
|
|
advancedRow.Add(_advancedSettingsButton);
|
|
rightBottomContainer.Add(advancedRow);
|
|
|
|
(_root.childCount > 0 ? _root[0] : _root).Add(rightBottomContainer);
|
|
Melon<RenderMod>.Logger.Msg("[UI] GlobalSettingsButton and AdvancedSettingsButton dynamically created.");
|
|
|
|
_globalSettingsButton.RegisterCallback<ClickEvent>(_ => OpenGlobalConfig());
|
|
_advancedSettingsButton.RegisterCallback<ClickEvent>(_ => OpenAdvancedConfig());
|
|
|
|
var versionText =
|
|
$"<link='open_git'>ADOFAIRenderer v{Melon<RenderMod>.Instance.Info.Version}</link>";
|
|
if (DateTime.UtcNow >= RenderMod.ExpirationTime) versionText += " (Expired)";
|
|
|
|
_versionLabel = new Label(versionText)
|
|
{
|
|
name = "VersionLabel",
|
|
pickingMode = PickingMode.Position,
|
|
style =
|
|
{
|
|
position = Position.Absolute,
|
|
left = 25f, bottom = 25f,
|
|
fontSize = 24f,
|
|
paddingTop = 0, paddingBottom = 0, paddingLeft = 0, paddingRight = 0,
|
|
marginTop = 0, marginBottom = 0, marginLeft = 0, marginRight = 0,
|
|
color = new StyleColor(new Color(0f, 0f, 0f, 0.3f))
|
|
}
|
|
};
|
|
|
|
// Apply same font as existing UI
|
|
if (RenderMod.FontBundle)
|
|
{
|
|
try
|
|
{
|
|
var font = RenderMod.FontBundle.LoadAsset<Font>("NanumSquareNeoOTF-Bd");
|
|
if (font) { _versionLabel.style.unityFontDefinition = FontDefinition.FromFont(font); }
|
|
}
|
|
catch { /* ignored */ }
|
|
}
|
|
|
|
_versionLabel.RegisterCallback<PointerUpLinkTagEvent>(OnLinkTagClicked);
|
|
|
|
(_root.childCount > 0 ? _root[0] : _root).Add(_versionLabel);
|
|
Melon<RenderMod>.Logger.Msg("[UI] VersionLabel dynamically created.");
|
|
}
|
|
|
|
private void OpenGlobalConfig()
|
|
{
|
|
var tempGlobal = new GlobalConfig
|
|
{
|
|
OutputDirectory = GlobalSettings.Current.OutputDirectory
|
|
};
|
|
|
|
if (_globalConfigContainer != null && _root.Contains(_globalConfigContainer))
|
|
{
|
|
_root.Remove(_globalConfigContainer);
|
|
}
|
|
|
|
_globalConfigContainer = new VisualElement
|
|
{
|
|
name = "GlobalSettingsContainer",
|
|
style =
|
|
{
|
|
position = Position.Absolute,
|
|
width = Length.Percent(100),
|
|
height = Length.Percent(100),
|
|
left = 0,
|
|
top = 0,
|
|
backgroundColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f, 1.0f)),
|
|
justifyContent = Justify.Center,
|
|
alignItems = Align.Center
|
|
}
|
|
};
|
|
|
|
// 1. Title Row
|
|
var titleRow = CreateRow();
|
|
var titleLabel = new Label("Global Settings")
|
|
{
|
|
style =
|
|
{
|
|
fontSize = 24,
|
|
color = new StyleColor(Color.white)
|
|
}
|
|
};
|
|
titleRow.Add(titleLabel);
|
|
_globalConfigContainer.Add(titleRow);
|
|
|
|
// 2. Output Path Row
|
|
var pathRow = CreateRow();
|
|
var pathLabel = CreateFormLabel("Output Directory");
|
|
var defaultDir = GlobalSettings.DefaultOutputDirectory;
|
|
var currentDisplayDir = !string.IsNullOrWhiteSpace(tempGlobal.OutputDirectory)
|
|
? tempGlobal.OutputDirectory
|
|
: defaultDir;
|
|
|
|
var pathField = new TextField
|
|
{
|
|
name = "GlobalOutputDirectory",
|
|
label = "",
|
|
value = currentDisplayDir,
|
|
style =
|
|
{
|
|
width = 520f,
|
|
fontSize = 12f
|
|
}
|
|
};
|
|
|
|
var textInput = pathField.Q("unity-text-input");
|
|
if (textInput != null)
|
|
{
|
|
textInput.style.backgroundColor = new StyleColor(new Color(0.1f, 0.1f, 0.1f, 1.0f));
|
|
textInput.style.borderTopLeftRadius = 6f;
|
|
textInput.style.borderTopRightRadius = 6f;
|
|
textInput.style.borderBottomLeftRadius = 6f;
|
|
textInput.style.borderBottomRightRadius = 6f;
|
|
textInput.style.borderTopWidth = 1f;
|
|
textInput.style.borderBottomWidth = 1f;
|
|
textInput.style.borderLeftWidth = 1f;
|
|
textInput.style.borderRightWidth = 1f;
|
|
textInput.style.borderTopColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
textInput.style.borderBottomColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
textInput.style.borderLeftColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
textInput.style.borderRightColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
textInput.style.paddingLeft = 8f;
|
|
textInput.style.paddingRight = 8f;
|
|
textInput.style.fontSize = 12f;
|
|
textInput.style.unityTextAlign = TextAnchor.MiddleLeft;
|
|
textInput.style.color = new StyleColor(Color.white);
|
|
}
|
|
|
|
pathRow.Add(pathLabel);
|
|
pathRow.Add(pathField);
|
|
_globalConfigContainer.Add(pathRow);
|
|
|
|
// 3. Action Buttons Row (Browse & Reset)
|
|
var actionRow = CreateRow();
|
|
actionRow.style.height = StyleKeyword.Auto;
|
|
actionRow.style.maxHeight = StyleKeyword.Null;
|
|
actionRow.style.minHeight = StyleKeyword.Null;
|
|
actionRow.style.marginBottom = 15f;
|
|
|
|
var browseBtn = new Button { text = "Browse..." };
|
|
browseBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
var initial = string.IsNullOrWhiteSpace(pathField.value) ? defaultDir : pathField.value;
|
|
FolderDialogUtil.OpenFolderBrowser(initial, selected =>
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(selected))
|
|
{
|
|
pathField.value = selected;
|
|
}
|
|
});
|
|
});
|
|
|
|
var resetBtn = new Button { text = "Reset to Default" };
|
|
resetBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
pathField.value = defaultDir;
|
|
});
|
|
|
|
actionRow.Add(browseBtn);
|
|
actionRow.Add(resetBtn);
|
|
_globalConfigContainer.Add(actionRow);
|
|
|
|
// 4. Info Row
|
|
var infoRow = CreateRow();
|
|
infoRow.style.height = StyleKeyword.Auto;
|
|
infoRow.style.maxHeight = StyleKeyword.Null;
|
|
infoRow.style.minHeight = StyleKeyword.Null;
|
|
infoRow.style.marginBottom = 10f;
|
|
|
|
var infoLabel = new Label("Global output directory applies to all renders unless overridden by API.")
|
|
{
|
|
style =
|
|
{
|
|
color = new StyleColor(new Color(0.7f, 0.7f, 0.7f)),
|
|
fontSize = 18,
|
|
unityTextAlign = TextAnchor.MiddleCenter
|
|
}
|
|
};
|
|
infoRow.Add(infoLabel);
|
|
_globalConfigContainer.Add(infoRow);
|
|
|
|
// 5. Control Button Row (Cancel / Save)
|
|
var buttonRow = CreateRow();
|
|
var cancelBtn = new Button { text = "Cancel" };
|
|
var saveBtn = new Button { text = "Save" };
|
|
|
|
buttonRow.Add(cancelBtn);
|
|
buttonRow.Add(saveBtn);
|
|
_globalConfigContainer.Add(buttonRow);
|
|
|
|
cancelBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ToggleGlobalScreens(false);
|
|
_root.Remove(_globalConfigContainer);
|
|
_globalConfigContainer = null!;
|
|
});
|
|
|
|
saveBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
var input = pathField.value?.Trim() ?? string.Empty;
|
|
if (string.IsNullOrWhiteSpace(input) ||
|
|
string.Equals(Path.GetFullPath(input).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar),
|
|
Path.GetFullPath(defaultDir).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar),
|
|
StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
GlobalSettings.Current.OutputDirectory = null;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
var fullPath = Path.GetFullPath(input);
|
|
if (!Directory.Exists(fullPath))
|
|
{
|
|
Directory.CreateDirectory(fullPath);
|
|
}
|
|
GlobalSettings.Current.OutputDirectory = fullPath;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CommonWarningDialogUI.ShowDialog("Invalid Path", $"Directory path is invalid:\n{ex.Message}", new DialogButton("OK", () => { }));
|
|
return;
|
|
}
|
|
}
|
|
|
|
GlobalSettings.Save();
|
|
|
|
ToggleGlobalScreens(false);
|
|
_root.Remove(_globalConfigContainer);
|
|
_globalConfigContainer = null!;
|
|
});
|
|
|
|
_root.Add(_globalConfigContainer);
|
|
ToggleGlobalScreens(true);
|
|
|
|
void ToggleGlobalScreens(bool showGlobal)
|
|
{
|
|
if (_root.childCount > 0)
|
|
{
|
|
_root[0].style.display = showGlobal ? DisplayStyle.None : DisplayStyle.Flex;
|
|
}
|
|
_globalConfigContainer.style.display = showGlobal ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
}
|
|
|
|
private void OpenAdvancedConfig()
|
|
{
|
|
var config = RenderManager.Instance.Configuration;
|
|
|
|
var tempConfig = new AdvancedConfig
|
|
{
|
|
ProcessAudio = config.AdvancedConfig.ProcessAudio,
|
|
RandomSeed = config.AdvancedConfig.RandomSeed,
|
|
NonTile = config.AdvancedConfig.NonTile,
|
|
FileExtension = config.AdvancedConfig.FileExtension
|
|
};
|
|
|
|
if (_advancedConfigContainer != null && _root.Contains(_advancedConfigContainer))
|
|
{
|
|
_root.Remove(_advancedConfigContainer);
|
|
}
|
|
|
|
_advancedConfigContainer = new VisualElement
|
|
{
|
|
name = "AdvancedSettingsContainer",
|
|
style =
|
|
{
|
|
position = Position.Absolute,
|
|
width = Length.Percent(100),
|
|
height = Length.Percent(100),
|
|
left = 0,
|
|
top = 0,
|
|
backgroundColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f, 1.0f)),
|
|
justifyContent = Justify.Center,
|
|
alignItems = Align.Center
|
|
}
|
|
};
|
|
|
|
// 1. Title Row
|
|
var titleRow = CreateRow();
|
|
var titleLabel = new Label("Advanced Settings")
|
|
{
|
|
style =
|
|
{
|
|
fontSize = 24,
|
|
color = new StyleColor(Color.white)
|
|
}
|
|
};
|
|
titleRow.Add(titleLabel);
|
|
_advancedConfigContainer.Add(titleRow);
|
|
|
|
// 2. Extension Row
|
|
var fileExtensionRow = CreateRow();
|
|
var fileExtensionLabel = CreateFormLabel("File Extension");
|
|
var fileExtensionChoices = new List<string> { "mkv", "mp4", "mov", "webm", "avi" };
|
|
var currentFileExt = string.IsNullOrWhiteSpace(tempConfig.FileExtension) ? "mkv" : tempConfig.FileExtension.TrimStart('.').ToLower();
|
|
if (!fileExtensionChoices.Contains(currentFileExt))
|
|
{
|
|
fileExtensionChoices.Add(currentFileExt);
|
|
}
|
|
var fileExtensionDropdown = new DropdownField(fileExtensionChoices, currentFileExt) { name = "AdvancedFileExtension" };
|
|
fileExtensionRow.Add(fileExtensionLabel);
|
|
fileExtensionRow.Add(fileExtensionDropdown);
|
|
_advancedConfigContainer.Add(fileExtensionRow);
|
|
|
|
// 3. Audio Processing Row
|
|
var processAudioRow = CreateRow();
|
|
var processAudioLabel = CreateFormLabel("Process Audio");
|
|
var processAudioToggle = new Toggle { name = "AdvancedProcessAudioToggle", label = "", value = tempConfig.ProcessAudio };
|
|
processAudioRow.Add(processAudioLabel);
|
|
processAudioRow.Add(processAudioToggle);
|
|
_advancedConfigContainer.Add(processAudioRow);
|
|
|
|
// 4. Non-Tile Mode Row
|
|
var nonTileRow = CreateRow();
|
|
var nonTileLabel = CreateFormLabel("Non-Tile Mode");
|
|
var nonTileToggle = new Toggle { name = "AdvancedNonTileToggle", label = "", value = tempConfig.NonTile };
|
|
nonTileRow.Add(nonTileLabel);
|
|
nonTileRow.Add(nonTileToggle);
|
|
_advancedConfigContainer.Add(nonTileRow);
|
|
|
|
// 5. Random Seed Row
|
|
var randomSeedRow = CreateRow();
|
|
var randomSeedLabel = CreateFormLabel("Random Seed (Optional)");
|
|
var randomSeedField = new TextField { name = "AdvancedRandomSeed", label = "", value = tempConfig.RandomSeed?.ToString() ?? string.Empty };
|
|
|
|
randomSeedField.RegisterValueChangedCallback(evt =>
|
|
{
|
|
if (string.IsNullOrEmpty(evt.newValue)) return;
|
|
if (!int.TryParse(evt.newValue, out _) && evt.newValue != "-")
|
|
{
|
|
randomSeedField.SetValueWithoutNotify(evt.previousValue);
|
|
}
|
|
});
|
|
|
|
randomSeedRow.Add(randomSeedLabel);
|
|
randomSeedRow.Add(randomSeedField);
|
|
_advancedConfigContainer.Add(randomSeedRow);
|
|
|
|
// 6. Control Button Row (Cancel / Save)
|
|
var buttonRow = CreateRow();
|
|
var cancelBtn = new Button { text = "Cancel" };
|
|
var saveBtn = new Button { text = "Save" };
|
|
|
|
buttonRow.Add(cancelBtn);
|
|
buttonRow.Add(saveBtn);
|
|
_advancedConfigContainer.Add(buttonRow);
|
|
|
|
cancelBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ToggleAdvancedScreens(false);
|
|
_root.Remove(_advancedConfigContainer);
|
|
_advancedConfigContainer = null!;
|
|
});
|
|
|
|
saveBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
tempConfig.FileExtension = fileExtensionDropdown.value;
|
|
tempConfig.ProcessAudio = processAudioToggle.value;
|
|
tempConfig.NonTile = nonTileToggle.value;
|
|
if (int.TryParse(randomSeedField.value?.Trim(), out var seed))
|
|
{
|
|
tempConfig.RandomSeed = seed;
|
|
}
|
|
else
|
|
{
|
|
tempConfig.RandomSeed = null;
|
|
}
|
|
|
|
config.AdvancedConfig = tempConfig;
|
|
|
|
ToggleAdvancedScreens(false);
|
|
_root.Remove(_advancedConfigContainer);
|
|
_advancedConfigContainer = null!;
|
|
});
|
|
|
|
_root.Add(_advancedConfigContainer);
|
|
ToggleAdvancedScreens(true);
|
|
|
|
void ToggleAdvancedScreens(bool showAdvanced)
|
|
{
|
|
if (_root.childCount > 0)
|
|
{
|
|
_root[0].style.display = showAdvanced ? DisplayStyle.None : DisplayStyle.Flex;
|
|
}
|
|
_advancedConfigContainer.style.display = showAdvanced ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (Input.GetKeyDown(KeyCode.Escape))
|
|
{
|
|
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
|
}
|
|
}
|
|
|
|
private void OpenVideoEncodingConfig()
|
|
{
|
|
var config = RenderManager.Instance.Configuration;
|
|
|
|
var tempConfig = new VideoEncodingConfig
|
|
{
|
|
Codec = config.VideoConfig.Codec,
|
|
RateControlMode = config.VideoConfig.RateControlMode,
|
|
Bitrate = config.VideoConfig.Bitrate,
|
|
MaxBitrate = config.VideoConfig.MaxBitrate,
|
|
QP = config.VideoConfig.QP,
|
|
KeyframeInterval = config.VideoConfig.KeyframeInterval,
|
|
ForceSoftwareEncoder = config.VideoConfig.ForceSoftwareEncoder
|
|
};
|
|
|
|
if (_root.Contains(_videoEncodingConfigContainer))
|
|
{
|
|
_root.Remove(_videoEncodingConfigContainer);
|
|
}
|
|
|
|
// 1. Create full screen container and apply background
|
|
_videoEncodingConfigContainer = new VisualElement
|
|
{
|
|
name = "VideoEncodingSettingsContainer",
|
|
style =
|
|
{
|
|
position = Position.Absolute,
|
|
width = Length.Percent(100),
|
|
height = Length.Percent(100),
|
|
left = 0,
|
|
top = 0,
|
|
backgroundColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f, 1.0f)),
|
|
justifyContent = Justify.Center,
|
|
alignItems = Align.Center
|
|
}
|
|
};
|
|
|
|
// 2. Title Row
|
|
var titleRow = CreateRow();
|
|
var titleLabel = new Label("Video Encoding Settings")
|
|
{
|
|
style =
|
|
{
|
|
fontSize = 24,
|
|
color = new StyleColor(Color.white)
|
|
}
|
|
};
|
|
titleRow.Add(titleLabel);
|
|
_videoEncodingConfigContainer.Add(titleRow);
|
|
|
|
// 3. Codec Row
|
|
var codecRow = CreateRow();
|
|
var codecLabel = CreateFormLabel("Codec");
|
|
var codecChoices = new List<string>(Enum.GetNames(typeof(Codec)));
|
|
var codecDropdown = new DropdownField(codecChoices, tempConfig.Codec.ToString()) { name = "VideoCodec" };
|
|
codecRow.Add(codecLabel);
|
|
codecRow.Add(codecDropdown);
|
|
_videoEncodingConfigContainer.Add(codecRow);
|
|
|
|
// 4. Bitrate Control Mode Row
|
|
var bitrateControlModeRow = CreateRow();
|
|
var bitrateControlModeLabel = CreateFormLabel("Bitrate Control Mode");
|
|
var bitrateControlModeChoices = new List<string>(Enum.GetNames(typeof(RateControlMode)));
|
|
var bitrateControlModeDropdown = new DropdownField(bitrateControlModeChoices, tempConfig.RateControlMode.ToString()) { name = "VideoRateControlMode" };
|
|
bitrateControlModeRow.Add(bitrateControlModeLabel);
|
|
bitrateControlModeRow.Add(bitrateControlModeDropdown);
|
|
_videoEncodingConfigContainer.Add(bitrateControlModeRow);
|
|
|
|
// 5. Bitrate Row
|
|
var bitrateRow = CreateRow();
|
|
var bitrateLabel = CreateFormLabel("Bitrate (kbps)");
|
|
var bitrateField = new IntegerField { name = "VideoBitrate", label = "", value = tempConfig.Bitrate };
|
|
bitrateRow.Add(bitrateLabel);
|
|
bitrateRow.Add(bitrateField);
|
|
_videoEncodingConfigContainer.Add(bitrateRow);
|
|
|
|
// 5.5. Max Bitrate Row (VBR only)
|
|
var maxBitrateRow = CreateRow();
|
|
var maxBitrateLabel = CreateFormLabel("Max Bitrate (kbps)");
|
|
var maxBitrateField = new IntegerField { name = "VideoMaxBitrate", label = "", value = tempConfig.MaxBitrate };
|
|
maxBitrateRow.Add(maxBitrateLabel);
|
|
maxBitrateRow.Add(maxBitrateField);
|
|
_videoEncodingConfigContainer.Add(maxBitrateRow);
|
|
|
|
// 6. QP Row
|
|
var qpRow = CreateRow();
|
|
var qpLabel = CreateFormLabel("Quality (QP)");
|
|
var qpField = new IntegerField { name = "VideoQP", label = "", value = tempConfig.QP };
|
|
qpRow.Add(qpLabel);
|
|
qpRow.Add(qpField);
|
|
_videoEncodingConfigContainer.Add(qpRow);
|
|
|
|
// 6.5. Keyframe Interval Row (seconds)
|
|
var keyframeRow = CreateRow();
|
|
var keyframeLabel = CreateFormLabel("Keyframe Interval (s)");
|
|
var keyframeField = new DoubleField { name = "VideoKeyframeInterval", label = "", value = tempConfig.KeyframeInterval };
|
|
keyframeRow.Add(keyframeLabel);
|
|
keyframeRow.Add(keyframeField);
|
|
_videoEncodingConfigContainer.Add(keyframeRow);
|
|
|
|
// 6.75. Force Software Encoder Row (Label + Toggle)
|
|
var forceSoftwareRow = CreateRow();
|
|
var forceSoftwareLabel = CreateFormLabel("Force Software Encoder");
|
|
var forceSoftwareToggle = new Toggle { name = "VideoForceSoftwareEncoderToggle", label = "", value = tempConfig.ForceSoftwareEncoder };
|
|
forceSoftwareRow.Add(forceSoftwareLabel);
|
|
forceSoftwareRow.Add(forceSoftwareToggle);
|
|
_videoEncodingConfigContainer.Add(forceSoftwareRow);
|
|
|
|
// 7. QP = 0 (Lossless) Warning Label Row
|
|
var warningRow = CreateRow();
|
|
warningRow.style.height = StyleKeyword.Auto;
|
|
warningRow.style.maxHeight = StyleKeyword.Null;
|
|
warningRow.style.minHeight = StyleKeyword.Null;
|
|
|
|
var warningLabel = new Label("Warning: Setting QP to 0 applies lossless encoding and may result in very large file sizes.")
|
|
{
|
|
style =
|
|
{
|
|
color = new StyleColor(new Color(1.0f, 0.8f, 0.2f)),
|
|
fontSize = 20,
|
|
unityTextAlign = TextAnchor.MiddleCenter,
|
|
whiteSpace = WhiteSpace.Normal
|
|
}
|
|
};
|
|
warningRow.Add(warningLabel);
|
|
_videoEncodingConfigContainer.Add(warningRow);
|
|
|
|
var encodingValidator = new IntegerInputValidator(_videoEncodingConfigContainer);
|
|
encodingValidator
|
|
.AddLimits("VideoBitrate", new PositiveOnlyIntegerLimit(), new RangeIntegerLimit(1, 1000000))
|
|
.AddLimits("VideoMaxBitrate", new PositiveOnlyIntegerLimit(), new RangeIntegerLimit(1, 1000000))
|
|
.AddLimits("VideoQP", new RangeIntegerLimit(0, 51));
|
|
encodingValidator.SetupValidation();
|
|
|
|
var doubleValidator = new DoubleInputValidator(_videoEncodingConfigContainer);
|
|
doubleValidator.AddLimits("VideoKeyframeInterval", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0.1, 60.0));
|
|
doubleValidator.SetupValidation();
|
|
|
|
bitrateControlModeDropdown.RegisterValueChangedCallback(evt => UpdateVisibility(evt.newValue));
|
|
qpField.RegisterValueChangedCallback(evt =>
|
|
{
|
|
warningRow.style.display = bitrateControlModeDropdown.value == nameof(RateControlMode.CQP) && evt.newValue == 0
|
|
? DisplayStyle.Flex
|
|
: DisplayStyle.None;
|
|
});
|
|
|
|
// 8. Control Button Row (Cancel / Save)
|
|
var buttonRow = CreateRow();
|
|
var cancelBtn = new Button { text = "Cancel" };
|
|
var saveBtn = new Button { text = "Save" };
|
|
|
|
buttonRow.Add(cancelBtn);
|
|
buttonRow.Add(saveBtn);
|
|
_videoEncodingConfigContainer.Add(buttonRow);
|
|
|
|
cancelBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ToggleScreens(false);
|
|
_root.Remove(_videoEncodingConfigContainer);
|
|
_videoEncodingConfigContainer = null!;
|
|
});
|
|
|
|
UpdateVisibility(bitrateControlModeDropdown.value);
|
|
|
|
saveBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
if (Enum.TryParse<RateControlMode>(bitrateControlModeDropdown.value, out var mode))
|
|
{
|
|
tempConfig.RateControlMode = mode;
|
|
}
|
|
if (Enum.TryParse<Codec>(codecDropdown.value, out var codec))
|
|
{
|
|
tempConfig.Codec = codec;
|
|
}
|
|
tempConfig.Bitrate = bitrateField.value;
|
|
tempConfig.MaxBitrate = maxBitrateField.value;
|
|
tempConfig.QP = qpField.value;
|
|
tempConfig.KeyframeInterval = keyframeField.value;
|
|
tempConfig.ForceSoftwareEncoder = forceSoftwareToggle.value;
|
|
|
|
if (tempConfig.RateControlMode == RateControlMode.VBR && tempConfig.MaxBitrate < tempConfig.Bitrate)
|
|
{
|
|
tempConfig.MaxBitrate = tempConfig.Bitrate;
|
|
}
|
|
|
|
config.VideoConfig = tempConfig;
|
|
|
|
ToggleScreens(false);
|
|
_root.Remove(_videoEncodingConfigContainer);
|
|
_videoEncodingConfigContainer = null!;
|
|
});
|
|
|
|
_root.Add(_videoEncodingConfigContainer);
|
|
|
|
ToggleScreens(true);
|
|
return;
|
|
|
|
void ToggleScreens(bool showEncoding)
|
|
{
|
|
if (_root.childCount > 0)
|
|
{
|
|
_root[0].style.display = showEncoding ? DisplayStyle.None : DisplayStyle.Flex;
|
|
}
|
|
_videoEncodingConfigContainer.style.display = showEncoding ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
|
|
void UpdateVisibility(string selectedModeStr)
|
|
{
|
|
if (!Enum.TryParse<RateControlMode>(selectedModeStr, out var mode)) return;
|
|
bitrateRow.style.display = mode is RateControlMode.CBR or RateControlMode.VBR ? DisplayStyle.Flex : DisplayStyle.None;
|
|
maxBitrateRow.style.display = mode == RateControlMode.VBR ? DisplayStyle.Flex : DisplayStyle.None;
|
|
qpRow.style.display = mode == RateControlMode.CQP ? DisplayStyle.Flex : DisplayStyle.None;
|
|
warningRow.style.display = mode == RateControlMode.CQP && qpField.value == 0 ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
}
|
|
|
|
private void OpenAudioEncodingConfig()
|
|
{
|
|
var config = RenderManager.Instance.Configuration;
|
|
|
|
var tempAudioConfig = new AudioEncodingConfig
|
|
{
|
|
RenderAudio = config.AudioConfig.RenderAudio,
|
|
AudioSampleRate = config.AudioConfig.AudioSampleRate,
|
|
AudioChannels = config.AudioConfig.AudioChannels,
|
|
AudioBitrate = config.AudioConfig.AudioBitrate
|
|
};
|
|
|
|
if (_root.Contains(_audioEncodingConfigContainer))
|
|
{
|
|
_root.Remove(_audioEncodingConfigContainer);
|
|
}
|
|
|
|
_audioEncodingConfigContainer = new VisualElement
|
|
{
|
|
name = "AudioEncodingSettingsContainer",
|
|
style =
|
|
{
|
|
position = Position.Absolute,
|
|
width = Length.Percent(100),
|
|
height = Length.Percent(100),
|
|
left = 0,
|
|
top = 0,
|
|
backgroundColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f, 1.0f)),
|
|
justifyContent = Justify.Center,
|
|
alignItems = Align.Center
|
|
}
|
|
};
|
|
|
|
// 1. Title Row
|
|
var titleRow = CreateRow();
|
|
var titleLabel = new Label("Audio Encoding Settings")
|
|
{
|
|
style =
|
|
{
|
|
fontSize = 24,
|
|
color = new StyleColor(Color.white)
|
|
}
|
|
};
|
|
titleRow.Add(titleLabel);
|
|
_audioEncodingConfigContainer.Add(titleRow);
|
|
|
|
// 2. Audio Rendering Row (Label + Toggle)
|
|
var renderAudioRow = CreateRow();
|
|
var renderAudioLabel = CreateFormLabel("Audio Rendering");
|
|
var renderAudioToggle = new Toggle { name = "AudioRenderAudioToggle", label = "", value = tempAudioConfig.RenderAudio };
|
|
renderAudioRow.Add(renderAudioLabel);
|
|
renderAudioRow.Add(renderAudioToggle);
|
|
_audioEncodingConfigContainer.Add(renderAudioRow);
|
|
|
|
// 3. Sample Rate Row
|
|
var sampleRateRow = CreateRow();
|
|
var sampleRateLabel = CreateFormLabel("Sample Rate (Hz)");
|
|
var isOpus = config.VideoConfig.Codec is Codec.AV1 or Codec.VP9 or Codec.VVC;
|
|
|
|
List<string> sampleRateChoices;
|
|
string sampleRateStr;
|
|
|
|
if (isOpus)
|
|
{
|
|
sampleRateChoices = new List<string> { "48000" };
|
|
sampleRateStr = "48000";
|
|
}
|
|
else
|
|
{
|
|
sampleRateChoices = new List<string> { "44100", "48000" };
|
|
sampleRateStr = tempAudioConfig.AudioSampleRate.ToString();
|
|
if (sampleRateStr != "44100" && sampleRateStr != "48000")
|
|
{
|
|
sampleRateStr = "44100";
|
|
}
|
|
}
|
|
|
|
var sampleRateDropdown = new DropdownField(
|
|
sampleRateChoices,
|
|
sampleRateStr
|
|
) { name = "AudioSampleRate" };
|
|
sampleRateRow.Add(sampleRateLabel);
|
|
sampleRateRow.Add(sampleRateDropdown);
|
|
_audioEncodingConfigContainer.Add(sampleRateRow);
|
|
|
|
// 4. Channels Row
|
|
var channelsRow = CreateRow();
|
|
var channelsLabel = CreateFormLabel("Audio Channels");
|
|
var channelsDropdown = new DropdownField(
|
|
new List<string> { "1 (Mono)", "2 (Stereo)" },
|
|
tempAudioConfig.AudioChannels == 1 ? "1 (Mono)" : "2 (Stereo)"
|
|
) { name = "AudioChannels" };
|
|
channelsRow.Add(channelsLabel);
|
|
channelsRow.Add(channelsDropdown);
|
|
_audioEncodingConfigContainer.Add(channelsRow);
|
|
|
|
// 5. Bitrate Row (input in kbps)
|
|
var bitrateRow = CreateRow();
|
|
var bitrateLabel = CreateFormLabel("Audio Bitrate (kbps)");
|
|
var bitrateField = new IntegerField { name = "AudioBitrateKbps", label = "", value = tempAudioConfig.AudioBitrate / 1000 };
|
|
bitrateRow.Add(bitrateLabel);
|
|
bitrateRow.Add(bitrateField);
|
|
_audioEncodingConfigContainer.Add(bitrateRow);
|
|
|
|
var audioValidator = new IntegerInputValidator(_audioEncodingConfigContainer);
|
|
audioValidator.AddLimits("AudioBitrateKbps", new PositiveOnlyIntegerLimit(), new RangeIntegerLimit(32, 1024));
|
|
audioValidator.SetupValidation();
|
|
|
|
// Update visibility (hide sub-settings when audio rendering is disabled)
|
|
renderAudioToggle.RegisterValueChangedCallback(evt => UpdateAudioVisibility(evt.newValue));
|
|
UpdateAudioVisibility(tempAudioConfig.RenderAudio);
|
|
|
|
// 6. Control Button Row (Cancel / Save)
|
|
var buttonRow = CreateRow();
|
|
var cancelBtn = new Button { text = "Cancel" };
|
|
var saveBtn = new Button { text = "Save" };
|
|
|
|
buttonRow.Add(cancelBtn);
|
|
buttonRow.Add(saveBtn);
|
|
_audioEncodingConfigContainer.Add(buttonRow);
|
|
|
|
cancelBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ToggleAudioScreens(false);
|
|
_root.Remove(_audioEncodingConfigContainer);
|
|
_audioEncodingConfigContainer = null!;
|
|
});
|
|
|
|
saveBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
tempAudioConfig.RenderAudio = renderAudioToggle.value;
|
|
|
|
if (int.TryParse(sampleRateDropdown.value, out var sampleRate))
|
|
{
|
|
tempAudioConfig.AudioSampleRate = sampleRate;
|
|
}
|
|
|
|
tempAudioConfig.AudioChannels = channelsDropdown.value.StartsWith("1") ? 1 : 2;
|
|
tempAudioConfig.AudioBitrate = bitrateField.value * 1000;
|
|
|
|
config.AudioConfig = tempAudioConfig;
|
|
|
|
ToggleAudioScreens(false);
|
|
_root.Remove(_audioEncodingConfigContainer);
|
|
_audioEncodingConfigContainer = null!;
|
|
});
|
|
|
|
_root.Add(_audioEncodingConfigContainer);
|
|
ToggleAudioScreens(true);
|
|
return;
|
|
|
|
void ToggleAudioScreens(bool showAudio)
|
|
{
|
|
if (_root.childCount > 0)
|
|
{
|
|
_root[0].style.display = showAudio ? DisplayStyle.None : DisplayStyle.Flex;
|
|
}
|
|
_audioEncodingConfigContainer.style.display = showAudio ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
|
|
void UpdateAudioVisibility(bool isAudioEnabled)
|
|
{
|
|
sampleRateRow.style.display = isAudioEnabled ? DisplayStyle.Flex : DisplayStyle.None;
|
|
channelsRow.style.display = isAudioEnabled ? DisplayStyle.Flex : DisplayStyle.None;
|
|
bitrateRow.style.display = isAudioEnabled ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
}
|
|
|
|
private void TryGoRender()
|
|
{
|
|
if (!ValidateInputs())
|
|
{
|
|
Melon<RenderMod>.Logger.Error("Required input fields are empty or invalid. Please fill in all settings.");
|
|
return;
|
|
}
|
|
|
|
var widthField = _root.Q<IntegerField>("RenderWidth");
|
|
var heightField = _root.Q<IntegerField>("RenderHeight");
|
|
var renderFpsField = _root.Q<DoubleField>("RenderFPS");
|
|
var videoFpsField = _root.Q<DoubleField>("VideoFPS");
|
|
var delayField = _root.Q<DoubleField>("DelayAfterEnd");
|
|
var showJudgementText = _root.Q<Toggle>("ShowJudgementText");
|
|
|
|
// Copy validated values to Configuration manually
|
|
var config = RenderManager.Instance.Configuration;
|
|
config.RenderWidth = widthField.value;
|
|
config.RenderHeight = heightField.value;
|
|
config.RenderFPS = renderFpsField.value;
|
|
config.VideoFPS = videoFpsField.value;
|
|
config.DelayAfterEnd = delayField.value;
|
|
|
|
if (showJudgementText != null)
|
|
config.ShowJudgementText = showJudgementText.value;
|
|
|
|
new RenderSession().Start(config, RenderManager.Instance.levelPath);
|
|
}
|
|
|
|
private VisualElement? _presetConfigContainer;
|
|
|
|
private bool ValidateInputs()
|
|
{
|
|
var widthField = _root.Q<IntegerField>("RenderWidth");
|
|
var heightField = _root.Q<IntegerField>("RenderHeight");
|
|
var renderFpsField = _root.Q<DoubleField>("RenderFPS");
|
|
var videoFpsField = _root.Q<DoubleField>("VideoFPS");
|
|
var delayField = _root.Q<DoubleField>("DelayAfterEnd");
|
|
|
|
if (widthField == null || string.IsNullOrEmpty(widthField.text) ||
|
|
heightField == null || string.IsNullOrEmpty(heightField.text) ||
|
|
renderFpsField == null || string.IsNullOrEmpty(renderFpsField.text) ||
|
|
videoFpsField == null || string.IsNullOrEmpty(videoFpsField.text) ||
|
|
delayField == null || string.IsNullOrEmpty(delayField.text))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (widthField.value <= 0 || heightField.value <= 0 ||
|
|
renderFpsField.value <= 0 || videoFpsField.value <= 0 ||
|
|
delayField.value < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void ApplyConfigToUI(RenderConfig config)
|
|
{
|
|
var widthField = _root.Q<IntegerField>("RenderWidth");
|
|
var heightField = _root.Q<IntegerField>("RenderHeight");
|
|
var renderFpsField = _root.Q<DoubleField>("RenderFPS");
|
|
var videoFpsField = _root.Q<DoubleField>("VideoFPS");
|
|
var delayField = _root.Q<DoubleField>("DelayAfterEnd");
|
|
var showJudgementText = _root.Q<Toggle>("ShowJudgementText");
|
|
|
|
if (widthField != null) widthField.value = config.RenderWidth;
|
|
if (heightField != null) heightField.value = config.RenderHeight;
|
|
if (renderFpsField != null) renderFpsField.value = config.RenderFPS;
|
|
if (videoFpsField != null) videoFpsField.value = config.VideoFPS;
|
|
if (delayField != null) delayField.value = config.DelayAfterEnd;
|
|
if (showJudgementText != null) showJudgementText.value = config.ShowJudgementText;
|
|
|
|
RenderManager.Instance.Configuration = config;
|
|
}
|
|
|
|
private void OpenPresetConfig()
|
|
{
|
|
if (_presetConfigContainer != null && _root.Contains(_presetConfigContainer))
|
|
{
|
|
_root.Remove(_presetConfigContainer);
|
|
}
|
|
|
|
_presetConfigContainer = new VisualElement
|
|
{
|
|
name = "PresetSettingsContainer",
|
|
style =
|
|
{
|
|
position = Position.Absolute,
|
|
width = Length.Percent(100),
|
|
height = Length.Percent(100),
|
|
left = 0,
|
|
top = 0,
|
|
backgroundColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f, 1.0f)),
|
|
justifyContent = Justify.Center,
|
|
alignItems = Align.Center
|
|
}
|
|
};
|
|
|
|
// Title Row
|
|
var titleRow = CreateRow();
|
|
titleRow.style.height = StyleKeyword.Auto;
|
|
titleRow.style.minHeight = StyleKeyword.Null;
|
|
titleRow.style.maxHeight = StyleKeyword.Null;
|
|
var title = new Label("Preset Settings")
|
|
{
|
|
style =
|
|
{
|
|
fontSize = 36,
|
|
unityFontStyleAndWeight = FontStyle.Bold,
|
|
color = new StyleColor(Color.white),
|
|
unityTextAlign = TextAnchor.MiddleCenter,
|
|
width = 820,
|
|
height = StyleKeyword.Auto
|
|
}
|
|
};
|
|
titleRow.Add(title);
|
|
_presetConfigContainer.Add(titleRow);
|
|
|
|
// ScrollView for Preset List
|
|
var scrollView = new ScrollView
|
|
{
|
|
style =
|
|
{
|
|
width = 820,
|
|
height = 380,
|
|
backgroundColor = new StyleColor(new Color(0.12f, 0.12f, 0.12f, 0.5f)),
|
|
borderTopWidth = 1,
|
|
borderBottomWidth = 1,
|
|
borderLeftWidth = 1,
|
|
borderRightWidth = 1,
|
|
borderTopColor = new StyleColor(new Color(0.25f, 0.25f, 0.25f, 0.8f)),
|
|
borderBottomColor = new StyleColor(new Color(0.25f, 0.25f, 0.25f, 0.8f)),
|
|
borderLeftColor = new StyleColor(new Color(0.25f, 0.25f, 0.25f, 0.8f)),
|
|
borderRightColor = new StyleColor(new Color(0.25f, 0.25f, 0.25f, 0.8f)),
|
|
borderTopLeftRadius = 5,
|
|
borderTopRightRadius = 5,
|
|
borderBottomLeftRadius = 5,
|
|
borderBottomRightRadius = 5,
|
|
paddingBottom = 10,
|
|
paddingTop = 10,
|
|
paddingLeft = 10,
|
|
paddingRight = 10,
|
|
marginBottom = 20
|
|
}
|
|
};
|
|
_presetConfigContainer.Add(scrollView);
|
|
|
|
// Style the vertical scrollbar
|
|
scrollView.verticalScroller.style.width = 10f;
|
|
|
|
var tracker = scrollView.verticalScroller.Q<VisualElement>("unity-tracker");
|
|
if (tracker != null)
|
|
{
|
|
tracker.style.backgroundColor = new StyleColor(new Color(0.08f, 0.08f, 0.08f, 0.3f));
|
|
tracker.style.borderTopWidth = 0f;
|
|
tracker.style.borderBottomWidth = 0f;
|
|
tracker.style.borderLeftWidth = 0f;
|
|
tracker.style.borderRightWidth = 0f;
|
|
}
|
|
|
|
var dragger = scrollView.verticalScroller.Q<VisualElement>("unity-dragger");
|
|
if (dragger != null)
|
|
{
|
|
dragger.style.backgroundColor = new StyleColor(new Color(0.25f, 0.25f, 0.25f, 0.8f));
|
|
dragger.style.borderTopLeftRadius = 4f;
|
|
dragger.style.borderTopRightRadius = 4f;
|
|
dragger.style.borderBottomLeftRadius = 4f;
|
|
dragger.style.borderBottomRightRadius = 4f;
|
|
dragger.style.borderTopWidth = 0f;
|
|
dragger.style.borderBottomWidth = 0f;
|
|
dragger.style.borderLeftWidth = 0f;
|
|
dragger.style.borderRightWidth = 0f;
|
|
}
|
|
|
|
var lowButton = scrollView.verticalScroller.Q<VisualElement>("unity-low-button");
|
|
if (lowButton != null)
|
|
{
|
|
lowButton.style.display = DisplayStyle.None;
|
|
}
|
|
|
|
var highButton = scrollView.verticalScroller.Q<VisualElement>("unity-high-button");
|
|
if (highButton != null)
|
|
{
|
|
highButton.style.display = DisplayStyle.None;
|
|
}
|
|
|
|
RefreshPresets();
|
|
|
|
// Save row: Label + TextField + Save Button
|
|
var saveRow = CreateRow();
|
|
saveRow.style.marginBottom = 20;
|
|
|
|
var nameLabelLabel = CreateFormLabel("New Preset Name");
|
|
nameLabelLabel.style.marginRight = 10;
|
|
|
|
var nameField = new TextField
|
|
{
|
|
label = "", name = "NewPresetName",
|
|
style =
|
|
{
|
|
width = 400,
|
|
marginRight = 20
|
|
}
|
|
};
|
|
|
|
// Match existing form input field style
|
|
var textInput = nameField.Q("unity-text-input");
|
|
if (textInput != null)
|
|
{
|
|
textInput.style.backgroundColor = new StyleColor(new Color(0.1f, 0.1f, 0.1f, 1.0f));
|
|
textInput.style.borderTopLeftRadius = 6f;
|
|
textInput.style.borderTopRightRadius = 6f;
|
|
textInput.style.borderBottomLeftRadius = 6f;
|
|
textInput.style.borderBottomRightRadius = 6f;
|
|
textInput.style.borderTopWidth = 1f;
|
|
textInput.style.borderBottomWidth = 1f;
|
|
textInput.style.borderLeftWidth = 1f;
|
|
textInput.style.borderRightWidth = 1f;
|
|
textInput.style.borderTopColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
textInput.style.borderBottomColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
textInput.style.borderLeftColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
textInput.style.borderRightColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
textInput.style.paddingLeft = 8f;
|
|
textInput.style.color = new StyleColor(Color.white);
|
|
}
|
|
else
|
|
{
|
|
nameField.style.backgroundColor = new StyleColor(new Color(0.1f, 0.1f, 0.1f, 1.0f));
|
|
nameField.style.borderTopLeftRadius = 6f;
|
|
nameField.style.borderTopRightRadius = 6f;
|
|
nameField.style.borderBottomLeftRadius = 6f;
|
|
nameField.style.borderBottomRightRadius = 6f;
|
|
}
|
|
|
|
var saveBtn = new Button { text = "Save" };
|
|
|
|
saveBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
string presetName = nameField.value;
|
|
if (string.IsNullOrWhiteSpace(presetName))
|
|
{
|
|
CommonWarningDialogUI.ShowDialog(
|
|
"Save Failed",
|
|
"Please enter a preset name.",
|
|
new DialogButton("OK", () => {})
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!ValidateInputs())
|
|
{
|
|
CommonWarningDialogUI.ShowDialog(
|
|
"Save Failed",
|
|
"Cannot save preset because required input values are empty or invalid.",
|
|
new DialogButton("OK", () => {})
|
|
);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var widthField = _root.Q<IntegerField>("RenderWidth");
|
|
var heightField = _root.Q<IntegerField>("RenderHeight");
|
|
var renderFpsField = _root.Q<DoubleField>("RenderFPS");
|
|
var videoFpsField = _root.Q<DoubleField>("VideoFPS");
|
|
var delayField = _root.Q<DoubleField>("DelayAfterEnd");
|
|
var showJudgementText = _root.Q<Toggle>("ShowJudgementText");
|
|
|
|
var currentConfig = new RenderConfig
|
|
{
|
|
RenderWidth = widthField.value,
|
|
RenderHeight = heightField.value,
|
|
RenderFPS = renderFpsField.value,
|
|
VideoFPS = videoFpsField.value,
|
|
DelayAfterEnd = delayField.value,
|
|
ShowJudgementText = showJudgementText?.value ?? true,
|
|
VideoConfig = RenderManager.Instance.Configuration.VideoConfig,
|
|
AudioConfig = RenderManager.Instance.Configuration.AudioConfig,
|
|
AdvancedConfig = RenderManager.Instance.Configuration.AdvancedConfig
|
|
};
|
|
|
|
PresetManager.SavePreset(presetName, currentConfig);
|
|
nameField.value = string.Empty;
|
|
RefreshPresets();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CommonWarningDialogUI.ShowDialog(
|
|
"Save Failed",
|
|
$"An error occurred while saving the preset: {ex.Message}",
|
|
new DialogButton("OK", () => {})
|
|
);
|
|
}
|
|
});
|
|
|
|
saveRow.Add(nameLabelLabel);
|
|
saveRow.Add(nameField);
|
|
saveRow.Add(saveBtn);
|
|
_presetConfigContainer.Add(saveRow);
|
|
|
|
// Bottom controls: Close Button
|
|
var closeRow = CreateRow();
|
|
|
|
var closeBtn = new Button { text = "Close" };
|
|
|
|
closeBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ToggleScreens(false);
|
|
_root.Remove(_presetConfigContainer);
|
|
_presetConfigContainer = null;
|
|
});
|
|
closeRow.Add(closeBtn);
|
|
_presetConfigContainer.Add(closeRow);
|
|
|
|
_root.Add(_presetConfigContainer);
|
|
ToggleScreens(true);
|
|
return;
|
|
|
|
// Refresh Preset List
|
|
void RefreshPresets()
|
|
{
|
|
scrollView.Clear();
|
|
var presets = PresetManager.GetPresets();
|
|
if (presets.Count == 0)
|
|
{
|
|
var emptyRow = CreateRow();
|
|
emptyRow.style.height = StyleKeyword.Auto;
|
|
emptyRow.style.minHeight = StyleKeyword.Null;
|
|
emptyRow.style.maxHeight = StyleKeyword.Null;
|
|
|
|
var emptyLabel = new Label("No saved presets.") { style = { color = new StyleColor(Color.gray), unityTextAlign = TextAnchor.MiddleCenter, marginTop = 80, fontSize = 20 } };
|
|
emptyRow.Add(emptyLabel);
|
|
scrollView.Add(emptyRow);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < presets.Count; i++)
|
|
{
|
|
var preset = presets[i];
|
|
var itemRow = new VisualElement
|
|
{
|
|
style =
|
|
{
|
|
flexDirection = FlexDirection.Row,
|
|
justifyContent = Justify.SpaceBetween,
|
|
alignItems = Align.Center,
|
|
paddingBottom = 5,
|
|
paddingTop = 5,
|
|
borderBottomWidth = i == presets.Count - 1 ? 0 : 1,
|
|
borderBottomColor = new StyleColor(new Color(0.2f, 0.2f, 0.2f, 0.8f))
|
|
}
|
|
};
|
|
itemRow.AddToClassList("ui-row");
|
|
|
|
var nameLabel = new Label(preset.Name) { style = { color = new StyleColor(Color.white), fontSize = 30, flexGrow = 1 } };
|
|
|
|
var btnContainer = new VisualElement { style = { flexDirection = FlexDirection.Row } };
|
|
|
|
var loadBtn = new Button
|
|
{
|
|
text = "Load",
|
|
style =
|
|
{
|
|
marginRight = 10,
|
|
width = 180f,
|
|
marginTop = 0f,
|
|
marginBottom = 0f,
|
|
backgroundColor = new StyleColor(new Color(0.18f, 0.18f, 0.22f, 1.0f)),
|
|
borderTopColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f)),
|
|
borderBottomColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f)),
|
|
borderLeftColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f)),
|
|
borderRightColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f)),
|
|
borderTopWidth = 1f,
|
|
borderBottomWidth = 1f,
|
|
borderLeftWidth = 1f,
|
|
borderRightWidth = 1f,
|
|
borderTopLeftRadius = 8f,
|
|
borderTopRightRadius = 8f,
|
|
borderBottomLeftRadius = 8f,
|
|
borderBottomRightRadius = 8f,
|
|
paddingLeft = 10f,
|
|
paddingRight = 10f,
|
|
paddingTop = 10f,
|
|
paddingBottom = 10f,
|
|
fontSize = 30f,
|
|
height = 80f,
|
|
color = new StyleColor(Color.white)
|
|
}
|
|
};
|
|
|
|
loadBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ApplyConfigToUI(preset.Config);
|
|
ToggleScreens(false);
|
|
_root.Remove(_presetConfigContainer);
|
|
_presetConfigContainer = null;
|
|
});
|
|
|
|
var deleteBtn = new Button
|
|
{
|
|
text = "Delete",
|
|
style =
|
|
{
|
|
width = 120f,
|
|
marginTop = 0f,
|
|
marginBottom = 0f,
|
|
backgroundColor = new StyleColor(new Color(0.25f, 0.12f, 0.12f, 1.0f)),
|
|
borderTopColor = new StyleColor(new Color(0.45f, 0.2f, 0.2f, 0.8f)),
|
|
borderBottomColor = new StyleColor(new Color(0.45f, 0.2f, 0.2f, 0.8f)),
|
|
borderLeftColor = new StyleColor(new Color(0.45f, 0.2f, 0.2f, 0.8f)),
|
|
borderRightColor = new StyleColor(new Color(0.45f, 0.2f, 0.2f, 0.8f)),
|
|
borderTopWidth = 1f,
|
|
borderBottomWidth = 1f,
|
|
borderLeftWidth = 1f,
|
|
borderRightWidth = 1f,
|
|
borderTopLeftRadius = 8f,
|
|
borderTopRightRadius = 8f,
|
|
borderBottomLeftRadius = 8f,
|
|
borderBottomRightRadius = 8f,
|
|
paddingLeft = 10f,
|
|
paddingRight = 10f,
|
|
paddingTop = 10f,
|
|
paddingBottom = 10f,
|
|
fontSize = 30f,
|
|
height = 80f,
|
|
color = new StyleColor(Color.white)
|
|
}
|
|
};
|
|
|
|
deleteBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
CommonWarningDialogUI.ShowDialog("Delete Preset", $"Are you sure you want to delete the preset '{preset.Name}'?", new DialogButton("Yes", () =>
|
|
{
|
|
PresetManager.DeletePreset(preset.Name);
|
|
RefreshPresets();
|
|
}), new DialogButton("No", () => { }));
|
|
});
|
|
|
|
btnContainer.Add(loadBtn);
|
|
btnContainer.Add(deleteBtn);
|
|
|
|
itemRow.Add(nameLabel);
|
|
itemRow.Add(btnContainer);
|
|
|
|
scrollView.Add(itemRow);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ToggleScreens(bool showPreset)
|
|
{
|
|
if (_root.childCount > 0)
|
|
{
|
|
_root[0].style.display = showPreset ? DisplayStyle.None : DisplayStyle.Flex;
|
|
}
|
|
_presetConfigContainer.style.display = showPreset ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
}
|
|
|
|
private VisualElement CreateRow()
|
|
{
|
|
var row = new VisualElement
|
|
{
|
|
style =
|
|
{
|
|
flexDirection = FlexDirection.Row,
|
|
height = 130,
|
|
maxHeight = 130,
|
|
minHeight = 80,
|
|
justifyContent = Justify.Center,
|
|
alignItems = Align.Center
|
|
}
|
|
};
|
|
row.AddToClassList("ui-row");
|
|
return row;
|
|
}
|
|
|
|
private Label CreateFormLabel(string text)
|
|
{
|
|
var label = new Label(text)
|
|
{
|
|
style =
|
|
{
|
|
unityTextAlign = TextAnchor.MiddleRight,
|
|
color = new StyleColor(Color.white)
|
|
}
|
|
};
|
|
return label;
|
|
}
|
|
|
|
private static void OnLinkTagClicked(PointerUpLinkTagEvent evt)
|
|
{
|
|
switch (evt.linkID)
|
|
{
|
|
case "open_git":
|
|
Application.OpenURL("https://git.yunizun.org/ADOFAIEditorForum/ADOFAIRenderer");
|
|
break;
|
|
default:
|
|
Melon<RenderMod>.Logger.Msg($"Clicked Link: {evt.linkID}");
|
|
break;
|
|
}
|
|
}
|
|
} |