9 Commits
12 changed files with 804 additions and 547 deletions

No files matched your search

+8 -3
View File
@@ -32,9 +32,11 @@ Thumbs.db
desktop.ini desktop.ini
## Agent Metadata ## Agent Metadata
.antigravitycli/ /.antigravitycli/
.agent/ /.agent/
GEMINI.md /GEMINI.md
/.mcp.json
/CLAUDE.md
# Decompiled Game Code # Decompiled Game Code
*.cs.txt *.cs.txt
@@ -43,3 +45,6 @@ GEMINI.md
/AsyncInputAutoplayFix.md /AsyncInputAutoplayFix.md
/AudioMixerExplanation.md /AudioMixerExplanation.md
/O5Theme.md /O5Theme.md
# Developer Utility Files
/REVIEW_CHECKLIST.md
+94 -18
View File
@@ -91,7 +91,50 @@ internal class RenderManager : MonoBehaviour
public long frameCount; public long frameCount;
public bool skipPressToStart; public bool skipPressToStart;
public string levelPath = ""; public string levelPath = "";
public bool IsRenderCancelled { get; private set; } private readonly object _abortStateLock = new();
private volatile bool _isRenderCancelled;
private volatile bool _renderFailed;
private volatile string? _failureReason;
// ReSharper disable once MemberCanBePrivate.Global
public bool IsRenderCancelled => _isRenderCancelled;
public bool IsRenderFailed => _renderFailed;
// Cancelled and failed are mutually exclusive; this is true for either (render must stop).
public bool IsRenderAborted => _isRenderCancelled || _renderFailed;
private void MarkCancelled()
{
lock (_abortStateLock)
{
if (!_renderFailed) _isRenderCancelled = true;
}
}
// The first failure reason wins; ignored once the render is already cancelled or failed.
private void MarkFailed(string reason)
{
lock (_abortStateLock)
{
if (_isRenderCancelled || _renderFailed) return;
_failureReason = reason;
_renderFailed = true;
}
}
private void ClearAbortState()
{
lock (_abortStateLock)
{
_isRenderCancelled = false;
_renderFailed = false;
_failureReason = null;
}
}
private string BuildAbortMessage() => IsRenderFailed
? $"Rendering failed: {_failureReason ?? "unknown error"}"
: "Rendering was cancelled by user.";
public RenderConfig Configuration = null!; public RenderConfig Configuration = null!;
// Render Sessions State (OOP Instance Fields) // Render Sessions State (OOP Instance Fields)
@@ -151,6 +194,7 @@ internal class RenderManager : MonoBehaviour
public long frameCountForVideo; public long frameCountForVideo;
public long lastVideoFrameIndex = -1; public long lastVideoFrameIndex = -1;
// long cannot be volatile: access via Volatile.Read/Write (written on main thread, read on render thread)
public long lastRequestedVideoFrameIndex = -1; public long lastRequestedVideoFrameIndex = -1;
public int readbackFailureCount; public int readbackFailureCount;
public int throttleCount; public int throttleCount;
@@ -170,8 +214,8 @@ internal class RenderManager : MonoBehaviour
internal AutoResetEvent FrameAvailableEvent = new(false); internal AutoResetEvent FrameAvailableEvent = new(false);
public Thread? RenderThread; public Thread? RenderThread;
public long passCount; public long passCount;
public bool endRequested; public volatile bool endRequested;
public bool endSuccessful; public volatile bool endSuccessful;
public long endFrame = -1; public long endFrame = -1;
// Private Instance Fields // Private Instance Fields
@@ -195,12 +239,15 @@ internal class RenderManager : MonoBehaviour
public void ResetRenderState(bool success) public void ResetRenderState(bool success)
{ {
IsRenderCancelled = !success; if (success) _isRenderCancelled = false;
else MarkCancelled();
stage = RenderStage.None; stage = RenderStage.None;
} }
public void CleanUpActiveRender() public void CleanUpActiveRender()
{ {
if (stage != RenderStage.None)
MarkFailed("the game exited or RenderManager was destroyed during rendering.");
ResetRenderState(success: false); ResetRenderState(success: false);
AudioSpectrumGenerator.Clear(); AudioSpectrumGenerator.Clear();
@@ -283,8 +330,15 @@ internal class RenderManager : MonoBehaviour
} }
if (Instance.stage < RenderStage.OnScene) return; if (Instance.stage < RenderStage.OnScene) return;
if (stage >= RenderStage.Render && RenderThread is not { IsAlive: true })
{
MarkFailed("the render thread terminated unexpectedly.");
CancelRender();
return;
}
if (!scrController.instance || scrController.instance.state == States.Fail || scrController.instance.state == States.Fail2) if (!scrController.instance || scrController.instance.state == States.Fail || scrController.instance.state == States.Fail2)
{ {
MarkFailed("the level failed or the game controller was lost during rendering.");
ResetRenderState(success: false); ResetRenderState(success: false);
StopFFmpeg(false); StopFFmpeg(false);
EndRender(); EndRender();
@@ -333,6 +387,12 @@ internal class RenderManager : MonoBehaviour
while (Instance.stage >= RenderStage.Render) while (Instance.stage >= RenderStage.Render)
{ {
if (RenderThread is not { IsAlive: true })
{
isThrottling = false;
return;
}
int count; int count;
lock (ImageQueue) lock (ImageQueue)
{ {
@@ -362,7 +422,7 @@ internal class RenderManager : MonoBehaviour
if (videoFrameIndex > lastVideoFrameIndex) if (videoFrameIndex > lastVideoFrameIndex)
{ {
lastVideoFrameIndex = videoFrameIndex; lastVideoFrameIndex = videoFrameIndex;
lastRequestedVideoFrameIndex = videoFrameIndex; Volatile.Write(ref lastRequestedVideoFrameIndex, videoFrameIndex);
var targetTexture = texture; var targetTexture = texture;
if (HasActiveFrameHooks && _frameHookPipeline != null) if (HasActiveFrameHooks && _frameHookPipeline != null)
@@ -403,7 +463,6 @@ internal class RenderManager : MonoBehaviour
Application.wantsToQuit -= quitDelegate; Application.wantsToQuit -= quitDelegate;
RenderConfigWindow.CreateWindow(); RenderConfigWindow.CreateWindow();
// ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "Assets/Scenes/RenderSettingsScene.unity");
}); });
} }
@@ -511,10 +570,10 @@ internal class RenderManager : MonoBehaviour
ImageQueue.Clear(); ImageQueue.Clear();
} }
passCount = 0; passCount = 0;
IsRenderCancelled = false; ClearAbortState();
frameCountForVideo = 0; frameCountForVideo = 0;
lastVideoFrameIndex = -1; lastVideoFrameIndex = -1;
lastRequestedVideoFrameIndex = -1; Volatile.Write(ref lastRequestedVideoFrameIndex, -1);
endRequested = false; endRequested = false;
endSuccessful = false; endSuccessful = false;
FrameAvailableEvent.Reset(); FrameAvailableEvent.Reset();
@@ -655,17 +714,17 @@ internal class RenderManager : MonoBehaviour
continue; continue;
} }
if (IsRenderCancelled) break; if (IsRenderAborted) break;
if (endRequested) if (endRequested)
{ {
if (passCount > lastRequestedVideoFrameIndex) break; if (passCount > Volatile.Read(ref lastRequestedVideoFrameIndex)) break;
} }
FrameAvailableEvent.WaitOne(50); FrameAvailableEvent.WaitOne(50);
} }
if (!IsRenderCancelled) videoFFmpeg.End(); if (!IsRenderAborted) videoFFmpeg.End();
videoFFmpeg.Dispose(); videoFFmpeg.Dispose();
lock (_allVideoFFmpegInstances) lock (_allVideoFFmpegInstances)
@@ -674,12 +733,22 @@ internal class RenderManager : MonoBehaviour
} }
_activeVideoFFmpeg = null; _activeVideoFFmpeg = null;
endSuccessful = !IsRenderCancelled; endSuccessful = !IsRenderAborted;
} }
catch (Exception ex) catch (Exception ex)
{ {
Melon<RenderMod>.Logger.Error($"FFmpeg rendering thread error: {ex}"); Melon<RenderMod>.Logger.Error($"FFmpeg rendering thread error: {ex}");
videoFFmpeg.Dispose();
MarkFailed("encoder thread error. See the log for details.");
try
{
videoFFmpeg.Dispose();
}
catch (Exception disposeEx)
{
Melon<RenderMod>.Logger.Error($"Encoder dispose after thread error also failed: {disposeEx.Message}");
}
lock (_allVideoFFmpegInstances) lock (_allVideoFFmpegInstances)
{ {
_allVideoFFmpegInstances.Remove(videoFFmpeg); _allVideoFFmpegInstances.Remove(videoFFmpeg);
@@ -708,7 +777,7 @@ internal class RenderManager : MonoBehaviour
endRequested = true; endRequested = true;
if (!success) if (!success)
{ {
IsRenderCancelled = true; MarkCancelled();
} }
FrameAvailableEvent.Set(); FrameAvailableEvent.Set();
skipPressToStart = false; skipPressToStart = false;
@@ -722,7 +791,8 @@ internal class RenderManager : MonoBehaviour
if (success) StartCoroutine(StopRenderCoroutine()); if (success) StartCoroutine(StopRenderCoroutine());
else else
{ {
Melon<RenderMod>.Logger.Error("Rendering failed or was cancelled. Safely cleaning up state and resources."); var reason = IsRenderFailed ? $"FAILED ({_failureReason})" : "CANCELLED";
Melon<RenderMod>.Logger.Error($"Rendering {reason}. Safely cleaning up state and resources.");
// 1. Wait for render background thread to finish processing remaining queued frames and terminate // 1. Wait for render background thread to finish processing remaining queued frames and terminate
if (RenderThread is { IsAlive: true }) if (RenderThread is { IsAlive: true })
@@ -796,15 +866,20 @@ internal class RenderManager : MonoBehaviour
CurrentOutPath = null; CurrentOutPath = null;
} }
var result = BuildResult(false, var result = BuildResult(false, BuildAbortMessage());
IsRenderCancelled ? "Rendering was cancelled by user." : "Rendering failed.");
TriggerOnComplete(result); TriggerOnComplete(result);
} }
} }
private IEnumerator StopRenderCoroutine() private IEnumerator StopRenderCoroutine()
{ {
yield return new WaitUntil(() => endSuccessful); yield return new WaitUntil(() => endSuccessful || RenderThread is not { IsAlive: true });
if (!endSuccessful)
{
MarkFailed("the render thread terminated unexpectedly.");
CancelRender();
yield break;
}
var result = BuildResult(success: true); var result = BuildResult(success: true);
TriggerOnComplete(result); TriggerOnComplete(result);
@@ -849,6 +924,7 @@ internal class RenderManager : MonoBehaviour
AudioMixer?.Reset(); AudioMixer = null; AudioMixer?.Reset(); AudioMixer = null;
SyncCalculator.Reset(); SyncCalculator.Reset();
AudioSpectrumGenerator.Clear();
RenderContext.Reset(); RenderContext.Reset();
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor"); ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
+1
View File
@@ -5,6 +5,7 @@
"ui.render_config.preset_name_placeholder": "New preset name...", "ui.render_config.preset_name_placeholder": "New preset name...",
"ui.render_config.preset_save": "Save", "ui.render_config.preset_save": "Save",
"ui.render_config.preset_delete": "Delete", "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_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_overwrite_desc": "A preset named '{0}' already exists. Do you want to overwrite it?",
"ui.render_config.preset_delete_title": "Delete Preset", "ui.render_config.preset_delete_title": "Delete Preset",
+2 -1
View File
@@ -5,6 +5,7 @@
"ui.render_config.preset_name_placeholder": "새 프리셋 이름...", "ui.render_config.preset_name_placeholder": "새 프리셋 이름...",
"ui.render_config.preset_save": "저장", "ui.render_config.preset_save": "저장",
"ui.render_config.preset_delete": "삭제", "ui.render_config.preset_delete": "삭제",
"ui.render_config.preset_delete_confirm": "삭제 확인",
"ui.render_config.preset_overwrite_title": "프리셋 덮어쓰기", "ui.render_config.preset_overwrite_title": "프리셋 덮어쓰기",
"ui.render_config.preset_overwrite_desc": "'{0}' 프리셋이 이미 존재합니다. 덮어쓰시겠습니까?", "ui.render_config.preset_overwrite_desc": "'{0}' 프리셋이 이미 존재합니다. 덮어쓰시겠습니까?",
"ui.render_config.preset_delete_title": "프리셋 삭제", "ui.render_config.preset_delete_title": "프리셋 삭제",
@@ -23,7 +24,7 @@
"ui.render_config.audio_encoding": "오디오 인코딩 설정", "ui.render_config.audio_encoding": "오디오 인코딩 설정",
"ui.render_config.global_settings": "전역 설정", "ui.render_config.global_settings": "전역 설정",
"ui.render_config.advanced_settings": "고급 설정", "ui.render_config.advanced_settings": "고급 설정",
"ui.render_config.go_render": "렌더 시작", "ui.render_config.go_render": "렌더링하러 가다",
"ui.render_config.codec": "코덱", "ui.render_config.codec": "코덱",
"ui.render_config.rate_control": "비트레이트 제어 모드", "ui.render_config.rate_control": "비트레이트 제어 모드",
"ui.render_config.bitrate": "비트레이트 (kbps)", "ui.render_config.bitrate": "비트레이트 (kbps)",
+1 -1
View File
@@ -9,7 +9,7 @@ using MelonLoader.Utils;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; 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")] [assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
namespace ADOFAIRenderer; namespace ADOFAIRenderer;
+84 -158
View File
@@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using MelonLoader;
using O5Kit.Core;
using O5Kit.Factory;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityEngine.UIElements; using UnityEngine.UIElements;
using Button = UnityEngine.UI.Button;
using Image = UnityEngine.UI.Image; using Image = UnityEngine.UI.Image;
namespace ADOFAIRenderer.UI; namespace ADOFAIRenderer.UI;
@@ -29,6 +31,7 @@ internal class CommonWarningDialogUI : MonoBehaviour
{ {
public static CommonWarningDialogUI? Instance { get; private set; } public static CommonWarningDialogUI? Instance { get; private set; }
private readonly List<VisualElement> _disabledElements = new(); private readonly List<VisualElement> _disabledElements = new();
private O5Window? _window;
private void Awake() private void Awake()
{ {
@@ -37,6 +40,9 @@ internal class CommonWarningDialogUI : MonoBehaviour
private void OnDestroy() private void OnDestroy()
{ {
_window?.Dispose();
_window = null;
foreach (var elem in _disabledElements) foreach (var elem in _disabledElements)
{ {
if (elem != null) if (elem != null)
@@ -87,9 +93,8 @@ internal class CommonWarningDialogUI : MonoBehaviour
// Canvas setup // Canvas setup
var canvas = gameObject.AddComponent<Canvas>(); var canvas = gameObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 32766; // Just below FFmpegDownloadUI (32767) canvas.sortingOrder = 32766;
// Responsive Canvas Scaler setup
var scaler = gameObject.AddComponent<CanvasScaler>(); var scaler = gameObject.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920, 1080); scaler.referenceResolution = new Vector2(1920, 1080);
@@ -98,13 +103,15 @@ internal class CommonWarningDialogUI : MonoBehaviour
gameObject.AddComponent<GraphicRaycaster>(); gameObject.AddComponent<GraphicRaycaster>();
// 1. Create full screen overlay background var ctx = RendererUIManager.Context;
var bgPanelObj = new GameObject("BackgroundPanel");
// 1. Create full screen backdrop (scrim) to block background input
var bgPanelObj = new GameObject("Backdrop");
bgPanelObj.transform.SetParent(transform, false); bgPanelObj.transform.SetParent(transform, false);
bgPanelObj.layer = 5; bgPanelObj.layer = 5;
var bgImage = bgPanelObj.AddComponent<Image>(); 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; bgImage.raycastTarget = true;
var bgRect = bgPanelObj.GetComponent<RectTransform>(); var bgRect = bgPanelObj.GetComponent<RectTransform>();
@@ -113,172 +120,91 @@ internal class CommonWarningDialogUI : MonoBehaviour
bgRect.offsetMin = Vector2.zero; bgRect.offsetMin = Vector2.zero;
bgRect.offsetMax = 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 // 3. Build content inside window.Content
var modalContainerObj = new GameObject("ModalContainer"); var content = _window.Content;
modalContainerObj.transform.SetParent(bgPanelObj.transform, false);
modalContainerObj.layer = 5;
var modalImage = modalContainerObj.AddComponent<Image>(); var vLayout = content.gameObject.AddComponent<VerticalLayoutGroup>();
modalImage.color = new Color(0.08f, 0.08f, 0.1f, 0.88f); // Matching background tone of existing UI 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>(); // Description text
modalOutline.effectColor = Color.white; var descText = O5Factory.ControlText(ctx, content, 18f, noPad: true);
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>();
descText.text = description; 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.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>(); // ReSharper disable once InconsistentNaming
descRect.anchorMin = new Vector2(0, 0.5f); var descLE = descText.gameObject.AddComponent<LayoutElement>();
descRect.anchorMax = new Vector2(1, 1); descLE.flexibleWidth = 1f;
descRect.pivot = new Vector2(0.5f, 0.5f); descLE.flexibleHeight = 1f;
descRect.anchoredPosition = new Vector2(0, -65);
descRect.sizeDelta = new Vector2(-40, 120);
// 5. Create button container // Button action row
var buttonContainerObj = new GameObject("ButtonContainer", typeof(RectTransform)); var btnRowObj = new GameObject("ButtonRow");
buttonContainerObj.transform.SetParent(modalContainerObj.transform, false); btnRowObj.transform.SetParent(content, false);
buttonContainerObj.layer = 5; var btnRowRect = btnRowObj.AddComponent<RectTransform>();
var btnRowLE = btnRowObj.AddComponent<LayoutElement>();
btnRowLE.preferredHeight = 40f;
btnRowLE.flexibleWidth = 1f;
var btnContainerRect = buttonContainerObj.GetComponent<RectTransform>(); var hLayout = btnRowObj.AddComponent<HorizontalLayoutGroup>();
btnContainerRect.anchorMin = new Vector2(0, 0); hLayout.spacing = 10f;
btnContainerRect.anchorMax = new Vector2(1, 0); hLayout.childAlignment = TextAnchor.MiddleCenter;
btnContainerRect.pivot = new Vector2(0.5f, 0); hLayout.childControlWidth = true;
btnContainerRect.anchoredPosition = new Vector2(0, 35); hLayout.childControlHeight = true;
btnContainerRect.sizeDelta = new Vector2(0, 50); hLayout.childForceExpandWidth = true;
hLayout.childForceExpandHeight = true;
// 6. Dynamic button layout and styling for (var i = 0; i < buttons.Length; i++)
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++)
{ {
var btnData = buttons[i]; var btnData = buttons[i];
var btnObj = new GameObject($"Button_{i}"); var btn = O5Factory.Button(
btnObj.transform.SetParent(buttonContainerObj.transform, false); ctx: ctx,
btnObj.layer = 5; parent: btnRowRect,
onClick: () =>
{
try
{
btnData.OnClick();
}
catch (Exception ex)
{
Melon<RenderMod>.Logger.Error($"Dialog Button Click Event Error: {ex}");
}
Destroy(gameObject);
},
text: btnData.Text,
id: $"btn_dialog_{i}",
height: 40f
);
// Define default colors if (btnData.BackgroundColor.HasValue)
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(() =>
{ {
try btn.NormalColor = btnData.BackgroundColor.Value;
{ btn.UpdateVisual();
btnData.OnClick(); }
}
catch (Exception ex)
{
MelonLoader.Melon<RenderMod>.Logger.Error($"Dialog Button Click Event Error: {ex}");
}
Destroy(gameObject);
});
var btnRect = btnObj.GetComponent<RectTransform>(); var le = btn.Rect.gameObject.AddComponent<LayoutElement>();
btnRect.anchorMin = new Vector2(0.5f, 0.5f); le.flexibleWidth = 1f;
btnRect.anchorMax = new Vector2(0.5f, 0.5f); le.preferredHeight = 40f;
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)
{
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;
}
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);
}
} }
+124 -182
View File
@@ -4,11 +4,13 @@ using System.IO.Compression;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MelonLoader;
using MelonLoader.Utils;
using O5Kit.Core;
using O5Kit.Factory;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using MelonLoader;
using MelonLoader.Utils;
namespace ADOFAIRenderer.UI; namespace ADOFAIRenderer.UI;
@@ -22,6 +24,7 @@ internal class FFmpegDownloadUI : MonoBehaviour
private GameObject _statusTextObj = null!; private GameObject _statusTextObj = null!;
private TextMeshProUGUI _statusText = null!; private TextMeshProUGUI _statusText = null!;
private O5Window? _window;
private bool _isDownloading; private bool _isDownloading;
private string DownloadUrl => Application.platform switch private string DownloadUrl => Application.platform switch
{ {
@@ -104,13 +107,15 @@ internal class FFmpegDownloadUI : MonoBehaviour
gameObject.AddComponent<GraphicRaycaster>(); gameObject.AddComponent<GraphicRaycaster>();
// 1. Create full screen overlay background var ctx = RendererUIManager.Context;
var bgPanelObj = new GameObject("BackgroundPanel");
// 1. Create full screen backdrop
var bgPanelObj = new GameObject("Backdrop");
bgPanelObj.transform.SetParent(transform, false); bgPanelObj.transform.SetParent(transform, false);
bgPanelObj.layer = 5; bgPanelObj.layer = 5;
var bgImage = bgPanelObj.AddComponent<Image>(); 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; bgImage.raycastTarget = true;
var bgRect = bgPanelObj.GetComponent<RectTransform>(); var bgRect = bgPanelObj.GetComponent<RectTransform>();
@@ -119,51 +124,31 @@ internal class FFmpegDownloadUI : MonoBehaviour
bgRect.offsetMin = Vector2.zero; bgRect.offsetMin = Vector2.zero;
bgRect.offsetMax = 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 content = _window.Content;
var modalContainerObj = new GameObject("ModalContainer");
modalContainerObj.transform.SetParent(bgPanelObj.transform, false);
modalContainerObj.layer = 5;
var modalImage = modalContainerObj.AddComponent<Image>(); var vLayout = content.gameObject.AddComponent<VerticalLayoutGroup>();
modalImage.color = new Color(0.08f, 0.08f, 0.1f, 0.88f); 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>(); // 3. Description text
modalOutline.effectColor = Color.white; var descText = O5Factory.ControlText(ctx, content, 18f, noPad: true);
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>();
var descStrBuilder = new StringBuilder(); var descStrBuilder = new StringBuilder();
descStrBuilder.AppendLine("FFmpeg shared libraries are required to render video."); descStrBuilder.AppendLine("FFmpeg shared libraries are required to render video.");
if (Application.platform == RuntimePlatform.OSXPlayer) 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("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>"); descStrBuilder.AppendLine("<color=#aaaaaa><size=13>(Download size: ~70MB, extracted to UserLibs folder)</size></color>");
} }
descText.text = descStrBuilder.ToString(); 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.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>(); var descLE = descText.gameObject.AddComponent<LayoutElement>();
descRect.anchorMin = new Vector2(0, 0.5f); descLE.flexibleWidth = 1f;
descRect.anchorMax = new Vector2(1, 1); descLE.flexibleHeight = 1f;
descRect.pivot = new Vector2(0.5f, 0.5f);
descRect.anchoredPosition = new Vector2(0, -60);
descRect.sizeDelta = new Vector2(-40, 100);
// 6. Create button container // 4. Button container
_buttonContainerObj = new GameObject("ButtonContainer", typeof(RectTransform)); _buttonContainerObj = new GameObject("ButtonContainer");
_buttonContainerObj.transform.SetParent(modalContainerObj.transform, false); _buttonContainerObj.transform.SetParent(content, false);
_buttonContainerObj.layer = 5; _buttonContainerObj.layer = 5;
var btnContainerRect = _buttonContainerObj.GetComponent<RectTransform>(); var btnContainerLE = _buttonContainerObj.AddComponent<LayoutElement>();
btnContainerRect.anchorMin = new Vector2(0, 0); btnContainerLE.preferredHeight = 40f;
btnContainerRect.anchorMax = new Vector2(1, 0); btnContainerLE.flexibleWidth = 1f;
btnContainerRect.pivot = new Vector2(0.5f, 0);
btnContainerRect.anchoredPosition = new Vector2(0, 35);
btnContainerRect.sizeDelta = new Vector2(0, 50);
// 7. Create download button var btnLayout = _buttonContainerObj.AddComponent<HorizontalLayoutGroup>();
var downloadBtnObj = new GameObject("DownloadButton"); btnLayout.spacing = 10f;
downloadBtnObj.transform.SetParent(_buttonContainerObj.transform, false); btnLayout.childAlignment = TextAnchor.MiddleCenter;
downloadBtnObj.layer = 5; btnLayout.childControlWidth = true;
btnLayout.childControlHeight = true;
btnLayout.childForceExpandWidth = true;
btnLayout.childForceExpandHeight = true;
var downloadImg = downloadBtnObj.AddComponent<Image>(); Action onDownloadClick = Application.platform != RuntimePlatform.OSXPlayer
downloadImg.color = new Color(0.0f, 0.8f, 1.0f, 1.0f); // Sleek cyan/blue ? StartDownloadProcess
downloadImg.raycastTarget = true; : () => { Application.OpenURL("https://ffmpeg.org"); Destroy(gameObject); };
var downloadBtn = downloadBtnObj.AddComponent<Button>(); var downloadBtn = O5Factory.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)); ctx: ctx,
if (Application.platform != RuntimePlatform.OSXPlayer) downloadBtn.onClick.AddListener(StartDownloadProcess); parent: _buttonContainerObj.transform,
else onClick: onDownloadClick,
{ text: Application.platform != RuntimePlatform.OSXPlayer ? "Download & Install" : "Open Website",
downloadBtn.onClick.AddListener(OpenFFmpegWebsite); id: "btn_ffmpeg_download",
void OpenFFmpegWebsite() { Application.OpenURL("https://ffmpeg.org"); Destroy(gameObject); } height: 40f
} );
downloadBtn.NormalColor = ctx.Theme.ObjectActiveBright;
downloadBtn.UpdateVisual();
var downloadBtnRect = downloadBtnObj.GetComponent<RectTransform>(); var downloadBtnLE = downloadBtn.Rect.gameObject.AddComponent<LayoutElement>();
downloadBtnRect.anchorMin = new Vector2(0.5f, 0.5f); downloadBtnLE.flexibleWidth = 1f;
downloadBtnRect.anchorMax = new Vector2(0.5f, 0.5f); downloadBtnLE.preferredHeight = 40f;
downloadBtnRect.pivot = new Vector2(1f, 0.5f);
downloadBtnRect.anchoredPosition = new Vector2(-15, 0);
downloadBtnRect.sizeDelta = new Vector2(180, 45);
var downloadTextObj = new GameObject("DownloadText"); var cancelBtn = O5Factory.Button(
downloadTextObj.transform.SetParent(downloadBtnObj.transform, false); ctx: ctx,
downloadTextObj.layer = 5; 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>(); // 5. Progress bar container
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
_progressBarObj = new GameObject("ProgressBarContainer"); _progressBarObj = new GameObject("ProgressBarContainer");
_progressBarObj.transform.SetParent(modalContainerObj.transform, false); _progressBarObj.transform.SetParent(content, false);
_progressBarObj.layer = 5; _progressBarObj.layer = 5;
_progressBarObj.SetActive(false); _progressBarObj.SetActive(false);
var pbLE = _progressBarObj.AddComponent<LayoutElement>();
pbLE.preferredHeight = 24f;
pbLE.flexibleWidth = 1f;
var barBgImg = _progressBarObj.AddComponent<Image>(); 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>(); var barOutlineObj = new GameObject("BarOutline");
barBgOutline.effectColor = Color.white; barOutlineObj.transform.SetParent(_progressBarObj.transform, false);
barBgOutline.effectDistance = new Vector2(3.0f, -3.0f); barOutlineObj.layer = 5;
var barRect = _progressBarObj.GetComponent<RectTransform>(); var barOutlineImg = barOutlineObj.AddComponent<Image>();
barRect.anchorMin = new Vector2(0.5f, 0); barOutlineImg.sprite = ctx.Sprites.RoundedOutline;
barRect.anchorMax = new Vector2(0.5f, 0); barOutlineImg.type = Image.Type.Sliced;
barRect.pivot = new Vector2(0.5f, 0); barOutlineImg.color = ctx.Theme.ControlOutlineIdle;
barRect.anchoredPosition = new Vector2(0, 75); barOutlineImg.raycastTarget = false;
barRect.sizeDelta = new Vector2(480, 24);
// 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 = new GameObject("ProgressBarFill");
_progressBarFillObj.transform.SetParent(_progressBarObj.transform, false); _progressBarFillObj.transform.SetParent(_progressBarObj.transform, false);
_progressBarFillObj.layer = 5; _progressBarFillObj.layer = 5;
var fillImg = _progressBarFillObj.AddComponent<Image>(); 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 = _progressBarFillObj.GetComponent<RectTransform>();
_fillRect.anchorMin = new Vector2(0, 0); _fillRect.anchorMin = new Vector2(0, 0);
@@ -317,20 +267,17 @@ internal class FFmpegDownloadUI : MonoBehaviour
_fillRect.anchoredPosition = Vector2.zero; _fillRect.anchoredPosition = Vector2.zero;
_fillRect.sizeDelta = Vector2.zero; _fillRect.sizeDelta = Vector2.zero;
// 11. Create progress text // Progress text
var progressTextObj = new GameObject("ProgressBarText"); var progressTextObj = new GameObject("ProgressBarText");
progressTextObj.transform.SetParent(_progressBarObj.transform, false); progressTextObj.transform.SetParent(_progressBarObj.transform, false);
progressTextObj.layer = 5; progressTextObj.layer = 5;
_progressText = progressTextObj.AddComponent<TextMeshProUGUI>(); _progressText = progressTextObj.AddComponent<TextMeshProUGUI>();
_progressText.fontSize = 16; _progressText.fontSize = 13;
_progressText.font = font; _progressText.font = ctx.Fonts.Regular;
_progressText.color = Color.white; _progressText.color = Color.white;
_progressText.alignment = TextAlignmentOptions.Center; _progressText.alignment = TextAlignmentOptions.Center;
_progressText.raycastTarget = false;
var progressTextOutline = progressTextObj.AddComponent<Outline>();
progressTextOutline.effectColor = new Color(0, 0, 0, 0.8f);
progressTextOutline.effectDistance = new Vector2(1, -1);
var progressTextRect = progressTextObj.GetComponent<RectTransform>(); var progressTextRect = progressTextObj.GetComponent<RectTransform>();
progressTextRect.anchorMin = Vector2.zero; progressTextRect.anchorMin = Vector2.zero;
@@ -338,35 +285,28 @@ internal class FFmpegDownloadUI : MonoBehaviour
progressTextRect.offsetMin = Vector2.zero; progressTextRect.offsetMin = Vector2.zero;
progressTextRect.offsetMax = Vector2.zero; progressTextRect.offsetMax = Vector2.zero;
// 12. Create status text // 6. Status text
_statusTextObj = new GameObject("StatusText"); _statusTextObj = new GameObject("StatusText");
_statusTextObj.transform.SetParent(modalContainerObj.transform, false); _statusTextObj.transform.SetParent(content, false);
_statusTextObj.layer = 5; _statusTextObj.layer = 5;
_statusTextObj.SetActive(false); _statusTextObj.SetActive(false);
var statusLE = _statusTextObj.AddComponent<LayoutElement>();
statusLE.preferredHeight = 22f;
statusLE.flexibleWidth = 1f;
_statusText = _statusTextObj.AddComponent<TextMeshProUGUI>(); _statusText = _statusTextObj.AddComponent<TextMeshProUGUI>();
_statusText.fontSize = 16; _statusText.fontSize = 14;
_statusText.font = font; _statusText.font = ctx.Fonts.Regular;
_statusText.color = new Color(0.8f, 0.8f, 0.8f, 1f); _statusText.color = new Color(0.8f, 0.8f, 0.8f, 1f);
_statusText.alignment = TextAlignmentOptions.Center; _statusText.alignment = TextAlignmentOptions.Center;
_statusText.raycastTarget = false;
var statusRect = _statusTextObj.GetComponent<RectTransform>(); var statusRect = _statusTextObj.GetComponent<RectTransform>();
statusRect.anchorMin = new Vector2(0, 0); statusRect.anchorMin = Vector2.zero;
statusRect.anchorMax = new Vector2(1, 0); statusRect.anchorMax = Vector2.one;
statusRect.pivot = new Vector2(0.5f, 0); statusRect.offsetMin = Vector2.zero;
statusRect.anchoredPosition = new Vector2(0, 35); statusRect.offsetMax = Vector2.zero;
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;
} }
private void Update() private void Update()
@@ -643,6 +583,8 @@ internal class FFmpegDownloadUI : MonoBehaviour
private void OnDestroy() private void OnDestroy()
{ {
_window?.Dispose();
_window = null;
_cts?.Dispose(); _cts?.Dispose();
if (Instance == this) if (Instance == this)
{ {
+112
View File
@@ -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;
}
}
+375 -181
View File
@@ -26,6 +26,10 @@ internal static class RenderConfigWindow
private static O5InputField? _presetNameInput; private static O5InputField? _presetNameInput;
private static O5Button? _savePresetButton; private static O5Button? _savePresetButton;
private static O5Button? _deletePresetButton; private static O5Button? _deletePresetButton;
// ReSharper disable once InconsistentNaming
private static LayoutElement? _deletePresetButtonLE;
private static bool _isDeletePending;
private static Coroutine? _deleteResetCoroutine;
private static string? _lastLoadedPresetName; private static string? _lastLoadedPresetName;
// Controls cache for validation & sync // Controls cache for validation & sync
@@ -87,14 +91,20 @@ internal static class RenderConfigWindow
Size = new Vector2(900, 600), Draggable = true, Resizable = true, Size = new Vector2(900, 600), Draggable = true, Resizable = true,
ShowCloseButton = true ShowCloseButton = true
}); });
if (_configWindow.TitleText != null)
{
LocalizedText.Bind(_configWindow.TitleText, "ui.render_config.window_title");
}
_configWindow.CloseRequested += win => _configWindow.CloseRequested += win =>
{ {
CloseAllPickers(); CloseAllPickers();
ResetDeletePendingState();
win.Rect.gameObject.SetActive(false); win.Rect.gameObject.SetActive(false);
}; };
BuildUI(_configWindow.Content); BuildUI(_configWindow.Content);
LocalizationManager.OnLanguageChanged += OnLanguageChanged;
} }
else else
{ {
@@ -145,14 +155,9 @@ internal static class RenderConfigWindow
}; };
// Presets (Collapsible Card) // Presets (Collapsible Card)
var (_, presetContent) = O5Factory.Card( var (_, presetContent) = CreateCard(
ctx, scrollContent, ctx, scrollContent,
LocalizationManager.Get("ui.render_config.presets"), "ui.render_config.presets"
activeValue: true,
onActiveChanged: null,
onDeleteClick: null,
showDeleteButton: false,
showActiveToggle: false
); );
var presets = PresetManager.GetPresets(); var presets = PresetManager.GetPresets();
@@ -177,7 +182,7 @@ internal static class RenderConfigWindow
_presetDropdown = CreateDropdownField( _presetDropdown = CreateDropdownField(
ctx, presetContent, ctx, presetContent,
LocalizationManager.Get("ui.render_config.preset_select"), "ui.render_config.preset_select",
initialPresetName, initialPresetName, initialPresetName, initialPresetName,
presetNames, p => p, presetNames, p => p,
"dd_presets", "dd_presets",
@@ -213,18 +218,24 @@ internal static class RenderConfigWindow
icon: null, icon: null,
id: "input_preset_name" 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>(); var inputLE = _presetNameInput.Rect.GetComponent<LayoutElement>() ?? _presetNameInput.Rect.gameObject.AddComponent<LayoutElement>();
inputLE.flexibleWidth = 1f; inputLE.flexibleWidth = 1f;
inputLE.preferredHeight = 40f; inputLE.preferredHeight = 40f;
_savePresetButton = O5Factory.Button( _savePresetButton = CreateButton(
ctx: ctx, ctx: ctx,
parent: actionsRowRect, parent: actionsRowRect,
onClick: () => OnSavePresetClicked(config), textKey: "ui.render_config.preset_save",
text: LocalizationManager.Get("ui.render_config.preset_save"),
id: "btn_preset_save", id: "btn_preset_save",
onClick: () => OnSavePresetClicked(config),
height: 40f height: 40f
); );
// ReSharper disable once InconsistentNaming
var saveLE = _savePresetButton.Rect.GetComponent<LayoutElement>() ?? _savePresetButton.Rect.gameObject.AddComponent<LayoutElement>(); var saveLE = _savePresetButton.Rect.GetComponent<LayoutElement>() ?? _savePresetButton.Rect.gameObject.AddComponent<LayoutElement>();
saveLE.preferredWidth = 90f; saveLE.preferredWidth = 90f;
saveLE.flexibleWidth = 0f; saveLE.flexibleWidth = 0f;
@@ -241,104 +252,97 @@ internal static class RenderConfigWindow
fonts: ctx.Fonts, fonts: ctx.Fonts,
tween: ctx.Tween tween: ctx.Tween
); );
_deletePresetButton = O5Factory.Button( _deletePresetButton = CreateButton(
ctx: redCtx, ctx: redCtx,
parent: actionsRowRect, parent: actionsRowRect,
onClick: () => OnDeletePresetClicked(config), textKey: "ui.render_config.preset_delete",
text: LocalizationManager.Get("ui.render_config.preset_delete"),
id: "btn_preset_delete", id: "btn_preset_delete",
onClick: () => OnDeletePresetClicked(config),
height: 40f height: 40f
); );
var deleteLE = _deletePresetButton.Rect.GetComponent<LayoutElement>() ?? _deletePresetButton.Rect.gameObject.AddComponent<LayoutElement>(); if (_deletePresetButton.Label != null)
deleteLE.preferredWidth = 90f; {
deleteLE.flexibleWidth = 0f; LocalizedText.Bind(_deletePresetButton.Label, () =>
deleteLE.preferredHeight = 40f; _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(); UpdateDeleteButtonState();
// 1-3. Top section: 좌/우 컨테이너 1개씩으로 3행을 쌓는다 (중앙선 1개) // 1-3. Top section: 좌/우 컨테이너 1개씩으로 3행을 쌓는다 (중앙선 1개)
var (topLeft, topRight) = CreateTwoColumnPanel(scrollContent, spacing: 8f, columnSpacing: 10f); var (topLeft, topRight) = CreateTwoColumnPanel(scrollContent, spacing: 8f, columnSpacing: 10f);
_renderFpsSlider = O5Factory.Slider( _renderFpsSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topLeft), ctx: ctx, parent: CreateFieldSlot(topLeft),
labelKey: "ui.render_config.render_fps", id: "slider_render_fps",
defaultValue: 60, min: 0.01, max: 3000, value: 60, defaultValue: 60, min: 0.01, max: 3000, value: 60,
format: "F3", clampMode: ClampMode.All, format: "F3", clampMode: ClampMode.All,
sliderFilter: value => Snap(renderFpsSnap, value), sliderFilter: value => Snap(renderFpsSnap, value),
inputFilter: null, onComplete: null, onChanged: val => { config.RenderFPS = val; MarkConfigModified(); }, onChanged: val => { config.RenderFPS = val; MarkConfigModified(); }
text: LocalizationManager.Get("ui.render_config.render_fps"),
id: "slider_render_fps", showFill: true
); );
_videoFPSSlider = O5Factory.Slider( _videoFPSSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topRight), ctx: ctx, parent: CreateFieldSlot(topRight),
labelKey: "ui.render_config.video_fps", id: "slider_video_fps",
defaultValue: 60, min: 0.01, max: 600, value: 60, defaultValue: 60, min: 0.01, max: 600, value: 60,
format: "F3", clampMode: ClampMode.All, format: "F3", clampMode: ClampMode.All,
sliderFilter: value => Snap(videoFpsSnap, value), sliderFilter: value => Snap(videoFpsSnap, value),
inputFilter: null, onComplete: null, onChanged: val => { config.VideoFPS = val; MarkConfigModified(); }, onChanged: val => { config.VideoFPS = val; MarkConfigModified(); }
text: LocalizationManager.Get("ui.render_config.video_fps"),
id: "slider_video_fps", showFill: true
); );
_renderWidthSlider = O5Factory.Slider( _renderWidthSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topLeft), ctx: ctx, parent: CreateFieldSlot(topLeft),
labelKey: "ui.render_config.render_width", id: "slider_render_width",
defaultValue: 2560, min: 0, max: 15360, value: 2560, defaultValue: 2560, min: 0, max: 15360, value: 2560,
format: "F0", clampMode: ClampMode.All, format: "F0", clampMode: ClampMode.All,
sliderFilter: value => Snap(renderWidthSnap, value), inputFilter: value => Math.Max(value, 2), sliderFilter: value => Snap(renderWidthSnap, value), inputFilter: value => Math.Max(value, 2),
onComplete: null, onChanged: val => { config.RenderWidth = (int)Math.Round(val); MarkConfigModified(); }, onChanged: val => { config.RenderWidth = (int)Math.Round(val); MarkConfigModified(); }
text: LocalizationManager.Get("ui.render_config.render_width"),
id: "slider_render_width", showFill: true
); );
_renderHeightSlider = O5Factory.Slider( _renderHeightSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topRight), ctx: ctx, parent: CreateFieldSlot(topRight),
labelKey: "ui.render_config.render_height", id: "slider_render_height",
defaultValue: 1440, min: 0, max: 8640, value: 1440, defaultValue: 1440, min: 0, max: 8640, value: 1440,
format: "F0", clampMode: ClampMode.All, format: "F0", clampMode: ClampMode.All,
sliderFilter: value => Snap(renderHeightSnap, value), inputFilter: value => Math.Max(value, 2), sliderFilter: value => Snap(renderHeightSnap, value), inputFilter: value => Math.Max(value, 2),
onComplete: null, onChanged: val => { config.RenderHeight = (int)Math.Round(val); MarkConfigModified(); }, onChanged: val => { config.RenderHeight = (int)Math.Round(val); MarkConfigModified(); }
text: LocalizationManager.Get("ui.render_config.render_height"),
id: "slider_render_height", showFill: true
); );
_delayAfterEndSlider = O5Factory.Slider( _delayAfterEndSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(topLeft), 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, defaultValue: 15, min: 0, max: 9999.999, value: 15,
format: "F3", clampMode: ClampMode.All, format: "F3", clampMode: ClampMode.All,
onChanged: val => { config.DelayAfterEnd = val; MarkConfigModified(); }
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
); );
_showJudgementToggle = CreateToggleField( _showJudgementToggle = CreateToggleField(
ctx, CreateFieldSlot(topRight), "", ctx, CreateFieldSlot(topRight),
false, config.ShowJudgementText, labelKey: "",
LocalizationManager.Get("ui.render_config.show_judgement"), defaultValue: false,
"toggle_show_judgement", value: config.ShowJudgementText,
val => { config.ShowJudgementText = val; MarkConfigModified(); }, toggleTextKey: "ui.render_config.show_judgement",
id: "toggle_show_judgement",
onChanged: val => { config.ShowJudgementText = val; MarkConfigModified(); },
generateLabel: false generateLabel: false
); );
// 4. Video Encoding Config (Collapsible Card) // 4. Video Encoding Config (Collapsible Card)
var (_, videoContent) = O5Factory.Card( var (_, videoContent) = CreateCard(
ctx, scrollContent, ctx, scrollContent,
LocalizationManager.Get("ui.render_config.video_encoding"), "ui.render_config.video_encoding"
activeValue: true,
onActiveChanged: null,
onDeleteClick: null,
showDeleteButton: false,
showActiveToggle: false
); );
var (videoLeft, videoRight) = CreateTwoColumnPanel(videoContent); var (videoLeft, videoRight) = CreateTwoColumnPanel(videoContent);
var codecs = (Codec[])Enum.GetValues(typeof(Codec)); var codecs = (Codec[])Enum.GetValues(typeof(Codec));
_codecDropdown = CreateDropdownField( _codecDropdown = CreateDropdownField(
ctx, CreateFieldSlot(videoLeft), ctx, CreateFieldSlot(videoLeft),
LocalizationManager.Get("ui.render_config.codec"), "ui.render_config.codec",
Codec.H264, config.VideoConfig.Codec, Codec.H264, config.VideoConfig.Codec,
codecs, c => c.ToString(), codecs, c => c.ToString(),
"dd_video_codec", "dd_video_codec",
@@ -351,7 +355,7 @@ internal static class RenderConfigWindow
_bitrateInput = CreateInputField( _bitrateInput = CreateInputField(
ctx, CreateFieldSlot(videoLeft), ctx, CreateFieldSlot(videoLeft),
LocalizationManager.Get("ui.render_config.bitrate"), "ui.render_config.bitrate",
"30000", config.VideoConfig.Bitrate.ToString(), "30000", config.VideoConfig.Bitrate.ToString(),
"input_bitrate", out _bitrateLabel, "input_bitrate", out _bitrateLabel,
TMPro.TMP_InputField.CharacterValidation.Integer, TMPro.TMP_InputField.CharacterValidation.Integer,
@@ -360,7 +364,7 @@ internal static class RenderConfigWindow
_qpInput = CreateInputField( _qpInput = CreateInputField(
ctx, CreateFieldSlot(videoLeft), ctx, CreateFieldSlot(videoLeft),
LocalizationManager.Get("ui.render_config.qp"), "ui.render_config.qp",
"18", config.VideoConfig.QP.ToString(), "18", config.VideoConfig.QP.ToString(),
"input_qp", out _qpLabel, "input_qp", out _qpLabel,
TMPro.TMP_InputField.CharacterValidation.Integer, TMPro.TMP_InputField.CharacterValidation.Integer,
@@ -370,7 +374,7 @@ internal static class RenderConfigWindow
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode)); var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
_rateControlDropdown = CreateDropdownField( _rateControlDropdown = CreateDropdownField(
ctx, CreateFieldSlot(videoRight), ctx, CreateFieldSlot(videoRight),
LocalizationManager.Get("ui.render_config.rate_control"), "ui.render_config.rate_control",
RateControlMode.CBR, config.VideoConfig.RateControlMode, RateControlMode.CBR, config.VideoConfig.RateControlMode,
rateModes, m => m.ToString(), rateModes, m => m.ToString(),
"dd_rate_control", "dd_rate_control",
@@ -384,7 +388,7 @@ internal static class RenderConfigWindow
_maxBitrateInput = CreateInputField( _maxBitrateInput = CreateInputField(
ctx, CreateFieldSlot(videoRight), ctx, CreateFieldSlot(videoRight),
LocalizationManager.Get("ui.render_config.max_bitrate"), "ui.render_config.max_bitrate",
"50000", config.VideoConfig.MaxBitrate.ToString(), "50000", config.VideoConfig.MaxBitrate.ToString(),
"input_max_bitrate", out _maxBitrateLabel, "input_max_bitrate", out _maxBitrateLabel,
TMPro.TMP_InputField.CharacterValidation.Integer, TMPro.TMP_InputField.CharacterValidation.Integer,
@@ -392,61 +396,55 @@ internal static class RenderConfigWindow
); );
_forceSoftwareToggle = CreateToggleField( _forceSoftwareToggle = CreateToggleField(
ctx, CreateFieldSlot(videoRight), "", ctx, CreateFieldSlot(videoRight),
false, config.VideoConfig.ForceSoftwareEncoder, labelKey: "",
LocalizationManager.Get("ui.render_config.force_software"), defaultValue: false,
"toggle_force_software", value: config.VideoConfig.ForceSoftwareEncoder,
val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); }, toggleTextKey: "ui.render_config.force_software",
id: "toggle_force_software",
onChanged: val => { config.VideoConfig.ForceSoftwareEncoder = val; MarkConfigModified(); },
generateLabel: true generateLabel: true
); );
_keyframeIntervalSlider = O5Factory.Slider( _keyframeIntervalSlider = CreateSlider(
ctx: ctx, parent: videoContent, ctx: ctx, parent: videoContent,
labelKey: "ui.render_config.keyframe_interval", id: "slider_keyframe_interval",
defaultValue: 2, min: 0.1, max: 10, value: 2, defaultValue: 2, min: 0.1, max: 10, value: 2,
format: "F1", clampMode: ClampMode.All, format: "F1", clampMode: ClampMode.All,
onChanged: val => { config.VideoConfig.KeyframeInterval = val; MarkConfigModified(); }
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
); );
// 5. Audio Encoding Config (Collapsible Card) // 5. Audio Encoding Config (Collapsible Card)
var (_, audioContent) = O5Factory.Card( var (_, audioContent) = CreateCard(
ctx, scrollContent, ctx, scrollContent,
LocalizationManager.Get("ui.render_config.audio_encoding"), "ui.render_config.audio_encoding"
activeValue: true,
onActiveChanged: null,
onDeleteClick: null,
showDeleteButton: false,
showActiveToggle: false
); );
var (audioLeft, audioRight) = CreateTwoColumnPanel(audioContent); var (audioLeft, audioRight) = CreateTwoColumnPanel(audioContent);
_renderAudioToggle = CreateToggleField( _renderAudioToggle = CreateToggleField(
ctx, CreateFieldSlot(audioLeft), "", ctx, CreateFieldSlot(audioLeft),
true, config.AudioConfig.RenderAudio, labelKey: "",
LocalizationManager.Get("ui.render_config.render_audio"), defaultValue: true,
"toggle_render_audio", value: config.AudioConfig.RenderAudio,
val => { config.AudioConfig.RenderAudio = val; MarkConfigModified(); }, toggleTextKey: "ui.render_config.render_audio",
id: "toggle_render_audio",
onChanged: val => { config.AudioConfig.RenderAudio = val; MarkConfigModified(); },
generateLabel: false generateLabel: false
); );
_audioBitrateSlider = O5Factory.Slider( _audioBitrateSlider = CreateSlider(
ctx: ctx, parent: CreateFieldSlot(audioRight), ctx: ctx, parent: CreateFieldSlot(audioRight),
labelKey: "ui.render_config.audio_bitrate", id: "slider_audio_bitrate",
defaultValue: 256, min: 64, max: 384, value: 256, defaultValue: 256, min: 64, max: 384, value: 256,
format: "F0", clampMode: ClampMode.All, format: "F0", clampMode: ClampMode.All,
sliderFilter: value => Snap(audioBitrateSnap, value), sliderFilter: value => Snap(audioBitrateSnap, value),
inputFilter: null, onComplete: null, onChanged: val => { config.AudioConfig.AudioBitrate = (int)Math.Round(val) * 1000; MarkConfigModified(); }, 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
); );
var channels = new[] { 1, 2 }; var channels = new[] { 1, 2 };
_channelsDropdown = CreateDropdownField( _channelsDropdown = CreateDropdownField(
ctx, CreateFieldSlot(audioLeft), ctx, CreateFieldSlot(audioLeft),
LocalizationManager.Get("ui.render_config.channels"), "ui.render_config.channels",
2, config.AudioConfig.AudioChannels, 2, config.AudioConfig.AudioChannels,
channels, c => c == 1 ? "1 (Mono)" : "2 (Stereo)", channels, c => c == 1 ? "1 (Mono)" : "2 (Stereo)",
"dd_channels", "dd_channels",
@@ -455,7 +453,7 @@ internal static class RenderConfigWindow
_sampleRateDropdown = CreateDropdownField( _sampleRateDropdown = CreateDropdownField(
ctx, CreateFieldSlot(audioRight), ctx, CreateFieldSlot(audioRight),
LocalizationManager.Get("ui.render_config.sample_rate"), "ui.render_config.sample_rate",
48000, config.AudioConfig.AudioSampleRate, 48000, config.AudioConfig.AudioSampleRate,
CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[config.VideoConfig.Codec]], r => $"{r/1000.0} kHz", CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[config.VideoConfig.Codec]], r => $"{r/1000.0} kHz",
"dd_sample_rate", "dd_sample_rate",
@@ -463,19 +461,14 @@ internal static class RenderConfigWindow
); );
// 6. Global Settings (Collapsible Card) // 6. Global Settings (Collapsible Card)
var (_, globalContent) = O5Factory.Card( var (_, globalContent) = CreateCard(
ctx, scrollContent, ctx, scrollContent,
LocalizationManager.Get("ui.render_config.global_settings"), "ui.render_config.global_settings"
activeValue: true,
onActiveChanged: null,
onDeleteClick: null,
showDeleteButton: false,
showActiveToggle: false
); );
_outputDirInput = CreateFullWidthInputField( _outputDirInput = CreateFullWidthInputField(
ctx, globalContent, ctx, globalContent,
LocalizationManager.Get("ui.render_config.output_dir"), "ui.render_config.output_dir",
GlobalSettings.DefaultOutputDirectory, GlobalSettings.DefaultOutputDirectory,
GlobalSettings.EffectiveOutputDirectory, GlobalSettings.EffectiveOutputDirectory,
"input_output_dir", "input_output_dir",
@@ -495,8 +488,10 @@ internal static class RenderConfigWindow
); );
var (globalLeft, globalRight) = CreateTwoColumnPanel(globalContent); var (globalLeft, globalRight) = CreateTwoColumnPanel(globalContent);
O5Factory.Button( CreateButton(
ctx, globalLeft, ctx, globalLeft,
"ui.render_config.browse",
"btn_browse_output",
() => () =>
{ {
var initial = string.IsNullOrWhiteSpace(_outputDirInput.Value) var initial = string.IsNullOrWhiteSpace(_outputDirInput.Value)
@@ -512,39 +507,32 @@ internal static class RenderConfigWindow
GlobalSettings.Save(); GlobalSettings.Save();
} }
}); });
}, }
LocalizationManager.Get("ui.render_config.browse"),
"btn_browse_output"
); );
O5Factory.Button( CreateButton(
ctx, globalRight, ctx, globalRight,
"ui.render_config.reset_default",
"btn_reset_output",
() => () =>
{ {
_outputDirInput.Set(GlobalSettings.DefaultOutputDirectory); _outputDirInput.Set(GlobalSettings.DefaultOutputDirectory);
GlobalSettings.Current.OutputDirectory = null; GlobalSettings.Current.OutputDirectory = null;
GlobalSettings.Save(); GlobalSettings.Save();
}, }
LocalizationManager.Get("ui.render_config.reset_default"),
"btn_reset_output"
); );
// 7. Advanced Settings (Collapsible Card) // 7. Advanced Settings (Collapsible Card)
var (_, advContent) = O5Factory.Card( var (_, advContent) = CreateCard(
ctx, scrollContent, ctx, scrollContent,
LocalizationManager.Get("ui.render_config.advanced_settings"), "ui.render_config.advanced_settings"
activeValue: true,
onActiveChanged: null,
onDeleteClick: null,
showDeleteButton: false,
showActiveToggle: false
); );
var (advLeft, advRight) = CreateTwoColumnPanel(advContent); var (advLeft, advRight) = CreateTwoColumnPanel(advContent);
var extensions = new[] { "mkv", "mp4", "mov", "webm", "avi" }; var extensions = new[] { "mkv", "mp4", "mov", "webm", "avi" };
_fileExtensionDropdown = CreateDropdownField( _fileExtensionDropdown = CreateDropdownField(
ctx, CreateFieldSlot(advLeft), ctx, CreateFieldSlot(advLeft),
LocalizationManager.Get("ui.render_config.file_extension"), "ui.render_config.file_extension",
"mkv", config.AdvancedConfig.FileExtension, "mkv", config.AdvancedConfig.FileExtension,
extensions, e => e, extensions, e => e,
"dd_adv_file_extension", "dd_adv_file_extension",
@@ -556,11 +544,13 @@ internal static class RenderConfigWindow
); );
_processAudioToggle = CreateToggleField( _processAudioToggle = CreateToggleField(
ctx, CreateFieldSlot(advLeft), "", ctx, CreateFieldSlot(advLeft),
true, config.AdvancedConfig.ProcessAudio, labelKey: "",
LocalizationManager.Get("ui.render_config.process_audio"), defaultValue: true,
"toggle_process_audio", value: config.AdvancedConfig.ProcessAudio,
val => toggleTextKey: "ui.render_config.process_audio",
id: "toggle_process_audio",
onChanged: val =>
{ {
config.AdvancedConfig.ProcessAudio = val; config.AdvancedConfig.ProcessAudio = val;
MarkConfigModified(); MarkConfigModified();
@@ -570,7 +560,7 @@ internal static class RenderConfigWindow
_randomSeedInput = CreateInputField( _randomSeedInput = CreateInputField(
ctx, CreateFieldSlot(advRight), ctx, CreateFieldSlot(advRight),
LocalizationManager.Get("ui.render_config.random_seed"), "ui.render_config.random_seed",
"", config.AdvancedConfig.RandomSeed?.ToString() ?? "", "", config.AdvancedConfig.RandomSeed?.ToString() ?? "",
"input_random_seed", "input_random_seed",
TMPro.TMP_InputField.CharacterValidation.Integer, TMPro.TMP_InputField.CharacterValidation.Integer,
@@ -583,11 +573,13 @@ internal static class RenderConfigWindow
); );
_nonTileToggle = CreateToggleField( _nonTileToggle = CreateToggleField(
ctx, CreateFieldSlot(advRight), "", ctx, CreateFieldSlot(advRight),
false, config.AdvancedConfig.NonTile, labelKey: "",
LocalizationManager.Get("ui.render_config.non_tile"), defaultValue: false,
"toggle_non_tile", value: config.AdvancedConfig.NonTile,
val => toggleTextKey: "ui.render_config.non_tile",
id: "toggle_non_tile",
onChanged: val =>
{ {
config.AdvancedConfig.NonTile = val; config.AdvancedConfig.NonTile = val;
MarkConfigModified(); MarkConfigModified();
@@ -596,9 +588,7 @@ internal static class RenderConfigWindow
); );
// 7-1. Planet Color Settings (Optional) // 7-1. Planet Color Settings (Optional)
var planetColorsLabel = O5Factory.ControlText(ctx, advContent, 13f, noPad: true); var planetColorsLabel = CreateLabel(ctx, advContent, "ui.render_config.planet_colors");
planetColorsLabel.text = LocalizationManager.Get("ui.render_config.planet_colors");
planetColorsLabel.color = ctx.Theme.TextDim;
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
var planetLabelLE = planetColorsLabel.gameObject.AddComponent<LayoutElement>(); var planetLabelLE = planetColorsLabel.gameObject.AddComponent<LayoutElement>();
planetLabelLE.preferredHeight = 18f; planetLabelLE.preferredHeight = 18f;
@@ -609,11 +599,13 @@ internal static class RenderConfigWindow
var fireSlot = CreateFieldSlot(fireCol); var fireSlot = CreateFieldSlot(fireCol);
_firePlanetToggle = CreateToggleField( _firePlanetToggle = CreateToggleField(
ctx, fireSlot, "", ctx, fireSlot,
false, config.AdvancedConfig.firePlanetColor.HasValue, labelKey: "",
LocalizationManager.Get("ui.render_config.fire_planet"), defaultValue: false,
"toggle_fire_planet", value: config.AdvancedConfig.firePlanetColor.HasValue,
val => toggleTextKey: "ui.render_config.fire_planet",
id: "toggle_fire_planet",
onChanged: val =>
{ {
_firePlanetPicker?.SetBlocked(!val); _firePlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.firePlanetColor = val ? _firePlanetPicker?.Value : null; config.AdvancedConfig.firePlanetColor = val ? _firePlanetPicker?.Value : null;
@@ -643,11 +635,13 @@ internal static class RenderConfigWindow
var iceSlot = CreateFieldSlot(iceCol); var iceSlot = CreateFieldSlot(iceCol);
_icePlanetToggle = CreateToggleField( _icePlanetToggle = CreateToggleField(
ctx, iceSlot, "", ctx, iceSlot,
false, config.AdvancedConfig.icePlanetColor.HasValue, labelKey: "",
LocalizationManager.Get("ui.render_config.ice_planet"), defaultValue: false,
"toggle_ice_planet", value: config.AdvancedConfig.icePlanetColor.HasValue,
val => toggleTextKey: "ui.render_config.ice_planet",
id: "toggle_ice_planet",
onChanged: val =>
{ {
_icePlanetPicker?.SetBlocked(!val); _icePlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.icePlanetColor = val ? _icePlanetPicker?.Value : null; config.AdvancedConfig.icePlanetColor = val ? _icePlanetPicker?.Value : null;
@@ -677,11 +671,13 @@ internal static class RenderConfigWindow
var windSlot = CreateFieldSlot(windCol); var windSlot = CreateFieldSlot(windCol);
_windPlanetToggle = CreateToggleField( _windPlanetToggle = CreateToggleField(
ctx, windSlot, "", ctx, windSlot,
false, config.AdvancedConfig.windPlanetColor.HasValue, labelKey: "",
LocalizationManager.Get("ui.render_config.wind_planet"), defaultValue: false,
"toggle_wind_planet", value: config.AdvancedConfig.windPlanetColor.HasValue,
val => toggleTextKey: "ui.render_config.wind_planet",
id: "toggle_wind_planet",
onChanged: val =>
{ {
_windPlanetPicker?.SetBlocked(!val); _windPlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.windPlanetColor = val ? _windPlanetPicker?.Value : null; config.AdvancedConfig.windPlanetColor = val ? _windPlanetPicker?.Value : null;
@@ -710,20 +706,17 @@ internal static class RenderConfigWindow
} }
// 8. Go Render Button // 8. Go Render Button
O5Factory.Button( CreateButton(
ctx, scrollContent, ctx, scrollContent,
TryGoRender, "ui.render_config.go_render",
LocalizationManager.Get("ui.render_config.go_render"),
"btn_go_render", "btn_go_render",
onClick: TryGoRender,
height: 48f height: 48f
); );
// 9. Version Label // 9. Version Label
var versionLabel = O5Factory.ControlText(ctx, scrollContent, 12f, noPad: true); var versionLabel = O5Factory.ControlText(ctx, scrollContent, 12f, noPad: true);
var versionText = $"ADOFAI Renderer v{Melon<RenderMod>.Instance.Info.Version}"; LocalizedText.Bind(versionLabel, GetVersionText);
if (DateTime.UtcNow >= RenderMod.ExpirationTime) versionText += " " + LocalizationManager.Get("ui.render_config.expired");
versionLabel.text = versionText;
versionLabel.alignment = TMPro.TextAlignmentOptions.Center; versionLabel.alignment = TMPro.TextAlignmentOptions.Center;
versionLabel.color = ctx.Theme.TextDim; versionLabel.color = ctx.Theme.TextDim;
@@ -926,6 +919,7 @@ internal static class RenderConfigWindow
private static void OnPresetSelected(string? selectedName, RenderConfig config) private static void OnPresetSelected(string? selectedName, RenderConfig config)
{ {
ResetDeletePendingState();
if (string.IsNullOrEmpty(selectedName) || selectedName == CustomPresetLabel) return; if (string.IsNullOrEmpty(selectedName) || selectedName == CustomPresetLabel) return;
var preset = PresetManager.GetPresets().FirstOrDefault(p => string.Equals(p.Name, selectedName, StringComparison.OrdinalIgnoreCase)); var preset = PresetManager.GetPresets().FirstOrDefault(p => string.Equals(p.Name, selectedName, StringComparison.OrdinalIgnoreCase));
if (preset == null) return; if (preset == null) return;
@@ -953,7 +947,7 @@ internal static class RenderConfigWindow
{ {
SyncUItoConfig(config); SyncUItoConfig(config);
var name = _presetNameInput?.Value?.Trim(); var name = _presetNameInput?.Value.Trim();
if (string.IsNullOrWhiteSpace(name) || name == CustomPresetLabel) if (string.IsNullOrWhiteSpace(name) || name == CustomPresetLabel)
{ {
CommonWarningDialogUI.ShowDialog( CommonWarningDialogUI.ShowDialog(
@@ -1010,20 +1004,62 @@ internal static class RenderConfigWindow
} }
} }
var ctx = RendererUIManager.Context; if (!_isDeletePending)
CommonWarningDialogUI.ShowDialog( {
LocalizationManager.Get("ui.render_config.preset_delete_title"), _isDeletePending = true;
LocalizationManager.Get("ui.render_config.preset_delete_desc", targetPresetName), if (_deletePresetButtonLE != null) _deletePresetButtonLE.preferredWidth = 110f;
new DialogButton( if (_deletePresetButton != null)
LocalizationManager.Get("ui.render_config.confirm"), {
() => DoDeletePreset(targetPresetName, config), if (_deletePresetButton.Label != null)
ctx.Theme.SoftRed {
), _deletePresetButton.Label.text = LocalizationManager.Get("ui.render_config.preset_delete_confirm");
new DialogButton( }
LocalizationManager.Get("ui.render_config.cancel"), _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) private static void DoDeletePreset(string name, RenderConfig config)
@@ -1048,6 +1084,7 @@ internal static class RenderConfigWindow
private static void ReloadPresetDropdown(string? selectName = null) private static void ReloadPresetDropdown(string? selectName = null)
{ {
ResetDeletePendingState();
var presets = PresetManager.GetPresets(); var presets = PresetManager.GetPresets();
var names = presets.Select(p => p.Name).ToList(); var names = presets.Select(p => p.Name).ToList();
if (names.Count == 0) if (names.Count == 0)
@@ -1087,6 +1124,26 @@ internal static class RenderConfigWindow
_deletePresetButton?.SetBlocked(!canDelete); _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) private static void ShowValidationError(string message)
{ {
Melon<RenderMod>.Logger.Error(message); Melon<RenderMod>.Logger.Error(message);
@@ -1189,23 +1246,139 @@ internal static class RenderConfigWindow
return slotRect; 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( private static O5InputField CreateInputField(
O5Context ctx, O5Context ctx,
Transform colParent, Transform colParent,
string labelText, string labelKey,
string? defaultValue, string? defaultValue,
string value, string value,
string id, string id,
TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None, TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None,
Action<string>? onChanged = null) 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( private static O5InputField CreateInputField(
O5Context ctx, O5Context ctx,
Transform colParent, Transform colParent,
string labelText, string labelKey,
string? defaultValue, string? defaultValue,
string value, string value,
string id, string id,
@@ -1214,8 +1387,9 @@ internal static class RenderConfigWindow
Action<string>? onChanged = null) Action<string>? onChanged = null)
{ {
label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true); label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
label.text = labelText; label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim; label.color = ctx.Theme.TextDim;
LocalizedText.Bind(label, labelKey);
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
var labelLE = label.gameObject.AddComponent<LayoutElement>(); var labelLE = label.gameObject.AddComponent<LayoutElement>();
@@ -1228,11 +1402,16 @@ internal static class RenderConfigWindow
defaultValue: defaultValue, defaultValue: defaultValue,
value: value, value: value,
onChanged: onChanged, onChanged: onChanged,
placeholder: labelText, placeholder: LocalizationManager.Get(labelKey),
icon: null, icon: null,
id: id id: id
); );
if (inputField.Placeholder != null)
{
LocalizedText.Bind(inputField.Placeholder, labelKey);
}
inputField.InputField.characterValidation = characterValidation; inputField.InputField.characterValidation = characterValidation;
return inputField; return inputField;
} }
@@ -1291,7 +1470,7 @@ internal static class RenderConfigWindow
private static O5InputField CreateFullWidthInputField( private static O5InputField CreateFullWidthInputField(
O5Context ctx, O5Context ctx,
Transform parent, Transform parent,
string labelText, string labelKey,
string? defaultValue, string? defaultValue,
string value, string value,
string id, string id,
@@ -1309,8 +1488,9 @@ internal static class RenderConfigWindow
layout.childForceExpandHeight = false; layout.childForceExpandHeight = false;
var label = O5Factory.ControlText(ctx, containerRect, 13f, noPad: true); var label = O5Factory.ControlText(ctx, containerRect, 13f, noPad: true);
label.text = labelText; label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim; label.color = ctx.Theme.TextDim;
LocalizedText.Bind(label, labelKey);
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
var labelLE = label.gameObject.AddComponent<LayoutElement>(); var labelLE = label.gameObject.AddComponent<LayoutElement>();
@@ -1323,21 +1503,26 @@ internal static class RenderConfigWindow
defaultValue: defaultValue, defaultValue: defaultValue,
value: value, value: value,
onChanged: onChanged, onChanged: onChanged,
placeholder: labelText, placeholder: LocalizationManager.Get(labelKey),
icon: null, icon: null,
id: id id: id
); );
if (input.Placeholder != null)
{
LocalizedText.Bind(input.Placeholder, labelKey);
}
return input; return input;
} }
private static O5Toggle CreateToggleField( private static O5Toggle CreateToggleField(
O5Context ctx, O5Context ctx,
Transform colParent, Transform colParent,
string labelText, string labelKey,
bool? defaultValue, bool? defaultValue,
bool value, bool value,
string toggleText, string toggleTextKey,
string id, string id,
Action<bool>? onChanged = null, Action<bool>? onChanged = null,
bool generateLabel = true) bool generateLabel = true)
@@ -1345,8 +1530,9 @@ internal static class RenderConfigWindow
if (generateLabel) if (generateLabel)
{ {
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true); var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
label.text = labelText; label.text = string.IsNullOrEmpty(labelKey) ? "" : LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim; label.color = ctx.Theme.TextDim;
LocalizedText.Bind(label, labelKey);
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
var labelLE = label.gameObject.AddComponent<LayoutElement>(); var labelLE = label.gameObject.AddComponent<LayoutElement>();
@@ -1354,21 +1540,28 @@ internal static class RenderConfigWindow
labelLE.minHeight = 18f; labelLE.minHeight = 18f;
} }
return O5Factory.Toggle( var toggle = O5Factory.Toggle(
ctx: ctx, ctx: ctx,
parent: colParent, parent: colParent,
defaultValue: defaultValue, defaultValue: defaultValue,
value: value, value: value,
onChanged: onChanged, onChanged: onChanged,
text: toggleText, text: !string.IsNullOrEmpty(toggleTextKey) ? LocalizationManager.Get(toggleTextKey) : "",
id: id id: id
); );
if (toggle.Label != null && !string.IsNullOrEmpty(toggleTextKey))
{
LocalizedText.Bind(toggle.Label, toggleTextKey);
}
return toggle;
} }
private static O5Dropdown<T> CreateDropdownField<T>( private static O5Dropdown<T> CreateDropdownField<T>(
O5Context ctx, O5Context ctx,
Transform colParent, Transform colParent,
string labelText, string labelKey,
T defaultValue, T defaultValue,
T value, T value,
IReadOnlyList<T> values, IReadOnlyList<T> values,
@@ -1377,8 +1570,9 @@ internal static class RenderConfigWindow
Action<T>? onChanged = null) Action<T>? onChanged = null)
{ {
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true); var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
label.text = labelText; label.text = LocalizationManager.Get(labelKey);
label.color = ctx.Theme.TextDim; label.color = ctx.Theme.TextDim;
LocalizedText.Bind(label, labelKey);
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
var labelLE = label.gameObject.AddComponent<LayoutElement>(); var labelLE = label.gameObject.AddComponent<LayoutElement>();
+1 -1
View File
@@ -108,7 +108,7 @@ internal static class AudioSpectrumGenerator
return; return;
} }
var currentFrame = (RenderManager.Instance != null) ? RenderManager.Instance.frameCount : -1; var currentFrame = RenderManager.Instance ? RenderManager.Instance.frameCount : -1;
lock (CachedSpectrums) lock (CachedSpectrums)
{ {
+1 -1
View File
@@ -140,7 +140,7 @@ namespace ADOFAIRenderer.Util
} }
} }
if (RenderManager.Instance && RenderManager.Instance.IsRenderCancelled) if (RenderManager.Instance && RenderManager.Instance.IsRenderAborted)
{ {
RecycleState(state); RecycleState(state);
return; return;
+1 -1
View File
@@ -121,7 +121,7 @@ internal static class GPUUtil
!int.TryParse(regMatch.Groups[4].Value, out var build)) continue; !int.TryParse(regMatch.Groups[4].Value, out var build)) continue;
var nvidiaVerInt = sub % 10 * 10000 + build; var nvidiaVerInt = sub % 10 * 10000 + build;
_cachedNvidiaVersion = nvidiaVerInt / 100f; _cachedNvidiaVersion = nvidiaVerInt / 100.0;
Melon<RenderMod>.Logger.Msg($"Parsed NVIDIA Driver Version from Registry (RegistryUtil): {_cachedNvidiaVersion:F2}"); Melon<RenderMod>.Logger.Msg($"Parsed NVIDIA Driver Version from Registry (RegistryUtil): {_cachedNvidiaVersion:F2}");
return _cachedNvidiaVersion; return _cachedNvidiaVersion;
} }