diff --git a/Knossos.NET/App.axaml.cs b/Knossos.NET/App.axaml.cs index d01764fa..af828745 100644 --- a/Knossos.NET/App.axaml.cs +++ b/Knossos.NET/App.axaml.cs @@ -1,29 +1,342 @@ 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; + bool minimizeToTray = false; + public override void Initialize() { AvaloniaXamlLoader.Load(this); } + public void DisableMinimizeToTrayRuntime() + { + minimizeToTray = false; + } + + public void EnableMinimizeToTrayRuntime() + { + if (!minimizeToTray && ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (desktop.MainWindow != null) + { + desktop.MainWindow.PropertyChanged += (v, __) => + { + 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; + } + } + } + public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow + minimizeToTray = Environment.GetCommandLineArgs().FirstOrDefault(x => x.ToLower() == "-traymode") != null; + if (minimizeToTray) { - DataContext = new MainWindowViewModel(), - }; + 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"); + open.Click += (s, _) => { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (desktop.MainWindow == null) + { + 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(); + } + }; + 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); + mods.Sort(Mod.CompareTitles); + 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 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(); + } + + 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 aa744da0..26125531 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); } @@ -822,6 +823,8 @@ await Task.Run(async () => { NebulaModListView.Instance?.GenerateFilterButtons(); ModListView.Instance?.GenerateFilterButtons(); }); + + initIsComplete = true; } } diff --git a/Knossos.NET/Models/GlobalSettings.cs b/Knossos.NET/Models/GlobalSettings.cs index 35e97fd0..d6ebf275 100644 --- a/Knossos.NET/Models/GlobalSettings.cs +++ b/Knossos.NET/Models/GlobalSettings.cs @@ -121,10 +121,31 @@ 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(); + } + } + } + } + } [JsonPropertyName("ignored_launcher_updates")] public List ignoredLauncherUpdates { get; set; } = new List(); - /* * Settings that can wait to be saved at app close so we dont have to call save() all the time * use JsonIgnore, private and '_' for the actual variable name @@ -674,6 +695,7 @@ public void Load() mainMenuOpen = tempSettings.mainMenuOpen; sortType = tempSettings.sortType; portableFsoPreferences = tempSettings.portableFsoPreferences; + minimizeToTray = tempSettings.minimizeToTray; ignoredLauncherUpdates = tempSettings.ignoredLauncherUpdates; ReadFS2IniValues(); diff --git a/Knossos.NET/Models/Mod.cs b/Knossos.NET/Models/Mod.cs index f2b6ae80..967759c7 100644 --- a/Knossos.NET/Models/Mod.cs +++ b/Knossos.NET/Models/Mod.cs @@ -830,6 +830,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: diff --git a/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs b/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs index e4f1b6b8..b63ef4a4 100644 --- a/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs +++ b/Knossos.NET/ViewModels/GlobalSettingsViewModel.cs @@ -197,6 +197,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 @@ -660,6 +668,7 @@ public void LoadData() PrefixCMD = Knossos.globalSettings.prefixCMD; EnvVars = Knossos.globalSettings.envVars; ShowDevOptions = Knossos.globalSettings.showDevOptions || NoSystemCMD; + MinimizeToTray = Knossos.globalSettings.minimizeToTray; /* VIDEO SETTINGS */ //RESOLUTION @@ -1239,6 +1248,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 3d30c099..cfd8049f 100644 --- a/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs +++ b/Knossos.NET/ViewModels/Windows/MainWindowViewModel.cs @@ -97,15 +97,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; @@ -155,7 +149,16 @@ public MainWindowViewModel() MainWindow.instance?.FixMarginButtomTasks(); } } - Knossos.StartUp(isQuickLaunch, forceUpdate); + 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 10f8eaed..7066eba6 100644 --- a/Knossos.NET/Views/GlobalSettingsView.axaml +++ b/Knossos.NET/Views/GlobalSettingsView.axaml @@ -60,6 +60,11 @@ + + + + +