From 8016fc491bae3fb3f0472ec7fcacb7b601f3e0a1 Mon Sep 17 00:00:00 2001 From: Salvador Cipolla Date: Sat, 1 Feb 2025 02:35:38 -0300 Subject: [PATCH 1/3] Add experimental tray icon --- Knossos.NET/App.axaml.cs | 268 +++++++++++++++++- Knossos.NET/Classes/Knossos.cs | 7 +- .../ViewModels/Windows/MainWindowViewModel.cs | 17 +- 3 files changed, 279 insertions(+), 13 deletions(-) diff --git a/Knossos.NET/App.axaml.cs b/Knossos.NET/App.axaml.cs index d01764fa..de6b5257 100644 --- a/Knossos.NET/App.axaml.cs +++ b/Knossos.NET/App.axaml.cs @@ -1,13 +1,27 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Knossos.NET.Classes; +using Knossos.NET.Models; using Knossos.NET.ViewModels; using Knossos.NET.Views; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; namespace Knossos.NET { public partial class App : Application { + TrayIcon? trayIcon = null; + public override void Initialize() { AvaloniaXamlLoader.Load(this); @@ -17,13 +31,261 @@ public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow + bool trayMode = false; + foreach (var arg in Environment.GetCommandLineArgs()) { - DataContext = new MainWindowViewModel(), - }; + if (arg.ToLower() == "-traymode") + { + trayMode = true; + } + } + if (trayMode) + { + desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; + Knossos.StartUp(false, false); + StartTrayIcon(); + } + else + { + desktop.MainWindow = new MainWindow + { + DataContext = new MainWindowViewModel() + }; + } } base.OnFrameworkInitializationCompleted(); } + + + private async void StartTrayIcon() + { + if (trayIcon == null) + { + trayIcon = new TrayIcon + { + IsVisible = true, + ToolTipText = "Knossos.NET v" + Knossos.AppVersion, + Icon = new WindowIcon(new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/knossos-icon.ico")))), + Menu = new NativeMenu() { new NativeMenuItem("Loading...") } + }; + + while (!Knossos.initIsComplete) { await Task.Delay(10); } + } + + trayIcon.Menu = new NativeMenu(); + + /*****************************OPEN***********************************/ + var open = new NativeMenuItem("Open Launcher"); + open.Click += (s, e) => { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow + { + DataContext = new MainWindowViewModel() + }; + desktop.MainWindow.Closing += (s, e) => + { + StartTrayIcon(); + trayIcon.IsVisible = true; + }; + desktop.MainWindow.Show(); + trayIcon.IsVisible = false; + GC.Collect(); + } + }; + trayIcon.Menu.Add(open); + trayIcon.Menu.Add(new NativeMenuItemSeparator()); + + try + { + /*****************************PLAY***********************************/ + var play = new NativeMenuItem("Play") { Menu = new NativeMenu(), Icon = new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/general/menu_play.png"))) }; + var mods = Knossos.GetInstalledModList(null); + var filters = ModTags.GetListAllFilters(); + foreach (var filter in filters) + { + TextInfo myTI = new CultureInfo("en-US", false).TextInfo; + var displayName = myTI.ToTitleCase(filter.Replace("_", " ")); + var filterItem = new NativeMenuItem(displayName) { Menu = new NativeMenu() }; + var modsInFilter = mods.Where(x => ModTags.IsFilterPresentInModID(x.id, filter)); + if (modsInFilter.Any()) + { + var addedIds = new List(); + foreach (var mod in modsInFilter) + { + if (!addedIds.Contains(mod.id)) + { + var m = mods.Where(x => x.id == mod.id)?.MaxBy(x => new SemanticVersion(x.version)); + if (m != null) + { + var modItem = new NativeMenuItem(m.ToString()) { Menu = new NativeMenu() }; + /*************************************MOD SUB BUTTONS*************************************/ + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.Release)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.Debug)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.Fred2)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.Fred2Debug)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.QtFred)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.QtFredDebug)); + var settings = new NativeMenuItem("Settings"); + settings.Click += (s, e) => { + var dialog = new ModSettingsView(); + dialog.DataContext = new ModSettingsViewModel(m); + dialog.Show(); + }; + modItem.Menu.Add(settings); + /*****************************************************************************************/ + filterItem.Menu.Add(modItem); + addedIds.Add(mod.id); + } + } + } + play.Menu.Add(filterItem); + } + } + if(play.Menu.Any()) + trayIcon.Menu.Add(play); + /*****************************DEVELOP***********************************/ + var dev = new NativeMenuItem("Develop") { Menu = new NativeMenu(), Icon = new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/general/menu_develop.png"))) }; + var devMods = mods.Where(x => x.devMode); + if(devMods != null && devMods.Any()) + { + var addedIds = new List(); + foreach (var devMod in devMods) + { + if (!addedIds.Contains(devMod.id)) + { + var m = mods.Where(x => x.id == devMod.id)?.MaxBy(x => new SemanticVersion(x.version)); + if(m != null) + { + var modItem = new NativeMenuItem(m.ToString()) { Menu = new NativeMenu() }; + /*************************************MOD SUB BUTTONS*************************************/ + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.Release)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.Debug)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.Fred2)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.Fred2Debug)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.QtFred)); + modItem.Menu.Add(CreateLaunchFSOButton(m, FsoExecType.QtFredDebug)); + var settings = new NativeMenuItem("Settings"); + settings.Click += (s, e) => { + var dialog = new ModSettingsView(); + dialog.DataContext = new ModSettingsViewModel(m); + dialog.Show(); + }; + modItem.Menu.Add(settings); + /*****************************************************************************************/ + dev.Menu.Add(modItem); + addedIds.Add(devMod.id); + } + } + } + } + if(dev.Menu.Any()) + trayIcon.Menu.Add(dev); + /*****************************TOOLS*************************************/ + var toolsItem = new NativeMenuItem("Tools") { Menu = new NativeMenu(), Icon = new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/general/custom-config-icon.png"))) }; + var tools = Knossos.GetTools(); + if (tools != null && tools.Any()) + { + foreach (var tool in tools) + { + var tItem = new NativeMenuItem(tool.name); + tItem.Click += (s, e) => { + if(tool.isInstalled) + tool?.Open(); + }; + toolsItem.Menu.Add(tItem); + } + } + if (toolsItem.Menu.Any()) + trayIcon.Menu.Add(toolsItem); + /*****************************DEBUG*************************************/ + var debug = new NativeMenuItem("Debug") { Menu = new NativeMenu(), Icon = new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/general/menu_debug.png"))) }; + var openFs2Log = new NativeMenuItem("Open fs2_open.log"); + openFs2Log.Click += (s, e) => { + OpenFS2Log(); + }; + debug.Menu.Add(openFs2Log); + var openLog = new NativeMenuItem("Open knossos.log"); + openLog.Click += (s, e) => { + OpenLog(); + }; + debug.Menu.Add(openLog); + trayIcon.Menu.Add(debug); + } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "App.StartTrayIconApp()", ex); + } + + /*****************************CLOSE***********************************/ + trayIcon.Menu.Add(new NativeMenuItemSeparator()); + var close = new NativeMenuItem("Exit"); + close.Click += (s, e) => { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.Shutdown(); + } + }; + trayIcon.Menu.Add(close); + GC.Collect(); + } + + private NativeMenuItem CreateLaunchFSOButton(Mod mod, FsoExecType fsoExecType) + { + var item = new NativeMenuItem(fsoExecType.ToString()); + item.Click += (s, e) => { + Knossos.PlayMod(mod, fsoExecType); + }; + return item; + } + + private void OpenLog() + { + if (File.Exists(KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "Knossos.log")) + { + try + { + var cmd = new Process(); + cmd.StartInfo.FileName = KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "Knossos.log"; + cmd.StartInfo.UseShellExecute = true; + cmd.Start(); + cmd.Dispose(); + } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "App.OpenLog", ex); + } + } + else + { + if (MainWindow.instance != null) + MessageBox.Show(MainWindow.instance, "Log File " + KnUtils.GetKnossosDataFolderPath() + Path.DirectorySeparatorChar + "Knossos.log not found.", "File not found", MessageBox.MessageBoxButtons.OK); + } + } + + private void OpenFS2Log() + { + if (File.Exists(KnUtils.GetFSODataFolderPath() + Path.DirectorySeparatorChar + "data" + Path.DirectorySeparatorChar + "fs2_open.log")) + { + try + { + var cmd = new Process(); + cmd.StartInfo.FileName = KnUtils.GetFSODataFolderPath() + Path.DirectorySeparatorChar + "data" + Path.DirectorySeparatorChar + "fs2_open.log"; + cmd.StartInfo.UseShellExecute = true; + cmd.Start(); + cmd.Dispose(); + } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "App.OpenFS2Log", ex); + } + } + else + { + if (MainWindow.instance != null) + MessageBox.Show(MainWindow.instance, "Log File " + KnUtils.GetFSODataFolderPath() + Path.DirectorySeparatorChar + "data" + Path.DirectorySeparatorChar + "fs2_open.log not found.", "File not found", MessageBox.MessageBoxButtons.OK); + } + } } } diff --git a/Knossos.NET/Classes/Knossos.cs b/Knossos.NET/Classes/Knossos.cs index 1cadfa90..99f07d6f 100644 --- a/Knossos.NET/Classes/Knossos.cs +++ b/Knossos.NET/Classes/Knossos.cs @@ -32,6 +32,7 @@ public static class Knossos public static bool inPortableMode { get; private set; } = false; public static bool isKnDataFolderReadOnly { get; private set; } = false; public static bool inSingleTCMode { get; private set; } = false; + public static bool initIsComplete { get; private set; } = false; /// /// Static constructor @@ -162,8 +163,8 @@ public static async void StartUp(bool isQuickLaunch, bool forceUpdate) if (globalSettings.basePath == null && !isQuickLaunch && !inSingleTCMode) OpenQuickSetup(); - - }catch(Exception ex) + } + catch(Exception ex) { Log.Add(Log.LogSeverity.Error, "Knossos.StartUp", ex); } @@ -744,6 +745,8 @@ await Task.Run(async () => { NebulaModListView.Instance?.GenerateFilterButtons(); ModListView.Instance?.GenerateFilterButtons(); }); + + initIsComplete = true; } } diff --git a/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs b/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs index b578f772..69ee0825 100644 --- a/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs +++ b/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs @@ -93,15 +93,9 @@ public MainWindowViewModel() { Instance = this; TaskInfoButton = new TaskInfoButtonViewModel(this.TaskView); - string[] args = Environment.GetCommandLineArgs(); - bool isQuickLaunch = false; bool forceUpdate = false; - foreach (var arg in args) + foreach (var arg in Environment.GetCommandLineArgs()) { - if (arg.ToLower() == "-playmod") - { - isQuickLaunch = true; - } if (arg.ToLower() == "-forceupdate") { forceUpdate = true; @@ -147,7 +141,14 @@ public MainWindowViewModel() MainWindow.instance?.FixMarginButtomTasks(); } } - Knossos.StartUp(isQuickLaunch, forceUpdate); + if (!Knossos.initIsComplete) + { + Knossos.StartUp(false, forceUpdate); + } + else + { + Knossos.LoadBasePath(); + } CustomHomeVM?.CheckBasePath(); } From 9d44135a7bfb8a454c2ce65eabd9c96a15a1629c Mon Sep 17 00:00:00 2001 From: Salvador Cipolla Date: Sat, 1 Feb 2025 17:15:44 -0300 Subject: [PATCH 2/3] sort mods by title --- Knossos.NET/App.axaml.cs | 1 + Knossos.NET/Models/Mod.cs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/Knossos.NET/App.axaml.cs b/Knossos.NET/App.axaml.cs index de6b5257..005499d3 100644 --- a/Knossos.NET/App.axaml.cs +++ b/Knossos.NET/App.axaml.cs @@ -102,6 +102,7 @@ private async void StartTrayIcon() /*****************************PLAY***********************************/ var play = new NativeMenuItem("Play") { Menu = new NativeMenu(), Icon = new Bitmap(AssetLoader.Open(new Uri("avares://Knossos.NET/Assets/general/menu_play.png"))) }; var mods = Knossos.GetInstalledModList(null); + mods.Sort(Mod.CompareTitles); var filters = ModTags.GetListAllFilters(); foreach (var filter in filters) { diff --git a/Knossos.NET/Models/Mod.cs b/Knossos.NET/Models/Mod.cs index e03cac6a..76a7af6e 100644 --- a/Knossos.NET/Models/Mod.cs +++ b/Knossos.NET/Models/Mod.cs @@ -846,6 +846,17 @@ public static int CompareTitles(string title1, string title2) return String.Compare(KnUtils.RemoveArticles(title1), KnUtils.RemoveArticles(title2), StringComparison.CurrentCultureIgnoreCase); } + /// + /// To use with the List .Sort() + /// Orders the two titles using a regular case-insensitive string comparison, but ignoring any leading 'A', 'An', or 'The' articles + /// + /// + /// + public static int CompareTitles(Mod mod1, Mod mod2) + { + return String.Compare(KnUtils.RemoveArticles(mod1.title), KnUtils.RemoveArticles(mod2.title), StringComparison.CurrentCultureIgnoreCase); + } + /// /// Does mod sorting based on the global Knossos.globalSettings.sortType variable /// Returns: From c5b73dd4dd3bef3247787008c7e093d39b92a972 Mon Sep 17 00:00:00 2001 From: Salvador Cipolla Date: Wed, 5 Feb 2025 19:08:06 -0300 Subject: [PATCH 3/3] improve transition and enable in runtime option --- Knossos.NET/App.axaml.cs | 90 ++++++++++++++----- Knossos.NET/Models/GlobalSettings.cs | 23 +++++ .../ViewModels/GlobalSettingsViewModel.cs | 10 +++ .../ViewModels/Windows/MainWindowViewModel.cs | 2 + Knossos.NET/Views/GlobalSettingsView.axaml | 5 ++ 5 files changed, 110 insertions(+), 20 deletions(-) diff --git a/Knossos.NET/App.axaml.cs b/Knossos.NET/App.axaml.cs index 005499d3..af828745 100644 --- a/Knossos.NET/App.axaml.cs +++ b/Knossos.NET/App.axaml.cs @@ -21,25 +21,53 @@ namespace Knossos.NET public partial class App : Application { TrayIcon? trayIcon = null; + bool minimizeToTray = false; public override void Initialize() { AvaloniaXamlLoader.Load(this); } - public override void OnFrameworkInitializationCompleted() + public void DisableMinimizeToTrayRuntime() { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + minimizeToTray = false; + } + + public void EnableMinimizeToTrayRuntime() + { + if (!minimizeToTray && ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - bool trayMode = false; - foreach (var arg in Environment.GetCommandLineArgs()) + if (desktop.MainWindow != null) { - if (arg.ToLower() == "-traymode") + desktop.MainWindow.PropertyChanged += (v, __) => { - trayMode = true; - } + if (minimizeToTray && v is MainWindow view && view.WindowState == WindowState.Minimized) + { + desktop.MainWindow.Hide(); + desktop.MainWindow.WindowState = WindowState.Normal; + StartTrayIcon(); + trayIcon!.IsVisible = true; + } + }; + desktop.MainWindow.Closing += (_, __) => + { + if (trayIcon != null) + { + trayIcon.IsVisible = false; + trayIcon = null; + } + }; + minimizeToTray = true; } - if (trayMode) + } + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + minimizeToTray = Environment.GetCommandLineArgs().FirstOrDefault(x => x.ToLower() == "-traymode") != null; + if (minimizeToTray) { desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; Knossos.StartUp(false, false); @@ -76,20 +104,37 @@ private async void StartTrayIcon() trayIcon.Menu = new NativeMenu(); /*****************************OPEN***********************************/ - var open = new NativeMenuItem("Open Launcher"); - open.Click += (s, e) => { + var open = new NativeMenuItem("Open"); + open.Click += (s, _) => { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow + if (desktop.MainWindow == null) { - DataContext = new MainWindowViewModel() - }; - desktop.MainWindow.Closing += (s, e) => - { - StartTrayIcon(); - trayIcon.IsVisible = true; - }; - desktop.MainWindow.Show(); + desktop.MainWindow = new MainWindow + { + DataContext = new MainWindowViewModel() + }; + desktop.MainWindow.PropertyChanged += (v, __) => + { + if (v is MainWindow view && view.WindowState == WindowState.Minimized) + { + desktop.MainWindow.Hide(); + desktop.MainWindow.WindowState = WindowState.Normal; + StartTrayIcon(); + trayIcon.IsVisible = true; + } + }; + desktop.MainWindow.Closing += (_, __) => + { + if (trayIcon != null) + { + trayIcon.IsVisible = false; + trayIcon = null; + } + }; + desktop.ShutdownMode = ShutdownMode.OnLastWindowClose; + } + desktop.MainWindow?.Show(); trayIcon.IsVisible = false; GC.Collect(); } @@ -221,12 +266,17 @@ private async void StartTrayIcon() /*****************************CLOSE***********************************/ trayIcon.Menu.Add(new NativeMenuItemSeparator()); - var close = new NativeMenuItem("Exit"); + var close = new NativeMenuItem("Exit Knossos.NET"); close.Click += (s, e) => { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.Shutdown(); } + if(trayIcon != null) + { + trayIcon.IsVisible = false; + trayIcon = null; + } }; trayIcon.Menu.Add(close); GC.Collect(); diff --git a/Knossos.NET/Models/GlobalSettings.cs b/Knossos.NET/Models/GlobalSettings.cs index 5f87c691..8adde177 100644 --- a/Knossos.NET/Models/GlobalSettings.cs +++ b/Knossos.NET/Models/GlobalSettings.cs @@ -120,6 +120,28 @@ public AutoUpdateFsoBuilds(bool stable = false, bool rc = false, bool nightly = public AutoUpdateFsoBuilds autoUpdateBuilds { get; set; } = new AutoUpdateFsoBuilds(); [JsonPropertyName("warn_new_settings_system")] public bool warnNewSettingsSystem { get; set; } = true; + [JsonIgnore] + public bool _minimizeToTray { get; set; } = false; + [JsonPropertyName("minimize_to_tray")] + public bool minimizeToTray { + get { return _minimizeToTray; } + set { if (_minimizeToTray != value) { + _minimizeToTray = value; + pendingChangesOnAppClose = true; + if (Avalonia.Application.Current is App app) + { + if (_minimizeToTray) + { + app.EnableMinimizeToTrayRuntime(); + } + else + { + app.DisableMinimizeToTrayRuntime(); + } + } + } + } + } /* * Settings that can wait to be saved at app close so we dont have to call save() all the time @@ -670,6 +692,7 @@ public void Load() mainMenuOpen = tempSettings.mainMenuOpen; sortType = tempSettings.sortType; portableFsoPreferences = tempSettings.portableFsoPreferences; + minimizeToTray = tempSettings.minimizeToTray; ReadFS2IniValues(); Log.Add(Log.LogSeverity.Information, "GlobalSettings.Load()", "Global settings have been loaded"); diff --git a/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs b/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs index 54019739..ea25fa8d 100644 --- a/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs +++ b/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs @@ -193,6 +193,14 @@ internal bool ShowDevOptions set { if (showDevOptions != value) { this.SetProperty(ref showDevOptions, value); UnCommitedChanges = true; } } } + /* This change is applied right away */ + private bool minimizeToTray = false; + internal bool MinimizeToTray + { + get { return minimizeToTray; } + set { if (minimizeToTray != value) { this.SetProperty(ref minimizeToTray, value); Knossos.globalSettings.minimizeToTray = value ; UnCommitedChanges = true; } } + } + /*VIDEO*/ private int bitsSelectedIndex = 0; internal int BitsSelectedIndex @@ -655,6 +663,7 @@ public void LoadData() PrefixCMD = Knossos.globalSettings.prefixCMD; EnvVars = Knossos.globalSettings.envVars; ShowDevOptions = Knossos.globalSettings.showDevOptions || NoSystemCMD; + MinimizeToTray = Knossos.globalSettings.minimizeToTray; /* VIDEO SETTINGS */ //RESOLUTION @@ -1234,6 +1243,7 @@ internal void SaveCommand() Knossos.globalSettings.prefixCMD = PrefixCMD; Knossos.globalSettings.envVars = EnvVars; Knossos.globalSettings.showDevOptions = ShowDevOptions; + Knossos.globalSettings.minimizeToTray = MinimizeToTray; /* VIDEO */ //Resolution diff --git a/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs b/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs index 69ee0825..2dae33c0 100644 --- a/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs +++ b/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs @@ -143,10 +143,12 @@ public MainWindowViewModel() } if (!Knossos.initIsComplete) { + //Normal Startup Knossos.StartUp(false, forceUpdate); } else { + //Loading main view the first time from the tray icon Knossos.LoadBasePath(); } CustomHomeVM?.CheckBasePath(); diff --git a/Knossos.NET/Views/GlobalSettingsView.axaml b/Knossos.NET/Views/GlobalSettingsView.axaml index 898440cd..61449b2a 100644 --- a/Knossos.NET/Views/GlobalSettingsView.axaml +++ b/Knossos.NET/Views/GlobalSettingsView.axaml @@ -60,6 +60,11 @@ + + + + +