diff --git a/src/Starward.Language/Lang.Designer.cs b/src/Starward.Language/Lang.Designer.cs index c14e2c908..f23b75100 100644 --- a/src/Starward.Language/Lang.Designer.cs +++ b/src/Starward.Language/Lang.Designer.cs @@ -1529,6 +1529,15 @@ public static string GameLauncherPage_PleaseRestartAsAdministrator { } } + /// + /// 查找类似 Relocate 的本地化字符串。 + /// + public static string GameLauncherPage_Relocate { + get { + return ResourceManager.GetString("GameLauncherPage_Relocate", resourceCulture); + } + } + /// /// 查找类似 Removable storage device not connected. 的本地化字符串。 /// @@ -4072,6 +4081,24 @@ public static string StarRailGachaService_ImportWarpRecordsSuccessfully { } } + /// + /// 查找类似 Resume Download 的本地化字符串。 + /// + public static string StartGameButton_ResumeDownload { + get { + return ResourceManager.GetString("StartGameButton_ResumeDownload", resourceCulture); + } + } + + /// + /// 查找类似 Waiting 的本地化字符串。 + /// + public static string StartGameButton_Waiting { + get { + return ResourceManager.GetString("StartGameButton_Waiting", resourceCulture); + } + } + /// /// 查找类似 This feature can quickly switch the client to the corresponding server, only needing to download some resources during the first switch. 的本地化字符串。 /// diff --git a/src/Starward.Language/Lang.resx b/src/Starward.Language/Lang.resx index 9c8f9e9ed..3c47d3062 100644 --- a/src/Starward.Language/Lang.resx +++ b/src/Starward.Language/Lang.resx @@ -1609,4 +1609,13 @@ Do you accept the risk and continue to use it? Other Rewards + + Resume Download + + + Waiting + + + Relocate + \ No newline at end of file diff --git a/src/Starward.Language/Lang.zh-CN.resx b/src/Starward.Language/Lang.zh-CN.resx index 3687f014f..a668f91d6 100644 --- a/src/Starward.Language/Lang.zh-CN.resx +++ b/src/Starward.Language/Lang.zh-CN.resx @@ -59,46 +59,46 @@ : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> - - + + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + @@ -1580,7 +1580,7 @@ 辉彩祝福 - 可移动存储设备未连接 + 可移动存储设备未连接。 菲林收入组成 @@ -1609,4 +1609,13 @@ 其他奖励 + + 继续下载 + + + 等待中 + + + 重新定位 + \ No newline at end of file diff --git a/src/Starward.Language/Lang.zh-TW.resx b/src/Starward.Language/Lang.zh-TW.resx index 84ce46146..e73fcf67a 100644 --- a/src/Starward.Language/Lang.zh-TW.resx +++ b/src/Starward.Language/Lang.zh-TW.resx @@ -60,45 +60,45 @@ : and then encoded with base64 encoding. --> - + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + @@ -1580,7 +1580,7 @@ 輝彩祝福 - 可移動存儲設備未連接 + 可移動存儲設備未連接。 Polychrome Revenue Streams diff --git a/src/Starward/Features/Background/AppBackground.xaml b/src/Starward/Features/Background/AppBackground.xaml index 6e7d85f64..d52414e65 100644 --- a/src/Starward/Features/Background/AppBackground.xaml +++ b/src/Starward/Features/Background/AppBackground.xaml @@ -43,6 +43,19 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Starward/Features/GameLauncher/GameLauncherPage.xaml.cs b/src/Starward/Features/GameLauncher/GameLauncherPage.xaml.cs index 5f5073f1a..28640781c 100644 --- a/src/Starward/Features/GameLauncher/GameLauncherPage.xaml.cs +++ b/src/Starward/Features/GameLauncher/GameLauncherPage.xaml.cs @@ -1,4 +1,9 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.Extensions.Logging; using Starward.Frameworks; +using System; +using System.Threading.Tasks; namespace Starward.Features.GameLauncher; @@ -7,10 +12,200 @@ public sealed partial class GameLauncherPage : PageBase { + private readonly ILogger _logger = AppService.GetLogger(); + + private readonly GameLauncherService _gameLauncherService = AppService.GetService(); + + private readonly GamePackageService _gamePackageService = AppService.GetService(); + + public GameLauncherPage() { this.InitializeComponent(); } + + [NotifyPropertyChangedFor(nameof(InstalledLocateGameEnabled))] + [ObservableProperty] + public partial GameState GameState { get; set; } + + + public bool StartGameButtonCanExecute { get; set => SetProperty(ref field, value); } = true; + + + + protected override void OnLoaded() + { + CheckGameVersion(); + } + + + + + + [RelayCommand] + private async Task ClickStartGameButtonAsync() + { + StartGameButtonCanExecute = false; + switch (GameState) + { + case GameState.StartGame: + await StartGameAsync(); + break; + case GameState.GameIsRunning: + case GameState.InstallGame: + case GameState.UpdateGame: + case GameState.Downloading: + case GameState.Waiting: + case GameState.Paused: + case GameState.ResumeDownload: + default: + StartGameButtonCanExecute = true; + break; + } + } + + + + + + + #region Game Version + + + public string? GameInstallPath { get; set => SetProperty(ref field, value); } + + + public bool IsInstallPathRemovableTipEnabled { get; set => SetProperty(ref field, value); } + + + public bool InstalledLocateGameEnabled => GameState is GameState.InstallGame && !IsInstallPathRemovableTipEnabled; + + + private Version? localGameVersion; + + + private Version? latestGameVersion; + + + private Version? preInstallGameVersion; + + + private bool isGameExeExists; + + + private bool isPreDownloadOK; + + + + + + + private async void CheckGameVersion() + { + try + { + GameState = GameState.Waiting; + GameInstallPath = _gameLauncherService.GetGameInstallPath(CurrentGameId, out bool storageRemoved); + IsInstallPathRemovableTipEnabled = storageRemoved; + if (GameInstallPath is null || storageRemoved) + { + GameState = GameState.InstallGame; + return; + } + isGameExeExists = await _gameLauncherService.IsGameExeExistsAsync(CurrentGameId); + localGameVersion = await _gameLauncherService.GetLocalGameVersionAsync(CurrentGameId); + if (isGameExeExists && localGameVersion != null) + { + GameState = GameState.StartGame; + } + else + { + GameState = GameState.ResumeDownload; + return; + } + latestGameVersion = await _gamePackageService.GetLatestGameVersionAsync(CurrentGameId); + if (latestGameVersion > localGameVersion) + { + GameState = GameState.UpdateGame; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Check game version"); + } + } + + + + + #endregion + + + + + + + + #region Start Game + + + + + + + + + private async void UpdateGameState() + { + try + { + + } + catch { } + } + + + + + + [RelayCommand] + private async Task StartGameAsync() + { + try + { + await Task.Delay(2000); + var process1 = await _gameLauncherService.StartGameAsync(CurrentGameId); + if (process1 == null) + { + StartGameButtonCanExecute = true; + } + else + { + GameState = GameState.GameIsRunning; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Start game"); + StartGameButtonCanExecute = true; + } + } + + + + + #endregion + + + + + + + + + + + } diff --git a/src/Starward/Features/GameLauncher/GameLauncherService.cs b/src/Starward/Features/GameLauncher/GameLauncherService.cs new file mode 100644 index 000000000..81977c6e7 --- /dev/null +++ b/src/Starward/Features/GameLauncher/GameLauncherService.cs @@ -0,0 +1,259 @@ +using Microsoft.Extensions.Logging; +using Starward.Core; +using Starward.Core.HoYoPlay; +using Starward.Features.HoYoPlay; +using Starward.Frameworks; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Starward.Features.GameLauncher; + +internal class GameLauncherService +{ + + + private readonly ILogger _logger; + + + private readonly HoYoPlayService _hoYoPlayService; + + + + + public GameLauncherService(ILogger logger, HoYoPlayService hoYoPlayService) + { + _logger = logger; + _hoYoPlayService = hoYoPlayService; + } + + + + + + /// + /// 游戏安装目录,为空时未找到 + /// + /// + /// + public string? GetGameInstallPath(GameId gameId) + { + var path = AppSetting.GetGameInstallPath(gameId.GameBiz); + if (Directory.Exists(path)) + { + return Path.GetFullPath(path); + } + else if (!string.IsNullOrWhiteSpace(path) && AppSetting.GetGameInstallPathRemovable(gameId.GameBiz)) + { + return path; + } + else + { + AppSetting.SetGameInstallPath(gameId.GameBiz, null); + AppSetting.SetGameInstallPathRemovable(gameId.GameBiz, false); + return null; + } + } + + + + /// + /// 游戏安装目录,为空时未找到 + /// + /// + /// 可移动存储设备已移除 + /// + public string? GetGameInstallPath(GameId gameId, out bool storageRemoved) + { + storageRemoved = false; + var path = AppSetting.GetGameInstallPath(gameId.GameBiz); + if (Directory.Exists(path)) + { + return Path.GetFullPath(path); + } + else if (!string.IsNullOrWhiteSpace(path) && AppSetting.GetGameInstallPathRemovable(gameId.GameBiz)) + { + storageRemoved = true; + return path; + } + else + { + AppSetting.SetGameInstallPath(gameId.GameBiz, null); + AppSetting.SetGameInstallPathRemovable(gameId.GameBiz, false); + return null; + } + } + + + + + + /// + /// 本地游戏版本 + /// + /// + /// + public async Task GetLocalGameVersionAsync(GameId gameId, string? installPath = null) + { + installPath ??= GetGameInstallPath(gameId); + if (string.IsNullOrWhiteSpace(installPath)) + { + return null; + } + var config = Path.Join(installPath, "config.ini"); + if (File.Exists(config)) + { + var str = await File.ReadAllTextAsync(config); + _ = Version.TryParse(Regex.Match(str, @"game_version=(.+)").Groups[1].Value, out Version? version); + return version; + } + else + { + _logger.LogWarning("config.ini not found: {path}", config); + return null; + } + } + + + + + + /// + /// 游戏进程名,带 .exe 扩展名 + /// + /// + /// + public async Task GetGameExeNameAsync(GameId gameId) + { + string? name = gameId.GameBiz.Value switch + { + GameBiz.hk4e_cn or GameBiz.hk4e_bilibili => "YuanShen.exe", + GameBiz.hk4e_global => "GenshinImpact.exe", + _ => gameId.GameBiz.Game switch + { + GameBiz.hkrpg => "StarRail.exe", + GameBiz.bh3 => "BH3.exe", + GameBiz.nap => "ZenlessZoneZero.exe", + _ => null, + }, + }; + if (string.IsNullOrWhiteSpace(name)) + { + var config = await _hoYoPlayService.GetGameConfigAsync(gameId); + name = config?.ExeFileName; + } + return name ?? throw new ArgumentOutOfRangeException($"Unknown game ({gameId.Id}, {gameId.GameBiz})."); + } + + + + /// + /// 游戏进程文件是否存在 + /// + /// + /// + /// + public async Task IsGameExeExistsAsync(GameId gameId, string? installPath = null) + { + installPath ??= GetGameInstallPath(gameId); + if (!string.IsNullOrWhiteSpace(installPath)) + { + var exe = Path.Join(installPath, await GetGameExeNameAsync(gameId)); + return File.Exists(exe); + } + return false; + } + + + + /// + /// 获取游戏进程 + /// + /// + /// + public async Task GetGameProcessAsync(GameId gameId) + { + int currentSessionId = Process.GetCurrentProcess().SessionId; + var name = (await GetGameExeNameAsync(gameId)).Replace(".exe", ""); + return Process.GetProcessesByName(name).Where(x => x.SessionId == currentSessionId).FirstOrDefault(); + } + + + + /// + /// 启动游戏 + /// + /// + public async Task StartGameAsync(GameId gameId, bool ignoreRunningGame = false, string? installPath = null) + { + const int ERROR_CANCELLED = 0x000004C7; + try + { + if (!ignoreRunningGame) + { + if (await GetGameProcessAsync(gameId) != null) + { + throw new Exception("Game process is running."); + } + } + string? exe = null, arg = null, verb = null; + if (Directory.Exists(installPath)) + { + var e = Path.Join(installPath, await GetGameExeNameAsync(gameId)); + if (File.Exists(e)) + { + exe = e; + } + } + if (string.IsNullOrWhiteSpace(exe) && AppSetting.GetEnableThirdPartyTool(gameId.GameBiz)) + { + exe = AppSetting.GetThirdPartyToolPath(gameId.GameBiz); + if (File.Exists(exe)) + { + verb = Path.GetExtension(exe) is ".exe" or ".bat" ? "runas" : ""; + } + else + { + exe = null; + AppSetting.SetThirdPartyToolPath(gameId.GameBiz, null); + _logger.LogWarning("Third party tool not found: {path}", exe); + } + } + if (string.IsNullOrWhiteSpace(exe)) + { + var folder = GetGameInstallPath(gameId); + var name = await GetGameExeNameAsync(gameId); + exe = Path.Join(folder, name); + arg = AppSetting.GetStartArgument(gameId.GameBiz)?.Trim(); + verb = "runas"; + if (!File.Exists(exe)) + { + _logger.LogWarning("Game exe not found: {path}", exe); + throw new FileNotFoundException("Game exe not found", name); + } + } + _logger.LogInformation("Start game ({biz})\r\npath: {exe}\r\narg: {arg}", gameId, exe, arg); + var info = new ProcessStartInfo + { + FileName = exe, + Arguments = arg, + UseShellExecute = true, + Verb = verb, + WorkingDirectory = Path.GetDirectoryName(exe), + }; + return Process.Start(info); + } + catch (Win32Exception ex) when (ex.NativeErrorCode == ERROR_CANCELLED) + { + // Operation canceled + _logger.LogInformation("Start game operation canceled."); + } + return null; + } + + +} diff --git a/src/Starward/Features/GameLauncher/GamePackageService.cs b/src/Starward/Features/GameLauncher/GamePackageService.cs new file mode 100644 index 000000000..4c9d576cd --- /dev/null +++ b/src/Starward/Features/GameLauncher/GamePackageService.cs @@ -0,0 +1,263 @@ +using Microsoft.Extensions.Logging; +using Starward.Core; +using Starward.Core.HoYoPlay; +using Starward.Features.HoYoPlay; +using Starward.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Starward.Features.GameLauncher; + +internal class GamePackageService +{ + + + private readonly ILogger _logger; + + private readonly HoYoPlayService _hoYoPlayService; + + private readonly GameLauncherService _gameLauncherService; + + + + public GamePackageService(ILogger logger, HoYoPlayService hoYoPlayService, GameLauncherService gameLauncherService) + { + _logger = logger; + _hoYoPlayService = hoYoPlayService; + _gameLauncherService = gameLauncherService; + } + + + + + + public async Task GetGamePackageAsync(GameId gameId) + { + return await _hoYoPlayService.GetGamePackageAsync(gameId); + } + + + + /// + /// 最新游戏版本 + /// + /// + /// + public async Task GetLatestGameVersionAsync(GameId gameId) + { + var package = await _hoYoPlayService.GetGamePackageAsync(gameId); + _ = Version.TryParse(package.Main.Major?.Version, out Version? version); + return version; + } + + + + + + public async Task CheckPreDownloadIsOKAsync(GameId gameId, string? installPath = null) + { + installPath ??= _gameLauncherService.GetGameInstallPath(gameId); + if (string.IsNullOrWhiteSpace(installPath)) + { + return false; + } + var package = await GetGamePackageAsync(gameId); + if (package.PreDownload?.Major != null) + { + var localVersion = await _gameLauncherService.GetLocalGameVersionAsync(gameId, installPath); + VoiceLanguage language = await GetVoiceLanguageAsync(gameId, installPath); + if (package.PreDownload.Patches?.FirstOrDefault(x => x.Version == localVersion?.ToString()) is GamePackageResource resource) + { + return CheckGamePackageResourceIsDownloadOK(resource, installPath, language); + } + else + { + return CheckGamePackageResourceIsDownloadOK(package.PreDownload.Major, installPath, language); + } + } + return false; + } + + + + + + public async Task GetNeedDownloadGamePackageResourceAsync(GameId gameId, string? installPath = null) + { + installPath ??= _gameLauncherService.GetGameInstallPath(gameId); + Version? localVersion = await _gameLauncherService.GetLocalGameVersionAsync(gameId, installPath); + Version? latestVersion = await GetLatestGameVersionAsync(gameId); + if (latestVersion is null) + { + return null; + } + GamePackage package = await GetGamePackageAsync(gameId); + if (localVersion is null) + { + return package.Main.Major; + } + else if (localVersion < latestVersion) + { + if (package.Main.Patches.FirstOrDefault(x => x.Version == localVersion.ToString()) is GamePackageResource resource) + { + return resource; + } + else + { + return package.Main.Major; + } + } + else if (package.PreDownload is not null) + { + if (package.PreDownload.Patches.FirstOrDefault(x => x.Version == localVersion.ToString()) is GamePackageResource resource) + { + return resource; + } + else + { + return package.PreDownload.Major; + } + } + return null; + } + + + + + + private static bool CheckGamePackageResourceIsDownloadOK(GamePackageResource resource, string installPath, VoiceLanguage language) + { + foreach (var item in resource.GamePackages) + { + string file = Path.Combine(installPath, Path.GetFileName(item.Url)); + if (!File.Exists(file)) + { + return false; + } + } + foreach (var lang in Enum.GetValues()) + { + if (language.HasFlag(lang)) + { + if (resource.AudioPackages.FirstOrDefault(x => x.Language == lang.ToDescription()) is GamePackageFile packageFile) + { + string file = Path.Combine(installPath, Path.GetFileName(packageFile.Url)); + if (!File.Exists(file)) + { + return false; + } + } + } + } + return true; + } + + + + + private static long GetFileDownloadedLength(string file) + { + if (File.Exists(file)) + { + return new FileInfo(file).Length; + } + else if (File.Exists(file + "_tmp")) + { + return new FileInfo(file + "_tmp").Length; + } + return 0; + } + + + + + public async Task GetVoiceLanguageAsync(GameId gameId, string? installPath = null) + { + if (gameId.GameBiz.Game == GameBiz.bh3) + { + return VoiceLanguage.None; + } + installPath ??= _gameLauncherService.GetGameInstallPath(gameId); + if (string.IsNullOrWhiteSpace(installPath)) + { + return VoiceLanguage.None; + } + VoiceLanguage flag = VoiceLanguage.None; + var config = await _hoYoPlayService.GetGameConfigAsync(gameId); + if (!string.IsNullOrWhiteSpace(config?.AudioPackageScanDir)) + { + string file = Path.Join(installPath, config.AudioPackageScanDir); + if (File.Exists(file)) + { + var lines = await File.ReadAllLinesAsync(file); + if (lines.Any(x => x.Contains("Chinese"))) { flag |= VoiceLanguage.Chinese; } + if (lines.Any(x => x.Contains("English(US)"))) { flag |= VoiceLanguage.English; } + if (lines.Any(x => x.Contains("Japanese"))) { flag |= VoiceLanguage.Japanese; } + if (lines.Any(x => x.Contains("Korean"))) { flag |= VoiceLanguage.Korean; } + } + } + return flag; + } + + + + + public async Task SetVoiceLanguageAsync(GameId gameId, string installPath, VoiceLanguage lang) + { + if (gameId.GameBiz.Game == GameBiz.bh3) + { + return; + } + var config = await _hoYoPlayService.GetGameConfigAsync(gameId); + if (!string.IsNullOrWhiteSpace(config?.AudioPackageScanDir)) + { + string file = Path.Join(installPath, config.AudioPackageScanDir); + Directory.CreateDirectory(Path.GetDirectoryName(file)!); + var lines = new List(4); + if (lang.HasFlag(VoiceLanguage.Chinese)) { lines.Add("Chinese"); } + if (lang.HasFlag(VoiceLanguage.English)) { lines.Add("English(US)"); } + if (lang.HasFlag(VoiceLanguage.Japanese)) { lines.Add("Japanese"); } + if (lang.HasFlag(VoiceLanguage.Korean)) { lines.Add("Korean"); } + await File.WriteAllLinesAsync(file, lines); + } + } + + + + + public DownloadGameResource GetDownloadGameResourceAsync(GamePackageResource resource, string installPath) + { + var downloadResource = new DownloadGameResource + { + Game = new DownloadPackageState + { + Name = resource.Version, + Url = resource.GamePackages[0].Url, + PackageSize = resource.GamePackages.Sum(x => x.Size), + DecompressedSize = resource.GamePackages.Sum(x => x.DecompressedSize), + DownloadedSize = resource.GamePackages.Sum(x => GetFileDownloadedLength(Path.Combine(installPath, Path.GetFileName(x.Url)))), + }, + FreeSpace = new DriveInfo(Path.GetFullPath(installPath)).AvailableFreeSpace, + }; + foreach (var item in resource.AudioPackages) + { + downloadResource.Voices.Add(new DownloadPackageState + { + Name = item.Language!, + Url = item.Url, + PackageSize = item.Size, + DecompressedSize = item.DecompressedSize, + DownloadedSize = GetFileDownloadedLength(Path.Combine(installPath, Path.GetFileName(item.Url))), + }); + } + return downloadResource; + } + + + + + +} diff --git a/src/Starward/Features/GameLauncher/GameState.cs b/src/Starward/Features/GameLauncher/GameState.cs new file mode 100644 index 000000000..0b6a2e865 --- /dev/null +++ b/src/Starward/Features/GameLauncher/GameState.cs @@ -0,0 +1,32 @@ +namespace Starward.Features.GameLauncher; + +public enum GameState +{ + + None, + + + StartGame, + + + GameIsRunning, + + + InstallGame, + + + UpdateGame, + + + Downloading, + + + Waiting, + + + Paused, + + + ResumeDownload, + +} \ No newline at end of file diff --git a/src/Starward/Features/GameLauncher/StartGameButton.xaml b/src/Starward/Features/GameLauncher/StartGameButton.xaml new file mode 100644 index 000000000..442148eee --- /dev/null +++ b/src/Starward/Features/GameLauncher/StartGameButton.xaml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Starward/Features/GameLauncher/StartGameButton.xaml.cs b/src/Starward/Features/GameLauncher/StartGameButton.xaml.cs new file mode 100644 index 000000000..dd520f28c --- /dev/null +++ b/src/Starward/Features/GameLauncher/StartGameButton.xaml.cs @@ -0,0 +1,152 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using System.Windows.Input; + + +namespace Starward.Features.GameLauncher; + +[INotifyPropertyChanged] +public sealed partial class StartGameButton : UserControl +{ + + + private static Brush AccentFillColorDefaultBrush => (Brush)Application.Current.Resources["AccentFillColorDefaultBrush"]; + private static Brush TextOnAccentFillColorDisabled => (Brush)Application.Current.Resources["TextOnAccentFillColorDisabledBrush"]; + private static Brush TextOnAccentFillColorPrimaryBrush => (Brush)Application.Current.Resources["TextOnAccentFillColorPrimaryBrush"]; + + + public StartGameButton() + { + this.InitializeComponent(); + this.ActualThemeChanged += StartGameButton_ActualThemeChanged; + } + + + + public GameState State + { + get; + set + { + SetProperty(ref field, value); + UpdateButtonState(); + } + } + + + public bool PointerOver + { + get; + set + { + SetProperty(ref field, value); + UpdateButtonState(); + } + } + + + public bool CanExecute + { + get; + set + { + SetProperty(ref field, value); + UpdateButtonState(); + } + } = true; + + + + + public bool TextBlock_StartGame_Visibility => State is GameState.StartGame; + public bool TextBlock_GameIsRunning_Visibility => State is GameState.GameIsRunning; + public bool TextBlock_InstallGame_Visibility => State is GameState.InstallGame; + public bool TextBlock_UpdateGame_Visibility => State is GameState.UpdateGame; + public bool TextBlock_ResumeDownload_Visibility => State is GameState.ResumeDownload or GameState.Paused; + public double TextBlock_ResumeDownload_Opacity => (State is GameState.ResumeDownload || PointerOver) ? 1 : 0; + public bool TextBlock_Waiting_Visibility => State is GameState.Waiting; + public bool TextBlock_Pause_Visibility => State is GameState.Downloading && PointerOver; + public double TextBlock_Pause_Opacity => TextBlock_Pause_Visibility ? 1 : 0; + public bool TextBlock_Paused_Visibility => State is GameState.Paused && !PointerOver; + + + public bool Rect_AccentBackground_Visibility => CanExecute && !(State is GameState.GameIsRunning or GameState.Downloading or GameState.Paused); + + public bool ProgressRing_IndeterminateLoading_Visibility => (State is GameState.StartGame or GameState.InstallGame or GameState.UpdateGame or GameState.ResumeDownload or GameState.Paused) && !CanExecute; + + + + + public ICommand Command { get; set => SetProperty(ref field, value); } + + + public ICommand SettingCommand { get; set => SetProperty(ref field, value); } + + + + + + private void UpdateButtonState() + { + OnPropertyChanged(nameof(TextBlock_StartGame_Visibility)); + OnPropertyChanged(nameof(TextBlock_StartGame_Visibility)); + OnPropertyChanged(nameof(TextBlock_GameIsRunning_Visibility)); + OnPropertyChanged(nameof(TextBlock_InstallGame_Visibility)); + OnPropertyChanged(nameof(TextBlock_UpdateGame_Visibility)); + OnPropertyChanged(nameof(TextBlock_ResumeDownload_Visibility)); + OnPropertyChanged(nameof(TextBlock_ResumeDownload_Opacity)); + OnPropertyChanged(nameof(TextBlock_Waiting_Visibility)); + OnPropertyChanged(nameof(TextBlock_Pause_Visibility)); + OnPropertyChanged(nameof(TextBlock_Pause_Opacity)); + OnPropertyChanged(nameof(TextBlock_Paused_Visibility)); + OnPropertyChanged(nameof(Rect_AccentBackground_Visibility)); + OnPropertyChanged(nameof(ProgressRing_IndeterminateLoading_Visibility)); + + Button_GameAction.Foreground = (CanExecute, Rect_AccentBackground_Visibility, PointerOver) switch + { + (false, _, _) => TextOnAccentFillColorDisabled, + (true, false, true) => AccentFillColorDefaultBrush, + (true, false, false) => TextOnAccentFillColorDisabled, + _ => TextOnAccentFillColorPrimaryBrush + }; + Button_Setting.Foreground = (Rect_AccentBackground_Visibility, PointerOver) switch + { + (false, true) => AccentFillColorDefaultBrush, + (false, false) => TextOnAccentFillColorDisabled, + _ => TextOnAccentFillColorPrimaryBrush + }; + } + + + + private void Grid_Root_PointerEntered(object sender, PointerRoutedEventArgs e) + { + PointerOver = true; + if (State is GameState.Downloading) + { + FlyoutBase.ShowAttachedFlyout(Grid_Root); + } + } + + + + private void Grid_Root_PointerExited(object sender, PointerRoutedEventArgs e) + { + PointerOver = false; + Flyout_DownloadProgress.Hide(); + } + + + + private void StartGameButton_ActualThemeChanged(FrameworkElement sender, object args) + { + UpdateButtonState(); + } + + + +} diff --git a/src/Starward/Features/GameSelector/GameSelector.xaml b/src/Starward/Features/GameSelector/GameSelector.xaml index 6b13c5ec2..aca916c6e 100644 --- a/src/Starward/Features/GameSelector/GameSelector.xaml +++ b/src/Starward/Features/GameSelector/GameSelector.xaml @@ -97,6 +97,7 @@ @@ -105,9 +106,12 @@ VerticalAlignment="Center" Background="#60000000" Source="{x:Bind GameInfo.Display.Logo.Url}" /> - + diff --git a/src/Starward/Frameworks/PageBase.cs b/src/Starward/Frameworks/PageBase.cs index ea900b1a6..c753450ae 100644 --- a/src/Starward/Frameworks/PageBase.cs +++ b/src/Starward/Frameworks/PageBase.cs @@ -11,7 +11,7 @@ public abstract partial class PageBase : Page { - public GameId? CurrentGameId { get; protected set => SetProperty(ref field, value); } + public GameId CurrentGameId { get; protected set => SetProperty(ref field, value); } public GameBiz CurrentGameBiz { get; protected set => SetProperty(ref field, value); } @@ -51,7 +51,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) else if (e.Parameter is GameBiz biz) { CurrentGameBiz = biz; - CurrentGameId = GameId.FromGameBiz(biz); + CurrentGameId = GameId.FromGameBiz(biz)!; } }