SHA256
Compare commits
5
Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
b4c950c9f7
|
||
|
|
723f4446f5
|
||
|
|
6f4c632b41
|
||
|
|
863dcd2357
|
||
|
|
00048b7b06
|
No files matched your search
@@ -5,6 +5,7 @@
|
||||
"ui.render_config.preset_name_placeholder": "New preset name...",
|
||||
"ui.render_config.preset_save": "Save",
|
||||
"ui.render_config.preset_delete": "Delete",
|
||||
"ui.render_config.preset_delete_confirm": "Confirm?",
|
||||
"ui.render_config.preset_overwrite_title": "Overwrite Preset",
|
||||
"ui.render_config.preset_overwrite_desc": "A preset named '{0}' already exists. Do you want to overwrite it?",
|
||||
"ui.render_config.preset_delete_title": "Delete Preset",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"ui.render_config.preset_name_placeholder": "새 프리셋 이름...",
|
||||
"ui.render_config.preset_save": "저장",
|
||||
"ui.render_config.preset_delete": "삭제",
|
||||
"ui.render_config.preset_delete_confirm": "삭제 확인",
|
||||
"ui.render_config.preset_overwrite_title": "프리셋 덮어쓰기",
|
||||
"ui.render_config.preset_overwrite_desc": "'{0}' 프리셋이 이미 존재합니다. 덮어쓰시겠습니까?",
|
||||
"ui.render_config.preset_delete_title": "프리셋 삭제",
|
||||
@@ -23,7 +24,7 @@
|
||||
"ui.render_config.audio_encoding": "오디오 인코딩 설정",
|
||||
"ui.render_config.global_settings": "전역 설정",
|
||||
"ui.render_config.advanced_settings": "고급 설정",
|
||||
"ui.render_config.go_render": "렌더 시작",
|
||||
"ui.render_config.go_render": "렌더링하러 가다",
|
||||
"ui.render_config.codec": "코덱",
|
||||
"ui.render_config.rate_control": "비트레이트 제어 모드",
|
||||
"ui.render_config.bitrate": "비트레이트 (kbps)",
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ using MelonLoader.Utils;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.2.4-INDEV", "ADOFAI Editor Forum")]
|
||||
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.2.6-INDEV", "ADOFAI Editor Forum")]
|
||||
[assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
|
||||
|
||||
namespace ADOFAIRenderer;
|
||||
|
||||
+75
-149
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MelonLoader;
|
||||
using O5Kit.Core;
|
||||
using O5Kit.Factory;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.UIElements;
|
||||
using Button = UnityEngine.UI.Button;
|
||||
using Image = UnityEngine.UI.Image;
|
||||
|
||||
namespace ADOFAIRenderer.UI;
|
||||
@@ -29,6 +31,7 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
{
|
||||
public static CommonWarningDialogUI? Instance { get; private set; }
|
||||
private readonly List<VisualElement> _disabledElements = new();
|
||||
private O5Window? _window;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -37,6 +40,9 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_window?.Dispose();
|
||||
_window = null;
|
||||
|
||||
foreach (var elem in _disabledElements)
|
||||
{
|
||||
if (elem != null)
|
||||
@@ -87,9 +93,8 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
// Canvas setup
|
||||
var canvas = gameObject.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 32766; // Just below FFmpegDownloadUI (32767)
|
||||
canvas.sortingOrder = 32766;
|
||||
|
||||
// Responsive Canvas Scaler setup
|
||||
var scaler = gameObject.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||||
@@ -98,13 +103,15 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
|
||||
gameObject.AddComponent<GraphicRaycaster>();
|
||||
|
||||
// 1. Create full screen overlay background
|
||||
var bgPanelObj = new GameObject("BackgroundPanel");
|
||||
var ctx = RendererUIManager.Context;
|
||||
|
||||
// 1. Create full screen backdrop (scrim) to block background input
|
||||
var bgPanelObj = new GameObject("Backdrop");
|
||||
bgPanelObj.transform.SetParent(transform, false);
|
||||
bgPanelObj.layer = 5;
|
||||
|
||||
var bgImage = bgPanelObj.AddComponent<Image>();
|
||||
bgImage.color = new Color(0f, 0f, 0f, 0.65f); // Dark transparent overlay
|
||||
bgImage.color = new Color(0f, 0f, 0f, 0.65f);
|
||||
bgImage.raycastTarget = true;
|
||||
|
||||
var bgRect = bgPanelObj.GetComponent<RectTransform>();
|
||||
@@ -113,112 +120,66 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
bgRect.offsetMin = Vector2.zero;
|
||||
bgRect.offsetMax = Vector2.zero;
|
||||
|
||||
var font = ModAssets.NanumSquareNeoNormal;
|
||||
// 2. Create actual native O5Window
|
||||
_window = O5Window.Create(ctx, bgPanelObj.transform, new O5WindowOptions
|
||||
{
|
||||
Id = "RendererMod_WarningDialog",
|
||||
Title = title,
|
||||
Size = new Vector2(540, 240),
|
||||
Draggable = true,
|
||||
Resizable = false,
|
||||
ShowCloseButton = false,
|
||||
ShowOutline = true,
|
||||
Padding = new RectOffset(16, 16, 16, 16)
|
||||
});
|
||||
|
||||
// 2. Create modal container
|
||||
var modalContainerObj = new GameObject("ModalContainer");
|
||||
modalContainerObj.transform.SetParent(bgPanelObj.transform, false);
|
||||
modalContainerObj.layer = 5;
|
||||
// 3. Build content inside window.Content
|
||||
var content = _window.Content;
|
||||
|
||||
var modalImage = modalContainerObj.AddComponent<Image>();
|
||||
modalImage.color = new Color(0.08f, 0.08f, 0.1f, 0.88f); // Matching background tone of existing UI
|
||||
var vLayout = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
vLayout.spacing = 16f;
|
||||
vLayout.padding = new RectOffset(16, 16, 12, 12);
|
||||
vLayout.childControlWidth = true;
|
||||
vLayout.childControlHeight = true;
|
||||
vLayout.childForceExpandWidth = true;
|
||||
vLayout.childForceExpandHeight = false;
|
||||
|
||||
var modalOutline = modalContainerObj.AddComponent<Outline>();
|
||||
modalOutline.effectColor = Color.white;
|
||||
modalOutline.effectDistance = new Vector2(3.0f, -3.0f); // Consistent with existing UI outline style
|
||||
|
||||
var modalRect = modalContainerObj.GetComponent<RectTransform>();
|
||||
modalRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
modalRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
modalRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
modalRect.sizeDelta = new Vector2(650, 360);
|
||||
|
||||
// 3. Create title text
|
||||
var titleObj = new GameObject("TitleText");
|
||||
titleObj.transform.SetParent(modalContainerObj.transform, false);
|
||||
titleObj.layer = 5;
|
||||
|
||||
var titleText = titleObj.AddComponent<TextMeshProUGUI>();
|
||||
titleText.text = title;
|
||||
titleText.fontSize = 24;
|
||||
titleText.font = font;
|
||||
titleText.color = new Color(1.0f, 0.8f, 0.2f, 1.0f); // Orange/yellow tone matching warning theme
|
||||
titleText.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var titleRect = titleObj.GetComponent<RectTransform>();
|
||||
titleRect.anchorMin = new Vector2(0, 1);
|
||||
titleRect.anchorMax = new Vector2(1, 1);
|
||||
titleRect.pivot = new Vector2(0.5f, 1);
|
||||
titleRect.anchoredPosition = new Vector2(0, -35);
|
||||
titleRect.sizeDelta = new Vector2(0, 40);
|
||||
|
||||
// 4. Create description text
|
||||
var descObj = new GameObject("DescriptionText");
|
||||
descObj.transform.SetParent(modalContainerObj.transform, false);
|
||||
descObj.layer = 5;
|
||||
|
||||
var descText = descObj.AddComponent<TextMeshProUGUI>();
|
||||
// Description text
|
||||
var descText = O5Factory.ControlText(ctx, content, 18f, noPad: true);
|
||||
descText.text = description;
|
||||
descText.fontSize = 20;
|
||||
descText.font = font;
|
||||
descText.color = new Color(0.85f, 0.85f, 0.85f, 1f);
|
||||
descText.alignment = TextAlignmentOptions.Center;
|
||||
descText.lineSpacing = 8;
|
||||
descText.textWrappingMode = TextWrappingModes.Normal;
|
||||
descText.lineSpacing = 6;
|
||||
descText.color = new Color(0.88f, 0.88f, 0.88f, 1f);
|
||||
|
||||
var descRect = descObj.GetComponent<RectTransform>();
|
||||
descRect.anchorMin = new Vector2(0, 0.5f);
|
||||
descRect.anchorMax = new Vector2(1, 1);
|
||||
descRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
descRect.anchoredPosition = new Vector2(0, -65);
|
||||
descRect.sizeDelta = new Vector2(-40, 120);
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var descLE = descText.gameObject.AddComponent<LayoutElement>();
|
||||
descLE.flexibleWidth = 1f;
|
||||
descLE.flexibleHeight = 1f;
|
||||
|
||||
// 5. Create button container
|
||||
var buttonContainerObj = new GameObject("ButtonContainer", typeof(RectTransform));
|
||||
buttonContainerObj.transform.SetParent(modalContainerObj.transform, false);
|
||||
buttonContainerObj.layer = 5;
|
||||
// Button action row
|
||||
var btnRowObj = new GameObject("ButtonRow");
|
||||
btnRowObj.transform.SetParent(content, false);
|
||||
var btnRowRect = btnRowObj.AddComponent<RectTransform>();
|
||||
var btnRowLE = btnRowObj.AddComponent<LayoutElement>();
|
||||
btnRowLE.preferredHeight = 40f;
|
||||
btnRowLE.flexibleWidth = 1f;
|
||||
|
||||
var btnContainerRect = buttonContainerObj.GetComponent<RectTransform>();
|
||||
btnContainerRect.anchorMin = new Vector2(0, 0);
|
||||
btnContainerRect.anchorMax = new Vector2(1, 0);
|
||||
btnContainerRect.pivot = new Vector2(0.5f, 0);
|
||||
btnContainerRect.anchoredPosition = new Vector2(0, 35);
|
||||
btnContainerRect.sizeDelta = new Vector2(0, 50);
|
||||
var hLayout = btnRowObj.AddComponent<HorizontalLayoutGroup>();
|
||||
hLayout.spacing = 10f;
|
||||
hLayout.childAlignment = TextAnchor.MiddleCenter;
|
||||
hLayout.childControlWidth = true;
|
||||
hLayout.childControlHeight = true;
|
||||
hLayout.childForceExpandWidth = true;
|
||||
hLayout.childForceExpandHeight = true;
|
||||
|
||||
// 6. Dynamic button layout and styling
|
||||
float totalWidth = 560f;
|
||||
float spacing = 15f;
|
||||
float buttonHeight = 45f;
|
||||
int numButtons = buttons.Length;
|
||||
|
||||
float buttonWidth = numButtons == 1
|
||||
? 200f
|
||||
: (totalWidth - (spacing * (numButtons - 1))) / numButtons;
|
||||
|
||||
float startX = numButtons == 1
|
||||
? 0
|
||||
: (-totalWidth / 2f) + (buttonWidth / 2f);
|
||||
|
||||
for (int i = 0; i < numButtons; i++)
|
||||
for (var i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
var btnData = buttons[i];
|
||||
var btnObj = new GameObject($"Button_{i}");
|
||||
btnObj.transform.SetParent(buttonContainerObj.transform, false);
|
||||
btnObj.layer = 5;
|
||||
|
||||
// Define default colors
|
||||
Color baseColor = btnData.BackgroundColor ?? new Color(0.2f, 0.2f, 0.23f, 1f); // Custom color or gray default
|
||||
Color hoverColor = ApplyColorBrightness(baseColor, 1.2f);
|
||||
Color pressedColor = ApplyColorBrightness(baseColor, 0.8f);
|
||||
|
||||
var btnImage = btnObj.AddComponent<Image>();
|
||||
btnImage.color = baseColor;
|
||||
btnImage.raycastTarget = true;
|
||||
|
||||
var btn = btnObj.AddComponent<Button>();
|
||||
SetupButtonStyle(btn, baseColor, hoverColor, pressedColor);
|
||||
|
||||
// Register click callback
|
||||
btn.onClick.AddListener(() =>
|
||||
var btn = O5Factory.Button(
|
||||
ctx: ctx,
|
||||
parent: btnRowRect,
|
||||
onClick: () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -226,59 +187,24 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MelonLoader.Melon<RenderMod>.Logger.Error($"Dialog Button Click Event Error: {ex}");
|
||||
Melon<RenderMod>.Logger.Error($"Dialog Button Click Event Error: {ex}");
|
||||
}
|
||||
Destroy(gameObject);
|
||||
});
|
||||
},
|
||||
text: btnData.Text,
|
||||
id: $"btn_dialog_{i}",
|
||||
height: 40f
|
||||
);
|
||||
|
||||
var btnRect = btnObj.GetComponent<RectTransform>();
|
||||
btnRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
btnRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
btnRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
|
||||
float posX = numButtons == 1
|
||||
? 0
|
||||
: startX + i * (buttonWidth + spacing);
|
||||
|
||||
btnRect.anchoredPosition = new Vector2(posX, 0);
|
||||
btnRect.sizeDelta = new Vector2(buttonWidth, buttonHeight);
|
||||
|
||||
// Add text label
|
||||
var txtObj = new GameObject("Text");
|
||||
txtObj.transform.SetParent(btnObj.transform, false);
|
||||
txtObj.layer = 5;
|
||||
|
||||
var text = txtObj.AddComponent<TextMeshProUGUI>();
|
||||
text.text = btnData.Text;
|
||||
text.fontSize = 20;
|
||||
text.font = font;
|
||||
text.color = Color.white;
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var txtRect = txtObj.GetComponent<RectTransform>();
|
||||
txtRect.anchorMin = Vector2.zero;
|
||||
txtRect.anchorMax = Vector2.one;
|
||||
txtRect.offsetMin = Vector2.zero;
|
||||
txtRect.offsetMax = Vector2.zero;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupButtonStyle(Button button, Color normalColor, Color hoverColor, Color pressedColor)
|
||||
if (btnData.BackgroundColor.HasValue)
|
||||
{
|
||||
button.transition = Selectable.Transition.ColorTint;
|
||||
var colors = button.colors;
|
||||
colors.normalColor = normalColor;
|
||||
colors.highlightedColor = hoverColor;
|
||||
colors.pressedColor = pressedColor;
|
||||
colors.disabledColor = new Color(0.3f, 0.3f, 0.3f, 1f);
|
||||
button.colors = colors;
|
||||
btn.NormalColor = btnData.BackgroundColor.Value;
|
||||
btn.UpdateVisual();
|
||||
}
|
||||
|
||||
private Color ApplyColorBrightness(Color color, float factor)
|
||||
{
|
||||
float r = Mathf.Clamp01(color.r * factor);
|
||||
float g = Mathf.Clamp01(color.g * factor);
|
||||
float b = Mathf.Clamp01(color.b * factor);
|
||||
return new Color(r, g, b, color.a);
|
||||
var le = btn.Rect.gameObject.AddComponent<LayoutElement>();
|
||||
le.flexibleWidth = 1f;
|
||||
le.preferredHeight = 40f;
|
||||
}
|
||||
}
|
||||
}
|
||||
+124
-182
@@ -4,11 +4,13 @@ using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using MelonLoader.Utils;
|
||||
using O5Kit.Core;
|
||||
using O5Kit.Factory;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using MelonLoader;
|
||||
using MelonLoader.Utils;
|
||||
|
||||
namespace ADOFAIRenderer.UI;
|
||||
|
||||
@@ -22,6 +24,7 @@ internal class FFmpegDownloadUI : MonoBehaviour
|
||||
private GameObject _statusTextObj = null!;
|
||||
private TextMeshProUGUI _statusText = null!;
|
||||
|
||||
private O5Window? _window;
|
||||
private bool _isDownloading;
|
||||
private string DownloadUrl => Application.platform switch
|
||||
{
|
||||
@@ -104,13 +107,15 @@ internal class FFmpegDownloadUI : MonoBehaviour
|
||||
|
||||
gameObject.AddComponent<GraphicRaycaster>();
|
||||
|
||||
// 1. Create full screen overlay background
|
||||
var bgPanelObj = new GameObject("BackgroundPanel");
|
||||
var ctx = RendererUIManager.Context;
|
||||
|
||||
// 1. Create full screen backdrop
|
||||
var bgPanelObj = new GameObject("Backdrop");
|
||||
bgPanelObj.transform.SetParent(transform, false);
|
||||
bgPanelObj.layer = 5;
|
||||
|
||||
var bgImage = bgPanelObj.AddComponent<Image>();
|
||||
bgImage.color = new Color(0f, 0f, 0f, 0.65f); // Dark transparent overlay
|
||||
bgImage.color = new Color(0f, 0f, 0f, 0.65f);
|
||||
bgImage.raycastTarget = true;
|
||||
|
||||
var bgRect = bgPanelObj.GetComponent<RectTransform>();
|
||||
@@ -119,51 +124,31 @@ internal class FFmpegDownloadUI : MonoBehaviour
|
||||
bgRect.offsetMin = Vector2.zero;
|
||||
bgRect.offsetMax = Vector2.zero;
|
||||
|
||||
var font = ModAssets.NanumSquareNeoNormal;
|
||||
// 2. Create actual native O5Window
|
||||
_window = O5Window.Create(ctx, bgPanelObj.transform, new O5WindowOptions
|
||||
{
|
||||
Id = "RendererMod_FFmpegDownloadDialog",
|
||||
Title = "FFmpeg Library Installation Required",
|
||||
Size = new Vector2(580, 270),
|
||||
Draggable = true,
|
||||
Resizable = false,
|
||||
ShowCloseButton = false,
|
||||
ShowOutline = true,
|
||||
Padding = new RectOffset(16, 16, 16, 16)
|
||||
});
|
||||
|
||||
// 2. Create modal container
|
||||
var modalContainerObj = new GameObject("ModalContainer");
|
||||
modalContainerObj.transform.SetParent(bgPanelObj.transform, false);
|
||||
modalContainerObj.layer = 5;
|
||||
var content = _window.Content;
|
||||
|
||||
var modalImage = modalContainerObj.AddComponent<Image>();
|
||||
modalImage.color = new Color(0.08f, 0.08f, 0.1f, 0.88f);
|
||||
var vLayout = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
vLayout.spacing = 14f;
|
||||
vLayout.padding = new RectOffset(16, 16, 10, 10);
|
||||
vLayout.childControlWidth = true;
|
||||
vLayout.childControlHeight = true;
|
||||
vLayout.childForceExpandWidth = true;
|
||||
vLayout.childForceExpandHeight = false;
|
||||
|
||||
var modalOutline = modalContainerObj.AddComponent<Outline>();
|
||||
modalOutline.effectColor = Color.white;
|
||||
modalOutline.effectDistance = new Vector2(3.0f, -3.0f);
|
||||
|
||||
var modalRect = modalContainerObj.GetComponent<RectTransform>();
|
||||
modalRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
modalRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
modalRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
modalRect.sizeDelta = new Vector2(600, 320);
|
||||
|
||||
// 4. Create title text
|
||||
var titleObj = new GameObject("TitleText");
|
||||
titleObj.transform.SetParent(modalContainerObj.transform, false);
|
||||
titleObj.layer = 5;
|
||||
|
||||
var titleText = titleObj.AddComponent<TextMeshProUGUI>();
|
||||
titleText.text = "FFmpeg Library Installation Required";
|
||||
titleText.fontSize = 26;
|
||||
titleText.font = font;
|
||||
titleText.color = Color.white;
|
||||
titleText.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var titleRect = titleObj.GetComponent<RectTransform>();
|
||||
titleRect.anchorMin = new Vector2(0, 1);
|
||||
titleRect.anchorMax = new Vector2(1, 1);
|
||||
titleRect.pivot = new Vector2(0.5f, 1);
|
||||
titleRect.anchoredPosition = new Vector2(0, -35);
|
||||
titleRect.sizeDelta = new Vector2(0, 40);
|
||||
|
||||
// 5. Create description text
|
||||
var descObj = new GameObject("DescriptionText");
|
||||
descObj.transform.SetParent(modalContainerObj.transform, false);
|
||||
descObj.layer = 5;
|
||||
|
||||
var descText = descObj.AddComponent<TextMeshProUGUI>();
|
||||
// 3. Description text
|
||||
var descText = O5Factory.ControlText(ctx, content, 18f, noPad: true);
|
||||
var descStrBuilder = new StringBuilder();
|
||||
descStrBuilder.AppendLine("FFmpeg shared libraries are required to render video.");
|
||||
if (Application.platform == RuntimePlatform.OSXPlayer)
|
||||
@@ -176,139 +161,104 @@ internal class FFmpegDownloadUI : MonoBehaviour
|
||||
descStrBuilder.AppendLine("Click the button below to automatically download and install from the internet.");
|
||||
descStrBuilder.AppendLine("<color=#aaaaaa><size=13>(Download size: ~70MB, extracted to UserLibs folder)</size></color>");
|
||||
}
|
||||
|
||||
descText.text = descStrBuilder.ToString();
|
||||
descText.fontSize = 16;
|
||||
descText.font = font;
|
||||
descText.color = new Color(0.85f, 0.85f, 0.85f, 1f);
|
||||
descText.alignment = TextAlignmentOptions.Center;
|
||||
descText.lineSpacing = 10;
|
||||
descText.textWrappingMode = TextWrappingModes.Normal;
|
||||
descText.lineSpacing = 5;
|
||||
descText.color = new Color(0.85f, 0.85f, 0.85f, 1f);
|
||||
|
||||
var descRect = descObj.GetComponent<RectTransform>();
|
||||
descRect.anchorMin = new Vector2(0, 0.5f);
|
||||
descRect.anchorMax = new Vector2(1, 1);
|
||||
descRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
descRect.anchoredPosition = new Vector2(0, -60);
|
||||
descRect.sizeDelta = new Vector2(-40, 100);
|
||||
var descLE = descText.gameObject.AddComponent<LayoutElement>();
|
||||
descLE.flexibleWidth = 1f;
|
||||
descLE.flexibleHeight = 1f;
|
||||
|
||||
// 6. Create button container
|
||||
_buttonContainerObj = new GameObject("ButtonContainer", typeof(RectTransform));
|
||||
_buttonContainerObj.transform.SetParent(modalContainerObj.transform, false);
|
||||
// 4. Button container
|
||||
_buttonContainerObj = new GameObject("ButtonContainer");
|
||||
_buttonContainerObj.transform.SetParent(content, false);
|
||||
_buttonContainerObj.layer = 5;
|
||||
|
||||
var btnContainerRect = _buttonContainerObj.GetComponent<RectTransform>();
|
||||
btnContainerRect.anchorMin = new Vector2(0, 0);
|
||||
btnContainerRect.anchorMax = new Vector2(1, 0);
|
||||
btnContainerRect.pivot = new Vector2(0.5f, 0);
|
||||
btnContainerRect.anchoredPosition = new Vector2(0, 35);
|
||||
btnContainerRect.sizeDelta = new Vector2(0, 50);
|
||||
var btnContainerLE = _buttonContainerObj.AddComponent<LayoutElement>();
|
||||
btnContainerLE.preferredHeight = 40f;
|
||||
btnContainerLE.flexibleWidth = 1f;
|
||||
|
||||
// 7. Create download button
|
||||
var downloadBtnObj = new GameObject("DownloadButton");
|
||||
downloadBtnObj.transform.SetParent(_buttonContainerObj.transform, false);
|
||||
downloadBtnObj.layer = 5;
|
||||
var btnLayout = _buttonContainerObj.AddComponent<HorizontalLayoutGroup>();
|
||||
btnLayout.spacing = 10f;
|
||||
btnLayout.childAlignment = TextAnchor.MiddleCenter;
|
||||
btnLayout.childControlWidth = true;
|
||||
btnLayout.childControlHeight = true;
|
||||
btnLayout.childForceExpandWidth = true;
|
||||
btnLayout.childForceExpandHeight = true;
|
||||
|
||||
var downloadImg = downloadBtnObj.AddComponent<Image>();
|
||||
downloadImg.color = new Color(0.0f, 0.8f, 1.0f, 1.0f); // Sleek cyan/blue
|
||||
downloadImg.raycastTarget = true;
|
||||
Action onDownloadClick = Application.platform != RuntimePlatform.OSXPlayer
|
||||
? StartDownloadProcess
|
||||
: () => { Application.OpenURL("https://ffmpeg.org"); Destroy(gameObject); };
|
||||
|
||||
var downloadBtn = downloadBtnObj.AddComponent<Button>();
|
||||
SetupButtonStyle(downloadBtn, new Color(0.0f, 0.8f, 1.0f, 1.0f), new Color(0.2f, 0.9f, 1.0f, 1.0f), new Color(0.0f, 0.6f, 0.8f, 1.0f));
|
||||
if (Application.platform != RuntimePlatform.OSXPlayer) downloadBtn.onClick.AddListener(StartDownloadProcess);
|
||||
else
|
||||
{
|
||||
downloadBtn.onClick.AddListener(OpenFFmpegWebsite);
|
||||
void OpenFFmpegWebsite() { Application.OpenURL("https://ffmpeg.org"); Destroy(gameObject); }
|
||||
}
|
||||
var downloadBtn = O5Factory.Button(
|
||||
ctx: ctx,
|
||||
parent: _buttonContainerObj.transform,
|
||||
onClick: onDownloadClick,
|
||||
text: Application.platform != RuntimePlatform.OSXPlayer ? "Download & Install" : "Open Website",
|
||||
id: "btn_ffmpeg_download",
|
||||
height: 40f
|
||||
);
|
||||
downloadBtn.NormalColor = ctx.Theme.ObjectActiveBright;
|
||||
downloadBtn.UpdateVisual();
|
||||
|
||||
var downloadBtnRect = downloadBtnObj.GetComponent<RectTransform>();
|
||||
downloadBtnRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
downloadBtnRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
downloadBtnRect.pivot = new Vector2(1f, 0.5f);
|
||||
downloadBtnRect.anchoredPosition = new Vector2(-15, 0);
|
||||
downloadBtnRect.sizeDelta = new Vector2(180, 45);
|
||||
var downloadBtnLE = downloadBtn.Rect.gameObject.AddComponent<LayoutElement>();
|
||||
downloadBtnLE.flexibleWidth = 1f;
|
||||
downloadBtnLE.preferredHeight = 40f;
|
||||
|
||||
var downloadTextObj = new GameObject("DownloadText");
|
||||
downloadTextObj.transform.SetParent(downloadBtnObj.transform, false);
|
||||
downloadTextObj.layer = 5;
|
||||
var cancelBtn = O5Factory.Button(
|
||||
ctx: ctx,
|
||||
parent: _buttonContainerObj.transform,
|
||||
onClick: CancelProcess,
|
||||
text: "Cancel",
|
||||
id: "btn_ffmpeg_cancel",
|
||||
height: 40f
|
||||
);
|
||||
var cancelBtnLE = cancelBtn.Rect.gameObject.AddComponent<LayoutElement>();
|
||||
cancelBtnLE.flexibleWidth = 1f;
|
||||
cancelBtnLE.preferredHeight = 40f;
|
||||
|
||||
var downloadText = downloadTextObj.AddComponent<TextMeshProUGUI>();
|
||||
downloadText.text = Application.platform != RuntimePlatform.OSXPlayer ? "Download & Install" : "Open Website";
|
||||
downloadText.fontSize = 20;
|
||||
downloadText.font = font;
|
||||
downloadText.color = Color.white;
|
||||
downloadText.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var downloadTextRect = downloadTextObj.GetComponent<RectTransform>();
|
||||
downloadTextRect.anchorMin = Vector2.zero;
|
||||
downloadTextRect.anchorMax = Vector2.one;
|
||||
downloadTextRect.offsetMin = Vector2.zero;
|
||||
downloadTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
// 8. Create cancel button
|
||||
var cancelBtnObj = new GameObject("CancelButton");
|
||||
cancelBtnObj.transform.SetParent(_buttonContainerObj.transform, false);
|
||||
cancelBtnObj.layer = 5;
|
||||
|
||||
var cancelImg = cancelBtnObj.AddComponent<Image>();
|
||||
cancelImg.color = new Color(0.2f, 0.2f, 0.23f, 1f);
|
||||
cancelImg.raycastTarget = true;
|
||||
|
||||
var cancelBtn = cancelBtnObj.AddComponent<Button>();
|
||||
SetupButtonStyle(cancelBtn, new Color(0.2f, 0.2f, 0.23f, 1f), new Color(0.25f, 0.25f, 0.28f, 1f), new Color(0.15f, 0.15f, 0.18f, 1f));
|
||||
cancelBtn.onClick.AddListener(CancelProcess);
|
||||
|
||||
var cancelBtnRect = cancelBtnObj.GetComponent<RectTransform>();
|
||||
cancelBtnRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
cancelBtnRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
cancelBtnRect.pivot = new Vector2(0f, 0.5f);
|
||||
cancelBtnRect.anchoredPosition = new Vector2(15, 0);
|
||||
cancelBtnRect.sizeDelta = new Vector2(180, 45);
|
||||
|
||||
var cancelTextObj = new GameObject("CancelText");
|
||||
cancelTextObj.transform.SetParent(cancelBtnObj.transform, false);
|
||||
cancelTextObj.layer = 5;
|
||||
|
||||
var cancelText = cancelTextObj.AddComponent<TextMeshProUGUI>();
|
||||
cancelText.text = "Cancel";
|
||||
cancelText.fontSize = 20;
|
||||
cancelText.font = font;
|
||||
cancelText.color = new Color(0.9f, 0.9f, 0.9f, 1f);
|
||||
cancelText.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var cancelTextRect = cancelTextObj.GetComponent<RectTransform>();
|
||||
cancelTextRect.anchorMin = Vector2.zero;
|
||||
cancelTextRect.anchorMax = Vector2.one;
|
||||
cancelTextRect.offsetMin = Vector2.zero;
|
||||
cancelTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
// 9. Create progress bar container
|
||||
// 5. Progress bar container
|
||||
_progressBarObj = new GameObject("ProgressBarContainer");
|
||||
_progressBarObj.transform.SetParent(modalContainerObj.transform, false);
|
||||
_progressBarObj.transform.SetParent(content, false);
|
||||
_progressBarObj.layer = 5;
|
||||
_progressBarObj.SetActive(false);
|
||||
|
||||
var pbLE = _progressBarObj.AddComponent<LayoutElement>();
|
||||
pbLE.preferredHeight = 24f;
|
||||
pbLE.flexibleWidth = 1f;
|
||||
|
||||
var barBgImg = _progressBarObj.AddComponent<Image>();
|
||||
barBgImg.color = new Color(0.08f, 0.08f, 0.1f, 1.0f);
|
||||
barBgImg.sprite = ctx.Sprites.RoundedControl;
|
||||
barBgImg.type = Image.Type.Sliced;
|
||||
barBgImg.color = ctx.Theme.ObjectBG;
|
||||
|
||||
var barBgOutline = _progressBarObj.AddComponent<Outline>();
|
||||
barBgOutline.effectColor = Color.white;
|
||||
barBgOutline.effectDistance = new Vector2(3.0f, -3.0f);
|
||||
var barOutlineObj = new GameObject("BarOutline");
|
||||
barOutlineObj.transform.SetParent(_progressBarObj.transform, false);
|
||||
barOutlineObj.layer = 5;
|
||||
|
||||
var barRect = _progressBarObj.GetComponent<RectTransform>();
|
||||
barRect.anchorMin = new Vector2(0.5f, 0);
|
||||
barRect.anchorMax = new Vector2(0.5f, 0);
|
||||
barRect.pivot = new Vector2(0.5f, 0);
|
||||
barRect.anchoredPosition = new Vector2(0, 75);
|
||||
barRect.sizeDelta = new Vector2(480, 24);
|
||||
var barOutlineImg = barOutlineObj.AddComponent<Image>();
|
||||
barOutlineImg.sprite = ctx.Sprites.RoundedOutline;
|
||||
barOutlineImg.type = Image.Type.Sliced;
|
||||
barOutlineImg.color = ctx.Theme.ControlOutlineIdle;
|
||||
barOutlineImg.raycastTarget = false;
|
||||
|
||||
// 10. Create progress bar fill image
|
||||
var barOutlineRect = barOutlineObj.GetComponent<RectTransform>();
|
||||
barOutlineRect.anchorMin = Vector2.zero;
|
||||
barOutlineRect.anchorMax = Vector2.one;
|
||||
barOutlineRect.offsetMin = Vector2.zero;
|
||||
barOutlineRect.offsetMax = Vector2.zero;
|
||||
|
||||
// Progress bar fill image
|
||||
_progressBarFillObj = new GameObject("ProgressBarFill");
|
||||
_progressBarFillObj.transform.SetParent(_progressBarObj.transform, false);
|
||||
_progressBarFillObj.layer = 5;
|
||||
|
||||
var fillImg = _progressBarFillObj.AddComponent<Image>();
|
||||
fillImg.color = new Color(0.0f, 0.8f, 1.0f, 1.0f);
|
||||
fillImg.sprite = ctx.Sprites.RoundedControl;
|
||||
fillImg.type = Image.Type.Sliced;
|
||||
fillImg.color = ctx.Theme.ObjectActiveBright;
|
||||
|
||||
_fillRect = _progressBarFillObj.GetComponent<RectTransform>();
|
||||
_fillRect.anchorMin = new Vector2(0, 0);
|
||||
@@ -317,20 +267,17 @@ internal class FFmpegDownloadUI : MonoBehaviour
|
||||
_fillRect.anchoredPosition = Vector2.zero;
|
||||
_fillRect.sizeDelta = Vector2.zero;
|
||||
|
||||
// 11. Create progress text
|
||||
// Progress text
|
||||
var progressTextObj = new GameObject("ProgressBarText");
|
||||
progressTextObj.transform.SetParent(_progressBarObj.transform, false);
|
||||
progressTextObj.layer = 5;
|
||||
|
||||
_progressText = progressTextObj.AddComponent<TextMeshProUGUI>();
|
||||
_progressText.fontSize = 16;
|
||||
_progressText.font = font;
|
||||
_progressText.fontSize = 13;
|
||||
_progressText.font = ctx.Fonts.Regular;
|
||||
_progressText.color = Color.white;
|
||||
_progressText.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var progressTextOutline = progressTextObj.AddComponent<Outline>();
|
||||
progressTextOutline.effectColor = new Color(0, 0, 0, 0.8f);
|
||||
progressTextOutline.effectDistance = new Vector2(1, -1);
|
||||
_progressText.raycastTarget = false;
|
||||
|
||||
var progressTextRect = progressTextObj.GetComponent<RectTransform>();
|
||||
progressTextRect.anchorMin = Vector2.zero;
|
||||
@@ -338,35 +285,28 @@ internal class FFmpegDownloadUI : MonoBehaviour
|
||||
progressTextRect.offsetMin = Vector2.zero;
|
||||
progressTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
// 12. Create status text
|
||||
// 6. Status text
|
||||
_statusTextObj = new GameObject("StatusText");
|
||||
_statusTextObj.transform.SetParent(modalContainerObj.transform, false);
|
||||
_statusTextObj.transform.SetParent(content, false);
|
||||
_statusTextObj.layer = 5;
|
||||
_statusTextObj.SetActive(false);
|
||||
|
||||
var statusLE = _statusTextObj.AddComponent<LayoutElement>();
|
||||
statusLE.preferredHeight = 22f;
|
||||
statusLE.flexibleWidth = 1f;
|
||||
|
||||
_statusText = _statusTextObj.AddComponent<TextMeshProUGUI>();
|
||||
_statusText.fontSize = 16;
|
||||
_statusText.font = font;
|
||||
_statusText.fontSize = 14;
|
||||
_statusText.font = ctx.Fonts.Regular;
|
||||
_statusText.color = new Color(0.8f, 0.8f, 0.8f, 1f);
|
||||
_statusText.alignment = TextAlignmentOptions.Center;
|
||||
_statusText.raycastTarget = false;
|
||||
|
||||
var statusRect = _statusTextObj.GetComponent<RectTransform>();
|
||||
statusRect.anchorMin = new Vector2(0, 0);
|
||||
statusRect.anchorMax = new Vector2(1, 0);
|
||||
statusRect.pivot = new Vector2(0.5f, 0);
|
||||
statusRect.anchoredPosition = new Vector2(0, 35);
|
||||
statusRect.sizeDelta = new Vector2(0, 30);
|
||||
}
|
||||
|
||||
private void SetupButtonStyle(Button button, Color normalColor, Color hoverColor, Color pressedColor)
|
||||
{
|
||||
button.transition = Selectable.Transition.ColorTint;
|
||||
var colors = button.colors;
|
||||
colors.normalColor = normalColor;
|
||||
colors.highlightedColor = hoverColor;
|
||||
colors.pressedColor = pressedColor;
|
||||
colors.disabledColor = new Color(0.3f, 0.3f, 0.3f, 1f);
|
||||
button.colors = colors;
|
||||
statusRect.anchorMin = Vector2.zero;
|
||||
statusRect.anchorMax = Vector2.one;
|
||||
statusRect.offsetMin = Vector2.zero;
|
||||
statusRect.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@@ -643,6 +583,8 @@ internal class FFmpegDownloadUI : MonoBehaviour
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_window?.Dispose();
|
||||
_window = null;
|
||||
_cts?.Dispose();
|
||||
if (Instance == this)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using ADOFAIRenderer.Localization;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ADOFAIRenderer.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Component that binds a TextMeshProUGUI component to a localization key or dynamic string provider,
|
||||
/// automatically updating the displayed text whenever the active language changes.
|
||||
/// </summary>
|
||||
internal class LocalizedText : MonoBehaviour
|
||||
{
|
||||
private TextMeshProUGUI? _textComponent;
|
||||
private string? _key;
|
||||
private Func<string>? _dynamicProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the localization key. Setting this will clear any dynamic provider.
|
||||
/// </summary>
|
||||
public string? Key
|
||||
{
|
||||
get => _key;
|
||||
set
|
||||
{
|
||||
_key = value;
|
||||
_dynamicProvider = null;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a dynamic string provider function.
|
||||
/// </summary>
|
||||
public void SetDynamic(Func<string> provider)
|
||||
{
|
||||
_dynamicProvider = provider;
|
||||
_key = null;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureComponent();
|
||||
LocalizationManager.OnLanguageChanged += HandleLanguageChanged;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
LocalizationManager.OnLanguageChanged -= HandleLanguageChanged;
|
||||
}
|
||||
|
||||
private void HandleLanguageChanged(SystemLanguage language)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void EnsureComponent()
|
||||
{
|
||||
if (_textComponent == null)
|
||||
{
|
||||
_textComponent = GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the text using either the dynamic provider or the static localization key.
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
EnsureComponent();
|
||||
if (_textComponent == null) return;
|
||||
|
||||
if (_dynamicProvider != null)
|
||||
{
|
||||
_textComponent.text = _dynamicProvider();
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(_key))
|
||||
{
|
||||
_textComponent.text = LocalizationManager.Get(_key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a TextMeshProUGUI component to a localization key.
|
||||
/// </summary>
|
||||
public static LocalizedText Bind(TextMeshProUGUI target, string key)
|
||||
{
|
||||
if (target == null) throw new ArgumentNullException(nameof(target));
|
||||
var component = target.GetComponent<LocalizedText>() ?? target.gameObject.AddComponent<LocalizedText>();
|
||||
component._textComponent = target;
|
||||
component.Key = key;
|
||||
return component;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a TextMeshProUGUI component to a dynamic string provider function.
|
||||
/// </summary>
|
||||
public static LocalizedText Bind(TextMeshProUGUI target, Func<string> provider)
|
||||
{
|
||||
if (target == null) throw new ArgumentNullException(nameof(target));
|
||||
var component = target.GetComponent<LocalizedText>() ?? target.gameObject.AddComponent<LocalizedText>();
|
||||
component._textComponent = target;
|
||||
component.SetDynamic(provider);
|
||||
return component;
|
||||
}
|
||||
}
|
||||
+376
-182
@@ -26,6 +26,10 @@ internal static class RenderConfigWindow
|
||||
private static O5InputField? _presetNameInput;
|
||||
private static O5Button? _savePresetButton;
|
||||
private static O5Button? _deletePresetButton;
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static LayoutElement? _deletePresetButtonLE;
|
||||
private static bool _isDeletePending;
|
||||
private static Coroutine? _deleteResetCoroutine;
|
||||
private static string? _lastLoadedPresetName;
|
||||
|
||||
// Controls cache for validation & sync
|
||||
@@ -87,14 +91,20 @@ internal static class RenderConfigWindow
|
||||
Size = new Vector2(900, 600), Draggable = true, Resizable = true,
|
||||
ShowCloseButton = true
|
||||
});
|
||||
if (_configWindow.TitleText != null)
|
||||
{
|
||||
LocalizedText.Bind(_configWindow.TitleText, "ui.render_config.window_title");
|
||||
}
|
||||
|
||||
_configWindow.CloseRequested += win =>
|
||||
{
|
||||
CloseAllPickers();
|
||||
ResetDeletePendingState();
|
||||
win.Rect.gameObject.SetActive(false);
|
||||
};
|
||||
|
||||
BuildUI(_configWindow.Content);
|
||||
LocalizationManager.OnLanguageChanged += OnLanguageChanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -145,14 +155,9 @@ internal static class RenderConfigWindow
|
||||
};
|
||||
|
||||
// Presets (Collapsible Card)
|
||||
var (_, presetContent) = O5Factory.Card(
|
||||
var (_, presetContent) = CreateCard(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.presets"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
"ui.render_config.presets"
|
||||
);
|
||||
|
||||
var presets = PresetManager.GetPresets();
|
||||
@@ -177,7 +182,7 @@ internal static class RenderConfigWindow
|
||||
|
||||
_presetDropdown = CreateDropdownField(
|
||||
ctx, presetContent,
|
||||
LocalizationManager.Get("ui.render_config.preset_select"),
|
||||
"ui.render_config.preset_select",
|
||||
initialPresetName, initialPresetName,
|
||||
presetNames, p => p,
|
||||
"dd_presets",
|
||||
@@ -213,18 +218,24 @@ internal static class RenderConfigWindow
|
||||
icon: null,
|
||||
id: "input_preset_name"
|
||||
);
|
||||
if (_presetNameInput.Placeholder != null)
|
||||
{
|
||||
LocalizedText.Bind(_presetNameInput.Placeholder, "ui.render_config.preset_name_placeholder");
|
||||
}
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var inputLE = _presetNameInput.Rect.GetComponent<LayoutElement>() ?? _presetNameInput.Rect.gameObject.AddComponent<LayoutElement>();
|
||||
inputLE.flexibleWidth = 1f;
|
||||
inputLE.preferredHeight = 40f;
|
||||
|
||||
_savePresetButton = O5Factory.Button(
|
||||
_savePresetButton = CreateButton(
|
||||
ctx: ctx,
|
||||
parent: actionsRowRect,
|
||||
onClick: () => OnSavePresetClicked(config),
|
||||
text: LocalizationManager.Get("ui.render_config.preset_save"),
|
||||
textKey: "ui.render_config.preset_save",
|
||||
id: "btn_preset_save",
|
||||
onClick: () => OnSavePresetClicked(config),
|
||||
height: 40f
|
||||
);
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var saveLE = _savePresetButton.Rect.GetComponent<LayoutElement>() ?? _savePresetButton.Rect.gameObject.AddComponent<LayoutElement>();
|
||||
saveLE.preferredWidth = 90f;
|
||||
saveLE.flexibleWidth = 0f;
|
||||
@@ -241,104 +252,97 @@ internal static class RenderConfigWindow
|
||||
fonts: ctx.Fonts,
|
||||
tween: ctx.Tween
|
||||
);
|
||||
_deletePresetButton = O5Factory.Button(
|
||||
_deletePresetButton = CreateButton(
|
||||
ctx: redCtx,
|
||||
parent: actionsRowRect,
|
||||
onClick: () => OnDeletePresetClicked(config),
|
||||
text: LocalizationManager.Get("ui.render_config.preset_delete"),
|
||||
textKey: "ui.render_config.preset_delete",
|
||||
id: "btn_preset_delete",
|
||||
onClick: () => OnDeletePresetClicked(config),
|
||||
height: 40f
|
||||
);
|
||||
var deleteLE = _deletePresetButton.Rect.GetComponent<LayoutElement>() ?? _deletePresetButton.Rect.gameObject.AddComponent<LayoutElement>();
|
||||
deleteLE.preferredWidth = 90f;
|
||||
deleteLE.flexibleWidth = 0f;
|
||||
deleteLE.preferredHeight = 40f;
|
||||
if (_deletePresetButton.Label != null)
|
||||
{
|
||||
LocalizedText.Bind(_deletePresetButton.Label, () =>
|
||||
_isDeletePending
|
||||
? LocalizationManager.Get("ui.render_config.preset_delete_confirm")
|
||||
: LocalizationManager.Get("ui.render_config.preset_delete"));
|
||||
}
|
||||
_deletePresetButtonLE = _deletePresetButton.Rect.GetComponent<LayoutElement>() ?? _deletePresetButton.Rect.gameObject.AddComponent<LayoutElement>();
|
||||
_deletePresetButtonLE.preferredWidth = 90f;
|
||||
_deletePresetButtonLE.flexibleWidth = 0f;
|
||||
_deletePresetButtonLE.preferredHeight = 40f;
|
||||
|
||||
UpdateDeleteButtonState();
|
||||
|
||||
// 1-3. Top section: 좌/우 컨테이너 1개씩으로 3행을 쌓는다 (중앙선 1개)
|
||||
var (topLeft, topRight) = CreateTwoColumnPanel(scrollContent, spacing: 8f, columnSpacing: 10f);
|
||||
|
||||
_renderFpsSlider = O5Factory.Slider(
|
||||
_renderFpsSlider = CreateSlider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topLeft),
|
||||
labelKey: "ui.render_config.render_fps", id: "slider_render_fps",
|
||||
defaultValue: 60, min: 0.01, max: 3000, value: 60,
|
||||
format: "F3", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => Snap(renderFpsSnap, value),
|
||||
inputFilter: null, onComplete: null, onChanged: val => { config.RenderFPS = val; MarkConfigModified(); },
|
||||
text: LocalizationManager.Get("ui.render_config.render_fps"),
|
||||
id: "slider_render_fps", showFill: true
|
||||
onChanged: val => { config.RenderFPS = val; MarkConfigModified(); }
|
||||
);
|
||||
|
||||
_videoFPSSlider = O5Factory.Slider(
|
||||
_videoFPSSlider = CreateSlider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topRight),
|
||||
labelKey: "ui.render_config.video_fps", id: "slider_video_fps",
|
||||
defaultValue: 60, min: 0.01, max: 600, value: 60,
|
||||
format: "F3", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => Snap(videoFpsSnap, value),
|
||||
inputFilter: null, onComplete: null, onChanged: val => { config.VideoFPS = val; MarkConfigModified(); },
|
||||
text: LocalizationManager.Get("ui.render_config.video_fps"),
|
||||
id: "slider_video_fps", showFill: true
|
||||
onChanged: val => { config.VideoFPS = val; MarkConfigModified(); }
|
||||
);
|
||||
|
||||
_renderWidthSlider = O5Factory.Slider(
|
||||
_renderWidthSlider = CreateSlider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topLeft),
|
||||
labelKey: "ui.render_config.render_width", id: "slider_render_width",
|
||||
defaultValue: 2560, min: 0, max: 15360, value: 2560,
|
||||
format: "F0", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => Snap(renderWidthSnap, value), inputFilter: value => Math.Max(value, 2),
|
||||
onComplete: null, onChanged: val => { config.RenderWidth = (int)Math.Round(val); MarkConfigModified(); },
|
||||
text: LocalizationManager.Get("ui.render_config.render_width"),
|
||||
id: "slider_render_width", showFill: true
|
||||
onChanged: val => { config.RenderWidth = (int)Math.Round(val); MarkConfigModified(); }
|
||||
);
|
||||
|
||||
_renderHeightSlider = O5Factory.Slider(
|
||||
_renderHeightSlider = CreateSlider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topRight),
|
||||
labelKey: "ui.render_config.render_height", id: "slider_render_height",
|
||||
defaultValue: 1440, min: 0, max: 8640, value: 1440,
|
||||
format: "F0", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => Snap(renderHeightSnap, value), inputFilter: value => Math.Max(value, 2),
|
||||
onComplete: null, onChanged: val => { config.RenderHeight = (int)Math.Round(val); MarkConfigModified(); },
|
||||
text: LocalizationManager.Get("ui.render_config.render_height"),
|
||||
id: "slider_render_height", showFill: true
|
||||
onChanged: val => { config.RenderHeight = (int)Math.Round(val); MarkConfigModified(); }
|
||||
);
|
||||
|
||||
_delayAfterEndSlider = O5Factory.Slider(
|
||||
_delayAfterEndSlider = CreateSlider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topLeft),
|
||||
labelKey: "ui.render_config.delay_after_end", id: "slider_delay_after_end",
|
||||
defaultValue: 15, min: 0, max: 9999.999, value: 15,
|
||||
format: "F3", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: null, inputFilter: null,
|
||||
onComplete: null, onChanged: val => { config.DelayAfterEnd = val; MarkConfigModified(); },
|
||||
text: LocalizationManager.Get("ui.render_config.delay_after_end"),
|
||||
id: "slider_delay_after_end", showFill: true
|
||||
onChanged: val => { config.DelayAfterEnd = val; MarkConfigModified(); }
|
||||
);
|
||||
|
||||
_showJudgementToggle = CreateToggleField(
|
||||
ctx, CreateFieldSlot(topRight), "",
|
||||
false, config.ShowJudgementText,
|
||||
LocalizationManager.Get("ui.render_config.show_judgement"),
|
||||
"toggle_show_judgement",
|
||||
val => { config.ShowJudgementText = val; MarkConfigModified(); },
|
||||
ctx, CreateFieldSlot(topRight),
|
||||
labelKey: "",
|
||||
defaultValue: false,
|
||||
value: config.ShowJudgementText,
|
||||
toggleTextKey: "ui.render_config.show_judgement",
|
||||
id: "toggle_show_judgement",
|
||||
onChanged: val => { config.ShowJudgementText = val; MarkConfigModified(); },
|
||||
generateLabel: false
|
||||
);
|
||||
|
||||
// 4. Video Encoding Config (Collapsible Card)
|
||||
var (_, videoContent) = O5Factory.Card(
|
||||
var (_, videoContent) = CreateCard(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.video_encoding"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
"ui.render_config.video_encoding"
|
||||
);
|
||||
|
||||
var (videoLeft, videoRight) = CreateTwoColumnPanel(videoContent);
|
||||
var codecs = (Codec[])Enum.GetValues(typeof(Codec));
|
||||
_codecDropdown = CreateDropdownField(
|
||||
ctx, CreateFieldSlot(videoLeft),
|
||||
LocalizationManager.Get("ui.render_config.codec"),
|
||||
"ui.render_config.codec",
|
||||
Codec.H264, config.VideoConfig.Codec,
|
||||
codecs, c => c.ToString(),
|
||||
"dd_video_codec",
|
||||
@@ -351,7 +355,7 @@ internal static class RenderConfigWindow
|
||||
|
||||
_bitrateInput = CreateInputField(
|
||||
ctx, CreateFieldSlot(videoLeft),
|
||||
LocalizationManager.Get("ui.render_config.bitrate"),
|
||||
"ui.render_config.bitrate",
|
||||
"30000", config.VideoConfig.Bitrate.ToString(),
|
||||
"input_bitrate", out _bitrateLabel,
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
@@ -360,7 +364,7 @@ internal static class RenderConfigWindow
|
||||
|
||||
_qpInput = CreateInputField(
|
||||
ctx, CreateFieldSlot(videoLeft),
|
||||
LocalizationManager.Get("ui.render_config.qp"),
|
||||
"ui.render_config.qp",
|
||||
"18", config.VideoConfig.QP.ToString(),
|
||||
"input_qp", out _qpLabel,
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
@@ -370,7 +374,7 @@ internal static class RenderConfigWindow
|
||||
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
|
||||
_rateControlDropdown = CreateDropdownField(
|
||||
ctx, CreateFieldSlot(videoRight),
|
||||
LocalizationManager.Get("ui.render_config.rate_control"),
|
||||
"ui.render_config.rate_control",
|
||||
RateControlMode.CBR, config.VideoConfig.RateControlMode,
|
||||
rateModes, m => m.ToString(),
|
||||
"dd_rate_control",
|
||||
@@ -384,7 +388,7 @@ internal static class RenderConfigWindow
|
||||
|
||||
_maxBitrateInput = CreateInputField(
|
||||
ctx, CreateFieldSlot(videoRight),
|
||||
LocalizationManager.Get("ui.render_config.max_bitrate"),
|
||||
"ui.render_config.max_bitrate",
|
||||
"50000", config.VideoConfig.MaxBitrate.ToString(),
|
||||
"input_max_bitrate", out _maxBitrateLabel,
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
@@ -392,61 +396,55 @@ internal static class RenderConfigWindow
|
||||
);
|
||||
|
||||
_forceSoftwareToggle = CreateToggleField(
|
||||
ctx, CreateFieldSlot(videoRight), "",
|
||||
false, config.VideoConfig.ForceSoftwareEncoder,
|
||||
LocalizationManager.Get("ui.render_config.force_software"),
|
||||
"toggle_force_software",
|
||||
val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); },
|
||||
ctx, CreateFieldSlot(videoRight),
|
||||
labelKey: "",
|
||||
defaultValue: false,
|
||||
value: config.VideoConfig.ForceSoftwareEncoder,
|
||||
toggleTextKey: "ui.render_config.force_software",
|
||||
id: "toggle_force_software",
|
||||
onChanged: val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); },
|
||||
generateLabel: true
|
||||
);
|
||||
|
||||
_keyframeIntervalSlider = O5Factory.Slider(
|
||||
_keyframeIntervalSlider = CreateSlider(
|
||||
ctx: ctx, parent: videoContent,
|
||||
labelKey: "ui.render_config.keyframe_interval", id: "slider_keyframe_interval",
|
||||
defaultValue: 2, min: 0.1, max: 10, value: 2,
|
||||
format: "F1", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: null, inputFilter: null,
|
||||
onComplete: null, onChanged: val => { config.VideoConfig.KeyframeInterval = val; MarkConfigModified(); },
|
||||
text: LocalizationManager.Get("ui.render_config.keyframe_interval"),
|
||||
id: "slider_keyframe_interval", showFill: true
|
||||
onChanged: val => { config.VideoConfig.KeyframeInterval = val; MarkConfigModified(); }
|
||||
);
|
||||
|
||||
// 5. Audio Encoding Config (Collapsible Card)
|
||||
var (_, audioContent) = O5Factory.Card(
|
||||
var (_, audioContent) = CreateCard(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.audio_encoding"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
"ui.render_config.audio_encoding"
|
||||
);
|
||||
|
||||
var (audioLeft, audioRight) = CreateTwoColumnPanel(audioContent);
|
||||
_renderAudioToggle = CreateToggleField(
|
||||
ctx, CreateFieldSlot(audioLeft), "",
|
||||
true, config.AudioConfig.RenderAudio,
|
||||
LocalizationManager.Get("ui.render_config.render_audio"),
|
||||
"toggle_render_audio",
|
||||
val => { config.AudioConfig.RenderAudio = val; MarkConfigModified(); },
|
||||
ctx, CreateFieldSlot(audioLeft),
|
||||
labelKey: "",
|
||||
defaultValue: true,
|
||||
value: config.AudioConfig.RenderAudio,
|
||||
toggleTextKey: "ui.render_config.render_audio",
|
||||
id: "toggle_render_audio",
|
||||
onChanged: val => { config.AudioConfig.RenderAudio = val; MarkConfigModified(); },
|
||||
generateLabel: false
|
||||
);
|
||||
|
||||
_audioBitrateSlider = O5Factory.Slider(
|
||||
_audioBitrateSlider = CreateSlider(
|
||||
ctx: ctx, parent: CreateFieldSlot(audioRight),
|
||||
labelKey: "ui.render_config.audio_bitrate", id: "slider_audio_bitrate",
|
||||
defaultValue: 256, min: 64, max: 384, value: 256,
|
||||
format: "F0", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => Snap(audioBitrateSnap, value),
|
||||
inputFilter: null, onComplete: null, onChanged: val => { config.AudioConfig.AudioBitrate = (int)Math.Round(val) * 1000; MarkConfigModified(); },
|
||||
text: LocalizationManager.Get("ui.render_config.audio_bitrate"),
|
||||
id: "slider_audio_bitrate", showFill: true
|
||||
onChanged: val => { config.AudioConfig.AudioBitrate = (int)Math.Round(val) * 1000; MarkConfigModified(); }
|
||||
);
|
||||
|
||||
var channels = new[] { 1, 2 };
|
||||
_channelsDropdown = CreateDropdownField(
|
||||
ctx, CreateFieldSlot(audioLeft),
|
||||
LocalizationManager.Get("ui.render_config.channels"),
|
||||
"ui.render_config.channels",
|
||||
2, config.AudioConfig.AudioChannels,
|
||||
channels, c => c == 1 ? "1 (Mono)" : "2 (Stereo)",
|
||||
"dd_channels",
|
||||
@@ -455,7 +453,7 @@ internal static class RenderConfigWindow
|
||||
|
||||
_sampleRateDropdown = CreateDropdownField(
|
||||
ctx, CreateFieldSlot(audioRight),
|
||||
LocalizationManager.Get("ui.render_config.sample_rate"),
|
||||
"ui.render_config.sample_rate",
|
||||
48000, config.AudioConfig.AudioSampleRate,
|
||||
CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[config.VideoConfig.Codec]], r => $"{r/1000.0} kHz",
|
||||
"dd_sample_rate",
|
||||
@@ -463,19 +461,14 @@ internal static class RenderConfigWindow
|
||||
);
|
||||
|
||||
// 6. Global Settings (Collapsible Card)
|
||||
var (_, globalContent) = O5Factory.Card(
|
||||
var (_, globalContent) = CreateCard(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.global_settings"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
"ui.render_config.global_settings"
|
||||
);
|
||||
|
||||
_outputDirInput = CreateFullWidthInputField(
|
||||
ctx, globalContent,
|
||||
LocalizationManager.Get("ui.render_config.output_dir"),
|
||||
"ui.render_config.output_dir",
|
||||
GlobalSettings.DefaultOutputDirectory,
|
||||
GlobalSettings.EffectiveOutputDirectory,
|
||||
"input_output_dir",
|
||||
@@ -495,8 +488,10 @@ internal static class RenderConfigWindow
|
||||
);
|
||||
|
||||
var (globalLeft, globalRight) = CreateTwoColumnPanel(globalContent);
|
||||
O5Factory.Button(
|
||||
CreateButton(
|
||||
ctx, globalLeft,
|
||||
"ui.render_config.browse",
|
||||
"btn_browse_output",
|
||||
() =>
|
||||
{
|
||||
var initial = string.IsNullOrWhiteSpace(_outputDirInput.Value)
|
||||
@@ -512,39 +507,32 @@ internal static class RenderConfigWindow
|
||||
GlobalSettings.Save();
|
||||
}
|
||||
});
|
||||
},
|
||||
LocalizationManager.Get("ui.render_config.browse"),
|
||||
"btn_browse_output"
|
||||
}
|
||||
);
|
||||
|
||||
O5Factory.Button(
|
||||
CreateButton(
|
||||
ctx, globalRight,
|
||||
"ui.render_config.reset_default",
|
||||
"btn_reset_output",
|
||||
() =>
|
||||
{
|
||||
_outputDirInput.Set(GlobalSettings.DefaultOutputDirectory);
|
||||
GlobalSettings.Current.OutputDirectory = null;
|
||||
GlobalSettings.Save();
|
||||
},
|
||||
LocalizationManager.Get("ui.render_config.reset_default"),
|
||||
"btn_reset_output"
|
||||
}
|
||||
);
|
||||
|
||||
// 7. Advanced Settings (Collapsible Card)
|
||||
var (_, advContent) = O5Factory.Card(
|
||||
var (_, advContent) = CreateCard(
|
||||
ctx, scrollContent,
|
||||
LocalizationManager.Get("ui.render_config.advanced_settings"),
|
||||
activeValue: true,
|
||||
onActiveChanged: null,
|
||||
onDeleteClick: null,
|
||||
showDeleteButton: false,
|
||||
showActiveToggle: false
|
||||
"ui.render_config.advanced_settings"
|
||||
);
|
||||
|
||||
var (advLeft, advRight) = CreateTwoColumnPanel(advContent);
|
||||
var extensions = new[] { "mkv", "mp4", "mov", "webm", "avi" };
|
||||
_fileExtensionDropdown = CreateDropdownField(
|
||||
ctx, CreateFieldSlot(advLeft),
|
||||
LocalizationManager.Get("ui.render_config.file_extension"),
|
||||
"ui.render_config.file_extension",
|
||||
"mkv", config.AdvancedConfig.FileExtension,
|
||||
extensions, e => e,
|
||||
"dd_adv_file_extension",
|
||||
@@ -556,11 +544,13 @@ internal static class RenderConfigWindow
|
||||
);
|
||||
|
||||
_processAudioToggle = CreateToggleField(
|
||||
ctx, CreateFieldSlot(advLeft), "",
|
||||
true, config.AdvancedConfig.ProcessAudio,
|
||||
LocalizationManager.Get("ui.render_config.process_audio"),
|
||||
"toggle_process_audio",
|
||||
val =>
|
||||
ctx, CreateFieldSlot(advLeft),
|
||||
labelKey: "",
|
||||
defaultValue: true,
|
||||
value: config.AdvancedConfig.ProcessAudio,
|
||||
toggleTextKey: "ui.render_config.process_audio",
|
||||
id: "toggle_process_audio",
|
||||
onChanged: val =>
|
||||
{
|
||||
config.AdvancedConfig.ProcessAudio = val;
|
||||
MarkConfigModified();
|
||||
@@ -570,7 +560,7 @@ internal static class RenderConfigWindow
|
||||
|
||||
_randomSeedInput = CreateInputField(
|
||||
ctx, CreateFieldSlot(advRight),
|
||||
LocalizationManager.Get("ui.render_config.random_seed"),
|
||||
"ui.render_config.random_seed",
|
||||
"", config.AdvancedConfig.RandomSeed?.ToString() ?? "",
|
||||
"input_random_seed",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
@@ -583,11 +573,13 @@ internal static class RenderConfigWindow
|
||||
);
|
||||
|
||||
_nonTileToggle = CreateToggleField(
|
||||
ctx, CreateFieldSlot(advRight), "",
|
||||
false, config.AdvancedConfig.NonTile,
|
||||
LocalizationManager.Get("ui.render_config.non_tile"),
|
||||
"toggle_non_tile",
|
||||
val =>
|
||||
ctx, CreateFieldSlot(advRight),
|
||||
labelKey: "",
|
||||
defaultValue: false,
|
||||
value: config.AdvancedConfig.NonTile,
|
||||
toggleTextKey: "ui.render_config.non_tile",
|
||||
id: "toggle_non_tile",
|
||||
onChanged: val =>
|
||||
{
|
||||
config.AdvancedConfig.NonTile = val;
|
||||
MarkConfigModified();
|
||||
@@ -596,9 +588,7 @@ internal static class RenderConfigWindow
|
||||
);
|
||||
|
||||
// 7-1. Planet Color Settings (Optional)
|
||||
var planetColorsLabel = O5Factory.ControlText(ctx, advContent, 13f, noPad: true);
|
||||
planetColorsLabel.text = LocalizationManager.Get("ui.render_config.planet_colors");
|
||||
planetColorsLabel.color = ctx.Theme.TextDim;
|
||||
var planetColorsLabel = CreateLabel(ctx, advContent, "ui.render_config.planet_colors");
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var planetLabelLE = planetColorsLabel.gameObject.AddComponent<LayoutElement>();
|
||||
planetLabelLE.preferredHeight = 18f;
|
||||
@@ -609,11 +599,13 @@ internal static class RenderConfigWindow
|
||||
|
||||
var fireSlot = CreateFieldSlot(fireCol);
|
||||
_firePlanetToggle = CreateToggleField(
|
||||
ctx, fireSlot, "",
|
||||
false, config.AdvancedConfig.firePlanetColor.HasValue,
|
||||
LocalizationManager.Get("ui.render_config.fire_planet"),
|
||||
"toggle_fire_planet",
|
||||
val =>
|
||||
ctx, fireSlot,
|
||||
labelKey: "",
|
||||
defaultValue: false,
|
||||
value: config.AdvancedConfig.firePlanetColor.HasValue,
|
||||
toggleTextKey: "ui.render_config.fire_planet",
|
||||
id: "toggle_fire_planet",
|
||||
onChanged: val =>
|
||||
{
|
||||
_firePlanetPicker?.SetBlocked(!val);
|
||||
config.AdvancedConfig.firePlanetColor = val ? _firePlanetPicker?.Value : null;
|
||||
@@ -643,11 +635,13 @@ internal static class RenderConfigWindow
|
||||
|
||||
var iceSlot = CreateFieldSlot(iceCol);
|
||||
_icePlanetToggle = CreateToggleField(
|
||||
ctx, iceSlot, "",
|
||||
false, config.AdvancedConfig.icePlanetColor.HasValue,
|
||||
LocalizationManager.Get("ui.render_config.ice_planet"),
|
||||
"toggle_ice_planet",
|
||||
val =>
|
||||
ctx, iceSlot,
|
||||
labelKey: "",
|
||||
defaultValue: false,
|
||||
value: config.AdvancedConfig.icePlanetColor.HasValue,
|
||||
toggleTextKey: "ui.render_config.ice_planet",
|
||||
id: "toggle_ice_planet",
|
||||
onChanged: val =>
|
||||
{
|
||||
_icePlanetPicker?.SetBlocked(!val);
|
||||
config.AdvancedConfig.icePlanetColor = val ? _icePlanetPicker?.Value : null;
|
||||
@@ -677,11 +671,13 @@ internal static class RenderConfigWindow
|
||||
|
||||
var windSlot = CreateFieldSlot(windCol);
|
||||
_windPlanetToggle = CreateToggleField(
|
||||
ctx, windSlot, "",
|
||||
false, config.AdvancedConfig.windPlanetColor.HasValue,
|
||||
LocalizationManager.Get("ui.render_config.wind_planet"),
|
||||
"toggle_wind_planet",
|
||||
val =>
|
||||
ctx, windSlot,
|
||||
labelKey: "",
|
||||
defaultValue: false,
|
||||
value: config.AdvancedConfig.windPlanetColor.HasValue,
|
||||
toggleTextKey: "ui.render_config.wind_planet",
|
||||
id: "toggle_wind_planet",
|
||||
onChanged: val =>
|
||||
{
|
||||
_windPlanetPicker?.SetBlocked(!val);
|
||||
config.AdvancedConfig.windPlanetColor = val ? _windPlanetPicker?.Value : null;
|
||||
@@ -710,20 +706,17 @@ internal static class RenderConfigWindow
|
||||
}
|
||||
|
||||
// 8. Go Render Button
|
||||
O5Factory.Button(
|
||||
CreateButton(
|
||||
ctx, scrollContent,
|
||||
TryGoRender,
|
||||
LocalizationManager.Get("ui.render_config.go_render"),
|
||||
"ui.render_config.go_render",
|
||||
"btn_go_render",
|
||||
onClick: TryGoRender,
|
||||
height: 48f
|
||||
);
|
||||
|
||||
// 9. Version Label
|
||||
var versionLabel = O5Factory.ControlText(ctx, scrollContent, 12f, noPad: true);
|
||||
var versionText = $"ADOFAI Renderer v{Melon<RenderMod>.Instance.Info.Version}";
|
||||
if (DateTime.UtcNow >= RenderMod.ExpirationTime) versionText += " " + LocalizationManager.Get("ui.render_config.expired");
|
||||
|
||||
versionLabel.text = versionText;
|
||||
LocalizedText.Bind(versionLabel, GetVersionText);
|
||||
versionLabel.alignment = TMPro.TextAlignmentOptions.Center;
|
||||
versionLabel.color = ctx.Theme.TextDim;
|
||||
|
||||
@@ -926,6 +919,7 @@ internal static class RenderConfigWindow
|
||||
|
||||
private static void OnPresetSelected(string? selectedName, RenderConfig config)
|
||||
{
|
||||
ResetDeletePendingState();
|
||||
if (string.IsNullOrEmpty(selectedName) || selectedName == CustomPresetLabel) return;
|
||||
var preset = PresetManager.GetPresets().FirstOrDefault(p => string.Equals(p.Name, selectedName, StringComparison.OrdinalIgnoreCase));
|
||||
if (preset == null) return;
|
||||
@@ -953,7 +947,7 @@ internal static class RenderConfigWindow
|
||||
{
|
||||
SyncUItoConfig(config);
|
||||
|
||||
var name = _presetNameInput?.Value?.Trim();
|
||||
var name = _presetNameInput?.Value.Trim();
|
||||
if (string.IsNullOrWhiteSpace(name) || name == CustomPresetLabel)
|
||||
{
|
||||
CommonWarningDialogUI.ShowDialog(
|
||||
@@ -1010,20 +1004,62 @@ internal static class RenderConfigWindow
|
||||
}
|
||||
}
|
||||
|
||||
var ctx = RendererUIManager.Context;
|
||||
CommonWarningDialogUI.ShowDialog(
|
||||
LocalizationManager.Get("ui.render_config.preset_delete_title"),
|
||||
LocalizationManager.Get("ui.render_config.preset_delete_desc", targetPresetName),
|
||||
new DialogButton(
|
||||
LocalizationManager.Get("ui.render_config.confirm"),
|
||||
() => DoDeletePreset(targetPresetName, config),
|
||||
ctx.Theme.SoftRed
|
||||
),
|
||||
new DialogButton(
|
||||
LocalizationManager.Get("ui.render_config.cancel"),
|
||||
() => { }
|
||||
)
|
||||
);
|
||||
if (!_isDeletePending)
|
||||
{
|
||||
_isDeletePending = true;
|
||||
if (_deletePresetButtonLE != null) _deletePresetButtonLE.preferredWidth = 110f;
|
||||
if (_deletePresetButton != null)
|
||||
{
|
||||
if (_deletePresetButton.Label != null)
|
||||
{
|
||||
_deletePresetButton.Label.text = LocalizationManager.Get("ui.render_config.preset_delete_confirm");
|
||||
}
|
||||
_deletePresetButton.NormalColor = new Color(0.95f, 0.25f, 0.25f, 1f);
|
||||
_deletePresetButton.UpdateVisual();
|
||||
}
|
||||
|
||||
if (_deleteResetCoroutine != null)
|
||||
{
|
||||
RendererUIManager.Instance.StopCoroutine(_deleteResetCoroutine);
|
||||
}
|
||||
_deleteResetCoroutine = RendererUIManager.Instance.StartCoroutine(ResetDeletePendingAfterDelay());
|
||||
return;
|
||||
}
|
||||
|
||||
ResetDeletePendingState();
|
||||
DoDeletePreset(targetPresetName, config);
|
||||
}
|
||||
|
||||
private static System.Collections.IEnumerator ResetDeletePendingAfterDelay()
|
||||
{
|
||||
yield return new WaitForSeconds(3f);
|
||||
ResetDeletePendingState();
|
||||
}
|
||||
|
||||
private static void ResetDeletePendingState()
|
||||
{
|
||||
if (_deleteResetCoroutine != null)
|
||||
{
|
||||
RendererUIManager.Instance.StopCoroutine(_deleteResetCoroutine);
|
||||
_deleteResetCoroutine = null;
|
||||
}
|
||||
|
||||
if (!_isDeletePending && (_deletePresetButton == null || _deletePresetButton.Label?.text == LocalizationManager.Get("ui.render_config.preset_delete")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDeletePending = false;
|
||||
if (_deletePresetButtonLE) _deletePresetButtonLE.preferredWidth = 90f;
|
||||
if (_deletePresetButton != null)
|
||||
{
|
||||
if (_deletePresetButton.Label != null)
|
||||
{
|
||||
_deletePresetButton.Label.text = LocalizationManager.Get("ui.render_config.preset_delete");
|
||||
}
|
||||
_deletePresetButton.NormalColor = RendererUIManager.Context.Theme.SoftRed;
|
||||
_deletePresetButton.UpdateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoDeletePreset(string name, RenderConfig config)
|
||||
@@ -1048,6 +1084,7 @@ internal static class RenderConfigWindow
|
||||
|
||||
private static void ReloadPresetDropdown(string? selectName = null)
|
||||
{
|
||||
ResetDeletePendingState();
|
||||
var presets = PresetManager.GetPresets();
|
||||
var names = presets.Select(p => p.Name).ToList();
|
||||
if (names.Count == 0)
|
||||
@@ -1087,6 +1124,26 @@ internal static class RenderConfigWindow
|
||||
_deletePresetButton?.SetBlocked(!canDelete);
|
||||
}
|
||||
|
||||
private static void OnLanguageChanged(SystemLanguage language)
|
||||
{
|
||||
if (_configWindow == null) return;
|
||||
|
||||
if (_presetDropdown != null)
|
||||
{
|
||||
var cur = _presetDropdown.Value;
|
||||
var isCustom = cur == "(Modified)" || cur == "(수정됨)" || cur == CustomPresetLabel;
|
||||
ReloadPresetDropdown(isCustom ? CustomPresetLabel : cur);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetVersionText()
|
||||
{
|
||||
var versionText = $"ADOFAI Renderer v{Melon<RenderMod>.Instance.Info.Version}";
|
||||
if (DateTime.UtcNow >= RenderMod.ExpirationTime)
|
||||
versionText += " " + LocalizationManager.Get("ui.render_config.expired");
|
||||
return versionText;
|
||||
}
|
||||
|
||||
private static void ShowValidationError(string message)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error(message);
|
||||
@@ -1189,23 +1246,139 @@ internal static class RenderConfigWindow
|
||||
return slotRect;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedTupleComponentInReturnValue
|
||||
private static (RectTransform card, RectTransform content) CreateCard(
|
||||
O5Context ctx,
|
||||
Transform parent,
|
||||
string titleKey,
|
||||
bool activeValue = true,
|
||||
Action<bool>? onActiveChanged = null,
|
||||
Action? onDeleteClick = null,
|
||||
bool showDeleteButton = false,
|
||||
bool showActiveToggle = false)
|
||||
{
|
||||
var (card, content) = O5Factory.Card(
|
||||
ctx,
|
||||
parent,
|
||||
LocalizationManager.Get(titleKey),
|
||||
activeValue,
|
||||
onActiveChanged,
|
||||
onDeleteClick,
|
||||
showDeleteButton,
|
||||
showActiveToggle
|
||||
);
|
||||
|
||||
var titleTmp = card.Find("Header/TitleTrigger/TitleText")?.GetComponent<TMPro.TextMeshProUGUI>()
|
||||
?? card.GetComponentsInChildren<TMPro.TextMeshProUGUI>(true).FirstOrDefault(t => t.gameObject.name == "TitleText");
|
||||
if (titleTmp)
|
||||
{
|
||||
titleTmp.fontSize = 18f;
|
||||
LocalizedText.Bind(titleTmp, titleKey);
|
||||
}
|
||||
|
||||
return (card, content);
|
||||
}
|
||||
|
||||
private static TMPro.TextMeshProUGUI CreateLabel(
|
||||
O5Context ctx,
|
||||
Transform parent,
|
||||
string textKey,
|
||||
float fontSize = 13f,
|
||||
bool noPad = true,
|
||||
Color? color = null)
|
||||
{
|
||||
var label = O5Factory.ControlText(ctx, parent, fontSize, noPad: noPad);
|
||||
label.text = LocalizationManager.Get(textKey);
|
||||
label.color = color ?? ctx.Theme.TextDim;
|
||||
LocalizedText.Bind(label, textKey);
|
||||
return label;
|
||||
}
|
||||
|
||||
private static O5Slider CreateSlider(
|
||||
O5Context ctx,
|
||||
Transform parent,
|
||||
string labelKey,
|
||||
string id,
|
||||
double defaultValue,
|
||||
double min,
|
||||
double max,
|
||||
double value,
|
||||
string format = "F0",
|
||||
ClampMode clampMode = ClampMode.All,
|
||||
Func<double, double>? sliderFilter = null,
|
||||
Func<double, double>? inputFilter = null,
|
||||
Action<double>? onChanged = null,
|
||||
Action<double>? onComplete = null,
|
||||
bool showFill = true)
|
||||
{
|
||||
var slider = O5Factory.Slider(
|
||||
ctx: ctx,
|
||||
parent: parent,
|
||||
defaultValue: defaultValue,
|
||||
min: min,
|
||||
max: max,
|
||||
value: value,
|
||||
format: format,
|
||||
clampMode: clampMode,
|
||||
sliderFilter: sliderFilter,
|
||||
inputFilter: inputFilter,
|
||||
onComplete: onComplete,
|
||||
onChanged: onChanged,
|
||||
text: LocalizationManager.Get(labelKey),
|
||||
id: id,
|
||||
showFill: showFill
|
||||
);
|
||||
|
||||
if (slider.Label != null)
|
||||
{
|
||||
LocalizedText.Bind(slider.Label, labelKey);
|
||||
}
|
||||
|
||||
return slider;
|
||||
}
|
||||
|
||||
private static O5Button CreateButton(
|
||||
O5Context ctx,
|
||||
Transform parent,
|
||||
string textKey,
|
||||
string id,
|
||||
Action onClick,
|
||||
float? height = null)
|
||||
{
|
||||
var button = O5Factory.Button(
|
||||
ctx: ctx,
|
||||
parent: parent,
|
||||
onClick: onClick,
|
||||
text: LocalizationManager.Get(textKey),
|
||||
id: id,
|
||||
height: height
|
||||
);
|
||||
|
||||
if (button.Label != null)
|
||||
{
|
||||
LocalizedText.Bind(button.Label, textKey);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private static O5InputField CreateInputField(
|
||||
O5Context ctx,
|
||||
Transform colParent,
|
||||
string labelText,
|
||||
string labelKey,
|
||||
string? defaultValue,
|
||||
string value,
|
||||
string id,
|
||||
TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None,
|
||||
Action<string>? onChanged = null)
|
||||
{
|
||||
return CreateInputField(ctx, colParent, labelText, defaultValue, value, id, out _, characterValidation, onChanged);
|
||||
return CreateInputField(ctx, colParent, labelKey, defaultValue, value, id, out _, characterValidation, onChanged);
|
||||
}
|
||||
|
||||
private static O5InputField CreateInputField(
|
||||
O5Context ctx,
|
||||
Transform colParent,
|
||||
string labelText,
|
||||
string labelKey,
|
||||
string? defaultValue,
|
||||
string value,
|
||||
string id,
|
||||
@@ -1214,8 +1387,9 @@ internal static class RenderConfigWindow
|
||||
Action<string>? onChanged = null)
|
||||
{
|
||||
label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.text = LocalizationManager.Get(labelKey);
|
||||
label.color = ctx.Theme.TextDim;
|
||||
LocalizedText.Bind(label, labelKey);
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
@@ -1228,11 +1402,16 @@ internal static class RenderConfigWindow
|
||||
defaultValue: defaultValue,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
placeholder: labelText,
|
||||
placeholder: LocalizationManager.Get(labelKey),
|
||||
icon: null,
|
||||
id: id
|
||||
);
|
||||
|
||||
if (inputField.Placeholder != null)
|
||||
{
|
||||
LocalizedText.Bind(inputField.Placeholder, labelKey);
|
||||
}
|
||||
|
||||
inputField.InputField.characterValidation = characterValidation;
|
||||
return inputField;
|
||||
}
|
||||
@@ -1291,7 +1470,7 @@ internal static class RenderConfigWindow
|
||||
private static O5InputField CreateFullWidthInputField(
|
||||
O5Context ctx,
|
||||
Transform parent,
|
||||
string labelText,
|
||||
string labelKey,
|
||||
string? defaultValue,
|
||||
string value,
|
||||
string id,
|
||||
@@ -1309,8 +1488,9 @@ internal static class RenderConfigWindow
|
||||
layout.childForceExpandHeight = false;
|
||||
|
||||
var label = O5Factory.ControlText(ctx, containerRect, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.text = LocalizationManager.Get(labelKey);
|
||||
label.color = ctx.Theme.TextDim;
|
||||
LocalizedText.Bind(label, labelKey);
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
@@ -1323,30 +1503,36 @@ internal static class RenderConfigWindow
|
||||
defaultValue: defaultValue,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
placeholder: labelText,
|
||||
placeholder: LocalizationManager.Get(labelKey),
|
||||
icon: null,
|
||||
id: id
|
||||
);
|
||||
|
||||
if (input.Placeholder != null)
|
||||
{
|
||||
LocalizedText.Bind(input.Placeholder, labelKey);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static O5Toggle CreateToggleField(
|
||||
O5Context ctx,
|
||||
Transform colParent,
|
||||
string labelText,
|
||||
string labelKey,
|
||||
bool? defaultValue,
|
||||
bool value,
|
||||
string toggleText,
|
||||
string toggleTextKey,
|
||||
string id,
|
||||
Action<bool>? onChanged = null,
|
||||
bool generateLabel = true)
|
||||
{
|
||||
if (generateLabel)
|
||||
if (generateLabel && !string.IsNullOrEmpty(labelKey))
|
||||
{
|
||||
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.text = LocalizationManager.Get(labelKey);
|
||||
label.color = ctx.Theme.TextDim;
|
||||
LocalizedText.Bind(label, labelKey);
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
@@ -1354,21 +1540,28 @@ internal static class RenderConfigWindow
|
||||
labelLE.minHeight = 18f;
|
||||
}
|
||||
|
||||
return O5Factory.Toggle(
|
||||
var toggle = O5Factory.Toggle(
|
||||
ctx: ctx,
|
||||
parent: colParent,
|
||||
defaultValue: defaultValue,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
text: toggleText,
|
||||
text: !string.IsNullOrEmpty(toggleTextKey) ? LocalizationManager.Get(toggleTextKey) : "",
|
||||
id: id
|
||||
);
|
||||
|
||||
if (toggle.Label != null && !string.IsNullOrEmpty(toggleTextKey))
|
||||
{
|
||||
LocalizedText.Bind(toggle.Label, toggleTextKey);
|
||||
}
|
||||
|
||||
return toggle;
|
||||
}
|
||||
|
||||
private static O5Dropdown<T> CreateDropdownField<T>(
|
||||
O5Context ctx,
|
||||
Transform colParent,
|
||||
string labelText,
|
||||
string labelKey,
|
||||
T defaultValue,
|
||||
T value,
|
||||
IReadOnlyList<T> values,
|
||||
@@ -1377,8 +1570,9 @@ internal static class RenderConfigWindow
|
||||
Action<T>? onChanged = null)
|
||||
{
|
||||
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.text = LocalizationManager.Get(labelKey);
|
||||
label.color = ctx.Theme.TextDim;
|
||||
LocalizedText.Bind(label, labelKey);
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
|
||||
Reference in new issue
Block a user