Przeglądaj źródła

gui: Refactoring Part 1 (#1859)

* gui: Refactoring Part 1

* Fix ProfileDialog.glade path

* Fix Application.Quit assert

* Fix TitleUpdateWindow parent

* Fix TitleUpdate selected item

* Remove extra line in TitleUpdateWindow

* Fix empty assign of Enum.TryParse

* Add Patrons list in the About Window

* update about error messages
Ac_K 5 lat temu
rodzic
commit
a9cb31e75f
71 zmienionych plików z 1920 dodań i 2358 usunięć
  1. 16 0
      Ryujinx.Common/System/ForceDedicatedGpu.cs
  2. 11 10
      Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs
  3. 5 4
      Ryujinx/Modules/DiscordIntegrationModule.cs
  4. 4 4
      Ryujinx/Modules/Motion/Client.cs
  5. 5 5
      Ryujinx/Modules/Motion/MotionDevice.cs
  6. 2 2
      Ryujinx/Modules/Motion/MotionInput.cs
  7. 4 8
      Ryujinx/Modules/Motion/MotionSensorFilter.cs
  8. 15 14
      Ryujinx/Modules/Motion/Protocol/ControllerData.cs
  9. 5 5
      Ryujinx/Modules/Motion/Protocol/ControllerInfo.cs
  10. 5 5
      Ryujinx/Modules/Motion/Protocol/Header.cs
  11. 2 2
      Ryujinx/Modules/Motion/Protocol/MessageType.cs
  12. 11 11
      Ryujinx/Modules/Motion/Protocol/SharedResponse.cs
  13. 17 16
      Ryujinx/Modules/Updater/UpdateDialog.cs
  14. 0 0
      Ryujinx/Modules/Updater/UpdateDialog.glade
  15. 4 3
      Ryujinx/Modules/Updater/Updater.cs
  16. 47 33
      Ryujinx/Program.cs
  17. 40 42
      Ryujinx/Ryujinx.csproj
  18. 0 74
      Ryujinx/Ui/AboutWindow.cs
  19. 0 574
      Ryujinx/Ui/AboutWindow.glade
  20. 1 1
      Ryujinx/Ui/App/ApplicationAddedEventArgs.cs
  21. 1 1
      Ryujinx/Ui/App/ApplicationCountUpdatedEventArgs.cs
  22. 4 5
      Ryujinx/Ui/App/ApplicationData.cs
  23. 48 47
      Ryujinx/Ui/App/ApplicationLibrary.cs
  24. 9 0
      Ryujinx/Ui/App/ApplicationMetadata.cs
  25. 1 6
      Ryujinx/Ui/Applet/ErrorAppletDialog.cs
  26. 36 29
      Ryujinx/Ui/Applet/GtkHostUiHandler.cs
  27. 89 0
      Ryujinx/Ui/Applet/SwkbdAppletDialog.cs
  28. 0 9
      Ryujinx/Ui/ApplicationMetadata.cs
  29. 0 36
      Ryujinx/Ui/Diagnostic/GuideDialog.cs
  30. 0 137
      Ryujinx/Ui/Diagnostic/UserErrorDialog.cs
  31. 6 5
      Ryujinx/Ui/GLRenderer.cs
  32. 13 3
      Ryujinx/Ui/Helper/OpenHelper.cs
  33. 2 1
      Ryujinx/Ui/Helper/SetupValidator.cs
  34. 116 0
      Ryujinx/Ui/Helper/SortHelper.cs
  35. 35 0
      Ryujinx/Ui/Helper/ThemeHelper.cs
  36. 0 69
      Ryujinx/Ui/InputDialog.cs
  37. 243 342
      Ryujinx/Ui/MainWindow.cs
  38. 0 189
      Ryujinx/Ui/Migration.cs
  39. 0 0
      Ryujinx/Ui/Resources/Controller_JoyConLeft.svg
  40. 0 0
      Ryujinx/Ui/Resources/Controller_JoyConPair.svg
  41. 0 0
      Ryujinx/Ui/Resources/Controller_JoyConRight.svg
  42. 0 0
      Ryujinx/Ui/Resources/Controller_ProCon.svg
  43. 0 0
      Ryujinx/Ui/Resources/Icon_NCA.png
  44. 0 0
      Ryujinx/Ui/Resources/Icon_NRO.png
  45. 0 0
      Ryujinx/Ui/Resources/Icon_NSO.png
  46. 0 0
      Ryujinx/Ui/Resources/Icon_NSP.png
  47. 0 0
      Ryujinx/Ui/Resources/Icon_XCI.png
  48. 0 0
      Ryujinx/Ui/Resources/Logo_Discord.png
  49. 0 0
      Ryujinx/Ui/Resources/Logo_GitHub.png
  50. 0 0
      Ryujinx/Ui/Resources/Logo_Patreon.png
  51. 0 0
      Ryujinx/Ui/Resources/Logo_Ryujinx.png
  52. 0 0
      Ryujinx/Ui/Resources/Logo_Twitter.png
  53. 0 219
      Ryujinx/Ui/SaveImporter.cs
  54. 1 1
      Ryujinx/Ui/StatusUpdatedEventArgs.cs
  55. 198 0
      Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs
  56. 108 279
      Ryujinx/Ui/Widgets/GameTableContextMenu.cs
  57. 29 12
      Ryujinx/Ui/Widgets/GtkDialog.cs
  58. 3 6
      Ryujinx/Ui/Widgets/ProfileDialog.cs
  59. 0 0
      Ryujinx/Ui/Widgets/ProfileDialog.glade
  60. 2 2
      Ryujinx/Ui/Widgets/UserError.cs
  61. 122 0
      Ryujinx/Ui/Widgets/UserErrorDialog.cs
  62. 467 0
      Ryujinx/Ui/Windows/AboutWindow.Designer.cs
  63. 73 0
      Ryujinx/Ui/Windows/AboutWindow.cs
  64. 37 44
      Ryujinx/Ui/Windows/ControllerWindow.cs
  65. 0 0
      Ryujinx/Ui/Windows/ControllerWindow.glade
  66. 7 19
      Ryujinx/Ui/Windows/DlcWindow.cs
  67. 0 0
      Ryujinx/Ui/Windows/DlcWindow.glade
  68. 40 42
      Ryujinx/Ui/Windows/SettingsWindow.cs
  69. 0 0
      Ryujinx/Ui/Windows/SettingsWindow.glade
  70. 36 42
      Ryujinx/Ui/Windows/TitleUpdateWindow.cs
  71. 0 0
      Ryujinx/Ui/Windows/TitleUpdateWindow.glade

+ 16 - 0
Ryujinx.Common/System/ForceDedicatedGpu.cs

@@ -0,0 +1,16 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.System
+{
+    public static class ForceDedicatedGpu
+    {
+        public static void Nvidia()
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                // NOTE: If the DLL exists, we can load it to force the usage of the dedicated Nvidia Gpu.
+                NativeLibrary.TryLoad("nvapi64.dll", out _);
+            }
+        }
+    }
+}

+ 11 - 10
Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs

@@ -17,14 +17,14 @@ namespace Ryujinx.Common.System
             public uint wPeriodMax;
         };
 
-        [DllImport("winmm.dll", SetLastError = true)]
-        private static extern uint timeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps);
+        [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)]
+        private static extern uint TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps);
 
-        [DllImport("winmm.dll")]
-        private static extern uint timeBeginPeriod(uint uMilliseconds);
+        [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
+        private static extern uint TimeBeginPeriod(uint uMilliseconds);
 
-        [DllImport("winmm.dll")]
-        private static extern uint timeEndPeriod(uint uMilliseconds);
+        [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
+        private static extern uint TimeEndPeriod(uint uMilliseconds);
 
         private uint _targetResolutionInMilliseconds;
         private bool _isActive;
@@ -45,7 +45,7 @@ namespace Ryujinx.Common.System
         {
             TimeCaps timeCaps = default;
 
-            uint result = timeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf<TimeCaps>());
+            uint result = TimeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf<TimeCaps>());
 
             if (result != 0)
             {
@@ -66,7 +66,7 @@ namespace Ryujinx.Common.System
 
         private void Activate()
         {
-            uint result = timeBeginPeriod(_targetResolutionInMilliseconds);
+            uint result = TimeBeginPeriod(_targetResolutionInMilliseconds);
 
             if (result != 0)
             {
@@ -82,7 +82,7 @@ namespace Ryujinx.Common.System
         {
             if (_isActive)
             {
-                uint result = timeEndPeriod(_targetResolutionInMilliseconds);
+                uint result = TimeEndPeriod(_targetResolutionInMilliseconds);
 
                 if (result != 0)
                 {
@@ -98,6 +98,7 @@ namespace Ryujinx.Common.System
         public void Dispose()
         {
             Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
         protected virtual void Dispose(bool disposing)
@@ -108,4 +109,4 @@ namespace Ryujinx.Common.System
             }
         }
     }
-}
+}

+ 5 - 4
Ryujinx/Configuration/DiscordIntegrationModule.cs → Ryujinx/Modules/DiscordIntegrationModule.cs

@@ -1,15 +1,16 @@
 using DiscordRPC;
 using Ryujinx.Common;
+using Ryujinx.Configuration;
 using System;
 using System.Linq;
 
-namespace Ryujinx.Configuration
+namespace Ryujinx.Modules
 {
     static class DiscordIntegrationModule
     {
         private static DiscordRpcClient _discordClient;
 
-        private static string LargeDescription = "Ryujinx is a Nintendo Switch emulator.";
+        private const string LargeDescription = "Ryujinx is a Nintendo Switch emulator.";
 
         public static RichPresence DiscordPresence { get; private set; }
 
@@ -17,7 +18,7 @@ namespace Ryujinx.Configuration
         {
             DiscordPresence = new RichPresence
             {
-                Assets     = new Assets
+                Assets = new Assets
                 {
                     LargeImageKey  = "ryujinx",
                     LargeImageText = LargeDescription
@@ -169,4 +170,4 @@ namespace Ryujinx.Configuration
             "051337133769a000", // RGB-Seizure
         };
     }
-}
+}

+ 4 - 4
Ryujinx/Motion/Client.cs → Ryujinx/Modules/Motion/Client.cs

@@ -11,7 +11,7 @@ using System.Net.Sockets;
 using System.Numerics;
 using System.Threading.Tasks;
 
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     public class Client : IDisposable
     {
@@ -396,7 +396,7 @@ namespace Ryujinx.Motion
             }
         }
 
-        public unsafe  void RequestData(int clientId, int slot)
+        public unsafe void RequestData(int clientId, int slot)
         {
             if (!_active)
             {
@@ -439,7 +439,7 @@ namespace Ryujinx.Motion
         {
             Header header = new Header()
             {
-                ID          = (uint)clientId,
+                Id          = (uint)clientId,
                 MagicString = Magic,
                 Version     = Version,
                 Length      = 0,
@@ -456,4 +456,4 @@ namespace Ryujinx.Motion
             CloseClients();
         }
     }
-}
+}

+ 5 - 5
Ryujinx/Motion/MotionDevice.cs → Ryujinx/Modules/Motion/MotionDevice.cs

@@ -3,7 +3,7 @@ using Ryujinx.Configuration;
 using System;
 using System.Numerics;
 
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     public class MotionDevice
     {
@@ -12,7 +12,7 @@ namespace Ryujinx.Motion
         public Vector3 Rotation      { get; private set; }
         public float[] Orientation   { get; private set; }
 
-        private Client _motionSource;
+        private readonly Client _motionSource;
 
         public MotionDevice(Client motionSource)
         {
@@ -51,8 +51,8 @@ namespace Ryujinx.Motion
             }
 
             Gyroscope     = Truncate(input.Gyroscrope * 0.0027f, 3);
-            Accelerometer = Truncate(input.Accelerometer, 3);
-            Rotation      = Truncate(input.Rotation * 0.0027f, 3);
+            Accelerometer = Truncate(input.Accelerometer,        3);
+            Rotation      = Truncate(input.Rotation * 0.0027f,   3);
 
             Matrix4x4 orientation = input.GetOrientation();
 
@@ -78,4 +78,4 @@ namespace Ryujinx.Motion
             return value;
         }
     }
-}
+}

+ 2 - 2
Ryujinx/Motion/MotionInput.cs → Ryujinx/Modules/Motion/MotionInput.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Numerics;
 
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     public class MotionInput
     {
@@ -82,4 +82,4 @@ namespace Ryujinx.Motion
             return degree * (MathF.PI / 180);
         }
     }
-}
+}

+ 4 - 8
Ryujinx/Motion/MotionSensorFilter.cs → Ryujinx/Modules/Motion/MotionSensorFilter.cs

@@ -1,6 +1,6 @@
 using System.Numerics;
 
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     // MahonyAHRS class. Madgwick's implementation of Mayhony's AHRS algorithm.
     // See: https://x-io.co.uk/open-source-imu-and-ahrs-algorithms/
@@ -43,9 +43,7 @@ namespace Ryujinx.Motion
         /// <param name="samplePeriod">
         /// Sample period.
         /// </param>
-        public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f)
-        {
-        }
+        public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f) { }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
@@ -56,9 +54,7 @@ namespace Ryujinx.Motion
         /// <param name="kp">
         /// Algorithm proportional gain.
         /// </param>
-        public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f)
-        {
-        }
+        public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f) { }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
@@ -163,4 +159,4 @@ namespace Ryujinx.Motion
             Quaternion = Quaternion.Identity;
         }
     }
-}
+}

+ 15 - 14
Ryujinx/Motion/Protocol/ControllerData.cs → Ryujinx/Modules/Motion/Protocol/ControllerData.cs

@@ -1,13 +1,13 @@
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     struct ControllerDataRequest
     {
-        public MessageType Type;
+        public MessageType    Type;
         public SubscriberType SubscriberType;
-        public byte Slot;
+        public byte           Slot;
 
         [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
         public byte[] MacAddress;
@@ -17,21 +17,22 @@ namespace Ryujinx.Motion
     public struct ControllerDataResponse
     {
         public SharedResponse Shared;
-        public byte Connected;
-        public uint PacketID;
-        public byte ExtraButtons;
-        public byte MainButtons;
-        public ushort PSExtraInput;
-        public ushort LeftStickXY;
-        public ushort RightStickXY;
-        public uint DPadAnalog;
-        public ulong MainButtonsAnalog;
+        public byte           Connected;
+        public uint           PacketId;
+        public byte           ExtraButtons;
+        public byte           MainButtons;
+        public ushort         PSExtraInput;
+        public ushort         LeftStickXY;
+        public ushort         RightStickXY;
+        public uint           DPadAnalog;
+        public ulong          MainButtonsAnalog;
 
         [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
         public byte[] Touch1;
 
         [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
         public byte[] Touch2;
+
         public ulong MotionTimestamp;
         public float AccelerometerX;
         public float AccelerometerY;
@@ -43,8 +44,8 @@ namespace Ryujinx.Motion
 
     enum SubscriberType : byte
     {
-        All = 0,
+        All,
         Slot,
         Mac
     }
-}
+}

+ 5 - 5
Ryujinx/Motion/Protocol/ControllerInfo.cs → Ryujinx/Modules/Motion/Protocol/ControllerInfo.cs

@@ -1,21 +1,21 @@
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct ControllerInfoResponse
     {
-        public SharedResponse Shared;
-        private byte _zero;
+        public  SharedResponse Shared;
+        private byte           _zero;
     }
 
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct ControllerInfoRequest
     {
         public MessageType Type;
-        public int PortsCount;
+        public int         PortsCount;
 
         [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
         public byte[] PortIndices;
     }
-}
+}

+ 5 - 5
Ryujinx/Motion/Protocol/Header.cs → Ryujinx/Modules/Motion/Protocol/Header.cs

@@ -1,14 +1,14 @@
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct Header
     {
-        public uint MagicString;
+        public uint   MagicString;
         public ushort Version;
         public ushort Length;
-        public uint Crc32;
-        public uint ID;
+        public uint   Crc32;
+        public uint   Id;
     }
-}
+}

+ 2 - 2
Ryujinx/Motion/Protocol/MessageType.cs → Ryujinx/Modules/Motion/Protocol/MessageType.cs

@@ -1,4 +1,4 @@
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     public enum MessageType : uint
     {
@@ -6,4 +6,4 @@
         Info,
         Data
     }
-}
+}

+ 11 - 11
Ryujinx/Motion/Protocol/SharedResponse.cs → Ryujinx/Modules/Motion/Protocol/SharedResponse.cs

@@ -1,45 +1,45 @@
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.Motion
+namespace Ryujinx.Modules.Motion
 {
     [StructLayout(LayoutKind.Sequential, Pack = 1)]
     public struct SharedResponse
     {
-        public MessageType Type;
-        public byte Slot;
-        public SlotState State;
+        public MessageType     Type;
+        public byte            Slot;
+        public SlotState       State;
         public DeviceModelType ModelType;
-        public ConnectionType ConnectionType;
+        public ConnectionType  ConnectionType;
 
         [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
-        public byte[] MacAddress;
+        public byte[]        MacAddress;
         public BatteryStatus BatteryStatus;
     }
 
     public enum SlotState : byte
     {
-        Disconnected = 0,
+        Disconnected,
         Reserved,
         Connected
     }
 
     public enum DeviceModelType : byte
     {
-        None = 0,
+        None,
         PartialGyro,
         FullGyro
     }
 
     public enum ConnectionType : byte
     {
-        None = 0,
+        None,
         USB,
         Bluetooth
     }
 
     public enum BatteryStatus : byte
     {
-        NA = 0,
+        NA,
         Dying,
         Low,
         Medium,
@@ -48,4 +48,4 @@ namespace Ryujinx.Motion
         Charging,
         Charged
     }
-}
+}

+ 17 - 16
Ryujinx/Updater/UpdateDialog.cs → Ryujinx/Modules/Updater/UpdateDialog.cs

@@ -1,28 +1,29 @@
 using Gdk;
 using Gtk;
 using Mono.Unix;
+using Ryujinx.Ui;
 using System;
 using System.Diagnostics;
 using System.Linq;
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Modules
 {
     public class UpdateDialog : Gtk.Window
     {
 #pragma warning disable CS0649, IDE0044
-        [Builder.Object] public Label MainText;
-        [Builder.Object] public Label SecondaryText;
+        [Builder.Object] public Label    MainText;
+        [Builder.Object] public Label    SecondaryText;
         [Builder.Object] public LevelBar ProgressBar;
-        [Builder.Object] public Button YesButton;
-        [Builder.Object] public Button NoButton;
+        [Builder.Object] public Button   YesButton;
+        [Builder.Object] public Button   NoButton;
 #pragma warning restore CS0649, IDE0044
 
         private readonly MainWindow _mainWindow;
-        private readonly string _buildUrl;
-        private bool _restartQuery;
+        private readonly string     _buildUrl;
+        private          bool       _restartQuery;
 
-        public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { }
+        public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx..Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { }
 
         private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetObject("UpdateDialog").Handle)
         {
@@ -36,17 +37,17 @@ namespace Ryujinx.Ui
 
             ProgressBar.Hide();
 
-            YesButton.Clicked += YesButton_Pressed;
-            NoButton.Clicked  += NoButton_Pressed;
+            YesButton.Clicked += YesButton_Clicked;
+            NoButton.Clicked  += NoButton_Clicked;
         }
         
-        private void YesButton_Pressed(object sender, EventArgs args)
+        private void YesButton_Clicked(object sender, EventArgs args)
         {
             if (_restartQuery)
             {
                 string ryuName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Ryujinx.exe" : "Ryujinx";
                 string ryuExe  = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
-                string ryuArg = String.Join(" ", Environment.GetCommandLineArgs().AsEnumerable().Skip(1).ToArray());
+                string ryuArg  = string.Join(" ", Environment.GetCommandLineArgs().AsEnumerable().Skip(1).ToArray());
 
                 if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                 {
@@ -60,7 +61,7 @@ namespace Ryujinx.Ui
             }
             else
             {
-                this.Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close;
+                Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close;
                 _mainWindow.ExitMenuItem.Sensitive = false;
 
                 YesButton.Hide();
@@ -74,7 +75,7 @@ namespace Ryujinx.Ui
             }
         }
 
-        private void NoButton_Pressed(object sender, EventArgs args)
+        private void NoButton_Clicked(object sender, EventArgs args)
         {
             Updater.Running = false;
             _mainWindow.Window.Functions = WMFunction.All;
@@ -82,7 +83,7 @@ namespace Ryujinx.Ui
             _mainWindow.ExitMenuItem.Sensitive   = true;
             _mainWindow.UpdateMenuItem.Sensitive = true;
 
-            this.Dispose();
+            Dispose();
         }
     }
-}
+}

+ 0 - 0
Ryujinx/Updater/UpdateDialog.glade → Ryujinx/Modules/Updater/UpdateDialog.glade


+ 4 - 3
Ryujinx/Updater/Updater.cs → Ryujinx/Modules/Updater/Updater.cs

@@ -5,6 +5,7 @@ using ICSharpCode.SharpZipLib.Zip;
 using Newtonsoft.Json.Linq;
 using Ryujinx.Common.Logging;
 using Ryujinx.Ui;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.IO;
 using System.Net;
@@ -13,7 +14,7 @@ using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace Ryujinx
+namespace Ryujinx.Modules
 {
     public static class Updater
     {
@@ -84,7 +85,7 @@ namespace Ryujinx
                     {
                         if (showVersionUpToDate)
                         {
-                            GtkDialog.CreateInfoDialog("Ryujinx - Updater", "You are already using the most updated version of Ryujinx!", "");
+                            GtkDialog.CreateUpdaterInfoDialog("You are already using the most updated version of Ryujinx!", "");
                         }
 
                         return;
@@ -115,7 +116,7 @@ namespace Ryujinx
             {
                 if (showVersionUpToDate)
                 {
-                    GtkDialog.CreateInfoDialog("Ryujinx - Updater", "You are already using the most updated version of Ryujinx!", "");
+                    GtkDialog.CreateUpdaterInfoDialog("You are already using the most updated version of Ryujinx!", "");
                 }
 
                 Running = false;

+ 47 - 33
Ryujinx/Program.cs

@@ -3,10 +3,12 @@ using Gtk;
 using OpenTK;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
+using Ryujinx.Common.System;
 using Ryujinx.Common.SystemInfo;
 using Ryujinx.Configuration;
+using Ryujinx.Modules;
 using Ryujinx.Ui;
-using Ryujinx.Ui.Diagnostic;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.IO;
 using System.Reflection;
@@ -22,10 +24,11 @@ namespace Ryujinx
 
         static void Main(string[] args)
         {
-            // Parse Arguments
-            string launchPath = null;
-            string baseDirPath = null;
-            bool startFullscreen = false;
+            // Parse Arguments.
+            string launchPathArg      = null;
+            string baseDirPathArg     = null;
+            bool   startFullscreenArg = false;
+
             for (int i = 0; i < args.Length; ++i)
             {
                 string arg = args[i];
@@ -35,28 +38,28 @@ namespace Ryujinx
                     if (i + 1 >= args.Length)
                     {
                         Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
                         continue;
                     }
 
-                    baseDirPath = args[++i];
+                    baseDirPathArg = args[++i];
                 }
                 else if (arg == "-f" || arg == "--fullscreen")
                 {
-                    startFullscreen = true;
+                    startFullscreenArg = true;
                 }
-                else if (launchPath == null)
+                else if (launchPathArg == null)
                 {
-                    launchPath = arg;
+                    launchPathArg = arg;
                 }
             }
 
-            // Delete backup files after updating
+            // Delete backup files after updating.
             Task.Run(Updater.CleanupUpdate);
 
             Toolkit.Init(new ToolkitOptions
             {
-                Backend = PlatformBackend.PreferNative,
-                EnableHighResolution = true
+                Backend = PlatformBackend.PreferNative
             });
 
             Version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
@@ -66,27 +69,27 @@ namespace Ryujinx
             string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
             Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
 
-            // Hook unhandled exception and process exit events
-            GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
+            // Hook unhandled exception and process exit events.
+            GLib.ExceptionManager.UnhandledException   += (GLib.UnhandledExceptionArgs e)                => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
             AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
-            AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => ProgramExit();
+            AppDomain.CurrentDomain.ProcessExit        += (object sender, EventArgs e)                   => Exit();
 
-            // Setup base data directory
-            AppDataManager.Initialize(baseDirPath);
+            // Setup base data directory.
+            AppDataManager.Initialize(baseDirPathArg);
 
-            // Initialize the configuration
+            // Initialize the configuration.
             ConfigurationState.Initialize();
 
-            // Initialize the logger system
+            // Initialize the logger system.
             LoggerModule.Initialize();
 
-            // Initialize Discord integration
+            // Initialize Discord integration.
             DiscordIntegrationModule.Initialize();
 
             string localConfigurationPath   = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
-            string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
+            string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath,            "Config.json");
 
-            // Now load the configuration as the other subsystems are now registered
+            // Now load the configuration as the other subsystems are now registered.
             if (File.Exists(localConfigurationPath))
             {
                 ConfigurationPath = localConfigurationPath;
@@ -105,35 +108,42 @@ namespace Ryujinx
             }
             else
             {
-                // No configuration, we load the default values and save it on disk
+                // No configuration, we load the default values and save it on disk.
                 ConfigurationPath = appDataConfigurationPath;
 
                 ConfigurationState.Instance.LoadDefault();
                 ConfigurationState.Instance.ToFileFormat().SaveConfig(appDataConfigurationPath);
             }
 
-            if (startFullscreen)
+            if (startFullscreenArg)
             {
                 ConfigurationState.Instance.Ui.StartFullscreen.Value = true;
             }
 
+            // Logging system informations.
             PrintSystemInfo();
 
+            // Initialize Gtk.
             Application.Init();
 
+            // Check if keys exists.
             bool hasGlobalProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"));
             bool hasAltProdKeys    = !AppDataManager.IsCustomBasePath && File.Exists(Path.Combine(AppDataManager.KeysDirPathAlt, "prod.keys"));
-            if (!hasGlobalProdKeys && !hasAltProdKeys && !Migration.IsMigrationNeeded())
+            if (!hasGlobalProdKeys && !hasAltProdKeys)
             {
                 UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys);
             }
 
+            // Force dedicated GPU if we can.
+            ForceDedicatedGpu.Nvidia();
+
+            // Show the main window UI.
             MainWindow mainWindow = new MainWindow();
             mainWindow.Show();
 
-            if (launchPath != null)
+            if (launchPathArg != null)
             {
-                mainWindow.LoadApplication(launchPath);
+                mainWindow.LoadApplication(launchPathArg);
             }
 
             if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
@@ -147,7 +157,6 @@ namespace Ryujinx
         private static void PrintSystemInfo()
         {
             Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
-
             Logger.Notice.Print(LogClass.Application, $"Operating System: {SystemInfo.Instance.OsDescription}");
             Logger.Notice.Print(LogClass.Application, $"CPU: {SystemInfo.Instance.CpuName}");
             Logger.Notice.Print(LogClass.Application, $"Total RAM: {SystemInfo.Instance.RamSizeInMB}");
@@ -161,25 +170,30 @@ namespace Ryujinx
             }
         }
 
-        private static void ProcessUnhandledException(Exception e, bool isTerminating)
+        private static void ProcessUnhandledException(Exception ex, bool isTerminating)
         {
             Ptc.Close();
             PtcProfiler.Stop();
 
-            string message = $"Unhandled exception caught: {e}";
+            string message = $"Unhandled exception caught: {ex}";
 
             Logger.Error?.PrintMsg(LogClass.Application, message);
 
-            if (Logger.Error == null) Logger.Notice.PrintMsg(LogClass.Application, message);
+            if (Logger.Error == null)
+            {
+                Logger.Notice.PrintMsg(LogClass.Application, message);
+            }
 
             if (isTerminating)
             {
-                ProgramExit();
+                Exit();
             }
         }
 
-        private static void ProgramExit()
+        public static void Exit()
         {
+            DiscordIntegrationModule.Exit();
+
             Ptc.Dispose();
             PtcProfiler.Dispose();
 

+ 40 - 42
Ryujinx/Ryujinx.csproj

@@ -55,53 +55,51 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <None Remove="Ui\AboutWindow.glade" />
-    <None Remove="Ui\assets\JoyConLeft.svg" />
-    <None Remove="Ui\assets\JoyConPair.svg" />
-    <None Remove="Ui\assets\JoyConRight.svg" />
-    <None Remove="Ui\assets\ProCon.svg" />
-    <None Remove="Ui\assets\NCAIcon.png" />
-    <None Remove="Ui\assets\NROIcon.png" />
-    <None Remove="Ui\assets\NSOIcon.png" />
-    <None Remove="Ui\assets\NSPIcon.png" />
-    <None Remove="Ui\assets\XCIIcon.png" />
-    <None Remove="Ui\assets\DiscordLogo.png" />
-    <None Remove="Ui\assets\GitHubLogo.png" />
-    <None Remove="Ui\assets\PatreonLogo.png" />
-    <None Remove="Ui\assets\Icon.png" />
-    <None Remove="Ui\assets\TwitterLogo.png" />
-    <None Remove="Ui\ControllerWindow.glade" />
-    <None Remove="Ui\DlcWindow.glade" />
     <None Remove="Ui\MainWindow.glade" />
-    <None Remove="Ui\ProfileDialog.glade" />
-    <None Remove="Ui\SettingsWindow.glade" />
-    <None Remove="Ui\TitleUpdateWindow.glade" />
-    <None Remove="Ui\UpdateDialog.glade" />
+    <None Remove="Ui\Resources\Controller_JoyConLeft.svg" />
+    <None Remove="Ui\Resources\Controller_JoyConPair.svg" />
+    <None Remove="Ui\Resources\Controller_JoyConRight.svg" />
+    <None Remove="Ui\Resources\Controller_ProCon.svg" />
+    <None Remove="Ui\Resources\Icon_NCA.png" />
+    <None Remove="Ui\Resources\Icon_NRO.png" />
+    <None Remove="Ui\Resources\Icon_NSO.png" />
+    <None Remove="Ui\Resources\Icon_NSP.png" />
+    <None Remove="Ui\Resources\Icon_XCI.png" />
+    <None Remove="Ui\Resources\Logo_Discord.png" />
+    <None Remove="Ui\Resources\Logo_GitHub.png" />
+    <None Remove="Ui\Resources\Logo_Patreon.png" />
+    <None Remove="Ui\Resources\Logo_Ryujinx.png" />
+    <None Remove="Ui\Resources\Logo_Twitter.png" />
+    <None Remove="Ui\Widgets\ProfileDialog.glade" />
+    <None Remove="Ui\Windows\ControllerWindow.glade" />
+    <None Remove="Ui\Windows\DlcWindow.glade" />
+    <None Remove="Ui\Windows\SettingsWindow.glade" />
+    <None Remove="Ui\Windows\TitleUpdateWindow.glade" />
+    <None Remove="Modules\Updater\UpdateDialog.glade" />
   </ItemGroup>
 
   <ItemGroup>
-    <EmbeddedResource Include="Ui\AboutWindow.glade" />
-    <EmbeddedResource Include="Ui\assets\JoyConLeft.svg" />
-    <EmbeddedResource Include="Ui\assets\JoyConPair.svg" />
-    <EmbeddedResource Include="Ui\assets\JoyConRight.svg" />
-    <EmbeddedResource Include="Ui\assets\ProCon.svg" />
-    <EmbeddedResource Include="Ui\assets\NCAIcon.png" />
-    <EmbeddedResource Include="Ui\assets\NROIcon.png" />
-    <EmbeddedResource Include="Ui\assets\NSOIcon.png" />
-    <EmbeddedResource Include="Ui\assets\NSPIcon.png" />
-    <EmbeddedResource Include="Ui\assets\XCIIcon.png" />
-    <EmbeddedResource Include="Ui\assets\DiscordLogo.png" />
-    <EmbeddedResource Include="Ui\assets\GitHubLogo.png" />
-    <EmbeddedResource Include="Ui\assets\PatreonLogo.png" />
-    <EmbeddedResource Include="Ui\assets\Icon.png" />
-    <EmbeddedResource Include="Ui\assets\TwitterLogo.png" />
-    <EmbeddedResource Include="Ui\ControllerWindow.glade" />
     <EmbeddedResource Include="Ui\MainWindow.glade" />
-    <EmbeddedResource Include="Ui\ProfileDialog.glade" />
-    <EmbeddedResource Include="Ui\SettingsWindow.glade" />
-    <EmbeddedResource Include="Ui\DlcWindow.glade" />
-    <EmbeddedResource Include="Ui\TitleUpdateWindow.glade" />
-    <EmbeddedResource Include="Updater\UpdateDialog.glade" />
+    <EmbeddedResource Include="Ui\Resources\Controller_JoyConLeft.svg" />
+    <EmbeddedResource Include="Ui\Resources\Controller_JoyConPair.svg" />
+    <EmbeddedResource Include="Ui\Resources\Controller_JoyConRight.svg" />
+    <EmbeddedResource Include="Ui\Resources\Controller_ProCon.svg" />
+    <EmbeddedResource Include="Ui\Resources\Icon_NCA.png" />
+    <EmbeddedResource Include="Ui\Resources\Icon_NRO.png" />
+    <EmbeddedResource Include="Ui\Resources\Icon_NSO.png" />
+    <EmbeddedResource Include="Ui\Resources\Icon_NSP.png" />
+    <EmbeddedResource Include="Ui\Resources\Icon_XCI.png" />
+    <EmbeddedResource Include="Ui\Resources\Logo_Discord.png" />
+    <EmbeddedResource Include="Ui\Resources\Logo_GitHub.png" />
+    <EmbeddedResource Include="Ui\Resources\Logo_Patreon.png" />
+    <EmbeddedResource Include="Ui\Resources\Logo_Ryujinx.png" />
+    <EmbeddedResource Include="Ui\Resources\Logo_Twitter.png" />
+    <EmbeddedResource Include="Ui\Widgets\ProfileDialog.glade" />
+    <EmbeddedResource Include="Ui\Windows\ControllerWindow.glade" />
+    <EmbeddedResource Include="Ui\Windows\DlcWindow.glade" />
+    <EmbeddedResource Include="Ui\Windows\SettingsWindow.glade" />
+    <EmbeddedResource Include="Ui\Windows\TitleUpdateWindow.glade" />
+    <EmbeddedResource Include="Modules\Updater\UpdateDialog.glade" />
   </ItemGroup>
 
 </Project>

+ 0 - 74
Ryujinx/Ui/AboutWindow.cs

@@ -1,74 +0,0 @@
-using Gtk;
-using System;
-using System.Reflection;
-
-using GUI = Gtk.Builder.ObjectAttribute;
-
-namespace Ryujinx.Ui
-{
-    public class AboutWindow : Window
-    {
-#pragma warning disable CS0649
-#pragma warning disable IDE0044
-        [GUI] Label  _versionText;
-        [GUI] Image  _ryujinxLogo;
-        [GUI] Image  _patreonLogo;
-        [GUI] Image  _gitHubLogo;
-        [GUI] Image  _discordLogo;
-        [GUI] Image  _twitterLogo;
-#pragma warning restore CS0649
-#pragma warning restore IDE0044
-
-        public AboutWindow() : this(new Builder("Ryujinx.Ui.AboutWindow.glade")) { }
-
-        private AboutWindow(Builder builder) : base(builder.GetObject("_aboutWin").Handle)
-        {
-            builder.Autoconnect(this);
-
-            this.Icon           = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
-            _ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"       , 100, 100);
-            _patreonLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.PatreonLogo.png", 30 , 30 );
-            _gitHubLogo.Pixbuf  = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.GitHubLogo.png" , 30 , 30 );
-            _discordLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.DiscordLogo.png", 30 , 30 );
-            _twitterLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.TwitterLogo.png", 30 , 30 );
-
-            _versionText.Text = Program.Version;
-        }
-
-        //Events
-        private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args)
-        {
-            UrlHelper.OpenUrl("https://ryujinx.org");
-        }
-
-        private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args)
-        {
-            UrlHelper.OpenUrl("https://www.patreon.com/ryujinx");
-        }
-
-        private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args)
-        {
-            UrlHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx");
-        }
-
-        private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args)
-        {
-            UrlHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc");
-        }
-
-        private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args)
-        {
-            UrlHelper.OpenUrl("https://twitter.com/RyujinxEmu");
-        }
-
-        private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args)
-        {
-            UrlHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
-        }
-
-        private void CloseToggle_Activated(object sender, EventArgs args)
-        {
-            Dispose();
-        }
-    }
-}

+ 0 - 574
Ryujinx/Ui/AboutWindow.glade

@@ -1,574 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.1 -->
-<interface>
-  <requires lib="gtk+" version="3.20"/>
-  <object class="GtkDialog" id="_aboutWin">
-    <property name="can_focus">False</property>
-    <property name="resizable">False</property>
-    <property name="modal">True</property>
-    <property name="window_position">center</property>
-    <property name="default_width">800</property>
-    <property name="default_height">350</property>
-    <property name="type_hint">dialog</property>
-    <child type="titlebar">
-      <placeholder/>
-    </child>
-    <child internal-child="vbox">
-      <object class="GtkBox">
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox">
-            <property name="can_focus">False</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="bigBox">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkBox" id="leftBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_left">10</property>
-                <property name="margin_right">15</property>
-                <property name="margin_top">10</property>
-                <property name="margin_bottom">15</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="valign">start</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <child>
-                          <object class="GtkImage" id="_ryujinxLogo">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="margin_left">10</property>
-                            <property name="margin_right">10</property>
-                            <property name="margin_top">10</property>
-                            <property name="margin_bottom">10</property>
-                          </object>
-                          <packing>
-                            <property name="expand">True</property>
-                            <property name="fill">True</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkBox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="valign">center</property>
-                            <property name="orientation">vertical</property>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Ryujinx</property>
-                                <property name="justify">center</property>
-                                <attributes>
-                                  <attribute name="scale" value="2.7000000000000002"/>
-                                </attributes>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">(REE-YOU-JI-NX)</property>
-                                <property name="justify">center</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEventBox">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <signal name="button-press-event" handler="RyujinxButton_Pressed" swapped="no"/>
-                                <child>
-                                  <object class="GtkLabel">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="tooltip_text" translatable="yes">Click to open the Ryujinx website in your default browser</property>
-                                    <property name="label" translatable="yes">www.ryujinx.org</property>
-                                    <property name="justify">center</property>
-                                    <attributes>
-                                      <attribute name="underline" value="True"/>
-                                    </attributes>
-                                  </object>
-                                </child>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="padding">5</property>
-                                <property name="position">2</property>
-                              </packing>
-                            </child>
-                          </object>
-                          <packing>
-                            <property name="expand">True</property>
-                            <property name="fill">True</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="_versionText">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Version x.x.x (Commit Number)</property>
-                        <property name="justify">center</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="padding">2</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="license">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">MIT License</property>
-                        <property name="justify">center</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="padding">5</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="disclaimer">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Ryujinx is not affiliated with Nintendo,
-or any of its partners, in any way</property>
-                        <property name="justify">center</property>
-                        <attributes>
-                          <attribute name="scale" value="0.80000000000000004"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="padding">5</property>
-                        <property name="position">3</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="margin_top">25</property>
-                    <child>
-                      <object class="GtkEventBox" id="PatreonButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="tooltip_text" translatable="yes">Click to open the Ryujinx Patreon page in your default browser</property>
-                        <signal name="button-press-event" handler="PatreonButton_Pressed" swapped="no"/>
-                        <child>
-                          <object class="GtkBox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="orientation">vertical</property>
-                            <child>
-                              <object class="GtkImage" id="_patreonLogo">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="expand">True</property>
-                                <property name="fill">True</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Patreon</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEventBox" id="GitHubButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="tooltip_text" translatable="yes">Click to open the Ryujinx GitHub page in your default browser</property>
-                        <signal name="button-press-event" handler="GitHubButton_Pressed" swapped="no"/>
-                        <child>
-                          <object class="GtkBox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="orientation">vertical</property>
-                            <child>
-                              <object class="GtkImage" id="_gitHubLogo">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="expand">True</property>
-                                <property name="fill">True</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">GitHub</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEventBox" id="DiscordButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="tooltip_text" translatable="yes">Click to open an invite to the Ryujinx Discord server in your default browser</property>
-                        <signal name="button-press-event" handler="DiscordButton_Pressed" swapped="no"/>
-                        <child>
-                          <object class="GtkBox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="orientation">vertical</property>
-                            <child>
-                              <object class="GtkImage" id="_discordLogo">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="expand">True</property>
-                                <property name="fill">True</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Discord</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEventBox" id="TwitterButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="tooltip_text" translatable="yes">Click to open the Ryujinx Twitter page in your default browser</property>
-                        <signal name="button-press-event" handler="TwitterButton_Pressed" swapped="no"/>
-                        <child>
-                          <object class="GtkBox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="orientation">vertical</property>
-                            <child>
-                              <object class="GtkImage" id="_twitterLogo">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                              </object>
-                              <packing>
-                                <property name="expand">True</property>
-                                <property name="fill">True</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Twitter</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">3</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="pack_type">end</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkSeparator">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_top">10</property>
-                <property name="margin_bottom">10</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkBox" id="rightBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_left">15</property>
-                <property name="margin_right">10</property>
-                <property name="margin_top">40</property>
-                <property name="margin_bottom">15</property>
-                <property name="orientation">vertical</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes">About</property>
-                    <attributes>
-                      <attribute name="weight" value="bold"/>
-                    </attributes>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="margin_left">10</property>
-                    <property name="label" translatable="yes">Ryujinx is an emulator for the Nintendo Switch.
-Please support us on Patreon.
-Get all the latest news on our Twitter or Discord.
-Developers interested in contributing can find out more on our Discord.</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="padding">5</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes">Created By:</property>
-                    <attributes>
-                      <attribute name="weight" value="bold"/>
-                    </attributes>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="padding">5</property>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="margin_left">10</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkScrolledWindow">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="shadow_type">in</property>
-                        <child>
-                          <object class="GtkViewport">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <child>
-                              <object class="GtkBox">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="baseline_position">top</property>
-                                <child>
-                                  <object class="GtkLabel">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="halign">start</property>
-                                    <property name="label" translatable="yes">gdkchan
-LDj3SNuD
-Ac_K
-Thog</property>
-                                    <property name="yalign">0</property>
-                                  </object>
-                                  <packing>
-                                    <property name="expand">True</property>
-                                    <property name="fill">True</property>
-                                    <property name="position">0</property>
-                                  </packing>
-                                </child>
-                                <child>
-                                  <object class="GtkLabel">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="halign">start</property>
-                                    <property name="label" translatable="yes">»jD«
-emmaus
-Thealexbarney
-Andy A (BaronKiko)</property>
-                                    <property name="yalign">0</property>
-                                  </object>
-                                  <packing>
-                                    <property name="expand">True</property>
-                                    <property name="fill">True</property>
-                                    <property name="position">1</property>
-                                  </packing>
-                                </child>
-                              </object>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEventBox" id="ContributorsButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="valign">start</property>
-                        <signal name="button-press-event" handler="ContributorsButton_Pressed" swapped="no"/>
-                        <child>
-                          <object class="GtkLabel">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="halign">end</property>
-                            <property name="margin_right">5</property>
-                            <property name="label" translatable="yes">All Contributors...</property>
-                            <attributes>
-                              <attribute name="underline" value="True"/>
-                            </attributes>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">3</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">2</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-  </object>
-</interface>

+ 1 - 1
Ryujinx/Ui/ApplicationAddedEventArgs.cs → Ryujinx/Ui/App/ApplicationAddedEventArgs.cs

@@ -1,6 +1,6 @@
 using System;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.App
 {
     public class ApplicationAddedEventArgs : EventArgs
     {

+ 1 - 1
Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs → Ryujinx/Ui/App/ApplicationCountUpdatedEventArgs.cs

@@ -1,6 +1,6 @@
 using System;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.App
 {
     public class ApplicationCountUpdatedEventArgs : EventArgs
     {

+ 4 - 5
Ryujinx/Ui/ApplicationData.cs → Ryujinx/Ui/App/ApplicationData.cs

@@ -1,10 +1,9 @@
-using LibHac;
-using LibHac.Common;
+using LibHac.Common;
 using LibHac.Ns;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.App
 {
-    public struct ApplicationData
+    public class ApplicationData
     {
         public bool   Favorite      { get; set; }
         public byte[] Icon          { get; set; }
@@ -19,4 +18,4 @@ namespace Ryujinx.Ui
         public string Path          { get; set; }
         public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
     }
-}
+}

+ 48 - 47
Ryujinx/Ui/ApplicationLibrary.cs → Ryujinx/Ui/App/ApplicationLibrary.cs

@@ -11,6 +11,7 @@ using Ryujinx.Configuration.System;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.Loaders.Npdm;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -20,24 +21,45 @@ using System.Text.Json;
 
 using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.App
 {
     public class ApplicationLibrary
     {
-        public static event EventHandler<ApplicationAddedEventArgs>        ApplicationAdded;
-        public static event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
+        public event EventHandler<ApplicationAddedEventArgs>        ApplicationAdded;
+        public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
 
-        private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png");
-        private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png");
-        private static readonly byte[] _ncaIcon = GetResourceBytes("Ryujinx.Ui.assets.NCAIcon.png");
-        private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png");
-        private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png");
+        private readonly byte[] _nspIcon;
+        private readonly byte[] _xciIcon;
+        private readonly byte[] _ncaIcon;
+        private readonly byte[] _nroIcon;
+        private readonly byte[] _nsoIcon;
 
-        private static VirtualFileSystem _virtualFileSystem;
-        private static Language          _desiredTitleLanguage;
-        private static bool              _loadingError;
+        private VirtualFileSystem _virtualFileSystem;
+        private Language          _desiredTitleLanguage;
+        private bool              _loadingError;
 
-        public static IEnumerable<string> GetFilesInDirectory(string directory)
+        public ApplicationLibrary(VirtualFileSystem virtualFileSystem)
+        {
+            _virtualFileSystem = virtualFileSystem;
+
+            _nspIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_NSP.png");
+            _xciIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_XCI.png");
+            _ncaIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_NCA.png");
+            _nroIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_NRO.png");
+            _nsoIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_NSO.png");
+        }
+
+        private byte[] GetResourceBytes(string resourceName)
+        {
+            Stream resourceStream    = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
+            byte[] resourceByteArray = new byte[resourceStream.Length];
+
+            resourceStream.Read(resourceByteArray);
+
+            return resourceByteArray;
+        }
+
+        public IEnumerable<string> GetFilesInDirectory(string directory)
         {
             Stack<string> stack = new Stack<string>();
 
@@ -46,7 +68,7 @@ namespace Ryujinx.Ui
             while (stack.Count > 0)
             {
                 string   dir     = stack.Pop();
-                string[] content = { };
+                string[] content = Array.Empty<string>();
 
                 try
                 {
@@ -84,19 +106,18 @@ namespace Ryujinx.Ui
             }
         }
 
-        public static void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
+        public void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
         {
             controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
             controlFile.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
         }
 
-        public static void LoadApplications(List<string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage)
+        public void LoadApplications(List<string> appDirs, Language desiredTitleLanguage)
         {
             int numApplicationsFound  = 0;
             int numApplicationsLoaded = 0;
 
             _loadingError         = false;
-            _virtualFileSystem    = virtualFileSystem;
             _desiredTitleLanguage = desiredTitleLanguage;
 
             // Builds the applications list with paths to found applications
@@ -442,36 +463,26 @@ namespace Ryujinx.Ui
             }
         }
 
-        protected static void OnApplicationAdded(ApplicationAddedEventArgs e)
+        protected void OnApplicationAdded(ApplicationAddedEventArgs e)
         {
             ApplicationAdded?.Invoke(null, e);
         }
 
-        protected static void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
+        protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
         {
             ApplicationCountUpdated?.Invoke(null, e);
         }
 
-        private static byte[] GetResourceBytes(string resourceName)
-        {
-            Stream resourceStream    = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
-            byte[] resourceByteArray = new byte[resourceStream.Length];
-
-            resourceStream.Read(resourceByteArray);
-
-            return resourceByteArray;
-        }
-
-        private static void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
+        private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
         {
             (_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0);
 
             // Return the ControlFS
             controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
-            titleId = controlNca?.Header.TitleId.ToString("x16");
+            titleId   = controlNca?.Header.TitleId.ToString("x16");
         }
 
-        internal static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
+        internal ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
         {
             string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
             string metadataFile   = Path.Combine(metadataFolder, "metadata.json");
@@ -482,12 +493,7 @@ namespace Ryujinx.Ui
             {
                 Directory.CreateDirectory(metadataFolder);
 
-                appMetadata = new ApplicationMetadata
-                {
-                    Favorite   = false,
-                    TimePlayed = 0,
-                    LastPlayed = "Never"
-                };
+                appMetadata = new ApplicationMetadata();
 
                 using (FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough))
                 {
@@ -503,12 +509,7 @@ namespace Ryujinx.Ui
             {
                 Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
 
-                appMetadata = new ApplicationMetadata
-                {
-                    Favorite   = false,
-                    TimePlayed = 0,
-                    LastPlayed = "Never"
-                };
+                appMetadata = new ApplicationMetadata();
             }
 
             if (modifyFunction != null)
@@ -524,7 +525,7 @@ namespace Ryujinx.Ui
             return appMetadata;
         }
 
-        private static string ConvertSecondsToReadableString(double seconds)
+        private string ConvertSecondsToReadableString(double seconds)
         {
             const int secondsPerMinute = 60;
             const int secondsPerHour   = secondsPerMinute * 60;
@@ -552,9 +553,9 @@ namespace Ryujinx.Ui
             return readableString;
         }
 
-        private static void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher)
+        private void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher)
         {
-            Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
+            _ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
 
             if (controlData.Titles.Length > (int)desiredTitleLanguage)
             {
@@ -611,7 +612,7 @@ namespace Ryujinx.Ui
             }
         }
 
-        private static bool IsUpdateApplied(string titleId, out string version)
+        private bool IsUpdateApplied(string titleId, out string version)
         {
             string updatePath = "(unknown)";
 

+ 9 - 0
Ryujinx/Ui/App/ApplicationMetadata.cs

@@ -0,0 +1,9 @@
+namespace Ryujinx.Ui.App
+{
+    public class ApplicationMetadata
+    {
+        public bool   Favorite   { get; set; }
+        public double TimePlayed { get; set; }
+        public string LastPlayed { get; set; } = "Never";
+    }
+}

+ 1 - 6
Ryujinx/Ui/ErrorAppletDialog.cs → Ryujinx/Ui/Applet/ErrorAppletDialog.cs

@@ -1,16 +1,11 @@
 using Gtk;
-using System.Reflection;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Applet
 {
     internal class ErrorAppletDialog : MessageDialog
     {
-        internal static bool _isExitDialogOpen = false;
-
         public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null)
         {
-            Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
-
             int responseId = 0;
 
             if (buttons != null)

+ 36 - 29
Ryujinx/Ui/GtkHostUiHandler.cs → Ryujinx/Ui/Applet/GtkHostUiHandler.cs

@@ -1,12 +1,12 @@
 using Gtk;
-using Ryujinx.Common.Logging;
 using Ryujinx.HLE;
 using Ryujinx.HLE.HOS.Applets;
 using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.Threading;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Applet
 {
     internal class GtkHostUiHandler : IHostUiHandler
     {
@@ -19,16 +19,13 @@ namespace Ryujinx.Ui
 
         public bool DisplayMessageDialog(ControllerAppletUiArgs args)
         {
-            string playerCount = args.PlayerCountMin == args.PlayerCountMax
-                ? $"exactly {args.PlayerCountMin}"
-                : $"{args.PlayerCountMin}-{args.PlayerCountMax}";
+            string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}";
 
-            string message =
-                $"Application requests <b>{playerCount}</b> player(s) with:\n\n"
-                + $"<tt><b>TYPES:</b> {args.SupportedStyles}</tt>\n\n"
-                + $"<tt><b>PLAYERS:</b> {string.Join(", ", args.SupportedPlayers)}</tt>\n\n"
-                + (args.IsDocked ? "Docked mode set. <tt>Handheld</tt> is also invalid.\n\n" : "")
-                + "<i>Please reconfigure Input now and then press OK.</i>";
+            string message = $"Application requests <b>{playerCount}</b> player(s) with:\n\n"
+                           + $"<tt><b>TYPES:</b> {args.SupportedStyles}</tt>\n\n"
+                           + $"<tt><b>PLAYERS:</b> {string.Join(", ", args.SupportedPlayers)}</tt>\n\n"
+                           + (args.IsDocked ? "Docked mode set. <tt>Handheld</tt> is also invalid.\n\n" : "")
+                           + "<i>Please reconfigure Input now and then press OK.</i>";
 
             return DisplayMessageDialog("Controller Applet", message);
         }
@@ -36,17 +33,19 @@ namespace Ryujinx.Ui
         public bool DisplayMessageDialog(string title, string message)
         {
             ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
+
             bool okPressed = false;
 
             Application.Invoke(delegate
             {
                 MessageDialog msgDialog = null;
+
                 try
                 {
                     msgDialog = new MessageDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null)
                     {
-                        Title = title,
-                        Text = message,
+                        Title     = title,
+                        Text      = message,
                         UseMarkup = true
                     };
 
@@ -54,16 +53,21 @@ namespace Ryujinx.Ui
 
                     msgDialog.Response += (object o, ResponseArgs args) =>
                     {
-                        if (args.ResponseId == ResponseType.Ok) okPressed = true;
+                        if (args.ResponseId == ResponseType.Ok)
+                        {
+                            okPressed = true;
+                        }
+
                         dialogCloseEvent.Set();
                         msgDialog?.Dispose();
                     };
 
                     msgDialog.Show();
                 }
-                catch (Exception e)
+                catch (Exception ex)
                 {
-                    Logger.Error?.Print(LogClass.Application, $"Error displaying Message Dialog: {e}");
+                    GtkDialog.CreateErrorDialog($"Error displaying Message Dialog: {ex}");
+
                     dialogCloseEvent.Set();
                 }
             });
@@ -76,24 +80,25 @@ namespace Ryujinx.Ui
         public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
         {
             ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
-            bool okPressed = false;
-            bool error = false;
+
+            bool   okPressed = false;
+            bool   error     = false;
             string inputText = args.InitialText ?? "";
 
             Application.Invoke(delegate
             {
                 try
                 {
-                    var swkbdDialog = new InputDialog(_parent)
+                    var swkbdDialog = new SwkbdAppletDialog(_parent)
                     {
-                        Title = "Software Keyboard",
-                        Text = args.HeaderText,
+                        Title         = "Software Keyboard",
+                        Text          = args.HeaderText,
                         SecondaryText = args.SubtitleText
                     };
 
-                    swkbdDialog.InputEntry.Text = inputText;
+                    swkbdDialog.InputEntry.Text            = inputText;
                     swkbdDialog.InputEntry.PlaceholderText = args.GuideText;
-                    swkbdDialog.OkButton.Label = args.SubmitText;
+                    swkbdDialog.OkButton.Label             = args.SubmitText;
 
                     swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
 
@@ -105,10 +110,11 @@ namespace Ryujinx.Ui
 
                     swkbdDialog.Dispose();
                 }
-                catch (Exception e)
+                catch (Exception ex)
                 {
                     error = true;
-                    Logger.Error?.Print(LogClass.Application, $"Error displaying Software Keyboard: {e}");
+
+                    GtkDialog.CreateErrorDialog($"Error displaying Software Keyboard: {ex}");
                 }
                 finally
                 {
@@ -126,12 +132,13 @@ namespace Ryujinx.Ui
         public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value)
         {
             device.UserChannelPersistence.ExecuteProgram(kind, value);
-            MainWindow.GlWidget?.Exit();
+            ((MainWindow)_parent).GlRendererWidget?.Exit();
         }
 
         public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
         {
             ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
+
             bool showDetails = false;
 
             Application.Invoke(delegate
@@ -167,9 +174,9 @@ namespace Ryujinx.Ui
 
                     msgDialog.Show();
                 }
-                catch (Exception e)
+                catch (Exception ex)
                 {
-                    Logger.Error?.Print(LogClass.Application, $"Error displaying ErrorApplet Dialog: {e}");
+                    GtkDialog.CreateErrorDialog($"Error displaying ErrorApplet Dialog: {ex}");
 
                     dialogCloseEvent.Set();
                 }
@@ -180,4 +187,4 @@ namespace Ryujinx.Ui
             return showDetails;
         }
     }
-}
+}

+ 89 - 0
Ryujinx/Ui/Applet/SwkbdAppletDialog.cs

@@ -0,0 +1,89 @@
+using Gtk;
+using System;
+
+namespace Ryujinx.Ui.Applet
+{
+    public class SwkbdAppletDialog : MessageDialog
+    {
+        private int _inputMin;
+        private int _inputMax;
+
+        private Predicate<int> _checkLength;
+
+        private readonly Label _validationInfo;
+
+        public Entry  InputEntry   { get; }
+        public Button OkButton     { get; }
+        public Button CancelButton { get; }
+
+        public SwkbdAppletDialog(Window parent) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.None, null)
+        {
+            SetDefaultSize(300, 0);
+
+            _validationInfo = new Label()
+            {
+                Visible = false
+            };
+
+            InputEntry = new Entry()
+            {
+                Visible = true
+            };
+
+            InputEntry.Activated += OnInputActivated;
+            InputEntry.Changed   += OnInputChanged;
+
+            OkButton     = (Button)AddButton("OK",     ResponseType.Ok);
+            CancelButton = (Button)AddButton("Cancel", ResponseType.Cancel);
+
+            ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0);
+            ((Box)MessageArea).PackEnd(InputEntry,      true, true, 4);
+
+            SetInputLengthValidation(0, int.MaxValue); // Disable by default.
+        }
+
+        public void SetInputLengthValidation(int min, int max)
+        {
+            _inputMin = Math.Min(min, max);
+            _inputMax = Math.Max(min, max);
+
+            _validationInfo.Visible = false;
+
+            if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable.
+            {
+                _validationInfo.Visible = false;
+
+                _checkLength = (length) => true;
+            }
+            else if (_inputMin > 0 && _inputMax == int.MaxValue)
+            {
+                _validationInfo.Visible = true;
+                _validationInfo.Markup  = $"<i>Must be at least {_inputMin} characters long</i>";
+
+                _checkLength = (length) => _inputMin <= length;
+            }
+            else
+            {
+                _validationInfo.Visible = true;
+                _validationInfo.Markup  = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
+
+                _checkLength = (length) => _inputMin <= length && length <= _inputMax;
+            }
+
+            OnInputChanged(this, EventArgs.Empty);
+        }
+
+        private void OnInputActivated(object sender, EventArgs e)
+        {
+            if (OkButton.IsSensitive)
+            {
+                Respond(ResponseType.Ok);
+            }
+        }
+
+        private void OnInputChanged(object sender, EventArgs e)
+        {
+            OkButton.Sensitive = _checkLength(InputEntry.Text.Length);
+        }
+    }
+}

+ 0 - 9
Ryujinx/Ui/ApplicationMetadata.cs

@@ -1,9 +0,0 @@
-namespace Ryujinx.Ui
-{
-    internal class ApplicationMetadata
-    {
-        public bool   Favorite   { get; set; }
-        public double TimePlayed { get; set; }
-        public string LastPlayed { get; set; }
-    }
-}

+ 0 - 36
Ryujinx/Ui/Diagnostic/GuideDialog.cs

@@ -1,36 +0,0 @@
-using Gtk;
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-using System.Text;
-
-namespace Ryujinx.Ui.Diagnostic
-{
-    internal class GuideDialog : MessageDialog
-    {
-        internal static bool _isExitDialogOpen = false;
-
-        public GuideDialog(string title, string mainText, string secondaryText) : base(null, DialogFlags.Modal, MessageType.Other, ButtonsType.None, null)
-        {
-            Title = title;
-            Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
-            Text = mainText;
-            SecondaryText = secondaryText;
-            WindowPosition = WindowPosition.Center;
-            Response += GtkDialog_Response;
-
-            Button guideButton = new Button();
-            guideButton.Label = "Open the Setup Guide";
-
-            ContentArea.Add(guideButton);
-
-            SetSizeRequest(100, 10);
-            ShowAll();
-        }
-
-        private void GtkDialog_Response(object sender, ResponseArgs args)
-        {
-            Dispose();
-        }
-    }
-}

+ 0 - 137
Ryujinx/Ui/Diagnostic/UserErrorDialog.cs

@@ -1,137 +0,0 @@
-using Gtk;
-using System.Reflection;
-
-namespace Ryujinx.Ui.Diagnostic
-{
-    internal class UserErrorDialog : MessageDialog
-    {
-        private static string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide";
-        private const int OkResponseId = 0;
-        private const int SetupGuideResponseId = 1;
-
-        private UserError _userError;
-
-        private UserErrorDialog(UserError error) : base(null, DialogFlags.Modal, MessageType.Error, ButtonsType.None, null)
-        {
-            _userError = error;
-            Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
-            WindowPosition = WindowPosition.Center;
-            Response += UserErrorDialog_Response;
-
-            SetSizeRequest(120, 50);
-
-            AddButton("OK", OkResponseId);
-
-            bool isInSetupGuide = IsCoveredBySetupGuide(error);
-
-            if (isInSetupGuide)
-            {
-                AddButton("Open the Setup Guide", SetupGuideResponseId);
-            }
-
-            string errorCode = GetErrorCode(error);
-
-            SecondaryUseMarkup = true;
-
-            Title = $"Ryujinx error ({errorCode})";
-            Text = $"{errorCode}: {GetErrorTitle(error)}";
-            SecondaryText = GetErrorDescription(error);
-
-            if (isInSetupGuide)
-            {
-                SecondaryText += "\n<b>For more information on how to fix this error, follow our Setup Guide.</b>";
-            }
-        }
-
-        private static string GetErrorCode(UserError error)
-        {
-            return $"RYU-{(uint)error:X4}";
-        }
-
-        private static string GetErrorTitle(UserError error)
-        {
-            switch (error)
-            {
-                case UserError.NoKeys:
-                    return "Keys not found";
-                case UserError.NoFirmware:
-                    return "Firmware not found";
-                case UserError.FirmwareParsingFailed:
-                    return "Firmware parsing error";
-                case UserError.ApplicationNotFound:
-                    return "Application not found";
-                case UserError.Unknown:
-                    return "Unknown error";
-                default:
-                    return "Undefined error";
-            }
-        }
-
-        private static string GetErrorDescription(UserError error)
-        {
-            switch (error)
-            {
-                case UserError.NoKeys:
-                    return "Ryujinx was unable to find your 'prod.keys' file";
-                case UserError.NoFirmware:
-                    return "Ryujinx was unable to find any firmwares installed";
-                case UserError.FirmwareParsingFailed:
-                    return "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.";
-                case UserError.ApplicationNotFound:
-                    return "Ryujinx couldn't find a valid application at the given path.";
-                case UserError.Unknown:
-                    return "An unknown error occured!";
-                default:
-                    return "An undefined error occured! This shouldn't happen, please contact a dev!";
-            }
-        }
-
-        private static bool IsCoveredBySetupGuide(UserError error)
-        {
-            switch (error)
-            {
-                case UserError.NoKeys:
-                case UserError.NoFirmware:
-                case UserError.FirmwareParsingFailed:
-                    return true;
-                default:
-                    return false;
-            }
-        }
-
-        private static string GetSetupGuideUrl(UserError error)
-        {
-            if (!IsCoveredBySetupGuide(error))
-            {
-                return null;
-            }
-
-            switch (error)
-            {
-                case UserError.NoKeys:
-                    return SetupGuideUrl + "#initial-setup---placement-of-prodkeys";
-                case UserError.NoFirmware:
-                    return SetupGuideUrl + "#initial-setup-continued---installation-of-firmware";
-            }
-
-            return SetupGuideUrl;
-        }
-
-        private void UserErrorDialog_Response(object sender, ResponseArgs args)
-        {
-            int responseId = (int)args.ResponseId;
-
-            if (responseId == SetupGuideResponseId)
-            {
-                UrlHelper.OpenUrl(GetSetupGuideUrl(_userError));
-            }
-
-            Dispose();
-        }
-
-        public static void CreateUserErrorDialog(UserError error)
-        {
-            new UserErrorDialog(error).Run();
-        }
-    }
-}

+ 6 - 5
Ryujinx/Ui/GLRenderer.cs

@@ -11,7 +11,8 @@ using Ryujinx.Configuration;
 using Ryujinx.Graphics.OpenGL;
 using Ryujinx.HLE;
 using Ryujinx.HLE.HOS.Services.Hid;
-using Ryujinx.Motion;
+using Ryujinx.Modules.Motion;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.Collections.Generic;
 using System.Threading;
@@ -73,9 +74,9 @@ namespace Ryujinx.Ui
 
             _device = device;
 
-            this.Initialized  += GLRenderer_Initialized;
-            this.Destroyed    += GLRenderer_Destroyed;
-            this.ShuttingDown += GLRenderer_ShuttingDown;
+            Initialized  += GLRenderer_Initialized;
+            Destroyed    += GLRenderer_Destroyed;
+            ShuttingDown += GLRenderer_ShuttingDown;
 
             Initialize();
 
@@ -89,7 +90,7 @@ namespace Ryujinx.Ui
                           | EventMask.KeyPressMask
                           | EventMask.KeyReleaseMask));
 
-            this.Shown += Renderer_Shown;
+            Shown += Renderer_Shown;
 
             _dsuClient = new Client();
 

+ 13 - 3
Ryujinx/Ui/UrlHelper.cs → Ryujinx/Ui/Helper/OpenHelper.cs

@@ -2,10 +2,20 @@
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Helper
 {
-    static class UrlHelper
+    static class OpenHelper
     {
+        public static void OpenFolder(string path)
+        {
+            Process.Start(new ProcessStartInfo
+            {
+                FileName        = path,
+                UseShellExecute = true,
+                Verb            = "open"
+            });
+        }
+
         public static void OpenUrl(string url)
         {
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -26,4 +36,4 @@ namespace Ryujinx.Ui
             }
         }
     }
-}
+}

+ 2 - 1
Ryujinx/Ui/Diagnostic/SetupValidator.cs → Ryujinx/Ui/Helper/SetupValidator.cs

@@ -1,9 +1,10 @@
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem.Content;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.IO;
 
-namespace Ryujinx.Ui.Diagnostic
+namespace Ryujinx.Ui.Helper
 {
     /// <summary>
     /// Ensure installation validity

+ 116 - 0
Ryujinx/Ui/Helper/SortHelper.cs

@@ -0,0 +1,116 @@
+using Gtk;
+using System;
+
+namespace Ryujinx.Ui.Helper
+{
+    static class SortHelper
+    {
+        public static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
+        {
+            string aValue = model.GetValue(a, 5).ToString();
+            string bValue = model.GetValue(b, 5).ToString();
+
+            if (aValue.Length > 4 && aValue[^4..] == "mins")
+            {
+                aValue = (float.Parse(aValue[0..^5]) * 60).ToString();
+            }
+            else if (aValue.Length > 3 && aValue[^3..] == "hrs")
+            {
+                aValue = (float.Parse(aValue[0..^4]) * 3600).ToString();
+            }
+            else if (aValue.Length > 4 && aValue[^4..] == "days")
+            {
+                aValue = (float.Parse(aValue[0..^5]) * 86400).ToString();
+            }
+            else
+            {
+                aValue = aValue[0..^1];
+            }
+
+            if (bValue.Length > 4 && bValue[^4..] == "mins")
+            {
+                bValue = (float.Parse(bValue[0..^5]) * 60).ToString();
+            }
+            else if (bValue.Length > 3 && bValue[^3..] == "hrs")
+            {
+                bValue = (float.Parse(bValue[0..^4]) * 3600).ToString();
+            }
+            else if (bValue.Length > 4 && bValue[^4..] == "days")
+            {
+                bValue = (float.Parse(bValue[0..^5]) * 86400).ToString();
+            }
+            else
+            {
+                bValue = bValue[0..^1];
+            }
+
+            if (float.Parse(aValue) > float.Parse(bValue))
+            {
+                return -1;
+            }
+            else if (float.Parse(bValue) > float.Parse(aValue))
+            {
+                return 1;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        public static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b)
+        {
+            string aValue = model.GetValue(a, 6).ToString();
+            string bValue = model.GetValue(b, 6).ToString();
+
+            if (aValue == "Never")
+            {
+                aValue = DateTime.UnixEpoch.ToString();
+            }
+
+            if (bValue == "Never")
+            {
+                bValue = DateTime.UnixEpoch.ToString();
+            }
+
+            return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
+        }
+
+        public static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b)
+        {
+            string aValue = model.GetValue(a, 8).ToString();
+            string bValue = model.GetValue(b, 8).ToString();
+
+            if (aValue[^2..] == "GB")
+            {
+                aValue = (float.Parse(aValue[0..^2]) * 1024).ToString();
+            }
+            else
+            {
+                aValue = aValue[0..^2];
+            }
+
+            if (bValue[^2..] == "GB")
+            {
+                bValue = (float.Parse(bValue[0..^2]) * 1024).ToString();
+            }
+            else
+            {
+                bValue = bValue[0..^2];
+            }
+
+            if (float.Parse(aValue) > float.Parse(bValue))
+            {
+                return -1;
+            }
+            else if (float.Parse(bValue) > float.Parse(aValue))
+            {
+                return 1;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+    }
+}

+ 35 - 0
Ryujinx/Ui/Helper/ThemeHelper.cs

@@ -0,0 +1,35 @@
+using Gtk;
+using Ryujinx.Common.Logging;
+using Ryujinx.Configuration;
+using System.IO;
+
+namespace Ryujinx.Ui.Helper
+{
+    static class ThemeHelper
+    {
+        public static void ApplyTheme()
+        {
+            if (!ConfigurationState.Instance.Ui.EnableCustomTheme)
+            {
+                return;
+            }
+
+            if (File.Exists(ConfigurationState.Instance.Ui.CustomThemePath) && (Path.GetExtension(ConfigurationState.Instance.Ui.CustomThemePath) == ".css"))
+            {
+                CssProvider cssProvider = new CssProvider();
+
+                cssProvider.LoadFromPath(ConfigurationState.Instance.Ui.CustomThemePath);
+
+                StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
+            }
+            else
+            {
+                Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\".");
+
+                ConfigurationState.Instance.Ui.CustomThemePath.Value   = "";
+                ConfigurationState.Instance.Ui.EnableCustomTheme.Value = false;
+                ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
+            }
+        }
+    }
+}

+ 0 - 69
Ryujinx/Ui/InputDialog.cs

@@ -1,69 +0,0 @@
-using Gtk;
-using System;
-
-namespace Ryujinx.Ui
-{
-    public class InputDialog : MessageDialog
-    {
-        private int _inputMin, _inputMax;
-        private Predicate<int> _checkLength;
-        private Label _validationInfo;
-
-        public Entry InputEntry { get; }
-        public Button OkButton { get; }
-        public Button CancelButton { get; }
-
-        public InputDialog(Window parent)
-            : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.None, null)
-        {
-            SetDefaultSize(300, 0);
-
-            _validationInfo = new Label() { Visible = false };
-
-            InputEntry = new Entry() { Visible = true };
-            InputEntry.Activated += (object sender, EventArgs e) => { if (OkButton.IsSensitive) Respond(ResponseType.Ok); };
-            InputEntry.Changed += OnInputChanged;
-
-            OkButton = (Button)AddButton("OK", ResponseType.Ok);
-            CancelButton = (Button)AddButton("Cancel", ResponseType.Cancel);
-
-            ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0);
-            ((Box)MessageArea).PackEnd(InputEntry, true, true, 4);
-
-            SetInputLengthValidation(0, int.MaxValue); // disable by default
-        }
-
-        public void SetInputLengthValidation(int min, int max)
-        {
-            _inputMin = Math.Min(min, max);
-            _inputMax = Math.Max(min, max);
-
-            _validationInfo.Visible = false;
-
-            if (_inputMin <= 0 && _inputMax == int.MaxValue) // disable
-            {
-                _validationInfo.Visible = false;
-                _checkLength = (length) => true;
-            }
-            else if (_inputMin > 0 && _inputMax == int.MaxValue)
-            {
-                _validationInfo.Visible = true;
-                _validationInfo.Markup = $"<i>Must be at least {_inputMin} characters long</i>";
-                _checkLength = (length) => _inputMin <= length;
-            }
-            else
-            {
-                _validationInfo.Visible = true;
-                _validationInfo.Markup = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
-                _checkLength = (length) => _inputMin <= length && length <= _inputMax;
-            }
-
-            OnInputChanged(this, EventArgs.Empty);
-        }
-
-        private void OnInputChanged(object sender, EventArgs e)
-        {
-            OkButton.Sensitive = _checkLength(InputEntry.Text.Length);
-        }
-    }
-}

Plik diff jest za duży
+ 243 - 342
Ryujinx/Ui/MainWindow.cs


+ 0 - 189
Ryujinx/Ui/Migration.cs

@@ -1,189 +0,0 @@
-using Gtk;
-using LibHac;
-using Ryujinx.Common.Configuration;
-using Ryujinx.HLE.FileSystem;
-using System;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-
-namespace Ryujinx.Ui
-{
-    internal class Migration
-    {
-        private VirtualFileSystem _virtualFileSystem;
-
-        public Migration(VirtualFileSystem virtualFileSystem)
-        {
-            _virtualFileSystem = virtualFileSystem;
-        }
-
-        public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded)
-        {
-            if (!IsMigrationNeeded())
-            {
-                isMigrationNeeded = false;
-
-                return true;
-            }
-
-            isMigrationNeeded = true;
-
-            int dialogResponse;
-
-            using (MessageDialog dialog = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Question,
-                ButtonsType.YesNo, "What's this?"))
-            {
-                dialog.Title = "Data Migration Needed";
-                dialog.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
-                dialog.Text =
-                    "The folder structure of Ryujinx's RyuFs folder has been updated and renamed to \"Ryujinx\". " +
-                    "Your RyuFs folder must be copied and migrated to the new \"Ryujinx\" structure. Would you like to do the migration now?\n\n" +
-                    "Select \"Yes\" to automatically perform the migration. Your old RyuFs folder will remain as it is.\n\n" +
-                    "Selecting \"No\" will exit Ryujinx without changing anything.";
-
-                dialogResponse = dialog.Run();
-            }
-
-            return dialogResponse == (int)ResponseType.Yes;
-        }
-
-        public static bool DoMigrationForStartup(MainWindow parentWindow, VirtualFileSystem virtualFileSystem)
-        {
-            try
-            {
-                Migration migration = new Migration(virtualFileSystem);
-                int saveCount = migration.Migrate();
-
-                using MessageDialog dialogSuccess = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, null)
-                {
-                    Title = "Migration Success",
-                    Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
-                    Text = $"Data migration was successful. {saveCount} saves were migrated.",
-                };
-
-                dialogSuccess.Run();
-
-                return true;
-            }
-            catch (HorizonResultException ex)
-            {
-                GtkDialog.CreateErrorDialog(ex.Message);
-
-                return false;
-            }
-        }
-
-        // Returns the number of saves migrated
-        public int Migrate()
-        {
-            // Make sure FsClient is initialized
-            _virtualFileSystem.Reload();
-
-            string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
-
-            string oldBasePath = Path.Combine(appDataPath, "RyuFs");
-            string newBasePath = Path.Combine(appDataPath, "Ryujinx");
-
-            string oldSaveDir = Path.Combine(oldBasePath, "nand/user/save");
-
-            CopyRyuFs(oldBasePath, newBasePath);
-
-            SaveImporter importer = new SaveImporter(oldSaveDir, _virtualFileSystem.FsClient);
-
-            return importer.Import();
-        }
-
-        private static void CopyRyuFs(string oldPath, string newPath)
-        {
-            Directory.CreateDirectory(newPath);
-
-            CopyExcept(oldPath, newPath, "nand", "bis", "sdmc", "sdcard");
-
-            string oldNandPath = Path.Combine(oldPath, "nand");
-            string newNandPath = Path.Combine(newPath, "bis");
-
-            CopyExcept(oldNandPath, newNandPath, "system", "user");
-
-            string oldSdPath = Path.Combine(oldPath, "sdmc");
-            string newSdPath = Path.Combine(newPath, "sdcard");
-
-            CopyDirectory(oldSdPath, newSdPath);
-
-            string oldSystemPath = Path.Combine(oldNandPath, "system");
-            string newSystemPath = Path.Combine(newNandPath, "system");
-
-            CopyExcept(oldSystemPath, newSystemPath, "save");
-
-            string oldUserPath = Path.Combine(oldNandPath, "user");
-            string newUserPath = Path.Combine(newNandPath, "user");
-
-            CopyExcept(oldUserPath, newUserPath, "save");
-        }
-
-        private static void CopyExcept(string srcPath, string dstPath, params string[] exclude)
-        {
-            exclude = exclude.Select(x => x.ToLowerInvariant()).ToArray();
-
-            DirectoryInfo srcDir = new DirectoryInfo(srcPath);
-
-            if (!srcDir.Exists)
-            {
-                return;
-            }
-
-            Directory.CreateDirectory(dstPath);
-
-            foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories())
-            {
-                if (exclude.Contains(subDir.Name.ToLowerInvariant()))
-                {
-                    continue;
-                }
-
-                CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name));
-            }
-
-            foreach (FileInfo file in srcDir.EnumerateFiles())
-            {
-                file.CopyTo(Path.Combine(dstPath, file.Name));
-            }
-        }
-
-        private static void CopyDirectory(string srcPath, string dstPath)
-        {
-            Directory.CreateDirectory(dstPath);
-
-            DirectoryInfo srcDir = new DirectoryInfo(srcPath);
-
-            if (!srcDir.Exists)
-            {
-                return;
-            }
-
-            Directory.CreateDirectory(dstPath);
-
-            foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories())
-            {
-                CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name));
-            }
-
-            foreach (FileInfo file in srcDir.EnumerateFiles())
-            {
-                file.CopyTo(Path.Combine(dstPath, file.Name));
-            }
-        }
-
-        public static bool IsMigrationNeeded()
-        {
-            if (AppDataManager.IsCustomBasePath) return false;
-
-            string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
-
-            string oldBasePath = Path.Combine(appDataPath, "RyuFs");
-            string newBasePath = Path.Combine(appDataPath, "Ryujinx");
-
-            return Directory.Exists(oldBasePath) && !Directory.Exists(newBasePath);
-        }
-    }
-}

+ 0 - 0
Ryujinx/Ui/assets/JoyConLeft.svg → Ryujinx/Ui/Resources/Controller_JoyConLeft.svg


+ 0 - 0
Ryujinx/Ui/assets/JoyConPair.svg → Ryujinx/Ui/Resources/Controller_JoyConPair.svg


+ 0 - 0
Ryujinx/Ui/assets/JoyConRight.svg → Ryujinx/Ui/Resources/Controller_JoyConRight.svg


+ 0 - 0
Ryujinx/Ui/assets/ProCon.svg → Ryujinx/Ui/Resources/Controller_ProCon.svg


+ 0 - 0
Ryujinx/Ui/assets/NCAIcon.png → Ryujinx/Ui/Resources/Icon_NCA.png


+ 0 - 0
Ryujinx/Ui/assets/NROIcon.png → Ryujinx/Ui/Resources/Icon_NRO.png


+ 0 - 0
Ryujinx/Ui/assets/NSOIcon.png → Ryujinx/Ui/Resources/Icon_NSO.png


+ 0 - 0
Ryujinx/Ui/assets/NSPIcon.png → Ryujinx/Ui/Resources/Icon_NSP.png


+ 0 - 0
Ryujinx/Ui/assets/XCIIcon.png → Ryujinx/Ui/Resources/Icon_XCI.png


+ 0 - 0
Ryujinx/Ui/assets/DiscordLogo.png → Ryujinx/Ui/Resources/Logo_Discord.png


+ 0 - 0
Ryujinx/Ui/assets/GitHubLogo.png → Ryujinx/Ui/Resources/Logo_GitHub.png


+ 0 - 0
Ryujinx/Ui/assets/PatreonLogo.png → Ryujinx/Ui/Resources/Logo_Patreon.png


+ 0 - 0
Ryujinx/Ui/assets/Icon.png → Ryujinx/Ui/Resources/Logo_Ryujinx.png


+ 0 - 0
Ryujinx/Ui/assets/TwitterLogo.png → Ryujinx/Ui/Resources/Logo_Twitter.png


+ 0 - 219
Ryujinx/Ui/SaveImporter.cs

@@ -1,219 +0,0 @@
-using LibHac;
-using LibHac.Common;
-using LibHac.Fs;
-using LibHac.Fs.Shim;
-using LibHac.FsSystem;
-using LibHac.Ncm;
-using Ryujinx.HLE.Utilities;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.CompilerServices;
-
-using ApplicationId = LibHac.Ncm.ApplicationId;
-
-namespace Ryujinx.Ui
-{
-    internal class SaveImporter
-    {
-        private FileSystemClient FsClient { get; }
-        private string ImportPath { get; }
-
-        public SaveImporter(string importPath, FileSystemClient destFsClient)
-        {
-            ImportPath = importPath;
-            FsClient = destFsClient;
-        }
-
-        // Returns the number of saves imported
-        public int Import()
-        {
-            return ImportSaves(FsClient, ImportPath);
-        }
-
-        private static int ImportSaves(FileSystemClient fsClient, string rootSaveDir)
-        {
-            if (!Directory.Exists(rootSaveDir))
-            {
-                return 0;
-            }
-
-            SaveFinder finder = new SaveFinder();
-            finder.FindSaves(rootSaveDir);
-
-            foreach (SaveToImport save in finder.Saves)
-            {
-                Result importResult = ImportSave(fsClient, save);
-
-                if (importResult.IsFailure())
-                {
-                    throw new HorizonResultException(importResult, $"Error importing save {save.Path}");
-                }
-            }
-
-            return finder.Saves.Count;
-        }
-
-        private static Result ImportSave(FileSystemClient fs, SaveToImport save)
-        {
-            SaveDataAttribute key = save.Attribute;
-
-            Result result = fs.CreateSaveData(new ApplicationId(key.ProgramId.Value), key.UserId, key.ProgramId.Value, 0, 0, 0);
-            if (result.IsFailure()) return result;
-
-            bool isOldMounted = false;
-            bool isNewMounted = false;
-
-            try
-            {
-                result = fs.Register("OldSave".ToU8Span(), new LocalFileSystem(save.Path));
-                if (result.IsFailure()) return result;
-
-                isOldMounted = true;
-
-                result = fs.MountSaveData("NewSave".ToU8Span(), new ApplicationId(key.ProgramId.Value), key.UserId);
-                if (result.IsFailure()) return result;
-
-                isNewMounted = true;
-
-                result = fs.CopyDirectory("OldSave:/", "NewSave:/");
-                if (result.IsFailure()) return result;
-
-                result = fs.Commit("NewSave".ToU8Span());
-            }
-            finally
-            {
-                if (isOldMounted)
-                {
-                    fs.Unmount("OldSave".ToU8Span());
-                }
-
-                if (isNewMounted)
-                {
-                    fs.Unmount("NewSave".ToU8Span());
-                }
-            }
-
-            return result;
-        }
-
-        private class SaveFinder
-        {
-            public List<SaveToImport> Saves { get; } = new List<SaveToImport>();
-
-            public void FindSaves(string rootPath)
-            {
-                foreach (string subDir in Directory.EnumerateDirectories(rootPath))
-                {
-                    if (TryGetUInt64(subDir, out ulong saveDataId))
-                    {
-                        SearchSaveId(subDir, saveDataId);
-                    }
-                }
-            }
-
-            private void SearchSaveId(string path, ulong saveDataId)
-            {
-                foreach (string subDir in Directory.EnumerateDirectories(path))
-                {
-                    if (TryGetUserId(subDir, out UserId userId))
-                    {
-                        SearchUser(subDir, saveDataId, userId);
-                    }
-                }
-            }
-
-            private void SearchUser(string path, ulong saveDataId, UserId userId)
-            {
-                foreach (string subDir in Directory.EnumerateDirectories(path))
-                {
-                    if (TryGetUInt64(subDir, out ulong titleId) && TryGetDataPath(subDir, out string dataPath))
-                    {
-                        SaveDataAttribute attribute = new SaveDataAttribute
-                        {
-                            Type = SaveDataType.Account,
-                            UserId = userId,
-                            ProgramId = new ProgramId(titleId)
-                        };
-
-                        SaveToImport save = new SaveToImport(dataPath, attribute);
-
-                        Saves.Add(save);
-                    }
-                }
-            }
-
-            private static bool TryGetDataPath(string path, out string dataPath)
-            {
-                string committedPath = Path.Combine(path, "0");
-                string workingPath = Path.Combine(path, "1");
-
-                if (Directory.Exists(committedPath) && Directory.EnumerateFileSystemEntries(committedPath).Any())
-                {
-                    dataPath = committedPath;
-                    return true;
-                }
-
-                if (Directory.Exists(workingPath) && Directory.EnumerateFileSystemEntries(workingPath).Any())
-                {
-                    dataPath = workingPath;
-                    return true;
-                }
-
-                dataPath = default;
-                return false;
-            }
-
-            private static bool TryGetUInt64(string path, out ulong converted)
-            {
-                string name = Path.GetFileName(path);
-
-                if (name.Length == 16)
-                {
-                    try
-                    {
-                        converted = Convert.ToUInt64(name, 16);
-                        return true;
-                    }
-                    catch { }
-                }
-
-                converted = default;
-                return false;
-            }
-
-            private static bool TryGetUserId(string path, out UserId userId)
-            {
-                string name = Path.GetFileName(path);
-
-                if (name.Length == 32)
-                {
-                    try
-                    {
-                        UInt128 id = new UInt128(name);
-
-                        userId = Unsafe.As<UInt128, UserId>(ref id);
-                        return true;
-                    }
-                    catch { }
-                }
-
-                userId = default;
-                return false;
-            }
-        }
-
-        private class SaveToImport
-        {
-            public string Path { get; }
-            public SaveDataAttribute Attribute { get; }
-
-            public SaveToImport(string path, SaveDataAttribute attribute)
-            {
-                Path = path;
-                Attribute = attribute;
-            }
-        }
-    }
-}

+ 1 - 1
Ryujinx/Ui/StatusUpdatedEventArgs.cs

@@ -21,4 +21,4 @@ namespace Ryujinx.Ui
             GpuName      = gpuName;
         }
     }
-}
+}

+ 198 - 0
Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs

@@ -0,0 +1,198 @@
+using Gtk;
+
+namespace Ryujinx.Ui.Widgets
+{
+    public partial class GameTableContextMenu : Menu
+    {
+        private MenuItem _openSaveUserDirMenuItem;
+        private MenuItem _openSaveDeviceDirMenuItem;
+        private MenuItem _openSaveBcatDirMenuItem;
+        private MenuItem _manageTitleUpdatesMenuItem;
+        private MenuItem _manageDlcMenuItem;
+        private MenuItem _openTitleModDirMenuItem;
+        private Menu     _extractSubMenu;
+        private MenuItem _extractMenuItem;
+        private MenuItem _extractRomFsMenuItem;
+        private MenuItem _extractExeFsMenuItem;
+        private MenuItem _extractLogoMenuItem;
+        private Menu     _manageSubMenu;
+        private MenuItem _manageCacheMenuItem;
+        private MenuItem _purgePtcCacheMenuItem;
+        private MenuItem _purgeShaderCacheMenuItem;
+        private MenuItem _openPtcDirMenuItem;
+        private MenuItem _openShaderCacheDirMenuItem;
+
+        private void InitializeComponent()
+        {
+            //
+            // _openSaveUserDirMenuItem
+            //
+            _openSaveUserDirMenuItem = new MenuItem("Open User Save Directory")
+            {
+                TooltipText = "Open the directory which contains Application's User Saves."
+            };
+            _openSaveUserDirMenuItem.Activated += OpenSaveUserDir_Clicked;
+
+            //
+            // _openSaveDeviceDirMenuItem
+            //
+            _openSaveDeviceDirMenuItem = new MenuItem("Open Device Save Directory")
+            {
+                TooltipText = "Open the directory which contains Application's Device Saves."
+            };
+            _openSaveDeviceDirMenuItem.Activated += OpenSaveDeviceDir_Clicked;
+
+            //
+            // _openSaveBcatDirMenuItem
+            //
+            _openSaveBcatDirMenuItem = new MenuItem("Open BCAT Save Directory")
+            {
+                TooltipText = "Open the directory which contains Application's BCAT Saves."
+            };
+            _openSaveBcatDirMenuItem.Activated += OpenSaveBcatDir_Clicked;
+
+            //
+            // _manageTitleUpdatesMenuItem
+            //
+            _manageTitleUpdatesMenuItem = new MenuItem("Manage Title Updates")
+            {
+                TooltipText = "Open the Title Update management window"
+            };
+            _manageTitleUpdatesMenuItem.Activated += ManageTitleUpdates_Clicked;
+
+            //
+            // _manageDlcMenuItem
+            //
+            _manageDlcMenuItem = new MenuItem("Manage DLC")
+            {
+                TooltipText = "Open the DLC management window"
+            };
+            _manageDlcMenuItem.Activated += ManageDlc_Clicked;
+
+            //
+            // _openTitleModDirMenuItem
+            //
+            _openTitleModDirMenuItem = new MenuItem("Open Mods Directory")
+            {
+                TooltipText = "Open the directory which contains Application's Mods."
+            };
+            _openTitleModDirMenuItem.Activated += OpenTitleModDir_Clicked;
+
+            //
+            // _extractSubMenu
+            //
+            _extractSubMenu = new Menu();
+
+            //
+            // _extractMenuItem
+            //
+            _extractMenuItem = new MenuItem("Extract Data")
+            {
+                Submenu = _extractSubMenu
+            };
+
+            //
+            // _extractRomFsMenuItem
+            //
+            _extractRomFsMenuItem = new MenuItem("RomFS")
+            {
+                TooltipText = "Extract the RomFS section from Application's current config (including updates)."
+            };
+            _extractRomFsMenuItem.Activated += ExtractRomFs_Clicked;
+
+            //
+            // _extractExeFsMenuItem
+            //
+            _extractExeFsMenuItem = new MenuItem("ExeFS")
+            {
+                TooltipText = "Extract the ExeFS section from Application's current config (including updates)."
+            };
+            _extractExeFsMenuItem.Activated += ExtractExeFs_Clicked;
+
+            //
+            // _extractLogoMenuItem
+            //
+            _extractLogoMenuItem = new MenuItem("Logo")
+            {
+                TooltipText = "Extract the Logo section from Application's current config (including updates)."
+            };
+            _extractLogoMenuItem.Activated += ExtractLogo_Clicked;
+
+            //
+            // _manageSubMenu
+            //
+            _manageSubMenu = new Menu();
+
+            //
+            // _manageCacheMenuItem
+            //
+            _manageCacheMenuItem = new MenuItem("Cache Management")
+            {
+                Submenu = _manageSubMenu
+            };
+
+            //
+            // _purgePtcCacheMenuItem
+            //
+            _purgePtcCacheMenuItem = new MenuItem("Purge PPTC Cache")
+            {
+                TooltipText = "Delete the Application's PPTC cache."
+            };
+            _purgePtcCacheMenuItem.Activated += PurgePtcCache_Clicked;
+
+            //
+            // _purgeShaderCacheMenuItem
+            //
+            _purgeShaderCacheMenuItem = new MenuItem("Purge Shader Cache")
+            {
+                TooltipText = "Delete the Application's shader cache."
+            };
+            _purgeShaderCacheMenuItem.Activated += PurgeShaderCache_Clicked;
+
+            //
+            // _openPtcDirMenuItem
+            //
+            _openPtcDirMenuItem = new MenuItem("Open PPTC Directory")
+            {
+                TooltipText = "Open the directory which contains the Application's PPTC cache."
+            };
+            _openPtcDirMenuItem.Activated += OpenPtcDir_Clicked;
+
+            //
+            // _openShaderCacheDirMenuItem
+            //
+            _openShaderCacheDirMenuItem = new MenuItem("Open Shader Cache Directory")
+            {
+                TooltipText = "Open the directory which contains the Application's shader cache."
+            };
+            _openShaderCacheDirMenuItem.Activated += OpenShaderCacheDir_Clicked;
+
+            ShowComponent();
+        }
+
+        private void ShowComponent()
+        {
+            _extractSubMenu.Append(_extractExeFsMenuItem);
+            _extractSubMenu.Append(_extractRomFsMenuItem);
+            _extractSubMenu.Append(_extractLogoMenuItem);
+
+            _manageSubMenu.Append(_purgePtcCacheMenuItem);
+            _manageSubMenu.Append(_purgeShaderCacheMenuItem);
+            _manageSubMenu.Append(_openPtcDirMenuItem);
+            _manageSubMenu.Append(_openShaderCacheDirMenuItem);
+
+            Add(_openSaveUserDirMenuItem);
+            Add(_openSaveDeviceDirMenuItem);
+            Add(_openSaveBcatDirMenuItem);
+            Add(new SeparatorMenuItem());
+            Add(_manageTitleUpdatesMenuItem);
+            Add(_manageDlcMenuItem);
+            Add(_openTitleModDirMenuItem);
+            Add(new SeparatorMenuItem());
+            Add(_manageCacheMenuItem);
+            Add(_extractMenuItem);
+
+            ShowAll();
+        }
+    }
+}

+ 108 - 279
Ryujinx/Ui/GameTableContextMenu.cs → Ryujinx/Ui/Widgets/GameTableContextMenu.cs

@@ -11,159 +11,66 @@ using LibHac.Ncm;
 using LibHac.Ns;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
-using Ryujinx.Common.Utilities;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
+using Ryujinx.Ui.Helper;
+using Ryujinx.Ui.Windows;
 using System;
 using System.Buffers;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Globalization;
 using System.IO;
-using System.Reflection;
 using System.Threading;
 
 using static LibHac.Fs.ApplicationSaveDataManagement;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Widgets
 {
-    public class GameTableContextMenu : Menu
+    public partial class GameTableContextMenu : Menu
     {
-        private readonly ListStore         _gameTableStore;
-        private readonly TreeIter          _rowIter;
-        private readonly VirtualFileSystem _virtualFileSystem;
-
+        private readonly MainWindow                             _parent;
+        private readonly VirtualFileSystem                      _virtualFileSystem;
         private readonly BlitStruct<ApplicationControlProperty> _controlData;
 
+        private readonly string _titleFilePath;
+        private readonly string _titleName;
+        private readonly string _titleIdText;
+        private readonly ulong  _titleId;
+
         private MessageDialog _dialog;
         private bool          _cancel;
 
-        public GameTableContextMenu(ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem)
+        public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
         {
-            _gameTableStore    = gameTableStore;
-            _rowIter           = rowIter;
-            _virtualFileSystem = virtualFileSystem;
-            _controlData       = controlData;
-
-            MenuItem openSaveUserDir = new MenuItem("Open User Save Directory")
-            {
-                Sensitive   = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0,
-                TooltipText = "Open the directory which contains Application's User Saves."
-            };
-
-            MenuItem openSaveDeviceDir = new MenuItem("Open Device Save Directory")
-            {
-                Sensitive   = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0,
-                TooltipText = "Open the directory which contains Application's Device Saves."
-            };
-
-            MenuItem openSaveBcatDir = new MenuItem("Open BCAT Save Directory")
-            {
-                Sensitive   = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0,
-                TooltipText = "Open the directory which contains Application's BCAT Saves."
-            };
-
-            MenuItem manageTitleUpdates = new MenuItem("Manage Title Updates")
-            {
-                TooltipText = "Open the Title Update management window"
-            };
+            _parent = parent;
 
-            MenuItem manageDlc = new MenuItem("Manage DLC")
-            {
-                TooltipText = "Open the DLC management window"
-            };
-
-            MenuItem openTitleModDir = new MenuItem("Open Mods Directory")
-            {
-                TooltipText = "Open the directory which contains Application's Mods."
-            };
-
-            string ext    = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower();
-            bool   hasNca = ext == ".nca" || ext == ".nsp" || ext == ".pfs0" || ext == ".xci";
-
-            MenuItem extractMenu = new MenuItem("Extract Data");
-
-            MenuItem extractRomFs = new MenuItem("RomFS")
-            {
-                Sensitive   = hasNca,
-                TooltipText = "Extract the RomFS section from Application's current config (including updates)."
-            };
+            InitializeComponent();
 
-            MenuItem extractExeFs = new MenuItem("ExeFS")
-            {
-                Sensitive   = hasNca,
-                TooltipText = "Extract the ExeFS section from Application's current config (including updates)."
-            };
+            _virtualFileSystem = virtualFileSystem;
+            _titleFilePath     = titleFilePath;
+            _titleName         = titleName;
+            _titleIdText       = titleId;
+            _controlData       = controlData;
 
-            MenuItem extractLogo = new MenuItem("Logo")
+            if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId))
             {
-                Sensitive   = hasNca,
-                TooltipText = "Extract the Logo section from Application's current config (including updates)."
-            };
-
-            Menu extractSubMenu = new Menu();
-            
-            extractSubMenu.Append(extractExeFs);
-            extractSubMenu.Append(extractRomFs);
-            extractSubMenu.Append(extractLogo);
+                GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id");
 
-            extractMenu.Submenu = extractSubMenu;
-
-            MenuItem managePtcMenu = new MenuItem("Cache Management");
-
-            MenuItem purgePtcCache = new MenuItem("Purge PPTC Cache")
-            {
-                TooltipText = "Delete the Application's PPTC cache."
-            };
+                return;
+            }
 
-            MenuItem purgeShaderCache = new MenuItem("Purge Shader Cache")
-            {
-                TooltipText = "Delete the Application's shader cache."
-            };
+            _openSaveUserDirMenuItem.Sensitive   = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize      > 0;
+            _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize           > 0;
+            _openSaveBcatDirMenuItem.Sensitive   = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
 
-            MenuItem openPtcDir = new MenuItem("Open PPTC Directory")
-            {
-                TooltipText = "Open the directory which contains the Application's PPTC cache."
-            };
+            string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
+            bool   hasNca  = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
 
-            MenuItem openShaderCacheDir = new MenuItem("Open Shader Cache Directory")
-            {
-                TooltipText = "Open the directory which contains the Application's shader cache."
-            };
+            _extractRomFsMenuItem.Sensitive = hasNca;
+            _extractExeFsMenuItem.Sensitive = hasNca;
+            _extractLogoMenuItem.Sensitive  = hasNca;
 
-            Menu manageSubMenu = new Menu();
-            
-            manageSubMenu.Append(purgePtcCache);
-            manageSubMenu.Append(purgeShaderCache);
-            manageSubMenu.Append(openPtcDir);
-            manageSubMenu.Append(openShaderCacheDir);
-            
-            managePtcMenu.Submenu = manageSubMenu;
-
-            openSaveUserDir.Activated    += OpenSaveUserDir_Clicked;
-            openSaveDeviceDir.Activated  += OpenSaveDeviceDir_Clicked;
-            openSaveBcatDir.Activated    += OpenSaveBcatDir_Clicked;
-            manageTitleUpdates.Activated += ManageTitleUpdates_Clicked;
-            manageDlc.Activated          += ManageDlc_Clicked;
-            openTitleModDir.Activated    += OpenTitleModDir_Clicked;
-            extractRomFs.Activated       += ExtractRomFs_Clicked;
-            extractExeFs.Activated       += ExtractExeFs_Clicked;
-            extractLogo.Activated        += ExtractLogo_Clicked;
-            purgePtcCache.Activated      += PurgePtcCache_Clicked;
-            purgeShaderCache.Activated   += PurgeShaderCache_Clicked;
-            openPtcDir.Activated         += OpenPtcDir_Clicked;
-            openShaderCacheDir.Activated += OpenShaderCacheDir_Clicked;
-
-            this.Add(openSaveUserDir);
-            this.Add(openSaveDeviceDir);
-            this.Add(openSaveBcatDir);
-            this.Add(new SeparatorMenuItem());
-            this.Add(manageTitleUpdates);
-            this.Add(manageDlc);
-            this.Add(openTitleModDir);
-            this.Add(new SeparatorMenuItem());
-            this.Add(managePtcMenu);
-            this.Add(extractMenu);
+            PopupAtPointer(null);
         }
 
         private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId)
@@ -178,7 +85,6 @@ namespace Ryujinx.Ui
                 using MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null)
                 {
                     Title          = "Ryujinx",
-                    Icon           = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
                     Text           = $"There is no savedata for {titleName} [{titleId:x16}]",
                     SecondaryText  = "Would you like to create savedata for this game?",
                     WindowPosition = WindowPosition.Center
@@ -191,7 +97,7 @@ namespace Ryujinx.Ui
 
                 ref ApplicationControlProperty control = ref controlHolder.Value;
 
-                if (LibHac.Utilities.IsEmpty(controlHolder.ByteSpan))
+                if (Utilities.IsEmpty(controlHolder.ByteSpan))
                 {
                     // If the current application doesn't have a loaded control property, create a dummy one
                     // and set the savedata sizes so a user savedata will be created.
@@ -201,11 +107,10 @@ namespace Ryujinx.Ui
                     control.UserAccountSaveDataSize        = 0x4000;
                     control.UserAccountSaveDataJournalSize = 0x4000;
 
-                    Logger.Warning?.Print(LogClass.Application,
-                        "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
+                    Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
                 }
 
-                Uid user = new Uid(1, 0);
+                Uid user = new Uid(1, 0); // TODO: Remove Hardcoded value.
 
                 result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
 
@@ -232,8 +137,15 @@ namespace Ryujinx.Ui
             return false;
         }
 
-        private string GetSaveDataDirectory(ulong saveDataId)
+        private void OpenSaveDir(SaveDataFilter saveDataFilter)
         {
+            saveDataFilter.SetProgramId(new ProgramId(_titleId));
+
+            if (!TryFindSaveData(_titleName, _titleId, _controlData, saveDataFilter, out ulong saveDataId))
+            {
+                return;
+            }
+
             string saveRootPath = System.IO.Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
 
             if (!Directory.Exists(saveRootPath))
@@ -248,17 +160,19 @@ namespace Ryujinx.Ui
             // If the committed directory exists, that path will be loaded the next time the savedata is mounted
             if (Directory.Exists(committedPath))
             {
-                return committedPath;
+                OpenHelper.OpenFolder(committedPath);
             }
-
-            // If the working directory exists and the committed directory doesn't,
-            // the working directory will be loaded the next time the savedata is mounted
-            if (!Directory.Exists(workingPath))
+            else
             {
-                Directory.CreateDirectory(workingPath);
-            }
+                // If the working directory exists and the committed directory doesn't,
+                // the working directory will be loaded the next time the savedata is mounted
+                if (!Directory.Exists(workingPath))
+                {
+                    Directory.CreateDirectory(workingPath);
+                }
 
-            return workingPath;
+                OpenHelper.OpenFolder(workingPath);
+            }
         }
 
         private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0)
@@ -266,24 +180,21 @@ namespace Ryujinx.Ui
             FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to extract into", null, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Extract", ResponseType.Accept);
             fileChooser.SetPosition(WindowPosition.Center);
 
-            int    response    = fileChooser.Run();
-            string destination = fileChooser.Filename;
+            ResponseType response    = (ResponseType)fileChooser.Run();
+            string       destination = fileChooser.Filename;
             
             fileChooser.Dispose();
 
-            if (response == (int)ResponseType.Accept)
+            if (response == ResponseType.Accept)
             {
                 Thread extractorThread = new Thread(() =>
                 {
-                    string sourceFile = _gameTableStore.GetValue(_rowIter, 9).ToString();
-
                     Gtk.Application.Invoke(delegate
                     {
                         _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null)
                         {
                             Title          = "Ryujinx - NCA Section Extractor",
-                            Icon           = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
-                            SecondaryText  = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(sourceFile)}...",
+                            SecondaryText  = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
                             WindowPosition = WindowPosition.Center
                         };
 
@@ -295,18 +206,18 @@ namespace Ryujinx.Ui
                         }
                     });
 
-                    using (FileStream file = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
+                    using (FileStream file = new FileStream(_titleFilePath, FileMode.Open, FileAccess.Read))
                     {
                         Nca mainNca  = null;
                         Nca patchNca = null;
 
-                        if ((System.IO.Path.GetExtension(sourceFile).ToLower() == ".nsp")  ||
-                            (System.IO.Path.GetExtension(sourceFile).ToLower() == ".pfs0") ||
-                            (System.IO.Path.GetExtension(sourceFile).ToLower() == ".xci"))
+                        if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp")  ||
+                            (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
+                            (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
                         {
                             PartitionFileSystem pfs;
 
-                            if (System.IO.Path.GetExtension(sourceFile) == ".xci")
+                            if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
                             {
                                 Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
 
@@ -338,7 +249,7 @@ namespace Ryujinx.Ui
                                 }
                             }
                         }
-                        else if (System.IO.Path.GetExtension(sourceFile).ToLower() == ".nca")
+                        else if (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca")
                         {
                             mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
                         }
@@ -355,7 +266,6 @@ namespace Ryujinx.Ui
                             return;
                         }
 
-
                         (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
 
                         if (updatePatchNca != null)
@@ -370,8 +280,8 @@ namespace Ryujinx.Ui
 
                         FileSystemClient fsClient = _virtualFileSystem.FsClient;
 
-                        string source = DateTime.Now.ToFileTime().ToString().Substring(10);
-                        string output = DateTime.Now.ToFileTime().ToString().Substring(10);
+                        string source = DateTime.Now.ToFileTime().ToString()[10..];
+                        string output = DateTime.Now.ToFileTime().ToString()[10..];
 
                         fsClient.Register(source.ToU8Span(), ncaFileSystem);
                         fsClient.Register(output.ToU8Span(), new LocalFileSystem(destination));
@@ -400,7 +310,6 @@ namespace Ryujinx.Ui
                                     MessageDialog dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null)
                                     {
                                         Title          = "Ryujinx - NCA Section Extractor",
-                                        Icon           = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
                                         SecondaryText  = "Extraction has completed successfully.",
                                         WindowPosition = WindowPosition.Center
                                     };
@@ -510,111 +419,49 @@ namespace Ryujinx.Ui
             return Result.Success;
         }
 
+        //
         // Events
+        //
         private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
         {
-            string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
-            string titleId   = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
-
-            if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
-            {
-                GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
-
-                return;
-            }
-
-            SaveDataFilter filter = new SaveDataFilter();
-            filter.SetUserId(new UserId(1, 0));
-
-            OpenSaveDir(titleName, titleIdNumber, filter);
-        }
-
-        private void OpenSaveDir(string titleName, ulong titleId, SaveDataFilter filter)
-        {
-            filter.SetProgramId(new ProgramId(titleId));
-
-            if (!TryFindSaveData(titleName, titleId, _controlData, filter, out ulong saveDataId))
-            {
-                return;
-            }
-
-            string saveDir = GetSaveDataDirectory(saveDataId);
+            SaveDataFilter saveDataFilter = new SaveDataFilter();
+            saveDataFilter.SetUserId(new UserId(1, 0)); // TODO: Remove Hardcoded value.
 
-            Process.Start(new ProcessStartInfo
-            {
-                FileName        = saveDir,
-                UseShellExecute = true,
-                Verb            = "open"
-            });
+            OpenSaveDir(saveDataFilter);
         }
 
         private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
         {
-            string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
-            string titleId   = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
-
-            if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
-            {
-                GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
-
-                return;
-            }
-
-            SaveDataFilter filter = new SaveDataFilter();
-            filter.SetSaveDataType(SaveDataType.Device);
+            SaveDataFilter saveDataFilter = new SaveDataFilter();
+            saveDataFilter.SetSaveDataType(SaveDataType.Device);
 
-            OpenSaveDir(titleName, titleIdNumber, filter);
+            OpenSaveDir(saveDataFilter);
         }
 
         private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
         {
-            string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
-            string titleId   = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
+            SaveDataFilter saveDataFilter = new SaveDataFilter();
+            saveDataFilter.SetSaveDataType(SaveDataType.Bcat);
 
-            if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
-            {
-                GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
-
-                return;
-            }
-
-            SaveDataFilter filter = new SaveDataFilter();
-            filter.SetSaveDataType(SaveDataType.Bcat);
-
-            OpenSaveDir(titleName, titleIdNumber, filter);
+            OpenSaveDir(saveDataFilter);
         }
 
         private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
         {
-            string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
-            string titleId   = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
-
-            TitleUpdateWindow titleUpdateWindow = new TitleUpdateWindow(titleId, titleName, _virtualFileSystem);
-            titleUpdateWindow.Show();
+            new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show();
         }
 
         private void ManageDlc_Clicked(object sender, EventArgs args)
         {
-            string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
-            string titleId   = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
-
-            DlcWindow dlcWindow = new DlcWindow(titleId, titleName, _virtualFileSystem);
-            dlcWindow.Show();
+            new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
         }
 
         private void OpenTitleModDir_Clicked(object sender, EventArgs args)
         {
-            string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
-
-            var modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath();
-            var titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId);
+            string modsBasePath  = _virtualFileSystem.ModLoader.GetModsBasePath();
+            string titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, _titleIdText);
 
-            Process.Start(new ProcessStartInfo
-            {
-                FileName = titleModsPath,
-                UseShellExecute = true,
-                Verb = "open"
-            });
+            OpenHelper.OpenFolder(titleModsPath);
         }
 
         private void ExtractRomFs_Clicked(object sender, EventArgs args)
@@ -634,8 +481,7 @@ namespace Ryujinx.Ui
 
         private void OpenPtcDir_Clicked(object sender, EventArgs args)
         {
-            string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
-            string ptcDir  = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu");
+            string ptcDir  = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu");
             
             string mainPath   = System.IO.Path.Combine(ptcDir, "0");
             string backupPath = System.IO.Path.Combine(ptcDir, "1");
@@ -646,52 +492,40 @@ namespace Ryujinx.Ui
                 Directory.CreateDirectory(mainPath);
                 Directory.CreateDirectory(backupPath);
             }
-            
-            Process.Start(new ProcessStartInfo
-            {
-                FileName        = ptcDir,
-                UseShellExecute = true,
-                Verb            = "open"
-            });
+
+            OpenHelper.OpenFolder(ptcDir);
         }
 
         private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
         {
-            string titleId        = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
-            string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
+            string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader");
 
             if (!Directory.Exists(shaderCacheDir))
             {
                 Directory.CreateDirectory(shaderCacheDir);
             }
 
-            Process.Start(new ProcessStartInfo
-            {
-                FileName        = shaderCacheDir,
-                UseShellExecute = true,
-                Verb            = "open"
-            });
+            OpenHelper.OpenFolder(shaderCacheDir);
         }
         
         private void PurgePtcCache_Clicked(object sender, EventArgs args)
         {
-            string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n");
-            string titleId = tableEntry[1].ToLower();
-            
-            DirectoryInfo mainDir   = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu", "0"));
-            DirectoryInfo backupDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu", "1"));
-            
-            MessageDialog warningDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
-            {
-                Title          = "Ryujinx - Warning",
-                Text           = $"You are about to delete the PPTC cache for '{tableEntry[0]}'. Are you sure you want to proceed?",
-                WindowPosition = WindowPosition.Center
-            };
+            DirectoryInfo mainDir   = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
+            DirectoryInfo backupDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
+
+            MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the PPTC cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
 
             List<FileInfo> cacheFiles = new List<FileInfo>();
 
-            if (mainDir.Exists)   { cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); }
-            if (backupDir.Exists) { cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); }
+            if (mainDir.Exists)
+            { 
+                cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
+            }
+
+            if (backupDir.Exists)
+            {
+                cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
+            }
 
             if (cacheFiles.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes)
             {
@@ -703,7 +537,7 @@ namespace Ryujinx.Ui
                     }
                     catch(Exception e)
                     {
-                        Logger.Error?.Print(LogClass.Application, $"Error purging PPTC cache {file.Name}: {e}");
+                        GtkDialog.CreateErrorDialog($"Error purging PPTC cache {file.Name}: {e}");
                     }
                 }
             }
@@ -713,21 +547,16 @@ namespace Ryujinx.Ui
 
         private void PurgeShaderCache_Clicked(object sender, EventArgs args)
         {
-            string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n");
-            string titleId = tableEntry[1].ToLower();
-
-            DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader"));
+            DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
 
-            MessageDialog warningDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
-            {
-                Title          = "Ryujinx - Warning",
-                Text           = $"You are about to delete the shader cache for '{tableEntry[0]}'. Are you sure you want to proceed?",
-                WindowPosition = WindowPosition.Center
-            };
+            MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
 
             List<DirectoryInfo> cacheDirectory = new List<DirectoryInfo>();
 
-            if (shaderCacheDir.Exists) { cacheDirectory.AddRange(shaderCacheDir.EnumerateDirectories("*")); }
+            if (shaderCacheDir.Exists)
+            {
+                cacheDirectory.AddRange(shaderCacheDir.EnumerateDirectories("*"));
+            }
 
             if (cacheDirectory.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes)
             {
@@ -739,7 +568,7 @@ namespace Ryujinx.Ui
                     }
                     catch (Exception e)
                     {
-                        Logger.Error?.Print(LogClass.Application, $"Error purging shader cache {directory.Name}: {e}");
+                        GtkDialog.CreateErrorDialog($"Error purging shader cache {directory.Name}: {e}");
                     }
                 }
             }
@@ -747,4 +576,4 @@ namespace Ryujinx.Ui
             warningDialog.Dispose();
         }
     }
-}
+}

+ 29 - 12
Ryujinx/Ui/GtkDialog.cs → Ryujinx/Ui/Widgets/GtkDialog.cs

@@ -1,7 +1,7 @@
 using Gtk;
-using System.Reflection;
+using Ryujinx.Common.Logging;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Widgets
 {
     internal class GtkDialog : MessageDialog
     {
@@ -10,14 +10,15 @@ namespace Ryujinx.Ui
         private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok) 
             : base(null, DialogFlags.Modal, messageType, buttonsType, null)
         {
-            Title          = title;
-            Icon           = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
-            Text           = mainText;
-            SecondaryText  = secondaryText;
-            WindowPosition = WindowPosition.Center;
-            Response      += GtkDialog_Response;
-
-            SetSizeRequest(100, 20);
+            Title              = title;
+            Text               = mainText;
+            SecondaryText      = secondaryText;
+            WindowPosition     = WindowPosition.Center;
+            SecondaryUseMarkup = true;
+
+            Response += GtkDialog_Response;
+
+            SetSizeRequest(200, 20);
         }
 
         private void GtkDialog_Response(object sender, ResponseArgs args)
@@ -25,9 +26,19 @@ namespace Ryujinx.Ui
             Dispose();
         }
 
-        internal static void CreateInfoDialog(string title, string mainText, string secondaryText)
+        internal static void CreateInfoDialog(string mainText, string secondaryText)
+        {
+            new GtkDialog("Ryujinx - Info", mainText, secondaryText, MessageType.Info).Run();
+        }
+
+        internal static void CreateUpdaterInfoDialog(string mainText, string secondaryText)
         {
-            new GtkDialog(title, mainText, secondaryText, MessageType.Info).Run();
+            new GtkDialog("Ryujinx - Updater", mainText, secondaryText, MessageType.Info).Run();
+        }
+
+        internal static MessageDialog CreateWaitingDialog(string mainText, string secondaryText)
+        {
+            return new GtkDialog("Ryujinx - Waiting", mainText, secondaryText, MessageType.Info, ButtonsType.None);
         }
 
         internal static void CreateWarningDialog(string mainText, string secondaryText)
@@ -37,6 +48,8 @@ namespace Ryujinx.Ui
 
         internal static void CreateErrorDialog(string errorMessage)
         {
+            Logger.Error?.Print(LogClass.Application, errorMessage);
+
             new GtkDialog("Ryujinx - Error", "Ryujinx has encountered an error", errorMessage, MessageType.Error).Run();
         }
 
@@ -48,10 +61,14 @@ namespace Ryujinx.Ui
         internal static bool CreateChoiceDialog(string title, string mainText, string secondaryText)
         {
             if (_isChoiceDialogOpen)
+            {
                 return false;
+            }
 
             _isChoiceDialogOpen = true;
+
             ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run();
+
             _isChoiceDialogOpen = false;
 
             return response == ResponseType.Yes;

+ 3 - 6
Ryujinx/Ui/ProfileDialog.cs → Ryujinx/Ui/Widgets/ProfileDialog.cs

@@ -1,10 +1,9 @@
 using Gtk;
 using System;
-using System.Reflection;
 
 using GUI = Gtk.Builder.ObjectAttribute;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Widgets
 {
     public class ProfileDialog : Dialog
     {
@@ -15,18 +14,16 @@ namespace Ryujinx.Ui
         [GUI] Label _errorMessage;
 #pragma warning restore CS0649, IDE0044
 
-        public ProfileDialog() : this(new Builder("Ryujinx.Ui.ProfileDialog.glade")) { }
+        public ProfileDialog() : this(new Builder("Ryujinx.Ui.Widgets.ProfileDialog.glade")) { }
 
         private ProfileDialog(Builder builder) : base(builder.GetObject("_profileDialog").Handle)
         {
             builder.Autoconnect(this);
-
-            Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
         }
 
         private void OkToggle_Activated(object sender, EventArgs args)
         {
-            ((ToggleButton)sender).SetStateFlags(0, true);
+            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
 
             bool validFileName = true;
 

+ 0 - 0
Ryujinx/Ui/ProfileDialog.glade → Ryujinx/Ui/Widgets/ProfileDialog.glade


+ 2 - 2
Ryujinx/Ui/Diagnostic/UserError.cs → Ryujinx/Ui/Widgets/UserError.cs

@@ -1,4 +1,4 @@
-namespace Ryujinx.Ui.Diagnostic
+namespace Ryujinx.Ui.Widgets
 {
     /// <summary>
     /// Represent a common error that could be reported to the user by the emulator.
@@ -36,4 +36,4 @@
         /// </summary>
         Unknown = 0xDEAD
     }
-}
+}

+ 122 - 0
Ryujinx/Ui/Widgets/UserErrorDialog.cs

@@ -0,0 +1,122 @@
+using Gtk;
+using Ryujinx.Ui.Helper;
+
+namespace Ryujinx.Ui.Widgets
+{
+    internal class UserErrorDialog : MessageDialog
+    {
+        private const string SetupGuideUrl        = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide";
+        private const int    OkResponseId         = 0;
+        private const int    SetupGuideResponseId = 1;
+
+        private readonly UserError _userError;
+
+        private UserErrorDialog(UserError error) : base(null, DialogFlags.Modal, MessageType.Error, ButtonsType.None, null)
+        {
+            _userError = error;
+
+            WindowPosition     = WindowPosition.Center;
+            SecondaryUseMarkup = true;
+
+            Response += UserErrorDialog_Response;
+
+            SetSizeRequest(120, 50);
+
+            AddButton("OK", OkResponseId);
+
+            bool isInSetupGuide = IsCoveredBySetupGuide(error);
+
+            if (isInSetupGuide)
+            {
+                AddButton("Open the Setup Guide", SetupGuideResponseId);
+            }
+
+            string errorCode = GetErrorCode(error);
+
+            SecondaryUseMarkup = true;
+
+            Title         = $"Ryujinx error ({errorCode})";
+            Text          = $"{errorCode}: {GetErrorTitle(error)}";
+            SecondaryText = GetErrorDescription(error);
+
+            if (isInSetupGuide)
+            {
+                SecondaryText += "\n<b>For more information on how to fix this error, follow our Setup Guide.</b>";
+            }
+        }
+
+        private string GetErrorCode(UserError error)
+        {
+            return $"RYU-{(uint)error:X4}";
+        }
+
+        private string GetErrorTitle(UserError error)
+        {
+            return error switch
+            {
+                UserError.NoKeys                => "Keys not found",
+                UserError.NoFirmware            => "Firmware not found",
+                UserError.FirmwareParsingFailed => "Firmware parsing error",
+                UserError.ApplicationNotFound   => "Application not found",
+                UserError.Unknown               => "Unknown error",
+                _                               => "Undefined error",
+            };
+        }
+
+        private string GetErrorDescription(UserError error)
+        {
+            return error switch
+            {
+                UserError.NoKeys                => "Ryujinx was unable to find your 'prod.keys' file",
+                UserError.NoFirmware            => "Ryujinx was unable to find any firmwares installed",
+                UserError.FirmwareParsingFailed => "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.",
+                UserError.ApplicationNotFound   => "Ryujinx couldn't find a valid application at the given path.",
+                UserError.Unknown               => "An unknown error occured!",
+                _                               => "An undefined error occured! This shouldn't happen, please contact a dev!",
+            };
+        }
+
+        private static bool IsCoveredBySetupGuide(UserError error)
+        {
+            return error switch
+            {
+                UserError.NoKeys or
+                UserError.NoFirmware or 
+                UserError.FirmwareParsingFailed => true,
+                _                               => false,
+            };
+        }
+
+        private static string GetSetupGuideUrl(UserError error)
+        {
+            if (!IsCoveredBySetupGuide(error))
+            {
+                return null;
+            }
+
+            return error switch
+            {
+                UserError.NoKeys     => SetupGuideUrl + "#initial-setup---placement-of-prodkeys",
+                UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware",
+                _                    => SetupGuideUrl,
+            };
+        }
+
+        private void UserErrorDialog_Response(object sender, ResponseArgs args)
+        {
+            int responseId = (int)args.ResponseId;
+
+            if (responseId == SetupGuideResponseId)
+            {
+                OpenHelper.OpenUrl(GetSetupGuideUrl(_userError));
+            }
+
+            Dispose();
+        }
+
+        public static void CreateUserErrorDialog(UserError error)
+        {
+            new UserErrorDialog(error).Run();
+        }
+    }
+}

+ 467 - 0
Ryujinx/Ui/Windows/AboutWindow.Designer.cs

@@ -0,0 +1,467 @@
+using Gtk;
+using Pango;
+using System.Reflection;
+
+namespace Ryujinx.Ui.Windows
+{
+    public partial class AboutWindow : Window
+    {
+        private Box            _mainBox;
+        private Box            _leftBox;
+        private Box            _logoBox;
+        private Image          _ryujinxLogo;
+        private Box            _logoTextBox;
+        private Label          _ryujinxLabel;
+        private Label          _ryujinxPhoneticLabel;
+        private EventBox       _ryujinxLink;
+        private Label          _ryujinxLinkLabel;
+        private Label          _versionLabel;
+        private Label          _disclaimerLabel;
+        private Box            _socialBox;
+        private EventBox       _patreonEventBox;
+        private Box            _patreonBox;
+        private Image          _patreonLogo;
+        private Label          _patreonLabel;
+        private EventBox       _githubEventBox;
+        private Box            _githubBox;
+        private Image          _githubLogo;
+        private Label          _githubLabel;
+        private Box            _discordBox;
+        private EventBox       _discordEventBox;
+        private Image          _discordLogo;
+        private Label          _discordLabel;
+        private EventBox       _twitterEventBox;
+        private Box            _twitterBox;
+        private Image          _twitterLogo;
+        private Label          _twitterLabel;
+        private Separator      _separator;
+        private Box            _rightBox;
+        private Label          _aboutLabel;
+        private Label          _aboutDescriptionLabel;
+        private Label          _createdByLabel;
+        private TextView       _createdByText;
+        private EventBox       _contributorsEventBox;
+        private Label          _contributorsLinkLabel;
+        private Label          _patreonNamesLabel;
+        private ScrolledWindow _patreonNamesScrolled;
+        private TextView       _patreonNamesText;
+
+        private void InitializeComponent()
+        {
+
+#pragma warning disable CS0612
+
+            //
+            // AboutWindow
+            //
+            CanFocus       = false;
+            Resizable      = false;
+            Modal          = true;
+            WindowPosition = WindowPosition.Center;
+            DefaultWidth   = 800;
+            DefaultHeight  = 450;
+            TypeHint       = Gdk.WindowTypeHint.Dialog;
+
+            //
+            // _mainBox
+            //
+            _mainBox = new Box(Orientation.Horizontal, 0);
+
+            //
+            // _leftBox
+            //
+            _leftBox = new Box(Orientation.Vertical, 0)
+            {
+                Margin      = 15,
+                MarginLeft  = 30,
+                MarginRight = 0
+            };
+
+            //
+            // _logoBox
+            //
+            _logoBox = new Box(Orientation.Horizontal, 0);
+
+            //
+            // _ryujinxLogo
+            //
+            _ryujinxLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Ryujinx.png", 100, 100))
+            {
+                Margin     = 10,
+                MarginLeft = 15
+            };
+
+            //
+            // _logoTextBox
+            //
+            _logoTextBox = new Box(Orientation.Vertical, 0);
+
+            //
+            // _ryujinxLabel
+            //
+            _ryujinxLabel = new Label("Ryujinx")
+            {
+                MarginTop  = 15,
+                Justify    = Justification.Center,
+                Attributes = new AttrList()
+            };
+            _ryujinxLabel.Attributes.Insert(new Pango.AttrScale(2.7f));
+
+            //
+            // _ryujinxPhoneticLabel
+            //
+            _ryujinxPhoneticLabel = new Label("(REE-YOU-JI-NX)")
+            {
+                Justify = Justification.Center
+            };
+
+            //
+            // _ryujinxLink
+            //
+            _ryujinxLink = new EventBox()
+            {
+                Margin = 5
+            };
+            _ryujinxLink.ButtonPressEvent += RyujinxButton_Pressed;
+
+            //
+            // _ryujinxLinkLabel
+            //
+            _ryujinxLinkLabel = new Label("www.ryujinx.org")
+            {
+                TooltipText = "Click to open the Ryujinx website in your default browser.",
+                Justify     = Justification.Center,
+                Attributes  = new AttrList()
+            };
+            _ryujinxLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
+
+            //
+            // _versionLabel
+            //
+            _versionLabel = new Label(Program.Version)
+            {
+                Expand  = true,
+                Justify = Justification.Center,
+                Margin  = 5
+            };
+
+            //
+            // _disclaimerLabel
+            //
+            _disclaimerLabel = new Label("Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.")
+            {
+                Expand     = true,
+                Justify    = Justification.Center,
+                Margin     = 5,
+                Attributes = new AttrList()
+            };
+            _disclaimerLabel.Attributes.Insert(new Pango.AttrScale(0.8f));
+
+            //
+            // _socialBox
+            //
+            _socialBox = new Box(Orientation.Horizontal, 0)
+            {
+                Margin       = 25,
+                MarginBottom = 10
+            };
+
+            //
+            // _patreonEventBox
+            //
+            _patreonEventBox = new EventBox()
+            {
+                TooltipText = "Click to open the Ryujinx Patreon page in your default browser."
+            };
+            _patreonEventBox.ButtonPressEvent += PatreonButton_Pressed;
+
+            //
+            // _patreonBox
+            //
+            _patreonBox = new Box(Orientation.Vertical, 0);
+
+            //
+            // _patreonLogo
+            //
+            _patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Patreon.png", 30, 30))
+            {
+                Margin = 10
+            };
+
+            //
+            // _patreonLabel
+            //
+            _patreonLabel = new Label("Patreon")
+            {
+                Justify = Justification.Center
+            };
+
+            //
+            // _githubEventBox
+            //
+            _githubEventBox = new EventBox()
+            {
+                TooltipText = "Click to open the Ryujinx GitHub page in your default browser."
+            };
+            _githubEventBox.ButtonPressEvent += GitHubButton_Pressed;
+
+            //
+            // _githubBox
+            //
+            _githubBox = new Box(Orientation.Vertical, 0);
+
+            //
+            // _githubLogo
+            //
+            _githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_GitHub.png", 30, 30))
+            {
+                Margin = 10
+            };
+
+            //
+            // _githubLabel
+            //
+            _githubLabel = new Label("GitHub")
+            {
+                Justify = Justification.Center
+            };
+
+            //
+            // _discordBox
+            //
+            _discordBox = new Box(Orientation.Vertical, 0);
+
+            //
+            // _discordEventBox
+            //
+            _discordEventBox = new EventBox()
+            {
+                TooltipText = "Click to open an invite to the Ryujinx Discord server in your default browser."
+            };
+            _discordEventBox.ButtonPressEvent += DiscordButton_Pressed;
+
+            //
+            // _discordLogo
+            //
+            _discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Discord.png", 30, 30))
+            {
+                Margin = 10
+            };
+
+            //
+            // _discordLabel
+            //
+            _discordLabel = new Label("Discord")
+            {
+                Justify = Justification.Center
+            };
+
+            //
+            // _twitterEventBox
+            //
+            _twitterEventBox = new EventBox()
+            {
+                TooltipText = "Click to open the Ryujinx Twitter page in your default browser."
+            };
+            _twitterEventBox.ButtonPressEvent += TwitterButton_Pressed;
+
+            //
+            // _twitterBox
+            //
+            _twitterBox = new Box(Orientation.Vertical, 0);
+
+            //
+            // _twitterLogo
+            //
+            _twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Twitter.png", 30, 30))
+            {
+                Margin = 10
+            };
+
+            //
+            // _twitterLabel
+            //
+            _twitterLabel = new Label("Twitter")
+            {
+                Justify = Justification.Center
+            };
+
+            //
+            // _separator
+            //
+            _separator = new Separator(Orientation.Vertical)
+            {
+                Margin = 15
+            };
+
+            //
+            // _rightBox
+            //
+            _rightBox = new Box(Orientation.Vertical, 0)
+            {
+                Margin    = 15,
+                MarginTop = 40
+            };
+
+            //
+            // _aboutLabel
+            //
+            _aboutLabel = new Label("About :")
+            {
+                Halign     = Align.Start,
+                Attributes = new AttrList()
+            };
+            _aboutLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
+            _aboutLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
+
+            //
+            // _aboutDescriptionLabel
+            //
+            _aboutDescriptionLabel = new Label("Ryujinx is an emulator for the Nintendo Switch™.\n" +
+                                               "Please support us on Patreon.\n" +
+                                               "Get all the latest news on our Twitter or Discord.\n" +
+                                               "Developers interested in contributing can find out more on our GitHub or Discord.")
+            {
+                Margin = 15,
+                Halign = Align.Start
+            };
+
+            //
+            // _createdByLabel
+            //
+            _createdByLabel = new Label("Maintained by :")
+            {
+                Halign     = Align.Start,
+                Attributes = new AttrList()
+            };
+            _createdByLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
+            _createdByLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
+
+            //
+            // _createdByText
+            //
+            _createdByText = new TextView()
+            {
+                WrapMode      = Gtk.WrapMode.Word,
+                Editable      = false,
+                CursorVisible = false,
+                Margin        = 15,
+                MarginRight   = 30
+            };
+            _createdByText.Buffer.Text = "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD« and more...";
+
+            //
+            // _contributorsEventBox
+            //
+            _contributorsEventBox = new EventBox();
+            _contributorsEventBox.ButtonPressEvent += ContributorsButton_Pressed;
+
+            //
+            // _contributorsLinkLabel
+            //
+            _contributorsLinkLabel = new Label("See All Contributors...")
+            {
+                TooltipText = "Click to open the Contributors page in your default browser.",
+                MarginRight = 30,
+                Halign      = Align.End,
+                Attributes  = new AttrList()
+            };
+            _contributorsLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
+
+            //
+            // _patreonNamesLabel
+            //
+            _patreonNamesLabel = new Label("Supported on Patreon by :")
+            {
+                Halign     = Align.Start,
+                Attributes = new AttrList()
+            };
+            _patreonNamesLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
+            _patreonNamesLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
+
+            //
+            // _patreonNamesScrolled
+            //
+            _patreonNamesScrolled = new ScrolledWindow()
+            {
+                Margin      = 15,
+                MarginRight = 30,
+                Expand      = true,
+                ShadowType  = ShadowType.In
+            };
+            _patreonNamesScrolled.SetPolicy(PolicyType.Never, PolicyType.Automatic);
+
+            //
+            // _patreonNamesText
+            //
+            _patreonNamesText = new TextView()
+            {
+                WrapMode = Gtk.WrapMode.Word
+            };
+            _patreonNamesText.Buffer.Text = "Loading...";
+            _patreonNamesText.SetProperty("editable", new GLib.Value(false));
+
+#pragma warning restore CS0612
+
+            ShowComponent();
+        }
+
+        private void ShowComponent()
+        {
+            _logoBox.Add(_ryujinxLogo);
+
+            _ryujinxLink.Add(_ryujinxLinkLabel);
+
+            _logoTextBox.Add(_ryujinxLabel);
+            _logoTextBox.Add(_ryujinxPhoneticLabel);
+            _logoTextBox.Add(_ryujinxLink);
+
+            _logoBox.Add(_logoTextBox);
+
+            _patreonBox.Add(_patreonLogo);
+            _patreonBox.Add(_patreonLabel);
+            _patreonEventBox.Add(_patreonBox);
+
+            _githubBox.Add(_githubLogo);
+            _githubBox.Add(_githubLabel);
+            _githubEventBox.Add(_githubBox);
+
+            _discordBox.Add(_discordLogo);
+            _discordBox.Add(_discordLabel);
+            _discordEventBox.Add(_discordBox);
+
+            _twitterBox.Add(_twitterLogo);
+            _twitterBox.Add(_twitterLabel);
+            _twitterEventBox.Add(_twitterBox);
+
+            _socialBox.Add(_patreonEventBox);
+            _socialBox.Add(_githubEventBox);
+            _socialBox.Add(_discordEventBox);
+            _socialBox.Add(_twitterEventBox);
+
+            _leftBox.Add(_logoBox);
+            _leftBox.Add(_versionLabel);
+            _leftBox.Add(_disclaimerLabel);
+            _leftBox.Add(_socialBox);
+
+            _contributorsEventBox.Add(_contributorsLinkLabel);
+            _patreonNamesScrolled.Add(_patreonNamesText);
+
+            _rightBox.Add(_aboutLabel);
+            _rightBox.Add(_aboutDescriptionLabel);
+            _rightBox.Add(_createdByLabel);
+            _rightBox.Add(_createdByText);
+            _rightBox.Add(_contributorsEventBox);
+            _rightBox.Add(_patreonNamesLabel);
+            _rightBox.Add(_patreonNamesScrolled);
+
+            _mainBox.Add(_leftBox);
+            _mainBox.Add(_separator);
+            _mainBox.Add(_rightBox);
+
+            Add(_mainBox);
+
+            ShowAll();
+        }
+    }
+}

+ 73 - 0
Ryujinx/Ui/Windows/AboutWindow.cs

@@ -0,0 +1,73 @@
+using Gtk;
+using Ryujinx.Common.Utilities;
+using Ryujinx.Ui.Helper;
+using System.Net.Http;
+using System.Net.NetworkInformation;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ui.Windows
+{
+    public partial class AboutWindow : Window
+    {
+        public AboutWindow() : base($"Ryujinx {Program.Version} - About")
+        {
+            InitializeComponent();
+
+            _ = DownloadPatronsJson();
+        }
+
+        private async Task DownloadPatronsJson()
+        {
+            if (!NetworkInterface.GetIsNetworkAvailable())
+            {
+                _patreonNamesText.Buffer.Text = "Connection Error.";
+            }
+
+            HttpClient httpClient = new HttpClient();
+
+            try
+            {
+                string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
+
+                _patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString));
+            }
+            catch
+            {
+                _patreonNamesText.Buffer.Text = "API Error.";
+            }
+        }
+
+        //
+        // Events
+        //
+        private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args)
+        {
+            OpenHelper.OpenUrl("https://ryujinx.org");
+        }
+
+        private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args)
+        {
+            OpenHelper.OpenUrl("https://www.patreon.com/ryujinx");
+        }
+
+        private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args)
+        {
+            OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx");
+        }
+
+        private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args)
+        {
+            OpenHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc");
+        }
+
+        private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args)
+        {
+            OpenHelper.OpenUrl("https://twitter.com/RyujinxEmu");
+        }
+
+        private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args)
+        {
+            OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
+        }
+    }
+}

+ 37 - 44
Ryujinx/Ui/ControllerWindow.cs → Ryujinx/Ui/Windows/ControllerWindow.cs

@@ -4,7 +4,7 @@ using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Configuration.Hid;
 using Ryujinx.Common.Utilities;
 using Ryujinx.Configuration;
-using Ryujinx.HLE.FileSystem;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -15,14 +15,14 @@ using System.Threading;
 using GUI = Gtk.Builder.ObjectAttribute;
 using Key = Ryujinx.Configuration.Hid.Key;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Windows
 {
     public class ControllerWindow : Window
     {
-        private PlayerIndex       _playerIndex;
-        private InputConfig       _inputConfig;
-        private bool              _isWaitingForInput;
-        private VirtualFileSystem _virtualFileSystem;
+        private readonly PlayerIndex _playerIndex;
+        private readonly InputConfig _inputConfig;
+
+        private bool _isWaitingForInput;
 
 #pragma warning disable CS0649, IDE0044
         [GUI] Adjustment   _controllerDeadzoneLeft;
@@ -90,17 +90,14 @@ namespace Ryujinx.Ui
         [GUI] Image        _controllerImage;
 #pragma warning restore CS0649, IDE0044
 
-        public ControllerWindow(PlayerIndex controllerId, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.ControllerWindow.glade"), controllerId, virtualFileSystem) { }
+        public ControllerWindow(PlayerIndex controllerId) : this(new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { }
 
-        private ControllerWindow(Builder builder, PlayerIndex controllerId, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_controllerWin").Handle)
+        private ControllerWindow(Builder builder, PlayerIndex controllerId) : base(builder.GetObject("_controllerWin").Handle)
         {
             builder.Autoconnect(this);
 
-            this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
-
-            _playerIndex       = controllerId;
-            _virtualFileSystem = virtualFileSystem;
-            _inputConfig       = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex);
+            _playerIndex = controllerId;
+            _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex);
 
             Title = $"Ryujinx - Controller Settings - {_playerIndex}";
 
@@ -119,7 +116,7 @@ namespace Ryujinx.Ui
 
             _controllerType.Active = 0; // Set initial value to first in list.
 
-            //Bind Events
+            // Bind Events.
             _lStickX.Clicked        += Button_Pressed;
             _lStickY.Clicked        += Button_Pressed;
             _lStickUp.Clicked       += Button_Pressed;
@@ -153,12 +150,15 @@ namespace Ryujinx.Ui
             _rSl.Clicked            += Button_Pressed;
             _rSr.Clicked            += Button_Pressed;
 
-            // Setup current values
+            // Setup current values.
             UpdateInputDeviceList();
             SetAvailableOptions();
 
             ClearValues();
-            if (_inputDevice.ActiveId != null) SetCurrentValues();
+            if (_inputDevice.ActiveId != null)
+            {
+                SetCurrentValues();
+            }
         }
 
         private void UpdateInputDeviceList()
@@ -193,7 +193,7 @@ namespace Ryujinx.Ui
         {
             if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard"))
             {
-                this.ShowAll();
+                ShowAll();
                 _leftStickController.Hide();
                 _rightStickController.Hide();
                 _deadZoneLeftBox.Hide();
@@ -202,7 +202,7 @@ namespace Ryujinx.Ui
             }
             else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller"))
             {
-                this.ShowAll();
+                ShowAll();
                 _leftStickKeyboard.Hide();
                 _rightStickKeyboard.Hide();
             }
@@ -249,21 +249,13 @@ namespace Ryujinx.Ui
                     break;
             }
 
-            switch (_controllerType.ActiveId)
+            _controllerImage.Pixbuf = _controllerType.ActiveId switch
             {
-                case "ProController":
-                    _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ProCon.svg", 400, 400);
-                    break;
-                case "JoyconLeft":
-                    _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyConLeft.svg", 400, 400);
-                    break;
-                case "JoyconRight":
-                    _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyConRight.svg", 400, 400);
-                    break;
-                default:
-                    _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyConPair.svg", 400, 400);
-                    break;
-            }
+                "ProController" => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_ProCon.svg", 400, 400),
+                "JoyconLeft"    => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConLeft.svg", 400, 400),
+                "JoyconRight"   => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConRight.svg", 400, 400),
+                _               => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConPair.svg", 400, 400),
+            };
         }
 
         private void ClearValues()
@@ -620,8 +612,7 @@ namespace Ryujinx.Ui
                 if (joystickState.IsButtonDown(i))
                 {
                     Enum.TryParse($"Button{i}", out pressedButton);
-
-                    return true;
+                                        return true;
                 }
             }
 
@@ -674,7 +665,9 @@ namespace Ryujinx.Ui
             return path;
         }
 
-        //Events
+        //
+        // Events
+        //
         private void InputDevice_Changed(object sender, EventArgs args)
         {
             SetAvailableOptions();
@@ -692,7 +685,7 @@ namespace Ryujinx.Ui
         {
             UpdateInputDeviceList();
 
-            _refreshInputDevicesButton.SetStateFlags(0, true);
+            _refreshInputDevicesButton.SetStateFlags(StateFlags.Normal, true);
         }
 
         private void Button_Pressed(object sender, EventArgs args)
@@ -719,7 +712,7 @@ namespace Ryujinx.Ui
                         {
                             Application.Invoke(delegate
                             {
-                                button.SetStateFlags(0, true);
+                                button.SetStateFlags(StateFlags.Normal, true);
                             });
 
                             _isWaitingForInput = false;
@@ -731,7 +724,7 @@ namespace Ryujinx.Ui
                     Application.Invoke(delegate
                     {
                         button.Label = pressedKey.ToString();
-                        button.SetStateFlags(0, true);
+                        button.SetStateFlags(StateFlags.Normal, true);
                     });
                 }
                 else if (_inputDevice.ActiveId.StartsWith("controller"))
@@ -745,7 +738,7 @@ namespace Ryujinx.Ui
                         {
                             Application.Invoke(delegate
                             {
-                                button.SetStateFlags(0, true);
+                                button.SetStateFlags(StateFlags.Normal, true);
                             });
 
                             _isWaitingForInput = false;
@@ -757,7 +750,7 @@ namespace Ryujinx.Ui
                     Application.Invoke(delegate
                     {
                         button.Label = pressedButton.ToString();
-                        button.SetStateFlags(0, true);
+                        button.SetStateFlags(StateFlags.Normal, true);
                     });
                 }
 
@@ -788,7 +781,7 @@ namespace Ryujinx.Ui
 
         private void ProfileLoad_Activated(object sender, EventArgs args)
         {
-            ((ToggleButton)sender).SetStateFlags(0, true);
+            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
 
             if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == null) return;
 
@@ -940,7 +933,7 @@ namespace Ryujinx.Ui
 
         private void ProfileAdd_Activated(object sender, EventArgs args)
         {
-            ((ToggleButton)sender).SetStateFlags(0, true);
+            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
 
             if (_inputDevice.ActiveId == "disabled") return;
 
@@ -973,7 +966,7 @@ namespace Ryujinx.Ui
 
         private void ProfileRemove_Activated(object sender, EventArgs args)
         {
-            ((ToggleButton) sender).SetStateFlags(0, true);
+            ((ToggleButton) sender).SetStateFlags(StateFlags.Normal, true);
 
             if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == "default" || _profile.ActiveId == null) return;
 
@@ -1021,7 +1014,7 @@ namespace Ryujinx.Ui
             // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
             ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
 
-            MainWindow.SaveConfig();
+            ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
 
             Dispose();
         }

+ 0 - 0
Ryujinx/Ui/ControllerWindow.glade → Ryujinx/Ui/Windows/ControllerWindow.glade


+ 7 - 19
Ryujinx/Ui/DlcWindow.cs → Ryujinx/Ui/Windows/DlcWindow.cs

@@ -1,13 +1,12 @@
 using Gtk;
-using LibHac;
 using LibHac.Common;
 using LibHac.Fs;
 using LibHac.Fs.Fsa;
 using LibHac.FsSystem;
 using LibHac.FsSystem.NcaUtils;
 using Ryujinx.Common.Configuration;
-using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -16,7 +15,7 @@ using System.Text;
 using GUI        = Gtk.Builder.ObjectAttribute;
 using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Windows
 {
     public class DlcWindow : Window
     {
@@ -31,9 +30,9 @@ namespace Ryujinx.Ui
         [GUI] TreeSelection _dlcTreeSelection;
 #pragma warning restore CS0649, IDE0044
 
-        public DlcWindow(string titleId, string titleName, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.DlcWindow.glade"), titleId, titleName, virtualFileSystem) { }
+        public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
 
-        private DlcWindow(Builder builder, string titleId, string titleName, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_dlcWindow").Handle)
+        private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_dlcWindow").Handle)
         {
             builder.Autoconnect(this);
 
@@ -51,10 +50,7 @@ namespace Ryujinx.Ui
                 _dlcContainerList = new List<DlcContainer>();
             }
             
-            _dlcTreeView.Model = new TreeStore(
-                typeof(bool),
-                typeof(string),
-                typeof(string));
+            _dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string));
 
             CellRendererToggle enableToggle = new CellRendererToggle();
             enableToggle.Toggled += (sender, args) =>
@@ -104,17 +100,9 @@ namespace Ryujinx.Ui
             {
                 return new Nca(_virtualFileSystem.KeySet, ncaStorage);
             }
-            catch (InvalidDataException exception)
+            catch (Exception exception)
             {
-                Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {containerPath}");
-
-                GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing.");
-            }
-            catch (MissingKeyException exception)
-            {
-                Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {containerPath}");
-
-                GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", $"Your key set is missing a key with the name: {exception.Name}");
+                GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {containerPath}");
             }
 
             return null;

+ 0 - 0
Ryujinx/Ui/DlcWindow.glade → Ryujinx/Ui/Windows/DlcWindow.glade


+ 40 - 42
Ryujinx/Ui/SettingsWindow.cs → Ryujinx/Ui/Windows/SettingsWindow.cs

@@ -6,20 +6,20 @@ using Ryujinx.Configuration;
 using Ryujinx.Configuration.System;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using Ryujinx.Ui.Helper;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
-using System.Reflection;
 using System.Threading.Tasks;
 
 using GUI = Gtk.Builder.ObjectAttribute;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Windows
 {
     public class SettingsWindow : Window
     {
-        private readonly VirtualFileSystem      _virtualFileSystem;
+        private readonly MainWindow             _parent;
         private readonly ListStore              _gameDirsBoxStore;
         private readonly ListStore              _audioBackendStore;
         private readonly TimeZoneContentManager _timeZoneContentManager;
@@ -86,36 +86,34 @@ namespace Ryujinx.Ui
 
 #pragma warning restore CS0649, IDE0044
 
-        public SettingsWindow(VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(new Builder("Ryujinx.Ui.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
+        public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(parent, new Builder("Ryujinx.Ui.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
 
-        private SettingsWindow(Builder builder, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle)
+        private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle)
         {
-            builder.Autoconnect(this);
-
-            this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
+            _parent = parent;
 
-            _virtualFileSystem = virtualFileSystem;
+            builder.Autoconnect(this);
 
             _timeZoneContentManager = new TimeZoneContentManager();
             _timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, LibHac.FsSystem.IntegrityCheckLevel.None);
 
             _validTzRegions = new HashSet<string>(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly.
 
-            //Bind Events
-            _configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player1);
-            _configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player2);
-            _configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player3);
-            _configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player4);
-            _configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player5);
-            _configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player6);
-            _configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player7);
-            _configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player8);
-            _configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Handheld);
+            // Bind Events.
+            _configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player1);
+            _configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player2);
+            _configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player3);
+            _configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player4);
+            _configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player5);
+            _configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player6);
+            _configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player7);
+            _configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player8);
+            _configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Handheld);
             _systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut;
 
             _resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
 
-            //Setup Currents
+            // Setup Currents.
             if (ConfigurationState.Instance.Logger.EnableFileLog)
             {
                 _fileLogToggle.Click();
@@ -419,12 +417,14 @@ namespace Ryujinx.Ui
                 ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1);
             }
 
-            MainWindow.SaveConfig();
-            MainWindow.UpdateGraphicsConfig();
-            MainWindow.ApplyTheme();
+            ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
+            _parent.UpdateGraphicsConfig();
+            ThemeHelper.ApplyTheme();
         }
 
-        //Events
+        //
+        // Events
+        //
         private void TimeZoneEntry_FocusOut(object sender, FocusOutEventArgs e)
         {
             if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text))
@@ -439,7 +439,7 @@ namespace Ryujinx.Ui
 
             return ((string)compl.Model.GetValue(iter, 1)).Contains(key, StringComparison.OrdinalIgnoreCase) || // region
                    ((string)compl.Model.GetValue(iter, 2)).StartsWith(key, StringComparison.OrdinalIgnoreCase) || // abbr
-                   ((string)compl.Model.GetValue(iter, 0)).Substring(3).StartsWith(key); // offset
+                   ((string)compl.Model.GetValue(iter, 0))[3..].StartsWith(key); // offset
         }
 
         private void SystemTimeSpin_ValueChanged(object sender, EventArgs e)
@@ -511,7 +511,7 @@ namespace Ryujinx.Ui
 
             _addGameDirBox.Buffer.Text = "";
 
-            ((ToggleButton)sender).SetStateFlags(0, true);
+            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
         }
 
         private void RemoveDir_Pressed(object sender, EventArgs args)
@@ -523,7 +523,7 @@ namespace Ryujinx.Ui
                 _gameDirsBoxStore.Remove(ref treeIter);
             }
 
-            ((ToggleButton)sender).SetStateFlags(0, true);
+            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
         }
 
         private void CustThemeToggle_Activated(object sender, EventArgs args)
@@ -535,27 +535,25 @@ namespace Ryujinx.Ui
 
         private void BrowseThemeDir_Pressed(object sender, EventArgs args)
         {
-            FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept);
-
-            fileChooser.Filter = new FileFilter();
-            fileChooser.Filter.AddPattern("*.css");
-
-            if (fileChooser.Run() == (int)ResponseType.Accept)
+            using (FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept))
             {
-                _custThemePath.Buffer.Text = fileChooser.Filename;
-            }
+                fileChooser.Filter = new FileFilter();
+                fileChooser.Filter.AddPattern("*.css");
 
-            fileChooser.Dispose();
+                if (fileChooser.Run() == (int)ResponseType.Accept)
+                {
+                    _custThemePath.Buffer.Text = fileChooser.Filename;
+                }
+            }
 
-            _browseThemePath.SetStateFlags(0, true);
+            _browseThemePath.SetStateFlags(StateFlags.Normal, true);
         }
 
-        private void ConfigureController_Pressed(object sender, EventArgs args, PlayerIndex playerIndex)
+        private void ConfigureController_Pressed(object sender, PlayerIndex playerIndex)
         {
-            ((ToggleButton)sender).SetStateFlags(0, true);
+            ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
 
-            ControllerWindow controllerWin = new ControllerWindow(playerIndex, _virtualFileSystem);
-            controllerWin.Show();
+            new ControllerWindow(playerIndex).Show();
         }
 
         private void SaveToggle_Activated(object sender, EventArgs args)
@@ -574,4 +572,4 @@ namespace Ryujinx.Ui
             Dispose();
         }
     }
-}
+}

+ 0 - 0
Ryujinx/Ui/SettingsWindow.glade → Ryujinx/Ui/Windows/SettingsWindow.glade


+ 36 - 42
Ryujinx/Ui/TitleUpdateWindow.cs → Ryujinx/Ui/Windows/TitleUpdateWindow.cs

@@ -1,5 +1,4 @@
 using Gtk;
-using LibHac;
 using LibHac.Common;
 using LibHac.Fs;
 using LibHac.Fs.Fsa;
@@ -7,9 +6,9 @@ using LibHac.FsSystem;
 using LibHac.FsSystem.NcaUtils;
 using LibHac.Ns;
 using Ryujinx.Common.Configuration;
-using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
+using Ryujinx.Ui.Widgets;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -19,16 +18,18 @@ using System.Text;
 using GUI        = Gtk.Builder.ObjectAttribute;
 using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
 
-namespace Ryujinx.Ui
+namespace Ryujinx.Ui.Windows
 {
     public class TitleUpdateWindow : Window
     {
+        private readonly MainWindow        _parent;
         private readonly VirtualFileSystem _virtualFileSystem;
         private readonly string            _titleId;
         private readonly string            _updateJsonPath;
 
-        private TitleUpdateMetadata             _titleUpdateWindowData;
-        private Dictionary<RadioButton, string> _radioButtonToPathDictionary;
+        private TitleUpdateMetadata _titleUpdateWindowData;
+
+        private readonly Dictionary<RadioButton, string> _radioButtonToPathDictionary;
 
 #pragma warning disable CS0649, IDE0044
         [GUI] Label       _baseTitleInfoLabel;
@@ -36,10 +37,12 @@ namespace Ryujinx.Ui
         [GUI] RadioButton _noUpdateRadioButton;
 #pragma warning restore CS0649, IDE0044
 
-        public TitleUpdateWindow(string titleId, string titleName, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.TitleUpdateWindow.glade"), titleId, titleName, virtualFileSystem) { }
+        public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
 
-        private TitleUpdateWindow(Builder builder, string titleId, string titleName, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_titleUpdateWindow").Handle)
+        private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_titleUpdateWindow").Handle)
         {
+            _parent = parent;
+
             builder.Autoconnect(this);
 
             _titleId                     = titleId;
@@ -61,20 +64,26 @@ namespace Ryujinx.Ui
             }
 
             _baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
-            _noUpdateRadioButton.Active = true;
-
+            
             foreach (string path in _titleUpdateWindowData.Paths)
             {
-                AddUpdate(path, false);
+                AddUpdate(path);
             }
 
-            foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected))
+            if (_titleUpdateWindowData.Selected == "")
+            {
+                _noUpdateRadioButton.Active = true;
+            }
+            else
             {
-                update.Active = true;
+                foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected))
+                {
+                    update.Active = true;
+                }
             }
         }
 
-        private void AddUpdate(string path, bool showErrorDialog = true)
+        private void AddUpdate(string path)
         {
             if (File.Exists(path))
             {
@@ -107,23 +116,9 @@ namespace Ryujinx.Ui
                             GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
                         }
                     }
-                    catch (InvalidDataException exception)
-                    {
-                        Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {path}");
-
-                        if (showErrorDialog)
-                        {
-                            GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing.");
-                        }
-                    }
-                    catch (MissingKeyException exception)
+                    catch (Exception exception)
                     {
-                        Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {path}");
-
-                        if (showErrorDialog)
-                        {
-                            GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", $"Your key set is missing a key with the name: {exception.Name}");
-                        }
+                        GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
                     }
                 }
             }
@@ -144,23 +139,21 @@ namespace Ryujinx.Ui
 
         private void AddButton_Clicked(object sender, EventArgs args)
         {
-            FileChooserDialog fileChooser = new FileChooserDialog("Select update files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept)
+            using (FileChooserDialog fileChooser = new FileChooserDialog("Select update files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept))
             {
-                SelectMultiple = true,
-                Filter         = new FileFilter()
-            };
-            fileChooser.SetPosition(WindowPosition.Center);
-            fileChooser.Filter.AddPattern("*.nsp");
+                fileChooser.SelectMultiple = true;
+                fileChooser.SetPosition(WindowPosition.Center);
+                fileChooser.Filter = new FileFilter();
+                fileChooser.Filter.AddPattern("*.nsp");
 
-            if (fileChooser.Run() == (int)ResponseType.Accept)
-            {
-                foreach (string path in fileChooser.Filenames)
+                if (fileChooser.Run() == (int)ResponseType.Accept)
                 {
-                    AddUpdate(path);
+                    foreach (string path in fileChooser.Filenames)
+                    {
+                        AddUpdate(path);
+                    }
                 }
             }
-
-            fileChooser.Dispose();
         }
 
         private void RemoveButton_Clicked(object sender, EventArgs args)
@@ -196,7 +189,8 @@ namespace Ryujinx.Ui
                 dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
             }
 
-            MainWindow.UpdateGameTable();
+            _parent.UpdateGameTable();
+
             Dispose();
         }
 

+ 0 - 0
Ryujinx/Ui/TitleUpdateWindow.glade → Ryujinx/Ui/Windows/TitleUpdateWindow.glade


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików