Compare commits

..

23 Commits

Author SHA1 Message Date
UltraHDR
d16288a2a8 Set LSApplicationCategoryType to games (#4257)
https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype
Makes it auto-add to the macOS Launchpad games folder
2023-01-10 21:49:46 +00:00
Isaac Marovitz
600f86dc7b Fix context menu locales (#4242) 2023-01-10 19:15:15 +01:00
Mary-nyan
7210c17c5e misc: Enforce LF (#4253)
Because we are building everything on Windows for release at the moment,
git default line ending to CRLF causing issues when packing the
Ryujinx.sh script.

This addresses this by enforcing all files to use LF via .gitattributes.
2023-01-10 19:00:14 +01:00
Ac_K
a16854e55a ava: Cleanup AppHost (#4240)
* ava: Cleanup AppHost

This PR cleaned up the AppHost file a bit (adding the infamous extra spaces to improve readability), resorting private vars, remove useless vars, and improve the code here and there, like the AudioBackend check.

Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com>

* Remove 'renderer"

* Revert currentTime

* revert if condition

Co-authored-by: gdkchan <5624669+gdkchan@users.noreply.github.com>
2023-01-10 18:45:55 +01:00
TSRBerry
3e455a90a1 Ava: Add missing null check to ContentDialogHelper.ShowAsync() (#4248)
* ava: Add missing null check to ContentDialogHelper.ShowAsync()

* Replace "is not" with != operator

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-01-10 09:22:25 +01:00
TSRBerry
e4413542b2 Add command line arguments to override docked mode (#4239)
* Add command line args for docked mode

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-10 00:59:23 +01:00
TSRBerry
8c720783f5 linux: Fix packaging step for CI & Add Ryujinx.Headless.SDL2 to Ryujinx.sh (#4249) 2023-01-09 23:45:46 +00:00
TSRBerry
8734ea9dd4 Linux: Add Avalonia detection to Ryujinx.sh (#4224)
* Revert "ava: Fix regression caused by #4013 (#4222)"

This reverts commit b9f2a96595.

* linux: Detect Ryujinx.Ava and don't rename the Ryujinx.Ava assembly
2023-01-09 22:58:51 +01:00
gdkchan
c586e6d2b7 Replace tabs with spaces across the project (#4244)
* Replace tabs with spaces across the project

* Include AXAML files too
2023-01-09 22:58:29 +01:00
TSRBerry
3a4eeb77fe headless: Change window icon size to 48x48 (#4247) 2023-01-09 18:02:41 +00:00
TSRBerry
51b3953cfc [Headless] Add missing arguments & Fix typos (#4193)
* headless: Fix typos in command line options

* Remove nullable from command line options

Add EnableMacroHLE option
Add HideCursorOnIdle option

* headless: Adjust enable-ptc help text

* headless: Use switch statement instead of if-else chain

* headless: Improve formatting for long constructors

* headless: Remove discards from SDL_ShowCursor()

* headless: Add window icon

* Fix hiding cursor on idle

At least on Wayland, SDL2 doesn't produce any mouse motion events.

* Add new command line args: BaseDataDir and UserProfile

* headless: Read icon from embedded resource

* headless: Skip SetWindowIcon() on Windows if dll isn't present

* headless: Fix division by zero

* headless: Fix command line options not working correctly

* headless: Fix crash when viewing command line options

* headless: Load window icon bmp from memory

* Add comment to the workaround for SDL_LoadBMP_RW

* headless: Enable logging to file by default

* headless: Add 3 options for --hide-cursor

Replaces --disable-hide-cursor-on-idle
2023-01-09 04:55:37 +01:00
Ac_K
610eecc1c1 ava: Fixes regressions from refactoring (#4237)
* ava: Fix regressions from #4178

* Remove duplicated code

* real fix for right click menu

Co-Authored-By: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>

* Remove ContentDialogOverlay

Co-authored-by: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>
2023-01-09 03:37:20 +00:00
merry
492056abf6 Ava: Make Avalonia use our logging system (#4231)
* Ava: Make Avalonia use our logging system

* LoggerAdapter: Address review comments

* Update Ryujinx.Common/Logging/LogClass.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-09 04:32:20 +01:00
Isaac Marovitz
ee6e682ab4 Fix selection bar (#4236) 2023-01-09 03:36:11 +01:00
gdkchan
6f60e102a2 HIPC: Fix reply possibly also receiving one request (#4232) 2023-01-08 15:34:49 -03:00
Isaac Marovitz
eeb2af9953 Ava GUI: MainWindow Refactor (#4178)
* Fix redundancies

* Add back elses

* `MainWindow` Refactor

* Switch commands to `ReflectionBinding`

Not required in Ava 11

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Resolve issues

* Remove Ava 11 Fix

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Fix whitespace + other suggestions

* Move Vsync colours to `Styles.xaml`

* Remove catch all

* Use `switch` instead of `if`

* Update locale keys

* Use block-scoped namespaces

* Fix improper Ava api usage then

* Static PTC

* Fix `GridItemSelectorSize` with `ShowNames`

* Update for new About Window

* Add back search fix

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-01-08 18:46:25 +01:00
Ac_K
550747eac6 Horizon: Impl Prepo, Fixes bugs, Clean things (#4220)
* Horizon: Impl Prepo, Fixes bugs, Clean things

* remove ToArray()

* resultCode > status

* Remove old services

* Addresses gdkchan's comments and more cleanup

* Addresses Gdkchan's feedback 2

* Reorganize services, make sure service are loaded before guest

Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com>

* Create interfaces for lm and sm

Co-authored-by: gdkchan <5624669+gdkchan@users.noreply.github.com>
2023-01-08 12:13:39 +00:00
merry
3ffceab1fb MainWindow: Vertically center SearchBox TextPresenter (#4223) 2023-01-07 16:01:53 +00:00
Mary-nyan
b9f2a96595 ava: Fix regression caused by #4013 (#4222)
Avalonia seems to not like when the artifact doesns't match the root namespace...

Address that by moving the binary to "Ryujinx" like we do on macOS build.
2023-01-07 12:24:21 +01:00
RMED24
cbaa845f5d Include a start.sh file with correct launch options (#4013)
* Include reference to start.sh to be bundled

* Add start.sh

* Fix silly mistake I made on windows-x64

* ... I cannot read properly

* Make same changes for avalonia csproj

* Remove notice from start.sh

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx/Ryujinx.csproj

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx.Ava/Ryujinx.Ava.csproj

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update distribution/linux/start.sh

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update distribution/linux/start.sh

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/Ryujinx.Ava.csproj

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.csproj

* Update Ryujinx.Ava.csproj

* Rename start.sh to Ryujinx.sh

* Update Ryujinx.csproj

* Update Ryujinx.Ava.csproj

* Update Ryujinx.Ava.csproj

* Update Ryujinx.Ava/Ryujinx.Ava.csproj

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Add `GDK_BACKEND` variable

* Update Ryujinx.Ava.csproj

* Update Program.cs

* Update Program.cs

* Update Ryujinx.sh

* Update Program.cs

* linux: Register mime types on launch

* Add DOTNET_EnableAlternateStackCheck=1 to desktop file

* linux: Add exclusion for RegisterMimeTypes for flathub builds

* Update logo path

* Cleanup Ryujinx.sh

* Fix typo in ReleaseInformation

* gha: Fix permissions for linux release binaries

* ava: Rename output assembly to Ryujinx

* Update mime database after installing new types

Wait until logging is available before registering mime types

* Copy mime types to output directory

Co-authored-by: Mary-nyan <thog@protonmail.com>
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-01-07 09:06:13 +01:00
Isaac Marovitz
9e2681f2d7 Ava GUI: AboutWindow Refactor (#4196)
* Start `AboutWindow` refactor

* Redesign

* Update logos + ViewModel

* Fix GTK

* Cleanup usings

* Fix mismatched font size

* Update LocaleKeys

* Block scoped namespace

* Fix appearence on German

* Update Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Move version number up

* Move see all contributors button left

* Add a couple `\n`

* Tooltips

* Layout fix

* Update Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-07 01:29:18 +01:00
TSRBerry
81fae0d1a6 [hipc] Fix 'Unexpected result code Success returned' in Reply() (#4215)
* horizon: Add AbortOnFailureUnless()

* hipc: Replace AbortUnless() with AbortOnFailureUnless() in Reply()
2023-01-07 00:57:21 +01:00
Isaac Marovitz
38519f3b9a Ava GUI: SettingsWindow Refactor (#4177)
* Fix redundancies

* Add back elses

* Settings Refactor

* Fix Disposal functions

* Use `ReflectionBinding` instead of redundant funcs

* Ac_K suggestions

* Update Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update locale keys

* Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Use block-scoped namespaces

* Fix typo

* Make `TimeZone` internal again

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-01-07 00:35:21 +01:00
195 changed files with 7845 additions and 6390 deletions

61
.gitattributes vendored
View File

@@ -1,63 +1,4 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
* text=auto eol=lf

View File

@@ -38,11 +38,11 @@ jobs:
shell: bash
- name: Configure for release
run: |
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Create output dir
run: "mkdir release_output"
@@ -75,15 +75,27 @@ jobs:
- name: Packing Linux builds
run: |
pushd publish_linux
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
tar --exclude "publish/Ryujinx" --exclude "publish/Ryujinx.sh" -cvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx" "publish/Ryujinx"
python3 ../distribution/misc/add_tar_exec.py ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
gzip -9 < ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd
pushd publish_linux_sdl2_headless
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
tar --exclude "publish/Ryujinx.Headless.SDL2" --exclude "publish/Ryujinx.sh" -cvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2"
python3 ../distribution/misc/add_tar_exec.py ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
gzip -9 < ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd
pushd publish_linux_ava
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
tar --exclude "publish/Ryujinx.Ava" --exclude "publish/Ryujinx.sh" -cvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.Ava" "publish/Ryujinx.Ava"
python3 ../distribution/misc/add_tar_exec.py ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
gzip -9 < ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd
shell: bash

View File

@@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Backends.CompatLayer
{
private IHardwareDeviceDriver _realDriver;
public static bool IsSupported => true;
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
{
_realDriver = realDevice;

View File

@@ -12,6 +12,8 @@ namespace Ryujinx.Audio.Backends.Dummy
private ManualResetEvent _updateRequiredEvent;
private ManualResetEvent _pauseEvent;
public static bool IsSupported => true;
public DummyHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);

View File

@@ -26,6 +26,8 @@ namespace Ryujinx.Audio.Integration
bool SupportsSampleFormat(SampleFormat sampleFormat);
bool SupportsChannelCount(uint channelCount);
static abstract bool IsSupported { get; }
IHardwareDeviceDriver GetRealDeviceDriver()
{
return this;

View File

@@ -1,4 +1,6 @@
using ARMeilleure.Translation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Threading;
using LibHac.Tools.FsSystem;
@@ -13,6 +15,7 @@ using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@@ -38,11 +41,12 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SPB.Graphics.Vulkan;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager;
using Key = Ryujinx.Input.Key;
using MouseButton = Ryujinx.Input.MouseButton;
@@ -54,133 +58,142 @@ namespace Ryujinx.Ava
{
internal class AppHost
{
private const int CursorHideIdleTime = 8; // Hide Cursor seconds
private const int CursorHideIdleTime = 8; // Hide Cursor seconds.
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
private const int TargetFps = 60;
private const int TargetFps = 60;
private const float VolumeDelta = 0.05f;
private const float VolumeDelta = 0.05f;
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
private readonly long _ticksPerFrame;
private readonly long _ticksPerFrame;
private readonly Stopwatch _chrono;
private readonly AccountManager _accountManager;
private long _ticks;
private readonly AccountManager _accountManager;
private readonly UserChannelPersistence _userChannelPersistence;
private readonly InputManager _inputManager;
private readonly MainWindow _parent;
private readonly IKeyboard _keyboardInterface;
private readonly InputManager _inputManager;
private readonly MainWindowViewModel _viewModel;
private readonly IKeyboard _keyboardInterface;
private readonly TopLevel _topLevel;
private readonly GraphicsDebugLevel _glLogLevel;
private float _newVolume;
private KeyboardHotkeyState _prevHotkeyState;
private bool _hideCursorOnIdle;
private long _lastCursorMoveTime;
private bool _isCursorInRenderer;
private bool _isStopped;
private bool _isActive;
private long _lastCursorMoveTime;
private float _newVolume;
private long _ticks = 0;
private KeyboardHotkeyState _prevHotkeyState;
private IRenderer _renderer;
private readonly Thread _renderingThread;
private bool _isMouseInRenderer;
private bool _renderingStarted;
private bool _dialogShown;
private IRenderer _renderer;
private readonly Thread _renderingThread;
private readonly CancellationTokenSource _gpuCancellationTokenSource;
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
private readonly CancellationTokenSource _gpuCancellationTokenSource;
private bool _dialogShown;
private readonly bool _isFirmwareTitle;
private readonly object _lockObject = new();
public event EventHandler AppExit;
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
public RendererHost Renderer { get; }
public VirtualFileSystem VirtualFileSystem { get; }
public ContentManager ContentManager { get; }
public Switch Device { get; set; }
public NpadManager NpadManager { get; }
public RendererHost Renderer { get; }
public VirtualFileSystem VirtualFileSystem { get; }
public ContentManager ContentManager { get; }
public NpadManager NpadManager { get; }
public TouchScreenManager TouchScreenManager { get; }
public Switch Device { get; set; }
public int Width { get; private set; }
public int Height { get; private set; }
public string ApplicationPath { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
public string ApplicationPath { get; private set; }
public bool ScreenshotRequested { get; set; }
private bool _isFirmwareTitle;
public bool ScreenshotRequested { get; set; }
private object _lockObject = new();
public AppHost(
RendererHost renderer,
InputManager inputManager,
string applicationPath,
VirtualFileSystem virtualFileSystem,
ContentManager contentManager,
AccountManager accountManager,
RendererHost renderer,
InputManager inputManager,
string applicationPath,
VirtualFileSystem virtualFileSystem,
ContentManager contentManager,
AccountManager accountManager,
UserChannelPersistence userChannelPersistence,
MainWindow parent)
MainWindowViewModel viewmodel,
TopLevel topLevel)
{
_parent = parent;
_inputManager = inputManager;
_accountManager = accountManager;
_viewModel = viewmodel;
_inputManager = inputManager;
_accountManager = accountManager;
_userChannelPersistence = userChannelPersistence;
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
_lastCursorMoveTime = Stopwatch.GetTimestamp();
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(_parent, renderer));
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
_lastCursorMoveTime = Stopwatch.GetTimestamp();
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
_topLevel = topLevel;
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(_topLevel, renderer));
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
NpadManager = _inputManager.CreateNpadManager();
NpadManager = _inputManager.CreateNpadManager();
TouchScreenManager = _inputManager.CreateTouchScreenManager();
Renderer = renderer;
ApplicationPath = applicationPath;
VirtualFileSystem = virtualFileSystem;
ContentManager = contentManager;
Renderer = renderer;
ApplicationPath = applicationPath;
VirtualFileSystem = virtualFileSystem;
ContentManager = contentManager;
_chrono = new Stopwatch();
_chrono = new Stopwatch();
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
if (ApplicationPath.StartsWith("@SystemContent"))
{
ApplicationPath = _parent.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath);
ApplicationPath = _viewModel.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath);
_isFirmwareTitle = true;
}
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
_parent.PointerLeave += Parent_PointerLeft;
_parent.PointerMoved += Parent_PointerMoved;
_topLevel.PointerLeave += TopLevel_PointerLeave;
_topLevel.PointerMoved += TopLevel_PointerMoved;
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
_gpuCancellationTokenSource = new CancellationTokenSource();
}
private void Parent_PointerMoved(object sender, PointerEventArgs e)
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
var p = e.GetCurrentPoint(_parent).Position;
var r = _parent.InputHitTest(p);
_isMouseInRenderer = r == Renderer;
if (sender is Control visual)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
var point = e.GetCurrentPoint(visual).Position;
_isCursorInRenderer = Equals(visual.InputHitTest(point), Renderer);
}
}
private void Parent_PointerLeft(object sender, PointerEventArgs e)
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
{
_isMouseInRenderer = false;
_parent.Cursor = Cursor.Default;
_isCursorInRenderer = false;
_viewModel.Cursor = Cursor.Default;
}
private void SetRendererWindowSize(Size size)
{
if (_renderer != null)
{
double scale = _parent.PlatformImpl.RenderScaling;
double scale = _topLevel.PlatformImpl.RenderScaling;
_renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale));
}
}
@@ -193,12 +206,13 @@ namespace Ryujinx.Ava
{
lock (_lockObject)
{
var currentTime = DateTime.Now;
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
DateTime currentTime = DateTime.Now;
string filename = $"ryujinx_capture_{currentTime}-{currentTime:D2}-{currentTime:D2}_{currentTime:D2}-{currentTime:D2}-{currentTime:D2}.png";
string directory = AppDataManager.Mode switch
{
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
};
string path = Path.Combine(directory, filename);
@@ -256,30 +270,19 @@ namespace Ryujinx.Ava
NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
TouchScreenManager.Initialize(Device);
_parent.ViewModel.IsGameRunning = true;
_viewModel.IsGameRunning = true;
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName)
? string.Empty
: $" - {Device.Application.TitleName}";
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion)
? string.Empty
: $" v{Device.Application.DisplayVersion}";
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText)
? string.Empty
: $" ({Device.Application.TitleIdText.ToUpper()})";
string titleArchSection = Device.Application.TitleIs64Bit
? " (64-bit)"
: " (32-bit)";
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}";
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})";
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
Dispatcher.UIThread.InvokeAsync(() =>
{
_parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
_viewModel.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
});
_parent.ViewModel.SetUiProgressHandlers(Device);
_viewModel.SetUIProgressHandlers(Device);
Renderer.SizeChanged += Window_SizeChanged;
@@ -287,7 +290,7 @@ namespace Ryujinx.Ava
_renderingThread.Start();
_parent.ViewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value;
_viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value;
MainLoop();
@@ -318,10 +321,10 @@ namespace Ryujinx.Ava
private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e)
{
Device?.SetVolume(e.NewValue);
Dispatcher.UIThread.Post(() =>
{
var value = e.NewValue;
_parent.ViewModel.Volume = e.NewValue;
_viewModel.Volume = e.NewValue;
});
}
@@ -340,7 +343,7 @@ namespace Ryujinx.Ava
}
_isStopped = true;
_isActive = false;
_isActive = false;
}
public void DisposeContext()
@@ -369,13 +372,16 @@ namespace Ryujinx.Ava
{
if (Device.Application != null)
{
_parent.UpdateGameMetadata(Device.Application.TitleIdText);
_viewModel.UpdateGameMetadata(Device.Application.TitleIdText);
}
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
_topLevel.PointerLeave -= TopLevel_PointerLeave;
_topLevel.PointerMoved -= TopLevel_PointerMoved;
_gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose();
@@ -410,7 +416,7 @@ namespace Ryujinx.Ava
}
else
{
_parent.Cursor = Cursor.Default;
_viewModel.Cursor = Cursor.Default;
}
});
}
@@ -422,57 +428,65 @@ namespace Ryujinx.Ava
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError))
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion))
if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError))
{
if (userError == UserError.NoFirmware)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedMessage], firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
"");
if (result != UserResult.Yes)
if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion))
{
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
if (userError == UserError.NoFirmware)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedMessage],
firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
"");
if (result != UserResult.Yes)
{
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
Device.Dispose();
return false;
}
}
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
Device.Dispose();
return false;
}
// Tell the user that we installed a firmware for them.
if (userError == UserError.NoFirmware)
{
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
_viewModel.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstalledMessage],
firmwareVersion.VersionString),
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage],
firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
}
}
else
{
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
Device.Dispose();
return false;
}
}
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
Device.Dispose();
return false;
}
// Tell the user that we installed a firmware for them.
if (userError == UserError.NoFirmware)
{
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
_parent.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstalledMessage], firmwareVersion.VersionString),
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage], firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
}
}
else
{
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
Device.Dispose();
return false;
}
}
@@ -567,7 +581,7 @@ namespace Ryujinx.Ava
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName);
_parent.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
{
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
});
@@ -578,19 +592,23 @@ namespace Ryujinx.Ava
internal void Resume()
{
Device?.System.TogglePauseEmulation(false);
_parent.ViewModel.IsPaused = false;
_viewModel.IsPaused = false;
}
internal void Pause()
{
Device?.System.TogglePauseEmulation(true);
_parent.ViewModel.IsPaused = true;
_viewModel.IsPaused = true;
}
private void InitializeSwitchInstance()
{
// Initialize KeySet.
VirtualFileSystem.ReloadKeySet();
// Initialize Renderer.
IRenderer renderer;
if (Renderer.IsVulkan)
@@ -604,12 +622,9 @@ namespace Ryujinx.Ava
renderer = new OpenGLRenderer();
}
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
var isGALthreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
if (isGALthreaded)
{
renderer = new ThreadedRenderer(renderer);
@@ -617,159 +632,104 @@ namespace Ryujinx.Ava
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALthreaded}");
if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2)
{
if (SDL2HardwareDeviceDriver.IsSupported)
{
deviceDriver = new SDL2HardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL.");
if (OpenALHardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl;
MainWindow.SaveConfig();
deviceDriver = new OpenALHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO.");
if (SoundIoHardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
MainWindow.SaveConfig();
deviceDriver = new SoundIoHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out.");
}
}
}
}
else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo)
{
if (SoundIoHardwareDeviceDriver.IsSupported)
{
deviceDriver = new SoundIoHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, trying to fall back to SDL2.");
if (SDL2HardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2;
MainWindow.SaveConfig();
deviceDriver = new SDL2HardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL.");
if (OpenALHardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl;
MainWindow.SaveConfig();
deviceDriver = new OpenALHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, falling back to dummy audio out.");
}
}
}
}
else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl)
{
if (OpenALHardwareDeviceDriver.IsSupported)
{
deviceDriver = new OpenALHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SDL2.");
if (SDL2HardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2;
MainWindow.SaveConfig();
deviceDriver = new SDL2HardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to SoundIO.");
if (SoundIoHardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
MainWindow.SaveConfig();
deviceDriver = new SoundIoHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out.");
}
}
}
}
// Initialize Configuration.
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? HLE.MemoryConfiguration.MemoryConfiguration6GiB : HLE.MemoryConfiguration.MemoryConfiguration4GiB;
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(VirtualFileSystem,
_parent.LibHacHorizonManager,
ContentManager,
_accountManager,
_userChannelPersistence,
renderer,
deviceDriver,
memoryConfiguration,
_parent.UiHandler,
(SystemLanguage)ConfigurationState.Instance.System.Language.Value,
(RegionCode)ConfigurationState.Instance.System.Region.Value,
ConfigurationState.Instance.Graphics.EnableVsync,
ConfigurationState.Instance.System.EnableDockedMode,
ConfigurationState.Instance.System.EnablePtc,
ConfigurationState.Instance.System.EnableInternetAccess,
fsIntegrityCheckLevel,
ConfigurationState.Instance.System.FsGlobalAccessLogMode,
ConfigurationState.Instance.System.SystemTimeOffset,
ConfigurationState.Instance.System.TimeZone,
ConfigurationState.Instance.System.MemoryManagerMode,
ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume);
HLE.HLEConfiguration configuration = new(VirtualFileSystem,
_viewModel.LibHacHorizonManager,
ContentManager,
_accountManager,
_userChannelPersistence,
renderer,
InitializeAudio(),
memoryConfiguration,
_viewModel.UiHandler,
(SystemLanguage)ConfigurationState.Instance.System.Language.Value,
(RegionCode)ConfigurationState.Instance.System.Region.Value,
ConfigurationState.Instance.Graphics.EnableVsync,
ConfigurationState.Instance.System.EnableDockedMode,
ConfigurationState.Instance.System.EnablePtc,
ConfigurationState.Instance.System.EnableInternetAccess,
ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
ConfigurationState.Instance.System.FsGlobalAccessLogMode,
ConfigurationState.Instance.System.SystemTimeOffset,
ConfigurationState.Instance.System.TimeZone,
ConfigurationState.Instance.System.MemoryManagerMode,
ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume);
Device = new Switch(configuration);
}
private static IHardwareDeviceDriver InitializeAudio()
{
var availableBackends = new List<AudioBackend>()
{
AudioBackend.SDL2,
AudioBackend.SoundIo,
AudioBackend.OpenAl,
AudioBackend.Dummy
};
AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value;
for (int i = 0; i < availableBackends.Count; i++)
{
if (availableBackends[i] == preferredBackend)
{
availableBackends.RemoveAt(i);
availableBackends.Insert(0, preferredBackend);
break;
}
}
static IHardwareDeviceDriver InitializeAudioBackend<T>(AudioBackend backend, AudioBackend nextBackend) where T : IHardwareDeviceDriver, new()
{
if (T.IsSupported)
{
return new T();
}
else
{
Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}.");
return null;
}
}
IHardwareDeviceDriver deviceDriver = null;
for (int i = 0; i < availableBackends.Count; i++)
{
AudioBackend currentBackend = availableBackends[i];
AudioBackend nextBackend = i + 1 < availableBackends.Count ? availableBackends[i + 1] : AudioBackend.Dummy;
deviceDriver = currentBackend switch
{
AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend),
AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend),
AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend),
_ => new DummyHardwareDeviceDriver()
};
if (deviceDriver != null)
{
ConfigurationState.Instance.System.AudioBackend.Value = currentBackend;
break;
}
}
MainWindowViewModel.SaveConfig();
return deviceDriver;
}
private void Window_SizeChanged(object sender, Size e)
{
Width = (int)e.Width;
Width = (int)e.Width;
Height = (int)e.Height;
SetRendererWindowSize(e);
}
@@ -779,7 +739,7 @@ namespace Ryujinx.Ava
{
UpdateFrame();
// Polling becomes expensive if it's not slept
// Polling becomes expensive if it's not slept.
Thread.Sleep(1);
}
}
@@ -788,25 +748,18 @@ namespace Ryujinx.Ava
{
Dispatcher.UIThread.InvokeAsync(() =>
{
if (_parent.ViewModel.StartGamesInFullscreen)
if (_viewModel.StartGamesInFullscreen)
{
_parent.WindowState = WindowState.FullScreen;
_viewModel.WindowState = WindowState.FullScreen;
}
if (_parent.WindowState == WindowState.FullScreen)
if (_viewModel.WindowState == WindowState.FullScreen)
{
_parent.ViewModel.ShowMenuAndStatusBar = false;
_viewModel.ShowMenuAndStatusBar = false;
}
});
IRenderer renderer = Device.Gpu.Renderer;
if (renderer is ThreadedRenderer tr)
{
renderer = tr.BaseRenderer;
}
_renderer = renderer;
_renderer = Device.Gpu.Renderer is ThreadedRenderer tr ? tr.BaseRenderer : Device.Gpu.Renderer;
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
@@ -819,7 +772,7 @@ namespace Ryujinx.Ava
Width = (int)Renderer.Bounds.Width;
Height = (int)Renderer.Bounds.Height;
_renderer.Window.SetSize((int)(Width * _parent.PlatformImpl.RenderScaling), (int)(Height * _parent.PlatformImpl.RenderScaling));
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
_chrono.Start();
@@ -847,7 +800,7 @@ namespace Ryujinx.Ava
if (!_renderingStarted)
{
_renderingStarted = true;
_parent.SwitchToGameControl();
_viewModel.SwitchToRenderer(false);
}
Device.PresentFrame(() => Renderer?.SwapBuffers());
@@ -865,13 +818,12 @@ namespace Ryujinx.Ava
public void UpdateStatus()
{
// Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued
// Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued.
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
float scale = GraphicsConfig.ResScale;
if (scale != 1)
if (GraphicsConfig.ResScale != 1)
{
dockedMode += $" ({scale}x)";
dockedMode += $" ({GraphicsConfig.ResScale}x)";
}
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
@@ -888,7 +840,6 @@ namespace Ryujinx.Ava
public async Task ShowExitPrompt()
{
bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit;
if (!shouldExit)
{
if (_dialogShown)
@@ -897,6 +848,7 @@ namespace Ryujinx.Ava
}
_dialogShown = true;
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog();
_dialogShown = false;
@@ -914,7 +866,7 @@ namespace Ryujinx.Ava
{
Dispatcher.UIThread.Post(() =>
{
_parent.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default;
_viewModel.Cursor = _isCursorInRenderer ? InvisibleCursor : Cursor.Default;
});
}
else
@@ -925,7 +877,7 @@ namespace Ryujinx.Ava
Dispatcher.UIThread.Post(() =>
{
_parent.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default;
_viewModel.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default;
});
}
}
@@ -938,13 +890,13 @@ namespace Ryujinx.Ava
return false;
}
if (_parent.IsActive)
if (_viewModel.IsActive)
{
Dispatcher.UIThread.Post(() =>
{
HandleScreenState();
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _parent.WindowState != WindowState.FullScreen)
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
{
Device.Application.DiskCacheLoadState?.Cancel();
}
@@ -953,7 +905,7 @@ namespace Ryujinx.Ava
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
if (_parent.IsActive)
if (_viewModel.IsActive)
{
KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
@@ -969,10 +921,10 @@ namespace Ryujinx.Ava
ScreenshotRequested = true;
break;
case KeyboardHotkeyState.ShowUi:
_parent.ViewModel.ShowMenuAndStatusBar = true;
_viewModel.ShowMenuAndStatusBar = true;
break;
case KeyboardHotkeyState.Pause:
if (_parent.ViewModel.IsPaused)
if (_viewModel.IsPaused)
{
Resume();
}
@@ -991,7 +943,7 @@ namespace Ryujinx.Ava
Device.SetVolume(0);
}
_parent.ViewModel.Volume = Device.GetVolume();
_viewModel.Volume = Device.GetVolume();
break;
case KeyboardHotkeyState.ResScaleUp:
GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1;
@@ -1004,13 +956,13 @@ namespace Ryujinx.Ava
_newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2);
Device.SetVolume(_newVolume);
_parent.ViewModel.Volume = Device.GetVolume();
_viewModel.Volume = Device.GetVolume();
break;
case KeyboardHotkeyState.VolumeDown:
_newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2);
Device.SetVolume(_newVolume);
_parent.ViewModel.Volume = Device.GetVolume();
_viewModel.Volume = Device.GetVolume();
break;
case KeyboardHotkeyState.None:
(_keyboardInterface as AvaloniaKeyboard).Clear();
@@ -1027,10 +979,10 @@ namespace Ryujinx.Ava
}
}
// Touchscreen
// Touchscreen.
bool hasTouch = false;
if (_parent.IsActive && !ConfigurationState.Instance.Hid.EnableMouse)
if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse)
{
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Pfad",
"GameListContextMenuOpenUserSaveDirectory": "Spielstand-Verzeichnis öffnen",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Benutzer-Spielstand beinhaltet",
"GameListContextMenuOpenUserDeviceDirectory": "Benutzer-Geräte-Verzeichnis öffnen",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Öffnet das Verzeichnis, welches den Geräte-Spielstände beinhaltet",
"GameListContextMenuOpenUserBcatDirectory": "Benutzer-BCAT-Vezeichnis öffnen",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Öffnet das Verzeichnis, welches den BCAT Cache des Spiels beinhaltet",
"GameListContextMenuOpenDeviceSaveDirectory": "Benutzer-Geräte-Verzeichnis öffnen",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Geräte-Spielstände beinhaltet",
"GameListContextMenuOpenBcatSaveDirectory": "Benutzer-BCAT-Vezeichnis öffnen",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den BCAT Cache des Spiels beinhaltet",
"GameListContextMenuManageTitleUpdates": "Verwalten von Spiel Updates",
"GameListContextMenuManageTitleUpdatesToolTip": "Öffnet den Spiel-Update-Manager",
"GameListContextMenuManageDlc": "Verwalten von DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Konto wiederherstellen",
"Recover": "Wiederherstellen",
"UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Τοποθεσία",
"GameListContextMenuOpenUserSaveDirectory": "Άνοιγμα Τοποθεσίας Αποθήκευσης Χρήστη",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Χρήστη της εφαρμογής",
"GameListContextMenuOpenUserDeviceDirectory": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής",
"GameListContextMenuOpenUserBcatDirectory": "Άνοιγμα Τοποθεσίας BCAT",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής",
"GameListContextMenuOpenDeviceSaveDirectory": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής",
"GameListContextMenuOpenBcatSaveDirectory": "Άνοιγμα Τοποθεσίας BCAT",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής",
"GameListContextMenuManageTitleUpdates": "Διαχείριση Ενημερώσεων Παιχνιδιού",
"GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού",
"GameListContextMenuManageDlc": "Διαχείριση DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
"Recover": "Recover",
"UserProfilesRecoverHeading": "Saves were found for the following accounts"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Path",
"GameListContextMenuOpenUserSaveDirectory": "Open User Save Directory",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Opens the directory which contains Application's User Save",
"GameListContextMenuOpenUserDeviceDirectory": "Open User Device Directory",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Opens the directory which contains Application's Device Save",
"GameListContextMenuOpenUserBcatDirectory": "Open User BCAT Directory",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Opens the directory which contains Application's BCAT Save",
"GameListContextMenuOpenDeviceSaveDirectory": "Open Device Save Directory",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Opens the directory which contains Application's Device Save",
"GameListContextMenuOpenBcatSaveDirectory": "Open BCAT Save Directory",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Opens the directory which contains Application's BCAT Save",
"GameListContextMenuManageTitleUpdates": "Manage Title Updates",
"GameListContextMenuManageTitleUpdatesToolTip": "Opens the Title Update management window",
"GameListContextMenuManageDlc": "Manage DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
"Recover": "Recover",
"UserProfilesRecoverHeading" : "Saves were found for the following accounts"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Directorio",
"GameListContextMenuOpenUserSaveDirectory": "Abrir carpeta de guardado de este usuario",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del usuario para esta aplicación",
"GameListContextMenuOpenUserDeviceDirectory": "Abrir carpeta de guardado del sistema para el usuario actual",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación",
"GameListContextMenuOpenUserBcatDirectory": "Abrir carpeta de guardado BCAT del usuario",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Abrir la carpeta que contiene el guardado BCAT de esta aplicación",
"GameListContextMenuOpenDeviceSaveDirectory": "Abrir carpeta de guardado del sistema para el usuario actual",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación",
"GameListContextMenuOpenBcatSaveDirectory": "Abrir carpeta de guardado BCAT del usuario",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abrir la carpeta que contiene el guardado BCAT de esta aplicación",
"GameListContextMenuManageTitleUpdates": "Gestionar actualizaciones del juego",
"GameListContextMenuManageTitleUpdatesToolTip": "Abrir la ventana de gestión de actualizaciones de esta aplicación",
"GameListContextMenuManageDlc": "Gestionar DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
"Recover": "Recover",
"UserProfilesRecoverHeading": "Saves were found for the following accounts"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Chemin",
"GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu",
"GameListContextMenuOpenUserDeviceDirectory": "Ouvrir le dossier de sauvegarde console",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu",
"GameListContextMenuOpenUserBcatDirectory": "Ouvrir le dossier de sauvegarde BCAT",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu",
"GameListContextMenuOpenDeviceSaveDirectory": "Ouvrir le dossier de sauvegarde console",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu",
"GameListContextMenuOpenBcatSaveDirectory": "Ouvrir le dossier de sauvegarde BCAT",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu",
"GameListContextMenuManageTitleUpdates": "Gérer les mises à jour",
"GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion des mises à jour",
"GameListContextMenuManageDlc": "Gérer les DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Récupérer les comptes perdus",
"Recover": "Récupérer",
"UserProfilesRecoverHeading": "Des sauvegardes ont été trouvées pour les comptes suivants"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Percorso",
"GameListContextMenuOpenUserSaveDirectory": "Apri la cartella salvataggi dell'utente",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco dell'utente",
"GameListContextMenuOpenUserDeviceDirectory": "Apri la cartella dispositivo dell'utente",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo",
"GameListContextMenuOpenUserBcatDirectory": "Apri la cartella BCAT dell'utente",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione",
"GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dispositivo dell'utente",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo",
"GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella BCAT dell'utente",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione",
"GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco",
"GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco",
"GameListContextMenuManageDlc": "Gestici DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Recupera il tuo account",
"Recover": "Recupera",
"UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "パス",
"GameListContextMenuOpenUserSaveDirectory": "セーブディレクトリを開く",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "アプリケーションのユーザセーブデータを格納するディレクトリを開きます",
"GameListContextMenuOpenUserDeviceDirectory": "デバイスディレクトリを開く",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "アプリケーションのデバイスセーブデータを格納するディレクトリを開きます",
"GameListContextMenuOpenUserBcatDirectory": "BCATディレクトリを開く",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "アプリケーションの BCAT セーブデータを格納するディレクトリを開きます",
"GameListContextMenuOpenDeviceSaveDirectory": "デバイスディレクトリを開く",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "アプリケーションのデバイスセーブデータを格納するディレクトリを開きます",
"GameListContextMenuOpenBcatSaveDirectory": "BCATディレクトリを開く",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "アプリケーションの BCAT セーブデータを格納するディレクトリを開きます",
"GameListContextMenuManageTitleUpdates": "アップデートを管理",
"GameListContextMenuManageTitleUpdatesToolTip": "タイトルのアップデート管理ウインドウを開きます",
"GameListContextMenuManageDlc": "DLCを管理",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "アカウントの復旧",
"Recover": "復旧",
"UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "경로",
"GameListContextMenuOpenUserSaveDirectory": "사용자 저장 디렉토리 열기\n",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "응용 프로그램의 사용자 저장이 포함된 디렉토리 열기\n",
"GameListContextMenuOpenUserDeviceDirectory": "사용자 장치 디렉토리 열기",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "응용 프로그램의 장치 저장이 포함된 디렉토리 열기\n",
"GameListContextMenuOpenUserBcatDirectory": "사용자의 BCAT 디렉토리 열기\n",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "응용 프로그램의 BCAT 저장이 포함된 디렉토리 열기\n",
"GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉토리 열기",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "응용 프로그램의 장치 저장이 포함된 디렉토리 열기\n",
"GameListContextMenuOpenBcatSaveDirectory": "사용자의 BCAT 디렉토리 열기\n",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "응용 프로그램의 BCAT 저장이 포함된 디렉토리 열기\n",
"GameListContextMenuManageTitleUpdates": "타이틀 업데이트 관리\n",
"GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기",
"GameListContextMenuManageDlc": "DLC 관리",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "잃어버린 계정 복구",
"Recover": "복구",
"UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Ścieżka",
"GameListContextMenuOpenUserSaveDirectory": "Otwórz Katalog Zapisów Użytkownika",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Użytkownika Aplikacji",
"GameListContextMenuOpenUserDeviceDirectory": "Otwórz Katalog Urządzeń Użytkownika",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Urządzenia Aplikacji",
"GameListContextMenuOpenUserBcatDirectory": "Otwórz Katalog BCAT Użytkownika",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Otwiera katalog, który zawiera Zapis BCAT Aplikacji",
"GameListContextMenuOpenDeviceSaveDirectory": "Otwórz Katalog Urządzeń Użytkownika",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Urządzenia Aplikacji",
"GameListContextMenuOpenBcatSaveDirectory": "Otwórz Katalog BCAT Użytkownika",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis BCAT Aplikacji",
"GameListContextMenuManageTitleUpdates": "Zarządzaj Aktualizacjami Tytułów",
"GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania Aktualizacjami Tytułu",
"GameListContextMenuManageDlc": "Zarządzaj DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Odzyskaj Utracone Konta",
"Recover": "Odzyskaj",
"UserProfilesRecoverHeading": "Znaleziono zapisy dla następujących kont"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Caminho",
"GameListContextMenuOpenUserSaveDirectory": "Abrir diretório de saves do usuário",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre o diretório que contém jogos salvos para o usuário atual",
"GameListContextMenuOpenUserDeviceDirectory": "Abrir diretório de saves de dispositivo do usuário",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Abre o diretório que contém saves do dispositivo para o usuário atual",
"GameListContextMenuOpenUserBcatDirectory": "Abrir diretório de saves BCAT do usuário",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Abre o diretório que contém saves BCAT para o usuário atual",
"GameListContextMenuOpenDeviceSaveDirectory": "Abrir diretório de saves de dispositivo do usuário",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre o diretório que contém saves do dispositivo para o usuário atual",
"GameListContextMenuOpenBcatSaveDirectory": "Abrir diretório de saves BCAT do usuário",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abre o diretório que contém saves BCAT para o usuário atual",
"GameListContextMenuManageTitleUpdates": "Gerenciar atualizações do jogo",
"GameListContextMenuManageTitleUpdatesToolTip": "Abre a janela de gerenciamento de atualizações",
"GameListContextMenuManageDlc": "Gerenciar DLCs",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Recuperar contas perdidas",
"Recover": "Recuperar",
"UserProfilesRecoverHeading": "Jogos salvos foram encontrados para as seguintes contas"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Путь",
"GameListContextMenuOpenUserSaveDirectory": "Открыть каталог сохранений пользователя",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает каталог, содержащий пользовательское сохранение приложения",
"GameListContextMenuOpenUserDeviceDirectory": "Открыть каталог пользовательских устройств",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Открывает каталог, содержащий сохранение устройства приложения",
"GameListContextMenuOpenUserBcatDirectory": "Открыть каталог пользователей BCAT",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Открывает каталог, содержащий BCAT сохранения приложения.",
"GameListContextMenuOpenDeviceSaveDirectory": "Открыть каталог пользовательских устройств",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает каталог, содержащий сохранение устройства приложения",
"GameListContextMenuOpenBcatSaveDirectory": "Открыть каталог пользователей BCAT",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает каталог, содержащий BCAT сохранения приложения.",
"GameListContextMenuManageTitleUpdates": "Управление обновлениями заголовков",
"GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков",
"GameListContextMenuManageDlc": "Управление DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
"Recover": "Recover",
"UserProfilesRecoverHeading": "Saves were found for the following accounts"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Yol",
"GameListContextMenuOpenUserSaveDirectory": "Kullanıcı Kayıt Dosyası Dizinini Aç",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Uygulamanın Kullanıcı Kaydı'nın bulunduğu dizini açar",
"GameListContextMenuOpenUserDeviceDirectory": "Kullanıcı Cihaz Dizinini Aç",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Uygulamanın Kullanıcı Cihaz Kaydı'nın bulunduğu dizini açar",
"GameListContextMenuOpenUserBcatDirectory": "Kullanıcı BCAT Dizinini Aç",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Uygulamanın Kullanıcı BCAT Kaydı'nın bulunduğu dizini açar",
"GameListContextMenuOpenDeviceSaveDirectory": "Kullanıcı Cihaz Dizinini Aç",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Uygulamanın Kullanıcı Cihaz Kaydı'nın bulunduğu dizini açar",
"GameListContextMenuOpenBcatSaveDirectory": "Kullanıcı BCAT Dizinini Aç",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Uygulamanın Kullanıcı BCAT Kaydı'nın bulunduğu dizini açar",
"GameListContextMenuManageTitleUpdates": "Oyun Güncellemelerini Yönet",
"GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme Yönetim Penceresini Açar",
"GameListContextMenuManageDlc": "DLC'leri Yönet",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Kayıp Hesapları Kurtar",
"Recover": "Kurtar",
"UserProfilesRecoverHeading": "Aşağıdaki hesaplar için kayıtlar bulundu"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "Шлях",
"GameListContextMenuOpenUserSaveDirectory": "Відкрити каталог збереження користувача",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Відкриває каталог, який містить збереження користувача програми",
"GameListContextMenuOpenUserDeviceDirectory": "Відкрити каталог пристроїв користувача",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми",
"GameListContextMenuOpenUserBcatDirectory": "Відкрити каталог користувача BCAT",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Відкриває каталог, який містить BCAT-збереження програми",
"GameListContextMenuOpenDeviceSaveDirectory": "Відкрити каталог пристроїв користувача",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми",
"GameListContextMenuOpenBcatSaveDirectory": "Відкрити каталог користувача BCAT",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "Відкриває каталог, який містить BCAT-збереження програми",
"GameListContextMenuManageTitleUpdates": "Керування оновленнями заголовків",
"GameListContextMenuManageTitleUpdatesToolTip": "Відкриває вікно керування оновленням заголовка",
"GameListContextMenuManageDlc": "Керування DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів",
"Recover": "Відновити",
"UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "路径",
"GameListContextMenuOpenUserSaveDirectory": "打开应用存档目录",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录",
"GameListContextMenuOpenUserDeviceDirectory": "打开应用系统目录",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "打开包含游戏系统设置的目录",
"GameListContextMenuOpenUserBcatDirectory": "打开 BCAT 目录",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "打开包含游戏 BCAT 数据的目录",
"GameListContextMenuOpenDeviceSaveDirectory": "打开应用系统目录",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开包含游戏系统设置的目录",
"GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录",
"GameListContextMenuManageTitleUpdates": "管理游戏更新",
"GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理器",
"GameListContextMenuManageDlc": "管理 DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "恢复丢失的账户",
"Recover": "恢复",
"UserProfilesRecoverHeading": "找到了这些用户的存档数据"
}
}

View File

@@ -41,10 +41,10 @@
"GameListHeaderPath": "路徑",
"GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟儲存遊戲存檔的資料夾",
"GameListContextMenuOpenUserDeviceDirectory": "開啟系統資料夾",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "開啟包含遊戲系統設定的資料夾",
"GameListContextMenuOpenUserBcatDirectory": "開啟 BCAT 資料夾",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "開啟包含遊戲 BCAT 資料的資料夾",
"GameListContextMenuOpenDeviceSaveDirectory": "開啟系統資料夾",
"GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟包含遊戲系統設定的資料夾",
"GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 資料夾",
"GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟包含遊戲 BCAT 資料的資料夾",
"GameListContextMenuManageTitleUpdates": "管理遊戲更新",
"GameListContextMenuManageTitleUpdatesToolTip": "開啟更新管理視窗",
"GameListContextMenuManageDlc": "管理 DLC",
@@ -610,4 +610,4 @@
"UserProfilesRecoverLostAccounts": "恢復遺失的帳號",
"Recover": "恢復",
"UserProfilesRecoverHeading": "在以下帳號找到了一些遊戲存檔"
}
}

View File

@@ -269,6 +269,8 @@
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
<Color x:Key="VsyncEnabled">#FF2EEAC9</Color>
<Color x:Key="VsyncDisabled">#FFFF4554</Color>
<x:Double x:Key="ScrollBarThickness">15</x:Double>
<x:Double x:Key="FontSizeSmall">8</x:Double>
<x:Double x:Key="FontSizeNormal">10</x:Double>

View File

@@ -0,0 +1,111 @@
using Avalonia.Utilities;
using System;
using System.Text;
namespace Ryujinx.Ava.UI.Helper
{
using AvaLogger = Avalonia.Logging.Logger;
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
using RyuLogger = Ryujinx.Common.Logging.Logger;
using RyuLogClass = Ryujinx.Common.Logging.LogClass;
internal class LoggerAdapter : Avalonia.Logging.ILogSink
{
public static void Register()
{
AvaLogger.Sink = new LoggerAdapter();
}
private static RyuLogger.Log? GetLog(AvaLogLevel level)
{
return level switch
{
AvaLogLevel.Verbose => RyuLogger.Trace,
AvaLogLevel.Debug => RyuLogger.Debug,
AvaLogLevel.Information => RyuLogger.Info,
AvaLogLevel.Warning => RyuLogger.Warning,
AvaLogLevel.Error => RyuLogger.Error,
AvaLogLevel.Fatal => RyuLogger.Notice,
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
};
}
public bool IsEnabled(AvaLogLevel level, string area)
{
return GetLog(level) != null;
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, null));
}
public void Log<T0>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0 }));
}
public void Log<T0, T1>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0, propertyValue1 }));
}
public void Log<T0, T1, T2>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0, propertyValue1, propertyValue2 }));
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, propertyValues));
}
private static string Format(string area, string template, object source, object[] v)
{
var result = new StringBuilder();
var r = new CharacterReader(template.AsSpan());
var i = 0;
result.Append('[');
result.Append(area);
result.Append("] ");
while (!r.End)
{
var c = r.Take();
if (c != '{')
{
result.Append(c);
}
else
{
if (r.Peek != '{')
{
result.Append('\'');
result.Append(i < v.Length ? v[i++] : null);
result.Append('\'');
r.TakeUntil('}');
r.Take();
}
else
{
result.Append('{');
r.Take();
}
}
}
if (source != null)
{
result.Append(" (");
result.Append(source.GetType().Name);
result.Append(" #");
result.Append(source.GetHashCode());
result.Append(')');
}
return result.ToString();
}
}
}

View File

@@ -11,10 +11,10 @@ namespace Ryujinx.Ava.Input
{
internal class AvaloniaMouseDriver : IGamepadDriver
{
private Control _widget;
private bool _isDisposed;
private Size _size;
private readonly Window _window;
private Control _widget;
private bool _isDisposed;
private Size _size;
private readonly TopLevel _window;
public bool[] PressedButtons { get; }
public Vector2 CurrentPosition { get; private set; }
@@ -23,7 +23,7 @@ namespace Ryujinx.Ava.Input
public string DriverName => "AvaloniaMouseDriver";
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public AvaloniaMouseDriver(Window window, Control parent)
public AvaloniaMouseDriver(TopLevel window, Control parent)
{
_widget = parent;
_window = window;

View File

@@ -56,7 +56,7 @@ namespace Ryujinx.Modules
}
Running = true;
mainWindow.CanUpdate = false;
mainWindow.ViewModel.CanUpdate = false;
// Detect current platform
if (OperatingSystem.IsMacOS())
@@ -95,7 +95,7 @@ namespace Ryujinx.Modules
{
using (HttpClient jsonClient = ConstructHttpClient())
{
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformations.ReleaseChannelOwner}/{ReleaseInformations.ReleaseChannelRepo}/releases/latest";
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
JObject jsonRoot = JObject.Parse(fetchedJson);
@@ -182,7 +182,7 @@ namespace Ryujinx.Modules
}
Running = false;
mainWindow.CanUpdate = true;
mainWindow.ViewModel.CanUpdate = true;
return;
}
@@ -625,7 +625,7 @@ namespace Ryujinx.Modules
return false;
}
if (Program.Version.Contains("dirty") || !ReleaseInformations.IsValid())
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
{
if (showWarnings)
{
@@ -640,7 +640,7 @@ namespace Ryujinx.Modules
#else
if (showWarnings)
{
if (ReleaseInformations.IsFlatHubBuild())
if (ReleaseInformation.IsFlatHubBuild())
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
}
@@ -711,4 +711,4 @@ namespace Ryujinx.Modules
}
}
}
}
}

View File

@@ -1,20 +1,23 @@
using Avalonia;
using Avalonia.Threading;
using Ryujinx.Ava.UI.Helper;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.SystemInfo;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
namespace Ryujinx.Ava
@@ -32,9 +35,51 @@ namespace Ryujinx.Ava
private const uint MB_ICONWARNING = 0x30;
[SupportedOSPlatform("linux")]
static void RegisterMimeTypes()
{
if (ReleaseInformation.IsFlatHubBuild())
{
return;
}
string mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
if (!File.Exists(Path.Combine(mimeDbPath, "packages", "Ryujinx.xml")))
{
string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
using Process mimeProcess = new();
mimeProcess.StartInfo.FileName = "xdg-mime";
mimeProcess.StartInfo.Arguments = $"install --novendor --mode user {mimeTypesFile}";
mimeProcess.Start();
mimeProcess.WaitForExit();
if (mimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Unable to install mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
return;
}
using Process updateMimeProcess = new();
updateMimeProcess.StartInfo.FileName = "update-mime-database";
updateMimeProcess.StartInfo.Arguments = mimeDbPath;
updateMimeProcess.Start();
updateMimeProcess.WaitForExit();
if (updateMimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
}
}
}
public static void Main(string[] args)
{
Version = ReleaseInformations.GetVersion();
Version = ReleaseInformation.GetVersion();
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
@@ -45,6 +90,8 @@ namespace Ryujinx.Ava
Initialize(args);
LoggerAdapter.Register();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
@@ -66,8 +113,7 @@ namespace Ryujinx.Ava
AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8.0f,
})
.UseSkia()
.LogToTrace();
.UseSkia();
}
private static void Initialize(string[] args)
@@ -93,6 +139,12 @@ namespace Ryujinx.Ava
// Initialize the logger system.
LoggerModule.Initialize();
// Register mime types on linux.
if (OperatingSystem.IsLinux())
{
RegisterMimeTypes();
}
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
@@ -175,6 +227,12 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
}
private static void PrintSystemInfo()
@@ -218,4 +276,4 @@ namespace Ryujinx.Ava
Logger.Shutdown();
}
}
}
}

View File

@@ -76,6 +76,16 @@
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<Content Include="..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\distribution\linux\mime\Ryujinx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>mime\Ryujinx.xml</TargetPath>
</Content>
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Ui\**\*.xaml">
<SubType>Designer</SubType>

View File

@@ -144,9 +144,9 @@ namespace Ryujinx.Ava.UI.Applet
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
{
device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);
if (_parent.AppHost != null)
if (_parent.ViewModel.AppHost != null)
{
_parent.AppHost.Stop();
_parent.ViewModel.AppHost.Stop();
}
}

View File

@@ -136,7 +136,7 @@ namespace Ryujinx.Ava.UI.Applet
Dispatcher.UIThread.Post(() =>
{
_hiddenTextBox.Clear();
_parent.RendererControl.Focus();
_parent.ViewModel.RendererControl.Focus();
_parent = null;
});

View File

@@ -13,8 +13,8 @@ namespace Ryujinx.Ava.UI.Applet
DefaultBackgroundColor = BrushToThemeColor(parent.Background);
DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
SelectionBackgroundColor = BrushToThemeColor(parent.SearchBox.SelectionBrush);
SelectionForegroundColor = BrushToThemeColor(parent.SearchBox.SelectionForegroundBrush);
SelectionBackgroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
SelectionForegroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
}
public string FontFamily { get; }

View File

@@ -26,12 +26,12 @@
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenDeviceSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" />
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenBcatSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" />
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
<Separator />
<MenuItem
Command="{Binding OpenTitleUpdateManager}"
@@ -139,6 +139,9 @@
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.GridItemSelectorSize}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>

View File

@@ -38,9 +38,9 @@ namespace Ryujinx.Ava.UI.Controls
{
if (sender is ListBox listBox)
{
var selected = listBox.SelectedItem as ApplicationData;
_selectedApplication = listBox.SelectedItem as ApplicationData;
_selectedApplication = selected;
(DataContext as MainWindowViewModel).GridSelectedApplication = _selectedApplication;
}
}

View File

@@ -25,12 +25,12 @@
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenDeviceSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" />
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenBcatSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" />
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
<Separator />
<MenuItem
Command="{Binding OpenTitleUpdateManager}"
@@ -137,12 +137,12 @@
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="MinHeight" Value="100" />
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.ListItemSelectorSize}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>

View File

@@ -38,9 +38,9 @@ namespace Ryujinx.Ava.UI.Controls
{
if (sender is ListBox listBox)
{
var selected = listBox.SelectedItem as ApplicationData;
_selectedApplication = listBox.SelectedItem as ApplicationData;
_selectedApplication = selected;
(DataContext as MainWindowViewModel).ListSelectedApplication = _selectedApplication;
}
}

View File

@@ -19,7 +19,56 @@ namespace Ryujinx.Ava.UI.Helpers
{
private static bool _isChoiceDialogOpen;
private async static Task<UserResult> ShowContentDialog(
public async static Task<UserResult> ShowContentDialog(
string title,
object content,
string primaryButton,
string secondaryButton,
string closeButton,
UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null,
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{
UserResult result = UserResult.None;
ContentDialog contentDialog = new()
{
Title = title,
PrimaryButtonText = primaryButton,
SecondaryButtonText = secondaryButton,
CloseButtonText = closeButton,
Content = content
};
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = primaryButtonResult;
});
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.No;
contentDialog.PrimaryButtonClick -= deferCloseAction;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Cancel;
contentDialog.PrimaryButtonClick -= deferCloseAction;
});
if (deferResetEvent != null)
{
contentDialog.PrimaryButtonClick += deferCloseAction;
}
await ShowAsync(contentDialog);
return result;
}
private async static Task<UserResult> ShowTextDialog(
string title,
string primaryText,
string secondaryText,
@@ -32,119 +81,9 @@ namespace Ryujinx.Ava.UI.Helpers
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{
UserResult result = UserResult.None;
Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol);
bool useOverlay = false;
Window mainWindow = null;
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al)
{
foreach (var item in al.Windows)
{
if (item.IsActive && item is MainWindow window && window.ViewModel.IsGameRunning)
{
mainWindow = window;
useOverlay = true;
break;
}
}
}
ContentDialog contentDialog = null;
ContentDialogOverlayWindow overlay = null;
if (useOverlay)
{
overlay = new ContentDialogOverlayWindow()
{
Height = mainWindow.Bounds.Height,
Width = mainWindow.Bounds.Width,
Position = mainWindow.PointToScreen(new Point())
};
mainWindow.PositionChanged += OverlayOnPositionChanged;
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
{
overlay.Position = mainWindow.PointToScreen(new Point());
}
contentDialog = overlay.ContentDialog;
bool opened = false;
overlay.Opened += OverlayOnActivated;
async void OverlayOnActivated(object sender, EventArgs e)
{
if (opened)
{
return;
}
opened = true;
overlay.Position = mainWindow.PointToScreen(new Point());
await ShowDialog();
}
await overlay.ShowDialog(mainWindow);
}
else
{
contentDialog = new ContentDialog();
await ShowDialog();
}
async Task ShowDialog()
{
contentDialog.Title = title;
contentDialog.PrimaryButtonText = primaryButton;
contentDialog.SecondaryButtonText = secondaryButton;
contentDialog.CloseButtonText = closeButton;
contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = primaryButtonResult;
});
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.No;
contentDialog.PrimaryButtonClick -= deferCloseAction;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Cancel;
contentDialog.PrimaryButtonClick -= deferCloseAction;
});
if (deferResetEvent != null)
{
contentDialog.PrimaryButtonClick += deferCloseAction;
}
if (useOverlay)
{
await contentDialog.ShowAsync(overlay, ContentDialogPlacement.Popup);
overlay!.Close();
}
else
{
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
}
}
if (useOverlay)
{
overlay.Content = null;
overlay.Close();
}
return result;
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, doWhileDeferred, deferCloseAction);
}
public async static Task<UserResult> ShowDeferredContentDialog(
@@ -162,7 +101,7 @@ namespace Ryujinx.Ava.UI.Helpers
bool startedDeferring = false;
UserResult result = UserResult.None;
return await ShowContentDialog(
return await ShowTextDialog(
title,
primaryText,
secondaryText,
@@ -192,8 +131,7 @@ namespace Ryujinx.Ava.UI.Helpers
sender.PrimaryButtonClick -= DeferClose;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() =>
_ = Task.Run(() =>
{
deferResetEvent.WaitOne();
@@ -202,7 +140,6 @@ namespace Ryujinx.Ava.UI.Helpers
deferral.Complete();
});
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
if (doWhileDeferred != null)
{
@@ -213,34 +150,42 @@ namespace Ryujinx.Ava.UI.Helpers
}
}
private static Grid CreateDialogTextContent(string primaryText, string secondaryText, int symbol)
private static Grid CreateTextDialogContent(string primaryText, string secondaryText, int symbol)
{
Grid content = new Grid();
content.RowDefinitions = new RowDefinitions() { new RowDefinition(), new RowDefinition() };
content.ColumnDefinitions = new ColumnDefinitions() { new ColumnDefinition(GridLength.Auto), new ColumnDefinition() };
Grid content = new()
{
RowDefinitions = new RowDefinitions() { new RowDefinition(), new RowDefinition() },
ColumnDefinitions = new ColumnDefinitions() { new ColumnDefinition(GridLength.Auto), new ColumnDefinition() },
content.MinHeight = 80;
MinHeight = 80
};
SymbolIcon icon = new()
{
Symbol = (Symbol)symbol,
Margin = new Thickness(10),
FontSize = 40,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
SymbolIcon icon = new SymbolIcon { Symbol = (Symbol)symbol, Margin = new Thickness(10) };
icon.FontSize = 40;
icon.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center;
Grid.SetColumn(icon, 0);
Grid.SetRowSpan(icon, 2);
Grid.SetRow(icon, 0);
TextBlock primaryLabel = new TextBlock()
TextBlock primaryLabel = new()
{
Text = primaryText,
Margin = new Thickness(5),
Text = primaryText,
Margin = new Thickness(5),
TextWrapping = TextWrapping.Wrap,
MaxWidth = 450
MaxWidth = 450
};
TextBlock secondaryLabel = new TextBlock()
TextBlock secondaryLabel = new()
{
Text = secondaryText,
Margin = new Thickness(5),
Text = secondaryText,
Margin = new Thickness(5),
TextWrapping = TextWrapping.Wrap,
MaxWidth = 450
MaxWidth = 450
};
Grid.SetColumn(primaryLabel, 1);
@@ -262,7 +207,7 @@ namespace Ryujinx.Ava.UI.Helpers
string closeButton,
string title)
{
return await ShowContentDialog(
return await ShowTextDialog(
title,
primary,
secondaryText,
@@ -280,7 +225,7 @@ namespace Ryujinx.Ava.UI.Helpers
string title,
UserResult primaryButtonResult = UserResult.Yes)
{
return await ShowContentDialog(
return await ShowTextDialog(
string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle] : title,
primaryText,
secondaryText,
@@ -298,7 +243,7 @@ namespace Ryujinx.Ava.UI.Helpers
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
{
await ShowContentDialog(
await ShowTextDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle],
primary,
secondaryText,
@@ -310,7 +255,7 @@ namespace Ryujinx.Ava.UI.Helpers
internal static async Task CreateWarningDialog(string primary, string secondaryText)
{
await ShowContentDialog(
await ShowTextDialog(
LocaleManager.Instance[LocaleKeys.DialogWarningTitle],
primary,
secondaryText,
@@ -324,7 +269,7 @@ namespace Ryujinx.Ava.UI.Helpers
{
Logger.Error?.Print(LogClass.Application, errorMessage);
await ShowContentDialog(
await ShowTextDialog(
LocaleManager.Instance[LocaleKeys.DialogErrorTitle],
LocaleManager.Instance[LocaleKeys.DialogErrorMessage],
errorMessage,
@@ -343,16 +288,15 @@ namespace Ryujinx.Ava.UI.Helpers
_isChoiceDialogOpen = true;
UserResult response =
await ShowContentDialog(
title,
primary,
secondaryText,
LocaleManager.Instance[LocaleKeys.InputDialogYes],
"",
LocaleManager.Instance[LocaleKeys.InputDialogNo],
(int)Symbol.Help,
UserResult.Yes);
UserResult response = await ShowTextDialog(
title,
primary,
secondaryText,
LocaleManager.Instance[LocaleKeys.InputDialogYes],
"",
LocaleManager.Instance[LocaleKeys.InputDialogNo],
(int)Symbol.Help,
UserResult.Yes);
_isChoiceDialogOpen = false;
@@ -396,5 +340,98 @@ namespace Ryujinx.Ava.UI.Helpers
return string.Empty;
}
public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog)
{
ContentDialogResult result;
ContentDialogOverlayWindow contentDialogOverlayWindow = null;
Window parent = GetMainWindow();
if (parent != null && parent.IsActive && parent is MainWindow window && window.ViewModel.IsGameRunning)
{
contentDialogOverlayWindow = new()
{
Height = parent.Bounds.Height,
Width = parent.Bounds.Width,
Position = parent.PointToScreen(new Point()),
ShowInTaskbar = false
};
parent.PositionChanged += OverlayOnPositionChanged;
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
{
contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
}
contentDialogOverlayWindow.ContentDialog = contentDialog;
bool opened = false;
contentDialogOverlayWindow.Opened += OverlayOnActivated;
async void OverlayOnActivated(object sender, EventArgs e)
{
if (opened)
{
return;
}
opened = true;
contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
result = await ShowDialog();
}
result = await contentDialogOverlayWindow.ShowDialog<ContentDialogResult>(parent);
}
else
{
result = await ShowDialog();
}
async Task<ContentDialogResult> ShowDialog()
{
if (contentDialogOverlayWindow is not null)
{
result = await contentDialog.ShowAsync(contentDialogOverlayWindow);
contentDialogOverlayWindow!.Close();
}
else
{
result = await contentDialog.ShowAsync();
}
return result;
}
if (contentDialogOverlayWindow is not null)
{
contentDialogOverlayWindow.Content = null;
contentDialogOverlayWindow.Close();
}
return result;
}
private static Window GetMainWindow()
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al)
{
foreach (Window item in al.Windows)
{
if (item.IsActive && item is MainWindow window)
{
return window;
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,136 @@
using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration;
using System;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.ViewModels
{
public class AboutWindowViewModel : BaseModel
{
private Bitmap _githubLogo;
private Bitmap _discordLogo;
private Bitmap _patreonLogo;
private Bitmap _twitterLogo;
private string _version;
private string _supporters;
public Bitmap GithubLogo
{
get => _githubLogo;
set
{
_githubLogo = value;
OnPropertyChanged();
}
}
public Bitmap DiscordLogo
{
get => _discordLogo;
set
{
_discordLogo = value;
OnPropertyChanged();
}
}
public Bitmap PatreonLogo
{
get => _patreonLogo;
set
{
_patreonLogo = value;
OnPropertyChanged();
}
}
public Bitmap TwitterLogo
{
get => _twitterLogo;
set
{
_twitterLogo = value;
OnPropertyChanged();
}
}
public string Supporters
{
get => _supporters;
set
{
_supporters = value;
OnPropertyChanged();
}
}
public string Version
{
get => _version;
set
{
_version = value;
OnPropertyChanged();
}
}
public string Developers
{
get => string.Format(LocaleManager.Instance[LocaleKeys.AboutPageDeveloperListMore], "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz");
}
public AboutWindowViewModel()
{
Version = Program.Version;
var assets = AvaloniaLocator.Current.GetService<Avalonia.Platform.IAssetLoader>();
if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light")
{
GithubLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.Ui.Common")));
}
else
{
GithubLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.Ui.Common")));
}
Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);
}
private async Task DownloadPatronsJson()
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
Supporters = LocaleManager.Instance[LocaleKeys.ConnectionError];
return;
}
HttpClient httpClient = new();
try
{
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString)) + "\n\n";
}
catch
{
Supporters = LocaleManager.Instance[LocaleKeys.ApiError];
}
}
}
}

View File

@@ -244,9 +244,9 @@ namespace Ryujinx.Ava.UI.ViewModels
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
if (_mainWindow.AppHost != null)
if (_mainWindow.ViewModel.AppHost != null)
{
_mainWindow.AppHost.NpadManager.BlockInputUpdates();
_mainWindow.ViewModel.AppHost.NpadManager.BlockInputUpdates();
}
_isLoaded = false;
@@ -862,7 +862,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
_mainWindow.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
// Atomically replace and signal input change.
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
@@ -891,7 +891,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
_mainWindow.AppHost?.NpadManager.UnblockInputUpdates();
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
SelectedGamepad?.Dispose();

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
@@ -8,8 +7,6 @@ using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
@@ -19,7 +16,6 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.Input;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Configuration.System;
using System;
@@ -30,11 +26,10 @@ using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.ViewModels
{
internal class SettingsViewModel : BaseModel
public class SettingsViewModel : BaseModel
{
private readonly VirtualFileSystem _virtualFileSystem;
private readonly ContentManager _contentManager;
private readonly StyleableWindow _owner;
private TimeZoneContentManager _timeZoneContentManager;
private readonly List<string> _validTzRegions;
@@ -44,10 +39,14 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _graphicsBackendMultithreadingIndex;
private float _volume;
private bool _isVulkanAvailable = true;
private bool _directoryChanged = false;
private List<string> _gpuIds = new List<string>();
private bool _directoryChanged;
private List<string> _gpuIds = new();
private KeyboardHotkeys _keyboardHotkeys;
private int _graphicsBackendIndex;
private string _customThemePath;
public event Action CloseWindow;
public event Action SaveSettingsEvent;
public int ResolutionScale
{
@@ -67,19 +66,16 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_graphicsBackendMultithreadingIndex = value;
if (_owner != null)
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
{
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
Dispatcher.UIThread.Post(async () =>
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
"",
"",
LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]);
});
}
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
"",
"",
LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]);
});
}
OnPropertyChanged();
@@ -120,12 +116,12 @@ namespace Ryujinx.Ava.UI.ViewModels
OnPropertyChanged();
}
}
public bool IsMacOS
{
get => OperatingSystem.IsMacOS();
}
public bool EnableDiscordIntegration { get; set; }
public bool CheckUpdatesOnStart { get; set; }
public bool ShowConfirmExit { get; set; }
@@ -160,7 +156,20 @@ namespace Ryujinx.Ava.UI.ViewModels
public string TimeZone { get; set; }
public string ShaderDumpPath { get; set; }
public string CustomThemePath { get; set; }
public string CustomThemePath
{
get
{
return _customThemePath;
}
set
{
_customThemePath = value;
OnPropertyChanged();
}
}
public int Language { get; set; }
public int Region { get; set; }
@@ -191,7 +200,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_volume = value;
ConfigurationState.Instance.System.AudioVolume.Value = (float)(_volume / 100);
ConfigurationState.Instance.System.AudioVolume.Value = _volume / 100;
OnPropertyChanged();
}
@@ -199,7 +208,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public DateTimeOffset DateOffset { get; set; }
public TimeSpan TimeOffset { get; set; }
public AvaloniaList<TimeZone> TimeZones { get; set; }
private AvaloniaList<TimeZone> TimeZones { get; set; }
public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
@@ -214,17 +223,13 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public IGamepadDriver AvaloniaKeyboardDriver { get; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager, StyleableWindow owner) : this()
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
_owner = owner;
if (Program.PreviewerDetached)
{
LoadTimeZones();
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
}
}
@@ -251,10 +256,10 @@ namespace Ryujinx.Ava.UI.ViewModels
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
}
private unsafe void LoadAvailableGpus()
private void LoadAvailableGpus()
{
_gpuIds = new List<string>();
List<string> names = new List<string>();
List<string> names = new();
var devices = VulkanRenderer.GetPhysicalDevices();
if (devices.Length == 0)
@@ -272,7 +277,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
AvailableGpus.Clear();
AvailableGpus.AddRange(names.Select(x => new ComboBoxItem() { Content = x }));
AvailableGpus.AddRange(names.Select(x => new ComboBoxItem { Content = x }));
}
public void LoadTimeZones()
@@ -302,25 +307,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public async void BrowseTheme()
{
var dialog = new OpenFileDialog()
{
Title = LocaleManager.Instance[LocaleKeys.SettingsSelectThemeFileDialogTitle],
AllowMultiple = false
};
dialog.Filters.Add(new FileDialogFilter() { Extensions = { "xaml" }, Name = LocaleManager.Instance[LocaleKeys.SettingsXamlThemeFile] });
var file = await dialog.ShowAsync(_owner);
if (file != null && file.Length > 0)
{
CustomThemePath = file[0];
OnPropertyChanged(nameof(CustomThemePath));
}
}
public void LoadCurrentConfiguration()
{
ConfigurationState config = ConfigurationState.Instance;
@@ -477,16 +463,8 @@ namespace Ryujinx.Ava.UI.ViewModels
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();
if (_owner is SettingsWindow owner)
{
owner.ControllerSettings?.SaveCurrentProfile();
}
if (_owner.Owner is MainWindow window && _directoryChanged)
{
window.ViewModel.LoadApplications();
}
SaveSettingsEvent?.Invoke();
_directoryChanged = false;
}
@@ -504,13 +482,13 @@ namespace Ryujinx.Ava.UI.ViewModels
public void OkButton()
{
SaveSettings();
_owner.Close();
CloseWindow?.Invoke();
}
public void CancelButton()
{
RevertIfNotSaved();
_owner.Close();
CloseWindow?.Invoke();
}
}
}

View File

@@ -0,0 +1,216 @@
<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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel"
x:Class="Ryujinx.Ava.UI.Views.Main.MainMenuBarView"
x:CompileBindings="True">
<Design.DataContext>
<viewModels:MainWindowViewModel />
</Design.DataContext>
<DockPanel HorizontalAlignment="Stretch">
<Menu
Name="Menu"
Height="35"
Margin="0"
HorizontalAlignment="Left">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
<MenuItem
Command="{ReflectionBinding OpenFile}"
Header="{locale:Locale MenuBarFileOpenFromFile}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenFolder}"
Header="{locale:Locale MenuBarFileOpenUnpacked}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
<MenuItem
Click="OpenMiiApplet"
Header="Mii Edit Applet"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem>
<Separator />
<MenuItem
Command="{ReflectionBinding OpenRyujinxFolder}"
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenLogsFolder}"
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
<Separator />
<MenuItem
Click="CloseWindow"
Header="{locale:Locale MenuBarFileExit}"
ToolTip.Tip="{locale:Locale ExitTooltip}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
<MenuItem
Command="{ReflectionBinding ToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
InputGesture="F11" />
<MenuItem>
<MenuItem.Icon>
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem IsVisible="{Binding ShowConsoleVisible}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsShowConsole}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="de_DE"
Header="Deutsch" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="en_US"
Header="English (US)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="es_ES"
Header="Español (ES)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="fr_FR"
Header="Français" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="it_IT"
Header="Italiano" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="pt_BR"
Header="Português (BR)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="tr_TR"
Header="Türkçe" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="el_GR"
Header="Ελληνικά" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="pl_PL"
Header="Polski" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ru_RU"
Header="Русский" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_CN"
Header="简体中文" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_TW"
Header="繁體中文" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ja_JP"
Header="日本語" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ko_KR"
Header="한국어" />
</MenuItem>
<Separator />
<MenuItem
Click="OpenSettings"
Header="{locale:Locale MenuBarOptionsSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
<MenuItem
Command="{ReflectionBinding ManageProfiles}"
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
</MenuItem>
<MenuItem
Name="ActionsMenuItem"
VerticalAlignment="Center"
Header="{locale:Locale MenuBarActions}"
IsEnabled="{Binding IsGameRunning}">
<MenuItem
Click="PauseEmulation_Click"
Header="{locale:Locale MenuBarOptionsPauseEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" />
<MenuItem
Click="ResumeEmulation_Click"
Header="{locale:Locale MenuBarOptionsResumeEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" />
<MenuItem
Click="StopEmulation_Click"
Header="{locale:Locale MenuBarOptionsStopEmulation}"
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
<Separator />
<MenuItem
Name="ScanAmiiboMenuItem"
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
Click="OpenAmiiboWindow"
Header="{locale:Locale MenuBarActionsScanAmiibo}"
IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem
Command="{ReflectionBinding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
InputGesture="{Binding ScreenshotKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{ReflectionBinding HideUi}"
Header="{locale:Locale MenuBarFileToolsHideUi}"
InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Click="OpenCheatManagerForCurrentApp"
Header="{locale:Locale GameListContextMenuManageCheat}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
<MenuItem
Name="UpdateMenuItem"
IsEnabled="{Binding CanUpdate}"
Click="CheckForUpdates"
Header="{locale:Locale MenuBarHelpCheckForUpdates}"
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
<Separator />
<MenuItem
Click="OpenAboutWindow"
Header="{locale:Locale MenuBarHelpAbout}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
</MenuItem>
</Menu>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,143 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using System.Threading.Tasks;
using LibHac.FsSystem;
using LibHac.Ncm;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainMenuBarView : UserControl
{
public MainWindow Window { get; private set; }
public MainWindowViewModel ViewModel { get; private set; }
public MainMenuBarView()
{
InitializeComponent();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (this.VisualRoot is MainWindow window)
{
Window = window;
}
ViewModel = Window.ViewModel;
DataContext = ViewModel;
}
private async void StopEmulation_Click(object sender, RoutedEventArgs e)
{
await Window.ViewModel.AppHost?.ShowExitPrompt();
}
private async void PauseEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.Pause();
});
}
private async void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.Resume();
});
}
public async void OpenSettings(object sender, RoutedEventArgs e)
{
Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager);
await Window.SettingsWindow.ShowDialog(Window);
ViewModel.LoadConfigurableHotKeys();
}
public void OpenMiiApplet(object sender, RoutedEventArgs e)
{
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
if (!string.IsNullOrEmpty(contentPath))
{
ViewModel.LoadApplication(contentPath, false, "Mii Applet");
}
}
public async void OpenAmiiboWindow(object sender, RoutedEventArgs e)
{
if (!ViewModel.IsAmiiboRequested)
{
return;
}
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper();
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
await window.ShowDialog(Window);
if (window.IsScanned)
{
ViewModel.ShowAll = window.ViewModel.ShowAllAmiibo;
ViewModel.LastScannedAmiiboId = window.ScannedAmiibo.GetId();
ViewModel.AppHost.Device.System.ScanAmiibo(deviceId, ViewModel.LastScannedAmiiboId, window.ViewModel.UseRandomUuid);
}
}
}
public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e)
{
if (!ViewModel.IsGameRunning)
{
return;
}
ApplicationLoader application = ViewModel.AppHost.Device.Application;
if (application != null)
{
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
ViewModel.AppHost.Device.EnableCheats();
}
}
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is MenuItem)
{
ViewModel.IsAmiiboRequested = Window.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _);
}
}
public async void CheckForUpdates(object sender, RoutedEventArgs e)
{
if (Updater.CanUpdate(true, Window))
{
await Updater.BeginParse(Window, true);
}
}
public async void OpenAboutWindow(object sender, RoutedEventArgs e)
{
await AboutWindow.Show();
}
public void CloseWindow(object sender, RoutedEventArgs e)
{
Window.Close();
}
}
}

View File

@@ -0,0 +1,232 @@
<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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel">
<Design.DataContext>
<viewModels:MainWindowViewModel />
</Design.DataContext>
<Grid
Name="StatusBar"
Margin="0"
MinHeight="22"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="{DynamicResource ThemeContentBackgroundColor}"
DockPanel.Dock="Bottom"
IsVisible="{Binding ShowMenuAndStatusBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
Margin="5"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Width="25"
Height="25"
MinWidth="0"
Margin="0,0,5,0"
VerticalAlignment="Center"
Background="Transparent"
Command="{ReflectionBinding LoadApplications}">
<ui:SymbolIcon
Width="50"
Height="100"
Symbol="Refresh" />
</Button>
<TextBlock
Name="LoadStatus"
Grid.Column="1"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}"
Text="{locale:Locale StatusBarGamesLoaded}" />
<ProgressBar
Name="LoadProgressBar"
Grid.Column="2"
Height="6"
VerticalAlignment="Center"
Foreground="{DynamicResource HighlightColor}"
IsVisible="{Binding StatusBarVisible}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
</Grid>
</StackPanel>
<StackPanel
Grid.Column="1"
Margin="0,2"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding IsGameRunning}"
MaxHeight="18"
Orientation="Horizontal">
<TextBlock
Name="VsyncStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{Binding VsyncColor}"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="VsyncStatus_PointerReleased"
Text="VSync"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="DockedStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="DockedStatus_PointerReleased"
Text="{Binding DockedStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="AspectRatioStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="AspectRatioStatus_PointerReleased"
Text="{Binding AspectRatioStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<ui:ToggleSplitButton
Name="VolumeStatus"
Padding="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Background="{DynamicResource ThemeContentBackgroundColor}"
BorderThickness="0"
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}"
IsVisible="{Binding !ShowLoadProgress}">
<ui:ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0">
<Slider
MaxHeight="40"
Width="150"
Margin="0"
Padding="0"
IsSnapToTickEnabled="True"
LargeChange="0.05"
Maximum="1"
Minimum="0"
SmallChange="0.01"
TickFrequency="0.05"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Value="{Binding Volume}" />
</Grid>
</Flyout>
</ui:ToggleSplitButton.Flyout>
</ui:ToggleSplitButton>
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GameStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding FifoStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding BackendText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GpuNameText}"
TextAlignment="Left" />
</StackPanel>
<StackPanel
Grid.Column="3"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">
<TextBlock
Name="FirmwareStatus"
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{locale:Locale StatusBarSystemVersion}" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,52 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Configuration;
using System;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainStatusBarView : UserControl
{
public MainWindow Window;
public MainStatusBarView()
{
InitializeComponent();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (VisualRoot is MainWindow window)
{
Window = window;
}
DataContext = Window.ViewModel;
}
private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
Window.ViewModel.AppHost.Device.EnableDeviceVsync = !Window.ViewModel.AppHost.Device.EnableDeviceVsync;
Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}");
}
private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
}
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
}
}
}

View File

@@ -0,0 +1,176 @@
<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"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Views.Main.MainViewControls"
x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel">
<Design.DataContext>
<viewModels:MainWindowViewModel />
</Design.DataContext>
<DockPanel
Margin="0,0,0,5"
HorizontalAlignment="Stretch">
<Button
Width="40"
MinWidth="40"
Margin="5,2,0,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetListMode}"
IsEnabled="{Binding IsGrid}">
<ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter List}" />
</Button>
<Button
Width="40"
MinWidth="40"
Margin="5,2,5,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetGridMode}"
IsEnabled="{Binding IsList}">
<ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter Grid}" />
</Button>
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
<Slider
Width="150"
Height="35"
Margin="5,-10,5,0"
VerticalAlignment="Center"
IsSnapToTickEnabled="True"
Maximum="4"
Minimum="1"
TickFrequency="1"
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
Value="{Binding GridSizeScale}" />
<CheckBox
Margin="0"
VerticalAlignment="Center"
IsChecked="{Binding ShowNames, Mode=TwoWay}"
IsVisible="{Binding IsGrid}">
<TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
</CheckBox>
<TextBox
Name="SearchBox"
MinWidth="200"
Margin="5,0,5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
DockPanel.Dock="Right"
KeyUp="SearchBox_OnKeyUp"
Text="{Binding SearchText}"
Watermark="{locale:Locale MenuSearch}" />
<ui:DropDownButton
Width="150"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{Binding SortName}"
DockPanel.Dock="Right">
<ui:DropDownButton.Flyout>
<Flyout Placement="Bottom">
<StackPanel
Margin="0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel>
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale CommonFavorite}"
GroupName="Sort"
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
Tag="Favorite" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderApplication}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
Tag="Title" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderDeveloper}"
GroupName="Sort"
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
Tag="Developer" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderTimePlayed}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
Tag="TotalTimePlayed" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderLastPlayed}"
GroupName="Sort"
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
Tag="LastPlayed" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileExtension}"
GroupName="Sort"
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
Tag="FileType" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileSize}"
GroupName="Sort"
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
Tag="FileSize" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderPath}"
GroupName="Sort"
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
Tag="Path" />
</StackPanel>
<Border
Width="60"
Height="2"
Margin="5"
HorizontalAlignment="Stretch"
BorderBrush="White"
BorderThickness="0,1,0,0">
<Separator Height="0" HorizontalAlignment="Stretch" />
</Border>
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderAscending}"
GroupName="Order"
IsChecked="{Binding IsAscending, Mode=OneTime}"
Tag="Ascending" />
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderDescending}"
GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}"
Tag="Descending" />
</StackPanel>
</Flyout>
</ui:DropDownButton.Flyout>
</ui:DropDownButton>
<TextBlock
Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{locale:Locale CommonSort}" />
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,54 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using System;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainViewControls : UserControl
{
public MainWindowViewModel ViewModel;
public MainViewControls()
{
InitializeComponent();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (VisualRoot is MainWindow window)
{
ViewModel = window.ViewModel;
}
DataContext = ViewModel;
}
public void Sort_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
ViewModel.Sort(Enum.Parse<ApplicationSort>(button.Tag.ToString()));
}
}
public void Order_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
ViewModel.Sort(button.Tag.ToString() != "Descending");
}
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
{
ViewModel.SearchText = SearchBox.Text;
}
}
}

View File

@@ -0,0 +1,81 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsAudioView"
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"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="AudioPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabAudio}" />
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemAudioBackend}"
ToolTip.Tip="{locale:Locale AudioBackendTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding AudioBackend}"
Width="350"
HorizontalContentAlignment="Left">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendDummy}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsOpenAlEnabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendOpenAL}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsSoundIoEnabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSDL2}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemAudioVolume}"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Width="250" />
<ui:NumberBox Value="{Binding Volume}"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Width="350"
SmallChange="1"
LargeChange="10"
SimpleNumberFormat="F0"
SpinButtonPlacementMode="Inline"
Minimum="0"
Maximum="100" />
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<Slider Value="{Binding Volume}"
Margin="250,0,0,0"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Minimum="0"
Maximum="100"
SmallChange="5"
TickFrequency="5"
IsSnapToTickEnabled="True"
LargeChange="10"
Width="350" />
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsAudioView : UserControl
{
public SettingsAudioView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,72 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="CpuPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuCache}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding EnablePptc}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnablePptc}"
ToolTip.Tip="{locale:Locale PptcToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuMemory}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemMemoryManagerMode}"
ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding MemoryMode}"
ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerSoftwareTooltip}">
<TextBlock
Text="{locale:Locale SettingsTabSystemMemoryManagerModeSoftware}" />
</ComboBoxItem>
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerHostTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemMemoryManagerModeHost}" />
</ComboBoxItem>
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerUnsafeTooltip}">
<TextBlock
Text="{locale:Locale SettingsTabSystemMemoryManagerModeHostUnchecked}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsCPUView : UserControl
{
public SettingsCPUView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,218 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsGraphicsView"
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"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="GraphicsPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsAPI}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
Text="{locale:Locale SettingsTabGraphicsBackend}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
SelectedIndex="{Binding GraphicsBackendIndex}">
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
<TextBlock Text="Vulkan" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
<TextBlock Text="OpenGL" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding IsVulkanSelected}">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
Text="{locale:Locale SettingsTabGraphicsPreferredGpu}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
SelectedIndex="{Binding PreferredGpuIndex}"
Items="{Binding AvailableGpus}"/>
</StackPanel>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsFeatures}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableShaderCache}"
ToolTip.Tip="{locale:Locale ShaderCacheToggleTooltip}">
<TextBlock Text="{locale:Locale SettingsTabGraphicsEnableShaderCache}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableTextureRecompression}"
ToolTip.Tip="{locale:Locale SettingsEnableTextureRecompressionTooltip}">
<TextBlock Text="{locale:Locale SettingsEnableTextureRecompression}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableMacroHLE}"
ToolTip.Tip="{locale:Locale SettingsEnableMacroHLETooltip}">
<TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}"
Text="{locale:Locale SettingsTabGraphicsResolutionScale}"
Width="250" />
<ComboBox SelectedIndex="{Binding ResolutionScale}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale2x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale3x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
</ComboBoxItem>
</ComboBox>
<ui:NumberBox
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale ResolutionScaleEntryTooltip}"
MinWidth="150"
SmallChange="0.1"
LargeChange="1"
SimpleNumberFormat="F2"
SpinButtonPlacementMode="Inline"
IsVisible="{Binding IsCustomResolutionScaleActive}"
Maximum="100"
Minimum="0.1"
Value="{Binding CustomResolutionScale}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale AnisotropyTooltip}"
Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering}"
Width="250" />
<ComboBox SelectedIndex="{Binding MaxAnisotropy}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale AnisotropyTooltip}">
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabGraphicsAnisotropicFilteringAuto}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering2x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering4x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering8x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering16x}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale AspectRatioTooltip}"
Text="{locale:Locale SettingsTabGraphicsAspectRatio}"
Width="250" />
<ComboBox SelectedIndex="{Binding AspectRatio}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale GraphicsBackendThreadingTooltip}"
Text="{locale:Locale SettingsTabGraphicsBackendMultithreading}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale GalThreadingTooltip}"
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonAuto}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonOff}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonOn}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsDeveloperOptions}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}"
Text="{locale:Locale SettingsTabGraphicsShaderDumpPath}"
Width="250" />
<TextBox Text="{Binding ShaderDumpPath}"
Width="350"
ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" />
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsGraphicsView : UserControl
{
public SettingsGraphicsView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,104 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsHotkeysView"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel"
Focusable="True">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<UserControl.Resources>
<helpers:KeyValueConverter x:Key="Key" />
</UserControl.Resources>
<ScrollViewer
Name="HotkeysPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel Margin="10" Orientation="Vertical" Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabHotkeysHotkeys}" />
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ToggleVsync, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.Screenshot, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ShowUi, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.Pause, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ToggleMute, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ResScaleUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ResScaleDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.VolumeUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.VolumeDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,81 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsHotkeysView : UserControl
{
private ButtonKeyAssigner _currentAssigner;
private IGamepadDriver AvaloniaKeyboardDriver;
public SettingsHotkeysView()
{
InitializeComponent();
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this);
}
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private void Button_Checked(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton button)
{
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
return;
}
if (_currentAssigner == null && button.IsChecked != null && (bool)button.IsChecked)
{
_currentAssigner = new ButtonKeyAssigner(button);
FocusManager.Instance?.Focus(this, NavigationMethod.Pointer);
PointerPressed += MouseClick;
var keyboard = (IKeyboard)AvaloniaKeyboardDriver.GetGamepad(AvaloniaKeyboardDriver.GamepadsIds[0]);
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
_currentAssigner.GetInputAndAssign(assigner);
}
else
{
if (_currentAssigner != null)
{
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
}
}
private void Button_Unchecked(object sender, RoutedEventArgs e)
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
public void Dispose()
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
}
}

View File

@@ -0,0 +1,46 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="InputPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel Margin="4" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
IsChecked="{Binding EnableDockedMode}">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
</CheckBox>
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
IsChecked="{Binding EnableKeyboard}">
<TextBlock Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
</CheckBox>
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
IsChecked="{Binding EnableMouse}">
<TextBlock Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
</CheckBox>
</StackPanel>
<window:ControllerSettingsWindow Name="ControllerSettings" Margin="0" MinHeight="600" />
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,17 @@
using Avalonia.Controls;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsInputView : UserControl
{
public SettingsInputView()
{
InitializeComponent();
}
public void Dispose()
{
ControllerSettings.Dispose();
}
}
}

View File

@@ -0,0 +1,118 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsLoggingView"
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"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="LoggingPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingLogging}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableFileLog}"
ToolTip.Tip="{locale:Locale FileLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableLoggingToFile}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableStub}"
ToolTip.Tip="{locale:Locale StubLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableStubLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableInfo}"
ToolTip.Tip="{locale:Locale InfoLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableInfoLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableWarn}"
ToolTip.Tip="{locale:Locale WarnLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableWarningLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableError}"
ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableTrace}"
ToolTip.Tip="{locale:Locale TraceLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableGuest}"
ToolTip.Tip="{locale:Locale GuestLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDebug}"
ToolTip.Tip="{locale:Locale DebugLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableFsAccessLog}"
ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
</CheckBox>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
Text="{locale:Locale SettingsTabLoggingFsGlobalAccessLogMode}"
Width="285" />
<ui:NumberBox
Maximum="3"
Minimum="0"
Width="150"
SpinButtonPlacementMode="Inline"
SmallChange="1"
LargeChange="1"
Value="{Binding FsGlobalAccessLogMode}" />
</StackPanel>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevel}"
ToolTip.Tip="{locale:Locale OpenGlLogLevel}"
Width="285" />
<ComboBox SelectedIndex="{Binding OpenglDebugLevel}"
Width="150"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale OpenGlLogLevel}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelError}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelPerformance}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelAll}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsLoggingView : UserControl
{
public SettingsLoggingView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,35 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="NetworkPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" />
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</CheckBox>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsNetworkView : UserControl
{
public SettingsNetworkView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,195 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsSystemView"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="SystemPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemCore}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemRegion}"
Width="250" />
<ComboBox SelectedIndex="{Binding Region}"
ToolTip.Tip="{locale:Locale RegionTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionUSA}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionEurope}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionAustralia}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionChina}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionKorea}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionTaiwan}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
ToolTip.Tip="{locale:Locale LanguageTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding Language}"
ToolTip.Tip="{locale:Locale LanguageTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageGerman}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageItalian}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSpanish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageKorean}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageDutch}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguagePortuguese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageRussian}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
Width="250" />
<AutoCompleteBox
Name="TimeZoneBox"
Width="350"
MaxDropDownHeight="500"
FilterMode="Contains"
Items="{Binding TimeZones}"
SelectionChanged="TimeZoneBox_OnSelectionChanged"
Text="{Binding Path=TimeZone, Mode=OneWay}"
TextChanged="TimeZoneBox_OnTextChanged"
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTime}"
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="250"/>
<DatePicker VerticalAlignment="Center" SelectedDate="{Binding DateOffset}"
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="350" />
</StackPanel>
<StackPanel Margin="250,0,0,10" Orientation="Horizontal">
<TimePicker
VerticalAlignment="Center"
ClockIdentifier="24HourClock"
SelectedTime="{Binding TimeOffset}"
Width="350"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
</StackPanel>
<CheckBox IsChecked="{Binding EnableVsync}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}"
ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}"
ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<StackPanel Orientation="Horizontal">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
<TextBlock Text="{locale:Locale SettingsTabSystemHacksNote}" />
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding ExpandDramSize}"
ToolTip.Tip="{locale:Locale DRamTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" />
</CheckBox>
<CheckBox IsChecked="{Binding IgnoreMissingServices}"
ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" />
</CheckBox>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,52 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Ryujinx.Ava.UI.ViewModels;
using System;
using System.Linq;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsSystemView : UserControl
{
public SettingsViewModel ViewModel;
public SettingsSystemView()
{
InitializeComponent();
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()).Trim());
MultiBinding tzMultiBinding = new() { Converter = converter };
tzMultiBinding.Bindings.Add(new Binding("UtcDifference"));
tzMultiBinding.Bindings.Add(new Binding("Location"));
tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
TimeZoneBox.ValueMemberBinding = tzMultiBinding;
}
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems != null && e.AddedItems.Count > 0)
{
if (e.AddedItems[0] is TimeZone timeZone)
{
e.Handled = true;
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
}
}
}
private void TimeZoneBox_OnTextChanged(object sender, EventArgs e)
{
if (sender is AutoCompleteBox box && box.SelectedItem is TimeZone timeZone)
{
{
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
}
}
}
}
}

View File

@@ -0,0 +1,156 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUIView"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
Name="UiPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGeneral}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDiscordIntegration}">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ToggleDiscordTooltip}"
Text="{locale:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
</CheckBox>
<CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
<TextBlock Text="{locale:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
</CheckBox>
<CheckBox IsChecked="{Binding ShowConfirmExit}">
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
</CheckBox>
<CheckBox IsChecked="{Binding HideCursorOnIdle}">
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<ListBox
Name="GameList"
MinHeight="230"
Items="{Binding GameDirectories}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="10" />
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
</Style>
</ListBox.Styles>
</ListBox>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
Name="PathBox"
Margin="0"
ToolTip.Tip="{locale:Locale AddGameDirBoxTooltip}"
VerticalAlignment="Stretch" />
<Button
Name="AddButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale AddGameDirTooltip}"
Click="AddButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{locale:Locale SettingsTabGeneralAdd}" />
</Button>
<Button
Name="RemoveButton"
Grid.Column="2"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale RemoveGameDirTooltip}"
Click="RemoveButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{locale:Locale SettingsTabGeneralRemove}" />
</Button>
</Grid>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralTheme}" />
<Grid Margin="10,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox
IsChecked="{Binding EnableCustomTheme}"
ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
</CheckBox>
<TextBlock
Grid.Column="0"
Grid.Row="1"
VerticalAlignment="Center"
Margin="0,10,0,0"
Text="{locale:Locale SettingsTabGeneralThemeCustomTheme}"
ToolTip.Tip="{locale:Locale CustomThemePathTooltip}" />
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="0,10,0,0"
Text="{Binding CustomThemePath}" />
<Button
Grid.Row="1"
Grid.Column="2"
Margin="10,10,0,0"
Click="BrowseTheme"
ToolTip.Tip="{locale:Locale CustomThemeBrowseTooltip}"
Content="{locale:Locale ButtonBrowse}" />
<TextBlock
Grid.Column="0"
Grid.Row="2"
VerticalAlignment="Center"
Margin="0,10,0,0"
Text="{locale:Locale SettingsTabGeneralThemeBaseStyle}" />
<ComboBox
Grid.Column="1"
Grid.Row="2"
VerticalAlignment="Center"
Margin="0,10,0,0"
MinWidth="100"
SelectedIndex="{Binding BaseStyleIndex}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleLight}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleDark}" />
</ComboBoxItem>
</ComboBox>
</Grid>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,82 @@
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsUIView : UserControl
{
public SettingsViewModel ViewModel;
public SettingsUIView()
{
InitializeComponent();
}
private async void AddButton_OnClick(object sender, RoutedEventArgs e)
{
string path = PathBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
{
ViewModel.GameDirectories.Add(path);
ViewModel.DirectoryChanged = true;
}
else
{
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
path = await new OpenFolderDialog().ShowAsync(desktop.MainWindow);
if (!string.IsNullOrWhiteSpace(path))
{
ViewModel.GameDirectories.Add(path);
ViewModel.DirectoryChanged = true;
}
}
}
}
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
{
int oldIndex = GameList.SelectedIndex;
foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
{
ViewModel.GameDirectories.Remove(path);
ViewModel.DirectoryChanged = true;
}
if (GameList.ItemCount > 0)
{
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
}
}
public async void BrowseTheme(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog()
{
Title = LocaleManager.Instance[LocaleKeys.SettingsSelectThemeFileDialogTitle],
AllowMultiple = false
};
dialog.Filters.Add(new FileDialogFilter() { Extensions = { "xaml" }, Name = LocaleManager.Instance[LocaleKeys.SettingsXamlThemeFile] });
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var file = await dialog.ShowAsync(desktop.MainWindow);
if (file != null && file.Length > 0)
{
ViewModel.CustomThemePath = file[0];
}
}
}
}
}

View File

@@ -1,282 +1,247 @@
<window:StyleableWindow
<UserControl
x:Class="Ryujinx.Ava.UI.Windows.AboutWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
Width="850"
Height="550"
MinWidth="500"
MinHeight="550"
d:DesignHeight="350"
d:DesignWidth="400"
CanResize="False"
SizeToContent="Width"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d"
Focusable="True">
<Grid
Margin="15"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="1"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModel="clr-namespace:Ryujinx.Ava.UI.ViewModels"
Width="550"
Height="260"
Margin="0,-12,0,0"
d:DesignHeight="260"
d:DesignWidth="550"
x:CompileBindings="True"
x:DataType="viewModel:AboutWindowViewModel"
Focusable="True"
mc:Ignorable="d">
<Design.DataContext>
<viewModel:AboutWindowViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Column="0"
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="0"
Height="110"
MinWidth="50"
Margin="5,10,20,10"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="0,20,0,0"
FontSize="35"
Text="Ryujinx"
TextAlignment="Center" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,0"
FontSize="16"
Text="(REE-YOU-JINX)"
TextAlignment="Center" />
<Button
Grid.Row="2"
Grid.Column="1"
Margin="0"
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Spacing="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Height="80"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<flex:FlexPanel
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Direction="Column"
JustifyContent="SpaceAround"
RowSpacing="2">
<TextBlock
FontSize="28"
FontWeight="Bold"
Text="Ryujinx"
TextAlignment="Left" />
<TextBlock Text="(REE-YOU-JINX)" TextAlignment="Left" />
</flex:FlexPanel>
</Grid>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="10"
LineHeight="12"
Text="{Binding Version}"
TextAlignment="Center" />
</StackPanel>
<StackPanel
Grid.Row="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Spacing="10">
<TextBlock
Width="200"
HorizontalAlignment="Center"
FontSize="10"
LineHeight="12"
Text="{locale:Locale AboutDisclaimerMessage}"
TextAlignment="Center"
TextWrapping="Wrap" />
<TextBlock
Name="AmiiboLabel"
Width="200"
HorizontalAlignment="Center"
FontSize="10"
LineHeight="12"
PointerPressed="AmiiboLabel_OnPointerPressed"
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
TextAlignment="Center"
TextWrapping="Wrap" />
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="10">
<Button
MinWidth="30"
MinHeight="30"
MaxWidth="30"
MaxHeight="30"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://www.patreon.com/ryujinx"
ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Image Source="{Binding PatreonLogo}" />
</Button>
<Button
MinWidth="30"
MinHeight="30"
MaxWidth="30"
MaxHeight="30"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://github.com/Ryujinx/Ryujinx"
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Image Source="{Binding GithubLogo}" />
</Button>
<Button
MinWidth="30"
MinHeight="30"
MaxWidth="30"
MaxHeight="30"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://discordapp.com/invite/N2FmfVc"
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Image Source="{Binding DiscordLogo}" />
</Button>
<Button
MinWidth="30"
MinHeight="30"
MaxWidth="30"
MaxHeight="30"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://twitter.com/RyujinxEmu"
ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
<Image Source="{Binding TwitterLogo}" />
</Button>
<Button
MinWidth="30"
MinHeight="30"
MaxWidth="30"
MaxHeight="30"
Padding="8"
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://www.ryujinx.org"
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}">
<ui:SymbolIcon Foreground="{DynamicResource ThemeForegroundColor}" Symbol="Link" />
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Border
Grid.Column="1"
Width="1"
Margin="20,0"
VerticalAlignment="Stretch"
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1,0,0,0" />
<Grid
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="0,10,0,0"
Spacing="2">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxAboutTitle}" />
<TextBlock
FontSize="10"
Text="{locale:Locale AboutRyujinxAboutContent}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel
Grid.Row="1"
Margin="0,10,0,0"
Spacing="2">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxMaintainersTitle}" />
<TextBlock
FontSize="10"
Text="{Binding Developers}"
TextWrapping="Wrap" />
<Button
Padding="5"
HorizontalAlignment="Left"
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.ryujinx.org/">
<TextBlock
Text="www.ryujinx.org"
TextAlignment="Center"
TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
</Button>
</Grid>
<TextBlock
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Version}"
TextAlignment="Center" />
<TextBlock
Grid.Row="2"
Margin="20"
HorizontalAlignment="Center"
MaxLines="2"
Text="{locale:Locale AboutDisclaimerMessage}"
TextAlignment="Center" />
<TextBlock
Name="AmiiboLabel"
Grid.Row="3"
Margin="20"
HorizontalAlignment="Center"
MaxLines="2"
PointerPressed="AmiiboLabel_OnPointerPressed"
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
TextAlignment="Center" />
<StackPanel
Grid.Row="4"
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="10">
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.patreon.com/ryujinx">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Patreon" />
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="GitHub" />
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://discordapp.com/invite/N2FmfVc">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Discord" />
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://twitter.com/RyujinxEmu">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Twitter" />
</Grid>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Border
Grid.Row="1"
Grid.Column="1"
Width="2"
Margin="5"
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1,0,0,0">
<Separator Width="0" />
</Border>
<Grid
Grid.Row="1"
Grid.Column="2"
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxAboutTitle}"
TextDecorations="Underline" />
<TextBlock
Grid.Row="1"
Margin="20,5,5,5"
LineHeight="20"
Text="{locale:Locale AboutRyujinxAboutContent}" />
<TextBlock
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
<TextBlock
FontSize="10"
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
TextAlignment="Right"
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
</Button>
</StackPanel>
<StackPanel
Grid.Row="2"
Margin="0,10,0,0"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxMaintainersTitle}"
TextDecorations="Underline" />
<TextBlock
Grid.Row="3"
Margin="20,5,5,5"
LineHeight="20"
Text="{Binding Developers}" />
<Button
Grid.Row="4"
HorizontalAlignment="Right"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
<TextBlock
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
TextAlignment="Right"
TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
</Button>
<TextBlock
Grid.Row="5"
Margin="0,0,0,0"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxSupprtersTitle}"
TextDecorations="Underline" />
<Border
Grid.Row="6"
Width="460"
Height="200"
Margin="20,5"
Padding="5"
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1">
<TextBlock
Name="SupportersTextBlock"
VerticalAlignment="Top"
Text="{Binding Supporters}"
TextWrapping="Wrap" />
</Border>
</Grid>
</Grid>
</window:StyleableWindow>
Spacing="2">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxSupprtersTitle}" />
<ScrollViewer
Height="70"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Visible">
<TextBlock
Name="SupportersTextBlock"
VerticalAlignment="Top"
FontSize="10"
Text="{Binding Supporters}"
TextWrapping="Wrap" />
</ScrollViewer>
</StackPanel>
</Grid>
</Grid>
</UserControl>

View File

@@ -1,38 +1,47 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.Common.Helper;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows
{
public partial class AboutWindow : StyleableWindow
public partial class AboutWindow : UserControl
{
public AboutWindow()
{
if (Program.PreviewerDetached)
{
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.MenuBarHelpAbout];
}
Version = Program.Version;
DataContext = this;
DataContext = new AboutWindowViewModel();
InitializeComponent();
_ = DownloadPatronsJson();
}
public string Supporters { get; set; }
public string Version { get; set; }
public static async Task Show()
{
ContentDialog contentDialog = new()
{
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
Content = new AboutWindow()
};
public string Developers => string.Format(LocaleManager.Instance[LocaleKeys.AboutPageDeveloperListMore], "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD«");
Style closeButton = new(x => x.Name("CloseButton"));
closeButton.Setters.Add(new Setter(WidthProperty, 80d));
Style closeButtonParent = new(x => x.Name("CommandSpace"));
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, Avalonia.Layout.HorizontalAlignment.Right));
contentDialog.Styles.Add(closeButton);
contentDialog.Styles.Add(closeButtonParent);
await ContentDialogHelper.ShowAsync(contentDialog);
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
@@ -42,31 +51,6 @@ namespace Ryujinx.Ava.UI.Windows
}
}
private async Task DownloadPatronsJson()
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
Supporters = LocaleManager.Instance[LocaleKeys.ConnectionError];
return;
}
HttpClient httpClient = new();
try
{
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString));
}
catch
{
Supporters = LocaleManager.Instance[LocaleKeys.ApiError];
}
await Dispatcher.UIThread.InvokeAsync(() => SupportersTextBlock.Text = Supporters);
}
private void AmiiboLabel_OnPointerPressed(object sender, PointerPressedEventArgs e)
{
if (sender is TextBlock)

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -17,9 +17,9 @@
<Design.DataContext>
<viewModels:AvatarProfileViewModel />
</Design.DataContext>
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Grid Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />

View File

@@ -2,15 +2,16 @@
x:Class="Ryujinx.Ava.UI.Windows.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
Title="Ryujinx"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
Cursor="{Binding Cursor}"
Title="{Binding Title}"
WindowState="{Binding WindowState}"
Width="1280"
Height="777"
MinWidth="1092"
@@ -66,206 +67,8 @@
VerticalAlignment="Stretch"
IsVisible="{Binding ShowMenuAndStatusBar}"
Orientation="Vertical">
<DockPanel HorizontalAlignment="Stretch">
<Menu
Name="Menu"
Height="35"
Margin="0"
HorizontalAlignment="Left">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
<MenuItem
Command="{ReflectionBinding OpenFile}"
Header="{locale:Locale MenuBarFileOpenFromFile}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenFolder}"
Header="{locale:Locale MenuBarFileOpenUnpacked}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
<MenuItem
Command="{ReflectionBinding OpenMiiApplet}"
Header="Mii Edit Applet"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem>
<Separator />
<MenuItem
Command="{ReflectionBinding OpenRyujinxFolder}"
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenLogsFolder}"
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
<Separator />
<MenuItem
Command="{ReflectionBinding CloseWindow}"
Header="{locale:Locale MenuBarFileExit}"
ToolTip.Tip="{locale:Locale ExitTooltip}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
<MenuItem
Command="{ReflectionBinding ToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
InputGesture="F11" />
<MenuItem>
<MenuItem.Icon>
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem IsVisible="{Binding ShowConsoleVisible}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsShowConsole}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="de_DE"
Header="Deutsch" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="en_US"
Header="English (US)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="es_ES"
Header="Español (ES)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="fr_FR"
Header="Français" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="it_IT"
Header="Italiano" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="pt_BR"
Header="Português (BR)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="tr_TR"
Header="Türkçe" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="el_GR"
Header="Ελληνικά" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="pl_PL"
Header="Polski" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ru_RU"
Header="Русский" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_CN"
Header="简体中文" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_TW"
Header="繁體中文" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ja_JP"
Header="日本語" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ko_KR"
Header="한국어" />
</MenuItem>
<Separator />
<MenuItem
Command="{ReflectionBinding OpenSettings}"
Header="{locale:Locale MenuBarOptionsSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
<MenuItem
Command="{ReflectionBinding ManageProfiles}"
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
</MenuItem>
<MenuItem
Name="ActionsMenuItem"
VerticalAlignment="Center"
Header="{locale:Locale MenuBarActions}"
IsEnabled="{Binding IsGameRunning}">
<MenuItem
Click="PauseEmulation_Click"
Header="{locale:Locale MenuBarOptionsPauseEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" />
<MenuItem
Click="ResumeEmulation_Click"
Header="{locale:Locale MenuBarOptionsResumeEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" />
<MenuItem
Click="StopEmulation_Click"
Header="{locale:Locale MenuBarOptionsStopEmulation}"
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
<Separator />
<MenuItem
Name="ScanAmiiboMenuItem"
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
Command="{ReflectionBinding OpenAmiiboWindow}"
Header="{locale:Locale MenuBarActionsScanAmiibo}"
IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem
Command="{ReflectionBinding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
InputGesture="{Binding ScreenshotKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{ReflectionBinding HideUi}"
Header="{locale:Locale MenuBarFileToolsHideUi}"
InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
Header="{locale:Locale GameListContextMenuManageCheat}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
<MenuItem
Name="UpdateMenuItem"
Command="{ReflectionBinding CheckForUpdates}"
Header="{locale:Locale MenuBarHelpCheckForUpdates}"
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
<Separator />
<MenuItem
Command="{ReflectionBinding OpenAboutWindow}"
Header="{locale:Locale MenuBarHelpAbout}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
</MenuItem>
</Menu>
</DockPanel>
<main:MainMenuBarView
Name="MenuBarView" />
</StackPanel>
<ContentControl
Name="MainContent"
@@ -277,170 +80,14 @@
BorderThickness="0,0,0,0"
DockPanel.Dock="Top"
IsVisible="{Binding ShowContent}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="GameLibrary">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel
Grid.Row="0"
Margin="0,0,0,5"
HorizontalAlignment="Stretch">
<Button
Width="40"
MinWidth="40"
Margin="5,2,0,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetListMode}"
IsEnabled="{Binding IsGrid}">
<ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter List}" />
</Button>
<Button
Width="40"
MinWidth="40"
Margin="5,2,5,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetGridMode}"
IsEnabled="{Binding IsList}">
<ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter Grid}" />
</Button>
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
<Slider
Width="150"
Height="35"
Margin="5,-10,5,0"
VerticalAlignment="Center"
IsSnapToTickEnabled="True"
Maximum="4"
Minimum="1"
TickFrequency="1"
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
Value="{Binding GridSizeScale}" />
<CheckBox
Margin="0"
VerticalAlignment="Center"
IsChecked="{Binding ShowNames, Mode=TwoWay}"
IsVisible="{Binding IsGrid}">
<TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
</CheckBox>
<TextBox
Name="SearchBox"
MinWidth="200"
Margin="5,0,5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
KeyUp="SearchBox_OnKeyUp"
Text="{Binding SearchText}"
Watermark="{locale:Locale MenuSearch}" />
<ui:DropDownButton
Width="150"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{Binding SortName}"
DockPanel.Dock="Right">
<ui:DropDownButton.Flyout>
<Flyout Placement="Bottom">
<StackPanel
Margin="0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel>
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale CommonFavorite}"
GroupName="Sort"
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
Tag="Favorite" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderApplication}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
Tag="Title" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderDeveloper}"
GroupName="Sort"
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
Tag="Developer" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderTimePlayed}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
Tag="TotalTimePlayed" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderLastPlayed}"
GroupName="Sort"
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
Tag="LastPlayed" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileExtension}"
GroupName="Sort"
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
Tag="FileType" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileSize}"
GroupName="Sort"
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
Tag="FileSize" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderPath}"
GroupName="Sort"
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
Tag="Path" />
</StackPanel>
<Border
Width="60"
Height="2"
Margin="5"
HorizontalAlignment="Stretch"
BorderBrush="White"
BorderThickness="0,1,0,0">
<Separator Height="0" HorizontalAlignment="Stretch" />
</Border>
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderAscending}"
GroupName="Order"
IsChecked="{Binding IsAscending, Mode=OneTime}"
Tag="Ascending" />
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderDescending}"
GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}"
Tag="Descending" />
</StackPanel>
</Flyout>
</ui:DropDownButton.Flyout>
</ui:DropDownButton>
<TextBlock
Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{locale:Locale CommonSort}" />
</DockPanel>
<main:MainViewControls
Name="ViewControls"
Grid.Row="0"/>
<controls:GameListView
x:Name="GameList"
Grid.Row="1"
@@ -512,7 +159,8 @@
IsVisible="{Binding ShowLoadProgress}"
Text="{Binding LoadHeading}"
TextAlignment="Left"
TextWrapping="Wrap" />
TextWrapping="Wrap"
MaxWidth="500" />
<Border
Grid.Row="1"
Margin="10"
@@ -529,7 +177,6 @@
Margin="0"
Padding="0"
HorizontalAlignment="Stretch"
Background="{Binding ProgressBarBackgroundColor}"
ClipToBounds="True"
CornerRadius="5"
Foreground="{Binding ProgressBarForegroundColor}"
@@ -545,226 +192,14 @@
FontSize="18"
IsVisible="{Binding ShowLoadProgress}"
Text="{Binding CacheLoadStatus}"
TextAlignment="Left" />
TextAlignment="Left"
MaxWidth="500" />
</Grid>
</Grid>
</Grid>
<Grid
Name="StatusBar"
Grid.Row="2"
Margin="0"
MinHeight="22"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="{DynamicResource ThemeContentBackgroundColor}"
DockPanel.Dock="Bottom"
IsVisible="{Binding ShowMenuAndStatusBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
Margin="5"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Width="25"
Height="25"
MinWidth="0"
Margin="0,0,5,0"
VerticalAlignment="Center"
Background="Transparent"
Command="{ReflectionBinding LoadApplications}">
<ui:SymbolIcon
Width="50"
Height="100"
Symbol="Refresh" />
</Button>
<TextBlock
Name="LoadStatus"
Grid.Column="1"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}"
Text="{locale:Locale StatusBarGamesLoaded}" />
<ProgressBar
Name="LoadProgressBar"
Grid.Column="2"
Height="6"
VerticalAlignment="Center"
Foreground="{DynamicResource HighlightColor}"
IsVisible="{Binding EnableNonGameRunningControls}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
</Grid>
</StackPanel>
<StackPanel
Grid.Column="1"
Margin="0,2"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding IsGameRunning}"
Orientation="Horizontal">
<TextBlock
Name="VsyncStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{Binding VsyncColor}"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="VsyncStatus_PointerReleased"
Text="VSync"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="DockedStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="DockedStatus_PointerReleased"
Text="{Binding DockedStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="AspectRatioStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="AspectRatioStatus_PointerReleased"
Text="{Binding AspectRatioStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<ui:ToggleSplitButton
Name="VolumeStatus"
Padding="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Background="{DynamicResource ThemeContentBackgroundColor}"
BorderThickness="0"
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}"
IsVisible="{Binding !ShowLoadProgress}">
<ui:ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0">
<Slider
MaxHeight="40"
Width="150"
Margin="0"
Padding="0"
IsSnapToTickEnabled="True"
LargeChange="0.05"
Maximum="1"
Minimum="0"
SmallChange="0.01"
TickFrequency="0.05"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Value="{Binding Volume}" />
</Grid>
</Flyout>
</ui:ToggleSplitButton.Flyout>
</ui:ToggleSplitButton>
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GameStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding FifoStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding BackendText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GpuNameText}"
TextAlignment="Left" />
</StackPanel>
<StackPanel
Grid.Column="3"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">
<TextBlock
Name="FirmwareStatus"
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{locale:Locale StatusBarSystemVersion}" />
</StackPanel>
</Grid>
<main:MainStatusBarView
Name="StatusBarView"
Grid.Row="2" />
</Grid>
</Grid>
</window:StyleableWindow>
</window:StyleableWindow>

View File

@@ -1,19 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Applet;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem;
@@ -25,11 +20,9 @@ using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager;
@@ -38,19 +31,14 @@ namespace Ryujinx.Ava.UI.Windows
public partial class MainWindow : StyleableWindow
{
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
private bool _canUpdate;
private bool _isClosing;
private bool _isLoading;
private Control _mainViewContent;
private bool _isLoading;
private UserChannelPersistence _userChannelPersistence;
private static bool _deferLoad;
private static string _launchPath;
private static bool _startFullscreen;
private string _currentEmulatedGamePath;
internal readonly AvaHostUiHandler UiHandler;
private AutoResetEvent _rendererWaitEvent;
public VirtualFileSystem VirtualFileSystem { get; private set; }
public ContentManager ContentManager { get; private set; }
@@ -58,30 +46,17 @@ namespace Ryujinx.Ava.UI.Windows
public LibHacHorizonManager LibHacHorizonManager { get; private set; }
internal AppHost AppHost { get; private set; }
public InputManager InputManager { get; private set; }
internal RendererHost RendererControl { get; private set; }
internal MainWindowViewModel ViewModel { get; private set; }
public SettingsWindow SettingsWindow { get; set; }
public bool CanUpdate
{
get => _canUpdate;
set
{
_canUpdate = value;
Dispatcher.UIThread.InvokeAsync(() => UpdateMenuItem.IsEnabled = _canUpdate);
}
}
public static bool ShowKeyErrorOnLoad { get; set; }
public ApplicationLibrary ApplicationLibrary { get; set; }
public MainWindow()
{
ViewModel = new MainWindowViewModel(this);
ViewModel = new MainWindowViewModel();
MainWindowViewModel = ViewModel;
@@ -92,10 +67,10 @@ namespace Ryujinx.Ava.UI.Windows
UiHandler = new AvaHostUiHandler(this);
Title = $"Ryujinx {Program.Version}";
ViewModel.Title = $"Ryujinx {Program.Version}";
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
double barHeight = MenuBar.MinHeight + StatusBar.MinHeight;
double barHeight = MenuBar.MinHeight + StatusBarView.StatusBar.MinHeight;
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
Width /= Program.WindowScaleFactor;
@@ -103,14 +78,37 @@ namespace Ryujinx.Ava.UI.Windows
{
Initialize();
ViewModel.Initialize();
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
ViewModel.Initialize(
ContentManager,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ViewModel.RefreshFirmwareStatus();
LoadGameList();
this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
}
_rendererWaitEvent = new AutoResetEvent(false);
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ViewModel.ReloadGameList += ReloadGameList;
}
private void IsActiveChanged(bool obj)
{
ViewModel.IsActive = obj;
}
public void LoadGameList()
@@ -122,45 +120,51 @@ namespace Ryujinx.Ava.UI.Windows
_isLoading = true;
ViewModel.LoadApplications();
LoadApplications();
_isLoading = false;
}
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
{
if (ViewModel.ShowMenuAndStatusBar && !ViewModel.ShowLoadProgress)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
if (args.VSyncEnabled)
{
ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ff2eeac9"));
}
else
{
ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ffff4554"));
}
ViewModel.DockedStatusText = args.DockedMode;
ViewModel.AspectRatioStatusText = args.AspectRatio;
ViewModel.GameStatusText = args.GameStatus;
ViewModel.VolumeStatusText = args.VolumeStatus;
ViewModel.FifoStatusText = args.FifoStatus;
ViewModel.GpuNameText = args.GpuName;
ViewModel.BackendText = args.GpuBackend;
ViewModel.ShowStatusSeparator = true;
});
}
}
protected override void HandleScalingChanged(double scale)
{
Program.DesktopScaleFactor = scale;
base.HandleScalingChanged(scale);
}
public void AddApplication(ApplicationData applicationData)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Add(applicationData);
});
}
private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e)
{
AddApplication(e.AppData);
}
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
{
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound);
Dispatcher.UIThread.Post(() =>
{
ViewModel.StatusBarProgressValue = e.NumAppsLoaded;
ViewModel.StatusBarProgressMaximum = e.NumAppsFound;
if (e.NumAppsFound == 0)
{
StatusBarView.LoadProgressBar.IsVisible = false;
}
if (e.NumAppsLoaded == e.NumAppsFound)
{
StatusBarView.LoadProgressBar.IsVisible = false;
}
});
}
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
{
if (args.Application != null)
@@ -169,50 +173,12 @@ namespace Ryujinx.Ava.UI.Windows
string path = new FileInfo(args.Application.Path).FullName;
LoadApplication(path);
ViewModel.LoadApplication(path);
}
args.Handled = true;
}
public async Task PerformanceCheck()
{
if (ConfigurationState.Instance.Logger.EnableTrace.Value)
{
string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage];
string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result != UserResult.Yes)
{
ConfigurationState.Instance.Logger.EnableTrace.Value = false;
SaveConfig();
}
}
if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
{
string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage];
string secondaryMessage =
LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result != UserResult.Yes)
{
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
SaveConfig();
}
}
}
internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg)
{
_deferLoad = true;
@@ -220,109 +186,6 @@ namespace Ryujinx.Ava.UI.Windows
_startFullscreen = startFullscreenArg;
}
#pragma warning disable CS1998
public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "")
#pragma warning restore CS1998
{
if (AppHost != null)
{
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage],
LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
return;
}
#if RELEASE
await PerformanceCheck();
#endif
Logger.RestartTime();
if (ViewModel.SelectedIcon == null)
{
ViewModel.SelectedIcon = ApplicationLibrary.GetApplicationIcon(path);
}
PrepareLoadScreen();
_mainViewContent = MainContent.Content as Control;
RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel);
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
{
RendererControl.CreateOpenGL();
}
else
{
RendererControl.CreateVulkan();
}
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
Dispatcher.UIThread.Post(async () =>
{
if (!await AppHost.LoadGuestApplication())
{
AppHost.DisposeContext();
AppHost = null;
return;
}
CanUpdate = false;
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], AppHost.Device.Application.TitleName) : titleName;
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
SwitchToGameControl(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame)
{
Name = "GUI.WindowThread"
};
gameThread.Start();
});
}
private void InitializeGame()
{
RendererControl.RendererInitialized += GlRenderer_Created;
AppHost.StatusUpdatedEvent += Update_StatusBar;
AppHost.AppExit += AppHost_AppExit;
_rendererWaitEvent.WaitOne();
AppHost?.Start();
AppHost.DisposeContext();
}
private void HandleRelaunch()
{
if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart)
{
_userChannelPersistence.ShouldRestart = false;
Dispatcher.UIThread.Post(() =>
{
LoadApplication(_currentEmulatedGamePath);
});
}
else
{
// otherwise, clear state.
_userChannelPersistence = new UserChannelPersistence();
_currentEmulatedGamePath = null;
}
}
public void SwitchToGameControl(bool startFullscreen = false)
{
ViewModel.ShowLoadProgress = false;
@@ -331,14 +194,10 @@ namespace Ryujinx.Ava.UI.Windows
Dispatcher.UIThread.InvokeAsync(() =>
{
MainContent.Content = RendererControl;
if (startFullscreen && WindowState != WindowState.FullScreen)
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
ViewModel.ToggleFullscreen();
}
RendererControl.Focus();
});
}
@@ -350,71 +209,16 @@ namespace Ryujinx.Ava.UI.Windows
Dispatcher.UIThread.InvokeAsync(() =>
{
if (startFullscreen && WindowState != WindowState.FullScreen)
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
ViewModel.ToggleFullscreen();
}
});
}
private void GlRenderer_Created(object sender, EventArgs e)
{
ShowLoading();
_rendererWaitEvent.Set();
}
private void AppHost_AppExit(object sender, EventArgs e)
{
if (_isClosing)
{
return;
}
ViewModel.IsGameRunning = false;
Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.ShowMenuAndStatusBar = true;
ViewModel.ShowContent = true;
ViewModel.ShowLoadProgress = false;
ViewModel.IsLoadingIndeterminate = false;
CanUpdate = true;
Cursor = Cursor.Default;
if (MainContent.Content != _mainViewContent)
{
MainContent.Content = _mainViewContent;
}
AppHost = null;
HandleRelaunch();
});
RendererControl.RendererInitialized -= GlRenderer_Created;
RendererControl = null;
ViewModel.SelectedIcon = null;
Dispatcher.UIThread.InvokeAsync(() =>
{
Title = $"Ryujinx {Program.Version}";
});
}
public void Sort_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
var sort = Enum.Parse<ApplicationSort>(button.Tag.ToString());
ViewModel.Sort(sort);
}
}
protected override void HandleWindowStateChanged(WindowState state)
{
WindowState = state;
ViewModel.WindowState = state;
if (state != WindowState.Minimized)
{
@@ -422,15 +226,6 @@ namespace Ryujinx.Ava.UI.Windows
}
}
public void Order_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
var tag = button.Tag.ToString();
ViewModel.Sort(tag != "Descending");
}
}
private void Initialize()
{
_userChannelPersistence = new UserChannelPersistence();
@@ -457,8 +252,6 @@ namespace Ryujinx.Ava.UI.Windows
VirtualFileSystem.ReloadKeySet();
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
RefreshFirmwareStatus();
}
protected void CheckLaunchState()
@@ -475,7 +268,7 @@ namespace Ryujinx.Ava.UI.Windows
{
_deferLoad = false;
LoadApplication(_launchPath, _startFullscreen);
ViewModel.LoadApplication(_launchPath, _startFullscreen);
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this))
@@ -487,35 +280,9 @@ namespace Ryujinx.Ava.UI.Windows
}
}
public void RefreshFirmwareStatus()
{
SystemVersion version = null;
try
{
version = ContentManager.GetCurrentFirmwareVersion();
}
catch (Exception) { }
bool hasApplet = false;
if (version != null)
{
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion,
version.VersionString);
hasApplet = version.Major > 3;
}
else
{
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0");
}
ViewModel.IsAppletMenuActive = hasApplet;
}
private void Load()
{
VolumeStatus.Click += VolumeStatus_CheckedChanged;
StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
GameGrid.ApplicationOpened += Application_Opened;
@@ -535,6 +302,19 @@ namespace Ryujinx.Ava.UI.Windows
CheckLaunchState();
}
private void SetMainContent(Control content = null)
{
if (content == null)
{
content = GameLibrary;
}
if (MainContent.Content != content)
{
MainContent.Content = content;
}
}
public static void UpdateGraphicsConfig()
{
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
@@ -553,99 +333,6 @@ namespace Ryujinx.Ava.UI.Windows
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
}
public static void SaveConfig()
{
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
public void UpdateGameMetadata(string titleId)
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
{
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
});
}
private void PrepareLoadScreen()
{
using MemoryStream stream = new MemoryStream(ViewModel.SelectedIcon);
using var gameIconBmp = SixLabors.ImageSharp.Image.Load<Bgra32>(stream);
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
const int ColorDivisor = 4;
Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B);
Color progressBgColor = Color.FromRgb(
(byte)(dominantColor.R / ColorDivisor),
(byte)(dominantColor.G / ColorDivisor),
(byte)(dominantColor.B / ColorDivisor));
ViewModel.ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
ViewModel.ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
{
ViewModel.SearchText = SearchBox.Text;
}
private async void StopEmulation_Click(object sender, RoutedEventArgs e)
{
if (AppHost != null)
{
await AppHost.ShowExitPrompt();
}
}
private async void PauseEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
AppHost?.Pause();
});
}
private async void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
AppHost?.Resume();
});
}
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is MenuItem)
{
ViewModel.IsAmiiboRequested = AppHost.Device.System.SearchingForAmiibo(out _);
}
}
private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
AppHost.Device.EnableDeviceVsync = !AppHost.Device.EnableDeviceVsync;
Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {AppHost.Device.EnableDeviceVsync}");
}
private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
}
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
}
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
{
var volumeSplitButton = sender as ToggleSplitButton;
@@ -653,20 +340,20 @@ namespace Ryujinx.Ava.UI.Windows
{
if (!volumeSplitButton.IsChecked)
{
AppHost.Device.SetVolume(ConfigurationState.Instance.System.AudioVolume);
ViewModel.AppHost.Device.SetVolume(ConfigurationState.Instance.System.AudioVolume);
}
else
{
AppHost.Device.SetVolume(0);
ViewModel.AppHost.Device.SetVolume(0);
}
ViewModel.Volume = AppHost.Device.GetVolume();
ViewModel.Volume = ViewModel.AppHost.Device.GetVolume();
}
}
protected override void OnClosing(CancelEventArgs e)
{
if (!_isClosing && AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
{
e.Cancel = true;
@@ -675,14 +362,14 @@ namespace Ryujinx.Ava.UI.Windows
return;
}
_isClosing = true;
ViewModel.IsClosing = true;
if (AppHost != null)
if (ViewModel.AppHost != null)
{
AppHost.AppExit -= AppHost_AppExit;
AppHost.AppExit += (sender, e) =>
ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit;
ViewModel.AppHost.AppExit += (sender, e) =>
{
AppHost = null;
ViewModel.AppHost = null;
Dispatcher.UIThread.Post(() =>
{
@@ -691,7 +378,7 @@ namespace Ryujinx.Ava.UI.Windows
Close();
});
};
AppHost?.Stop();
ViewModel.AppHost?.Stop();
e.Cancel = true;
@@ -709,13 +396,43 @@ namespace Ryujinx.Ava.UI.Windows
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
_isClosing = await ContentDialogHelper.CreateExitDialog();
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
if (_isClosing)
if (ViewModel.IsClosing)
{
Close();
}
});
}
public async void LoadApplications()
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Clear();
StatusBarView.LoadProgressBar.IsVisible = true;
ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
});
ReloadGameList();
}
private void ReloadGameList()
{
if (_isLoading)
{
return;
}
_isLoading = true;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
_isLoading = false;
}
}
}

View File

@@ -6,13 +6,12 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
Width="1100"
Height="768"
d:DesignWidth="800"
d:DesignHeight="950"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"
@@ -23,9 +22,6 @@
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<Window.Resources>
<helpers:KeyValueConverter x:Key="Key" />
</Window.Resources>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -38,871 +34,15 @@
IsVisible="False"
KeyboardNavigation.IsTabStop="False"/>
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
<ScrollViewer Name="UiPage"
Margin="0,0,2,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGeneral}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDiscordIntegration}">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ToggleDiscordTooltip}"
Text="{locale:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
</CheckBox>
<CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
<TextBlock Text="{locale:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
</CheckBox>
<CheckBox IsChecked="{Binding ShowConfirmExit}">
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
</CheckBox>
<CheckBox IsChecked="{Binding HideCursorOnIdle}">
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<ListBox
Name="GameList"
MinHeight="250"
Items="{Binding GameDirectories}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
Name="PathBox"
Margin="0"
ToolTip.Tip="{locale:Locale AddGameDirBoxTooltip}"
VerticalAlignment="Stretch" />
<Button
Name="AddButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale AddGameDirTooltip}"
Click="AddButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{locale:Locale SettingsTabGeneralAdd}" />
</Button>
<Button
Name="RemoveButton"
Grid.Column="2"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale RemoveGameDirTooltip}"
Click="RemoveButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{locale:Locale SettingsTabGeneralRemove}" />
</Button>
</Grid>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralTheme}" />
<Grid Margin="10,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox IsChecked="{Binding EnableCustomTheme}"
ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
</CheckBox>
<TextBlock VerticalAlignment="Center"
Margin="0,10,0,0"
Grid.Row="1"
Text="{locale:Locale SettingsTabGeneralThemeCustomTheme}"
ToolTip.Tip="{locale:Locale CustomThemePathTooltip}" />
<TextBox Margin="0,10,0,0"
Grid.Row="1"
Grid.Column="1"
Text="{Binding CustomThemePath}" />
<Button Grid.Row="1"
Grid.Column="2"
Margin="10,10,0,0"
Command="{ReflectionBinding BrowseTheme}"
ToolTip.Tip="{locale:Locale CustomThemeBrowseTooltip}"
Content="{locale:Locale ButtonBrowse}" />
<TextBlock VerticalAlignment="Center"
Margin="0,10,0,0"
Grid.Row="2"
Text="{locale:Locale SettingsTabGeneralThemeBaseStyle}" />
<ComboBox VerticalAlignment="Center"
Margin="0,10,0,0"
Grid.Column="1"
Grid.Row="2"
MinWidth="100"
SelectedIndex="{Binding BaseStyleIndex}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleLight}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleDark}" />
</ComboBoxItem>
</ComboBox>
</Grid>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer Name="InputPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Padding="0,0,2,0"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel Margin="4" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
IsChecked="{Binding EnableDockedMode}">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
</CheckBox>
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
IsChecked="{Binding EnableKeyboard}">
<TextBlock Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
</CheckBox>
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
IsChecked="{Binding EnableMouse}">
<TextBlock Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
</CheckBox>
</StackPanel>
<window:ControllerSettingsWindow Name="ControllerSettings" Margin="0,0,0,0" MinHeight="600" />
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer Name="HotkeysPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel Margin="10,5" Orientation="Vertical" Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabHotkeysHotkeys}" />
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ToggleVsync, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.Screenshot, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ShowUi, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.Pause, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ToggleMute, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ResScaleUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ResScaleDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.VolumeUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.VolumeDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer Name="SystemPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemCore}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemRegion}"
Width="250" />
<ComboBox SelectedIndex="{Binding Region}"
ToolTip.Tip="{locale:Locale RegionTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionUSA}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionEurope}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionAustralia}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionChina}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionKorea}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionTaiwan}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
ToolTip.Tip="{locale:Locale LanguageTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding Language}"
ToolTip.Tip="{locale:Locale LanguageTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageGerman}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageItalian}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSpanish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageKorean}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageDutch}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguagePortuguese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageRussian}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
Width="250" />
<AutoCompleteBox
Name="TimeZoneBox"
Width="350"
MaxDropDownHeight="500"
FilterMode="Contains"
Items="{Binding TimeZones}"
SelectionChanged="TimeZoneBox_OnSelectionChanged"
Text="{Binding Path=TimeZone, Mode=OneWay}"
TextChanged="TimeZoneBox_OnTextChanged"
ValueMemberBinding="{ReflectionBinding TzMultiBinding}"
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTime}"
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="250"/>
<DatePicker VerticalAlignment="Center" SelectedDate="{Binding DateOffset}"
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="350" />
</StackPanel>
<StackPanel Margin="250,0,0,10" Orientation="Horizontal">
<TimePicker
VerticalAlignment="Center"
ClockIdentifier="24HourClock"
SelectedTime="{Binding TimeOffset}"
Width="350"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
</StackPanel>
<CheckBox IsChecked="{Binding EnableVsync}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}"
ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}"
ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<StackPanel Orientation="Horizontal">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
<TextBlock Text="{locale:Locale SettingsTabSystemHacksNote}" />
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding ExpandDramSize}"
ToolTip.Tip="{locale:Locale DRamTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" />
</CheckBox>
<CheckBox IsChecked="{Binding IgnoreMissingServices}"
ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" />
</CheckBox>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="CpuPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuCache}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding EnablePptc}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnablePptc}"
ToolTip.Tip="{locale:Locale PptcToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuMemory}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemMemoryManagerMode}"
ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding MemoryMode}"
ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerSoftwareTooltip}">
<TextBlock
Text="{locale:Locale SettingsTabSystemMemoryManagerModeSoftware}" />
</ComboBoxItem>
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerHostTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemMemoryManagerModeHost}" />
</ComboBoxItem>
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerUnsafeTooltip}">
<TextBlock
Text="{locale:Locale SettingsTabSystemMemoryManagerModeHostUnchecked}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="GraphicsPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsAPI}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
Text="{locale:Locale SettingsTabGraphicsBackend}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
SelectedIndex="{Binding GraphicsBackendIndex}">
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
<TextBlock Text="Vulkan" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
<TextBlock Text="OpenGL" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding IsVulkanSelected}">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
Text="{locale:Locale SettingsTabGraphicsPreferredGpu}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
SelectedIndex="{Binding PreferredGpuIndex}"
Items="{Binding AvailableGpus}"/>
</StackPanel>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsFeatures}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableShaderCache}"
ToolTip.Tip="{locale:Locale ShaderCacheToggleTooltip}">
<TextBlock Text="{locale:Locale SettingsTabGraphicsEnableShaderCache}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableTextureRecompression}"
ToolTip.Tip="{locale:Locale SettingsEnableTextureRecompressionTooltip}">
<TextBlock Text="{locale:Locale SettingsEnableTextureRecompression}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableMacroHLE}"
ToolTip.Tip="{locale:Locale SettingsEnableMacroHLETooltip}">
<TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}"
Text="{locale:Locale SettingsTabGraphicsResolutionScale}"
Width="250" />
<ComboBox SelectedIndex="{Binding ResolutionScale}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale2x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale3x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
</ComboBoxItem>
</ComboBox>
<ui:NumberBox
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale ResolutionScaleEntryTooltip}"
MinWidth="150"
SmallChange="0.1"
LargeChange="1"
SimpleNumberFormat="F2"
SpinButtonPlacementMode="Inline"
IsVisible="{Binding IsCustomResolutionScaleActive}"
Maximum="100"
Minimum="0.1"
Value="{Binding CustomResolutionScale}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale AnisotropyTooltip}"
Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering}"
Width="250" />
<ComboBox SelectedIndex="{Binding MaxAnisotropy}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale AnisotropyTooltip}">
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabGraphicsAnisotropicFilteringAuto}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering2x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering4x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering8x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering16x}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale AspectRatioTooltip}"
Text="{locale:Locale SettingsTabGraphicsAspectRatio}"
Width="250" />
<ComboBox SelectedIndex="{Binding AspectRatio}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale GraphicsBackendThreadingTooltip}"
Text="{locale:Locale SettingsTabGraphicsBackendMultithreading}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale GalThreadingTooltip}"
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonAuto}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonOff}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonOn}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsDeveloperOptions}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}"
Text="{locale:Locale SettingsTabGraphicsShaderDumpPath}"
Width="250" />
<TextBox Text="{Binding ShaderDumpPath}"
Width="350"
ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" />
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="AudioPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabAudio}" />
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemAudioBackend}"
ToolTip.Tip="{locale:Locale AudioBackendTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding AudioBackend}"
Width="350"
HorizontalContentAlignment="Left">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendDummy}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsOpenAlEnabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendOpenAL}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsSoundIoEnabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSDL2}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemAudioVolume}"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Width="250" />
<ui:NumberBox Value="{Binding Volume}"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Width="350"
SmallChange="1"
LargeChange="10"
SimpleNumberFormat="F0"
SpinButtonPlacementMode="Inline"
Minimum="0"
Maximum="100" />
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<Slider Value="{Binding Volume}"
Margin="250,0,0,0"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Minimum="0"
Maximum="100"
SmallChange="5"
TickFrequency="5"
IsSnapToTickEnabled="True"
LargeChange="10"
Width="350" />
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="NetworkPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" />
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</CheckBox>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="LoggingPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingLogging}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableFileLog}"
ToolTip.Tip="{locale:Locale FileLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableLoggingToFile}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableStub}"
ToolTip.Tip="{locale:Locale StubLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableStubLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableInfo}"
ToolTip.Tip="{locale:Locale InfoLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableInfoLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableWarn}"
ToolTip.Tip="{locale:Locale WarnLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableWarningLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableError}"
ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableTrace}"
ToolTip.Tip="{locale:Locale TraceLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableGuest}"
ToolTip.Tip="{locale:Locale GuestLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDebug}"
ToolTip.Tip="{locale:Locale DebugLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableFsAccessLog}"
ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
</CheckBox>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
Text="{locale:Locale SettingsTabLoggingFsGlobalAccessLogMode}"
Width="285" />
<ui:NumberBox
Maximum="3"
Minimum="0"
Width="150"
SpinButtonPlacementMode="Inline"
SmallChange="1"
LargeChange="1"
Value="{Binding FsGlobalAccessLogMode}" />
</StackPanel>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevel}"
ToolTip.Tip="{locale:Locale OpenGlLogLevel}"
Width="285" />
<ComboBox SelectedIndex="{Binding OpenglDebugLevel}"
Width="150"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale OpenGlLogLevel}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelError}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelPerformance}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelAll}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<settings:SettingsUIView Name="UiPage" />
<settings:SettingsInputView Name="InputPage" />
<settings:SettingsHotkeysView Name="HotkeysPage" />
<settings:SettingsSystemView Name="SystemPage" />
<settings:SettingsCPUView Name="CpuPage" />
<settings:SettingsGraphicsView Name="GraphicsPage" />
<settings:SettingsAudioView Name="AudioPage" />
<settings:SettingsNetworkView Name="NetworkPage" />
<settings:SettingsLoggingView Name="LoggingPage" />
</Grid>
<ui:NavigationView Grid.Row="1"
IsSettingsVisible="False"
@@ -962,7 +102,7 @@
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right"
ReverseOrder="{ReflectionBinding IsMacOS}">
ReverseOrder="{Binding IsMacOS}">
<Button
HotKey="Enter"
Classes="accent"

View File

@@ -1,49 +1,29 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Interactivity;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
using System.ComponentModel;
namespace Ryujinx.Ava.UI.Windows
{
public partial class SettingsWindow : StyleableWindow
{
private ButtonKeyAssigner _currentAssigner;
internal SettingsViewModel ViewModel { get; set; }
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
{
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.Settings]}";
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager);
DataContext = ViewModel;
ViewModel.CloseWindow += Close;
ViewModel.SaveSettingsEvent += SaveSettings;
InitializeComponent();
Load();
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()).Trim());
MultiBinding tzMultiBinding = new() { Converter = converter };
tzMultiBinding.Bindings.Add(new Binding("UtcDifference"));
tzMultiBinding.Bindings.Add(new Binding("Location"));
tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
TimeZoneBox.ValueMemberBinding = tzMultiBinding;
}
public SettingsWindow()
@@ -55,6 +35,16 @@ namespace Ryujinx.Ava.UI.Windows
Load();
}
public void SaveSettings()
{
InputPage.ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
{
window.ViewModel.LoadApplications();
}
}
private void Load()
{
Pages.Children.Clear();
@@ -62,152 +52,52 @@ namespace Ryujinx.Ava.UI.Windows
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
}
private void Button_Checked(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton button)
{
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
return;
}
if (_currentAssigner == null && (bool)button.IsChecked)
{
_currentAssigner = new ButtonKeyAssigner(button);
FocusManager.Instance.Focus(this, NavigationMethod.Pointer);
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
_currentAssigner.GetInputAndAssign(assigner);
}
else
{
if (_currentAssigner != null)
{
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
}
}
private void Button_Unchecked(object sender, RoutedEventArgs e)
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = false;
if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
{
shouldUnbind = true;
}
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
{
if (e.SelectedItem is NavigationViewItem navitem)
if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null)
{
NavPanel.Content = navitem.Tag.ToString() switch
switch (navItem.Tag.ToString())
{
"UiPage" => UiPage,
"InputPage" => InputPage,
"HotkeysPage" => HotkeysPage,
"SystemPage" => SystemPage,
"CpuPage" => CpuPage,
"GraphicsPage" => GraphicsPage,
"AudioPage" => AudioPage,
"NetworkPage" => NetworkPage,
"LoggingPage" => LoggingPage,
_ => throw new NotImplementedException()
};
}
}
private async void AddButton_OnClick(object sender, RoutedEventArgs e)
{
string path = PathBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
{
ViewModel.GameDirectories.Add(path);
ViewModel.DirectoryChanged = true;
}
else
{
path = await new OpenFolderDialog().ShowAsync(this);
if (!string.IsNullOrWhiteSpace(path))
{
ViewModel.GameDirectories.Add(path);
ViewModel.DirectoryChanged = true;
case "UiPage":
UiPage.ViewModel = ViewModel;
NavPanel.Content = UiPage;
break;
case "InputPage":
NavPanel.Content = InputPage;
break;
case "HotkeysPage":
NavPanel.Content = HotkeysPage;
break;
case "SystemPage":
SystemPage.ViewModel = ViewModel;
NavPanel.Content = SystemPage;
break;
case "CpuPage":
NavPanel.Content = CpuPage;
break;
case "GraphicsPage":
NavPanel.Content = GraphicsPage;
break;
case "AudioPage":
NavPanel.Content = AudioPage;
break;
case "NetworkPage":
NavPanel.Content = NetworkPage;
break;
case "LoggingPage":
NavPanel.Content = LoggingPage;
break;
default:
throw new NotImplementedException();
}
}
}
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
protected override void OnClosing(CancelEventArgs e)
{
int oldIndex = GameList.SelectedIndex;
foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
{
ViewModel.GameDirectories.Remove(path);
ViewModel.DirectoryChanged = true;
}
if (GameList.ItemCount > 0)
{
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
}
}
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems != null && e.AddedItems.Count > 0)
{
if (e.AddedItems[0] is TimeZone timeZone)
{
e.Handled = true;
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
}
}
}
private void TimeZoneBox_OnTextChanged(object sender, EventArgs e)
{
if (sender is AutoCompleteBox box)
{
if (box.SelectedItem != null && box.SelectedItem is TimeZone timeZone)
{
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
}
}
}
protected override void OnClosed(EventArgs e)
{
ControllerSettings.Dispose();
_currentAssigner?.Cancel();
_currentAssigner = null;
base.OnClosed(e);
HotkeysPage.Dispose();
InputPage.Dispose();
base.OnClosing(e);
}
}
}

View File

@@ -7,9 +7,9 @@ namespace Ryujinx.Common.Logging
AudioRenderer,
Configuration,
Cpu,
Font,
Emulation,
FFmpeg,
Font,
Gpu,
Hid,
Host1x,
@@ -66,6 +66,7 @@ namespace Ryujinx.Common.Logging
ServiceVi,
SurfaceFlinger,
TamperMachine,
Ui,
Vic
}
}

View File

@@ -29,7 +29,7 @@ namespace Ryujinx.Common.Logging
files[i].Delete();
}
string version = ReleaseInformations.GetVersion();
string version = ReleaseInformation.GetVersion();
// Get path for the current time
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.log");

View File

@@ -5,7 +5,7 @@ using System.Reflection;
namespace Ryujinx.Common
{
// DO NOT EDIT, filled by CI
public static class ReleaseInformations
public static class ReleaseInformation
{
private const string FlatHubChannelOwner = "flathub";
@@ -50,4 +50,4 @@ namespace Ryujinx.Common
return AppDomain.CurrentDomain.BaseDirectory;
}
}
}
}

View File

@@ -123,6 +123,8 @@ namespace Ryujinx.HLE.HOS
internal LibHacHorizonManager LibHacHorizonManager { get; private set; }
internal ServiceTable ServiceTable { get; private set; }
public bool IsPaused { get; private set; }
public Horizon(Switch device)
@@ -326,6 +328,7 @@ namespace Ryujinx.HLE.HOS
private void StartNewServices()
{
ServiceTable = new ServiceTable();
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices));
foreach (var service in services)

View File

@@ -182,6 +182,8 @@ namespace Ryujinx.HLE.HOS
byte[] arguments = null,
params IExecutable[] executables)
{
context.Device.System.ServiceTable.WaitServicesReady();
LibHac.Result rc = metaData.GetNpdm(out var npdm);
if (rc.IsFailure())

View File

@@ -1,19 +0,0 @@
using Ryujinx.HLE.HOS.Services.Lm.LogService;
namespace Ryujinx.HLE.HOS.Services.Lm
{
[Service("lm")]
class ILogService : IpcService
{
public ILogService(ServiceCtx context) { }
[CommandHipc(0)]
// Initialize(u64, pid) -> object<nn::lm::ILogger>
public ResultCode Initialize(ServiceCtx context)
{
MakeObject(context, new ILogger());
return ResultCode.Success;
}
}
}

View File

@@ -1,109 +0,0 @@
using Ryujinx.Common.Logging;
using System.IO;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Lm.LogService
{
class ILogger : IpcService
{
public ILogger() { }
[CommandHipc(0)]
// Log(buffer<unknown, 0x21>)
public ResultCode Log(ServiceCtx context)
{
Logger.Guest?.Print(LogClass.ServiceLm, LogImpl(context));
return ResultCode.Success;
}
private string LogImpl(ServiceCtx context)
{
(ulong bufPos, ulong bufSize) = context.Request.GetBufferType0x21();
byte[] logBuffer = new byte[bufSize];
context.Memory.Read(bufPos, logBuffer);
using MemoryStream ms = new MemoryStream(logBuffer);
BinaryReader reader = new BinaryReader(ms);
long pid = reader.ReadInt64();
long threadContext = reader.ReadInt64();
short flags = reader.ReadInt16();
byte level = reader.ReadByte();
byte verbosity = reader.ReadByte();
int payloadLength = reader.ReadInt32();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Guest Log:\n Log level: {(LmLogLevel)level}");
while (ms.Position < ms.Length)
{
int type = ReadEncodedInt(reader);
int size = ReadEncodedInt(reader);
LmLogField field = (LmLogField)type;
string fieldStr = string.Empty;
if (field == LmLogField.Start)
{
reader.ReadBytes(size);
continue;
}
else if (field == LmLogField.Stop)
{
break;
}
else if (field == LmLogField.Line)
{
fieldStr = $"{field}: {reader.ReadInt32()}";
}
else if (field == LmLogField.DropCount)
{
fieldStr = $"{field}: {reader.ReadInt64()}";
}
else if (field == LmLogField.Time)
{
fieldStr = $"{field}: {reader.ReadInt64()}s";
}
else if (field < LmLogField.Count)
{
fieldStr = $"{field}: '{Encoding.UTF8.GetString(reader.ReadBytes(size)).TrimEnd()}'";
}
else
{
fieldStr = $"Field{field}: '{Encoding.UTF8.GetString(reader.ReadBytes(size)).TrimEnd()}'";
}
sb.AppendLine($" {fieldStr}");
}
return sb.ToString();
}
private static int ReadEncodedInt(BinaryReader reader)
{
int result = 0;
int position = 0;
byte encoded;
do
{
encoded = reader.ReadByte();
result += (encoded & 0x7F) << (7 * position);
position++;
} while ((encoded & 0x80) != 0);
return result;
}
}
}

View File

@@ -1,18 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Lm.LogService
{
enum LmLogField
{
Start = 0,
Stop = 1,
Message = 2,
Line = 3,
Filename = 4,
Function = 5,
Module = 6,
Thread = 7,
DropCount = 8,
Time = 9,
ProgramName = 10,
Count
}
}

View File

@@ -1,11 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Lm.LogService
{
enum LmLogLevel
{
Trace,
Info,
Warning,
Error,
Critical
}
}

View File

@@ -1,182 +0,0 @@
using MsgPack;
using MsgPack.Serialization;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.Utilities;
using System;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Prepo
{
[Service("prepo:a", PrepoServicePermissionLevel.Admin)] // 1.0.0-5.1.0
[Service("prepo:a2", PrepoServicePermissionLevel.Admin)] // 6.0.0+
[Service("prepo:m", PrepoServicePermissionLevel.Manager)]
[Service("prepo:u", PrepoServicePermissionLevel.User)]
[Service("prepo:s", PrepoServicePermissionLevel.System)]
class IPrepoService : IpcService
{
private PrepoServicePermissionLevel _permission;
private ulong _systemSessionId;
public IPrepoService(ServiceCtx context, PrepoServicePermissionLevel permission)
{
_permission = permission;
}
[CommandHipc(10100)] // 1.0.0-5.1.0
[CommandHipc(10102)] // 6.0.0-9.2.0
[CommandHipc(10104)] // 10.0.0+
// SaveReport(u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
public ResultCode SaveReport(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.User) == 0)
{
return ResultCode.PermissionDenied;
}
// We don't care about the differences since we don't use the play report.
return ProcessReport(context, withUserID: false);
}
[CommandHipc(10101)] // 1.0.0-5.1.0
[CommandHipc(10103)] // 6.0.0-9.2.0
[CommandHipc(10105)] // 10.0.0+
// SaveReportWithUser(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
public ResultCode SaveReportWithUser(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.User) == 0)
{
return ResultCode.PermissionDenied;
}
// We don't care about the differences since we don't use the play report.
return ProcessReport(context, withUserID: true);
}
[CommandHipc(10200)]
// RequestImmediateTransmission()
public ResultCode RequestImmediateTransmission(ServiceCtx context)
{
// It signals an event of nn::prepo::detail::service::core::TransmissionStatusManager that requests the transmission of the report.
// Since we don't use reports it's fine to do nothing.
return ResultCode.Success;
}
[CommandHipc(10300)]
// GetTransmissionStatus() -> u32
public ResultCode GetTransmissionStatus(ServiceCtx context)
{
// It returns the transmission result of nn::prepo::detail::service::core::TransmissionStatusManager.
// Since we don't use reports it's fine to return ResultCode.Success.
context.ResponseData.Write((int)ResultCode.Success);
return ResultCode.Success;
}
[CommandHipc(10400)] // 9.0.0+
// GetSystemSessionId() -> u64
public ResultCode GetSystemSessionId(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.User) == 0)
{
return ResultCode.PermissionDenied;
}
if (_systemSessionId == 0)
{
byte[] randomBuffer = new byte[8];
Random.Shared.NextBytes(randomBuffer);
_systemSessionId = BitConverter.ToUInt64(randomBuffer, 0);
}
context.ResponseData.Write(_systemSessionId);
return ResultCode.Success;
}
[CommandHipc(20100)]
// SaveSystemReport(u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
public ResultCode SaveSystemReport(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.System) != 0)
{
return ResultCode.PermissionDenied;
}
// We don't care about the differences since we don't use the play report.
return ProcessReport(context, withUserID: false);
}
[CommandHipc(20101)]
// SaveSystemReportWithUser(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
public ResultCode SaveSystemReportWithUser(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.System) != 0)
{
return ResultCode.PermissionDenied;
}
// We don't care about the differences since we don't use the play report.
return ProcessReport(context, withUserID: true);
}
private ResultCode ProcessReport(ServiceCtx context, bool withUserID)
{
UserId userId = withUserID ? context.RequestData.ReadStruct<UserId>() : new UserId();
string gameRoom = StringUtils.ReadUtf8String(context);
if (withUserID)
{
if (userId.IsNull)
{
return ResultCode.InvalidArgument;
}
}
if (gameRoom == string.Empty)
{
return ResultCode.InvalidState;
}
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
if (inputSize == 0)
{
return ResultCode.InvalidBufferSize;
}
byte[] inputBuffer = new byte[inputSize];
context.Memory.Read(inputPosition, inputBuffer);
Logger.Info?.Print(LogClass.ServicePrepo, ReadReportBuffer(inputBuffer, gameRoom, userId));
return ResultCode.Success;
}
private string ReadReportBuffer(byte[] buffer, string room, UserId userId)
{
StringBuilder builder = new StringBuilder();
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(buffer);
builder.AppendLine();
builder.AppendLine("PlayReport log:");
if (!userId.IsNull)
{
builder.AppendLine($" UserId: {userId}");
}
builder.AppendLine($" Room: {room}");
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
return builder.ToString();
}
}
}

View File

@@ -1,15 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Prepo
{
enum ResultCode
{
ModuleId = 129,
ErrorCodeShift = 9,
Success = 0,
InvalidArgument = (1 << ErrorCodeShift) | ModuleId,
InvalidState = (5 << ErrorCodeShift) | ModuleId,
InvalidBufferSize = (9 << ErrorCodeShift) | ModuleId,
PermissionDenied = (90 << ErrorCodeShift) | ModuleId
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -180,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
return ResultCode.InvalidName;
}
Logger.Info?.Print(LogClass.ServiceSm, $"Register \"{name}\".");
Logger.Debug?.Print(LogClass.ServiceSm, $"Register \"{name}\".");
KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, null);

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.Headless.SDL2
{
public enum HideCursor
{
Never,
OnIdle,
Always
}
}

View File

@@ -5,7 +5,6 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Input.HLE;
using System;
using static SDL2.SDL;
namespace Ryujinx.Headless.SDL2.OpenGL
@@ -103,7 +102,13 @@ namespace Ryujinx.Headless.SDL2.OpenGL
private GraphicsDebugLevel _glLogLevel;
private SDL2OpenGLContext _openGLContext;
public OpenGLWindow(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse) : base(inputManager, glLogLevel, aspectRatio, enableMouse)
public OpenGLWindow(
InputManager inputManager,
GraphicsDebugLevel glLogLevel,
AspectRatio aspectRatio,
bool enableMouse,
HideCursor hideCursor)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
{
_glLogLevel = glLogLevel;
}
@@ -161,4 +166,4 @@ namespace Ryujinx.Headless.SDL2.OpenGL
SDL_GL_SwapWindow(WindowHandle);
}
}
}
}

View File

@@ -6,6 +6,14 @@ namespace Ryujinx.Headless.SDL2
{
public class Options
{
// General
[Option("root-data-dir", Required = false, HelpText = "Set the custom folder path for Ryujinx data.")]
public string BaseDataDir { get; set; }
[Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
public string UserProfile { get; set; }
// Input
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
@@ -23,7 +31,7 @@ namespace Ryujinx.Headless.SDL2
[Option("input-profile-5", Required = false, HelpText = "Set the input profile in use for Player 5.")]
public string InputProfile5Name { get; set; }
[Option("input-profile-6", Required = false, HelpText = "Set the input profile in use for Player 5.")]
[Option("input-profile-6", Required = false, HelpText = "Set the input profile in use for Player 6.")]
public string InputProfile6Name { get; set; }
[Option("input-profile-7", Required = false, HelpText = "Set the input profile in use for Player 7.")]
@@ -63,42 +71,45 @@ namespace Ryujinx.Headless.SDL2
public string InputIdHandheld { get; set; }
[Option("enable-keyboard", Required = false, Default = false, HelpText = "Enable or disable keyboard support (Independent from controllers binding).")]
public bool? EnableKeyboard { get; set; }
public bool EnableKeyboard { get; set; }
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
public bool? EnableMouse { get; set; }
public bool EnableMouse { get; set; }
[Option("hide-cursor", Required = false, Default = HideCursor.OnIdle, HelpText = "Change when the cursor gets hidden.")]
public HideCursor HideCursor { get; set; }
[Option("list-input-profiles", Required = false, HelpText = "List inputs profiles.")]
public bool? ListInputProfiles { get; set; }
public bool ListInputProfiles { get; set; }
[Option("list-inputs-ids", Required = false, HelpText = "List inputs ids.")]
public bool ListInputIds { get; set; }
// System
[Option("enable-ptc", Required = false, Default = true, HelpText = "Enables profiled translation cache persistency.")]
public bool? EnablePtc { get; set; }
[Option("disable-ptc", Required = false, HelpText = "Disables profiled persistent translation cache.")]
public bool DisablePtc { get; set; }
[Option("enable-internet-connection", Required = false, Default = false, HelpText = "Enables guest Internet connection.")]
public bool? EnableInternetAccess { get; set; }
public bool EnableInternetAccess { get; set; }
[Option("enable-fs-integrity-checks", Required = false, Default = true, HelpText = "Enables integrity checks on Game content files.")]
public bool? EnableFsIntegrityChecks { get; set; }
[Option("disable-fs-integrity-checks", Required = false, HelpText = "Disables integrity checks on Game content files.")]
public bool DisableFsIntegrityChecks { get; set; }
[Option("fs-global-access-log-mode", Required = false, Default = 0, HelpText = "Enables FS access log output to the console.")]
public int FsGlobalAccessLogMode { get; set; }
[Option("enable-vsync", Required = false, Default = true, HelpText = "Enables Vertical Sync.")]
public bool? EnableVsync { get; set; }
[Option("disable-vsync", Required = false, HelpText = "Disables Vertical Sync.")]
public bool DisableVsync { get; set; }
[Option("enable-shader-cache", Required = false, Default = true, HelpText = "Enables Shader cache.")]
public bool? EnableShaderCache { get; set; }
[Option("disable-shader-cache", Required = false, HelpText = "Disables Shader cache.")]
public bool DisableShaderCache { get; set; }
[Option("enable-texture-recompression", Required = false, Default = false, HelpText = "Enables Texture recompression.")]
public bool? EnableTextureRecompression { get; set; }
public bool EnableTextureRecompression { get; set; }
[Option("enable-docked-mode", Required = false, Default = true, HelpText = "Enables Docked Mode.")]
public bool? EnableDockedMode { get; set; }
[Option("disable-docked-mode", Required = false, HelpText = "Disables Docked Mode.")]
public bool DisableDockedMode { get; set; }
[Option("system-language", Required = false, Default = SystemLanguage.AmericanEnglish, HelpText = "Change System Language.")]
public SystemLanguage SystemLanguage { get; set; }
@@ -120,32 +131,32 @@ namespace Ryujinx.Headless.SDL2
// Logging
[Option("enable-file-logging", Required = false, Default = false, HelpText = "Enables logging to a file on disk.")]
public bool? EnableFileLog { get; set; }
[Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")]
public bool DisableFileLog { get; set; }
[Option("enable-debug-logs", Required = false, Default = false, HelpText = "Enables printing debug log messages.")]
public bool? LoggingEnableDebug { get; set; }
public bool LoggingEnableDebug { get; set; }
[Option("enable-stub-logs", Required = false, Default = true, HelpText = "Enables printing stub log messages.")]
public bool? LoggingEnableStub { get; set; }
[Option("disable-stub-logs", Required = false, HelpText = "Disables printing stub log messages.")]
public bool LoggingDisableStub { get; set; }
[Option("enable-info-logs", Required = false, Default = true, HelpText = "Enables printing info log messages.")]
public bool? LoggingEnableInfo { get; set; }
[Option("disable-info-logs", Required = false, HelpText = "Disables printing info log messages.")]
public bool LoggingDisableInfo { get; set; }
[Option("enable-warning-logs", Required = false, Default = true, HelpText = "Enables printing warning log messages.")]
public bool? LoggingEnableWarning { get; set; }
[Option("disable-warning-logs", Required = false, HelpText = "Disables printing warning log messages.")]
public bool LoggingDisableWarning { get; set; }
[Option("enable-error-logs", Required = false, Default = true, HelpText = "Enables printing error log messages.")]
public bool? LoggingEnableError { get; set; }
[Option("disable-error-logs", Required = false, HelpText = "Disables printing error log messages.")]
public bool LoggingEnableError { get; set; }
[Option("enable-trace-logs", Required = false, Default = false, HelpText = "Enables printing trace log messages.")]
public bool? LoggingEnableTrace { get; set; }
public bool LoggingEnableTrace { get; set; }
[Option("enable-guest-logs", Required = false, Default = true, HelpText = "Enables printing guest log messages.")]
public bool? LoggingEnableGuest { get; set; }
[Option("disable-guest-logs", Required = false, HelpText = "Disables printing guest log messages.")]
public bool LoggingDisableGuest { get; set; }
[Option("enable-fs-access-logs", Required = false, Default = false, HelpText = "Enables printing FS access log messages.")]
public bool? LoggingEnableFsAccessLog { get; set; }
public bool LoggingEnableFsAccessLog { get; set; }
[Option("graphics-debug-level", Required = false, Default = GraphicsDebugLevel.None, HelpText = "Change Graphics API debug log level.")]
public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; }
@@ -164,6 +175,9 @@ namespace Ryujinx.Headless.SDL2
[Option("backend-threading", Required = false, Default = BackendThreading.Auto, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")]
public BackendThreading BackendThreading { get; set; }
[Option("disable-macro-hle", Required= false, HelpText = "Disables high-level emulation of Macro code. Leaving this enabled improves performance but may cause graphical glitches in some games.")]
public bool DisableMacroHLE { get; set; }
[Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")]
public string GraphicsShadersDumpPath { get; set; }
@@ -176,14 +190,14 @@ namespace Ryujinx.Headless.SDL2
// Hacks
[Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")]
public bool? ExpandRam { get; set; }
public bool ExpandRam { get; set; }
[Option("ignore-missing-services", Required = false, Default = false, HelpText = "Enable ignoring missing services.")]
public bool? IgnoreMissingServices { get; set; }
public bool IgnoreMissingServices { get; set; }
// Values
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
public string InputPath { get; set; }
}
}
}

View File

@@ -33,7 +33,6 @@ using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
@@ -59,24 +58,10 @@ namespace Ryujinx.Headless.SDL2
static void Main(string[] args)
{
Version = ReleaseInformations.GetVersion();
Version = ReleaseInformation.GetVersion();
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
AppDataManager.Initialize(null);
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
_contentManager = new ContentManager(_virtualFileSystem);
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
_userChannelPersistence = new UserChannelPersistence();
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
{
AutoResetEvent invoked = new AutoResetEvent(false);
@@ -97,15 +82,9 @@ namespace Ryujinx.Headless.SDL2
};
}
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = true;
Parser.Default.ParseArguments<Options>(args)
.WithParsed(options => Load(options))
.WithParsed(Load)
.WithNotParsed(errors => errors.Output());
_inputManager.Dispose();
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
@@ -343,6 +322,24 @@ namespace Ryujinx.Headless.SDL2
static void Load(Options option)
{
AppDataManager.Initialize(option.BaseDataDir);
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
_contentManager = new ContentManager(_virtualFileSystem);
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
_userChannelPersistence = new UserChannelPersistence();
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = true;
IGamepad gamepad;
if (option.ListInputIds)
@@ -378,8 +375,8 @@ namespace Ryujinx.Headless.SDL2
}
_inputConfiguration = new List<InputConfig>();
_enableKeyboard = (bool)option.EnableKeyboard;
_enableMouse = (bool)option.EnableMouse;
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
{
@@ -407,30 +404,31 @@ namespace Ryujinx.Headless.SDL2
}
// Setup logging level
Logger.SetEnable(LogLevel.Debug, (bool)option.LoggingEnableDebug);
Logger.SetEnable(LogLevel.Stub, (bool)option.LoggingEnableStub);
Logger.SetEnable(LogLevel.Info, (bool)option.LoggingEnableInfo);
Logger.SetEnable(LogLevel.Warning, (bool)option.LoggingEnableWarning);
Logger.SetEnable(LogLevel.Error, (bool)option.LoggingEnableError);
Logger.SetEnable(LogLevel.Trace, (bool)option.LoggingEnableTrace);
Logger.SetEnable(LogLevel.Guest, (bool)option.LoggingEnableGuest);
Logger.SetEnable(LogLevel.AccessLog, (bool)option.LoggingEnableFsAccessLog);
Logger.SetEnable(LogLevel.Debug, option.LoggingEnableDebug);
Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub);
Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo);
Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning);
Logger.SetEnable(LogLevel.Error, option.LoggingEnableError);
Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
if ((bool)option.EnableFileLog)
if (!option.DisableFileLog)
{
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
1000,
AsyncLogTargetOverflowAction.Block
));
}
// Setup graphics configuration
GraphicsConfig.EnableShaderCache = (bool)option.EnableShaderCache;
GraphicsConfig.EnableTextureRecompression = (bool)option.EnableTextureRecompression;
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
GraphicsConfig.EnableTextureRecompression = option.EnableTextureRecompression;
GraphicsConfig.ResScale = option.ResScale;
GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE;
while (true)
{
@@ -443,6 +441,8 @@ namespace Ryujinx.Headless.SDL2
_userChannelPersistence.ShouldRestart = false;
}
_inputManager.Dispose();
}
private static void SetupProgressHandler()
@@ -479,8 +479,8 @@ namespace Ryujinx.Headless.SDL2
private static WindowBase CreateWindow(Options options)
{
return options.GraphicsBackend == GraphicsBackend.Vulkan
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse)
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse);
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor)
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor);
}
private static IRenderer CreateRenderer(Options options, WindowBase window)
@@ -533,20 +533,20 @@ namespace Ryujinx.Headless.SDL2
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
(bool)options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB,
options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB,
window,
options.SystemLanguage,
options.SystemRegion,
(bool)options.EnableVsync,
(bool)options.EnableDockedMode,
(bool)options.EnablePtc,
(bool)options.EnableInternetAccess,
(bool)options.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
!options.DisableVsync,
!options.DisableDockedMode,
!options.DisablePtc,
options.EnableInternetAccess,
!options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
options.FsGlobalAccessLogMode,
options.SystemTimeOffset,
options.SystemTimeZone,
options.MemoryManagerMode,
(bool)options.IgnoreMissingServices,
options.IgnoreMissingServices,
options.AspectRatio,
options.AudioVolume);
@@ -649,7 +649,7 @@ namespace Ryujinx.Headless.SDL2
}
else
{
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{options.InputPath}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
_emulationContext.Dispose();

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
@@ -31,16 +31,26 @@
<PackageReference Include="CommandLineParser" />
</ItemGroup>
<ItemGroup>
<Content Include="..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<Content Include="..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Ryujinx.bmp" />
</ItemGroup>
<!-- Due to .net core 3.1 embedded resource loading -->
<PropertyGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -10,7 +10,12 @@ namespace Ryujinx.Headless.SDL2
{
class SDL2MouseDriver : IGamepadDriver
{
private const int CursorHideIdleTime = 8; // seconds
private bool _isDisposed;
private HideCursor _hideCursor;
private bool _isHidden;
private long _lastCursorMoveTime;
public bool[] PressedButtons { get; }
@@ -18,9 +23,16 @@ namespace Ryujinx.Headless.SDL2
public Vector2 Scroll { get; private set; }
public Size _clientSize;
public SDL2MouseDriver()
public SDL2MouseDriver(HideCursor hideCursor)
{
PressedButtons = new bool[(int)MouseButton.Count];
_hideCursor = hideCursor;
if (_hideCursor == HideCursor.Always)
{
SDL_ShowCursor(SDL_DISABLE);
_isHidden = true;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -31,26 +43,75 @@ namespace Ryujinx.Headless.SDL2
return (MouseButton)(rawButton - 1);
}
public void Update(SDL_Event evnt)
public void UpdatePosition()
{
if (evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN || evnt.type == SDL_EventType.SDL_MOUSEBUTTONUP)
SDL_GetMouseState(out int posX, out int posY);
Vector2 position = new(posX, posY);
if (CurrentPosition != position)
{
uint rawButton = evnt.button.button;
CurrentPosition = position;
_lastCursorMoveTime = Stopwatch.GetTimestamp();
}
if (rawButton > 0 && rawButton <= (int)MouseButton.Count)
CheckIdle();
}
private void CheckIdle()
{
if (_hideCursor != HideCursor.OnIdle)
{
return;
}
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
if (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency)
{
if (!_isHidden)
{
PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN;
CurrentPosition = new Vector2(evnt.button.x, evnt.button.y);
SDL_ShowCursor(SDL_DISABLE);
_isHidden = true;
}
}
else if (evnt.type == SDL_EventType.SDL_MOUSEMOTION)
else
{
CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y);
if (_isHidden)
{
SDL_ShowCursor(SDL_ENABLE);
_isHidden = false;
}
}
else if (evnt.type == SDL_EventType.SDL_MOUSEWHEEL)
}
public void Update(SDL_Event evnt)
{
switch (evnt.type)
{
Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y);
case SDL_EventType.SDL_MOUSEBUTTONDOWN:
case SDL_EventType.SDL_MOUSEBUTTONUP:
uint rawButton = evnt.button.button;
if (rawButton > 0 && rawButton <= (int)MouseButton.Count)
{
PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN;
CurrentPosition = new Vector2(evnt.button.x, evnt.button.y);
}
break;
// NOTE: On Linux using Wayland mouse motion events won't be received at all.
case SDL_EventType.SDL_MOUSEMOTION:
CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y);
_lastCursorMoveTime = Stopwatch.GetTimestamp();
break;
case SDL_EventType.SDL_MOUSEWHEEL:
Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y);
break;
}
}
@@ -100,4 +161,4 @@ namespace Ryujinx.Headless.SDL2
_isDisposed = true;
}
}
}
}

View File

@@ -12,7 +12,13 @@ namespace Ryujinx.Headless.SDL2.Vulkan
{
private GraphicsDebugLevel _glLogLevel;
public VulkanWindow(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse) : base(inputManager, glLogLevel, aspectRatio, enableMouse)
public VulkanWindow(
InputManager inputManager,
GraphicsDebugLevel glLogLevel,
AspectRatio aspectRatio,
bool enableMouse,
HideCursor hideCursor)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
{
_glLogLevel = glLogLevel;
}
@@ -95,4 +101,4 @@ namespace Ryujinx.Headless.SDL2.Vulkan
protected override void SwapBuffers() { }
}
}
}

View File

@@ -14,13 +14,16 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using static SDL2.SDL;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless.SDL2
{
abstract class WindowBase : IHostUiHandler, IDisposable
abstract partial class WindowBase : IHostUiHandler, IDisposable
{
protected const int DefaultWidth = 1280;
protected const int DefaultHeight = 720;
@@ -29,6 +32,10 @@ namespace Ryujinx.Headless.SDL2
private static ConcurrentQueue<Action> MainThreadActions = new ConcurrentQueue<Action>();
[LibraryImport("SDL2")]
// TODO: Remove this as soon as SDL2-CS was updated to expose this method publicly
private static partial IntPtr SDL_LoadBMP_RW(IntPtr src, int freesrc);
public static void QueueMainThreadAction(Action action)
{
MainThreadActions.Enqueue(action);
@@ -66,9 +73,14 @@ namespace Ryujinx.Headless.SDL2
private AspectRatio _aspectRatio;
private bool _enableMouse;
public WindowBase(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse)
public WindowBase(
InputManager inputManager,
GraphicsDebugLevel glLogLevel,
AspectRatio aspectRatio,
bool enableMouse,
HideCursor hideCursor)
{
MouseDriver = new SDL2MouseDriver();
MouseDriver = new SDL2MouseDriver(hideCursor);
_inputManager = inputManager;
_inputManager.SetMouseDriver(MouseDriver);
NpadManager = _inputManager.CreateNpadManager();
@@ -103,6 +115,34 @@ namespace Ryujinx.Headless.SDL2
TouchScreenManager.Initialize(device);
}
private void SetWindowIcon()
{
Stream iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.Headless.SDL2.Ryujinx.bmp");
byte[] iconBytes = new byte[iconStream!.Length];
if (iconStream.Read(iconBytes, 0, iconBytes.Length) != iconBytes.Length)
{
Logger.Error?.Print(LogClass.Application, "Failed to read icon to byte array.");
iconStream.Close();
return;
}
iconStream.Close();
unsafe
{
fixed (byte* iconPtr = iconBytes)
{
IntPtr rwOpsStruct = SDL_RWFromConstMem((IntPtr)iconPtr, iconBytes.Length);
IntPtr iconHandle = SDL_LoadBMP_RW(rwOpsStruct, 1);
SDL_SetWindowIcon(WindowHandle, iconHandle);
SDL_FreeSurface(iconHandle);
}
}
}
private void InitializeWindow()
{
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty
@@ -127,6 +167,8 @@ namespace Ryujinx.Headless.SDL2
throw new Exception(errorMessage);
}
SetWindowIcon();
_windowId = SDL_GetWindowID(WindowHandle);
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
@@ -146,9 +188,11 @@ namespace Ryujinx.Headless.SDL2
Renderer?.Window.SetSize(Width, Height);
MouseDriver.SetClientSize(Width, Height);
break;
case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
Exit();
break;
default:
break;
}
@@ -331,6 +375,9 @@ namespace Ryujinx.Headless.SDL2
Device.Hid.DebugPad.Update();
// TODO: Replace this with MouseDriver.CheckIdle() when mouse motion events are received on every supported platform.
MouseDriver.UpdatePosition();
return true;
}
@@ -451,4 +498,4 @@ namespace Ryujinx.Headless.SDL2
}
}
}
}
}

View File

@@ -115,4 +115,4 @@ namespace Ryujinx.Horizon.Common
return PrintableResult;
}
}
}
}

View File

@@ -123,44 +123,51 @@ namespace Ryujinx.Horizon.Generators.Hipc
{
string[] args = new string[method.ParameterList.Parameters.Count];
int index = 0;
foreach (var parameter in method.ParameterList.Parameters)
if (args.Length == 0)
{
string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type);
CommandArgType argType = GetCommandArgType(compilation, parameter);
generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, Array.Empty<CommandArg>()) }},");
}
else
{
int index = 0;
string arg;
if (argType == CommandArgType.Buffer)
foreach (var parameter in method.ParameterList.Parameters)
{
string bufferFlags = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 0);
string bufferFixedSize = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 1);
string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type);
CommandArgType argType = GetCommandArgType(compilation, parameter);
if (bufferFixedSize != null)
string arg;
if (argType == CommandArgType.Buffer)
{
arg = $"new CommandArg({bufferFlags}, {bufferFixedSize})";
string bufferFlags = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 0);
string bufferFixedSize = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 1);
if (bufferFixedSize != null)
{
arg = $"new CommandArg({bufferFlags}, {bufferFixedSize})";
}
else
{
arg = $"new CommandArg({bufferFlags})";
}
}
else if (argType == CommandArgType.InArgument || argType == CommandArgType.OutArgument)
{
string alignment = GetTypeAlignmentExpression(compilation, parameter.Type);
arg = $"new CommandArg(CommandArgType.{argType}, Unsafe.SizeOf<{canonicalTypeName}>(), {alignment})";
}
else
{
arg = $"new CommandArg({bufferFlags})";
arg = $"new CommandArg(CommandArgType.{argType})";
}
}
else if (argType == CommandArgType.InArgument || argType == CommandArgType.OutArgument)
{
string alignment = GetTypeAlignmentExpression(compilation, parameter.Type);
arg = $"new CommandArg(CommandArgType.{argType}, Unsafe.SizeOf<{canonicalTypeName}>(), {alignment})";
}
else
{
arg = $"new CommandArg(CommandArgType.{argType})";
args[index++] = arg;
}
args[index++] = arg;
generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)}) }},");
}
generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)}) }},");
}
}

View File

@@ -12,12 +12,12 @@ namespace Ryujinx.Horizon
private struct Range : IComparable<Range>
{
public ulong Offset { get; }
public ulong Size { get; }
public ulong Size { get; }
public Range(ulong offset, ulong size)
{
Offset = offset;
Size = size;
Size = size;
}
public int CompareTo(Range other)
@@ -31,7 +31,7 @@ namespace Ryujinx.Horizon
public HeapAllocator()
{
_freeRanges = new List<Range>();
_freeRanges = new List<Range>();
_currentHeapSize = 0;
}
@@ -70,8 +70,8 @@ namespace Ryujinx.Horizon
var range = _freeRanges[i];
ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment);
ulong sizeDelta = alignedOffset - range.Offset;
ulong usableSize = range.Size - sizeDelta;
ulong sizeDelta = alignedOffset - range.Offset;
ulong usableSize = range.Size - sizeDelta;
if (sizeDelta < range.Size && usableSize >= size)
{
@@ -82,7 +82,7 @@ namespace Ryujinx.Horizon
InsertFreeRange(range.Offset, sizeDelta);
}
ulong endOffset = range.Offset + range.Size;
ulong endOffset = range.Offset + range.Size;
ulong remainingSize = endOffset - (alignedOffset + size);
if (remainingSize != 0)
{

View File

@@ -2,11 +2,13 @@ namespace Ryujinx.Horizon
{
public struct HorizonOptions
{
public bool IgnoreMissingServices { get; }
public bool IgnoreMissingServices { get; }
public bool ThrowOnInvalidCommandIds { get; }
public HorizonOptions(bool ignoreMissingServices)
{
IgnoreMissingServices = ignoreMissingServices;
IgnoreMissingServices = ignoreMissingServices;
ThrowOnInvalidCommandIds = true;
}
}
}

View File

@@ -21,24 +21,24 @@ namespace Ryujinx.Horizon
[ThreadStatic]
private static int _threadHandle;
public static HorizonOptions Options => _options;
public static ISyscallApi Syscall => _syscall;
public static IVirtualMemoryManager AddressSpace => _addressSpace;
public static IThreadContext ThreadContext => _threadContext;
public static int CurrentThreadHandle => _threadHandle;
public static HorizonOptions Options => _options;
public static ISyscallApi Syscall => _syscall;
public static IVirtualMemoryManager AddressSpace => _addressSpace;
public static IThreadContext ThreadContext => _threadContext;
public static int CurrentThreadHandle => _threadHandle;
public static void Register(
HorizonOptions options,
ISyscallApi syscallApi,
HorizonOptions options,
ISyscallApi syscallApi,
IVirtualMemoryManager addressSpace,
IThreadContext threadContext,
int threadHandle)
IThreadContext threadContext,
int threadHandle)
{
_options = options;
_syscall = syscallApi;
_addressSpace = addressSpace;
_options = options;
_syscall = syscallApi;
_addressSpace = addressSpace;
_threadContext = threadContext;
_threadHandle = threadHandle;
_threadHandle = threadHandle;
}
}
}

View File

@@ -2,6 +2,6 @@
{
interface IService
{
abstract static void Main();
abstract static void Main(ServiceTable serviceTable);
}
}

View File

@@ -9,23 +9,23 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.Horizon.LogManager
namespace Ryujinx.Horizon.LogManager.Ipc
{
partial class LmLogger : IServiceObject
partial class LmLogger : ILmLogger
{
private readonly LmLog _log;
private readonly ulong _clientProcessId;
private readonly LogService _log;
private readonly ulong _pid;
public LmLogger(LmLog log, ulong clientProcessId)
public LmLogger(LogService log, ulong pid)
{
_log = log;
_clientProcessId = clientProcessId;
_pid = pid;
}
[CmifCommand(0)]
public Result Log([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] Span<byte> message)
{
if (!SetProcessId(message, _clientProcessId))
if (!SetProcessId(message, _pid))
{
return Result.Success;
}
@@ -35,7 +35,7 @@ namespace Ryujinx.Horizon.LogManager
return Result.Success;
}
[CmifCommand(1)]
[CmifCommand(1)] // 3.0.0+
public Result SetDestination(LogDestination destination)
{
_log.LogDestination = destination;
@@ -48,7 +48,6 @@ namespace Ryujinx.Horizon.LogManager
ref LogPacketHeader header = ref MemoryMarshal.Cast<byte, LogPacketHeader>(message)[0];
uint expectedMessageSize = (uint)Unsafe.SizeOf<LogPacketHeader>() + header.PayloadSize;
if (expectedMessageSize != (uint)message.Length)
{
Logger.Warning?.Print(LogClass.ServiceLm, $"Invalid message size (expected 0x{expectedMessageSize:X} but got 0x{message.Length:X}).");
@@ -63,13 +62,11 @@ namespace Ryujinx.Horizon.LogManager
private static string LogImpl(ReadOnlySpan<byte> message)
{
SpanReader reader = new SpanReader(message);
SpanReader reader = new(message);
LogPacketHeader header = reader.Read<LogPacketHeader>();
StringBuilder builder = new();
LogPacketHeader header = reader.Read<LogPacketHeader>();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Guest Log:\n Log level: {header.Severity}");
builder.AppendLine($"Guest Log:\n Log level: {header.Severity}");
while (reader.Length > 0)
{
@@ -78,7 +75,7 @@ namespace Ryujinx.Horizon.LogManager
LogDataChunkKey field = (LogDataChunkKey)type;
string fieldStr = string.Empty;
string fieldStr;
if (field == LogDataChunkKey.Start)
{
@@ -111,16 +108,16 @@ namespace Ryujinx.Horizon.LogManager
fieldStr = $"Field{field}: '{Encoding.UTF8.GetString(reader.GetSpan(size)).TrimEnd()}'";
}
sb.AppendLine($" {fieldStr}");
builder.AppendLine($" {fieldStr}");
}
return sb.ToString();
return builder.ToString();
}
private static int ReadUleb128(ref SpanReader reader)
{
int result = 0;
int count = 0;
int count = 0;
byte encoded;
@@ -136,4 +133,4 @@ namespace Ryujinx.Horizon.LogManager
return result;
}
}
}
}

View File

@@ -2,18 +2,19 @@
using Ryujinx.Horizon.Sdk.Lm;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.LogManager
namespace Ryujinx.Horizon.LogManager.Ipc
{
partial class LmLog : IServiceObject
partial class LogService : ILogService
{
public LogDestination LogDestination { get; set; } = LogDestination.TargetManager;
[CmifCommand(0)]
public Result OpenLogger(out LmLogger logger, [ClientProcessId] ulong clientProcessId)
public Result OpenLogger(out LmLogger logger, [ClientProcessId] ulong pid)
{
logger = new LmLogger(this, clientProcessId);
// NOTE: Internal name is Logger, but we rename it LmLogger to avoid name clash with Ryujinx.Common.Logging logger.
logger = new LmLogger(this, pid);
return Result.Success;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More