From 22770258297f147ea2503b0e1bade3a21f540d33d630b9c667e9e679339b15a4 Mon Sep 17 00:00:00 2001 From: copecone Date: Mon, 5 Oct 2026 13:49:02 +0900 Subject: [PATCH] Implement O5Kit --- .gitignore | 3 +- ADOFAIRenderer.csproj | 3 ++ Localization/Locale/en.json | 84 +---------------------------- Localization/Locale/ko.json | 84 +---------------------------- Localization/LocalizationManager.cs | 4 +- UI/RenderUI.cs | 6 --- UI/RendererUIManager.cs | 39 ++++++++++++++ UI/Window/RenderConfigWindow.cs | 51 ++++++++++++++++++ 8 files changed, 99 insertions(+), 175 deletions(-) delete mode 100644 UI/RenderUI.cs create mode 100644 UI/RendererUIManager.cs create mode 100644 UI/Window/RenderConfigWindow.cs diff --git a/.gitignore b/.gitignore index 8b8c619..e63010a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ GEMINI.md # Developer Description Files /AsyncInputAutoplayFix.md -/AudioMixerExplanation.md \ No newline at end of file +/AudioMixerExplanation.md +/O5Theme.md \ No newline at end of file diff --git a/ADOFAIRenderer.csproj b/ADOFAIRenderer.csproj index 386f650..24bc8c5 100644 --- a/ADOFAIRenderer.csproj +++ b/ADOFAIRenderer.csproj @@ -16,6 +16,9 @@ $(ADOFAI_PATH)\MelonLoader\net35\0Harmony.dll + + $(ADOFAI_PATH)\UserLibs\O5Kit.dll + $(ADOFAI_PATH)\A Dance of Fire and Ice_Data\Managed\Assembly-CSharp.dll diff --git a/Localization/Locale/en.json b/Localization/Locale/en.json index f6a667e..6287a43 100644 --- a/Localization/Locale/en.json +++ b/Localization/Locale/en.json @@ -1,85 +1,3 @@ { - "ui.button.presets": "Presets", - "ui.placeholder.render_fps": "Render FPS", - "ui.placeholder.video_fps": "Video FPS", - "ui.placeholder.render_width": "Render Width (px)", - "ui.placeholder.render_height": "Render Height (px)", - "ui.button.video_config": "Video Encoding Config", - "ui.button.audio_config": "Audio Encoding Config", - "ui.placeholder.delay_after_end": "Delay After Level End", - "ui.toggle.show_judgement": "Show Judgement", - "ui.button.go_render": "Go Render", - "ui.button.advanced_settings": "Advanced Settings...", - "ui.version.expired": " (Expired)", - "ui.validation.required_fields": "Required input fields are empty or invalid. Please fill in all settings.", - - "ui.advanced.title": "Advanced Settings", - "ui.advanced.file_extension": "File Extension", - "ui.advanced.process_audio": "Process Audio", - "ui.advanced.non_tile_mode": "Non-Tile Mode", - "ui.advanced.random_seed": "Random Seed (Optional)", - "ui.button.cancel": "Cancel", - "ui.button.save": "Save", - - "ui.video.title": "Video Encoding Settings", - "ui.video.codec": "Codec", - "ui.video.rate_control": "Bitrate Control Mode", - "ui.video.bitrate": "Bitrate (kbps)", - "ui.video.max_bitrate": "Max Bitrate (kbps)", - "ui.video.qp": "Quality (QP)", - "ui.video.keyframe_interval": "Keyframe Interval (s)", - "ui.video.force_software": "Force Software Encoder", - "ui.video.qp_zero_warning": "Warning: Setting QP to 0 applies lossless encoding and may result in very large file sizes.", - - "ui.audio.title": "Audio Encoding Settings", - "ui.audio.rendering": "Audio Rendering", - "ui.audio.sample_rate": "Sample Rate (Hz)", - "ui.audio.channels": "Audio Channels", - "ui.audio.channels_mono": "1 (Mono)", - "ui.audio.channels_stereo": "2 (Stereo)", - "ui.audio.bitrate": "Audio Bitrate (kbps)", - - "ui.preset.title": "Preset Settings", - "ui.preset.new_name": "New Preset Name", - "ui.preset.no_presets": "No saved presets.", - "ui.button.load": "Load", - "ui.button.delete": "Delete", - "ui.button.close": "Close", - "ui.preset.save_failed": "Save Failed", - "ui.preset.enter_name": "Please enter a preset name.", - "ui.preset.invalid_inputs": "Cannot save preset because required input values are empty or invalid.", - "ui.preset.save_error": "An error occurred while saving the preset: {0}", - "ui.preset.delete_title": "Delete Preset", - "ui.preset.delete_confirm": "Are you sure you want to delete the preset '{0}'?", - "ui.button.yes": "Yes", - "ui.button.no": "No", - "ui.button.ok": "OK", - - "ui.ffmpeg.title": "FFmpeg Library Installation Required", - "ui.ffmpeg.desc_main": "FFmpeg shared libraries are required to render video.", - "ui.ffmpeg.desc_osx": "For macOS, reliable public builds are unavailable, so FFmpeg 8.1 must be built manually.\nClick the button below to open the official FFmpeg website.", - "ui.ffmpeg.desc_auto": "Click the button below to automatically download and install from the internet.\n(Download size: ~70MB, extracted to UserLibs folder)", - "ui.ffmpeg.button_download": "Download & Install", - "ui.ffmpeg.button_open_web": "Open Download Page", - "ui.ffmpeg.button_cancel": "Cancel", - "ui.ffmpeg.button_opening": "Opening...", - "ui.ffmpeg.status_downloading": "Downloading... ({0:F1}%)", - "ui.ffmpeg.status_extracting": "Extracting FFmpeg files...", - "ui.ffmpeg.status_done": "Done! Restarting audio/video engine...", - "ui.ffmpeg.status_failed": "Failed to download FFmpeg: {0}", - - "ui.escape.cancelling": "Cancelling in {0:F1}s..\nHold Esc to cancel render", - - "ui.monitor.title": "ADOFAI Renderer Realtime Monitor", - "ui.monitor.queue": "• Queue: {0} / {1} frames", - "ui.monitor.rendered_frames": "• Rendered Frames: {0}", - "ui.monitor.frames_sent": "• Frames Sent: {0}", - "ui.monitor.throttling": "• Throttling Controls: {0} times", - "ui.monitor.readback_failures": "• Readback Hard Failures: {0} times", - "ui.monitor.skip_retry": "• Render Skip-Retry: {0} / {1}", - "ui.monitor.memory": "• Mono Heap Memory: {0:F2} MB", - - "ui.notification.complete_title": "Rendering Complete!", - "ui.notification.complete_msg": "{0} saved successfully.", - "ui.notification.video_fallback": "Video" + "ui.render_config.window_title": "Render Config" } diff --git a/Localization/Locale/ko.json b/Localization/Locale/ko.json index ecc7747..30ee532 100644 --- a/Localization/Locale/ko.json +++ b/Localization/Locale/ko.json @@ -1,85 +1,3 @@ { - "ui.button.presets": "프리셋", - "ui.placeholder.render_fps": "렌더링 FPS", - "ui.placeholder.video_fps": "영상 FPS", - "ui.placeholder.render_width": "렌더링 너비 (px)", - "ui.placeholder.render_height": "렌더링 높이 (px)", - "ui.button.video_config": "비디오 인코딩 설정", - "ui.button.audio_config": "오디오 인코딩 설정", - "ui.placeholder.delay_after_end": "레벨 종료 후 대기 시간 (초)", - "ui.toggle.show_judgement": "판정 텍스트 표시", - "ui.button.go_render": "렌더링 시작", - "ui.button.advanced_settings": "고급 설정...", - "ui.version.expired": " (만료됨)", - "ui.validation.required_fields": "필수 입력란이 비어 있거나 올바르지 않습니다. 모든 설정을 확인해 주세요.", - - "ui.advanced.title": "고급 설정", - "ui.advanced.file_extension": "파일 확장자", - "ui.advanced.process_audio": "오디오 처리", - "ui.advanced.non_tile_mode": "논타일 모드", - "ui.advanced.random_seed": "랜덤 시드 (선택 사항)", - "ui.button.cancel": "취소", - "ui.button.save": "저장", - - "ui.video.title": "비디오 인코딩 설정", - "ui.video.codec": "코덱", - "ui.video.rate_control": "비트레이트 제어 모드", - "ui.video.bitrate": "비트레이트 (kbps)", - "ui.video.max_bitrate": "최대 비트레이트 (kbps)", - "ui.video.qp": "품질 (QP)", - "ui.video.keyframe_interval": "키프레임 간격 (초)", - "ui.video.force_software": "강제 소프트웨어 인코더", - "ui.video.qp_zero_warning": "경고: QP를 0으로 설정하면 무손실 인코딩이 적용되어 파일 크기가 매우 커질 수 있습니다.", - - "ui.audio.title": "오디오 인코딩 설정", - "ui.audio.rendering": "오디오 렌더링", - "ui.audio.sample_rate": "샘플레이트 (Hz)", - "ui.audio.channels": "오디오 채널", - "ui.audio.channels_mono": "1 (모노)", - "ui.audio.channels_stereo": "2 (스테레오)", - "ui.audio.bitrate": "오디오 비트레이트 (kbps)", - - "ui.preset.title": "프리셋 설정", - "ui.preset.new_name": "새 프리셋 이름", - "ui.preset.no_presets": "저장된 프리셋이 없습니다.", - "ui.button.load": "불러오기", - "ui.button.delete": "삭제", - "ui.button.close": "닫기", - "ui.preset.save_failed": "저장 실패", - "ui.preset.enter_name": "프리셋 이름을 입력해 주세요.", - "ui.preset.invalid_inputs": "필수 입력값이 비어 있거나 올바르지 않아 프리셋을 저장할 수 없습니다.", - "ui.preset.save_error": "프리셋 저장 중 오류가 발생했습니다: {0}", - "ui.preset.delete_title": "프리셋 삭제", - "ui.preset.delete_confirm": "정말 '{0}' 프리셋을 삭제하시겠습니까?", - "ui.button.yes": "예", - "ui.button.no": "아니오", - "ui.button.ok": "확인", - - "ui.ffmpeg.title": "FFmpeg 라이브러리 설치 필요", - "ui.ffmpeg.desc_main": "영상을 렌더링하려면 FFmpeg 공유 라이브러리가 필요합니다.", - "ui.ffmpeg.desc_osx": "macOS의 경우 신뢰할 수 있는 빌드가 제공되지 않아 FFmpeg 8.1을 직접 빌드해야 합니다.\n아래 버튼을 눌러 공식 FFmpeg 웹사이트를 방문하세요.", - "ui.ffmpeg.desc_auto": "아래 버튼을 눌러 인터넷에서 자동으로 다운로드하여 설치합니다.\n(다운로드 크기: 약 70MB, UserLibs 폴더에 압축 해제됨)", - "ui.ffmpeg.button_download": "다운로드 및 설치", - "ui.ffmpeg.button_open_web": "다운로드 페이지 열기", - "ui.ffmpeg.button_cancel": "취소", - "ui.ffmpeg.button_opening": "열리는 중...", - "ui.ffmpeg.status_downloading": "다운로드 중... ({0:F1}%)", - "ui.ffmpeg.status_extracting": "FFmpeg 파일 압축 해제 중...", - "ui.ffmpeg.status_done": "완료! 오디오/비디오 엔진을 재시작합니다...", - "ui.ffmpeg.status_failed": "FFmpeg 다운로드 실패: {0}", - - "ui.escape.cancelling": "{0:F1}초 후 취소됩니다..\n렌더링을 취소하려면 Esc를 길게 누르세요", - - "ui.monitor.title": "ADOFAI Renderer 실시간 모니터", - "ui.monitor.queue": "• 대기열: {0} / {1} 프레임", - "ui.monitor.rendered_frames": "• 렌더링된 프레임: {0}", - "ui.monitor.frames_sent": "• 전송된 프레임: {0}", - "ui.monitor.throttling": "• 쓰로틀링 제어: {0}회", - "ui.monitor.readback_failures": "• 리드백 실패: {0}회", - "ui.monitor.skip_retry": "• 렌더 건너뜀-재시도: {0} / {1}", - "ui.monitor.memory": "• 모노 힙 메모리: {0:F2} MB", - - "ui.notification.complete_title": "렌더링 완료!", - "ui.notification.complete_msg": "{0}이(가) 성공적으로 저장되었습니다.", - "ui.notification.video_fallback": "동영상" + "ui.render_config.window_title": "렌더 설정" } diff --git a/Localization/LocalizationManager.cs b/Localization/LocalizationManager.cs index 181f77b..40658cb 100644 --- a/Localization/LocalizationManager.cs +++ b/Localization/LocalizationManager.cs @@ -165,7 +165,7 @@ internal static class LocalizationManager public static string Get(string key, params object[] args) { var template = Get(key); - if (args == null || args.Length == 0) return template; + if (args.Length == 0) return template; try { @@ -185,7 +185,6 @@ internal static class LocalizationManager return language switch { SystemLanguage.Korean => "ko", - SystemLanguage.English => "en", _ => "en" }; } @@ -218,6 +217,7 @@ internal static class LocalizationManager /// /// Convenient shorthand alias for . /// +// ReSharper disable once InconsistentNaming internal static class I18n { /// diff --git a/UI/RenderUI.cs b/UI/RenderUI.cs deleted file mode 100644 index 63aced3..0000000 --- a/UI/RenderUI.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ADOFAIRenderer.UI; - -public class RendererUI -{ - -} \ No newline at end of file diff --git a/UI/RendererUIManager.cs b/UI/RendererUIManager.cs new file mode 100644 index 0000000..593e7c3 --- /dev/null +++ b/UI/RendererUIManager.cs @@ -0,0 +1,39 @@ +using O5Kit.Core; + +namespace ADOFAIRenderer.UI; + +using UnityEngine; + +public class RendererUIManager : MonoBehaviour +{ + public static RendererUIManager Instance { get; private set; } = null!; + + public static O5Context Context = null!; + public O5WindowManager WindowManager = null!; + + private void Awake() { + Instance = this; + + Context = new O5Context( + config: new O5Config { + UIScale = 1.0f, + MiddleClickToDefault = true, + SliderSensitivity = 1.0f + }, + theme: O5Theme.Dark + ); + + WindowManager = O5WindowManager.Create(Context, transform, "RendererMod_Canvas"); + } + + private void Update() { + O5Object.TickAll(); + Context.Tooltip.Tick(); + O5Kit.Input.O5ShortcutManager.HandleUpdate(); + } + + private void OnDestroy() { + WindowManager.Dispose(); + Context.Dispose(); + } +} \ No newline at end of file diff --git a/UI/Window/RenderConfigWindow.cs b/UI/Window/RenderConfigWindow.cs new file mode 100644 index 0000000..33005b8 --- /dev/null +++ b/UI/Window/RenderConfigWindow.cs @@ -0,0 +1,51 @@ +using ADOFAIRenderer.Localization; +using O5Kit.Core; +using O5Kit.Factory; +using UnityEngine; + +namespace ADOFAIRenderer.UI.Window; + +internal static class RenderConfigWindow +{ + private static O5Window? _configWindow; + + // 윈도우 만드는 메서드... + public static void CreateWindow() + { + if (_configWindow == null) + { + _configWindow = RendererUIManager.Instance.WindowManager.Create(new O5WindowOptions + { + Id = "RendererMod_RenderConfig", + Title = LocalizationManager.Get("ui.render_config.window_title"), + Size = new Vector2(580f, 420f), + Draggable = true, + Resizable = true, + ShowCloseButton = true + }); + + _configWindow.CloseRequested += win => + { + win.Rect.gameObject.SetActive(false); + }; + + BuildUI(_configWindow.Content); + } + + SetWindowActive(true); + } + + private static void BuildUI(RectTransform content) + { + var (viewport, scrollContent, scroller) = + O5Factory.ScrollView(RendererUIManager.Context, content, spacing: 10f); + + // TODO: RenderFPS double 입력칸, VideoFPS double 입력칸, RenderWidth int (>0) 입력칸, RenderHeight int (>0) 입력칸, Video Encoding Config, Audio Encoding Config, Delay After Level End double (>=0) 입력칸, Show Judgement Toggle, Global Settings, Advanced Settings, Go Render 버튼 + // NOTE: 한 가로줄에 두개씩 + } + + private static void SetWindowActive(bool enabled) + { + _configWindow?.Rect.gameObject.SetActive(enabled); + } +} \ No newline at end of file