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>
This commit is contained in:
Emmanuel Hansen
2022-05-15 11:30:15 +00:00
committed by GitHub
parent 9ba73ffbe5
commit deb99d2cae
161 changed files with 17179 additions and 855 deletions

View File

@@ -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>

View File

@@ -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");
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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();
}
});
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}