SHA256
858 lines
38 KiB
C#
858 lines
38 KiB
C#
using ADOFAIRenderer.Core;
|
|
using ADOFAIRenderer.Data;
|
|
using ADOFAIRenderer.Util.FFmpeg;
|
|
using MelonLoader;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace ADOFAIRenderer.UI
|
|
{
|
|
public class RenderConfigUIManager : MonoBehaviour
|
|
{
|
|
[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 VisualElement _encodingConfigContainer = 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("DelayAfterLevelEnd", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0, 86400));
|
|
|
|
_intValidator.SetupValidation();
|
|
_doubleValidator.SetupValidation();
|
|
|
|
// Init Enum Binders & Event Listeners
|
|
_codecBinder = new EnumDropdownBinder<Codec>(_root, "CodecDropdown", Codec.H264);
|
|
_codecBinder.OnValueChanged(value => RenderManager.Instance.Configuration.Codec = value);
|
|
|
|
// Attach Button Event Listener
|
|
_goRenderButton = _root.Q<Button>("GoRenderButton");
|
|
_goRenderButton?.RegisterCallback<ClickEvent>(_ => TryGoRender());
|
|
|
|
_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'을(를) 찾을 수 없습니다.");
|
|
}
|
|
|
|
var presetSettingsButton = _root.Q<Button>("PresetSettingsButton");
|
|
if (presetSettingsButton != null)
|
|
{
|
|
Melon<RenderMod>.Logger.Msg($"[UI] PresetSettingsButton 발견: pickingMode={presetSettingsButton.pickingMode}, enabledSelf={presetSettingsButton.enabledSelf}");
|
|
presetSettingsButton.RegisterCallback<ClickEvent>(_ => OpenPresetConfig());
|
|
}
|
|
else
|
|
{
|
|
Melon<RenderMod>.Logger.Error("Button 'PresetSettingsButton'을(를) 찾을 수 없습니다.");
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (Input.GetKeyDown(KeyCode.Escape))
|
|
{
|
|
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
|
}
|
|
}
|
|
|
|
private void OpenEncodingConfig()
|
|
{
|
|
var config = RenderManager.Instance.Configuration;
|
|
config.EncodingConfig ??= GenerateEncodingConfig();
|
|
|
|
var tempConfig = new EncodingConfig
|
|
{
|
|
RateControlMode = config.EncodingConfig.RateControlMode,
|
|
Bitrate = config.EncodingConfig.Bitrate,
|
|
MaxBitrate = config.EncodingConfig.MaxBitrate,
|
|
QP = config.EncodingConfig.QP,
|
|
KeyframeInterval = config.EncodingConfig.KeyframeInterval,
|
|
ForceSoftwareEncoder = config.EncodingConfig.ForceSoftwareEncoder
|
|
};
|
|
|
|
if (_root.Contains(_encodingConfigContainer))
|
|
{
|
|
_root.Remove(_encodingConfigContainer);
|
|
}
|
|
|
|
// 1. 전체 화면 컨테이너 생성 및 배경 적용
|
|
_encodingConfigContainer = new VisualElement
|
|
{
|
|
name = "EncodingSettingsContainer",
|
|
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. 타이틀 로우
|
|
var titleRow = CreateRow();
|
|
var title = new Label("인코딩 설정")
|
|
{
|
|
style =
|
|
{
|
|
fontSize = 36,
|
|
unityFontStyleAndWeight = FontStyle.Bold,
|
|
color = new StyleColor(Color.white),
|
|
unityTextAlign = TextAnchor.MiddleCenter,
|
|
width = 770, // 360 * 2 + 50 (여백)에 맞춤
|
|
height = StyleKeyword.Auto
|
|
}
|
|
};
|
|
titleRow.Add(title);
|
|
_encodingConfigContainer.Add(titleRow);
|
|
|
|
// 3. 전송률 모드 로우 (라벨 + 드롭다운)
|
|
var modeRow = CreateRow();
|
|
var modeLabel = CreateFormLabel("전송률 제어 모드");
|
|
var modeDropdown = new DropdownField { label = "" };
|
|
foreach (RateControlMode modeVal in System.Enum.GetValues(typeof(RateControlMode)))
|
|
{
|
|
modeDropdown.choices.Add(modeVal.ToString());
|
|
}
|
|
modeDropdown.value = tempConfig.RateControlMode.ToString();
|
|
|
|
modeRow.Add(modeLabel);
|
|
modeRow.Add(modeDropdown);
|
|
_encodingConfigContainer.Add(modeRow);
|
|
|
|
// 4. Bitrate 로우 (라벨 + 입력)
|
|
var bitrateRow = CreateRow();
|
|
var bitrateLabel = CreateFormLabel("대상 비트레이트 (kbps)");
|
|
var bitrateField = new IntegerField { name = "EncodingBitrate", label = "", value = tempConfig.Bitrate };
|
|
bitrateRow.Add(bitrateLabel);
|
|
bitrateRow.Add(bitrateField);
|
|
_encodingConfigContainer.Add(bitrateRow);
|
|
|
|
// 5. Max Bitrate 로우 (라벨 + 입력)
|
|
var maxBitrateRow = CreateRow();
|
|
var maxBitrateLabel = CreateFormLabel("최대 비트레이트 (kbps)");
|
|
var maxBitrateField = new IntegerField { name = "EncodingMaxBitrate", label = "", value = tempConfig.MaxBitrate };
|
|
maxBitrateRow.Add(maxBitrateLabel);
|
|
maxBitrateRow.Add(maxBitrateField);
|
|
_encodingConfigContainer.Add(maxBitrateRow);
|
|
|
|
// 6. QP 로우 (라벨 + 입력)
|
|
var qpRow = CreateRow();
|
|
var qpLabel = CreateFormLabel("QP 품질 값 (0 ~ 51)");
|
|
var qpField = new IntegerField { name = "EncodingQP", label = "", value = tempConfig.QP };
|
|
qpRow.Add(qpLabel);
|
|
qpRow.Add(qpField);
|
|
_encodingConfigContainer.Add(qpRow);
|
|
|
|
// 6.5. 키프레임 간격 로우 (라벨 + 입력)
|
|
var keyframeRow = CreateRow();
|
|
var keyframeLabel = CreateFormLabel("키프레임 간격 (초)");
|
|
var keyframeField = new DoubleField { name = "EncodingKeyframeInterval", label = "", value = tempConfig.KeyframeInterval };
|
|
keyframeRow.Add(keyframeLabel);
|
|
keyframeRow.Add(keyframeField);
|
|
_encodingConfigContainer.Add(keyframeRow);
|
|
|
|
// 6.75. 강제 소프트웨어 인코더 로우 (라벨 + 토글)
|
|
var forceSoftwareRow = CreateRow();
|
|
var forceSoftwareLabel = CreateFormLabel("소프트웨어 인코더");
|
|
var forceSoftwareToggle = new Toggle { name = "ForceSoftwareEncoder", label = "", value = tempConfig.ForceSoftwareEncoder };
|
|
forceSoftwareRow.Add(forceSoftwareLabel);
|
|
forceSoftwareRow.Add(forceSoftwareToggle);
|
|
_encodingConfigContainer.Add(forceSoftwareRow);
|
|
|
|
// 7. QP = 0 (무손실) 경고 라벨 로우 생성 (ui-row 클래스를 상속받아 NanumSquareNeo 폰트가 적용되도록 처리)
|
|
var warningRow = CreateRow();
|
|
warningRow.style.height = StyleKeyword.Auto;
|
|
warningRow.style.maxHeight = StyleKeyword.Null;
|
|
warningRow.style.minHeight = StyleKeyword.Null;
|
|
|
|
var warningLabel = new Label("※ QP 0 (무손실)은 일부 하드웨어/코덱에서 지원되지 않을 수 있으며, 인코딩 실패 시 CPU 모드로 폴백됩니다.")
|
|
{
|
|
style =
|
|
{
|
|
fontSize = 20, // 30px에서 20px로 오버라이드
|
|
color = new StyleColor(new Color(0.9f, 0.6f, 0.2f)),
|
|
whiteSpace = WhiteSpace.Normal,
|
|
width = 720, // 360px에서 720px로 오버라이드
|
|
height = StyleKeyword.Auto,
|
|
unityTextAlign = TextAnchor.MiddleCenter,
|
|
marginTop = 10,
|
|
marginBottom = 10
|
|
}
|
|
};
|
|
|
|
warningRow.Add(warningLabel);
|
|
_encodingConfigContainer.Add(warningRow);
|
|
|
|
// 7.5. 유효성 검사기 설정 (기존에 작성된 validator 재사용 및 이전 값 복원)
|
|
var encodingValidator = new IntegerInputValidator(_encodingConfigContainer);
|
|
encodingValidator
|
|
.AddLimits("EncodingBitrate", new RangeIntegerLimit(100, 1000000))
|
|
.AddLimits("EncodingMaxBitrate", new RangeIntegerLimit(100, 1000000))
|
|
.AddLimits("EncodingQP", new RangeIntegerLimit(0, 51));
|
|
encodingValidator.SetupValidation();
|
|
|
|
var encodingDoubleValidator = new DoubleInputValidator(_encodingConfigContainer);
|
|
encodingDoubleValidator
|
|
.AddLimits("EncodingKeyframeInterval", new PositiveOnlyDoubleLimit(), new RangeDoubleLimit(0.01, 1000.0));
|
|
encodingDoubleValidator.SetupValidation();
|
|
|
|
modeDropdown.RegisterValueChangedCallback(evt => UpdateVisibility(evt.newValue));
|
|
qpField.RegisterValueChangedCallback(evt =>
|
|
{
|
|
warningRow.style.display = (modeDropdown.value == "CQP" && evt.newValue == 0) ? DisplayStyle.Flex : DisplayStyle.None;
|
|
});
|
|
|
|
UpdateVisibility(modeDropdown.value);
|
|
|
|
// 8. 제어 버튼 로우 (취소 / 저장)
|
|
var buttonRow = CreateRow();
|
|
var cancelBtn = new Button { text = "취소" };
|
|
var saveBtn = new Button { text = "저장" };
|
|
|
|
buttonRow.Add(cancelBtn);
|
|
buttonRow.Add(saveBtn);
|
|
_encodingConfigContainer.Add(buttonRow);
|
|
|
|
cancelBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ToggleScreens(false);
|
|
_root.Remove(_encodingConfigContainer);
|
|
_encodingConfigContainer = null!;
|
|
});
|
|
|
|
saveBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
if (System.Enum.TryParse<RateControlMode>(modeDropdown.value, out var mode))
|
|
{
|
|
tempConfig.RateControlMode = mode;
|
|
}
|
|
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.EncodingConfig = tempConfig;
|
|
|
|
ToggleScreens(false);
|
|
_root.Remove(_encodingConfigContainer);
|
|
_encodingConfigContainer = null!;
|
|
});
|
|
|
|
_root.Add(_encodingConfigContainer);
|
|
|
|
// 화면 전환 활성화
|
|
ToggleScreens(true);
|
|
return;
|
|
|
|
// 화면 전환 제어
|
|
void ToggleScreens(bool showEncoding)
|
|
{
|
|
if (_root.childCount > 0)
|
|
{
|
|
_root[0].style.display = showEncoding ? DisplayStyle.None : DisplayStyle.Flex;
|
|
}
|
|
_encodingConfigContainer.style.display = showEncoding ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
|
|
// 가시성 필터링 업데이트 (행 단위로 보임/숨김 제어)
|
|
void UpdateVisibility(string selectedModeStr)
|
|
{
|
|
if (!System.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 TryGoRender()
|
|
{
|
|
if (!ValidateInputs())
|
|
{
|
|
Melon<RenderMod>.Logger.Error("필수 입력 값이 비어 있거나 올바르지 않습니다. 모든 설정을 입력해 주세요.");
|
|
return;
|
|
}
|
|
|
|
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");
|
|
|
|
// 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;
|
|
else
|
|
config.RandomSeed = null;
|
|
|
|
RenderManager.Instance.Configuration.EncodingConfig ??= GenerateEncodingConfig();
|
|
|
|
RenderManager.Instance.SetupCaptureDirectory();
|
|
RenderManager.Instance.SetupGameScene();
|
|
}
|
|
|
|
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>("DelayAfterLevelEnd");
|
|
|
|
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 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 (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.DelayAfterLevelEnd;
|
|
if (showJudgementText != null) showJudgementText.value = config.ShowJudgementText;
|
|
if (processAudio != null) processAudio.value = config.ProcessAudio;
|
|
|
|
if (randomSeedField != null)
|
|
{
|
|
if (config.RandomSeed.HasValue)
|
|
{
|
|
randomSeedField.value = config.RandomSeed.Value;
|
|
}
|
|
else
|
|
{
|
|
randomSeedField.value = 0;
|
|
var textProperty = typeof(IntegerField).GetProperty("text", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
|
|
if (textProperty == null)
|
|
{
|
|
textProperty = typeof(TextInputBaseField<int>).GetProperty("text", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
|
|
}
|
|
textProperty?.SetValue(randomSeedField, string.Empty);
|
|
}
|
|
}
|
|
|
|
_codecBinder.SetValue(config.Codec);
|
|
RenderManager.Instance.Configuration = config;
|
|
}
|
|
|
|
private void OpenPresetConfig()
|
|
{
|
|
if (_presetConfigContainer != null && _root.Contains(_presetConfigContainer))
|
|
{
|
|
_root.Remove(_presetConfigContainer);
|
|
}
|
|
|
|
_presetConfigContainer = new VisualElement { name = "PresetSettingsContainer" };
|
|
_presetConfigContainer.style.position = Position.Absolute;
|
|
_presetConfigContainer.style.width = Length.Percent(100);
|
|
_presetConfigContainer.style.height = Length.Percent(100);
|
|
_presetConfigContainer.style.left = 0;
|
|
_presetConfigContainer.style.top = 0;
|
|
_presetConfigContainer.style.backgroundColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f, 1.0f));
|
|
_presetConfigContainer.style.justifyContent = Justify.Center;
|
|
_presetConfigContainer.style.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("프리셋 설정")
|
|
{
|
|
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;
|
|
}
|
|
|
|
// Refresh Preset List
|
|
System.Action refreshPresets = null!;
|
|
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("저장된 프리셋이 없습니다.")
|
|
{
|
|
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 = "불러오기" };
|
|
loadBtn.style.marginRight = 10;
|
|
loadBtn.style.width = 180f;
|
|
loadBtn.style.marginTop = 0f;
|
|
loadBtn.style.marginBottom = 0f;
|
|
loadBtn.style.backgroundColor = new StyleColor(new Color(0.18f, 0.18f, 0.22f, 1.0f));
|
|
loadBtn.style.borderTopColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
loadBtn.style.borderBottomColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
loadBtn.style.borderLeftColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
loadBtn.style.borderRightColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
|
loadBtn.style.borderTopWidth = 1f;
|
|
loadBtn.style.borderBottomWidth = 1f;
|
|
loadBtn.style.borderLeftWidth = 1f;
|
|
loadBtn.style.borderRightWidth = 1f;
|
|
loadBtn.style.borderTopLeftRadius = 8f;
|
|
loadBtn.style.borderTopRightRadius = 8f;
|
|
loadBtn.style.borderBottomLeftRadius = 8f;
|
|
loadBtn.style.borderBottomRightRadius = 8f;
|
|
loadBtn.style.paddingLeft = 10f;
|
|
loadBtn.style.paddingRight = 10f;
|
|
loadBtn.style.paddingTop = 10f;
|
|
loadBtn.style.paddingBottom = 10f;
|
|
loadBtn.style.fontSize = 30f;
|
|
loadBtn.style.height = 80f;
|
|
loadBtn.style.color = new StyleColor(Color.white);
|
|
|
|
loadBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ApplyConfigToUI(preset.Config);
|
|
ToggleScreens(false);
|
|
_root.Remove(_presetConfigContainer);
|
|
_presetConfigContainer = null;
|
|
});
|
|
|
|
var deleteBtn = new Button { text = "삭제" };
|
|
deleteBtn.style.width = 120f;
|
|
deleteBtn.style.marginTop = 0f;
|
|
deleteBtn.style.marginBottom = 0f;
|
|
deleteBtn.style.backgroundColor = new StyleColor(new Color(0.25f, 0.12f, 0.12f, 1.0f));
|
|
deleteBtn.style.borderTopColor = new StyleColor(new Color(0.45f, 0.2f, 0.2f, 0.8f));
|
|
deleteBtn.style.borderBottomColor = new StyleColor(new Color(0.45f, 0.2f, 0.2f, 0.8f));
|
|
deleteBtn.style.borderLeftColor = new StyleColor(new Color(0.45f, 0.2f, 0.2f, 0.8f));
|
|
deleteBtn.style.borderRightColor = new StyleColor(new Color(0.45f, 0.2f, 0.2f, 0.8f));
|
|
deleteBtn.style.borderTopWidth = 1f;
|
|
deleteBtn.style.borderBottomWidth = 1f;
|
|
deleteBtn.style.borderLeftWidth = 1f;
|
|
deleteBtn.style.borderRightWidth = 1f;
|
|
deleteBtn.style.borderTopLeftRadius = 8f;
|
|
deleteBtn.style.borderTopRightRadius = 8f;
|
|
deleteBtn.style.borderBottomLeftRadius = 8f;
|
|
deleteBtn.style.borderBottomRightRadius = 8f;
|
|
deleteBtn.style.paddingLeft = 10f;
|
|
deleteBtn.style.paddingRight = 10f;
|
|
deleteBtn.style.paddingTop = 10f;
|
|
deleteBtn.style.paddingBottom = 10f;
|
|
deleteBtn.style.fontSize = 30f;
|
|
deleteBtn.style.height = 80f;
|
|
deleteBtn.style.color = new StyleColor(Color.white);
|
|
|
|
deleteBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
CommonWarningDialogUI.ShowDialog(
|
|
"프리셋 삭제",
|
|
$"'{preset.Name}' 프리셋을 정말 삭제하시겠습니까?",
|
|
new DialogButton("예", () =>
|
|
{
|
|
PresetManager.DeletePreset(preset.Name);
|
|
refreshPresets();
|
|
}),
|
|
new DialogButton("아니오", () => {})
|
|
);
|
|
});
|
|
|
|
btnContainer.Add(loadBtn);
|
|
btnContainer.Add(deleteBtn);
|
|
|
|
itemRow.Add(nameLabel);
|
|
itemRow.Add(btnContainer);
|
|
|
|
scrollView.Add(itemRow);
|
|
}
|
|
}
|
|
};
|
|
|
|
refreshPresets();
|
|
|
|
// Save row: Label + TextField + Save Button
|
|
var saveRow = CreateRow();
|
|
saveRow.style.marginBottom = 20;
|
|
|
|
var nameLabelLabel = CreateFormLabel("새 프리셋 이름");
|
|
nameLabelLabel.style.marginRight = 10;
|
|
|
|
var nameField = new TextField { label = "", name = "NewPresetName" };
|
|
nameField.style.width = 400;
|
|
nameField.style.marginRight = 20;
|
|
|
|
// 기존 폼 입력 필드 스타일과 맞춤
|
|
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 = "저장" };
|
|
|
|
saveBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
string presetName = nameField.value;
|
|
if (string.IsNullOrWhiteSpace(presetName))
|
|
{
|
|
CommonWarningDialogUI.ShowDialog(
|
|
"저장 실패",
|
|
"프리셋 이름을 입력해 주세요.",
|
|
new DialogButton("확인", () => {})
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!ValidateInputs())
|
|
{
|
|
CommonWarningDialogUI.ShowDialog(
|
|
"저장 실패",
|
|
"필수 입력 값이 비어 있거나 올바르지 않아 프리셋을 저장할 수 없습니다.",
|
|
new DialogButton("확인", () => {})
|
|
);
|
|
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>("DelayAfterLevelEnd");
|
|
var showJudgementText = _root.Q<Toggle>("ShowJudgementText");
|
|
var processAudio = _root.Q<Toggle>("ProcessAudioToggle");
|
|
var randomSeedField = _root.Q<IntegerField>("RandomSeed");
|
|
|
|
var currentConfig = new RenderConfig
|
|
{
|
|
RenderWidth = widthField.value,
|
|
RenderHeight = heightField.value,
|
|
RenderFPS = renderFpsField.value,
|
|
VideoFPS = videoFpsField.value,
|
|
DelayAfterLevelEnd = delayField.value,
|
|
ShowJudgementText = showJudgementText.value,
|
|
ProcessAudio = processAudio.value,
|
|
Codec = _codecBinder.Value
|
|
};
|
|
|
|
if (!string.IsNullOrEmpty(randomSeedField.text))
|
|
{
|
|
currentConfig.RandomSeed = randomSeedField.value;
|
|
}
|
|
else
|
|
{
|
|
currentConfig.RandomSeed = null;
|
|
}
|
|
|
|
currentConfig.EncodingConfig = RenderManager.Instance.Configuration.EncodingConfig ?? GenerateEncodingConfig();
|
|
|
|
PresetManager.SavePreset(presetName, currentConfig);
|
|
nameField.value = string.Empty;
|
|
refreshPresets();
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
CommonWarningDialogUI.ShowDialog(
|
|
"저장 실패",
|
|
$"프리셋 저장 중 오류가 발생했습니다: {ex.Message}",
|
|
new DialogButton("확인", () => {})
|
|
);
|
|
}
|
|
});
|
|
|
|
saveRow.Add(nameLabelLabel);
|
|
saveRow.Add(nameField);
|
|
saveRow.Add(saveBtn);
|
|
_presetConfigContainer.Add(saveRow);
|
|
|
|
// Bottom controls: Close Button
|
|
var closeRow = CreateRow();
|
|
|
|
var closeBtn = new Button { text = "닫기" };
|
|
|
|
closeBtn.RegisterCallback<ClickEvent>(_ =>
|
|
{
|
|
ToggleScreens(false);
|
|
_root.Remove(_presetConfigContainer);
|
|
_presetConfigContainer = null;
|
|
});
|
|
closeRow.Add(closeBtn);
|
|
_presetConfigContainer.Add(closeRow);
|
|
|
|
_root.Add(_presetConfigContainer);
|
|
ToggleScreens(true);
|
|
|
|
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 EncodingConfig GenerateEncodingConfig()
|
|
{
|
|
return new EncodingConfig
|
|
{
|
|
RateControlMode = RateControlMode.CBR,
|
|
Bitrate = 30000, MaxBitrate = 50000,
|
|
QP = 18, KeyframeInterval = 2.0,
|
|
ForceSoftwareEncoder = false
|
|
};
|
|
}
|
|
}
|
|
} |