using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.UI.Classes; namespace Penumbra.UI; public partial class ConfigWindow { private partial class SettingsTab { public const int RootDirectoryMaxLength = 64; private readonly ConfigWindow _window; public SettingsTab( ConfigWindow window ) => _window = window; public void Draw() { using var tab = ImRaii.TabItem( "Settings" ); if( !tab ) { return; } using var child = ImRaii.Child( "##SettingsTab", -Vector2.One, false ); if( !child ) { return; } DrawEnabledBox(); DrawShowAdvancedBox(); Checkbox( "Lock Main Window", "Prevent the main window from being resized or moved.", Penumbra.Config.FixMainWindow, v => { Penumbra.Config.FixMainWindow = v; _window.Flags = v ? _window.Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize : _window.Flags & ~( ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize ); } ); ImGui.NewLine(); DrawRootFolder(); DrawRediscoverButton(); ImGui.NewLine(); DrawModSelectorSettings(); DrawColorSettings(); DrawAdvancedSettings(); _dialogManager.Draw(); DrawSupportButtons(); } // Changing the base mod directory. private string? _newModDirectory; private readonly FileDialogManager _dialogManager = SetupFileManager(); private bool _dialogOpen; // For toggling on/off. // Do not change the directory without explicitly pressing enter or this button. // Shows up only if the current input does not correspond to the current directory. private static bool DrawPressEnterWarning( string newName, string old, float width, bool saved ) { using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.PressEnterWarningBg ); var w = new Vector2( width, 0 ); var symbol = '\0'; var (text, valid) = newName.Length > RootDirectoryMaxLength ? ( $"Path is too long. The maximum length is {RootDirectoryMaxLength}.", false ) : newName.Any( c => ( symbol = c ) > ( char )0x7F ) ? ( $"Path contains invalid symbol {symbol}. Only ASCII is allowed.", false ) : ( $"Press Enter or Click Here to Save (Current Directory: {old})", true ); return ( ImGui.Button( text, w ) || saved ) && valid; } // Draw a directory picker button that toggles the directory picker. // Selecting a directory does behave the same as writing in the text input, i.e. needs to be saved. private void DrawDirectoryPickerButton() { if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Folder.ToIconString(), _window._iconButtonSize, "Select a directory via dialog.", false, true ) ) { if( _dialogOpen ) { _dialogManager.Reset(); _dialogOpen = false; } else { _newModDirectory ??= Penumbra.Config.ModDirectory; // Use the current input as start directory if it exists, // otherwise the current mod directory, otherwise the current application directory. var startDir = Directory.Exists( _newModDirectory ) ? _newModDirectory : Directory.Exists( Penumbra.Config.ModDirectory ) ? Penumbra.Config.ModDirectory : "."; _dialogManager.OpenFolderDialog( "Choose Mod Directory", ( b, s ) => { _newModDirectory = b ? s : _newModDirectory; _dialogOpen = false; }, startDir ); _dialogOpen = true; } } } private static void DrawOpenDirectoryButton( int id, DirectoryInfo directory, bool condition ) { using var _ = ImRaii.PushId( id ); var ret = ImGui.Button( "Open Directory" ); ImGuiUtil.HoverTooltip( "Open this directory in your configured file explorer." ); if( ret && condition && Directory.Exists( directory.FullName ) ) { Process.Start( new ProcessStartInfo( directory.FullName ) { UseShellExecute = true, } ); } } // Draw the text input for the mod directory, // as well as the directory picker button and the enter warning. private void DrawRootFolder() { _newModDirectory ??= Penumbra.Config.ModDirectory; var spacing = 3 * ImGuiHelpers.GlobalScale; using var group = ImRaii.Group(); ImGui.SetNextItemWidth( _window._inputTextWidth.X - spacing - _window._iconButtonSize.X ); var save = ImGui.InputText( "##rootDirectory", ref _newModDirectory, 64, ImGuiInputTextFlags.EnterReturnsTrue ); using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( spacing, 0 ) ); ImGui.SameLine(); DrawDirectoryPickerButton(); style.Pop(); ImGui.SameLine(); ImGuiUtil.LabeledHelpMarker( "Root Directory", "This is where Penumbra will store your extracted mod files.\n" + "TTMP files are not copied, just extracted.\n" + "This directory needs to be accessible and you need write access here.\n" + "It is recommended that this directory is placed on a fast hard drive, preferably an SSD.\n" + "It should also be placed near the root of a logical drive - the shorter the total path to this folder, the better.\n" + "Definitely do not place it in your Dalamud directory or any sub-directory thereof." ); group.Dispose(); ImGui.SameLine(); var pos = ImGui.GetCursorPosX(); ImGui.NewLine(); if( Penumbra.Config.ModDirectory != _newModDirectory && _newModDirectory.Length != 0 && DrawPressEnterWarning( _newModDirectory, Penumbra.Config.ModDirectory, pos, save ) ) { Penumbra.ModManager.DiscoverMods( _newModDirectory ); } } private static void DrawRediscoverButton() { DrawOpenDirectoryButton( 0, Penumbra.ModManager.BasePath, Penumbra.ModManager.Valid ); ImGui.SameLine(); var tt = Penumbra.ModManager.Valid ? "Force Penumbra to completely re-scan your root directory as if it was restarted." : "The currently selected folder is not valid. Please select a different folder."; if( ImGuiUtil.DrawDisabledButton( "Rediscover Mods", Vector2.Zero, tt, !Penumbra.ModManager.Valid ) ) { Penumbra.ModManager.DiscoverMods(); } } private void DrawEnabledBox() { var enabled = Penumbra.Config.EnableMods; if( ImGui.Checkbox( "Enable Mods", ref enabled ) ) { _window._penumbra.SetEnabled( enabled ); } } private static void DrawShowAdvancedBox() { var showAdvanced = Penumbra.Config.ShowAdvanced; if( ImGui.Checkbox( "##showAdvanced", ref showAdvanced ) ) { Penumbra.Config.ShowAdvanced = showAdvanced; Penumbra.Config.Save(); } ImGui.SameLine(); ImGuiUtil.LabeledHelpMarker( "Show Advanced Settings", "Enable some advanced options in this window and in the mod selector.\n" + "This is required to enable manually editing any mod information." ); } private static void DrawColorSettings() { if( !ImGui.CollapsingHeader( "Colors" ) ) { return; } foreach( var color in Enum.GetValues< ColorId >() ) { var (defaultColor, name, description) = color.Data(); var currentColor = Penumbra.Config.Colors.TryGetValue( color, out var current ) ? current : defaultColor; if( Widget.ColorPicker( name, description, currentColor, c => Penumbra.Config.Colors[ color ] = c, defaultColor ) ) { Penumbra.Config.Save(); } } ImGui.NewLine(); } public static void DrawDiscordButton( float width ) { const string discord = "Join Discord for Support"; const string address = @"https://discord.gg/kVva7DHV4r"; using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.DiscordColor ); if( ImGui.Button( discord, new Vector2( width, 0 ) ) ) { try { var process = new ProcessStartInfo( address ) { UseShellExecute = true, }; Process.Start( process ); } catch { // ignored } } ImGuiUtil.HoverTooltip( $"Open {address}" ); } private const string SupportInfoButtonText = "Copy Support Info to Clipboard"; public static void DrawSupportButton() { if( ImGui.Button( SupportInfoButtonText ) ) { var text = Penumbra.GatherSupportInformation(); ImGui.SetClipboardText( text ); } } private static void DrawSupportButtons() { var width = ImGui.CalcTextSize( SupportInfoButtonText ).X + ImGui.GetStyle().FramePadding.X * 2; if( ImGui.GetScrollMaxY() > 0 ) { width += ImGui.GetStyle().ScrollbarSize + ImGui.GetStyle().ItemSpacing.X; } ImGui.SetCursorPos( new Vector2( ImGui.GetWindowWidth() - width, ImGui.GetFrameHeightWithSpacing() ) ); DrawSupportButton(); ImGui.SetCursorPos( new Vector2( ImGui.GetWindowWidth() - width, 0 ) ); DrawDiscordButton( width ); } } }