diff options
| author | Emmanuel Hansen <emmausssss@gmail.com> | 2022-05-15 11:30:15 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-05-15 13:30:15 +0200 |
| commit | deb99d2cae3e80bdf70cb52c6c160094dc7c9292 (patch) | |
| tree | e60f44d1b4bd45bbf36fcfa750fb99787febfdbe /Ryujinx.Ava/Ui/Windows | |
| parent | 9ba73ffbe5f78c0403cf102b95768f388da05122 (diff) | |
Avalonia UI - Part 1 (#3270)
* avalonia part 1
* remove vulkan ui backend
* move ui common files to ui common project
* get name for oading screen from device
* rebase.
* review 1
* review 1.1
* review
* cleanup
* addressed review
* use cancellation token
* review
* review
* rebased
* cancel library loading when closing window
* remove star image, use fonticon instead
* delete render control frame buffer when game ends. change position of fav star
* addressed @Thog review
* ensure the right ui is downloaded in updates
* fix crash when showing not supported dialog during controller request
* add prefix to artifact names
* Auto-format Avalonia project
* Fix input
* Fix build, simplify app disposal
* remove nv stutter thread
* addressed review
* add missing change
* maintain window size if new size is zero length
* add game, handheld, docked to local
* reverse scale main window
* Update de_DE.json
* Update de_DE.json
* Update de_DE.json
* Update italian json
* Update it_IT.json
* let render timer poll with no wait
* remove unused code
* more unused code
* enabled tiered compilation and trimming
* check if window event is not closed before signaling
* fix atmospher case
* locale fix
* locale fix
* remove explicit tiered compilation declarations
* Remove ) it_IT.json
* Remove ) de_DE.json
* Update it_IT.json
* Update pt_BR locale with latest strings
* Remove ')'
* add more strings to locale
* update locale
* remove extra slash
* remove extra slash
* set firmware version to 0 if key's not found
* fix
* revert timer changes
* lock on object instead
* Update it_IT.json
* remove unused method
* add load screen text to locale
* drop swap event
* Update de_DE.json
* Update de_DE.json
* do null check when stopping emulator
* Update de_DE.json
* Create tr_TR.json
* Add tr_TR
* Add tr_TR + Turkish
* Update it_IT.json
* Update Ryujinx.Ava/Input/AvaloniaMappingHelper.cs
Co-authored-by: Ac_K <Acoustik666@gmail.com>
* Apply suggestions from code review
Co-authored-by: Ac_K <Acoustik666@gmail.com>
* Apply suggestions from code review
Co-authored-by: Ac_K <Acoustik666@gmail.com>
* addressed review
* Update Ryujinx.Ava/Ui/Backend/OpenGl/OpenGlRenderTarget.cs
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
* use avalonia's inbuilt renderer on linux
* removed whitespace
* workaround for queue render crash with vsync off
* drop custom backend
* format files
* fix not closing issue
* remove warnings
* rebase
* update avalonia library
* Reposition the Text and Button on About Page
* Assign build version
* Remove appveyor text
Co-authored-by: gdk <gab.dark.100@gmail.com>
Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com>
Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com>
Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: MostlyWhat <78652091+MostlyWhat@users.noreply.github.com>
Diffstat (limited to 'Ryujinx.Ava/Ui/Windows')
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/AboutWindow.axaml | 163 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs | 92 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/IconColorPicker.cs | 192 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/MainWindow.axaml | 639 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs | 719 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/StyleableWindow.cs | 47 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/TimeZone.cs | 16 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml | 37 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs | 93 |
9 files changed, 1998 insertions, 0 deletions
diff --git a/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml b/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml new file mode 100644 index 00000000..20185fd2 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml @@ -0,0 +1,163 @@ +<window:StyleableWindow xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350" + x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow" + xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + CanResize="False" + WindowStartupLocation="CenterOwner" + Width="850" MinHeight="550" Height="550" + SizeToContent="Width" + MinWidth="500" + Title="Ryujinx - About"> + <Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="*"/> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <Grid Grid.Row="1" Margin="20" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="0"> + <Grid.RowDefinitions> + <RowDefinition Height="*" /> + <RowDefinition Height="*" /> + <RowDefinition /> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid Grid.Row="0"> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Margin="5, 10, 20 , 10" + Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="110" MinWidth="50" /> + <TextBlock FontSize="35" TextAlignment="Center" Grid.Row="0" Grid.Column="1" Text="Ryujinx" + Margin="0,20,0,0" /> + <TextBlock FontSize="16" TextAlignment="Center" Grid.Row="1" Grid.Column="1" Text="(REE-YOU-JINX)" + Margin="0,0,0,0" /> + <Button Grid.Column="1" Background="Transparent" HorizontalAlignment="Center" Margin="0" Grid.Row="2" + Tag="https://www.ryujinx.org/" + Click="Button_OnClick"> + <TextBlock ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" + TextAlignment="Center" TextDecorations="Underline" Text="www.ryujinx.org" /> + </Button> + </Grid> + <TextBlock TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" + Text="{Binding Version}" Grid.Row="1" /> + <TextBlock Grid.Row="2" TextAlignment="Center" HorizontalAlignment="Center" Margin="20" + Text="{locale:Locale AboutDisclaimerMessage}" + MaxLines="2" /> + <TextBlock Grid.Row="3" TextAlignment="Center" HorizontalAlignment="Center" Margin="20" + Text="{locale:Locale AboutAmiiboDisclaimerMessage}" + Name="AmiiboLabel" + PointerPressed="AmiiboLabel_OnPointerPressed" + MaxLines="2" /> + <StackPanel Spacing="10" Orientation="Horizontal" Grid.Row="4" HorizontalAlignment="Center"> + <StackPanel Orientation="Vertical" + ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}"> + <Button Height="65" Background="Transparent" Tag="https://www.patreon.com/ryujinx" + Click="Button_OnClick"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" /> + <TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Patreon" HorizontalAlignment="Center" /> + </Grid> + </Button> + </StackPanel> + <StackPanel Orientation="Vertical" + ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}"> + <Button Height="65" Background="Transparent" Tag="https://github.com/Ryujinx/Ryujinx" + Click="Button_OnClick"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" /> + <TextBlock Grid.Row="1" Margin="0,5,0,0" Text="GitHub" HorizontalAlignment="Center" /> + </Grid> + </Button> + </StackPanel> + <StackPanel Orientation="Vertical" + ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}"> + <Button Height="65" Background="Transparent" Tag="https://discordapp.com/invite/N2FmfVc" + Click="Button_OnClick"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" /> + <TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Discord" HorizontalAlignment="Center" /> + </Grid> + </Button> + </StackPanel> + <StackPanel Orientation="Vertical" + ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}"> + <Button Height="65" Background="Transparent" Tag="https://twitter.com/RyujinxEmu" + Click="Button_OnClick"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" /> + <TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Twitter" HorizontalAlignment="Center" /> + </Grid> + </Button> + </StackPanel> + </StackPanel> + </Grid> + <Border Grid.Row="1" Grid.Column="1" VerticalAlignment="Stretch" Margin="5" Width="2" BorderBrush="White" + BorderThickness="1,0,0,0"> + <Separator Width="0" /> + </Border> + <Grid Grid.Row="1" Margin="20" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="2"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <TextBlock Text="{locale:Locale AboutRyujinxAboutTitle}" FontWeight="Bold" TextDecorations="Underline" /> + <TextBlock LineHeight="20" Grid.Row="1" Margin="20,5,5,5" + Text="{locale:Locale AboutRyujinxAboutContent}" /> + <TextBlock Grid.Row="2" Margin="0,10,0,0" Text="{locale:Locale AboutRyujinxMaintainersTitle}" + FontWeight="Bold" + TextDecorations="Underline" /> + <TextBlock LineHeight="20" Grid.Row="3" Margin="20,5,5,5" + Text="{Binding Developers}" /> + <Button Background="Transparent" HorizontalAlignment="Right" Grid.Row="4" + Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a" Click="Button_OnClick"> + <TextBlock ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" + TextAlignment="Right" TextDecorations="Underline" + Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" /> + </Button> + <TextBlock Grid.Row="5" Margin="0,0,0,0" Text="{locale:Locale AboutRyujinxSupprtersTitle}" + FontWeight="Bold" + TextDecorations="Underline" /> + <Border Width="460" Grid.Row="6" VerticalAlignment="Stretch" Height="200" BorderThickness="1" Margin="20,5" + BorderBrush="White" Padding="5"> + <TextBlock TextWrapping="Wrap" VerticalAlignment="Top" Name="SupportersTextBlock" + Text="{Binding Supporters}" /> + </Border> + </Grid> + </Grid> +</window:StyleableWindow> diff --git a/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs new file mode 100644 index 00000000..3f93ccb7 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs @@ -0,0 +1,92 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Common.Utilities; +using Ryujinx.Ui.Common.Helper; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class AboutWindow : StyleableWindow + { + public AboutWindow() + { + if (Program.PreviewerDetached) + { + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["MenuBarHelpAbout"]; + } + + Version = Program.Version; + + DataContext = this; + + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + + _ = DownloadPatronsJson(); + } + + public string Supporters { get; set; } + public string Version { get; set; } + + public string Developers => string.Format(LocaleManager.Instance["AboutPageDeveloperListMore"], "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD«"); + + public TextBlock SupportersTextBlock { get; set; } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + + SupportersTextBlock = this.FindControl<TextBlock>("SupportersTextBlock"); + } + + private void Button_OnClick(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + OpenHelper.OpenUrl(button.Tag.ToString()); + } + } + + private async Task DownloadPatronsJson() + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + Supporters = LocaleManager.Instance["ConnectionError"]; + + return; + } + + HttpClient httpClient = new(); + + try + { + string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); + + Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString)); + } + catch + { + Supporters = LocaleManager.Instance["ApiError"]; + } + + await Dispatcher.UIThread.InvokeAsync(() => SupportersTextBlock.Text = Supporters); + } + + private void AmiiboLabel_OnPointerPressed(object sender, PointerPressedEventArgs e) + { + if (sender is TextBlock) + { + OpenHelper.OpenUrl("https://amiiboapi.com"); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/IconColorPicker.cs b/Ryujinx.Ava/Ui/Windows/IconColorPicker.cs new file mode 100644 index 00000000..7cf4c2ed --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/IconColorPicker.cs @@ -0,0 +1,192 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.Ui.Windows +{ + static class IconColorPicker + { + private const int ColorsPerLine = 64; + private const int TotalColors = ColorsPerLine * ColorsPerLine; + + private const int UvQuantBits = 3; + private const int UvQuantShift = BitsPerComponent - UvQuantBits; + + private const int SatQuantBits = 5; + private const int SatQuantShift = BitsPerComponent - SatQuantBits; + + private const int BitsPerComponent = 8; + + private const int CutOffLuminosity = 64; + + private struct PaletteColor + { + public int Qck { get; } + public byte R { get; } + public byte G { get; } + public byte B { get; } + + public PaletteColor(int qck, byte r, byte g, byte b) + { + Qck = qck; + R = r; + G = g; + B = b; + } + } + + public static Color GetFilteredColor(Image<Bgra32> image) + { + var color = GetColor(image).ToPixel<Bgra32>(); + + // We don't want colors that are too dark. + // If the color is too dark, make it brighter by reducing the range + // and adding a constant color. + int luminosity = GetColorApproximateLuminosity(color.R, color.G, color.B); + if (luminosity < CutOffLuminosity) + { + color = Color.FromRgb( + (byte)Math.Min(CutOffLuminosity + color.R, byte.MaxValue), + (byte)Math.Min(CutOffLuminosity + color.G, byte.MaxValue), + (byte)Math.Min(CutOffLuminosity + color.B, byte.MaxValue)); + } + + return color; + } + + public static Color GetColor(Image<Bgra32> image) + { + var colors = new PaletteColor[TotalColors]; + + var dominantColorBin = new Dictionary<int, int>(); + + var buffer = GetBuffer(image); + + int w = image.Width; + + int w8 = w << 8; + int h8 = image.Height << 8; + + int xStep = w8 / ColorsPerLine; + int yStep = h8 / ColorsPerLine; + + int i = 0; + int maxHitCount = 0; + + for (int y = 0; y < image.Height; y++) + { + int yOffset = y * image.Width; + + for (int x = 0; x < image.Width && i < TotalColors; x++) + { + int offset = x + yOffset; + + byte cb = buffer[offset].B; + byte cg = buffer[offset].G; + byte cr = buffer[offset].R; + + var qck = GetQuantizedColorKey(cr, cg, cb); + + if (dominantColorBin.TryGetValue(qck, out int hitCount)) + { + dominantColorBin[qck] = hitCount + 1; + + if (maxHitCount < hitCount) + { + maxHitCount = hitCount; + } + } + else + { + dominantColorBin.Add(qck, 1); + } + + colors[i++] = new PaletteColor(qck, cr, cg, cb); + } + } + + int highScore = -1; + PaletteColor bestCandidate = default; + + for (i = 0; i < TotalColors; i++) + { + var score = GetColorScore(dominantColorBin, maxHitCount, colors[i]); + + if (highScore < score) + { + highScore = score; + bestCandidate = colors[i]; + } + } + + return Color.FromRgb(bestCandidate.R, bestCandidate.G, bestCandidate.B); + } + + public static Bgra32[] GetBuffer(Image<Bgra32> image) + { + return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : new Bgra32[0]; + } + + private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color) + { + var hitCount = dominantColorBin[color.Qck]; + var balancedHitCount = BalanceHitCount(hitCount, maxHitCount); + var quantSat = (GetColorSaturation(color) >> SatQuantShift) << SatQuantShift; + var value = GetColorValue(color); + + // If the color is rarely used on the image, + // then chances are that theres a better candidate, even if the saturation value + // is high. By multiplying the saturation value with a weight, we can lower + // it if the color is almost never used (hit count is low). + var satWeighted = quantSat; + var satWeight = balancedHitCount << 5; + if (satWeight < 0x100) + { + satWeighted = (satWeighted * satWeight) >> 8; + } + + // Compute score from saturation and dominance of the color. + // We prefer more vivid colors over dominant ones, so give more weight to the saturation. + var score = ((satWeighted << 1) + balancedHitCount) * value; + + return score; + } + + private static int BalanceHitCount(int hitCount, int maxHitCount) + { + return (hitCount << 8) / maxHitCount; + } + + private static int GetColorApproximateLuminosity(byte r, byte g, byte b) + { + return (r + g + b) / 3; + } + + private static int GetColorSaturation(PaletteColor color) + { + int cMax = Math.Max(Math.Max(color.R, color.G), color.B); + + if (cMax == 0) + { + return 0; + } + + int cMin = Math.Min(Math.Min(color.R, color.G), color.B); + int delta = cMax - cMin; + return (delta << 8) / cMax; + } + + private static int GetColorValue(PaletteColor color) + { + return Math.Max(Math.Max(color.R, color.G), color.B); + } + + private static int GetQuantizedColorKey(byte r, byte g, byte b) + { + int u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; + int v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128; + return (v >> UvQuantShift) | ((u >> UvQuantShift) << UvQuantBits); + } + } +} diff --git a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml new file mode 100644 index 00000000..642f241c --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml @@ -0,0 +1,639 @@ +<window:StyleableWindow + x:Class="Ryujinx.Ava.Ui.Windows.MainWindow" + xmlns="https://github.com/avaloniaui" + xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" + xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels" + xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + Title="Ryujinx" + Height="785" + Width="1280" + d:DesignHeight="720" + d:DesignWidth="1280" + MinWidth="1024" + MinHeight="680" + WindowStartupLocation="CenterScreen" + x:CompileBindings="True" + x:DataType="viewModels:MainWindowViewModel" + mc:Ignorable="d"> + <Window.Styles> + <Style Selector="TitleBar:fullscreen"> + <Setter Property="Background" Value="#000000" /> + </Style> + </Window.Styles> + <Design.DataContext> + <viewModels:MainWindowViewModel /> + </Design.DataContext> + <Window.Resources> + <controls:BitmapArrayValueConverter x:Key="ByteImage" /> + </Window.Resources> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" /> + <ContentControl Grid.Row="1" + Focusable="False" + IsVisible="False" + KeyboardNavigation.IsTabStop="False"> + <ui:ContentDialog Name="ContentDialog" + KeyboardNavigation.IsTabStop="False" + IsPrimaryButtonEnabled="True" + IsSecondaryButtonEnabled="True" + IsVisible="True" /> + </ContentControl> + <StackPanel IsVisible="False" Grid.Row="0"> + <controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" /> + <controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" /> + <controls:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" /> + <controls:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" /> + </StackPanel> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <StackPanel + Grid.Row="0" + Margin="0" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + IsVisible="{Binding ShowMenuAndStatusBar}" + Orientation="Vertical"> + <DockPanel HorizontalAlignment="Stretch"> + <Menu + Name="Menu" + Margin="0" + Height="35" + HorizontalAlignment="Left"> + <Menu.ItemsPanel> + <ItemsPanelTemplate> + <DockPanel HorizontalAlignment="Stretch" Margin="0" /> + </ItemsPanelTemplate> + </Menu.ItemsPanel> + <MenuItem + VerticalAlignment="Center" + Header="{locale:Locale MenuBarFile}"> + <MenuItem IsEnabled="{Binding EnableNonGameRunningControls}" + Command="{ReflectionBinding OpenFile}" + Header="{locale:Locale MenuBarFileOpenFromFile}" + ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" /> + <MenuItem IsEnabled="{Binding EnableNonGameRunningControls}" + Command="{ReflectionBinding OpenFolder}" + Header="{locale:Locale MenuBarFileOpenUnpacked}" + ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" /> + <MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" + IsEnabled="{Binding IsAppletMenuActive}"> + <MenuItem Command="{ReflectionBinding OpenMiiApplet}" Header="Mii Edit Applet" + ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" /> + </MenuItem> + <Separator /> + <MenuItem Command="{ReflectionBinding OpenRyujinxFolder}" + Header="{locale:Locale MenuBarFileOpenEmuFolder}" + ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" /> + <MenuItem Command="{ReflectionBinding OpenLogsFolder}" + Header="{locale:Locale MenuBarFileOpenLogsFolder}" + ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" /> + <Separator /> + <MenuItem Command="{ReflectionBinding CloseWindow}" + Header="{locale:Locale MenuBarFileExit}" + ToolTip.Tip="{locale:Locale ExitTooltip}" /> + </MenuItem> + <MenuItem + VerticalAlignment="Center" + Header="{locale:Locale MenuBarOptions}"> + <MenuItem Command="{ReflectionBinding ToggleFullscreen}" + Header="{locale:Locale MenuBarOptionsToggleFullscreen}" InputGesture="F11" /> + <MenuItem Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"> + <MenuItem.Icon> + <CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" /> + </MenuItem.Icon> + </MenuItem> + <MenuItem Header="{locale:Locale MenuBarOptionsShowConsole}"> + <MenuItem.Icon> + <CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}" /> + </MenuItem.Icon> + </MenuItem> + <Separator /> + <MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}"> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="en_US" + Header="American English" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="pt_BR" + Header="Brazilian Portuguese" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="es_ES" + Header="Castilian Spanish" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="fr_FR" + Header="French" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="de_DE" + Header="German" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="el_GR" + Header="Greek" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="it_IT" + Header="Italian" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ko_KR" + Header="Korean" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ru_RU" + Header="Russian" /> + <MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="tr_TR" + Header="Turkish" /> + </MenuItem> + <Separator /> + <MenuItem Command="{ReflectionBinding OpenSettings}" + Header="{locale:Locale MenuBarOptionsSettings}" + ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" /> + <MenuItem Command="{ReflectionBinding ManageProfiles}" + IsEnabled="{Binding EnableNonGameRunningControls}" + Header="{locale:Locale MenuBarOptionsManageUserProfiles}" + ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" /> + </MenuItem> + <MenuItem + VerticalAlignment="Center" + Header="{locale:Locale MenuBarActions}" + Name="ActionsMenuItem" + IsEnabled="{Binding IsGameRunning}"> + <MenuItem + Click="PauseEmulation_Click" + Header="{locale:Locale MenuBarOptionsPauseEmulation}" + IsEnabled="{Binding !IsPaused}" + IsVisible="{Binding !IsPaused}" + InputGesture="{Binding PauseKey}" /> + <MenuItem + Click="ResumeEmulation_Click" + Header="{locale:Locale MenuBarOptionsResumeEmulation}" + IsEnabled="{Binding IsPaused}" + IsVisible="{Binding IsPaused}" + InputGesture="{Binding PauseKey}" /> + <MenuItem + Click="StopEmulation_Click" + Header="{locale:Locale MenuBarOptionsStopEmulation}" + ToolTip.Tip="{locale:Locale StopEmulationTooltip}" + IsEnabled="{Binding IsGameRunning}" InputGesture="Escape" /> + <MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" + Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" /> + <Separator /> + <MenuItem + Name="ScanAmiiboMenuItem" + AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree" + Command="{ReflectionBinding OpenAmiiboWindow}" + Header="{locale:Locale MenuBarActionsScanAmiibo}" + IsEnabled="{Binding IsAmiiboRequested}" /> + <MenuItem Command="{ReflectionBinding TakeScreenshot}" + IsEnabled="{Binding IsGameRunning}" + Header="{locale:Locale MenuBarFileToolsTakeScreenshot}" + InputGesture="{Binding ScreenshotKey}" /> + <MenuItem Command="{ReflectionBinding HideUi}" + IsEnabled="{Binding IsGameRunning}" + Header="{locale:Locale MenuBarFileToolsHideUi}" + InputGesture="{Binding ShowUiKey}" /> + <MenuItem Command="{ReflectionBinding OpenCheatManagerForCurrentApp}" + IsEnabled="{Binding IsGameRunning}" + Header="{locale:Locale GameListContextMenuManageCheat}" /> + </MenuItem> + <MenuItem + VerticalAlignment="Center" + Header="{locale:Locale MenuBarTools}"> + <MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" + IsEnabled="{Binding EnableNonGameRunningControls}"> + <MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" + Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" /> + <MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" + Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" /> + </MenuItem> + </MenuItem> + <MenuItem + VerticalAlignment="Center" + Header="{locale:Locale MenuBarHelp}"> + <MenuItem + Name="UpdateMenuItem" + Command="{ReflectionBinding CheckForUpdates}" + Header="{locale:Locale MenuBarHelpCheckForUpdates}" + ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" /> + <Separator /> + <MenuItem Command="{ReflectionBinding OpenAboutWindow}" + Header="{locale:Locale MenuBarHelpAbout}" + ToolTip.Tip="{locale:Locale OpenAboutTooltip}" /> + </MenuItem> + </Menu> + </DockPanel> + </StackPanel> + <ContentControl + Name="Content" + Grid.Row="1" + Padding="0" + IsVisible="{Binding ShowContent}" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + BorderBrush="{DynamicResource ThemeControlBorderColor}" + BorderThickness="0,0,0,0" + DockPanel.Dock="Top"> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <DockPanel Grid.Row="0" HorizontalAlignment="Stretch" Margin="0,0,0,5"> + <Button + IsEnabled="{Binding IsGrid}" VerticalAlignment="Stretch" MinWidth="40" Width="40" + Margin="5,2,0,2" Command="{ReflectionBinding SetListMode}"> + <ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols" + VerticalAlignment="Center" + Margin="0" + Glyph="{controls:GlyphValueConverter List}" + HorizontalAlignment="Stretch" /> + </Button> + <Button + IsEnabled="{Binding IsList}" VerticalAlignment="Stretch" MinWidth="40" Width="40" + Margin="5,2,5,2" Command="{ReflectionBinding SetGridMode}"> + <ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols" + VerticalAlignment="Center" + Margin="0" + Glyph="{controls:GlyphValueConverter Grid}" + HorizontalAlignment="Stretch" /> + </Button> + <TextBlock Text="{locale:Locale IconSize}" + VerticalAlignment="Center" Margin="10,0" + ToolTip.Tip="{locale:Locale IconSizeTooltip}" /> + <Slider Width="150" Margin="5,-10,5 ,0" Height="35" + ToolTip.Tip="{locale:Locale IconSizeTooltip}" + VerticalAlignment="Center" Minimum="1" Maximum="4" IsSnapToTickEnabled="True" + TickFrequency="1" Value="{Binding GridSizeScale}" /> + <CheckBox Margin="0" IsChecked="{Binding ShowNames, Mode=TwoWay}" VerticalAlignment="Center" + IsVisible="{Binding IsGrid}"> + <TextBlock Text="{locale:Locale CommonShowNames}" Margin="5,3,0,0" /> + </CheckBox> + <TextBox + Name="SearchBox" + DockPanel.Dock="Right" + VerticalAlignment="Center" + MinWidth="200" + Margin="5,0,5,0" + HorizontalAlignment="Right" + KeyUp="SearchBox_OnKeyUp" + Text="{Binding SearchText}" + Watermark="{locale:Locale MenuSearch}" /> + <ui:DropDownButton DockPanel.Dock="Right" + HorizontalAlignment="Right" Width="150" VerticalAlignment="Center" + Content="{Binding SortName}"> + <ui:DropDownButton.Flyout> + <Flyout Placement="Bottom"> + <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Margin="0"> + <StackPanel> + <RadioButton Tag="Favorite" + IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}" + GroupName="Sort" + Content="{locale:Locale CommonFavorite}" + Checked="Sort_Checked" /> + <RadioButton Tag="Title" GroupName="Sort" + IsChecked="{Binding IsSortedByTitle, Mode=OneTime}" + Content="{locale:Locale GameListHeaderApplication}" + Checked="Sort_Checked" /> + <RadioButton Tag="Developer" GroupName="Sort" + IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}" + Content="{locale:Locale GameListHeaderDeveloper}" + Checked="Sort_Checked" /> + <RadioButton Tag="TotalTimePlayed" GroupName="Sort" + IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}" + Content="{locale:Locale GameListHeaderTimePlayed}" + Checked="Sort_Checked" /> + <RadioButton Tag="LastPlayed" GroupName="Sort" + IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}" + Content="{locale:Locale GameListHeaderLastPlayed}" + Checked="Sort_Checked" /> + <RadioButton Tag="FileType" GroupName="Sort" + IsChecked="{Binding IsSortedByType, Mode=OneTime}" + Content="{locale:Locale GameListHeaderFileExtension}" + Checked="Sort_Checked" /> + <RadioButton Tag="FileSize" GroupName="Sort" + IsChecked="{Binding IsSortedBySize, Mode=OneTime}" + Content="{locale:Locale GameListHeaderFileSize}" + Checked="Sort_Checked" /> + <RadioButton Tag="Path" GroupName="Sort" + IsChecked="{Binding IsSortedByPath, Mode=OneTime}" + Content="{locale:Locale GameListHeaderPath}" + Checked="Sort_Checked" /> + </StackPanel> + <Border HorizontalAlignment="Stretch" Margin="5" Height="2" BorderBrush="White" + Width="60" BorderThickness="0,1,0,0"> + <Separator HorizontalAlignment="Stretch" Height="0" /> + </Border> + <RadioButton Tag="Ascending" IsChecked="{Binding IsAscending, Mode=OneTime}" + GroupName="Order" + Content="{locale:Locale OrderAscending}" Checked="Order_Checked" /> + <RadioButton Tag="Descending" GroupName="Order" + IsChecked="{Binding !IsAscending, Mode=OneTime}" + Content="{locale:Locale OrderDescending}" Checked="Order_Checked" /> + </StackPanel> + </Flyout> + </ui:DropDownButton.Flyout> + </ui:DropDownButton> + <TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right" + Text="{locale:Locale CommonSort}" VerticalAlignment="Center" Margin="10,0" /> + </DockPanel> + <controls:GameListView + x:Name="GameList" + Grid.Row="1" + HorizontalContentAlignment="Stretch" + HorizontalAlignment="Stretch" + VerticalContentAlignment="Stretch" + VerticalAlignment="Stretch" + IsVisible="{Binding IsList}" /> + <controls:GameGridView + x:Name="GameGrid" + Grid.Row="1" + HorizontalContentAlignment="Stretch" + HorizontalAlignment="Stretch" + VerticalContentAlignment="Stretch" + VerticalAlignment="Stretch" + IsVisible="{Binding IsGrid}" /> + </Grid> + </ContentControl> + <Grid Grid.Row="1" + VerticalAlignment="Stretch" + Background="{DynamicResource ThemeContentBackgroundColor}" + IsVisible="{Binding ShowLoadProgress}" + ZIndex="1000" + HorizontalAlignment="Stretch"> + <Grid + HorizontalAlignment="Center" + IsVisible="{Binding ShowLoadProgress}" + Margin="40" + VerticalAlignment="Center"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <Border + Grid.Column="0" + Grid.RowSpan="2" + IsVisible="{Binding ShowLoadProgress}" + Width="256" + Height="256" + Margin="10" + Padding="4" + BorderBrush="Black" + BorderThickness="2" + BoxShadow="4 4 32 8 #40000000" + CornerRadius="3"> + <Image + IsVisible="{Binding ShowLoadProgress}" + Width="256" + Height="256" + Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" /> + </Border> + <Grid Grid.Column="1" + IsVisible="{Binding ShowLoadProgress}" + HorizontalAlignment="Stretch" + VerticalAlignment="Center"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <TextBlock + IsVisible="{Binding ShowLoadProgress}" + Grid.Row="0" + Margin="10" + FontSize="30" + FontWeight="Bold" + TextWrapping="Wrap" + Text="{Binding LoadHeading}" + TextAlignment="Left" /> + <Border + IsVisible="{Binding ShowLoadProgress}" + Grid.Row="1" + CornerRadius="5" + ClipToBounds="True" + BorderBrush="{Binding ProgressBarBackgroundColor}" + Padding="0" + HorizontalAlignment="Stretch" + Margin="10" + BorderThickness="1"> + <ProgressBar + IsVisible="{Binding ShowLoadProgress}" + Height="10" + Margin="0" + Padding="0" + CornerRadius="5" + ClipToBounds="True" + MinWidth="500" + HorizontalAlignment="Stretch" + Background="{Binding ProgressBarBackgroundColor}" + Foreground="{Binding ProgressBarForegroundColor}" + Maximum="{Binding ProgressMaximum}" + Minimum="0" + IsIndeterminate="{Binding IsLoadingIndeterminate}" + Value="{Binding ProgressValue}" /> + </Border> + <TextBlock + IsVisible="{Binding ShowLoadProgress}" + Grid.Row="2" + Margin="10" + FontSize="18" + Text="{Binding CacheLoadStatus}" + TextAlignment="Left" /> + </Grid> + </Grid> + </Grid> + <Grid + Name="StatusBar" + Grid.Row="2" + Height="30" + Margin="0,0" + HorizontalAlignment="Stretch" + Background="{DynamicResource ThemeContentBackgroundColor}" + VerticalAlignment="Bottom" + DockPanel.Dock="Bottom" + IsVisible="{Binding ShowMenuAndStatusBar}"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <StackPanel Grid.Column="0" IsVisible="{Binding EnableNonGameRunningControls}" + VerticalAlignment="Center" Margin="10,0"> + <Grid Margin="0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Button + Width="25" + Height="25" + MinWidth="0" + Margin="0,0,5,0" + VerticalAlignment="Center" + Background="Transparent" + Command="{ReflectionBinding LoadApplications}"> + <ui:SymbolIcon Symbol="Refresh" Height="100" Width="50" /> + </Button> + <TextBlock + Name="LoadStatus" + Grid.Column="1" + Margin="0,0,5,0" + VerticalAlignment="Center" + IsVisible="{Binding EnableNonGameRunningControls}" + Text="{locale:Locale StatusBarGamesLoaded}" /> + <ProgressBar + Name="LoadProgressBar" + Grid.Column="2" + Height="6" + Maximum="{Binding StatusBarProgressMaximum}" + Value="{Binding StatusBarProgressValue}" + VerticalAlignment="Center" + Foreground="{DynamicResource HighlightColor}" + IsVisible="{Binding EnableNonGameRunningControls}" /> + </Grid> + </StackPanel> + <StackPanel + Grid.Column="1" + Margin="10,0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + IsVisible="{Binding IsGameRunning}" + Orientation="Horizontal"> + <TextBlock + Name="VsyncStatus" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Foreground="{Binding VsyncColor}" + PointerReleased="VsyncStatus_PointerReleased" + IsVisible="{Binding !ShowLoadProgress}" + Margin="0,0,5,0" + Text="VSync" + TextAlignment="Left" /> + <Border + Width="2" + Margin="2,0" + IsVisible="{Binding !ShowLoadProgress}" + BorderThickness="1" + Height="12" + BorderBrush="Gray" /> + <TextBlock + Margin="5,0,5,0" + Name="DockedStatus" + IsVisible="{Binding !ShowLoadProgress}" + HorizontalAlignment="Left" + VerticalAlignment="Center" + PointerReleased="DockedStatus_PointerReleased" + Text="{Binding DockedStatusText}" + TextAlignment="Left" /> + <Border + Width="2" + Margin="2,0" + IsVisible="{Binding !ShowLoadProgress}" + BorderThickness="1" + Height="12" + BorderBrush="Gray" /> + <TextBlock + Margin="5,0,5,0" + Name="AspectRatioStatus" + IsVisible="{Binding !ShowLoadProgress}" + HorizontalAlignment="Left" + VerticalAlignment="Center" + PointerReleased="AspectRatioStatus_PointerReleased" + Text="{Binding AspectRatioStatusText}" + TextAlignment="Left" /> + <Border + Width="2" + Margin="2,0" + IsVisible="{Binding !ShowLoadProgress}" + BorderThickness="1" + Height="12" + BorderBrush="Gray" /> + <ui:ToggleSplitButton + Margin="-2,0,-3,0" + Padding="5,0,0,5" + Name="VolumeStatus" + HorizontalAlignment="Left" + VerticalAlignment="Center" + IsVisible="{Binding !ShowLoadProgress}" + BorderBrush="{DynamicResource ThemeContentBackgroundColor}" + Background="{DynamicResource ThemeContentBackgroundColor}" + IsChecked="{Binding VolumeMuted}" + Content="{Binding VolumeStatusText}"> + <ui:ToggleSplitButton.Flyout> + <Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway"> + <Grid Margin="0"> + <Slider Value="{Binding Volume}" + ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" + Minimum="0" + Maximum="1" + TickFrequency="0.05" + IsSnapToTickEnabled="True" + Padding="0" + Margin="0" + SmallChange="0.01" + LargeChange="0.05" + Width="150" /> + </Grid> + </Flyout> + </ui:ToggleSplitButton.Flyout> + </ui:ToggleSplitButton> + <Border + Width="2" + Margin="2,0" + IsVisible="{Binding !ShowLoadProgress}" + BorderThickness="1" + Height="12" + BorderBrush="Gray" /> + <TextBlock + Margin="5,0,5,0" + IsVisible="{Binding !ShowLoadProgress}" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{Binding GameStatusText}" + TextAlignment="Left" /> + <Border + Width="2" + IsVisible="{Binding !ShowLoadProgress}" + Margin="2,0" + BorderThickness="1" + Height="12" + BorderBrush="Gray" /> + <TextBlock + Margin="5,0,5,0" + IsVisible="{Binding !ShowLoadProgress}" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{Binding FifoStatusText}" + TextAlignment="Left" /> + <Border + Width="2" + Margin="2,0" + IsVisible="{Binding !ShowLoadProgress}" + BorderThickness="1" + Height="12" + BorderBrush="Gray" /> + <TextBlock + Margin="5,0,5,0" + IsVisible="{Binding !ShowLoadProgress}" + HorizontalAlignment="Left" + VerticalAlignment="Center" + Text="{Binding GpuStatusText}" + TextAlignment="Left" /> + </StackPanel> + <StackPanel VerticalAlignment="Center" IsVisible="{Binding ShowFirmwareStatus}" Grid.Column="3" + Orientation="Horizontal" Margin="10, 0"> + <TextBlock + Name="FirmwareStatus" + HorizontalAlignment="Right" + VerticalAlignment="Center" + Margin="0" + Text="{locale:Locale StatusBarSystemVersion}" /> + </StackPanel> + </Grid> + </Grid> + </Grid> +</window:StyleableWindow>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs new file mode 100644 index 00000000..33a29be7 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs @@ -0,0 +1,719 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using Avalonia.Threading; +using Avalonia.Win32; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.Ui.Applet; +using Ryujinx.Ava.Ui.Controls; +using Ryujinx.Ava.Ui.Models; +using Ryujinx.Ava.Ui.ViewModels; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.Input.SDL2; +using Ryujinx.Modules; +using Ryujinx.Ui.App.Common; +using Ryujinx.Ui.Common; +using Ryujinx.Ui.Common.Configuration; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using InputManager = Ryujinx.Input.HLE.InputManager; +using ProgressBar = Avalonia.Controls.ProgressBar; +namespace Ryujinx.Ava.Ui.Windows +{ + public class MainWindow : StyleableWindow + { + private bool _canUpdate; + private bool _isClosing; + private bool _isLoading; + + private Control _mainViewContent; + + private UserChannelPersistence _userChannelPersistence; + private static bool _deferLoad; + private static string _launchPath; + private static bool _startFullscreen; + private string _currentEmulatedGamePath; + internal readonly AvaHostUiHandler UiHandler; + private AutoResetEvent _rendererWaitEvent; + + public VirtualFileSystem VirtualFileSystem { get; private set; } + public ContentManager ContentManager { get; private set; } + public AccountManager AccountManager { get; private set; } + + public LibHacHorizonManager LibHacHorizonManager { get; private set; } + + public AppHost AppHost { get; private set; } + public InputManager InputManager { get; private set; } + + public RendererControl GlRenderer { get; private set; } + public ContentControl ContentFrame { get; private set; } + public TextBlock LoadStatus { get; private set; } + public TextBlock FirmwareStatus { get; private set; } + public TextBox SearchBox { get; private set; } + public ProgressBar LoadProgressBar { get; private set; } + public Menu Menu { get; private set; } + public MenuItem UpdateMenuItem { get; private set; } + public MenuItem ActionsMenuItem { get; private set; } + public GameGridView GameGrid { get; private set; } + public GameListView GameList { get; private set; } + public OffscreenTextBox HiddenTextBox { get; private set; } + public HotKeyControl FullscreenHotKey { get; private set; } + public HotKeyControl FullscreenHotKey2 { get; private set; } + public HotKeyControl DockToggleHotKey { get; private set; } + public HotKeyControl ExitHotKey { get; private set; } + public ToggleSplitButton VolumeStatus { get; set; } + public MainWindowViewModel ViewModel { get; private set; } + + public bool CanUpdate + { + get => _canUpdate; + set + { + _canUpdate = value; + + Dispatcher.UIThread.InvokeAsync(() => UpdateMenuItem.IsEnabled = _canUpdate); + } + } + + public static bool ShowKeyErrorOnLoad { get; set; } + public ApplicationLibrary ApplicationLibrary { get; set; } + + public MainWindow() + { + ViewModel = new MainWindowViewModel(this); + + DataContext = ViewModel; + + InitializeComponent(); + AttachDebugDevTools(); + + UiHandler = new AvaHostUiHandler(this); + + Title = $"Ryujinx {Program.Version}"; + + Height = Height / Program.WindowScaleFactor; + Width = Width / Program.WindowScaleFactor; + + if (Program.PreviewerDetached) + { + Initialize(); + + ViewModel.Initialize(); + + InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver()); + + LoadGameList(); + + CheckLaunchState(); + } + + if (OperatingSystem.IsLinux()) + { + Program.WindowScaleFactor = this.PlatformImpl.RenderScaling; + } + _rendererWaitEvent = new AutoResetEvent(false); + } + + [Conditional("DEBUG")] + private void AttachDebugDevTools() + { + this.AttachDevTools(); + } + + public void LoadGameList() + { + if (_isLoading) + { + return; + } + + _isLoading = true; + + ViewModel.LoadApplications(); + + _isLoading = false; + } + + private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) + { + if (ViewModel.ShowMenuAndStatusBar && !ViewModel.ShowLoadProgress) + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (args.VSyncEnabled) + { + ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ff2eeac9")); + } + else + { + ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ffff4554")); + } + + ViewModel.DockedStatusText = args.DockedMode; + ViewModel.AspectRatioStatusText = args.AspectRatio; + ViewModel.GameStatusText = args.GameStatus; + ViewModel.FifoStatusText = args.FifoStatus; + ViewModel.GpuStatusText = args.GpuName; + + ViewModel.ShowStatusSeparator = true; + }); + } + } + + public void Application_Opened(object sender, ApplicationOpenedEventArgs args) + { + if (args.Application != null) + { + ViewModel.SelectedIcon = args.Application.Icon; + + string path = new FileInfo(args.Application.Path).FullName; + + LoadApplication(path); + } + + args.Handled = true; + } + + public async Task PerformanceCheck() + { + if (ConfigurationState.Instance.Logger.EnableTrace.Value) + { + string mainMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledMessage"]; + string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledConfirmMessage"]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog(this, mainMessage, secondaryMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); + + if (result != UserResult.Yes) + { + ConfigurationState.Instance.Logger.EnableTrace.Value = false; + + SaveConfig(); + } + } + + if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) + { + string mainMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledMessage"]; + string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog(this, mainMessage, secondaryMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); + + if (result != UserResult.Yes) + { + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; + + SaveConfig(); + } + } + } + + internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg) + { + _deferLoad = true; + _launchPath = launchPathArg; + _startFullscreen = startFullscreenArg; + } + +#pragma warning disable CS1998 + public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "") +#pragma warning restore CS1998 + { + if (AppHost != null) + { + await ContentDialogHelper.CreateInfoDialog(this, + LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedMessage"], + LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedSubMessage"], + LocaleManager.Instance["InputDialogOk"], + "", + LocaleManager.Instance["RyujinxInfo"]); + + return; + } + +#if RELEASE + await PerformanceCheck(); +#endif + + Logger.RestartTime(); + + if (ViewModel.SelectedIcon == null) + { + ViewModel.SelectedIcon = ApplicationLibrary.GetApplicationIcon(path); + } + + PrepareLoadScreen(); + + _mainViewContent = ContentFrame.Content as Control; + + GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel); + AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this); + + if (!AppHost.LoadGuestApplication().Result) + { + AppHost.DisposeContext(); + + return; + } + + ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName; + ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName; + + SwitchToGameControl(startFullscreen); + + _currentEmulatedGamePath = path; + Thread gameThread = new Thread(InitializeGame) + { + Name = "GUI.WindowThread" + }; + gameThread.Start(); + } + + private void InitializeGame() + { + GlRenderer.GlInitialized += GlRenderer_Created; + + AppHost.StatusUpdatedEvent += Update_StatusBar; + AppHost.AppExit += AppHost_AppExit; + + _rendererWaitEvent.WaitOne(); + + AppHost?.Start(); + + AppHost.DisposeContext(); + } + + + private void HandleRelaunch() + { + if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart) + { + _userChannelPersistence.ShouldRestart = false; + + Dispatcher.UIThread.Post(() => + { + LoadApplication(_currentEmulatedGamePath); + }); + } + else + { + // otherwise, clear state. + _userChannelPersistence = new UserChannelPersistence(); + _currentEmulatedGamePath = null; + } + } + + public void SwitchToGameControl(bool startFullscreen = false) + { + ViewModel.ShowContent = true; + ViewModel.ShowLoadProgress = false; + ViewModel.IsLoadingIndeterminate = false; + + Dispatcher.UIThread.InvokeAsync(() => + { + ContentFrame.Content = GlRenderer; + + if (startFullscreen && WindowState != WindowState.FullScreen) + { + ViewModel.ToggleFullscreen(); + } + + GlRenderer.Focus(); + }); + } + + public void ShowLoading(bool startFullscreen = false) + { + ViewModel.ShowContent = false; + ViewModel.ShowLoadProgress = true; + ViewModel.IsLoadingIndeterminate = true; + + Dispatcher.UIThread.InvokeAsync(() => + { + if (startFullscreen && WindowState != WindowState.FullScreen) + { + ViewModel.ToggleFullscreen(); + } + }); + } + + private void GlRenderer_Created(object sender, EventArgs e) + { + ShowLoading(); + + _rendererWaitEvent.Set(); + } + + private void AppHost_AppExit(object sender, EventArgs e) + { + if (_isClosing) + { + return; + } + + ViewModel.IsGameRunning = false; + + Dispatcher.UIThread.InvokeAsync(() => + { + if (ContentFrame.Content != _mainViewContent) + { + ContentFrame.Content = _mainViewContent; + } + + ViewModel.ShowMenuAndStatusBar = true; + ViewModel.ShowContent = true; + ViewModel.ShowLoadProgress = false; + ViewModel.IsLoadingIndeterminate = false; + + AppHost = null; + + HandleRelaunch(); + }); + GlRenderer.GlInitialized -= GlRenderer_Created; + GlRenderer = null; + + ViewModel.SelectedIcon = null; + + Dispatcher.UIThread.InvokeAsync(() => + { + Title = $"Ryujinx {Program.Version}"; + }); + } + + public void Sort_Checked(object sender, RoutedEventArgs args) + { + if (sender is RadioButton button) + { + var sort = Enum.Parse<ApplicationSort>(button.Tag.ToString()); + ViewModel.Sort(sort); + } + } + + protected override void HandleWindowStateChanged(WindowState state) + { + WindowState = state; + + if (state != WindowState.Minimized) + { + Renderer.Start(); + } + } + + public void Order_Checked(object sender, RoutedEventArgs args) + { + if (sender is RadioButton button) + { + var tag = button.Tag.ToString(); + ViewModel.Sort(tag != "Descending"); + } + } + + private void Initialize() + { + _userChannelPersistence = new UserChannelPersistence(); + VirtualFileSystem = VirtualFileSystem.CreateInstance(); + LibHacHorizonManager = new LibHacHorizonManager(); + ContentManager = new ContentManager(VirtualFileSystem); + + LibHacHorizonManager.InitializeFsServer(VirtualFileSystem); + LibHacHorizonManager.InitializeArpServer(); + LibHacHorizonManager.InitializeBcatServer(); + LibHacHorizonManager.InitializeSystemClients(); + + ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem); + + // Save data created before we supported extra data in directory save data will not work properly if + // given empty extra data. Luckily some of that extra data can be created using the data from the + // save data indexer, which should be enough to check access permissions for user saves. + // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. + // Consider removing this at some point in the future when we don't need to worry about old saves. + VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient); + + AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, Program.CommandLineProfile); + + VirtualFileSystem.ReloadKeySet(); + + ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this); + + RefreshFirmwareStatus(); + } + + protected async void CheckLaunchState() + { + if (ShowKeyErrorOnLoad) + { + ShowKeyErrorOnLoad = false; + + Dispatcher.UIThread.Post(async () => await + UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, this)); + } + + if (_deferLoad) + { + _deferLoad = false; + + LoadApplication(_launchPath, _startFullscreen); + } + + if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this)) + { + await Updater.BeginParse(this, false).ContinueWith(task => + { + Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); + }, TaskContinuationOptions.OnlyOnFaulted); + } + } + + public void RefreshFirmwareStatus() + { + SystemVersion version = null; + try + { + version = ContentManager.GetCurrentFirmwareVersion(); + } + catch (Exception) { } + + bool hasApplet = false; + + if (version != null) + { + LocaleManager.Instance.UpdateDynamicValue("StatusBarSystemVersion", + version.VersionString); + + hasApplet = version.Major > 3; + } + else + { + LocaleManager.Instance.UpdateDynamicValue("StatusBarSystemVersion", "0.0"); + } + + ViewModel.IsAppletMenuActive = hasApplet; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + + ContentFrame = this.FindControl<ContentControl>("Content"); + GameList = this.FindControl<GameListView>("GameList"); + LoadStatus = this.FindControl<TextBlock>("LoadStatus"); + FirmwareStatus = this.FindControl<TextBlock>("FirmwareStatus"); + LoadProgressBar = this.FindControl<ProgressBar>("LoadProgressBar"); + SearchBox = this.FindControl<TextBox>("SearchBox"); + Menu = this.FindControl<Menu>("Menu"); + UpdateMenuItem = this.FindControl<MenuItem>("UpdateMenuItem"); + GameGrid = this.FindControl<GameGridView>("GameGrid"); + HiddenTextBox = this.FindControl<OffscreenTextBox>("HiddenTextBox"); + FullscreenHotKey = this.FindControl<HotKeyControl>("FullscreenHotKey"); + FullscreenHotKey2 = this.FindControl<HotKeyControl>("FullscreenHotKey2"); + DockToggleHotKey = this.FindControl<HotKeyControl>("DockToggleHotKey"); + ExitHotKey = this.FindControl<HotKeyControl>("ExitHotKey"); + VolumeStatus = this.FindControl<ToggleSplitButton>("VolumeStatus"); + ActionsMenuItem = this.FindControl<MenuItem>("ActionsMenuItem"); + + VolumeStatus.Click += VolumeStatus_CheckedChanged; + + GameGrid.ApplicationOpened += Application_Opened; + + GameGrid.DataContext = ViewModel; + + GameList.ApplicationOpened += Application_Opened; + + GameList.DataContext = ViewModel; + + LoadHotKeys(); + } + + public static void UpdateGraphicsConfig() + { + int resScale = ConfigurationState.Instance.Graphics.ResScale; + float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom; + + GraphicsConfig.ResScale = resScale == -1 ? resScaleCustom : resScale; + GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; + GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; + } + + public void LoadHotKeys() + { + HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt)); + HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11)); + HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9)); + HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape)); + } + + public static void SaveConfig() + { + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + + public void UpdateGameMetadata(string titleId) + { + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + { + DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed); + double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds; + + appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero); + }); + } + + private void PrepareLoadScreen() + { + using MemoryStream stream = new MemoryStream(ViewModel.SelectedIcon); + using var gameIconBmp = SixLabors.ImageSharp.Image.Load<Bgra32>(stream); + + var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>(); + + const int ColorDivisor = 4; + + Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B); + Color progressBgColor = Color.FromRgb( + (byte)(dominantColor.R / ColorDivisor), + (byte)(dominantColor.G / ColorDivisor), + (byte)(dominantColor.B / ColorDivisor)); + + ViewModel.ProgressBarForegroundColor = new SolidColorBrush(progressFgColor); + ViewModel.ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor); + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) + { + ViewModel.SearchText = SearchBox.Text; + } + + private async void StopEmulation_Click(object sender, RoutedEventArgs e) + { + if (AppHost != null) + { + await AppHost.ShowExitPrompt(); + } + } + + private async void PauseEmulation_Click(object sender, RoutedEventArgs e) + { + await Task.Run(() => + { + AppHost?.Pause(); + }); + } + + private async void ResumeEmulation_Click(object sender, RoutedEventArgs e) + { + await Task.Run(() => + { + AppHost?.Resume(); + }); + } + + private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + if (sender is MenuItem) + { + ViewModel.IsAmiiboRequested = AppHost.Device.System.SearchingForAmiibo(out _); + } + } + + private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e) + { + AppHost.Device.EnableDeviceVsync = !AppHost.Device.EnableDeviceVsync; + + Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {AppHost.Device.EnableDeviceVsync}"); + } + + private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + + private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e) + { + AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value; + + ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; + } + + private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e) + { + var volumeSplitButton = sender as ToggleSplitButton; + if (ViewModel.IsGameRunning) + { + if (!volumeSplitButton.IsChecked) + { + AppHost.Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + AppHost.Device.SetVolume(0); + } + + ViewModel.Volume = AppHost.Device.GetVolume(); + } + } + + protected override void OnClosing(CancelEventArgs e) + { + if (!_isClosing && AppHost != null && ConfigurationState.Instance.ShowConfirmExit) + { + e.Cancel = true; + + ConfirmExit(); + + return; + } + + _isClosing = true; + + if (AppHost != null) + { + AppHost.AppExit -= AppHost_AppExit; + AppHost.AppExit += (sender, e) => + { + AppHost = null; + + Dispatcher.UIThread.Post(Close); + }; + AppHost?.Stop(); + + e.Cancel = true; + + return; + } + + ApplicationLibrary.CancelLoading(); + InputManager.Dispose(); + Program.Exit(); + + base.OnClosing(e); + } + + private void ConfirmExit() + { + Dispatcher.UIThread.InvokeAsync(async () => + { + _isClosing = await ContentDialogHelper.CreateExitDialog(this); + + if (_isClosing) + { + Close(); + } + }); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/StyleableWindow.cs b/Ryujinx.Ava/Ui/Windows/StyleableWindow.cs new file mode 100644 index 00000000..e268cac1 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/StyleableWindow.cs @@ -0,0 +1,47 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using FluentAvalonia.UI.Controls; +using System; +using System.IO; +using System.Reflection; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class StyleableWindow : Window + { + public ContentDialog ContentDialog { get; private set; } + public IBitmap IconImage { get; set; } + + public StyleableWindow() + { + WindowStartupLocation = WindowStartupLocation.CenterOwner; + TransparencyLevelHint = WindowTransparencyLevel.None; + + using Stream stream = Assembly.GetAssembly(typeof(Ryujinx.Ui.Common.Configuration.ConfigurationState)).GetManifestResourceStream("Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + + Icon = new WindowIcon(stream); + stream.Position = 0; + IconImage = new Bitmap(stream); + } + + public void LoadDialog() + { + ContentDialog = this.FindControl<ContentDialog>("ContentDialog"); + } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + ContentDialog = this.FindControl<ContentDialog>("ContentDialog"); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/TimeZone.cs b/Ryujinx.Ava/Ui/Windows/TimeZone.cs new file mode 100644 index 00000000..e6806f15 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/TimeZone.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Ava.Ui.Windows +{ + public class TimeZone + { + public TimeZone(string utcDifference, string location, string abbreviation) + { + UtcDifference = utcDifference; + Location = location; + Abbreviation = abbreviation; + } + + public string UtcDifference { get; set; } + public string Location { get; set; } + public string Abbreviation { get; set; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml b/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml new file mode 100644 index 00000000..31654a22 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml @@ -0,0 +1,37 @@ +<window:StyleableWindow xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow" + xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + CanResize="False" + SizeToContent="Height" + Width="500" MinHeight="500" Height="500" + WindowStartupLocation="CenterOwner" + MinWidth="500" + Title="Ryujinx Updater"> + <Grid Margin="20" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition /> + <RowDefinition /> + <RowDefinition /> + <RowDefinition /> + </Grid.RowDefinitions> + <TextBlock Grid.Row="1" HorizontalAlignment="Stretch" TextAlignment="Center" Height="20" Name="MainText" /> + <TextBlock Height="20" HorizontalAlignment="Stretch" TextAlignment="Center" Name="SecondaryText" Grid.Row="2" /> + <ProgressBar IsVisible="False" HorizontalAlignment="Stretch" Name="ProgressBar" Maximum="100" Minimum="0" + Margin="20" Grid.Row="3" /> + <StackPanel IsVisible="False" Name="ButtonBox" Orientation="Horizontal" Spacing="20" Grid.Row="4" + HorizontalAlignment="Right"> + <Button Command="{Binding YesPressed}" MinWidth="50"> + <TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogYes}" /> + </Button> + <Button Command="{Binding NoPressed}" MinWidth="50"> + <TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogNo}" /> + </Button> + </StackPanel> + </Grid> +</window:StyleableWindow>
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs new file mode 100644 index 00000000..a3dd75a3 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs @@ -0,0 +1,93 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Modules; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Ava.Ui.Windows +{ + public class UpdaterWindow : StyleableWindow + { + private readonly string _buildUrl; + private readonly MainWindow _mainWindow; + private readonly Version _newVersion; + private bool _restartQuery; + + public UpdaterWindow() + { + DataContext = this; + + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + Title = LocaleManager.Instance["RyujinxUpdater"]; + } + + public UpdaterWindow(MainWindow mainWindow, Version newVersion, string buildUrl) : this() + { + _mainWindow = mainWindow; + _newVersion = newVersion; + _buildUrl = buildUrl; + } + + public TextBlock MainText { get; set; } + public TextBlock SecondaryText { get; set; } + public ProgressBar ProgressBar { get; set; } + public StackPanel ButtonBox { get; set; } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + + MainText = this.FindControl<TextBlock>("MainText"); + SecondaryText = this.FindControl<TextBlock>("SecondaryText"); + ProgressBar = this.FindControl<ProgressBar>("ProgressBar"); + ButtonBox = this.FindControl<StackPanel>("ButtonBox"); + } + + [DllImport("libc", SetLastError = true)] + private static extern int chmod(string path, uint mode); + + public void YesPressed() + { + if (_restartQuery) + { + string ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; + string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName); + string ryuArg = string.Join(" ", Environment.GetCommandLineArgs().AsEnumerable().Skip(1).ToArray()); + + if (!OperatingSystem.IsWindows()) + { + chmod(ryuExe, 0777); + } + + Process.Start(ryuExe, ryuArg); + + Environment.Exit(0); + } + else + { + ButtonBox.IsVisible = false; + ProgressBar.IsVisible = true; + + SecondaryText.Text = ""; + _restartQuery = true; + + Updater.UpdateRyujinx(this, _buildUrl); + } + } + + public void NoPressed() + { + _mainWindow.UpdateMenuItem.IsEnabled = true; + + Close(); + } + } +}
\ No newline at end of file |
