SHA256
forked from ADOFAIEditorForum/ADOFAIRenderer
Add Output Path Config Prototype
This commit is contained in:
1 parent
4097e14e7a
commit
43763b0b35
5 files changed
+477
-11
No files matched your search
@@ -411,7 +411,7 @@ internal class RenderManager : MonoBehaviour
|
|||||||
|
|
||||||
public void SetupCaptureDirectory()
|
public void SetupCaptureDirectory()
|
||||||
{
|
{
|
||||||
var renderDir = Path.Combine(MelonEnvironment.GameRootDirectory, "Render");
|
var renderDir = GlobalSettings.EffectiveOutputDirectory;
|
||||||
FileUtil.CreateDirectory(renderDir);
|
FileUtil.CreateDirectory(renderDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +525,7 @@ internal class RenderManager : MonoBehaviour
|
|||||||
else {
|
else {
|
||||||
var fileName = startTime.ToString("yyyy-MM-dd HH-mm-ss") +
|
var fileName = startTime.ToString("yyyy-MM-dd HH-mm-ss") +
|
||||||
$".{Configuration.AdvancedConfig.FileExtension.TrimStart('.').ToLower()}";
|
$".{Configuration.AdvancedConfig.FileExtension.TrimStart('.').ToLower()}";
|
||||||
CurrentOutPath = Path.Combine(MelonEnvironment.GameRootDirectory, "Render", fileName);
|
CurrentOutPath = Path.Combine(GlobalSettings.EffectiveOutputDirectory, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
FFmpegNativeEncoder? videoFFmpeg = null;
|
FFmpegNativeEncoder? videoFFmpeg = null;
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using MelonLoader;
|
||||||
|
using MelonLoader.Utils;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace ADOFAIRenderer.Data;
|
||||||
|
|
||||||
|
public class GlobalConfig
|
||||||
|
{
|
||||||
|
public string? OutputDirectory { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GlobalSettings
|
||||||
|
{
|
||||||
|
private static string SettingsFilePath =>
|
||||||
|
Path.Combine(MelonEnvironment.GameRootDirectory, "UserData", "ADOFAIRenderer", "global_settings.json");
|
||||||
|
|
||||||
|
public static GlobalConfig Current { get; set; } = new();
|
||||||
|
|
||||||
|
public static string DefaultOutputDirectory =>
|
||||||
|
Path.Combine(MelonEnvironment.GameRootDirectory, "Render");
|
||||||
|
|
||||||
|
public static string EffectiveOutputDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(Current.OutputDirectory) && Directory.Exists(Current.OutputDirectory))
|
||||||
|
{
|
||||||
|
return Current.OutputDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultOutputDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static GlobalSettings()
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Load()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(SettingsFilePath))
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(SettingsFilePath);
|
||||||
|
Current = JsonConvert.DeserializeObject<GlobalConfig>(json) ?? new GlobalConfig();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MelonLogger.Warning($"[GlobalSettings] Failed to load global settings: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = new GlobalConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dir = Path.GetDirectoryName(SettingsFilePath);
|
||||||
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(Current, Formatting.Indented);
|
||||||
|
File.WriteAllText(SettingsFilePath, json);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MelonLogger.Warning($"[GlobalSettings] Failed to save global settings: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using ADOFAIRenderer;
|
using ADOFAIRenderer;
|
||||||
using ADOFAIRenderer.Core;
|
using ADOFAIRenderer.Core;
|
||||||
|
using ADOFAIRenderer.Data;
|
||||||
using ADOFAIRenderer.Util;
|
using ADOFAIRenderer.Util;
|
||||||
using FFmpeg.AutoGen;
|
using FFmpeg.AutoGen;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
@@ -27,6 +28,7 @@ namespace ADOFAIRenderer
|
|||||||
|
|
||||||
public override void OnInitializeMelon()
|
public override void OnInitializeMelon()
|
||||||
{
|
{
|
||||||
|
GlobalSettings.Load();
|
||||||
ffmpeg.RootPath = MelonEnvironment.UserLibsDirectory;
|
ffmpeg.RootPath = MelonEnvironment.UserLibsDirectory;
|
||||||
|
|
||||||
var renderManagerObj = new GameObject("Render Manager");
|
var renderManagerObj = new GameObject("Render Manager");
|
||||||
|
|||||||
+233
-9
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using ADOFAIRenderer.Core;
|
using ADOFAIRenderer.Core;
|
||||||
using ADOFAIRenderer.Data;
|
using ADOFAIRenderer.Data;
|
||||||
using ADOFAIRenderer.Data.Input;
|
using ADOFAIRenderer.Data.Input;
|
||||||
|
using ADOFAIRenderer.Util;
|
||||||
using ADOFAIRenderer.Util.FFmpeg;
|
using ADOFAIRenderer.Util.FFmpeg;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -27,6 +29,8 @@ internal class RenderConfigUIManager : MonoBehaviour
|
|||||||
private Button _audioEncodingConfigButton = null!;
|
private Button _audioEncodingConfigButton = null!;
|
||||||
private VisualElement _audioEncodingConfigContainer = null!;
|
private VisualElement _audioEncodingConfigContainer = null!;
|
||||||
|
|
||||||
|
private Button _globalSettingsButton = null!;
|
||||||
|
private VisualElement? _globalConfigContainer;
|
||||||
private Button _advancedSettingsButton = null!;
|
private Button _advancedSettingsButton = null!;
|
||||||
private VisualElement? _advancedConfigContainer;
|
private VisualElement? _advancedConfigContainer;
|
||||||
private Label _versionLabel = null!;
|
private Label _versionLabel = null!;
|
||||||
@@ -92,19 +96,38 @@ internal class RenderConfigUIManager : MonoBehaviour
|
|||||||
Melon<RenderMod>.Logger.Error("Button 'PresetSettingsButton' not found.");
|
Melon<RenderMod>.Logger.Error("Button 'PresetSettingsButton' not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var bottomRow = CreateRow();
|
var rightBottomContainer = new VisualElement
|
||||||
bottomRow.pickingMode = PickingMode.Ignore;
|
{
|
||||||
bottomRow.style.position = Position.Absolute;
|
name = "RightBottomButtonContainer",
|
||||||
bottomRow.style.right = 0f;
|
pickingMode = PickingMode.Ignore,
|
||||||
bottomRow.style.bottom = 0f;
|
style =
|
||||||
bottomRow.style.justifyContent = Justify.FlexEnd;
|
{
|
||||||
|
position = Position.Absolute,
|
||||||
|
right = 0f,
|
||||||
|
bottom = 0f,
|
||||||
|
flexDirection = FlexDirection.Column,
|
||||||
|
alignItems = Align.FlexEnd
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var globalRow = CreateRow();
|
||||||
|
globalRow.pickingMode = PickingMode.Ignore;
|
||||||
|
globalRow.style.justifyContent = Justify.FlexEnd;
|
||||||
|
_globalSettingsButton = new Button { name = "GlobalSettingsButton", text = "Global Settings..." };
|
||||||
|
globalRow.Add(_globalSettingsButton);
|
||||||
|
rightBottomContainer.Add(globalRow);
|
||||||
|
|
||||||
|
var advancedRow = CreateRow();
|
||||||
|
advancedRow.pickingMode = PickingMode.Ignore;
|
||||||
|
advancedRow.style.justifyContent = Justify.FlexEnd;
|
||||||
_advancedSettingsButton = new Button { name = "AdvancedSettingsButton", text = "Advanced Settings..." };
|
_advancedSettingsButton = new Button { name = "AdvancedSettingsButton", text = "Advanced Settings..." };
|
||||||
bottomRow.Add(_advancedSettingsButton);
|
advancedRow.Add(_advancedSettingsButton);
|
||||||
|
rightBottomContainer.Add(advancedRow);
|
||||||
|
|
||||||
(_root.childCount > 0 ? _root[0] : _root).Add(bottomRow);
|
(_root.childCount > 0 ? _root[0] : _root).Add(rightBottomContainer);
|
||||||
Melon<RenderMod>.Logger.Msg("[UI] AdvancedSettingsButton dynamically created.");
|
Melon<RenderMod>.Logger.Msg("[UI] GlobalSettingsButton and AdvancedSettingsButton dynamically created.");
|
||||||
|
|
||||||
|
_globalSettingsButton.RegisterCallback<ClickEvent>(_ => OpenGlobalConfig());
|
||||||
_advancedSettingsButton.RegisterCallback<ClickEvent>(_ => OpenAdvancedConfig());
|
_advancedSettingsButton.RegisterCallback<ClickEvent>(_ => OpenAdvancedConfig());
|
||||||
|
|
||||||
var versionText =
|
var versionText =
|
||||||
@@ -143,6 +166,207 @@ internal class RenderConfigUIManager : MonoBehaviour
|
|||||||
Melon<RenderMod>.Logger.Msg("[UI] VersionLabel dynamically created.");
|
Melon<RenderMod>.Logger.Msg("[UI] VersionLabel dynamically created.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OpenGlobalConfig()
|
||||||
|
{
|
||||||
|
var tempGlobal = new GlobalConfig
|
||||||
|
{
|
||||||
|
OutputDirectory = GlobalSettings.Current.OutputDirectory
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_globalConfigContainer != null && _root.Contains(_globalConfigContainer))
|
||||||
|
{
|
||||||
|
_root.Remove(_globalConfigContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_globalConfigContainer = new VisualElement
|
||||||
|
{
|
||||||
|
name = "GlobalSettingsContainer",
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
position = Position.Absolute,
|
||||||
|
width = Length.Percent(100),
|
||||||
|
height = Length.Percent(100),
|
||||||
|
left = 0,
|
||||||
|
top = 0,
|
||||||
|
backgroundColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f, 1.0f)),
|
||||||
|
justifyContent = Justify.Center,
|
||||||
|
alignItems = Align.Center
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Title Row
|
||||||
|
var titleRow = CreateRow();
|
||||||
|
var titleLabel = new Label("Global Settings")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
fontSize = 24,
|
||||||
|
color = new StyleColor(Color.white)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
titleRow.Add(titleLabel);
|
||||||
|
_globalConfigContainer.Add(titleRow);
|
||||||
|
|
||||||
|
// 2. Output Path Row
|
||||||
|
var pathRow = CreateRow();
|
||||||
|
var pathLabel = CreateFormLabel("Output Directory");
|
||||||
|
var defaultDir = GlobalSettings.DefaultOutputDirectory;
|
||||||
|
var currentDisplayDir = !string.IsNullOrWhiteSpace(tempGlobal.OutputDirectory)
|
||||||
|
? tempGlobal.OutputDirectory
|
||||||
|
: defaultDir;
|
||||||
|
|
||||||
|
var pathField = new TextField
|
||||||
|
{
|
||||||
|
name = "GlobalOutputDirectory",
|
||||||
|
label = "",
|
||||||
|
value = currentDisplayDir,
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
width = 520f,
|
||||||
|
fontSize = 12f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var textInput = pathField.Q("unity-text-input");
|
||||||
|
if (textInput != null)
|
||||||
|
{
|
||||||
|
textInput.style.backgroundColor = new StyleColor(new Color(0.1f, 0.1f, 0.1f, 1.0f));
|
||||||
|
textInput.style.borderTopLeftRadius = 6f;
|
||||||
|
textInput.style.borderTopRightRadius = 6f;
|
||||||
|
textInput.style.borderBottomLeftRadius = 6f;
|
||||||
|
textInput.style.borderBottomRightRadius = 6f;
|
||||||
|
textInput.style.borderTopWidth = 1f;
|
||||||
|
textInput.style.borderBottomWidth = 1f;
|
||||||
|
textInput.style.borderLeftWidth = 1f;
|
||||||
|
textInput.style.borderRightWidth = 1f;
|
||||||
|
textInput.style.borderTopColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
||||||
|
textInput.style.borderBottomColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
||||||
|
textInput.style.borderLeftColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
||||||
|
textInput.style.borderRightColor = new StyleColor(new Color(0.3f, 0.3f, 0.35f, 0.8f));
|
||||||
|
textInput.style.paddingLeft = 8f;
|
||||||
|
textInput.style.paddingRight = 8f;
|
||||||
|
textInput.style.fontSize = 12f;
|
||||||
|
textInput.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||||
|
textInput.style.color = new StyleColor(Color.white);
|
||||||
|
}
|
||||||
|
|
||||||
|
pathRow.Add(pathLabel);
|
||||||
|
pathRow.Add(pathField);
|
||||||
|
_globalConfigContainer.Add(pathRow);
|
||||||
|
|
||||||
|
// 3. Action Buttons Row (Browse & Reset)
|
||||||
|
var actionRow = CreateRow();
|
||||||
|
actionRow.style.height = StyleKeyword.Auto;
|
||||||
|
actionRow.style.maxHeight = StyleKeyword.Null;
|
||||||
|
actionRow.style.minHeight = StyleKeyword.Null;
|
||||||
|
actionRow.style.marginBottom = 15f;
|
||||||
|
|
||||||
|
var browseBtn = new Button { text = "Browse..." };
|
||||||
|
browseBtn.RegisterCallback<ClickEvent>(_ =>
|
||||||
|
{
|
||||||
|
var initial = string.IsNullOrWhiteSpace(pathField.value) ? defaultDir : pathField.value;
|
||||||
|
FolderDialogUtil.OpenFolderBrowser(initial, selected =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(selected))
|
||||||
|
{
|
||||||
|
pathField.value = selected;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var resetBtn = new Button { text = "Reset to Default" };
|
||||||
|
resetBtn.RegisterCallback<ClickEvent>(_ =>
|
||||||
|
{
|
||||||
|
pathField.value = defaultDir;
|
||||||
|
});
|
||||||
|
|
||||||
|
actionRow.Add(browseBtn);
|
||||||
|
actionRow.Add(resetBtn);
|
||||||
|
_globalConfigContainer.Add(actionRow);
|
||||||
|
|
||||||
|
// 4. Info Row
|
||||||
|
var infoRow = CreateRow();
|
||||||
|
infoRow.style.height = StyleKeyword.Auto;
|
||||||
|
infoRow.style.maxHeight = StyleKeyword.Null;
|
||||||
|
infoRow.style.minHeight = StyleKeyword.Null;
|
||||||
|
infoRow.style.marginBottom = 10f;
|
||||||
|
|
||||||
|
var infoLabel = new Label("Global output directory applies to all renders unless overridden by API.")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
color = new StyleColor(new Color(0.7f, 0.7f, 0.7f)),
|
||||||
|
fontSize = 18,
|
||||||
|
unityTextAlign = TextAnchor.MiddleCenter
|
||||||
|
}
|
||||||
|
};
|
||||||
|
infoRow.Add(infoLabel);
|
||||||
|
_globalConfigContainer.Add(infoRow);
|
||||||
|
|
||||||
|
// 5. Control Button Row (Cancel / Save)
|
||||||
|
var buttonRow = CreateRow();
|
||||||
|
var cancelBtn = new Button { text = "Cancel" };
|
||||||
|
var saveBtn = new Button { text = "Save" };
|
||||||
|
|
||||||
|
buttonRow.Add(cancelBtn);
|
||||||
|
buttonRow.Add(saveBtn);
|
||||||
|
_globalConfigContainer.Add(buttonRow);
|
||||||
|
|
||||||
|
cancelBtn.RegisterCallback<ClickEvent>(_ =>
|
||||||
|
{
|
||||||
|
ToggleGlobalScreens(false);
|
||||||
|
_root.Remove(_globalConfigContainer);
|
||||||
|
_globalConfigContainer = null!;
|
||||||
|
});
|
||||||
|
|
||||||
|
saveBtn.RegisterCallback<ClickEvent>(_ =>
|
||||||
|
{
|
||||||
|
var input = pathField.value?.Trim() ?? string.Empty;
|
||||||
|
if (string.IsNullOrWhiteSpace(input) ||
|
||||||
|
string.Equals(Path.GetFullPath(input).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar),
|
||||||
|
Path.GetFullPath(defaultDir).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar),
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
GlobalSettings.Current.OutputDirectory = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fullPath = Path.GetFullPath(input);
|
||||||
|
if (!Directory.Exists(fullPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(fullPath);
|
||||||
|
}
|
||||||
|
GlobalSettings.Current.OutputDirectory = fullPath;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CommonWarningDialogUI.ShowDialog("Invalid Path", $"Directory path is invalid:\n{ex.Message}", new DialogButton("OK", () => { }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalSettings.Save();
|
||||||
|
|
||||||
|
ToggleGlobalScreens(false);
|
||||||
|
_root.Remove(_globalConfigContainer);
|
||||||
|
_globalConfigContainer = null!;
|
||||||
|
});
|
||||||
|
|
||||||
|
_root.Add(_globalConfigContainer);
|
||||||
|
ToggleGlobalScreens(true);
|
||||||
|
|
||||||
|
void ToggleGlobalScreens(bool showGlobal)
|
||||||
|
{
|
||||||
|
if (_root.childCount > 0)
|
||||||
|
{
|
||||||
|
_root[0].style.display = showGlobal ? DisplayStyle.None : DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
_globalConfigContainer.style.display = showGlobal ? DisplayStyle.Flex : DisplayStyle.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OpenAdvancedConfig()
|
private void OpenAdvancedConfig()
|
||||||
{
|
{
|
||||||
var config = RenderManager.Instance.Configuration;
|
var config = RenderManager.Instance.Configuration;
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MelonLoader;
|
||||||
|
|
||||||
|
namespace ADOFAIRenderer.Util;
|
||||||
|
|
||||||
|
internal static class FolderDialogUtil
|
||||||
|
{
|
||||||
|
public static void OpenFolderBrowser(string initialDirectory, Action<string?> onSelected)
|
||||||
|
{
|
||||||
|
var syncContext = SynchronizationContext.Current;
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
string? selected = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
selected = OpenWindowsFolderBrowser(initialDirectory);
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
selected = OpenMacFolderBrowser(initialDirectory);
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
selected = OpenLinuxFolderBrowser(initialDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MelonLogger.Warning($"[FolderDialogUtil] Failed to open folder browser: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syncContext != null)
|
||||||
|
{
|
||||||
|
syncContext.Post(_ => onSelected(selected), null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onSelected(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? OpenWindowsFolderBrowser(string initialDirectory)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var escapedDir = initialDirectory.Replace("'", "''");
|
||||||
|
var script = $@"
|
||||||
|
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null
|
||||||
|
$f = New-Object System.Windows.Forms.FolderBrowserDialog
|
||||||
|
$f.Description = 'Select Output Directory'
|
||||||
|
if ([System.IO.Directory]::Exists('{escapedDir}')) {{
|
||||||
|
$f.SelectedPath = '{escapedDir}'
|
||||||
|
}}
|
||||||
|
$f.ShowNewFolderButton = $true
|
||||||
|
if ($f.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {{
|
||||||
|
[Console]::Write($f.SelectedPath)
|
||||||
|
}}";
|
||||||
|
|
||||||
|
var bytes = System.Text.Encoding.Unicode.GetBytes(script);
|
||||||
|
var base64 = Convert.ToBase64String(bytes);
|
||||||
|
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "powershell.exe",
|
||||||
|
Arguments = $"-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand {base64}",
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden
|
||||||
|
};
|
||||||
|
|
||||||
|
using var process = Process.Start(startInfo);
|
||||||
|
if (process == null) return null;
|
||||||
|
|
||||||
|
var output = process.StandardOutput.ReadToEnd().Trim();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
return !string.IsNullOrEmpty(output) && Directory.Exists(output) ? output : null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MelonLogger.Warning($"[FolderDialogUtil] Windows folder dialog error: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? OpenMacFolderBrowser(string initialDirectory)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var escaped = initialDirectory.Replace("\"", "\\\"");
|
||||||
|
var script = $"POSIX path of (choose folder with prompt \"Select Output Directory\" default location \"{escaped}\")";
|
||||||
|
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "osascript",
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden
|
||||||
|
};
|
||||||
|
startInfo.ArgumentList.Add("-e");
|
||||||
|
startInfo.ArgumentList.Add(script);
|
||||||
|
|
||||||
|
using var process = Process.Start(startInfo);
|
||||||
|
if (process == null) return null;
|
||||||
|
|
||||||
|
var output = process.StandardOutput.ReadToEnd().Trim();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
return !string.IsNullOrEmpty(output) && Directory.Exists(output) ? output : null;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? OpenLinuxFolderBrowser(string initialDirectory)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "zenity",
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden
|
||||||
|
};
|
||||||
|
startInfo.ArgumentList.Add("--file-selection");
|
||||||
|
startInfo.ArgumentList.Add("--directory");
|
||||||
|
startInfo.ArgumentList.Add("--title=Select Output Directory");
|
||||||
|
if (!string.IsNullOrEmpty(initialDirectory) && Directory.Exists(initialDirectory))
|
||||||
|
{
|
||||||
|
startInfo.ArgumentList.Add($"--filename={initialDirectory}");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var process = Process.Start(startInfo);
|
||||||
|
if (process == null) return null;
|
||||||
|
|
||||||
|
var output = process.StandardOutput.ReadToEnd().Trim();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
return !string.IsNullOrEmpty(output) && Directory.Exists(output) ? output : null;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user