Compare commits

..

12 Commits

Author SHA1 Message Date
Emmanuel Hansen
6e02cac952 Avalonia - Use content dialog for user profile manager (#3455)
* remove content dialog placeholder from all windows

* remove redundant window argument

* redesign user profile window

* wip

* use avalonia auto name generator

* add edit and new user options

* move profile image selection to content dialog

* remove usings

* fix updater

* address review

* adjust avatar dialog size

* add validation for user editor

* fix typo

* Shorten some labels
2022-07-24 14:38:38 -03:00
Mary-nyan
3a3380fa25 fix: Ensure to load latest version of ffmpeg libraries first (#3473)
Fix a possible crash related to older version of ffmpeg being loaded
instewad of the one shipped with the emulator.
2022-07-24 11:39:56 +02:00
EmulationFanatic
2d252db0a7 GTK & Avalonia changes (#3480) 2022-07-23 12:05:51 -03:00
gdkchan
7f8a3541eb Fix decoding of block after shader BRA.CC instructions without predicate (#3472)
* Fix decoding of block after BRA.CC instructions without predicate

* Shader cache version bump
2022-07-23 11:53:14 -03:00
gdkchan
b34de74f81 Avoid adding shader buffer descriptors for constant buffers that are not used (#3478)
* Avoid adding shader buffer descriptors for constant buffers that are not used

* Shader cache version
2022-07-23 11:15:58 -03:00
riperiperi
5811d121df Avoid scaling 2d textures that could be used as 3d (#3464) 2022-07-15 09:24:13 -03:00
Logan Stromberg
6eb85e846f Reduce some unnecessary allocations in DMA handler (#2886)
* experimental changes to try and reduce allocations in kernel threading and DMA handler

* Simplify the changes in this branch to just 1. Don't make unnecessary copies of data just for texture-texture transfers and 2. Add a fast path for 1bpp linear byte copies

* forgot to check src + dst linearity in 1bpp DMA fast path. Fixes the UE4 regression.

* removing dev log I left in

* Generalizing the DMA linear fast path to cases other than 1bpp copies

* revert kernel changes

* revert whitespace

* remove unneeded references

* PR feedback

Co-authored-by: Logan Stromberg <lostromb@microsoft.com>
Co-authored-by: gdk <gab.dark.100@gmail.com>
2022-07-14 15:45:56 -03:00
Mary
c5bddfeab8 Remove dependency for FFmpeg.AutoGen and Update FFmpeg to 5.0.1 for Windows (#3466)
* Remove dependency for FFMpeg.AutoGen

Also prepare for FFMpeg 5.0 and 5.1

* Update Ryujinx.Graphics.Nvdec.Dependencies to 5.0.1-build10

* Address gdkchan's comments

* Address Ack's comment

* Address gdkchan's comment
2022-07-14 15:13:23 +02:00
Fruityloops
70ec5def9c BSD: Allow use of DontWait flag in Receive (#3462) 2022-07-14 11:47:25 +02:00
merry
7853faa334 Ava/MainWindow: Do not show Show Console menu item on non-Windows (#3461) 2022-07-12 12:58:31 +00:00
riperiperi
b7fb474bfe Handle the case where byte optionValues are sent to BSD (#3405)
Some games and the Mario Odyssey Multiplayer mod do this.

The SMO multiplayer mod also needs you to revert #3394 as it uses a blocking socket to receive (otherwise it hangs), and it doesn't seem to like being forced as non-blocking.
2022-07-12 00:50:01 +02:00
Emmanuel Hansen
2fa6413ed8 Avalonia - Add border to Flyouts (#3341)
* add borders to menus

* apply to dropdowns

* darken the border for dark theme

* fix duplicate keys
2022-07-12 00:44:35 +02:00
79 changed files with 1629 additions and 1084 deletions

View File

@@ -55,7 +55,6 @@ namespace Ryujinx.Ava
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
var result = await ContentDialogHelper.CreateConfirmationDialog( var result = await ContentDialogHelper.CreateConfirmationDialog(
(desktop.MainWindow as MainWindow).SettingsWindow,
LocaleManager.Instance["DialogThemeRestartMessage"], LocaleManager.Instance["DialogThemeRestartMessage"],
LocaleManager.Instance["DialogThemeRestartSubMessage"], LocaleManager.Instance["DialogThemeRestartSubMessage"],
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogYes"],

View File

@@ -417,10 +417,12 @@ namespace Ryujinx.Ava
{ {
if (userError == UserError.NoFirmware) if (userError == UserError.NoFirmware)
{ {
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString); string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"],
firmwareVersion.VersionString);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_parent, UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], ""); LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message,
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
if (result != UserResult.Yes) if (result != UserResult.Yes)
{ {
@@ -450,12 +452,12 @@ namespace Ryujinx.Ava
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString); string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(_parent, await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString), string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
message, message,
LocaleManager.Instance["InputDialogOk"], LocaleManager.Instance["InputDialogOk"],
"", "",
LocaleManager.Instance["RyujinxInfo"]); LocaleManager.Instance["RyujinxInfo"]);
} }
} }
else else
@@ -879,7 +881,7 @@ namespace Ryujinx.Ava
} }
_dialogShown = true; _dialogShown = true;
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(_parent); shouldExit = await ContentDialogHelper.CreateStopEmulationDialog();
_dialogShown = false; _dialogShown = false;
} }
@@ -896,7 +898,7 @@ namespace Ryujinx.Ava
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
_parent.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default; _parent.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default;
}); });
} }
else else

View File

@@ -118,7 +118,7 @@
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL2": "SDL2",
"SettingsTabSystemHacks": "Hacks", "SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": " - These may cause instabilities", "SettingsTabSystemHacksNote": " (may cause instability)",
"SettingsTabSystemExpandDramSize": "Expand DRAM Size to 6GB", "SettingsTabSystemExpandDramSize": "Expand DRAM Size to 6GB",
"SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services", "SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services",
"SettingsTabGraphics": "Graphics", "SettingsTabGraphics": "Graphics",
@@ -256,8 +256,8 @@
"UserProfilesSaveProfileName": "Save Profile Name", "UserProfilesSaveProfileName": "Save Profile Name",
"UserProfilesChangeProfileImage": "Change Profile Image", "UserProfilesChangeProfileImage": "Change Profile Image",
"UserProfilesAvailableUserProfiles": "Available User Profiles:", "UserProfilesAvailableUserProfiles": "Available User Profiles:",
"UserProfilesAddNewProfile": "Add New Profile", "UserProfilesAddNewProfile": "Create Profile",
"UserProfilesDeleteSelectedProfile": "Delete Selected Profile", "UserProfilesDeleteSelectedProfile": "Delete Selected",
"UserProfilesClose": "Close", "UserProfilesClose": "Close",
"ProfileImageSelectionTitle": "Profile Image Selection", "ProfileImageSelectionTitle": "Profile Image Selection",
"ProfileImageSelectionHeader": "Choose a profile Image", "ProfileImageSelectionHeader": "Choose a profile Image",
@@ -568,5 +568,12 @@
"UpdateWindowTitle": "Manage Game Updates", "UpdateWindowTitle": "Manage Game Updates",
"CheatWindowHeading": "Cheats Available for {0} [{1}]", "CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "DLC Available for {0} [{1}]", "DlcWindowHeading": "DLC Available for {0} [{1}]",
"UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel",
"Save": "Save",
"Discard": "Discard",
"UserProfilesSetProfileImage": "Set Profile Image",
"UserProfileEmptyNameError": "Name is required",
"UserProfileNoImageError": "Profile image must be set",
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]" "GameUpdateWindowHeading": "Updates Available for {0} [{1}]"
} }

View File

@@ -54,5 +54,6 @@
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color> <Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color> <Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
</Styles.Resources> </Styles.Resources>
</Styles> </Styles>

View File

@@ -49,5 +49,6 @@
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color> <Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color> <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FF000000</Color> <Color x:Key="ThemeForegroundColor">#FF000000</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
</Styles.Resources> </Styles.Resources>
</Styles> </Styles>

View File

@@ -221,6 +221,7 @@
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" /> <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
</Style> </Style>
<Styles.Resources> <Styles.Resources>
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="ListViewItemBackgroundSelected" ResourceKey="ThemeAccentColorBrush" /> <StaticResource x:Key="ListViewItemBackgroundSelected" ResourceKey="ThemeAccentColorBrush" />
<StaticResource x:Key="ListViewItemBackgroundPressed" ResourceKey="SystemAccentColorDark1" /> <StaticResource x:Key="ListViewItemBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" /> <StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" />
@@ -232,7 +233,7 @@
Color="{DynamicResource SystemBaseMediumLowColor}" /> Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" /> <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" /> <SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" /> <SolidColorBrush x:Key="FlyoutBorderThemeBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" /> <SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" />
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" /> <SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" /> <SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />

View File

@@ -81,7 +81,6 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog( await ContentDialogHelper.CreateErrorDialog(
_owner,
string.Format(LocaleManager.Instance["DialogMessageCreateSaveErrorMessage"], result.ToStringWithName())); string.Format(LocaleManager.Instance["DialogMessageCreateSaveErrorMessage"], result.ToStringWithName()));
}); });
@@ -101,8 +100,7 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName()));
string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName()));
}); });
return false; return false;
@@ -161,7 +159,6 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
_owner,
string.Format(LocaleManager.Instance["DialogNcaExtractionMessage"], ncaSectionType, Path.GetFileName(titleFilePath)), string.Format(LocaleManager.Instance["DialogNcaExtractionMessage"], ncaSectionType, Path.GetFileName(titleFilePath)),
"", "",
"", "",
@@ -232,7 +229,7 @@ namespace Ryujinx.Ava.Common
"Extraction failure. The main NCA was not present in the selected file"); "Extraction failure. The main NCA was not present in the selected file");
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]);
}); });
return; return;
} }
@@ -273,7 +270,7 @@ namespace Ryujinx.Ava.Common
$"LibHac returned error code: {resultCode.Value.ErrorCode}"); $"LibHac returned error code: {resultCode.Value.ErrorCode}");
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]);
}); });
} }
else if (resultCode.Value.IsSuccess()) else if (resultCode.Value.IsSuccess())
@@ -281,7 +278,6 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
_owner,
LocaleManager.Instance["DialogNcaExtractionSuccessMessage"], LocaleManager.Instance["DialogNcaExtractionSuccessMessage"],
"", "",
LocaleManager.Instance["InputDialogOk"], LocaleManager.Instance["InputDialogOk"],
@@ -298,7 +294,7 @@ namespace Ryujinx.Ava.Common
{ {
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message); await ContentDialogHelper.CreateErrorDialog(ex.Message);
}); });
} }
} }

View File

@@ -76,7 +76,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]); await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
}); });
return; return;
@@ -111,7 +111,7 @@ namespace Ryujinx.Modules
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
}); });
} }
@@ -129,7 +129,7 @@ namespace Ryujinx.Modules
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
}); });
} }
@@ -142,7 +142,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, exception.Message); Logger.Error?.Print(LogClass.Application, exception.Message);
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(mainWindow, LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]);
}); });
return; return;
@@ -157,7 +157,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]); await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
}); });
return; return;
@@ -169,7 +169,7 @@ namespace Ryujinx.Modules
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], ""); await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
}); });
} }
@@ -550,7 +550,7 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterArchNotSupportedMessage"], ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterArchNotSupportedMessage"],
LocaleManager.Instance["DialogUpdaterArchNotSupportedSubMessage"]); LocaleManager.Instance["DialogUpdaterArchNotSupportedSubMessage"]);
} }
@@ -561,7 +561,7 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterNoInternetMessage"], ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterNoInternetMessage"],
LocaleManager.Instance["DialogUpdaterNoInternetSubMessage"]); LocaleManager.Instance["DialogUpdaterNoInternetSubMessage"]);
} }
@@ -572,7 +572,7 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterDirtyBuildMessage"], ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterDirtyBuildMessage"],
LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]); LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
} }
@@ -585,13 +585,11 @@ namespace Ryujinx.Modules
{ {
if (ReleaseInformations.IsFlatHubBuild()) if (ReleaseInformations.IsFlatHubBuild())
{ {
ContentDialogHelper.CreateWarningDialog(parent, ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterFlatpakNotSupportedMessage"]);
LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterFlatpakNotSupportedMessage"]);
} }
else else
{ {
ContentDialogHelper.CreateWarningDialog(parent, ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
} }
} }

View File

@@ -26,9 +26,10 @@
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" /> <PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.9.4" /> <PackageReference Include="DynamicData" Version="7.9.4" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" /> <PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" /> <PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
<PackageReference Include="SPB" Version="0.0.4-build17" /> <PackageReference Include="SPB" Version="0.0.4-build17" />
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="SharpZipLib" Version="1.3.3" />

View File

@@ -92,7 +92,7 @@ namespace Ryujinx.Ava.Ui.Applet
} }
catch (Exception ex) catch (Exception ex)
{ {
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex)); await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex));
dialogCloseEvent.Set(); dialogCloseEvent.Set();
} }
@@ -126,7 +126,7 @@ namespace Ryujinx.Ava.Ui.Applet
catch (Exception ex) catch (Exception ex)
{ {
error = true; error = true;
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex)); await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex));
} }
finally finally
{ {
@@ -181,7 +181,7 @@ namespace Ryujinx.Ava.Ui.Applet
catch (Exception ex) catch (Exception ex)
{ {
dialogCloseEvent.Set(); dialogCloseEvent.Set();
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex)); await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex));
} }
}); });

View File

@@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Applet namespace Ryujinx.Ava.Ui.Applet
{ {
internal class ErrorAppletWindow : StyleableWindow internal partial class ErrorAppletWindow : StyleableWindow
{ {
private readonly Window _owner; private readonly Window _owner;
private object _buttonResponse; private object _buttonResponse;
@@ -50,8 +50,6 @@ namespace Ryujinx.Ava.Ui.Applet
public string Message { get; set; } public string Message { get; set; }
public StackPanel ButtonStack { get; set; }
private void AddButton(string label, object tag) private void AddButton(string label, object tag)
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
@@ -79,11 +77,5 @@ namespace Ryujinx.Ava.Ui.Applet
return _buttonResponse; return _buttonResponse;
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
ButtonStack = this.FindControl<StackPanel>("ButtonStack");
}
} }
} }

View File

@@ -13,7 +13,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls namespace Ryujinx.Ava.Ui.Controls
{ {
internal class SwkbdAppletDialog : UserControl internal partial class SwkbdAppletDialog : UserControl
{ {
private Predicate<int> _checkLength; private Predicate<int> _checkLength;
private int _inputMax; private int _inputMax;
@@ -30,6 +30,10 @@ namespace Ryujinx.Ava.Ui.Controls
_placeholder = placeholder; _placeholder = placeholder;
InitializeComponent(); InitializeComponent();
Input.Watermark = _placeholder;
Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
SetInputLengthValidation(0, int.MaxValue); // Disable by default. SetInputLengthValidation(0, int.MaxValue); // Disable by default.
} }
@@ -43,23 +47,9 @@ namespace Ryujinx.Ava.Ui.Controls
public string MainText { get; set; } = ""; public string MainText { get; set; } = "";
public string SecondaryText { get; set; } = ""; public string SecondaryText { get; set; } = "";
public TextBlock Error { get; private set; }
public TextBox Input { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
Error = this.FindControl<TextBlock>("Error");
Input = this.FindControl<TextBox>("Input");
Input.Watermark = _placeholder;
Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
}
public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, SoftwareKeyboardUiArgs args) public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, SoftwareKeyboardUiArgs args)
{ {
ContentDialog contentDialog = window.ContentDialog; ContentDialog contentDialog = new ContentDialog();
UserResult result = UserResult.Cancel; UserResult result = UserResult.Cancel;

View File

@@ -16,7 +16,6 @@ namespace Ryujinx.Ava.Ui.Controls
private static bool _isChoiceDialogOpen; private static bool _isChoiceDialogOpen;
private async static Task<UserResult> ShowContentDialog( private async static Task<UserResult> ShowContentDialog(
StyleableWindow window,
string title, string title,
string primaryText, string primaryText,
string secondaryText, string secondaryText,
@@ -28,35 +27,32 @@ namespace Ryujinx.Ava.Ui.Controls
{ {
UserResult result = UserResult.None; UserResult result = UserResult.None;
ContentDialog contentDialog = window.ContentDialog; ContentDialog contentDialog = new ContentDialog();
await ShowDialog(); await ShowDialog();
async Task ShowDialog() async Task ShowDialog()
{ {
if (contentDialog != null) contentDialog.Title = title;
contentDialog.PrimaryButtonText = primaryButton;
contentDialog.SecondaryButtonText = secondaryButton;
contentDialog.CloseButtonText = closeButton;
contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
{ {
contentDialog.Title = title; result = primaryButtonResult;
contentDialog.PrimaryButtonText = primaryButton; });
contentDialog.SecondaryButtonText = secondaryButton; contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
contentDialog.CloseButtonText = closeButton; {
contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol); result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Cancel;
});
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() => await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
{
result = primaryButtonResult;
});
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Cancel;
});
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
};
} }
return result; return result;
@@ -78,35 +74,30 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult result = UserResult.None; UserResult result = UserResult.None;
ContentDialog contentDialog = window.ContentDialog; ContentDialog contentDialog = new ContentDialog
Window overlay = window;
if (contentDialog != null)
{ {
contentDialog.PrimaryButtonClick += DeferClose; Title = title,
contentDialog.Title = title; PrimaryButtonText = primaryButton,
contentDialog.PrimaryButtonText = primaryButton; SecondaryButtonText = secondaryButton,
contentDialog.SecondaryButtonText = secondaryButton; CloseButtonText = closeButton,
contentDialog.CloseButtonText = closeButton; Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol),
contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol); PrimaryButtonCommand = MiniCommand.Create(() =>
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
{ {
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok; result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
}); }),
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.Cancel;
});
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
}; };
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.Cancel;
});
contentDialog.PrimaryButtonClick += DeferClose;
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
return result; return result;
@@ -141,7 +132,7 @@ namespace Ryujinx.Ava.Ui.Controls
if (doWhileDeferred != null) if (doWhileDeferred != null)
{ {
await doWhileDeferred(overlay); await doWhileDeferred(window);
deferResetEvent.Set(); deferResetEvent.Set();
} }
@@ -191,7 +182,6 @@ namespace Ryujinx.Ava.Ui.Controls
} }
public static async Task<UserResult> CreateInfoDialog( public static async Task<UserResult> CreateInfoDialog(
StyleableWindow window,
string primary, string primary,
string secondaryText, string secondaryText,
string acceptButton, string acceptButton,
@@ -199,7 +189,6 @@ namespace Ryujinx.Ava.Ui.Controls
string title) string title)
{ {
return await ShowContentDialog( return await ShowContentDialog(
window,
title, title,
primary, primary,
secondaryText, secondaryText,
@@ -210,7 +199,6 @@ namespace Ryujinx.Ava.Ui.Controls
} }
internal static async Task<UserResult> CreateConfirmationDialog( internal static async Task<UserResult> CreateConfirmationDialog(
StyleableWindow window,
string primaryText, string primaryText,
string secondaryText, string secondaryText,
string acceptButtonText, string acceptButtonText,
@@ -219,7 +207,6 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult primaryButtonResult = UserResult.Yes) UserResult primaryButtonResult = UserResult.Yes)
{ {
return await ShowContentDialog( return await ShowContentDialog(
window,
string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance["DialogConfirmationTitle"] : title, string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance["DialogConfirmationTitle"] : title,
primaryText, primaryText,
secondaryText, secondaryText,
@@ -235,10 +222,9 @@ namespace Ryujinx.Ava.Ui.Controls
return new(mainText, secondaryText); return new(mainText, secondaryText);
} }
internal static async Task CreateUpdaterInfoDialog(StyleableWindow window, string primary, string secondaryText) internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
{ {
await ShowContentDialog( await ShowContentDialog(
window,
LocaleManager.Instance["DialogUpdaterTitle"], LocaleManager.Instance["DialogUpdaterTitle"],
primary, primary,
secondaryText, secondaryText,
@@ -248,24 +234,9 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Important); (int)Symbol.Important);
} }
internal static async Task ShowNotAvailableMessage(StyleableWindow window) internal static async Task CreateWarningDialog(string primary, string secondaryText)
{
// Temporary placeholder for features to be added
await ShowContentDialog(
window,
"Feature Not Available",
"The selected feature is not available in this version.",
"",
"",
"",
LocaleManager.Instance["InputDialogOk"],
(int)Symbol.Important);
}
internal static async Task CreateWarningDialog(StyleableWindow window, string primary, string secondaryText)
{ {
await ShowContentDialog( await ShowContentDialog(
window,
LocaleManager.Instance["DialogWarningTitle"], LocaleManager.Instance["DialogWarningTitle"],
primary, primary,
secondaryText, secondaryText,
@@ -275,12 +246,11 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Important); (int)Symbol.Important);
} }
internal static async Task CreateErrorDialog(StyleableWindow owner, string errorMessage, string secondaryErrorMessage = "") internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "")
{ {
Logger.Error?.Print(LogClass.Application, errorMessage); Logger.Error?.Print(LogClass.Application, errorMessage);
await ShowContentDialog( await ShowContentDialog(
owner,
LocaleManager.Instance["DialogErrorTitle"], LocaleManager.Instance["DialogErrorTitle"],
LocaleManager.Instance["DialogErrorMessage"], LocaleManager.Instance["DialogErrorMessage"],
errorMessage, errorMessage,
@@ -290,7 +260,7 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Dismiss); (int)Symbol.Dismiss);
} }
internal static async Task<bool> CreateChoiceDialog(StyleableWindow window, string title, string primary, string secondaryText) internal static async Task<bool> CreateChoiceDialog(string title, string primary, string secondaryText)
{ {
if (_isChoiceDialogOpen) if (_isChoiceDialogOpen)
{ {
@@ -301,7 +271,6 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult response = UserResult response =
await ShowContentDialog( await ShowContentDialog(
window,
title, title,
primary, primary,
secondaryText, secondaryText,
@@ -316,19 +285,17 @@ namespace Ryujinx.Ava.Ui.Controls
return response == UserResult.Yes; return response == UserResult.Yes;
} }
internal static async Task<bool> CreateExitDialog(StyleableWindow owner) internal static async Task<bool> CreateExitDialog()
{ {
return await CreateChoiceDialog( return await CreateChoiceDialog(
owner,
LocaleManager.Instance["DialogExitTitle"], LocaleManager.Instance["DialogExitTitle"],
LocaleManager.Instance["DialogExitMessage"], LocaleManager.Instance["DialogExitMessage"],
LocaleManager.Instance["DialogExitSubMessage"]); LocaleManager.Instance["DialogExitSubMessage"]);
} }
internal static async Task<bool> CreateStopEmulationDialog(StyleableWindow owner) internal static async Task<bool> CreateStopEmulationDialog()
{ {
return await CreateChoiceDialog( return await CreateChoiceDialog(
owner,
LocaleManager.Instance["DialogStopEmulationTitle"], LocaleManager.Instance["DialogStopEmulationTitle"],
LocaleManager.Instance["DialogStopEmulationMessage"], LocaleManager.Instance["DialogStopEmulationMessage"],
LocaleManager.Instance["DialogExitSubMessage"]); LocaleManager.Instance["DialogExitSubMessage"]);
@@ -338,12 +305,10 @@ namespace Ryujinx.Ava.Ui.Controls
string title, string title,
string mainText, string mainText,
string subText, string subText,
StyleableWindow owner,
uint maxLength = int.MaxValue, uint maxLength = int.MaxValue,
string input = "") string input = "")
{ {
var result = await InputDialog.ShowInputDialog( var result = await InputDialog.ShowInputDialog(
owner,
title, title,
mainText, mainText,
input, input,

View File

@@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls namespace Ryujinx.Ava.Ui.Controls
{ {
public class InputDialog : UserControl public partial class InputDialog : UserControl
{ {
public string Message { get; set; } public string Message { get; set; }
public string Input { get; set; } public string Input { get; set; }
@@ -24,8 +24,6 @@ namespace Ryujinx.Ava.Ui.Controls
MaxLength = maxLength; MaxLength = maxLength;
DataContext = this; DataContext = this;
InitializeComponent();
} }
public InputDialog() public InputDialog()
@@ -33,33 +31,26 @@ namespace Ryujinx.Ava.Ui.Controls
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent() public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message,
string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{ {
AvaloniaXamlLoader.Load(this);
}
public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{
ContentDialog contentDialog = window.ContentDialog;
UserResult result = UserResult.Cancel; UserResult result = UserResult.Cancel;
InputDialog content = new InputDialog(message, input = "", subMessage = "", maxLength); InputDialog content = new InputDialog(message, input, subMessage, maxLength);
ContentDialog contentDialog = new ContentDialog
if (contentDialog != null)
{ {
contentDialog.Title = title; Title = title,
contentDialog.PrimaryButtonText = LocaleManager.Instance["InputDialogOk"]; PrimaryButtonText = LocaleManager.Instance["InputDialogOk"],
contentDialog.SecondaryButtonText = ""; SecondaryButtonText = "",
contentDialog.CloseButtonText = LocaleManager.Instance["InputDialogCancel"]; CloseButtonText = LocaleManager.Instance["InputDialogCancel"],
contentDialog.Content = content; Content = content,
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() => PrimaryButtonCommand = MiniCommand.Create(() =>
{ {
result = UserResult.Ok; result = UserResult.Ok;
input = content.Input; input = content.Input;
}); })
await contentDialog.ShowAsync(); };
} await contentDialog.ShowAsync();
return (result, input); return (result, input);
} }

View File

@@ -0,0 +1,10 @@
<UserControl 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"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost">
<ui:Frame HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
x:Name="ContentFrame" />
</UserControl>

View File

@@ -0,0 +1,85 @@
using Avalonia;
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class NavigationDialogHost : UserControl
{
public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; }
public UserProfileViewModel ViewModel { get; set; }
public NavigationDialogHost()
{
InitializeComponent();
}
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
VirtualFileSystem virtualFileSystem)
{
AccountManager = accountManager;
ContentManager = contentManager;
ViewModel = new UserProfileViewModel(this);
if (contentManager.GetCurrentFirmwareVersion() != null)
{
Task.Run(() =>
{
AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
});
}
InitializeComponent();
}
public void GoBack(object parameter = null)
{
if (ContentFrame.BackStack.Count > 0)
{
ContentFrame.GoBack();
}
ViewModel.LoadProfiles();
}
public void Navigate(Type sourcePageType, object parameter)
{
ContentFrame.Navigate(sourcePageType, parameter);
}
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem)
{
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem);
ContentDialog contentDialog = new ContentDialog
{
Title = LocaleManager.Instance["UserProfileWindowTitle"],
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
Content = content,
Padding = new Thickness(0)
};
contentDialog.Closed += (sender, args) =>
{
content.ViewModel.Dispose();
};
await contentDialog.ShowAsync();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
Navigate(typeof(UserSelector), this);
}
}
}

View File

@@ -1,14 +1,10 @@
<Window xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" mc:Ignorable="d"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog" x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog">
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner"
Title="{Locale:Locale ProfileImageSelectionTitle}"
CanResize="false">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -32,4 +28,4 @@
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Window> </UserControl>

View File

@@ -1,8 +1,10 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows; using Ryujinx.Ava.Ui.Windows;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
@@ -12,36 +14,40 @@ using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.Ui.Controls namespace Ryujinx.Ava.Ui.Controls
{ {
public class ProfileImageSelectionDialog : StyleableWindow public partial class ProfileImageSelectionDialog : UserControl
{ {
private readonly ContentManager _contentManager; private ContentManager _contentManager;
private NavigationDialogHost _parent;
private TempProfile _profile;
public bool FirmwareFound => _contentManager.GetCurrentFirmwareVersion() != null; public bool FirmwareFound => _contentManager.GetCurrentFirmwareVersion() != null;
public byte[] BufferImageProfile { get; set; }
public ProfileImageSelectionDialog(ContentManager contentManager)
{
_contentManager = contentManager;
DataContext = this;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
public ProfileImageSelectionDialog() public ProfileImageSelectionDialog()
{ {
DataContext = this;
InitializeComponent(); InitializeComponent();
#if DEBUG AddHandler(Frame.NavigatedToEvent, (s, e) =>
this.AttachDevTools(); {
#endif NavigatedTo(e);
}, RoutingStrategies.Direct);
} }
private void InitializeComponent() private void NavigatedTo(NavigationEventArgs arg)
{ {
AvaloniaXamlLoader.Load(this); if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
_contentManager = _parent.ContentManager;
break;
case NavigationMode.Back:
_parent.GoBack();
break;
}
DataContext = this;
}
} }
private async void Import_OnClick(object sender, RoutedEventArgs e) private async void Import_OnClick(object sender, RoutedEventArgs e)
@@ -58,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Controls
dialog.AllowMultiple = false; dialog.AllowMultiple = false;
string[] image = await dialog.ShowAsync(this); string[] image = await dialog.ShowAsync(((TopLevel)_parent.GetVisualRoot()) as Window);
if (image != null) if (image != null)
{ {
@@ -66,28 +72,22 @@ namespace Ryujinx.Ava.Ui.Controls
{ {
string imageFile = image[0]; string imageFile = image[0];
ProcessProfileImage(File.ReadAllBytes(imageFile)); _profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile));
} }
Close(); _parent.GoBack();
} }
} }
private async void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e) private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
{ {
if (FirmwareFound) if (FirmwareFound)
{ {
AvatarWindow window = new(_contentManager); _parent.Navigate(typeof(AvatarWindow), (_parent, _profile));
await window.ShowDialog(this);
BufferImageProfile = window.SelectedImage;
Close();
} }
} }
private void ProcessProfileImage(byte[] buffer) private static byte[] ProcessProfileImage(byte[] buffer)
{ {
using (Image image = Image.Load(buffer)) using (Image image = Image.Load(buffer))
{ {
@@ -97,7 +97,7 @@ namespace Ryujinx.Ava.Ui.Controls
{ {
image.SaveAsJpeg(streamJpg); image.SaveAsJpeg(streamJpg);
BufferImageProfile = streamJpg.ToArray(); return streamJpg.ToArray();
} }
} }
} }

View File

@@ -5,7 +5,7 @@ using Ryujinx.Ava.Ui.Windows;
namespace Ryujinx.Ava.Ui.Controls namespace Ryujinx.Ava.Ui.Controls
{ {
public class UpdateWaitWindow : StyleableWindow public partial class UpdateWaitWindow : StyleableWindow
{ {
public UpdateWaitWindow(string primaryText, string secondaryText) : this() public UpdateWaitWindow(string primaryText, string secondaryText) : this()
{ {
@@ -21,15 +21,5 @@ namespace Ryujinx.Ava.Ui.Controls
this.AttachDevTools(); this.AttachDevTools();
#endif #endif
} }
public TextBlock PrimaryText { get; private set; }
public TextBlock SecondaryText { get; private set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
PrimaryText = this.FindControl<TextBlock>("PrimaryText");
SecondaryText = this.FindControl<TextBlock>("SecondaryText");
}
} }
} }

View File

@@ -0,0 +1,55 @@
<UserControl 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"
Padding="0"
Margin="0"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
x:Class="Ryujinx.Ava.Ui.Controls.UserEditor">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch" HorizontalAlignment="Left">
<Image
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Height="96" Width="96"
Name="ProfileImage"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<Button Margin="5" Content="{Locale:Locale UserProfilesChangeProfileImage}"
Name="ChangePictureButton"
Click="ChangePictureButton_Click"
HorizontalAlignment="Stretch"/>
<Button Margin="5" Content="{Locale:Locale UserProfilesSetProfileImage}"
Name="AddPictureButton"
Click="ChangePictureButton_Click"
HorizontalAlignment="Stretch"/>
</StackPanel>
<StackPanel Grid.Row="0" Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10"
Margin="5, 10">
<TextBox Name="NameBox" Width="300" Text="{Binding Name}" MaxLength="{Binding MaxProfileNameLength}"
HorizontalAlignment="Stretch" />
<TextBlock Text="{Binding UserId}" Name="IdLabel" />
</StackPanel>
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
<Button Content="{Locale:Locale Save}" Name="SaveButton" Click="SaveButton_Click"/>
<Button HorizontalAlignment="Right" Content="{Locale:Locale Discard}"
Name="CloseButton" Click="CloseButton_Click"/>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,123 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Interactivity;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class UserEditor : UserControl
{
private NavigationDialogHost _parent;
private UserProfile _profile;
private bool _isNewUser;
public TempProfile TempProfile { get; set; }
public uint MaxProfileNameLength => 0x20;
public UserEditor()
{
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_isNewUser = args.isNewUser;
if (!_isNewUser)
{
_profile = args.profile;
TempProfile = new TempProfile(_profile);
}
else
{
TempProfile = new TempProfile();
}
_parent = args.parent;
break;
}
DataContext = TempProfile;
AddPictureButton.IsVisible = _isNewUser;
IdLabel.IsVisible = !_isNewUser;
ChangePictureButton.IsVisible = !_isNewUser;
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
_parent?.GoBack();
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
DataValidationErrors.ClearErrors(NameBox);
bool isInvalid = false;
if (string.IsNullOrWhiteSpace(TempProfile.Name))
{
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance["UserProfileEmptyNameError"]));
isInvalid = true;
}
if (TempProfile.Image == null)
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UserProfileNoImageError"], "");
isInvalid = true;
}
if(isInvalid)
{
return;
}
if (_profile != null)
{
_profile.Name = TempProfile.Name;
_profile.Image = TempProfile.Image;
_profile.UpdateState();
_parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
_parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
}
else if (_isNewUser)
{
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
}
else
{
return;
}
_parent?.GoBack();
}
public void SelectProfileImage()
{
_parent.Navigate(typeof(ProfileImageSelectionDialog), (_parent, TempProfile));
}
private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
{
if (_profile != null || _isNewUser)
{
SelectProfileImage();
}
}
}
}

View File

@@ -75,7 +75,7 @@ namespace Ryujinx.Ava.Ui.Controls
string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance["OpenSetupGuideMessage"] : ""; string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance["OpenSetupGuideMessage"] : "";
var result = await ContentDialogHelper.CreateInfoDialog(owner, var result = await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogUserErrorDialogMessage"], errorCode, GetErrorTitle(error)), string.Format(LocaleManager.Instance["DialogUserErrorDialogMessage"], errorCode, GetErrorTitle(error)),
GetErrorDescription(error) + (isInSetupGuide GetErrorDescription(error) + (isInSetupGuide
? LocaleManager.Instance["DialogUserErrorDialogInfoMessage"] ? LocaleManager.Instance["DialogUserErrorDialogInfoMessage"]

View File

@@ -0,0 +1,90 @@
<UserControl 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"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Ui.Controls.UserSelector">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Design.DataContext>
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5" Items="{Binding Profiles}"
DoubleTapped="ProfilesList_DoubleTapped"
SelectionChanged="SelectingItemsControl_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<flex:FlexPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AlignContent="FlexStart"
JustifyContent="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Height="96" Width="96"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<StackPanel
Grid.Row="1"
Height="30"
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Border>
<Border HorizontalAlignment="Left" VerticalAlignment="Top"
IsVisible="{Binding IsOpened}"
Background="LimeGreen"
Width="10"
Height="10"
Margin="5"
CornerRadius="5" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Center">
<Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" />
<Button IsEnabled="{Binding IsSelectedProfiledEditable}"
Content="{Locale:Locale UserProfilesEditProfile}" Command="{Binding EditUser}" />
<Button IsEnabled="{Binding IsSelectedProfileDeletable}"
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,79 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Ui.ViewModels;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class UserSelector : UserControl
{
private NavigationDialogHost _parent;
public UserProfileViewModel ViewModel { get; set; }
public UserSelector()
{
InitializeComponent();
if (Program.PreviewerDetached)
{
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, Avalonia.Interactivity.RoutingStrategies.Direct);
}
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
_parent = (NavigationDialogHost)arg.Parameter;
ViewModel = _parent.ViewModel;
break;
}
DataContext = ViewModel;
}
}
private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
{
if (sender is ListBox listBox)
{
int selectedIndex = listBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
{
ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
_parent?.AccountManager?.OpenUser(ViewModel.SelectedProfile.UserId);
ViewModel.LoadProfiles();
foreach (UserProfile profile in ViewModel.Profiles)
{
profile.UpdateState();
}
}
}
}
private void SelectingItemsControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ListBox listBox)
{
int selectedIndex = listBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
{
ViewModel.HighlightedProfile = ViewModel.Profiles[selectedIndex];
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
namespace Ryujinx.Ava.Ui.Models
{
public class TempProfile : BaseModel
{
private readonly UserProfile _profile;
private byte[] _image = null;
private string _name = String.Empty;
private UserId _userId;
public byte[] Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
public UserId UserId
{
get => _userId;
set
{
_userId = value;
OnPropertyChanged();
}
}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public TempProfile(UserProfile profile)
{
_profile = profile;
Image = profile.Image;
Name = profile.Name;
UserId = profile.UserId;
}
public TempProfile(){}
}
}

View File

@@ -390,7 +390,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
return amiiboJsonString; return amiiboJsonString;
} }
await ContentDialogHelper.CreateInfoDialog(_owner, LocaleManager.Instance["DialogAmiiboApiTitle"], await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogAmiiboApiTitle"],
LocaleManager.Instance["DialogAmiiboApiFailFetchMessage"], LocaleManager.Instance["DialogAmiiboApiFailFetchMessage"],
LocaleManager.Instance["InputDialogOk"], LocaleManager.Instance["InputDialogOk"],
"", "",
@@ -440,7 +440,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
private async void ShowInfoDialog() private async void ShowInfoDialog()
{ {
await ContentDialogHelper.CreateInfoDialog(_owner, LocaleManager.Instance["DialogAmiiboApiTitle"], await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogAmiiboApiTitle"],
LocaleManager.Instance["DialogAmiiboApiConnectErrorMessage"], LocaleManager.Instance["DialogAmiiboApiConnectErrorMessage"],
LocaleManager.Instance["InputDialogOk"], LocaleManager.Instance["InputDialogOk"],
"", "",

View File

@@ -327,12 +327,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void ShowMotionConfig() public async void ShowMotionConfig()
{ {
await MotionSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow); await MotionSettingsWindow.Show(this);
} }
public async void ShowRumbleConfig() public async void ShowRumbleConfig()
{ {
await RumbleSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow); await RumbleSettingsWindow.Show(this);
} }
private void LoadInputDriver() private void LoadInputDriver()
@@ -701,8 +701,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
catch (InvalidOperationException) catch (InvalidOperationException)
{ {
Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system."); Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system.");
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow,
String.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName)); await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
return; return;
} }
@@ -736,7 +736,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"]) if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"])
{ {
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
return; return;
} }
@@ -769,7 +769,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
else else
{ {
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
} }
} }
} }
@@ -782,7 +782,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
_owner.GetVisualRoot() as StyleableWindow,
LocaleManager.Instance["DialogProfileDeleteProfileTitle"], LocaleManager.Instance["DialogProfileDeleteProfileTitle"],
LocaleManager.Instance["DialogProfileDeleteProfileMessage"], LocaleManager.Instance["DialogProfileDeleteProfileMessage"],
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogYes"],

View File

@@ -535,6 +535,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
} }
public bool ShowConsoleVisible
{
get => ConsoleHelper.SetConsoleWindowStateSupported;
}
public ObservableCollection<ApplicationData> Applications public ObservableCollection<ApplicationData> Applications
{ {
get => _applications; get => _applications;
@@ -703,7 +708,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
return; return;
} }
if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{ {
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper(); string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
@@ -970,9 +975,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void ManageProfiles() public async void ManageProfiles()
{ {
UserProfileWindow window = new(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem); await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
await window.ShowDialog(_owner);
} }
public async void OpenAboutWindow() public async void OpenAboutWindow()
@@ -1049,8 +1052,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
}); });
return; return;
@@ -1133,7 +1135,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1")); DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?). // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, LocaleManager.Instance["DialogWarning"], UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
List<FileInfo> cacheFiles = new(); List<FileInfo> cacheFiles = new();
@@ -1158,7 +1160,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
catch (Exception e) catch (Exception e)
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e)); await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
} }
} }
} }
@@ -1196,7 +1198,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader")); DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?). // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, LocaleManager.Instance["DialogWarning"], UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>(); List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
@@ -1219,7 +1221,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
catch (Exception e) catch (Exception e)
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e)); await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
} }
} }
} }
@@ -1232,7 +1234,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
catch (Exception e) catch (Exception e)
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e)); await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
} }
} }
} }
@@ -1311,12 +1313,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
Task.Run(() => Task.Run(() =>
{ {
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber)) out ulong titleIdNumber))
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
}); });
return; return;
@@ -1337,12 +1338,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
Task.Run(() => Task.Run(() =>
{ {
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber)) out ulong titleIdNumber))
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
}); });
return; return;
@@ -1401,7 +1401,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (firmwareVersion == null) if (firmwareVersion == null)
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename)); await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
return; return;
} }
@@ -1421,7 +1421,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
dialogMessage += LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallConfirmMessage"]; dialogMessage += LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallConfirmMessage"];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
_owner,
dialogTitle, dialogTitle,
dialogMessage, dialogMessage,
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogYes"],
@@ -1451,7 +1450,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString); string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(_owner, dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]); await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
Logger.Info?.Print(LogClass.Application, message); Logger.Info?.Print(LogClass.Application, message);
// Purge Applet Cache. // Purge Applet Cache.
@@ -1470,7 +1469,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
waitingDialog.Close(); waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message); await ContentDialogHelper.CreateErrorDialog(ex.Message);
}); });
} }
finally finally
@@ -1491,7 +1490,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
catch (Exception ex) catch (Exception ex)
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message); await ContentDialogHelper.CreateErrorDialog(ex.Message);
} }
} }

View File

@@ -63,8 +63,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateInfoDialog(_owner, await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogSettingsBackendThreadingWarningMessage"],
LocaleManager.Instance["DialogSettingsBackendThreadingWarningMessage"],
"", "",
"", "",
LocaleManager.Instance["InputDialogOk"], LocaleManager.Instance["InputDialogOk"],

View File

@@ -1,31 +1,27 @@
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile; using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.ViewModels namespace Ryujinx.Ava.Ui.ViewModels
{ {
public class UserProfileViewModel : BaseModel, IDisposable public class UserProfileViewModel : BaseModel, IDisposable
{ {
private const uint MaxProfileNameLength = 0x20; private readonly NavigationDialogHost _owner;
private readonly UserProfileWindow _owner;
private UserProfile _selectedProfile; private UserProfile _selectedProfile;
private string _tempUserName; private UserProfile _highlightedProfile;
public UserProfileViewModel() public UserProfileViewModel()
{ {
Profiles = new ObservableCollection<UserProfile>(); Profiles = new ObservableCollection<UserProfile>();
} }
public UserProfileViewModel(UserProfileWindow owner) : this() public UserProfileViewModel(NavigationDialogHost owner) : this()
{ {
_owner = owner; _owner = owner;
@@ -42,12 +38,29 @@ namespace Ryujinx.Ava.Ui.ViewModels
_selectedProfile = value; _selectedProfile = value;
OnPropertyChanged(nameof(SelectedProfile)); OnPropertyChanged(nameof(SelectedProfile));
OnPropertyChanged(nameof(IsSelectedProfileDeletable)); OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
OnPropertyChanged(nameof(IsHighlightedProfileEditable));
} }
} }
public bool IsSelectedProfileDeletable => public bool IsHighlightedProfileEditable =>
_selectedProfile != null && _selectedProfile.UserId != AccountManager.DefaultUserId; _highlightedProfile != null;
public bool IsHighlightedProfileDeletable =>
_highlightedProfile != null && _highlightedProfile.UserId != AccountManager.DefaultUserId;
public UserProfile HighlightedProfile
{
get => _highlightedProfile;
set
{
_highlightedProfile = value;
OnPropertyChanged(nameof(HighlightedProfile));
OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
OnPropertyChanged(nameof(IsHighlightedProfileEditable));
}
}
public void Dispose() public void Dispose()
{ {
@@ -78,64 +91,24 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
} }
public async void ChooseProfileImage() public void AddUser()
{ {
await SelectProfileImage(); UserProfile userProfile = null;
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
} }
public async Task SelectProfileImage(bool isNewUser = false) public void EditUser()
{ {
ProfileImageSelectionDialog selectionDialog = new(_owner.ContentManager); _owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
await selectionDialog.ShowDialog(_owner);
if (selectionDialog.BufferImageProfile != null)
{
if (isNewUser)
{
if (!string.IsNullOrWhiteSpace(_tempUserName))
{
_owner.AccountManager.AddUser(_tempUserName, selectionDialog.BufferImageProfile);
}
}
else if (SelectedProfile != null)
{
_owner.AccountManager.SetUserImage(SelectedProfile.UserId, selectionDialog.BufferImageProfile);
SelectedProfile.Image = selectionDialog.BufferImageProfile;
SelectedProfile = null;
}
LoadProfiles();
}
}
public async void AddUser()
{
var dlgTitle = LocaleManager.Instance["InputDialogAddNewProfileTitle"];
var dlgMainText = LocaleManager.Instance["InputDialogAddNewProfileHeader"];
var dlgSubText = string.Format(LocaleManager.Instance["InputDialogAddNewProfileSubtext"],
MaxProfileNameLength);
_tempUserName =
await ContentDialogHelper.CreateInputDialog(dlgTitle, dlgMainText, dlgSubText, _owner,
MaxProfileNameLength);
if (!string.IsNullOrWhiteSpace(_tempUserName))
{
await SelectProfileImage(true);
}
_tempUserName = String.Empty;
} }
public async void DeleteUser() public async void DeleteUser()
{ {
if (_selectedProfile != null) if (_highlightedProfile != null)
{ {
var lastUserId = _owner.AccountManager.LastOpenedUser.UserId; var lastUserId = _owner.AccountManager.LastOpenedUser.UserId;
if (_selectedProfile.UserId == lastUserId) if (_highlightedProfile.UserId == lastUserId)
{ {
// If we are deleting the currently open profile, then we must open something else before deleting. // If we are deleting the currently open profile, then we must open something else before deleting.
var profile = Profiles.FirstOrDefault(x => x.UserId != lastUserId); var profile = Profiles.FirstOrDefault(x => x.UserId != lastUserId);
@@ -144,8 +117,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(_owner, await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUserProfileDeletionWarningMessage"]);
LocaleManager.Instance["DialogUserProfileDeletionWarningMessage"]);
}); });
return; return;
@@ -155,13 +127,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
var result = var result =
await ContentDialogHelper.CreateConfirmationDialog(_owner, await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogUserProfileDeletionConfirmMessage"], "",
LocaleManager.Instance["DialogUserProfileDeletionConfirmMessage"], "",
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], ""); LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
_owner.AccountManager.DeleteUser(_selectedProfile.UserId); _owner.AccountManager.DeleteUser(_highlightedProfile.UserId);
} }
} }

View File

@@ -17,43 +17,43 @@
SizeToContent="Width" SizeToContent="Width"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid <Grid
Margin="15" Margin="15"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid <Grid
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
Margin="20" Margin="20"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition /> <RowDefinition />
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="0"> <Grid Grid.Row="0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition /> <ColumnDefinition />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image <Image
Grid.Row="0" Grid.Row="0"
Grid.RowSpan="3" Grid.RowSpan="3"
Grid.Column="0" Grid.Column="0"
@@ -61,21 +61,21 @@
MinWidth="50" MinWidth="50"
Margin="5,10,20,10" Margin="5,10,20,10"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" /> Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Margin="0,20,0,0" Margin="0,20,0,0"
FontSize="35" FontSize="35"
Text="Ryujinx" Text="Ryujinx"
TextAlignment="Center" /> TextAlignment="Center" />
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Margin="0,0,0,0" Margin="0,0,0,0"
FontSize="16" FontSize="16"
Text="(REE-YOU-JINX)" Text="(REE-YOU-JINX)"
TextAlignment="Center" /> TextAlignment="Center" />
<Button <Button
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
Margin="0" Margin="0"
@@ -83,27 +83,27 @@
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
Tag="https://www.ryujinx.org/"> Tag="https://www.ryujinx.org/">
<TextBlock <TextBlock
Text="www.ryujinx.org" Text="www.ryujinx.org"
TextAlignment="Center" TextAlignment="Center"
TextDecorations="Underline" TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" /> ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
</Button> </Button>
</Grid> </Grid>
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Version}" Text="{Binding Version}"
TextAlignment="Center" /> TextAlignment="Center" />
<TextBlock <TextBlock
Grid.Row="2" Grid.Row="2"
Margin="20" Margin="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
MaxLines="2" MaxLines="2"
Text="{locale:Locale AboutDisclaimerMessage}" Text="{locale:Locale AboutDisclaimerMessage}"
TextAlignment="Center" /> TextAlignment="Center" />
<TextBlock <TextBlock
Name="AmiiboLabel" Name="AmiiboLabel"
Grid.Row="3" Grid.Row="3"
Margin="20" Margin="20"
@@ -112,94 +112,94 @@
PointerPressed="AmiiboLabel_OnPointerPressed" PointerPressed="AmiiboLabel_OnPointerPressed"
Text="{locale:Locale AboutAmiiboDisclaimerMessage}" Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
TextAlignment="Center" /> TextAlignment="Center" />
<StackPanel <StackPanel
Grid.Row="4" Grid.Row="4"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="10"> Spacing="10">
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}"> <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Button <Button
Height="65" Height="65"
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
Tag="https://www.patreon.com/ryujinx"> Tag="https://www.patreon.com/ryujinx">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" /> <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
Margin="0,5,0,0" Margin="0,5,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="Patreon" /> Text="Patreon" />
</Grid> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}"> <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Button <Button
Height="65" Height="65"
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx"> Tag="https://github.com/Ryujinx/Ryujinx">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" /> <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
Margin="0,5,0,0" Margin="0,5,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="GitHub" /> Text="GitHub" />
</Grid> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}"> <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Button <Button
Height="65" Height="65"
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
Tag="https://discordapp.com/invite/N2FmfVc"> Tag="https://discordapp.com/invite/N2FmfVc">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" /> <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
Margin="0,5,0,0" Margin="0,5,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="Discord" /> Text="Discord" />
</Grid> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}"> <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
<Button <Button
Height="65" Height="65"
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
Tag="https://twitter.com/RyujinxEmu"> Tag="https://twitter.com/RyujinxEmu">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" /> <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
Margin="0,5,0,0" Margin="0,5,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="Twitter" /> Text="Twitter" />
</Grid> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Border <Border
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Width="2" Width="2"
@@ -207,62 +207,62 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="White" BorderBrush="White"
BorderThickness="1,0,0,0"> BorderThickness="1,0,0,0">
<Separator Width="0" /> <Separator Width="0" />
</Border> </Border>
<Grid <Grid
Grid.Row="1" Grid.Row="1"
Grid.Column="2" Grid.Column="2"
Margin="20" Margin="20"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
FontWeight="Bold" FontWeight="Bold"
Text="{locale:Locale AboutRyujinxAboutTitle}" Text="{locale:Locale AboutRyujinxAboutTitle}"
TextDecorations="Underline" /> TextDecorations="Underline" />
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
Margin="20,5,5,5" Margin="20,5,5,5"
LineHeight="20" LineHeight="20"
Text="{locale:Locale AboutRyujinxAboutContent}" /> Text="{locale:Locale AboutRyujinxAboutContent}" />
<TextBlock <TextBlock
Grid.Row="2" Grid.Row="2"
Margin="0,10,0,0" Margin="0,10,0,0"
FontWeight="Bold" FontWeight="Bold"
Text="{locale:Locale AboutRyujinxMaintainersTitle}" Text="{locale:Locale AboutRyujinxMaintainersTitle}"
TextDecorations="Underline" /> TextDecorations="Underline" />
<TextBlock <TextBlock
Grid.Row="3" Grid.Row="3"
Margin="20,5,5,5" Margin="20,5,5,5"
LineHeight="20" LineHeight="20"
Text="{Binding Developers}" /> Text="{Binding Developers}" />
<Button <Button
Grid.Row="4" Grid.Row="4"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"> Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
<TextBlock <TextBlock
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
TextAlignment="Right" TextAlignment="Right"
TextDecorations="Underline" TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" /> ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
</Button> </Button>
<TextBlock <TextBlock
Grid.Row="5" Grid.Row="5"
Margin="0,0,0,0" Margin="0,0,0,0"
FontWeight="Bold" FontWeight="Bold"
Text="{locale:Locale AboutRyujinxSupprtersTitle}" Text="{locale:Locale AboutRyujinxSupprtersTitle}"
TextDecorations="Underline" /> TextDecorations="Underline" />
<Border <Border
Grid.Row="6" Grid.Row="6"
Width="460" Width="460"
Height="200" Height="200"
@@ -271,12 +271,12 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="White" BorderBrush="White"
BorderThickness="1"> BorderThickness="1">
<TextBlock <TextBlock
Name="SupportersTextBlock" Name="SupportersTextBlock"
VerticalAlignment="Top" VerticalAlignment="Top"
Text="{Binding Supporters}" Text="{Binding Supporters}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</Border> </Border>
</Grid> </Grid>
</Grid> </Grid>
</window:StyleableWindow> </window:StyleableWindow>

View File

@@ -13,7 +13,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class AboutWindow : StyleableWindow public partial class AboutWindow : StyleableWindow
{ {
public AboutWindow() public AboutWindow()
{ {
@@ -39,15 +39,6 @@ namespace Ryujinx.Ava.Ui.Windows
public string Developers => string.Format(LocaleManager.Instance["AboutPageDeveloperListMore"], "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD«"); 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) private void Button_OnClick(object sender, RoutedEventArgs e)
{ {
if (sender is Button button) if (sender is Button button)

View File

@@ -7,7 +7,7 @@ using Ryujinx.Ava.Ui.ViewModels;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class AmiiboWindow : StyleableWindow public partial class AmiiboWindow : StyleableWindow
{ {
public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId) public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId)
{ {
@@ -44,11 +44,6 @@ namespace Ryujinx.Ava.Ui.Windows
public Amiibo.AmiiboApi ScannedAmiibo { get; set; } public Amiibo.AmiiboApi ScannedAmiibo { get; set; }
public AmiiboWindowViewModel ViewModel { get; set; } public AmiiboWindowViewModel ViewModel { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void ScanButton_Click(object sender, RoutedEventArgs e) private void ScanButton_Click(object sender, RoutedEventArgs e)
{ {
if (ViewModel.AmiiboSelectedIndex > -1) if (ViewModel.AmiiboSelectedIndex > -1)

View File

@@ -1,36 +1,35 @@
<Window xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350" mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
x:Class="Ryujinx.Ava.Ui.Windows.AvatarWindow" x:Class="Ryujinx.Ava.Ui.Windows.AvatarWindow"
CanResize="False" Margin="0"
Padding="0"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
WindowStartupLocation="CenterOwner"
x:CompileBindings="True" x:CompileBindings="True"
x:DataType="viewModels:AvatarProfileViewModel" x:DataType="viewModels:AvatarProfileViewModel">
SizeToContent="WidthAndHeight">
<Design.DataContext> <Design.DataContext>
<viewModels:AvatarProfileViewModel /> <viewModels:AvatarProfileViewModel />
</Design.DataContext> </Design.DataContext>
<Window.Resources> <UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" /> <controls:BitmapArrayValueConverter x:Key="ByteImage" />
</Window.Resources> </UserControl.Resources>
<Grid Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Width="600" Height="500" <ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Height="400"
Items="{Binding Images}" HorizontalAlignment="Stretch" VerticalAlignment="Center"> Items="{Binding Images}" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<ListBox.ItemsPanel> <ListBox.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" MaxWidth="600" Margin="0" HorizontalAlignment="Center" /> <WrapPanel Orientation="Horizontal" MaxWidth="700" Margin="0" HorizontalAlignment="Center" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ListBox.ItemsPanel> </ListBox.ItemsPanel>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
@@ -45,9 +44,9 @@
<StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="10" HorizontalAlignment="Center"> <StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="10" HorizontalAlignment="Center">
<Button Content="{Locale:Locale AvatarChoose}" Width="200" Name="ChooseButton" Click="ChooseButton_OnClick" /> <Button Content="{Locale:Locale AvatarChoose}" Width="200" Name="ChooseButton" Click="ChooseButton_OnClick" />
<ui:ColorPickerButton Color="{Binding BackgroundColor, Mode=TwoWay}" Name="ColorButton" /> <ui:ColorPickerButton Color="{Binding BackgroundColor, Mode=TwoWay}" Name="ColorButton" />
<Button HorizontalAlignment="Right" Content="{Locale:Locale AvatarClose}" Click="CloseButton_OnClick" <Button HorizontalAlignment="Right" Content="{Locale:Locale Discard}" Click="CloseButton_OnClick"
Name="CloseButton" Name="CloseButton"
Width="200" /> Width="200" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Window> </UserControl>

View File

@@ -1,70 +1,76 @@
using Avalonia; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using System;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class AvatarWindow : StyleableWindow public partial class AvatarWindow : UserControl
{ {
private NavigationDialogHost _parent;
private TempProfile _profile;
public AvatarWindow(ContentManager contentManager) public AvatarWindow(ContentManager contentManager)
{ {
ContentManager = contentManager; ContentManager = contentManager;
ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages());
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"];
} }
public AvatarWindow() public AvatarWindow()
{ {
InitializeComponent(); InitializeComponent();
#if DEBUG
this.AttachDevTools(); AddHandler(Frame.NavigatedToEvent, (s, e) =>
#endif {
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"]; if (arg.NavigationMode == NavigationMode.New)
{
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
ContentManager = _parent.ContentManager;
if (Program.PreviewerDetached)
{
ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages());
}
DataContext = ViewModel;
}
} }
} }
public ContentManager ContentManager { get; } public ContentManager ContentManager { get; private set; }
public byte[] SelectedImage { get; set; }
internal AvatarProfileViewModel ViewModel { get; set; } internal AvatarProfileViewModel ViewModel { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override void OnClosed(EventArgs e)
{
ViewModel.Dispose();
base.OnClosed(e);
}
private void CloseButton_OnClick(object sender, RoutedEventArgs e) private void CloseButton_OnClick(object sender, RoutedEventArgs e)
{ {
Close(); ViewModel.Dispose();
_parent.GoBack();
} }
private void ChooseButton_OnClick(object sender, RoutedEventArgs e) private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
{ {
if (ViewModel.SelectedIndex > -1) if (ViewModel.SelectedIndex > -1)
{ {
SelectedImage = ViewModel.SelectedImage; _profile.Image = ViewModel.SelectedImage;
Close(); ViewModel.Dispose();
_parent.GoBack();
} }
} }
} }

View File

@@ -12,7 +12,7 @@ using System.Linq;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class CheatWindow : StyleableWindow public partial class CheatWindow : StyleableWindow
{ {
private readonly string _enabledCheatsPath; private readonly string _enabledCheatsPath;
public bool NoCheatsFound { get; } public bool NoCheatsFound { get; }
@@ -102,11 +102,6 @@ namespace Ryujinx.Ava.Ui.Windows
this.AttachDevTools(); this.AttachDevTools();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void Save() public void Save()
{ {
if (NoCheatsFound) if (NoCheatsFound)

View File

@@ -24,11 +24,10 @@ using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class ControllerSettingsWindow : UserControl public partial class ControllerSettingsWindow : UserControl
{ {
private bool _dialogOpen; private bool _dialogOpen;
public Grid SettingButtons { get; set; }
private ButtonKeyAssigner _currentAssigner; private ButtonKeyAssigner _currentAssigner;
internal ControllerSettingsViewModel ViewModel { get; set; } internal ControllerSettingsViewModel ViewModel { get; set; }
@@ -48,13 +47,6 @@ namespace Ryujinx.Ava.Ui.Windows
} }
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
SettingButtons = this.FindControl<Grid>("SettingButtons");
}
protected override void OnPointerReleased(PointerReleasedEventArgs e) protected override void OnPointerReleased(PointerReleasedEventArgs e)
{ {
base.OnPointerReleased(e); base.OnPointerReleased(e);
@@ -165,7 +157,6 @@ namespace Ryujinx.Ava.Ui.Windows
_dialogOpen = true; _dialogOpen = true;
var result = await ContentDialogHelper.CreateConfirmationDialog( var result = await ContentDialogHelper.CreateConfirmationDialog(
this.GetVisualRoot() as StyleableWindow,
LocaleManager.Instance["DialogControllerSettingsModifiedConfirmMessage"], LocaleManager.Instance["DialogControllerSettingsModifiedConfirmMessage"],
LocaleManager.Instance["DialogControllerSettingsModifiedConfirmSubMessage"], LocaleManager.Instance["DialogControllerSettingsModifiedConfirmSubMessage"],
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogYes"],

View File

@@ -1,7 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.Threading;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
@@ -27,7 +26,7 @@ using Path = System.IO.Path;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class DlcManagerWindow : StyleableWindow public partial class DlcManagerWindow : StyleableWindow
{ {
private readonly List<DlcContainer> _dlcContainerList; private readonly List<DlcContainer> _dlcContainerList;
private readonly string _dlcJsonPath; private readonly string _dlcJsonPath;
@@ -35,7 +34,6 @@ namespace Ryujinx.Ava.Ui.Windows
public VirtualFileSystem VirtualFileSystem { get; } public VirtualFileSystem VirtualFileSystem { get; }
public AvaloniaList<DlcModel> Dlcs { get; set; } public AvaloniaList<DlcModel> Dlcs { get; set; }
public Grid DlcGrid { get; private set; }
public ulong TitleId { get; } public ulong TitleId { get; }
public string TitleName { get; } public string TitleName { get; }
@@ -47,7 +45,7 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
AttachDebugDevTools(); AttachDebugDevTools();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
} }
@@ -72,7 +70,7 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
AttachDebugDevTools(); AttachDebugDevTools();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
LoadDlcs(); LoadDlcs();
@@ -84,15 +82,6 @@ namespace Ryujinx.Ava.Ui.Windows
this.AttachDevTools(); this.AttachDevTools();
} }
private void InitializeComponent()
{
Dlcs = new AvaloniaList<DlcModel>();
AvaloniaXamlLoader.Load(this);
DlcGrid = this.FindControl<Grid>("DlcGrid");
}
private void LoadDlcs() private void LoadDlcs()
{ {
foreach (DlcContainer dlcContainer in _dlcContainerList) foreach (DlcContainer dlcContainer in _dlcContainerList)
@@ -129,8 +118,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(this, await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[
string.Format(LocaleManager.Instance[
"DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath)); "DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath));
}); });
} }
@@ -180,7 +168,7 @@ namespace Ryujinx.Ava.Ui.Windows
if (!containsDlc) if (!containsDlc)
{ {
await ContentDialogHelper.CreateErrorDialog(this, LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
} }
} }
} }
@@ -189,7 +177,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
if (removeSelectedOnly) if (removeSelectedOnly)
{ {
Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList()); Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList());
} }
else else
{ {

View File

@@ -38,18 +38,6 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" /> <controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
<ContentControl
Grid.Row="1"
Focusable="False"
IsVisible="False"
KeyboardNavigation.IsTabStop="False">
<ui:ContentDialog
Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="True"
KeyboardNavigation.IsTabStop="False" />
</ContentControl>
<StackPanel Grid.Row="0" IsVisible="False"> <StackPanel Grid.Row="0" IsVisible="False">
<controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" /> <controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
<controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" /> <controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
@@ -128,7 +116,7 @@
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" /> <CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="{locale:Locale MenuBarOptionsShowConsole}"> <MenuItem Header="{locale:Locale MenuBarOptionsShowConsole}" IsVisible="{Binding ShowConsoleVisible}">
<MenuItem.Icon> <MenuItem.Icon>
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}" /> <CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}" />
</MenuItem.Icon> </MenuItem.Icon>

View File

@@ -36,7 +36,7 @@ using InputManager = Ryujinx.Input.HLE.InputManager;
using ProgressBar = Avalonia.Controls.ProgressBar; using ProgressBar = Avalonia.Controls.ProgressBar;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class MainWindow : StyleableWindow public partial class MainWindow : StyleableWindow
{ {
private bool _canUpdate; private bool _canUpdate;
private bool _isClosing; private bool _isClosing;
@@ -62,22 +62,6 @@ namespace Ryujinx.Ava.Ui.Windows
public InputManager InputManager { get; private set; } public InputManager InputManager { get; private set; }
internal RendererControl GlRenderer { get; private set; } internal 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; }
internal MainWindowViewModel ViewModel { get; private set; } internal MainWindowViewModel ViewModel { get; private set; }
public SettingsWindow SettingsWindow { get; set; } public SettingsWindow SettingsWindow { get; set; }
@@ -102,6 +86,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
Load();
AttachDebugDevTools(); AttachDebugDevTools();
UiHandler = new AvaHostUiHandler(this); UiHandler = new AvaHostUiHandler(this);
@@ -192,7 +177,9 @@ namespace Ryujinx.Ava.Ui.Windows
string mainMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledMessage"]; string mainMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledMessage"];
string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledConfirmMessage"]; string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledConfirmMessage"];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(this, mainMessage, secondaryMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
if (result != UserResult.Yes) if (result != UserResult.Yes)
{ {
@@ -205,9 +192,12 @@ namespace Ryujinx.Ava.Ui.Windows
if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
{ {
string mainMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledMessage"]; string mainMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledMessage"];
string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"]; string secondaryMessage =
LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(this, mainMessage, secondaryMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
if (result != UserResult.Yes) if (result != UserResult.Yes)
{ {
@@ -231,7 +221,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
if (AppHost != null) if (AppHost != null)
{ {
await ContentDialogHelper.CreateInfoDialog(this, await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedMessage"], LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedMessage"],
LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedSubMessage"], LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedSubMessage"],
LocaleManager.Instance["InputDialogOk"], LocaleManager.Instance["InputDialogOk"],
@@ -254,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows
PrepareLoadScreen(); PrepareLoadScreen();
_mainViewContent = ContentFrame.Content as Control; _mainViewContent = Content.Content as Control;
GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel); GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this); AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
@@ -321,7 +311,7 @@ namespace Ryujinx.Ava.Ui.Windows
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
ContentFrame.Content = GlRenderer; Content.Content = GlRenderer;
if (startFullscreen && WindowState != WindowState.FullScreen) if (startFullscreen && WindowState != WindowState.FullScreen)
{ {
@@ -365,9 +355,9 @@ namespace Ryujinx.Ava.Ui.Windows
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
if (ContentFrame.Content != _mainViewContent) if (Content.Content != _mainViewContent)
{ {
ContentFrame.Content = _mainViewContent; Content.Content = _mainViewContent;
} }
ViewModel.ShowMenuAndStatusBar = true; ViewModel.ShowMenuAndStatusBar = true;
@@ -501,27 +491,8 @@ namespace Ryujinx.Ava.Ui.Windows
ViewModel.IsAppletMenuActive = hasApplet; ViewModel.IsAppletMenuActive = hasApplet;
} }
private void InitializeComponent() private void Load()
{ {
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; VolumeStatus.Click += VolumeStatus_CheckedChanged;
GameGrid.ApplicationOpened += Application_Opened; GameGrid.ApplicationOpened += Application_Opened;
@@ -710,7 +681,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
_isClosing = await ContentDialogHelper.CreateExitDialog(this); _isClosing = await ContentDialogHelper.CreateExitDialog();
if (_isClosing) if (_isClosing)
{ {

View File

@@ -9,13 +9,14 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class MotionSettingsWindow : UserControl public partial class MotionSettingsWindow : UserControl
{ {
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel; private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
public MotionSettingsWindow() public MotionSettingsWindow()
{ {
InitializeComponent(); InitializeComponent();
DataContext = _viewmodel;
} }
public MotionSettingsWindow(ControllerSettingsViewModel viewmodel) public MotionSettingsWindow(ControllerSettingsViewModel viewmodel)
@@ -36,46 +37,36 @@ namespace Ryujinx.Ava.Ui.Windows
}; };
InitializeComponent(); InitializeComponent();
}
private void InitializeComponent()
{
DataContext = _viewmodel; DataContext = _viewmodel;
AvaloniaXamlLoader.Load(this);
} }
public static async Task Show(ControllerSettingsViewModel viewmodel, StyleableWindow window) public static async Task Show(ControllerSettingsViewModel viewmodel)
{ {
ContentDialog contentDialog = window.ContentDialog;
string name = string.Empty;
MotionSettingsWindow content = new MotionSettingsWindow(viewmodel); MotionSettingsWindow content = new MotionSettingsWindow(viewmodel);
if (contentDialog != null) ContentDialog contentDialog = new ContentDialog
{ {
contentDialog.Title = LocaleManager.Instance["ControllerMotionTitle"]; Title = LocaleManager.Instance["ControllerMotionTitle"],
contentDialog.PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"]; PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"],
contentDialog.SecondaryButtonText = ""; SecondaryButtonText = "",
contentDialog.CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"]; CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"],
contentDialog.Content = content; Content = content
contentDialog.PrimaryButtonClick += (sender, args) => };
{ contentDialog.PrimaryButtonClick += (sender, args) =>
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; {
config.Slot = content._viewmodel.Slot; var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.EnableMotion = content._viewmodel.EnableMotion; config.Slot = content._viewmodel.Slot;
config.Sensitivity = content._viewmodel.Sensitivity; config.EnableMotion = content._viewmodel.EnableMotion;
config.GyroDeadzone = content._viewmodel.GyroDeadzone; config.Sensitivity = content._viewmodel.Sensitivity;
config.AltSlot = content._viewmodel.AltSlot; config.GyroDeadzone = content._viewmodel.GyroDeadzone;
config.DsuServerHost = content._viewmodel.DsuServerHost; config.AltSlot = content._viewmodel.AltSlot;
config.DsuServerPort = content._viewmodel.DsuServerPort; config.DsuServerHost = content._viewmodel.DsuServerHost;
config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion; config.DsuServerPort = content._viewmodel.DsuServerPort;
config.MirrorInput = content._viewmodel.MirrorInput; config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
}; config.MirrorInput = content._viewmodel.MirrorInput;
};
await contentDialog.ShowAsync(); await contentDialog.ShowAsync();
}
} }
} }
} }

View File

@@ -9,13 +9,14 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class RumbleSettingsWindow : UserControl public partial class RumbleSettingsWindow : UserControl
{ {
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel; private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
public RumbleSettingsWindow() public RumbleSettingsWindow()
{ {
InitializeComponent(); InitializeComponent();
DataContext = _viewmodel;
} }
public RumbleSettingsWindow(ControllerSettingsViewModel viewmodel) public RumbleSettingsWindow(ControllerSettingsViewModel viewmodel)
@@ -24,44 +25,34 @@ namespace Ryujinx.Ava.Ui.Windows
_viewmodel = new InputConfiguration<GamepadInputId, StickInputId>() _viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
{ {
StrongRumble = config.StrongRumble, StrongRumble = config.StrongRumble, WeakRumble = config.WeakRumble
WeakRumble = config.WeakRumble
}; };
InitializeComponent(); InitializeComponent();
}
private void InitializeComponent()
{
DataContext = _viewmodel; DataContext = _viewmodel;
AvaloniaXamlLoader.Load(this);
} }
public static async Task Show(ControllerSettingsViewModel viewmodel, StyleableWindow window) public static async Task Show(ControllerSettingsViewModel viewmodel)
{ {
ContentDialog contentDialog = window.ContentDialog;
string name = string.Empty;
RumbleSettingsWindow content = new RumbleSettingsWindow(viewmodel); RumbleSettingsWindow content = new RumbleSettingsWindow(viewmodel);
if (contentDialog != null) ContentDialog contentDialog = new ContentDialog
{ {
contentDialog.Title = LocaleManager.Instance["ControllerRumbleTitle"]; Title = LocaleManager.Instance["ControllerRumbleTitle"],
contentDialog.PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"]; PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"],
contentDialog.SecondaryButtonText = ""; SecondaryButtonText = "",
contentDialog.CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"]; CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"],
contentDialog.Content = content; Content = content,
contentDialog.PrimaryButtonClick += (sender, args) => };
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; contentDialog.PrimaryButtonClick += (sender, args) =>
config.StrongRumble = content._viewmodel.StrongRumble; {
config.WeakRumble = content._viewmodel.WeakRumble; var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
}; config.StrongRumble = content._viewmodel.StrongRumble;
config.WeakRumble = content._viewmodel.WeakRumble;
await contentDialog.ShowAsync(); };
}
await contentDialog.ShowAsync();
} }
} }
} }

View File

@@ -31,16 +31,11 @@
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ContentControl <ContentPresenter
x:Name="ContentPresenter"
Grid.Row="1" Grid.Row="1"
Focusable="False"
IsVisible="False" IsVisible="False"
KeyboardNavigation.IsTabStop="False"> KeyboardNavigation.IsTabStop="False"/>
<ui:ContentDialog Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="False" />
</ContentControl>
<Grid Name="Pages" IsVisible="False" Grid.Row="2"> <Grid Name="Pages" IsVisible="False" Grid.Row="2">
<ScrollViewer Name="UiPage" <ScrollViewer Name="UiPage"
Margin="0,0,10,0" Margin="0,0,10,0"

View File

@@ -1,5 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
@@ -28,24 +29,8 @@ using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class SettingsWindow : StyleableWindow public partial class SettingsWindow : StyleableWindow
{ {
private ListBox _gameList;
private TextBox _pathBox;
private AutoCompleteBox _timeZoneBox;
private ControllerSettingsWindow _controllerSettings;
// Pages
private Control _uiPage;
private Control _inputPage;
private Control _hotkeysPage;
private Control _systemPage;
private Control _cpuPage;
private Control _graphicsPage;
private Control _audioPage;
private Control _networkPage;
private Control _loggingPage;
private NavigationView _navPanel;
private ButtonKeyAssigner _currentAssigner; private ButtonKeyAssigner _currentAssigner;
internal SettingsViewModel ViewModel { get; set; } internal SettingsViewModel ViewModel { get; set; }
@@ -58,6 +43,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
Load();
AttachDebugDevTools(); AttachDebugDevTools();
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray())); FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()));
@@ -66,7 +52,7 @@ namespace Ryujinx.Ava.Ui.Windows
tzMultiBinding.Bindings.Add(new Binding("Location")); tzMultiBinding.Bindings.Add(new Binding("Location"));
tzMultiBinding.Bindings.Add(new Binding("Abbreviation")); tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
_timeZoneBox.ValueMemberBinding = tzMultiBinding; TimeZoneBox.ValueMemberBinding = tzMultiBinding;
} }
public SettingsWindow() public SettingsWindow()
@@ -75,6 +61,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
Load();
AttachDebugDevTools(); AttachDebugDevTools();
} }
@@ -84,31 +71,11 @@ namespace Ryujinx.Ava.Ui.Windows
this.AttachDevTools(); this.AttachDevTools();
} }
private void InitializeComponent() private void Load()
{ {
AvaloniaXamlLoader.Load(this); Pages.Children.Clear();
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
_pathBox = this.FindControl<TextBox>("PathBox"); NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
_gameList = this.FindControl<ListBox>("GameList");
_timeZoneBox = this.FindControl<AutoCompleteBox>("TimeZoneBox");
_controllerSettings = this.FindControl<ControllerSettingsWindow>("ControllerSettings");
_uiPage = this.FindControl<Control>("UiPage");
_inputPage = this.FindControl<Control>("InputPage");
_hotkeysPage = this.FindControl<Control>("HotkeysPage");
_systemPage = this.FindControl<Control>("SystemPage");
_cpuPage = this.FindControl<Control>("CpuPage");
_graphicsPage = this.FindControl<Control>("GraphicsPage");
_audioPage = this.FindControl<Control>("AudioPage");
_networkPage = this.FindControl<Control>("NetworkPage");
_loggingPage = this.FindControl<Control>("LoggingPage");
var pageGrid = this.FindControl<Grid>("Pages");
pageGrid.Children.Clear();
_navPanel = this.FindControl<NavigationView>("NavPanel");
_navPanel.SelectionChanged += NavPanelOnSelectionChanged;
_navPanel.SelectedItem = _navPanel.MenuItems.ElementAt(0);
} }
private void Button_Checked(object sender, RoutedEventArgs e) private void Button_Checked(object sender, RoutedEventArgs e)
@@ -174,31 +141,31 @@ namespace Ryujinx.Ava.Ui.Windows
switch (navitem.Tag.ToString()) switch (navitem.Tag.ToString())
{ {
case "UiPage": case "UiPage":
_navPanel.Content = _uiPage; NavPanel.Content = UiPage;
break; break;
case "InputPage": case "InputPage":
_navPanel.Content = _inputPage; NavPanel.Content = InputPage;
break; break;
case "HotkeysPage": case "HotkeysPage":
_navPanel.Content = _hotkeysPage; NavPanel.Content = HotkeysPage;
break; break;
case "SystemPage": case "SystemPage":
_navPanel.Content = _systemPage; NavPanel.Content = SystemPage;
break; break;
case "CpuPage": case "CpuPage":
_navPanel.Content = _cpuPage; NavPanel.Content = CpuPage;
break; break;
case "GraphicsPage": case "GraphicsPage":
_navPanel.Content = _graphicsPage; NavPanel.Content = GraphicsPage;
break; break;
case "AudioPage": case "AudioPage":
_navPanel.Content = _audioPage; NavPanel.Content = AudioPage;
break; break;
case "NetworkPage": case "NetworkPage":
_navPanel.Content = _networkPage; NavPanel.Content = NetworkPage;
break; break;
case "LoggingPage": case "LoggingPage":
_navPanel.Content = _loggingPage; NavPanel.Content = LoggingPage;
break; break;
} }
} }
@@ -206,7 +173,7 @@ namespace Ryujinx.Ava.Ui.Windows
private async void AddButton_OnClick(object sender, RoutedEventArgs e) private async void AddButton_OnClick(object sender, RoutedEventArgs e)
{ {
string path = _pathBox.Text; string path = PathBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path)) if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
{ {
@@ -225,7 +192,7 @@ namespace Ryujinx.Ava.Ui.Windows
private void RemoveButton_OnClick(object sender, RoutedEventArgs e) private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
{ {
List<string> selected = new(_gameList.SelectedItems.Cast<string>()); List<string> selected = new(GameList.SelectedItems.Cast<string>());
foreach (string path in selected) foreach (string path in selected)
{ {
@@ -279,7 +246,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
ViewModel.SaveSettings(); ViewModel.SaveSettings();
_controllerSettings?.SaveCurrentProfile(); ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window) if (Owner is MainWindow window)
{ {
@@ -289,7 +256,7 @@ namespace Ryujinx.Ava.Ui.Windows
protected override void OnClosed(EventArgs e) protected override void OnClosed(EventArgs e)
{ {
_controllerSettings.Dispose(); ControllerSettings.Dispose();
_currentAssigner?.Cancel(); _currentAssigner?.Cancel();
_currentAssigner = null; _currentAssigner = null;
base.OnClosed(e); base.OnClosed(e);

View File

@@ -11,7 +11,6 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
public class StyleableWindow : Window public class StyleableWindow : Window
{ {
public ContentDialog ContentDialog { get; private set; }
public IBitmap IconImage { get; set; } public IBitmap IconImage { get; set; }
public StyleableWindow() public StyleableWindow()
@@ -26,15 +25,9 @@ namespace Ryujinx.Ava.Ui.Windows
IconImage = new Bitmap(stream); IconImage = new Bitmap(stream);
} }
public void LoadDialog()
{
ContentDialog = this.FindControl<ContentDialog>("ContentDialog");
}
protected override void OnOpened(EventArgs e) protected override void OnOpened(EventArgs e)
{ {
base.OnOpened(e); base.OnOpened(e);
ContentDialog = this.FindControl<ContentDialog>("ContentDialog");
} }
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)

View File

@@ -28,14 +28,14 @@ using Avalonia.Threading;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class TitleUpdateWindow : StyleableWindow public partial class TitleUpdateWindow : StyleableWindow
{ {
private readonly string _updateJsonPath; private readonly string _updateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData; private TitleUpdateMetadata _titleUpdateWindowData;
public VirtualFileSystem VirtualFileSystem { get; } public VirtualFileSystem VirtualFileSystem { get; }
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = new AvaloniaList<TitleUpdateModel>();
public string TitleId { get; } public string TitleId { get; }
public string TitleName { get; } public string TitleName { get; }
@@ -84,13 +84,6 @@ namespace Ryujinx.Ava.Ui.Windows
this.AttachDevTools(); this.AttachDevTools();
} }
private void InitializeComponent()
{
TitleUpdates = new AvaloniaList<TitleUpdateModel>();
AvaloniaXamlLoader.Load(this);
}
private void LoadUpdates() private void LoadUpdates()
{ {
TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true)); TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
@@ -154,8 +147,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(this, await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
}); });
} }
} }
@@ -163,8 +155,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(this, await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
}); });
} }
} }

View File

@@ -11,7 +11,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public class UpdaterWindow : StyleableWindow public partial class UpdaterWindow : StyleableWindow
{ {
private readonly string _buildUrl; private readonly string _buildUrl;
private readonly MainWindow _mainWindow; private readonly MainWindow _mainWindow;
@@ -36,21 +36,6 @@ namespace Ryujinx.Ava.Ui.Windows
_buildUrl = buildUrl; _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)] [DllImport("libc", SetLastError = true)]
private static extern int chmod(string path, uint mode); private static extern int chmod(string path, uint mode);

View File

@@ -1,107 +0,0 @@
<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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
x:Class="Ryujinx.Ava.Ui.Windows.UserProfileWindow"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
CanResize="False"
Width="850" MinHeight="550" Height="550"
WindowStartupLocation="CenterOwner"
SizeToContent="Manual"
MinWidth="600">
<Design.DataContext>
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Window.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</Window.Resources>
<Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ContentControl
Focusable="False"
IsVisible="False"
KeyboardNavigation.IsTabStop="False">
<ui:ContentDialog Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="False" />
</ContentControl>
<TextBlock Text="{Locale:Locale UserProfilesSelectedUserProfile}" />
<Grid Grid.Row="1" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Height="96" Width="96"
Source="{Binding SelectedProfile.Image, Converter={StaticResource ByteImage}}" />
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10"
Margin="5, 10">
<TextBox Name="NameBox" Text="{Binding SelectedProfile.Name, Mode=OneWay}"
HorizontalAlignment="Stretch" />
<TextBlock Text="{Binding SelectedProfile.UserId}" />
</StackPanel>
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="2" Spacing="10"
Margin="5">
<Button Content="{Locale:Locale UserProfilesSaveProfileName}" Name="SetNameButton"
Click="SetNameButton_OnClick" />
<Button Name="SelectProfileImage" Command="{Binding ChooseProfileImage}"
Content="{Locale:Locale UserProfilesChangeProfileImage}" />
</StackPanel>
</Grid>
</Grid>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Locale:Locale UserProfilesAvailableUserProfiles}" />
<ListBox Grid.Row="1" Margin="10" Name="ProfilesList" DoubleTapped="ProfilesList_DoubleTapped"
Items="{Binding Profiles}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="{DynamicResource ThemeAccentColorBrush}"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="5" MinWidth="5"
IsVisible="{Binding IsOpened}" />
<Image Grid.Column="0" Height="96" Width="96"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<StackPanel Margin="10" Orientation="Vertical" HorizontalAlignment="Stretch"
VerticalAlignment="Center" Grid.Column="1">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding UserId}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Stretch">
<Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" />
<Button IsEnabled="{Binding IsSelectedProfileDeletable}"
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" />
<Button HorizontalAlignment="Right" Content="{Locale:Locale UserProfilesClose}" Click="CloseButton_OnClick"
Name="CloseButton" />
</StackPanel>
</Grid>
</window:StyleableWindow>

View File

@@ -1,102 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.Windows
{
public class UserProfileWindow : StyleableWindow
{
private TextBox _nameBox;
public UserProfileWindow(AccountManager accountManager, ContentManager contentManager,
VirtualFileSystem virtualFileSystem)
{
AccountManager = accountManager;
ContentManager = contentManager;
ViewModel = new UserProfileViewModel(this);
DataContext = ViewModel;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
if (contentManager.GetCurrentFirmwareVersion() != null)
{
Task.Run(() =>
{
AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
});
}
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"];
}
public UserProfileWindow()
{
ViewModel = new UserProfileViewModel();
DataContext = ViewModel;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"];
}
public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; }
public UserProfileViewModel ViewModel { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_nameBox = this.FindControl<TextBox>("NameBox");
}
private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
{
if (sender is ListBox listBox)
{
int selectedIndex = listBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
{
ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
AccountManager.OpenUser(ViewModel.SelectedProfile.UserId);
ViewModel.LoadProfiles();
foreach (UserProfile profile in ViewModel.Profiles)
{
profile.UpdateState();
}
}
}
}
private void CloseButton_OnClick(object sender, RoutedEventArgs e)
{
Close();
}
private void SetNameButton_OnClick(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(_nameBox.Text))
{
ViewModel.SelectedProfile.Name = _nameBox.Text;
AccountManager.SetUserName(ViewModel.SelectedProfile.UserId, _nameBox.Text);
}
}
}
}

View File

@@ -208,7 +208,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
} }
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true); ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true);
Span<byte> dstSpan = memoryManager.GetSpan(dstGpuVa + (ulong)dstBaseOffset, dstSize).ToArray();
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount); bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount); bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
@@ -262,43 +261,63 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
target.SynchronizeMemory(); target.SynchronizeMemory();
target.SetData(data); target.SetData(data);
target.SignalModified(); target.SignalModified();
return; return;
} }
else if (srcCalculator.LayoutMatches(dstCalculator)) else if (srcCalculator.LayoutMatches(dstCalculator))
{ {
srcSpan.CopyTo(dstSpan); // No layout conversion has to be performed, just copy the data entirely. // No layout conversion has to be performed, just copy the data entirely.
memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, srcSpan);
memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, dstSpan);
return; return;
} }
} }
unsafe bool Convert<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan) where T : unmanaged unsafe bool Convert<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan) where T : unmanaged
{ {
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan) if (srcLinear && dstLinear && srcBpp == dstBpp)
{ {
byte* dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset. // Optimized path for purely linear copies - we don't need to calculate every single byte offset,
byte* srcBase = srcPtr - srcBaseOffset; // and we can make use of Span.CopyTo which is very very fast (even compared to pointers)
for (int y = 0; y < yCount; y++) for (int y = 0; y < yCount; y++)
{ {
srcCalculator.SetY(srcRegionY + y); srcCalculator.SetY(srcRegionY + y);
dstCalculator.SetY(dstRegionY + y); dstCalculator.SetY(dstRegionY + y);
int srcOffset = srcCalculator.GetOffset(srcRegionX);
int dstOffset = dstCalculator.GetOffset(dstRegionX);
srcSpan.Slice(srcOffset - srcBaseOffset, xCount * srcBpp)
.CopyTo(dstSpan.Slice(dstOffset - dstBaseOffset, xCount * dstBpp));
}
}
else
{
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
{
byte* dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
byte* srcBase = srcPtr - srcBaseOffset;
for (int x = 0; x < xCount; x++) for (int y = 0; y < yCount; y++)
{ {
int srcOffset = srcCalculator.GetOffset(srcRegionX + x); srcCalculator.SetY(srcRegionY + y);
int dstOffset = dstCalculator.GetOffset(dstRegionX + x); dstCalculator.SetY(dstRegionY + y);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset); for (int x = 0; x < xCount; x++)
{
int srcOffset = srcCalculator.GetOffset(srcRegionX + x);
int dstOffset = dstCalculator.GetOffset(dstRegionX + x);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
}
} }
} }
} }
return true; return true;
} }
// OPT: This allocates a (potentially) huge temporary array and then copies an existing
// region of memory into it, data that might get overwritten entirely anyways. Ideally this should
// all be rewritten to use pooled arrays, but that gets complicated with packed data and strides
Span<byte> dstSpan = memoryManager.GetSpan(dstGpuVa + (ulong)dstBaseOffset, dstSize).ToArray();
bool _ = srcBpp switch bool _ = srcBpp switch
{ {
1 => Convert<byte>(dstSpan, srcSpan), 1 => Convert<byte>(dstSpan, srcSpan),

View File

@@ -174,6 +174,14 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
if (info.Width == info.Height * info.Height)
{
// Possibly used for a "3D texture" drawn onto a 2D surface.
// Some games do this to generate a tone mapping LUT without rendering into 3D texture slices.
return false;
}
return true; return true;
} }

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 1; private const ushort FileFormatVersionMinor = 1;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 3457; private const uint CodeGenVersion = 3472;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@@ -1,23 +1,21 @@
using FFmpeg.AutoGen; using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging; using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
using System; using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Nvdec.FFmpeg namespace Ryujinx.Graphics.Nvdec.FFmpeg
{ {
unsafe class FFmpegContext : IDisposable unsafe class FFmpegContext : IDisposable
{ {
private readonly AVCodec_decode _decodeFrame; private readonly FFCodec.AVCodec_decode _decodeFrame;
private static readonly av_log_set_callback_callback _logFunc; private static readonly FFmpegApi.av_log_set_callback_callback _logFunc;
private readonly AVCodec* _codec; private readonly AVCodec* _codec;
private AVPacket* _packet; private AVPacket* _packet;
private AVCodecContext* _context; private AVCodecContext* _context;
public FFmpegContext(AVCodecID codecId) public FFmpegContext(AVCodecID codecId)
{ {
_codec = ffmpeg.avcodec_find_decoder(codecId); _codec = FFmpegApi.avcodec_find_decoder(codecId);
if (_codec == null) if (_codec == null)
{ {
Logger.Error?.PrintMsg(LogClass.FFmpeg, $"Codec wasn't found. Make sure you have the {codecId} codec present in your FFmpeg installation."); Logger.Error?.PrintMsg(LogClass.FFmpeg, $"Codec wasn't found. Make sure you have the {codecId} codec present in your FFmpeg installation.");
@@ -25,7 +23,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
return; return;
} }
_context = ffmpeg.avcodec_alloc_context3(_codec); _context = FFmpegApi.avcodec_alloc_context3(_codec);
if (_context == null) if (_context == null)
{ {
Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec context couldn't be allocated."); Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec context couldn't be allocated.");
@@ -33,14 +31,14 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
return; return;
} }
if (ffmpeg.avcodec_open2(_context, _codec, null) != 0) if (FFmpegApi.avcodec_open2(_context, _codec, null) != 0)
{ {
Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec couldn't be opened."); Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec couldn't be opened.");
return; return;
} }
_packet = ffmpeg.av_packet_alloc(); _packet = FFmpegApi.av_packet_alloc();
if (_packet == null) if (_packet == null)
{ {
Logger.Error?.PrintMsg(LogClass.FFmpeg, "Packet couldn't be allocated."); Logger.Error?.PrintMsg(LogClass.FFmpeg, "Packet couldn't be allocated.");
@@ -48,52 +46,39 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
return; return;
} }
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(_codec->decode.Pointer); int avCodecRawVersion = FFmpegApi.avcodec_version();
int avCodecMajorVersion = avCodecRawVersion >> 16;
int avCodecMinorVersion = (avCodecRawVersion >> 8) & 0xFF;
// libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union.
if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24))
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodec*)_codec)->CodecCallback);
}
// libavcodec 59.x changed AvCodec private API layout.
else if (avCodecMajorVersion == 59)
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
}
// libavcodec 58.x and lower
else
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodecLegacy>*)_codec)->Decode);
}
} }
static FFmpegContext() static FFmpegContext()
{ {
SetRootPath();
_logFunc = Log; _logFunc = Log;
// Redirect log output. // Redirect log output.
ffmpeg.av_log_set_level(ffmpeg.AV_LOG_MAX_OFFSET); FFmpegApi.av_log_set_level(AVLog.MaxOffset);
ffmpeg.av_log_set_callback(_logFunc); FFmpegApi.av_log_set_callback(_logFunc);
} }
private static void SetRootPath() private static void Log(void* ptr, AVLog level, string format, byte* vl)
{ {
if (OperatingSystem.IsLinux()) if (level > FFmpegApi.av_log_get_level())
{
// Configure FFmpeg search path
Process lddProcess = Process.Start(new ProcessStartInfo
{
FileName = "/bin/sh",
Arguments = "-c \"ldd $(which ffmpeg 2>/dev/null) | grep libavfilter\" 2>/dev/null",
UseShellExecute = false,
RedirectStandardOutput = true
});
string lddOutput = lddProcess.StandardOutput.ReadToEnd();
lddProcess.WaitForExit();
lddProcess.Close();
if (lddOutput.Contains(" => "))
{
ffmpeg.RootPath = Path.GetDirectoryName(lddOutput.Split(" => ")[1]);
}
else
{
Logger.Error?.PrintMsg(LogClass.FFmpeg, "FFmpeg wasn't found. Make sure that you have it installed and up to date.");
}
}
}
private static void Log(void* p0, int level, string format, byte* vl)
{
if (level > ffmpeg.av_log_get_level())
{ {
return; return;
} }
@@ -102,65 +87,67 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
byte* lineBuffer = stackalloc byte[lineSize]; byte* lineBuffer = stackalloc byte[lineSize];
int printPrefix = 1; int printPrefix = 1;
ffmpeg.av_log_format_line(p0, level, format, vl, lineBuffer, lineSize, &printPrefix); FFmpegApi.av_log_format_line(ptr, level, format, vl, lineBuffer, lineSize, &printPrefix);
string line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer).Trim(); string line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer).Trim();
switch (level) switch (level)
{ {
case ffmpeg.AV_LOG_PANIC: case AVLog.Panic:
case ffmpeg.AV_LOG_FATAL: case AVLog.Fatal:
case ffmpeg.AV_LOG_ERROR: case AVLog.Error:
Logger.Error?.Print(LogClass.FFmpeg, line); Logger.Error?.Print(LogClass.FFmpeg, line);
break; break;
case ffmpeg.AV_LOG_WARNING: case AVLog.Warning:
Logger.Warning?.Print(LogClass.FFmpeg, line); Logger.Warning?.Print(LogClass.FFmpeg, line);
break; break;
case ffmpeg.AV_LOG_INFO: case AVLog.Info:
Logger.Info?.Print(LogClass.FFmpeg, line); Logger.Info?.Print(LogClass.FFmpeg, line);
break; break;
case ffmpeg.AV_LOG_VERBOSE: case AVLog.Verbose:
case ffmpeg.AV_LOG_DEBUG: case AVLog.Debug:
case ffmpeg.AV_LOG_TRACE:
Logger.Debug?.Print(LogClass.FFmpeg, line); Logger.Debug?.Print(LogClass.FFmpeg, line);
break; break;
case AVLog.Trace:
Logger.Trace?.Print(LogClass.FFmpeg, line);
break;
} }
} }
public int DecodeFrame(Surface output, ReadOnlySpan<byte> bitstream) public int DecodeFrame(Surface output, ReadOnlySpan<byte> bitstream)
{ {
ffmpeg.av_frame_unref(output.Frame); FFmpegApi.av_frame_unref(output.Frame);
int result; int result;
int gotFrame; int gotFrame;
fixed (byte* ptr = bitstream) fixed (byte* ptr = bitstream)
{ {
_packet->data = ptr; _packet->Data = ptr;
_packet->size = bitstream.Length; _packet->Size = bitstream.Length;
result = _decodeFrame(_context, output.Frame, &gotFrame, _packet); result = _decodeFrame(_context, output.Frame, &gotFrame, _packet);
} }
if (gotFrame == 0) if (gotFrame == 0)
{ {
ffmpeg.av_frame_unref(output.Frame); FFmpegApi.av_frame_unref(output.Frame);
// If the frame was not delivered, it was probably delayed. // If the frame was not delivered, it was probably delayed.
// Get the next delayed frame by passing a 0 length packet. // Get the next delayed frame by passing a 0 length packet.
_packet->data = null; _packet->Data = null;
_packet->size = 0; _packet->Size = 0;
result = _decodeFrame(_context, output.Frame, &gotFrame, _packet); result = _decodeFrame(_context, output.Frame, &gotFrame, _packet);
// We need to set B frames to 0 as we already consumed all delayed frames. // We need to set B frames to 0 as we already consumed all delayed frames.
// This prevents the decoder from trying to return a delayed frame next time. // This prevents the decoder from trying to return a delayed frame next time.
_context->has_b_frames = 0; _context->HasBFrames = 0;
} }
ffmpeg.av_packet_unref(_packet); FFmpegApi.av_packet_unref(_packet);
if (gotFrame == 0) if (gotFrame == 0)
{ {
ffmpeg.av_frame_unref(output.Frame); FFmpegApi.av_frame_unref(output.Frame);
return -1; return -1;
} }
@@ -172,14 +159,14 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
{ {
fixed (AVPacket** ppPacket = &_packet) fixed (AVPacket** ppPacket = &_packet)
{ {
ffmpeg.av_packet_free(ppPacket); FFmpegApi.av_packet_free(ppPacket);
} }
ffmpeg.avcodec_close(_context); FFmpegApi.avcodec_close(_context);
fixed (AVCodecContext** ppContext = &_context) fixed (AVCodecContext** ppContext = &_context)
{ {
ffmpeg.avcodec_free_context(ppContext); FFmpegApi.avcodec_free_context(ppContext);
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using FFmpeg.AutoGen; using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
using Ryujinx.Graphics.Video; using Ryujinx.Graphics.Video;
using System; using System;

View File

@@ -0,0 +1,25 @@
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVCodec
{
#pragma warning disable CS0649
public unsafe byte* Name;
public unsafe byte* LongName;
public int Type;
public AVCodecID Id;
public int Capabilities;
public byte MaxLowRes;
public unsafe AVRational* SupportedFramerates;
public IntPtr PixFmts;
public IntPtr SupportedSamplerates;
public IntPtr SampleFmts;
// Deprecated
public unsafe ulong* ChannelLayouts;
public unsafe IntPtr PrivClass;
public IntPtr Profiles;
public unsafe byte* WrapperName;
#pragma warning restore CS0649
}
}

View File

@@ -0,0 +1,171 @@
using Ryujinx.Common.Memory;
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVCodecContext
{
#pragma warning disable CS0649
public unsafe IntPtr AvClass;
public int LogLevelOffset;
public int CodecType;
public unsafe AVCodecLegacy* Codec;
public AVCodecID CodecId;
public uint CodecTag;
public IntPtr PrivData;
public IntPtr Internal;
public IntPtr Opaque;
public long BitRate;
public int BitRateTolerance;
public int GlobalQuality;
public int CompressionLevel;
public int Flags;
public int Flags2;
public IntPtr ExtraData;
public int ExtraDataSize;
public AVRational TimeBase;
public int TicksPerFrame;
public int Delay;
public int Width;
public int Height;
public int CodedWidth;
public int CodedHeight;
public int GopSize;
public int PixFmt;
public IntPtr DrawHorizBand;
public IntPtr GetFormat;
public int MaxBFrames;
public float BQuantFactor;
public float BQuantOffset;
public int HasBFrames;
public float IQuantFactor;
public float IQuantOffset;
public float LumiMasking;
public float TemporalCplxMasking;
public float SpatialCplxMasking;
public float PMasking;
public float DarkMasking;
public int SliceCount;
public IntPtr SliceOffset;
public AVRational SampleAspectRatio;
public int MeCmp;
public int MeSubCmp;
public int MbCmp;
public int IldctCmp;
public int DiaSize;
public int LastPredictorCount;
public int MePreCmp;
public int PreDiaSize;
public int MeSubpelQuality;
public int MeRange;
public int SliceFlags;
public int MbDecision;
public IntPtr IntraMatrix;
public IntPtr InterMatrix;
public int IntraDcPrecision;
public int SkipTop;
public int SkipBottom;
public int MbLmin;
public int MbLmax;
public int BidirRefine;
public int KeyintMin;
public int Refs;
public int Mv0Threshold;
public int ColorPrimaries;
public int ColorPrc;
public int Colorspace;
public int ColorRange;
public int ChromaSampleLocation;
public int Slices;
public int FieldOrder;
public int SampleRate;
public int Channels;
public int SampleFmt;
public int FrameSize;
public int FrameNumber;
public int BlockAlign;
public int CutOff;
public ulong ChannelLayout;
public ulong RequestChannelLayout;
public int AudioServiceType;
public int RequestSampleFmt;
public IntPtr GetBuffer2;
public float QCompress;
public float QBlur;
public int QMin;
public int QMax;
public int MaxQdiff;
public int RcBufferSize;
public int RcOverrideCount;
public IntPtr RcOverride;
public long RcMaxRate;
public long RcMinRate;
public float RcMax_available_vbv_use;
public float RcMin_vbv_overflow_use;
public int RcInitialBufferOccupancy;
public int Trellis;
public IntPtr StatsOut;
public IntPtr StatsIn;
public int WorkaroundBugs;
public int StrictStdCompliance;
public int ErrorConcealment;
public int Debug;
public int ErrRecognition;
public long ReorderedOpaque;
public IntPtr HwAccel;
public IntPtr HwAccelContext;
public Array8<ulong> Error;
public int DctAlgo;
public int IdctAlgo;
public int BitsPerCodedSample;
public int BitsPerRawSample;
public int LowRes;
public int ThreadCount;
public int ThreadType;
public int ActiveThreadType;
public int ThreadSafeCallbacks;
public IntPtr Execute;
public IntPtr Execute2;
public int NsseWeight;
public int Profile;
public int Level;
public int SkipLoopFilter;
public int SkipIdct;
public int SkipFrame;
public IntPtr SubtitleHeader;
public int SubtitleHeaderSize;
public int InitialPadding;
public AVRational Framerate;
public int SwPixFmt;
public AVRational PktTimebase;
public IntPtr CodecDescriptor;
public long PtsCorrectionNumFaultyPts;
public long PtsCorrectionNumFaultyDts;
public long PtsCorrectionLastPts;
public long PtsCorrectionLastDts;
public IntPtr SubCharenc;
public int SubCharencMode;
public int SkipAlpha;
public int SeekPreroll;
public int DebugMv;
public IntPtr ChromaIntraMatrix;
public IntPtr DumpSeparator;
public IntPtr CodecWhitelist;
public uint Properties;
public IntPtr CodedSideData;
public int NbCodedSideData;
public IntPtr HwFramesCtx;
public int SubTextFormat;
public int TrailingPadding;
public long MaxPixels;
public IntPtr HwDeviceCtx;
public int HwAccelFlags;
public int applyCropping;
public int ExtraHwFrames;
public int DiscardDamagedPercentage;
public long MaxSamples;
public int ExportSideData;
public IntPtr GetEncodeBuffer;
#pragma warning restore CS0649
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
enum AVCodecID
{
AV_CODEC_ID_H264 = 27,
AV_CODEC_ID_VP8 = 139,
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVCodecLegacy
{
#pragma warning disable CS0649
public unsafe byte* Name;
public unsafe byte* LongName;
public int Type;
public AVCodecID Id;
public int Capabilities;
public byte MaxLowRes;
public unsafe AVRational* SupportedFramerates;
public IntPtr PixFmts;
public IntPtr SupportedSamplerates;
public IntPtr SampleFmts;
// Deprecated
public unsafe ulong* ChannelLayouts;
public unsafe IntPtr PrivClass;
public IntPtr Profiles;
public unsafe byte* WrapperName;
public IntPtr ChLayouts;
#pragma warning restore CS0649
}
}

View File

@@ -0,0 +1,37 @@
using Ryujinx.Common.Memory;
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVFrame
{
#pragma warning disable CS0649
public Array8<IntPtr> Data;
public Array8<int> LineSize;
public IntPtr ExtendedData;
public int Width;
public int Height;
public int NumSamples;
public int Format;
public int KeyFrame;
public int PictureType;
public AVRational SampleAspectRatio;
public long Pts;
public long PktDts;
public AVRational TimeBase;
public int CodedPictureNumber;
public int DisplayPictureNumber;
public int Quality;
public IntPtr Opaque;
public int RepeatPicture;
public int InterlacedFrame;
public int TopFieldFirst;
public int PaletteHasChanged;
public long ReorderedOpaque;
public int SampleRate;
public ulong ChannelLayout;
#pragma warning restore CS0649
// NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference.
}
}

View File

@@ -0,0 +1,15 @@
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
enum AVLog
{
Panic = 0,
Fatal = 8,
Error = 16,
Warning = 24,
Info = 32,
Verbose = 40,
Debug = 48,
Trace = 56,
MaxOffset = 64
}
}

View File

@@ -0,0 +1,26 @@
using System;
using AVBufferRef = System.IntPtr;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVPacket
{
#pragma warning disable CS0649
public unsafe AVBufferRef *Buf;
public long Pts;
public long Dts;
public unsafe byte* Data;
public int Size;
public int StreamIndex;
public int Flags;
public IntPtr SizeData;
public int SizeDataElems;
public long Duration;
public long Position;
public IntPtr Opaque;
public unsafe AVBufferRef *OpaqueRef;
public AVRational TimeBase;
#pragma warning restore CS0649
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
public struct AVRational
{
public int Numerator;
public int Denominator;
}
}

View File

@@ -0,0 +1,23 @@
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct FFCodec
{
public unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
#pragma warning disable CS0649
public AVCodec Base;
public int CapsInternalOrCbType;
public int PrivDataSize;
public IntPtr UpdateThreadContext;
public IntPtr UpdateThreadContextForUser;
public IntPtr Defaults;
public IntPtr InitStaticData;
public IntPtr Init;
public IntPtr CodecCallback;
#pragma warning restore CS0649
// NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference.
}
}

View File

@@ -0,0 +1,23 @@
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct FFCodecLegacy<T> where T: struct
{
#pragma warning disable CS0649
public T Base;
public uint CapsInternalOrCbType;
public int PrivDataSize;
public IntPtr UpdateThreadContext;
public IntPtr UpdateThreadContextForUser;
public IntPtr Defaults;
public IntPtr InitStaticData;
public IntPtr Init;
public IntPtr EncodeSub;
public IntPtr Encode2;
public IntPtr Decode;
#pragma warning restore CS0649
// NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference.
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
static class FFmpegApi
{
public const string AvCodecLibraryName = "avcodec";
public const string AvUtilLibraryName = "avutil";
private static readonly Dictionary<string, (int, int)> _librariesWhitelist = new Dictionary<string, (int, int)>
{
{ AvCodecLibraryName, (58, 59) },
{ AvUtilLibraryName, (56, 57) }
};
private static string FormatLibraryNameForCurrentOs(string libraryName, int version)
{
if (OperatingSystem.IsWindows())
{
return $"{libraryName}-{version}.dll";
}
else if (OperatingSystem.IsLinux())
{
return $"lib{libraryName}.so.{version}";
}
else if (OperatingSystem.IsMacOS())
{
return $"lib{libraryName}.{version}.dylib";
}
else
{
throw new NotImplementedException($"Unsupported OS for FFmpeg: {RuntimeInformation.RuntimeIdentifier}");
}
}
private static bool TryLoadWhitelistedLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle)
{
handle = IntPtr.Zero;
if (_librariesWhitelist.TryGetValue(libraryName, out var value))
{
(int minVersion, int maxVersion) = value;
for (int version = maxVersion; version >= minVersion; version--)
{
if (NativeLibrary.TryLoad(FormatLibraryNameForCurrentOs(libraryName, version), assembly, searchPath, out handle))
{
return true;
}
}
}
return false;
}
static FFmpegApi()
{
NativeLibrary.SetDllImportResolver(typeof(FFmpegApi).Assembly, (name, assembly, path) =>
{
IntPtr handle;
if (name == AvUtilLibraryName && TryLoadWhitelistedLibrary(AvUtilLibraryName, assembly, path, out handle))
{
return handle;
}
else if (name == AvCodecLibraryName && TryLoadWhitelistedLibrary(AvCodecLibraryName, assembly, path, out handle))
{
return handle;
}
return IntPtr.Zero;
});
}
public unsafe delegate void av_log_set_callback_callback(void* a0, AVLog level, [MarshalAs(UnmanagedType.LPUTF8Str)] string a2, byte* a3);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVFrame* av_frame_alloc();
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_frame_unref(AVFrame* frame);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_free(AVFrame* frame);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_log_set_level(AVLog level);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_log_set_callback(av_log_set_callback_callback callback);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVLog av_log_get_level();
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_log_format_line(void* ptr, AVLog level, [MarshalAs(UnmanagedType.LPUTF8Str)] string fmt, byte* vl, byte* line, int lineSize, int* printPrefix);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVCodec* avcodec_find_decoder(AVCodecID id);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVCodecContext* avcodec_alloc_context3(AVCodec* codec);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern int avcodec_open2(AVCodecContext* avctx, AVCodec* codec, void **options);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern int avcodec_close(AVCodecContext* avctx);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void avcodec_free_context(AVCodecContext** avctx);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVPacket* av_packet_alloc();
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_packet_unref(AVPacket* pkt);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_packet_free(AVPacket** pkt);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern int avcodec_version();
}
}

View File

@@ -1,14 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="FFmpeg.AutoGen" Version="4.4.1" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj" />

View File

@@ -1,4 +1,4 @@
using FFmpeg.AutoGen; using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
using Ryujinx.Graphics.Video; using Ryujinx.Graphics.Video;
using System; using System;
@@ -11,31 +11,31 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
public int RequestedWidth { get; } public int RequestedWidth { get; }
public int RequestedHeight { get; } public int RequestedHeight { get; }
public Plane YPlane => new Plane((IntPtr)Frame->data[0], Stride * Height); public Plane YPlane => new Plane((IntPtr)Frame->Data[0], Stride * Height);
public Plane UPlane => new Plane((IntPtr)Frame->data[1], UvStride * UvHeight); public Plane UPlane => new Plane((IntPtr)Frame->Data[1], UvStride * UvHeight);
public Plane VPlane => new Plane((IntPtr)Frame->data[2], UvStride * UvHeight); public Plane VPlane => new Plane((IntPtr)Frame->Data[2], UvStride * UvHeight);
public FrameField Field => Frame->interlaced_frame != 0 ? FrameField.Interlaced : FrameField.Progressive; public FrameField Field => Frame->InterlacedFrame != 0 ? FrameField.Interlaced : FrameField.Progressive;
public int Width => Frame->width; public int Width => Frame->Width;
public int Height => Frame->height; public int Height => Frame->Height;
public int Stride => Frame->linesize[0]; public int Stride => Frame->LineSize[0];
public int UvWidth => (Width + 1) >> 1; public int UvWidth => (Width + 1) >> 1;
public int UvHeight => (Height + 1) >> 1; public int UvHeight => (Height + 1) >> 1;
public int UvStride => Frame->linesize[1]; public int UvStride => Frame->LineSize[1];
public Surface(int width, int height) public Surface(int width, int height)
{ {
RequestedWidth = width; RequestedWidth = width;
RequestedHeight = height; RequestedHeight = height;
Frame = ffmpeg.av_frame_alloc(); Frame = FFmpegApi.av_frame_alloc();
} }
public void Dispose() public void Dispose()
{ {
ffmpeg.av_frame_unref(Frame); FFmpegApi.av_frame_unref(Frame);
ffmpeg.av_free(Frame); FFmpegApi.av_free(Frame);
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using FFmpeg.AutoGen; using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
using Ryujinx.Graphics.Video; using Ryujinx.Graphics.Video;
using System; using System;

View File

@@ -92,7 +92,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
pushOpInfo.Consumers.Add(rightBlock, local); pushOpInfo.Consumers.Add(rightBlock, local);
} }
rightBlock.SyncTargets.Union(SyncTargets); foreach ((ulong key, SyncTarget value) in SyncTargets)
{
rightBlock.SyncTargets.Add(key, value);
}
SyncTargets.Clear(); SyncTargets.Clear();
// Move push ops. // Move push ops.

View File

@@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
{ {
InstConditional condOp = new InstConditional(op.RawOpCode); InstConditional condOp = new InstConditional(op.RawOpCode);
if (op.Name == InstName.Exit && condOp.Ccc != Ccc.T) if ((op.Name == InstName.Bra || op.Name == InstName.Exit) && condOp.Ccc != Ccc.T)
{ {
return false; return false;
} }
@@ -672,6 +672,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
// Make sure we found the correct address, // Make sure we found the correct address,
// the push and pop instruction types must match, so: // the push and pop instruction types must match, so:
// - BRK can only consume addresses pushed by PBK. // - BRK can only consume addresses pushed by PBK.
// - CONT can only consume addresses pushed by PCNT.
// - SYNC can only consume addresses pushed by SSY. // - SYNC can only consume addresses pushed by SSY.
if (found) if (found)
{ {

View File

@@ -45,12 +45,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (isFP64) if (isFP64)
{ {
return context.PackDouble2x32( return context.PackDouble2x32(
context.Config.CreateCbuf(cbufSlot, cbufOffset), Cbuf(cbufSlot, cbufOffset),
context.Config.CreateCbuf(cbufSlot, cbufOffset + 1)); Cbuf(cbufSlot, cbufOffset + 1));
} }
else else
{ {
return context.Config.CreateCbuf(cbufSlot, cbufOffset); return Cbuf(cbufSlot, cbufOffset);
} }
} }

View File

@@ -300,6 +300,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
if (operand.Type != OperandType.LocalVariable) if (operand.Type != OperandType.LocalVariable)
{ {
if (operand.Type == OperandType.ConstantBuffer)
{
Config.SetUsedConstantBuffer(operand.GetCbufSlot());
}
return new AstOperand(operand); return new AstOperand(operand);
} }

View File

@@ -68,7 +68,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{ {
Operand addrLow = operation.GetSource(0); Operand addrLow = operation.GetSource(0);
Operand baseAddrLow = config.CreateCbuf(0, GetStorageCbOffset(config.Stage, storageIndex)); Operand baseAddrLow = Cbuf(0, GetStorageCbOffset(config.Stage, storageIndex));
Operand baseAddrTrunc = Local(); Operand baseAddrTrunc = Local();
@@ -152,7 +152,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{ {
Operand addrLow = operation.GetSource(0); Operand addrLow = operation.GetSource(0);
Operand baseAddrLow = config.CreateCbuf(0, UbeBaseOffset + storageIndex * StorageDescSize); Operand baseAddrLow = Cbuf(0, UbeBaseOffset + storageIndex * StorageDescSize);
Operand baseAddrTrunc = Local(); Operand baseAddrTrunc = Local();

View File

@@ -75,9 +75,9 @@ namespace Ryujinx.Graphics.Shader.Translation
int cbOffset = GetStorageCbOffset(config.Stage, slot); int cbOffset = GetStorageCbOffset(config.Stage, slot);
Operand baseAddrLow = config.CreateCbuf(0, cbOffset); Operand baseAddrLow = Cbuf(0, cbOffset);
Operand baseAddrHigh = config.CreateCbuf(0, cbOffset + 1); Operand baseAddrHigh = Cbuf(0, cbOffset + 1);
Operand size = config.CreateCbuf(0, cbOffset + 2); Operand size = Cbuf(0, cbOffset + 2);
Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow); Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow);
Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow); Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow);
@@ -164,7 +164,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isCoordNormalized = !isBindless && config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot); bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
if (!hasInvalidOffset && isCoordNormalized) if (!hasInvalidOffset && isCoordNormalized)
{ {

View File

@@ -360,12 +360,6 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedFeatures |= flags; UsedFeatures |= flags;
} }
public Operand CreateCbuf(int slot, int offset)
{
SetUsedConstantBuffer(slot);
return OperandHelper.Cbuf(slot, offset);
}
public void SetUsedConstantBuffer(int slot) public void SetUsedConstantBuffer(int slot)
{ {
_usedConstantBuffers |= 1 << slot; _usedConstantBuffers |= 1 << slot;

View File

@@ -184,18 +184,35 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags) public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
{ {
LinuxError result;
bool shouldBlockAfterOperation = false;
try try
{ {
if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait))
{
Blocking = false;
shouldBlockAfterOperation = true;
}
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags)); receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
return LinuxError.SUCCESS; result = LinuxError.SUCCESS;
} }
catch (SocketException exception) catch (SocketException exception)
{ {
receiveSize = -1; receiveSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
} }
if (shouldBlockAfterOperation)
{
Blocking = true;
}
return result;
} }
public LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint) public LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint)
@@ -304,7 +321,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return LinuxError.EOPNOTSUPP; return LinuxError.EOPNOTSUPP;
} }
int value = MemoryMarshal.Read<int>(optionValue); int value = optionValue.Length >= 4 ? MemoryMarshal.Read<int>(optionValue) : MemoryMarshal.Read<byte>(optionValue);
if (option == BsdSocketOption.SoLinger) if (option == BsdSocketOption.SoLinger)
{ {

View File

@@ -10,7 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build7" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -19,7 +19,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="GtkSharp" Version="3.22.25.128" /> <PackageReference Include="GtkSharp" Version="3.22.25.128" />
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" /> <PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
<PackageReference Include="SPB" Version="0.0.4-build17" /> <PackageReference Include="SPB" Version="0.0.4-build17" />

View File

@@ -1509,7 +1509,7 @@
</child> </child>
<child> <child>
<object class="GtkCheckButton" id="_internetToggle"> <object class="GtkCheckButton" id="_internetToggle">
<property name="label" translatable="yes">Enable guest Internet access</property> <property name="label" translatable="yes">Enable Guest Internet Access</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">False</property> <property name="receives-default">False</property>
@@ -1643,7 +1643,7 @@
</child> </child>
<child> <child>
<object class="GtkRadioButton" id="_mmHostUnsafe"> <object class="GtkRadioButton" id="_mmHostUnsafe">
<property name="label" translatable="yes">Host unchecked (fastest, unsafe)</property> <property name="label" translatable="yes">Host Unchecked (fastest, unsafe)</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">False</property> <property name="receives-default">False</property>
@@ -1725,7 +1725,7 @@
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
<property name="margin-bottom">5</property> <property name="margin-bottom">5</property>
<property name="label" translatable="yes"> - These may cause instability</property> <property name="label" translatable="yes"> (may cause instability)</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@@ -1749,7 +1749,7 @@
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkCheckButton" id="_expandRamToggle"> <object class="GtkCheckButton" id="_expandRamToggle">
<property name="label" translatable="yes">Expand DRAM size to 6GB</property> <property name="label" translatable="yes">Expand DRAM Size to 6GB</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">False</property> <property name="receives-default">False</property>