SHA256
Add Basic Reworked UI
This commit is contained in:
13 files changed
+218
-406
No files matched your search
@@ -1,284 +0,0 @@
|
||||
using System.Linq;
|
||||
using ADOFAIRenderer.Core;
|
||||
using ADOFAIRenderer.Util.FFmpeg;
|
||||
using MelonLoader;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ADOFAIRenderer.UI
|
||||
{
|
||||
public class RenderButton : MonoBehaviour
|
||||
{
|
||||
[Header("Input Fields")]
|
||||
[SerializeField] private TMP_InputField targetFPSInput = null!;
|
||||
[SerializeField] private TMP_InputField videoFPSInput = null!;
|
||||
[SerializeField] private TMP_InputField renderWidthInput = null!;
|
||||
[SerializeField] private TMP_InputField renderHeightInput = null!;
|
||||
[SerializeField] private TMP_InputField waitAfterEndInput = null!;
|
||||
[SerializeField] private TMP_InputField randomSeedInput = null!;
|
||||
|
||||
[Header("Dropdown")]
|
||||
[SerializeField] private TMP_Dropdown codecDropdown = null!;
|
||||
[SerializeField] private TMP_Dropdown bitrateModeDropdown = null!;
|
||||
|
||||
[Header("Toggle")]
|
||||
[SerializeField] private Toggle showHitTextToggle = null!;
|
||||
|
||||
[Header("Button")]
|
||||
[SerializeField] private Button advancedSettingsButton = null!;
|
||||
|
||||
[Header("Bitrate Dynamic UI Container")]
|
||||
[SerializeField] private RectTransform bitrateContainer = null!;
|
||||
|
||||
// Dynamically instantiated inputs
|
||||
private TMP_InputField _bitrateInput = null!;
|
||||
private TMP_InputField _maxBitrateInput = null!;
|
||||
private TMP_InputField _qpInput = null!;
|
||||
|
||||
private Button _buttonComponent = null!;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_buttonComponent = GetComponent<Button>();
|
||||
_buttonComponent.onClick.AddListener(OnClick);
|
||||
advancedSettingsButton.onClick.AddListener(AdvancedSettingsClick);
|
||||
|
||||
RenderMod.FixEventSystem();
|
||||
|
||||
if (codecDropdown != null)
|
||||
{
|
||||
codecDropdown.ClearOptions();
|
||||
var options = System.Enum.GetNames(typeof(Codec)).ToList();
|
||||
codecDropdown.AddOptions(options);
|
||||
}
|
||||
|
||||
// 1. Initialize Bitrate Mode Dropdown
|
||||
if (bitrateModeDropdown != null)
|
||||
{
|
||||
bitrateModeDropdown.ClearOptions();
|
||||
var modeOptions = System.Enum.GetNames(typeof(RateControlMode)).ToList();
|
||||
bitrateModeDropdown.AddOptions(modeOptions);
|
||||
bitrateModeDropdown.onValueChanged.AddListener(delegate { UpdateUIByMode(); });
|
||||
}
|
||||
|
||||
// 2. Dynamically instantiate input fields using targetFPSInput as a styling template
|
||||
if (bitrateContainer != null && targetFPSInput != null)
|
||||
{
|
||||
// Instantiate bitrateInput
|
||||
var bitrateInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
|
||||
bitrateInputObj.name = "Dynamic_BitrateInput";
|
||||
_bitrateInput = bitrateInputObj.GetComponent<TMP_InputField>();
|
||||
_bitrateInput.text = "15000";
|
||||
SetPlaceholderText(_bitrateInput, "비트레이트 (kbps)");
|
||||
|
||||
// Instantiate maxBitrateInput
|
||||
var maxBitrateInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
|
||||
maxBitrateInputObj.name = "Dynamic_MaxBitrateInput";
|
||||
_maxBitrateInput = maxBitrateInputObj.GetComponent<TMP_InputField>();
|
||||
_maxBitrateInput.text = "30000";
|
||||
SetPlaceholderText(_maxBitrateInput, "최대 비트레이트 (kbps)");
|
||||
|
||||
// Instantiate qpInput
|
||||
var qpInputObj = Instantiate(targetFPSInput.gameObject, bitrateContainer);
|
||||
qpInputObj.name = "Dynamic_QPInput";
|
||||
_qpInput = qpInputObj.GetComponent<TMP_InputField>();
|
||||
_qpInput.text = "18";
|
||||
SetPlaceholderText(_qpInput, "QP (0-51) / CRF");
|
||||
}
|
||||
|
||||
// 3. Perform initial UI state updates based on default mode
|
||||
UpdateUIByMode();
|
||||
}
|
||||
|
||||
private void SetPlaceholderText(TMP_InputField inputField, string text)
|
||||
{
|
||||
if (inputField.placeholder == null) return;
|
||||
var tmpText = inputField.placeholder.GetComponent<TMP_Text>();
|
||||
|
||||
if (tmpText)
|
||||
{
|
||||
tmpText.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUIByMode()
|
||||
{
|
||||
if (bitrateModeDropdown == null) return;
|
||||
if (_bitrateInput == null || _maxBitrateInput == null || _qpInput == null) return;
|
||||
|
||||
var selectedMode = (RateControlMode)bitrateModeDropdown.value;
|
||||
|
||||
switch (selectedMode)
|
||||
{
|
||||
case RateControlMode.CBR:
|
||||
_bitrateInput.gameObject.SetActive(true);
|
||||
_maxBitrateInput.gameObject.SetActive(false);
|
||||
_qpInput.gameObject.SetActive(false);
|
||||
break;
|
||||
case RateControlMode.VBR:
|
||||
_bitrateInput.gameObject.SetActive(true);
|
||||
_maxBitrateInput.gameObject.SetActive(true);
|
||||
_qpInput.gameObject.SetActive(false);
|
||||
break;
|
||||
case RateControlMode.CQP:
|
||||
_bitrateInput.gameObject.SetActive(false);
|
||||
_maxBitrateInput.gameObject.SetActive(false);
|
||||
_qpInput.gameObject.SetActive(true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bitrateContainer != null)
|
||||
{
|
||||
Canvas.ForceUpdateCanvases();
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(bitrateContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClick()
|
||||
{
|
||||
if (IsAnyFieldEmpty()) return;
|
||||
if (RenderManager.IsActive) return;
|
||||
|
||||
if (!double.TryParse(targetFPSInput.text, out var targetFPS) || targetFPS <= 0)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Target FPS 값입니다. 0보다 큰 숫자여야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (!double.TryParse(videoFPSInput.text, out var videoFPS) || videoFPS <= 0)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Video FPS 값입니다. 0보다 큰 숫자여야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (!int.TryParse(renderWidthInput.text, out var renderWidth) || renderWidth <= 0 || renderWidth > SystemInfo.maxTextureSize)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error($"올바르지 않은 Render Width 값입니다. 1에서 {SystemInfo.maxTextureSize} 사이의 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (!int.TryParse(renderHeightInput.text, out var renderHeight) || renderHeight <= 0 || renderHeight > SystemInfo.maxTextureSize)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error($"올바르지 않은 Render Height 값입니다. 1에서 {SystemInfo.maxTextureSize} 사이의 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bitrateMode = RateControlMode.CBR;
|
||||
if (bitrateModeDropdown != null)
|
||||
{
|
||||
bitrateMode = (RateControlMode)bitrateModeDropdown.value;
|
||||
}
|
||||
|
||||
var bitrate = 0;
|
||||
var maxBitrate = 0;
|
||||
var qp = 18;
|
||||
|
||||
if (bitrateMode == RateControlMode.CBR || bitrateMode == RateControlMode.VBR)
|
||||
{
|
||||
if (!int.TryParse(_bitrateInput.text, out bitrate) || bitrate <= 0 || bitrate > 1_000_000)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Bitrate 값입니다. 1에서 1,000,000 (kbps) 사이의 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitrateMode == RateControlMode.VBR)
|
||||
{
|
||||
if (!int.TryParse(_maxBitrateInput.text, out maxBitrate) || maxBitrate <= 0 || maxBitrate > 1_000_000)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Max Bitrate 값입니다. 1에서 1,000,000 (kbps) 사이의 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (maxBitrate < bitrate)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("Max Bitrate는 Target Bitrate보다 크거나 같아야 합니다.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitrateMode == RateControlMode.CQP)
|
||||
{
|
||||
if (!int.TryParse(_qpInput.text, out qp) || qp < 0 || qp > 51)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 QP 값입니다. 0에서 51 사이의 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!double.TryParse(waitAfterEndInput.text, out var waitAfterEnd) || waitAfterEnd < 0)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Wait After End 값입니다. 0 이상의 숫자여야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
int? parsedRandomSeed = null;
|
||||
var randomSeedText = randomSeedInput.text.Truncate(int.MaxValue);
|
||||
if (!randomSeedText.IsNullOrEmpty())
|
||||
{
|
||||
if (!int.TryParse(randomSeedText, out var seedVal))
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Random Seed 값입니다. 비어 있거나 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
parsedRandomSeed = seedVal;
|
||||
}
|
||||
|
||||
if (RenderManager.Instance)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Msg($"레벨 로드: {RenderManager.Instance.levelPath}");
|
||||
}
|
||||
|
||||
var selectedCodecIndex = codecDropdown.value;
|
||||
var selectedCodecName = codecDropdown.options[selectedCodecIndex].text;
|
||||
|
||||
var showHitText = showHitTextToggle != null && showHitTextToggle.isOn;
|
||||
|
||||
if (RenderManager.Instance)
|
||||
{
|
||||
RenderManager.Instance.Configuration = new RenderManager.RenderSettings(
|
||||
targetFPS,
|
||||
videoFPS,
|
||||
renderWidth,
|
||||
renderHeight,
|
||||
bitrateMode,
|
||||
bitrate,
|
||||
maxBitrate,
|
||||
qp,
|
||||
selectedCodecName,
|
||||
waitAfterEnd,
|
||||
showHitText,
|
||||
parsedRandomSeed
|
||||
);
|
||||
}
|
||||
|
||||
RenderManager.Instance!.SetupCaptureDirectory();
|
||||
RenderManager.Instance.SetupGameScene();
|
||||
}
|
||||
|
||||
public void AdvancedSettingsClick()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private bool IsAnyFieldEmpty()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(targetFPSInput.text)) return true;
|
||||
if (string.IsNullOrWhiteSpace(videoFPSInput.text)) return true;
|
||||
if (string.IsNullOrWhiteSpace(renderWidthInput.text)) return true;
|
||||
if (string.IsNullOrWhiteSpace(renderHeightInput.text)) return true;
|
||||
if (string.IsNullOrWhiteSpace(waitAfterEndInput.text)) return true;
|
||||
|
||||
if (_bitrateInput != null && _bitrateInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_bitrateInput.text)) return true;
|
||||
if (_maxBitrateInput != null && _maxBitrateInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_maxBitrateInput.text)) return true;
|
||||
if (_qpInput != null && _qpInput.gameObject.activeSelf && string.IsNullOrWhiteSpace(_qpInput.text)) return true;
|
||||
|
||||
return codecDropdown.value < 0 || (bitrateModeDropdown != null && bitrateModeDropdown.value < 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
+134
-2
@@ -1,12 +1,144 @@
|
||||
using System.Text;
|
||||
using ADOFAIRenderer.Core;
|
||||
using ADOFAIRenderer.Data;
|
||||
using ADOFAIRenderer.Util.FFmpeg;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace ADOFAIRenderer.Core
|
||||
namespace ADOFAIRenderer.UI
|
||||
{
|
||||
public class RenderConfigUIManager : MonoBehaviour
|
||||
{
|
||||
void Awake()
|
||||
[SerializeField]
|
||||
private UIDocument uiDocument = null!;
|
||||
|
||||
private VisualElement _root = null!;
|
||||
private IntegerInputValidator _intValidator = null!;
|
||||
private DoubleInputValidator _doubleValidator = null!;
|
||||
private EnumDropdownBinder<Codec> _codecBinder = null!;
|
||||
|
||||
private Button _goRenderButton = null!;
|
||||
private Button _encodingConfigButton = null!;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_root = uiDocument.rootVisualElement;
|
||||
RenderManager.Instance.Configuration = new RenderConfig();
|
||||
|
||||
_intValidator = new IntegerInputValidator(_root);
|
||||
_doubleValidator = new DoubleInputValidator(_root);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Melon<RenderMod>.Logger.Msg("[UI] 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("DelayAfterLevelEnd", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0, 86400));
|
||||
|
||||
_intValidator.SetupValidation();
|
||||
_doubleValidator.SetupValidation();
|
||||
Melon<RenderMod>.Logger.Msg("[UI] Validator 설정 완료");
|
||||
|
||||
// Init Enum Binders & Event Listeners
|
||||
_codecBinder = new EnumDropdownBinder<Codec>(_root, "CodecDropdown", Codec.H264);
|
||||
Melon<RenderMod>.Logger.Msg("[UI] CodecBinder 생성 완료");
|
||||
_codecBinder.OnValueChanged(value => RenderManager.Instance.Configuration.Codec = value);
|
||||
Melon<RenderMod>.Logger.Msg("[UI] CodecBinder 콜백 등록 완료");
|
||||
|
||||
// Bind Data
|
||||
_root.dataSource = RenderManager.Instance.Configuration;
|
||||
|
||||
// Attach Button Event Listener
|
||||
_goRenderButton = _root.Q<Button>("GoRenderButton");
|
||||
if (_goRenderButton != null)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Msg($"[UI] GoRenderButton 발견: pickingMode={_goRenderButton.pickingMode}, enabledSelf={_goRenderButton.enabledSelf}");
|
||||
_goRenderButton.RegisterCallback<ClickEvent>(_ => TryGoRender());
|
||||
}
|
||||
else
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("Button 'GoRenderButton'을(를) 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
_encodingConfigButton = _root.Q<Button>("EncodingSettingsButton");
|
||||
if (_encodingConfigButton != null)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Msg($"[UI] EncodingSettingsButton 발견: pickingMode={_encodingConfigButton.pickingMode}, enabledSelf={_encodingConfigButton.enabledSelf}");
|
||||
_encodingConfigButton.RegisterCallback<ClickEvent>(_ => OpenEncodingConfig());
|
||||
}
|
||||
else
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("Button 'EncodingSettingsButton'을(를) 찾을 수 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenEncodingConfig()
|
||||
{
|
||||
Melon<RenderMod>.Logger.Msg("[UI] OpenEncodingConfig 호출됨");
|
||||
RenderManager.Instance.Configuration.EncodingConfig ??= GenerateEncodingConfig(_codecBinder.Value);
|
||||
}
|
||||
|
||||
private void TryGoRender()
|
||||
{
|
||||
Melon<RenderMod>.Logger.Msg("[UI] TryGoRender 호출됨");
|
||||
|
||||
var widthField = _root.Q<IntegerField>("RenderWidth");
|
||||
var heightField = _root.Q<IntegerField>("RenderHeight");
|
||||
var randomSeedField = _root.Q<IntegerField>("RandomSeed");
|
||||
var renderFpsField = _root.Q<DoubleField>("RenderFPS");
|
||||
var videoFpsField = _root.Q<DoubleField>("VideoFPS");
|
||||
var delayField = _root.Q<DoubleField>("DelayAfterLevelEnd");
|
||||
|
||||
var showJudgementText = _root.Q<Toggle>("ShowJudgementText");
|
||||
var processAudio = _root.Q<Toggle>("ProcessAudioToggle");
|
||||
|
||||
if (string.IsNullOrEmpty(widthField.text) ||
|
||||
string.IsNullOrEmpty(heightField.text) ||
|
||||
string.IsNullOrEmpty(renderFpsField.text) ||
|
||||
string.IsNullOrEmpty(videoFpsField.text) ||
|
||||
string.IsNullOrEmpty(delayField.text))
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("필수 입력 값이 비어 있습니다. 모든 설정을 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.DelayAfterLevelEnd = delayField.value;
|
||||
|
||||
config.ShowJudgementText = showJudgementText.value;
|
||||
config.ProcessAudio = processAudio.value;
|
||||
|
||||
if (!string.IsNullOrEmpty(randomSeedField.text))
|
||||
config.RandomSeed = randomSeedField.value;
|
||||
|
||||
RenderManager.Instance.Configuration.EncodingConfig ??= GenerateEncodingConfig(_codecBinder.Value);
|
||||
|
||||
RenderManager.Instance.SetupCaptureDirectory();
|
||||
RenderManager.Instance.SetupGameScene();
|
||||
}
|
||||
|
||||
private EncodingConfig GenerateEncodingConfig(Codec codec)
|
||||
{
|
||||
return new EncodingConfig
|
||||
{
|
||||
RateControlMode = RateControlMode.CBR,
|
||||
Bitrate = 30000, MaxBitrate = 50000,
|
||||
QP = 18
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user