LANCommander/LANCommander.Launcher/Helpers/TrayIconExtensions.cs
2026-06-14 21:24:07 -05:00

142 lines
4.7 KiB
C#

using System;
using System.Globalization;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using LANCommander.Launcher.Converters;
using LANCommander.Launcher.ViewModels;
using LANCommander.Launcher.Views;
namespace LANCommander.Launcher.Helpers;
public static class TrayIconExtensions
{
/// <summary>
/// Creates the system tray icon for the launcher. The main window hides to tray on
/// close (see <see cref="MainWindow"/>'s Closing handler), so the tray provides a way
/// to navigate back into the app and to exit. The menu is rebuilt whenever the window
/// hides to tray so the recently played list stays current. The returned icon is
/// disposed automatically when the window closes.
/// </summary>
public static TrayIcon CreateTrayIcon(this MainWindow mainWindow, MainWindowViewModel mainViewModel)
{
var trayIcon = new TrayIcon
{
Icon = new WindowIcon(AssetLoader.Open(
new Uri("avares://LANCommander.Launcher/Assets/icon.ico"))),
ToolTipText = "LANCommander",
IsVisible = true,
};
void RestoreWindow()
{
mainWindow.Show();
if (mainWindow.WindowState == WindowState.Minimized)
mainWindow.WindowState = WindowState.Normal;
mainWindow.Activate();
}
var shell = mainViewModel.ShellViewModel;
NativeMenu BuildTrayMenu()
{
var menu = new NativeMenu();
var depotItem = new NativeMenuItem("Depot");
depotItem.Click += (_, _) =>
{
RestoreWindow();
if (mainViewModel.IsShellActive)
shell.ShowDepotCommand.Execute(null);
};
var libraryItem = new NativeMenuItem("Library");
libraryItem.Click += (_, _) =>
{
RestoreWindow();
if (mainViewModel.IsShellActive)
shell.ShowLibraryCommand.Execute(null);
};
var settingsItem = new NativeMenuItem("Settings");
settingsItem.Click += (_, _) =>
{
RestoreWindow();
if (mainViewModel.IsShellActive)
shell.ShowSettingsCommand.Execute(null);
};
menu.Add(depotItem);
menu.Add(libraryItem);
menu.Add(settingsItem);
// Last 5 recently played games (available once the shell/library is loaded)
var recentGames = shell.LibraryViewModel?.RecentlyPlayedGames;
if (recentGames is { Count: > 0 })
{
menu.Add(new NativeMenuItemSeparator());
foreach (var game in recentGames.Take(5))
{
var gameId = game.Id;
var isInstalled = game.IsInstalled;
var recentItem = new NativeMenuItem(game.Title)
{
Icon = FilePathToBitmapConverter.Instance.Convert(
game.IconPath, typeof(Bitmap), "h32", CultureInfo.CurrentCulture) as Bitmap,
};
recentItem.Click += async (_, _) =>
{
if (!mainViewModel.IsShellActive)
return;
// Installed games launch directly; uninstalled ones open their
// detail page so the user can install them.
if (isInstalled)
await shell.RunGameByIdAsync(gameId);
else
{
RestoreWindow();
await shell.NavigateToGameByIdAsync(gameId);
}
};
menu.Add(recentItem);
}
}
menu.Add(new NativeMenuItemSeparator());
var exitItem = new NativeMenuItem("Exit");
exitItem.Click += (_, _) => mainWindow.ExitApplication();
menu.Add(exitItem);
return menu;
}
trayIcon.Menu = BuildTrayMenu();
// Rebuild the menu each time the window hides to tray so the recently
// played list reflects the latest play sessions.
mainWindow.HiddenToTray += (_, _) => trayIcon.Menu = BuildTrayMenu();
trayIcon.Clicked += (_, _) => RestoreWindow();
mainWindow.Closed += (_, _) => trayIcon.Dispose();
return trayIcon;
}
}