Compare commits

..

60 Commits

Author SHA1 Message Date
49b37550ca Ava UI: Input Menu Refactor (#4998)
* So much boilerplate

* Slow and steady

* Restructure + Ack suggestions

* Restructure + Ack suggestions

* Restructure

* Clean

* Propogate those fields i forgot about

* It builds

* Progress

* Almost there

* Fix stupid mistake

* Fix more stupid mistakes

* Actually fix fuck ups

* Start localising

* r/therestofthefuckingowl

* Localise ButtonKeyAssigner

* Are you feeling it now mr krabs

* We’re done at last

* Crimes against code

* Try me in the Hague

* Please be quiet

* Crimes are here to stay

* Dispose stuff

* Cleanup a couple things

* Visual fixes and improvements

One weird bug

* Fix rebase errors

* Fixes

* Ack Suggestions

Remaining ack suggestions

Update src/Ryujinx.Ava/UI/Models/Input/ControllerInputConfig.cs

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

Update src/Ryujinx.Ava/UI/Models/Input/ControllerInputConfig.cs

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

* Formatting and error

More Ava 11-ness

Whoops

* Code style fixes

* Style fixes

* Analyzer fix

* Remove all ReflectionBindings

* Remove ambigious object

* Remove redundant property

* Old man yells at formatter

* r e a d o n l y

* Fix profiles

* Use new Sliders

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-10-21 13:26:51 +02:00
a42f0bbb87 Add "Create Shortcut" To app context menu (#4734)
* Added basic implementation for shortcut creation

Currently bitmaps (.bmp) are used as the source file, colours are good (unlike .ico rn) but are scaled poorly on desktop.

* Icons display properly in shortcut

* code cleanup

* Moved shortcut logic to specific file, added Ava UI for shortcuts

* Added linux .desktop shortcut creation

* fixes to .shortcut data

* code issue fixes

* Added basic implementation for shortcut creation

Currently bitmaps (.bmp) are used as the source file, colours are good (unlike .ico rn) but are scaled poorly on desktop.

* Icons display properly in shortcut

* code cleanup

* Moved shortcut logic to specific file, added Ava UI for shortcuts

* Added linux .desktop shortcut creation

* fixes to .shortcut data

* code issue fixes

* added back shortcut to new contextmenu file

* Replaced COM reference with ComImport for shortcut functionality

* remove specific platform values and regions

* Move ShortcutHelper to Ryujinx.Ui.Common.Helpers

* Adjust styling and structure

* code feedback changes

* Added MacOS support using .app folder

* Added basic implementation for shortcut creation

Currently bitmaps (.bmp) are used as the source file, colours are good (unlike .ico rn) but are scaled poorly on desktop.

* Icons display properly in shortcut

* code cleanup

* Moved shortcut logic to specific file, added Ava UI for shortcuts

* Added linux .desktop shortcut creation

* fixes to .shortcut data

* code issue fixes

* Added basic implementation for shortcut creation

Currently bitmaps (.bmp) are used as the source file, colours are good (unlike .ico rn) but are scaled poorly on desktop.

* Icons display properly in shortcut

* code cleanup

* Moved shortcut logic to specific file, added Ava UI for shortcuts

* Added linux .desktop shortcut creation

* fixes to .shortcut data

* code issue fixes

* Replaced COM reference with ComImport for shortcut functionality

* remove specific platform values and regions

* Move ShortcutHelper to Ryujinx.Ui.Common.Helpers

* Adjust styling and structure

* code feedback changes

* adjust tooltip message

* added shortcut-template.desktop file

* set shortcut icon location to .local/share/icons

* Linux code feedback changes

* change InteropServices to new securifybv.ShellLink Package

* added ShellLink to readme, updated shortcut comment

* Code feedback changes

* Added MacOS Support (As per Jose Estrada's PR)

* dotnet format

* Small restructuring

* Embed template files into Ryujinx.Ui.Common

* Disable "CreateShortcut" option for flatpak builds

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Jose Estrada <joseestradacobo@gmail.com>
2023-10-20 20:51:15 +02:00
b4bb22ba06 Avalonia: Make slider scrollable with mouse wheel (#5760)
* Add scrollable custom control based on TickFrequency

* Use custom slider to update value when pointer wheel scrolled

* Remove extra xaml file

* Address formatting issues

* Only scroll one element at a time

* Add OnPointerWheelChanged event to VolumeStatus button

Co-authored-by: Ahmad Tantowi <ahmadtantowi@outlook.com>

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-10-20 16:02:12 +02:00
6fdf774845 Ava UI: Update to 11.0.5 (#5815)
* Bump bump bump

* Missed one
2023-10-20 15:41:50 +02:00
76b53e018a GPU: Add fallback when textureGatherOffsets is not supported (#5792)
* GPU: Add fallback when textureGatherOffsets is not supported.

This PR adds a fallback for GPUs or APIs that don't support an equivalent to the method `textureGatherOffsets`, where each of the 4 gathered texels has an individual offset. This is done by reusing the existing code to handle non-const offsets for texture instructions, though it has also been corrected as there were a few implementation issues.

MoltenVK reports support for this capability, and it didn't error when we initially released the MacOS build, but that has since changed. MVK still reports support, but spirv-cross has been fixed in a way that it _attempts_ to use this capability, but the metal compiler errors since it doesn't exist.

Some other fixes:
- textureGatherOffsets emulation has been changed significantly. It now uses 4 texture sample instructions (not gather), calculates a base texel (i=0 j=0) and adds the offsets onto it before converting into a tex coord. The final result is offset into a texel center, so it shouldn't be subject to interpolation, though this isn't perfect and could have some error with floating point formats with linear sampling. It is subject to texture wrap mode as it should be, which is why texelFetch was not used.
  - Maybe gather should be used here with component `w` (i=0, j=0), though this multiplies number of texels fetched by 4... The way it was doing this before _was_ wrong_, but doing it right would avoid issues with texel center precision.
- textureGatherOffset (singular) now performs textureGather with the offset applied to the coords, rather than the slower fallback where each texel is fetched individually.

* Increment shader cache version, remove unused arg

* Use base texture size for gather coord offset.

Implicit LOD for gather is not supported.

* Use 4 texture gathers for offsets emulation

Avoids issues with interpolation at cost of performance

(not sure how bad this is)

* Address Feedback
2023-10-20 15:05:09 +02:00
28dd7d80af Enable copy between MS and non-MS textures with different height (#5801) 2023-10-18 04:47:22 +00:00
1e06b28b22 Horizon: Migrate usb and psc services (#5800)
* Horizon: Migrate Usb and Psc services

* Fix formatting

* Adresses feedback
2023-10-13 23:13:15 -03:00
e768a54f17 Replace ReaderWriterLock with ReaderWriterLockSlim (#5785)
* Replace ReaderWriterLock with ReaderWriterLockSlim

* Resolve Feedback + Correct typo

* Revert some unncessary logic
2023-10-12 18:11:15 +02:00
4e2bb13080 Fix games freezing after initializing LDN 1021 times (#5787)
* Close handle for stateChangeEvent on Finalize

* Properly dispose NetworkClient before setting it to null
2023-10-09 13:47:47 +00:00
ac4f2c1e70 Avalonia: Show aspect ratio popup options in status bar (#5780)
* Show aspect ratio selection popup in status bar

* Add aspect ratio tooltip

* Fix typo
2023-10-08 11:04:41 +02:00
e40470bbe1 Fix return value of Get function when a result does not yet exist for the address. (#5768) 2023-10-07 17:42:10 +02:00
f460ecc182 GPU: Add HLE macros for popular NVN macros (#5761)
* GPU: Add HLE macros for popular NVN macros

* Remove non-vector equality check

The case where it's not hardware accelerated will do the check integer-wise anyways.

* Whitespace 😔

* Address Feedback
2023-10-06 19:55:07 -03:00
086564c3c8 HLE: Fix Mii crc generation and minor issues (#5766)
* HLE: Fix Mii crc generation

Validating CRCs for data and device involves calculating the crc of all data including the crc being checked, which should then be 0.

The crc should be _generated_ on all data _before_ the crc in the struct. It shouldn't include the crcs themselves.

This fixes all generated miis (eg. default) having invalid crcs. This does not affect mii maker, as that generates its own charinfo.

Does not fix MK8D crash.

* Fix other mii issues

* Fully define all fields for Nickname and Ver3StoreData

Fixes an issue where the nickname for a mii would only have the first character on some method calls.

* Add Array96 type
2023-10-06 19:23:39 -03:00
b6ac45d36d Fix SPIR-V call out arguments regression (#5767)
* Fix SPIR-V call out arguments regression

* Shader cache version bump
2023-10-06 00:18:30 -03:00
7afae8c699 nuget: bump Microsoft.CodeAnalysis.CSharp from 4.6.0 to 4.7.0 (#5608)
Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.6.0 to 4.7.0.
- [Release notes](https://github.com/dotnet/roslyn/releases)
- [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md)
- [Commits](https://github.com/dotnet/roslyn/commits)

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.CSharp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-05 13:40:03 +02:00
7835968214 Strings should not be concatenated using '+' in a loop (#5664)
* Strings should not be concatenated using '+' in a loop

* fix IDE0090

* undo GenerateLoadOrStore

* prefer string interpolation

* Update src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs

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

---------

Co-authored-by: Mary <thog@protonmail.com>
2023-10-05 12:41:00 +02:00
0aceb534cb Fix SPIR-V function calls (#5764)
* Fix SPIR-V function calls

* Shader cache version bump
2023-10-04 21:35:26 -03:00
a0af6e4d07 Use unique temporary variables for function call parameters on SPIR-V (#5757)
* Use unique temporary variables for function call parameters on SPIR-V

* Shader cache version bump
2023-10-04 19:46:11 -03:00
jcm
f61b7818c3 Avalonia: Add macOS check for Color Space Passthrough (#5754)
* add macOS check for color passthrough

* use existing IsMacOS property

---------

Co-authored-by: jcm <butt@butts.com>
2023-10-04 19:15:37 +02:00
a2a97e1b11 Implement textureSamples texture query shader instruction (#5750)
* Implement textureSamples texture query shader instruction

* Shader cache version bump
2023-10-03 22:43:11 +00:00
8b2625b0be Decrement nvmap reference count on surface flinger prealloc (#5753) 2023-10-02 22:13:29 +00:00
651e24fed9 Signal friends completion event and stub CheckBlockedUserListAvailability (#5743) 2023-09-29 13:24:44 +02:00
41b104d0fb Fix audio renderer compressor effect (#5742)
* Delete DecibelToLinearExtended and fix Log10 function

* Fix CopyBuffer and ClearBuffer

* Change effect states from class to struct + formatting

* Formatting

* Make UpdateLowPassFilter readonly

* More compressor fixes
2023-09-29 10:48:49 +00:00
bc44b85b0b nuget: bump FluentAvaloniaUI from 2.0.1 to 2.0.4 (#5729)
* nuget: bump FluentAvaloniaUI from 2.0.1 to 2.0.4

Bumps [FluentAvaloniaUI](https://github.com/amwx/FluentAvalonia) from 2.0.1 to 2.0.4.
- [Commits](https://github.com/amwx/FluentAvalonia/commits)

---
updated-dependencies:
- dependency-name: FluentAvaloniaUI
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update Directory.Packages.props

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-09-28 00:15:45 +02:00
01c2b8097c Implement NGC service (#5681)
* Implement NGC service

* Use raw byte arrays instead of string for _wordSeparators

* Silence IDE0230 for _wordSeparators

* Try to silence warning about _rangeValuesCount not being read on SparseSet

* Make AcType enum private

* Add abstract methods and one TODO that I forgot

* PR feedback

* More PR feedback

* More PR feedback
2023-09-27 19:21:26 +02:00
4bd2ca3f0d nuget: bump System.IdentityModel.Tokens.Jwt from 6.31.0 to 7.0.0 (#5730)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.31.0 to 7.0.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.31.0...7.0.0)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 19:03:41 +02:00
e63157cc33 GPU: Don't create tracking handles for buffer textures (#5727)
* GPU: Don't create tracking handles for buffer textures

Buffer texture memory is handled by the buffer cache - the texture shouldn't create any tracking handles as they aren't used. This change simply makes them create and iterate 0 tracking handles, while keeping the rest of the texture group around.

This prevents a possible issue where many buffer textures are created as views of overlapping buffer ranges, and virtual regions have many dependant textures that don't actually contribute anything to handle state.

Should improve performance in Mortal Kombat 1, possibly certain UE4 games when FIFO raises to 100%.

* Fix interval tree bug

* Don't check view compatibility for buffer textures
2023-09-26 12:37:10 -03:00
7f2fb049f5 Ava: Fix regressions by rewriting CheckLaunchState (#5728) 2023-09-26 07:17:55 +02:00
4744bde0e5 Reduce the amount of descriptor pool allocations on Vulkan (#5673)
* Reduce the amount of descriptor pool allocations on Vulkan

* Formatting

* Slice can be simplified

* Make GetDescriptorPoolSizes static

* Adjust CanFit calculation so that TryAllocateDescriptorSets never fails

* Remove unused field
2023-09-26 02:00:02 +02:00
4a835bb2b9 Make Vulkan memory allocator actually thread safe (#5575)
* Make Vulkan memory allocator actually thread safe

* Make free thread safe too

* PR feedback
2023-09-26 01:50:06 +02:00
ddc9ae2a83 Add VTimer as alternative interrupt method on Apple Hypervisor (#5663)
* Add VTimer as alternative interrupt method on Apple Hypervisor

* Fix naming violations on TimeApi

* Fix timer interval (was 16us rather than 16ms)

* Fix delta ticks calculation

* Missing ThrowOnError call

* Add SupportedOSPlatform attribute on AppleHv classes
2023-09-26 01:18:32 +02:00
d6d3cdd573 Ava UI: Refactor async usage (#5516)
* Remove `async void`

* Async LoadApplications

* Formatting and such

* Remove async from InstallUpdate

* Update src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs

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

* Cleanup LoadApplications()

* Cleanup

* Formatting

* Revert some stuff

* Cleanup

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

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

* Ack suggestions

* Whitespace

* Fix Peri suggestion

* Add missing trailing commas

* Remove redundant method override

* Remove Dispatcher.UIThread.InvokeAsync/Post where possible

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-09-26 00:04:58 +02:00
53bd4c9f60 Add ldn:u implementation, INetworkClient interface and DisabledLdnClient (#5652)
* Impl first attempt to LDN

* Make this work.

- Endianness swap on all IPs.
- Use local network IP for connections, rather than 127.0.0.1. This is to be changed when tunnelling or whatever.
- Mac addresses are now randomly assigned on the server. (fixes joining lobbies)
- Fixed the "connected" handler for stations to actually find a
- Added info retrieval when connected to a station.
- Users that disconnect are now removed from rooms they were in. (still need to broadcast tho)
- The communication service does a bit better with being closed now.
- Some locking around the game instance dictionary.

* We may just be "initialized". Ignore this for now.

* Lots of WIP

* Add Disconnect packet

* Improve signalling of internal events.

* Fix scan.

* Fix some more stupid things.

* Enable NoDelay on all sockets.

* Add station accept policy, disconnect function.

* Limit max number of games.

* Split out networking stuff from HLE, so it can be swapped.

* Update logging calls.

* Missed a spot.

* Call SignalDisconnect instead of SetState

* Add comment to GetNetworkInfo

* Update configuration + UI

Now has its own tab, more options.

* Refactoring IUserLocalCommunicationService

( Expected new issues :'( )

* some cleanup

* More fix

* Correctly handle errors when connecting.

* Disable *Private call and clean symbols

* Structs cleanup

* Big cleanup

* Fix InvalidHandle (in MK8D and other games)

* Add Reject and Private Network support (v1)

RyuLdn Version bumped to 1.

* Add Initialize Packet

Allows users to keep Mac Addresses assigned by the server.

* Add SetWirelessControllerRestriction and some cleanup

* LDN-2 Initial Rebase

Make this work.

- Endianness swap on all IPs.
- Use local network IP for connections, rather than 127.0.0.1. This is to be changed when tunnelling or whatever.
- Fixed the "connected" handler for stations to actually find a
- The communication service does a bit better with being closed now.
- Some locking around the game instance dictionary.

We may just be "initialized". Ignore this for now.

Lots of WIP

Implement scan filter.

Improve signalling of internal events.

Fix scan.

Fix 0 width data, scan reply end delay removed.

Fix some more stupid things.

Enable NoDelay on all sockets.

Add station accept policy, disconnect function.

Limit max number of games.

Split out networking stuff from HLE, so it can be swapped.

Update logging calls.

Missed a spot.

SetAdvertiseData when open, don't return games that have accept policy 1

Update configuration + UI

Now has its own tab, more options.

Don't Keepalive, it causes problems.

Refactoring IUserLocalCommunicationService

( Expected new issues :'( )

some cleanup

More fix

Correctly handle errors when connecting.

Disable *Private call and clean symbols

Structs cleanup

Big cleanup

Fix InvalidHandle (in MK8D and other games)

Add Reject and Private Network support (v1)

Disable TcpNoDelay option on linux.

Add SetWirelessControllerRestriction and some cleanup

Misc cleanup, implement broadcast flag.

* Misc Changes

* Fix GetNetworkInfo

* Fix some small issues

* Implement GetNetworkInfoLatestUpdate

* Hotfix when LocalCommunicationId = 0xFFFFFFFFFFFFFFFF

* Fix ARMS Scan (and other games using wrong LocalCommunicationId

* Fix latest update when host leaves

* Revert "Fix ARMS Scan (and other games using wrong LocalCommunicationId"

This reverts commit 519c283d3993e2fdfafb8ac6b4e0a98231f6fb75.

* Fix the localCommunicationId = -1

* Don't set Connect flag for nodes already in the room before joining.

* Make IUserLocalCommunicationService disposable

* Don't dispose if there's no client.

* LDN-2-2 Rebase

Make this work.

- Endianness swap on all IPs.
- Use local network IP for connections, rather than 127.0.0.1. This is to be changed when tunnelling or whatever.
- Fixed the "connected" handler for stations to actually find a
- The communication service does a bit better with being closed now.
- Some locking around the game instance dictionary.

We may just be "initialized". Ignore this for now.

Put sockets behind an interface, so that they can be swapped for something proxyable

Lots of WIP

Implement scan filter.

Improve signalling of internal events.

Fix scan.

Fix 0 width data, scan reply end delay removed.

Fix some more stupid things.

Enable NoDelay on all sockets.

Add station accept policy, disconnect function.

Limit max number of games.

Split out networking stuff from HLE, so it can be swapped.

Update logging calls.

Missed a spot.

SetAdvertiseData when open, don't return games that have accept policy 1

Update configuration + UI

Now has its own tab, more options.

Don't Keepalive, it causes problems.

Refactoring IUserLocalCommunicationService

( Expected new issues :'( )

some cleanup

More fix

Correctly handle errors when connecting.

Disable *Private call and clean symbols

Structs cleanup

Big cleanup

Fix InvalidHandle (in MK8D and other games)

Add Reject and Private Network support (v1)

Disable TcpNoDelay option on linux.

Add SetWirelessControllerRestriction and some cleanup

Misc cleanup, implement broadcast flag.

Misc Changes

Fix GetNetworkInfo

Fix some small issues

Disable LAN by default til the config is added.

Fix Splatoon 2

- Stub nfp IUser::StartDetection / IUser::StopDetection.
- Stub ntc IEnsureNetworkClockAvailabilityService and needed calls.

Cleanup previous fixes

Stub IAudioInManager/IAudioIn for Splatoon 2 LAN

Add LAN settings to multiplayer tab

LAN Play > LAN Mode

Implement GetNetworkInfoLatestUpdate

Hotfix when LocalCommunicationId = 0xFFFFFFFFFFFFFFFF

Fix ARMS Scan (and other games using wrong LocalCommunicationId

Fix latest update when host leaves

Revert "Fix ARMS Scan (and other games using wrong LocalCommunicationId"

This reverts commit 519c283d3993e2fdfafb8ac6b4e0a98231f6fb75.

Fix the localCommunicationId = -1

Don't set Connect flag for nodes already in the room before joining.

Make IUserLocalCommunicationService disposable

Fix crash when using LAN mode on linux.

Actually use that call

Don't dispose if there's no client.

Fix the settings window crash

Fix configurationFileUpdated

* Make LDN compatible with Ryujinx/Ryujinx#3805

* Ava: Add Ldn options to SettingsNetworkTab

* Ava: Add update events for multiplayer options

* Apply formatting

* Remove LdnHelper

* ldn: Fix hardcoded /24 subnet mask

* Fix naming rule violations

* Add missing summary doc tag

* Remove NetCoreServer dependency

* Address code style issues and typos

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

* Call CloseStation/CloseAccessPoint to reduce code duplication

* Fix typo

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

* Fix missing trailing commas

* Extract AddressList from AddressEntry

* Use AcceptPolicy as a type for LdnNetworkInfo.StationAcceptPolicy

* Add Flags attribute to ScanFilterFlag

* Rename struct members for LdnNetworkInfo

* Remove extra line

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

* Extract NetworkErrorMessage from NetworkError

* Fix missing trailing commas

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: riperiperi <rhy3756547@hotmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-09-25 23:50:43 +02:00
eca8808649 Headless: Add support for Scaling Filters, Anti-aliasing and Exclusive Fullscreen (#5412)
* Headless: Added support for fullscreen option

* Headless: cleanup of fullscreen support

* Headless: fullscreen support : implemented proposed changes

* Headless: fullscreen support: cleanup

* Headless: exclusive fullscreen support: wip

* Headless: exclusive fullscreen support: add. windows scale interop

* Headless: exclusive fullscreen support: cleanup

* Headless: exclusive fullscreen support: cleanup

* Headless: fullscreen support: fix for OpenGL scaling

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: add. Vulkan fix

* Headless: fullscreen support: add. macOS fullscreen fix

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: fullscreen support: cleanup

* Headless: exclusive fullscreen support: add. display selection logic

* Headless: exclusive fullscreen support: add. anti-aliasing + scaling-filter logic

* Headless: exclusive fullscreen support: upd. options to be case-insensitive

* Headless: exclusive fullscreen support: force default values for scaling + anti-aliasing options

* Headless: upd. OpenGL --fullscreen window size logic

* Headless: upd. fullscreen logic

* Headless: cleanup

* Headless: refac. DisplayId option naming

* Headless: refac. scaling + anti-aliasing option handling

* Headless: refac. namespace handling

* Headless: upd. imports ordering

* Apply suggestions from code review

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

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-09-25 23:40:16 +02:00
f6c3f1cdfd GPU: Discard data when getting texture before full clear (#5719)
* GPU: Discard data when getting texture before full clear

* Fix rules and order of clear checks

* Fix formatting
2023-09-25 23:07:03 +02:00
8026e1c804 nuget: bump Microsoft.NET.Test.Sdk from 17.6.3 to 17.7.2 (#5622)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.3 to 17.7.2.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.6.3...v17.7.2)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 21:29:09 +02:00
d9f9bbfaa6 Vulkan: Fix barriers on macOS (#5700)
* Use old method on macOS

* gdk suggestions

* Update src/Ryujinx.Graphics.Vulkan/TextureStorage.cs

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

* Update src/Ryujinx.Graphics.Vulkan/TextureStorage.cs

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

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-09-23 19:32:36 -03:00
fe9e19d8cc [INFRA] Addition of basic contributor guides and docs framework. (#5581)
* Addition of basic contributor docs.

- Main contributor guide landing page.
- C# codestyle doc.
- Pull request guide doc.

All files and structure heavily inspired by the dotnet/runtime docs: https://github.com/dotnet/runtime/tree/main/docs

* fix typos and review changes

* Update XML doc requirement & conversation review.
2023-09-22 16:21:11 +02:00
fb55f57da7 Horizon: Migrate wlan and stubs latest services (#5708)
* Horizon: Migrate wlan and stubs latest services

This PR migrate empty wlan services, values are found by RE.
Latest firmwares added some other services which are now stubbed and up-to-date.

* Fix imports ordering
2023-09-20 22:55:27 +02:00
44862dce3e Stub unsupported BSD socket options (#5670)
* Stub unsupported BSD socket options

* Span.Clear
2023-09-19 19:35:56 +02:00
e601419bd4 make cheat list binding public (#5697) 2023-09-19 16:51:56 +00:00
d6bc0de785 use compiled bidning for localizations (#5684) 2023-09-18 22:20:59 +02:00
9f26fd3600 remove some usages of reflection binding (#5686) 2023-09-18 22:09:22 +02:00
88df636c87 Replace ShaderOutputLayer with equivalent ShaderViewportIndexLayerEXT capability (#5683) 2023-09-16 18:49:13 +02:00
7ccff037e8 Fix some Vulkan validation errors (mostly related to barriers) (#5603)
* Replace image barriers inside render pass with more generic memory barrier

* Remove forceStorage since it was creating images with storage bit for formats that are not StorageImage compatible

* Add missing flags on subpass dependency

* Don't call vkCmdSetScissor with a scissor count of 0

* One semaphore per swapchain image

* Remove compute stage from read to write barriers

* Try to improve Pipeline.Barrier nonsense

* Set PipelineStateFlags based on supported stages
2023-09-14 19:58:11 +02:00
a745913329 Fix gl_Layer to geometry shader change not writing gl_Layer (#5682)
* Fix gl_Layer to geometry shader change not writing gl_Layer

* Shader cache version bump
2023-09-14 14:53:53 -03:00
e6700b314f lbl: Migrate service to Horizon (#5628)
* lbl: Migrate service to Horizon

* Fix formatting

* Addresses gdkchan's feedback

* Fix comments
2023-09-14 09:50:19 +02:00
e2cfe6fe44 Fix shader GlobalToStorage pass when base address comes from local or shared memory (#5668)
* Fix shader GlobalToStorage pass when base address comes from local or shared memory

* Shader cache version bump
2023-09-11 01:22:18 +00:00
210f475484 Replacing 'Assembly.GetExecutingAssembly()' with 'Type.Assembly' (#5545) 2023-09-07 14:10:58 +02:00
ddb6493896 Delete ResourceAccess (#5626)
* Delete ResourceAccess

* Set write flag for vertex/geometry as compute output buffers
2023-09-05 22:59:21 +02:00
f631933e60 Add macOS Headless release workflow (#5272)
* Add macOS Headless release workflow

* Add MACH-O check before applying signature

* Rename script for consistency

* Remove redundant compiler flag

* Fix release.yml arguments + names

* Update headless.sh to include changes of pr #5398
2023-09-05 01:28:44 +02:00
5ff6ea6d82 Fix ShaderTools GpuAcessor default values (#5646) 2023-09-05 01:16:09 +02:00
c2d9c6955d Fix layer size for 3D textures with NPOT depth (#5640) 2023-09-04 20:14:08 -03:00
fbe0c211c1 Use poetry run instead of spawning a shell (#5653) 2023-09-05 00:55:04 +02:00
db0f3c0b74 ci: bump actions/checkout from 3 to 4 (#5650)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-04 22:16:17 +02:00
34447d7359 Fix overwriting .ryujinx-mako directory (#5651) 2023-09-04 19:14:20 +02:00
5f771f5661 Update reviewers using Ryujinx-Mako command (#5635)
* Update reviewers using Ryujinx-Mako command

* Fix worklflow step 'uses' path
2023-09-04 11:39:25 +00:00
93cd327873 Vulkan: Device Local and higher invocation count for buffer conversions (#5623)
Just some simple changes to the buffer conversion shaders. (stride conversion, D32S8 to D24S8)

The first change is using a device local buffer for converted vertex buffers, since they're only read/written on the GPU. These paths don't trigger on NVIDIA, but if you force them to use it demonstrates the full extent writing to host owned memory from compute absolutely destroys them. AMD GPUs are less heavily affected by this issue, but since the game in question was writing 230MB from compute, I imagine it should have some effect.

The second change is allowing the buffer conversion shaders to scale their work group count. While dividing the work between 32 invocations works OK for M1 macs, it's not so great for anything with more cores like AMD GPUs, which should be able to do a lot more parallel copies. Now, it scales by roughly 100 elements per invocation.

Some stride change cases could be improved further by either limiting vertex buffer size somehow (reading the index buffer could help, but is always risky) or only updating regions that changed, rather than invalidating the whole thing.
2023-09-02 17:58:15 -03:00
12cbacffca Fix numeric SWKB validation (#5627)
* Fix numeric swkbd validation

* GTK
2023-09-01 20:08:42 +02:00
437c78e198 opus: Implement GetWorkBufferSizeExEx and GetWorkBufferSizeForMultiStreamExEx (#5624)
* opus: Implement GetWorkBufferSizeExEx and GetWorkBufferSizeForMultiStreamExEx

* Fix comments
2023-08-30 22:04:25 +02:00
417 changed files with 14410 additions and 2761 deletions

View File

@ -1,87 +0,0 @@
from pathlib import Path
from typing import List, Set
from github import Auth, Github
from github.Repository import Repository
from github.GithubException import GithubException
import os
import sys
import yaml
def add_reviewers(
reviewers: Set[str], team_reviewers: Set[str], new_entries: List[str]
):
for reviewer in new_entries:
if reviewer.startswith("@"):
team_reviewers.add(reviewer[1:])
else:
reviewers.add(reviewer)
def update_reviewers(config, repo: Repository, pr_id: int) -> int:
pull_request = repo.get_pull(pr_id)
if not pull_request:
sys.stderr.writable(f"Unknown PR #{pr_id}\n")
return 1
if pull_request.draft:
print("Not assigning reviewers for draft PRs")
return 0
pull_request_author = pull_request.user.login
reviewers = set()
team_reviewers = set()
for label in pull_request.labels:
if label.name in config:
add_reviewers(reviewers, team_reviewers, config[label.name])
if "default" in config:
add_reviewers(reviewers, team_reviewers, config["default"])
if pull_request_author in reviewers:
reviewers.remove(pull_request_author)
try:
reviewers = list(reviewers)
team_reviewers = list(team_reviewers)
print(
f"Attempting to assign reviewers ({reviewers}) and team_reviewers ({team_reviewers})"
)
pull_request.create_review_request(reviewers, team_reviewers)
return 0
except GithubException as e:
sys.stderr.write(f"Cannot assign review request for PR #{pr_id}: {e}\n")
return 1
if __name__ == "__main__":
if len(sys.argv) != 7:
sys.stderr.write("usage: <app_id> <private_key_env_name> <installation_id> <repo_path> <pr_id> <config_path>\n")
sys.exit(1)
app_id = int(sys.argv[1])
private_key = os.environ[sys.argv[2]]
installation_id = int(sys.argv[3])
repo_path = sys.argv[4]
pr_id = int(sys.argv[5])
config_path = Path(sys.argv[6])
auth = Auth.AppAuth(app_id, private_key).get_installation_auth(installation_id)
g = Github(auth=auth)
repo = g.get_repo(repo_path)
if not repo:
sys.stderr.write("Repository not found!\n")
sys.exit(1)
if not config_path.exists():
sys.stderr.write(f'Config "{config_path}" not found!\n')
sys.exit(1)
with open(config_path, "r") as f:
config = yaml.safe_load(f)
sys.exit(update_reviewers(config, repo, pr_id))

View File

@ -35,7 +35,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v3
with:
@ -108,7 +108,7 @@ jobs:
configuration: [ Debug, Release ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v3
with:
@ -135,9 +135,13 @@ jobs:
id: git_short_hash
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
- name: Publish macOS
- name: Publish macOS Ryujinx.Ava
run: |
./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx.Ava artifact
uses: actions/upload-artifact@v3
@ -145,3 +149,10 @@ jobs:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_ava/*.tar.gz"
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v3
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_headless/*.tar.gz"
if: github.event_name == 'pull_request'

View File

@ -23,7 +23,7 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0

View File

@ -24,7 +24,7 @@ jobs:
RYUJINX_VERSION: "${{ inputs.ryujinx_version }}"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
path: Ryujinx
@ -38,7 +38,7 @@ jobs:
run: |
echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
repository: flathub/org.ryujinx.Ryujinx
token: ${{ secrets.RYUJINX_BOT_PAT }}

View File

@ -12,14 +12,24 @@ jobs:
runs-on: ubuntu-latest
steps:
# Grab sources to get update_reviewers.py and reviewers.yml
# Grab sources to get latest labeler.yml
- name: Fetch sources
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# Ensure we pin the source origin as pull_request_target run under forks.
fetch-depth: 0
repository: Ryujinx/Ryujinx
ref: master
# Ensure we pin the source origin as pull_request_target run under forks.
fetch-depth: 0
repository: Ryujinx/Ryujinx
ref: master
- name: Checkout Ryujinx-Mako
uses: actions/checkout@v4
with:
repository: Ryujinx/Ryujinx-Mako
ref: master
path: '.ryujinx-mako'
- name: Setup Ryujinx-Mako
uses: ./.ryujinx-mako/.github/actions/setup-mako
- name: Update labels based on changes
uses: actions/labeler@v4
@ -27,11 +37,11 @@ jobs:
sync-labels: true
dot: true
- run: pip3 install PyGithub
- name: Assign reviewers
run: |
python3 .github/update_reviewers.py ${{ secrets.MAKO_APP_ID }} "MAKO_PRIVATE_KEY" ${{ secrets.MAKO_INSTALLATION_ID }} ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml
poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml
shell: bash
env:
MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }}
MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }}
MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }}

View File

@ -62,7 +62,7 @@ jobs:
DOTNET_RUNTIME_IDENTIFIER: win10-x64
RELEASE_ZIP_OS_NAME: win_x64
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v3
with:
@ -150,7 +150,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v3
with:
@ -188,15 +188,19 @@ jobs:
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Publish macOS
- name: Publish macOS Ryujinx.Ava
run: |
./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "publish_ava/*.tar.gz"
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
omitBodyDuringUpdate: true

147
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,147 @@
# Contribution to Ryujinx
You can contribute to Ryujinx with PRs, testing of PRs and issues. Contributing code and other implementations is greatly appreciated alongside simply filing issues for problems you encounter.
Please read the entire document before continuing as it can potentially save everyone involved a significant amount of time.
# Quick Links
* [Code Style Documentation](docs/coding-guidelines/coding-style.md)
* [Pull Request Guidelines](docs/workflow/pr-guide.md)
## Reporting Issues
We always welcome bug reports, feature proposals and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible.
### Identify Where to Report
The Ryujinx codebase is distributed across multiple repositories in the [Ryujinx organization](https://github.com/Ryujinx). Depending on the feedback you might want to file the issue on a different repo. Here are a few common repos:
* [Ryujinx/Ryujinx](https://github.com/Ryujinx/Ryujinx) Ryujinx core project files.
* [Ryujinx/Ryujinx-Games-List](https://github.com/Ryujinx/Ryujinx-Games-List) Ryujinx game compatibility list.
* [Ryujinx/Ryujinx-Website](https://github.com/Ryujinx/Ryujinx-Website) Ryujinx website source code.
* [Ryujinx/Ryujinx-Ldn-Website](https://github.com/Ryujinx/Ryujinx-Ldn-Website) Ryujinx LDN website source code.
### Finding Existing Issues
Before filing a new issue, please search our [open issues](https://github.com/Ryujinx/Ryujinx/issues) to check if it already exists.
If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (👍 reaction) the original post, as this helps us prioritize popular issues in our backlog.
### Writing a Good Feature Request
Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please use the [Feature Request issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=%5BFeature+Request%5D).
### Writing a Good Bug Report
Good bug reports make it easier for maintainers to verify and root cause the underlying problem. The better a bug report, the faster the problem will be resolved.
Ideally, a bug report should contain the following information:
* A high-level description of the problem.
* A _minimal reproduction_, i.e. the smallest time commitment/configuration required to reproduce the wrong behavior. This can be in the form of a small homebrew application, or by providing a save file and reproduction steps for a specific game.
* A description of the _expected behavior_, contrasted with the _actual behavior_ observed.
* Information on the environment: OS/distro, CPU, GPU (including driver), RAM etc.
* A Ryujinx log file of the run instance where the issue occurred. Log files can be found in `[Executable Folder]/Logs` and are named chronologically.
* Additional information, e.g. is it a regression from previous versions? Are there any known workarounds?
When ready to submit a bug report, please use the [Bug Report issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=%5BBug%5D).
## Contributing Changes
Project maintainers will merge changes that both improve the project and meet our standards for code quality.
The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt) docs define additional guidance.
### DOs and DON'Ts
Please do:
* **DO** follow our [coding style](docs/coding-guidelines/coding-style.md) (C# code-specific).
* **DO** give priority to the current style of the project or file you're changing even if it diverges from the general guidelines.
* **DO** keep the discussions focused. When a new or related topic comes up
it's often better to create new issue than to side track the discussion.
* **DO** clearly state on an issue that you are going to take on implementing it.
* **DO** blog and tweet (or whatever) about your contributions, frequently!
Please do not:
* **DON'T** make PRs for style changes.
* **DON'T** surprise us with big pull requests. Instead, file an issue and talk with us on Discord to start
a discussion so we can agree on a direction before you invest a large amount
of time.
* **DON'T** commit code that you didn't write. If you find code that you think is a good fit to add to Ryujinx, file an issue or talk to us on Discord to start a discussion before proceeding.
* **DON'T** submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it.
### Suggested Workflow
We use and recommend the following workflow:
1. Create or find an issue for your work.
- You can skip this step for trivial changes.
- Get agreement from the team and the community that your proposed change is a good one if it is of significant size or changes core functionality.
- Clearly state that you are going to take on implementing it, if that's the case. You can request that the issue be assigned to you. Note: The issue filer and the implementer don't have to be the same person.
2. Create a personal fork of the repository on GitHub (if you don't already have one).
3. In your fork, create a branch off of main (`git checkout -b mybranch`).
- Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork.
4. Make and commit your changes to your branch.
- [Build Instructions](https://github.com/Ryujinx/Ryujinx#building) explains how to build and test.
- Commit messages should be clear statements of action and intent.
6. Build the repository with your changes.
- Make sure that the builds are clean.
- Make sure that `dotnet format` has been run and any corrections tested and committed.
7. Create a pull request (PR) against the Ryujinx/Ryujinx repository's **main** branch.
- State in the description what issue or improvement your change is addressing.
- Check if all the Continuous Integration checks are passing. Refer to [Actions](https://github.com/Ryujinx/Ryujinx/actions) to check for outstanding errors.
8. Wait for feedback or approval of your changes from the [core development team](https://github.com/orgs/Ryujinx/teams/developers)
- Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md).
9. When the team members have signed off, and all checks are green, your PR will be merged.
- The next official build will automatically include your change.
- You can delete the branch you used for making the change.
### Good First Issues
The team marks the most straightforward issues as [good first issues](https://github.com/Ryujinx/Ryujinx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). This set of issues is the place to start if you are interested in contributing but new to the codebase.
### Commit Messages
Please format commit messages as follows (based on [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)):
```
Summarize change in 50 characters or less
Provide more detail after the first line. Leave one blank line below the
summary and wrap all lines at 72 characters or less.
If the change fixes an issue, leave another blank line after the final
paragraph and indicate which issue is fixed in the specific format
below.
Fix #42
```
Also do your best to factor commits appropriately, not too large with unrelated things in the same commit, and not too small with the same small change applied N times in N different commits.
### PR - CI Process
The [Ryujinx continuous integration](https://github.com/Ryujinx/Ryujinx/actions) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean or have bugs properly filed against flaky/unexpected failures that are unrelated to your change.
If the CI build fails for any reason, the PR actions tab should be consulted for further information on the failure. There are a few usual suspects for such a failure:
* `dotnet format` has not been run on the PR and has outstanding stylistic issues.
* There is an error within the PR that fails a test or errors the compiler.
* Random failure of the workflow can occasionally result in a CI failure. In this scenario a maintainer will manually restart the job.
### PR Feedback
Ryujinx team and community members will provide feedback on your change. Community feedback is highly valued. You may see the absence of team feedback if the community has already provided good review feedback.
Two Ryujinx team members must review and approve every PR prior to merge. They will often reply with "LGTM, see nit". That means that the PR will be merged once the feedback is resolved. "LGTM" == "looks good to me".
There are lots of thoughts and [approaches](https://github.com/antlr/antlr4-cpp/blob/master/CONTRIBUTING.md#emoji) for how to efficiently discuss changes. It is best to be clear and explicit with your feedback. Please be patient with people who might not understand the finer details about your approach to feedback.
#### Copying Changes from Other Projects
Ryujinx uses some implementations and frameworks from other projects. The following rules must be followed for PRs that include changes from another project:
- The license of the file is [permissive](https://en.wikipedia.org/wiki/Permissive_free_software_licence).
- The license of the file is left in-tact.
- The contribution is correctly attributed in the [3rd party notices](https://github.com/Ryujinx/Ryujinx/blob/master/distribution/legal/THIRDPARTY.md) file in the repository, as needed.

View File

@ -3,25 +3,25 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.3" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.3" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.3" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.3" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.3" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0" />
<PackageVersion Include="Avalonia" Version="11.0.5" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.5" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.5" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.5" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.3" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.3" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="7.14.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.4" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
<PackageVersion Include="LibHac" Version="0.18.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NUnit" Version="3.13.3" />
@ -35,6 +35,7 @@
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
@ -44,9 +45,9 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.31.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@ -141,3 +141,4 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.

View File

@ -681,4 +681,33 @@
END OF TERMS AND CONDITIONS
```
</details>
# ShellLink (MIT)
<details>
<summary>See License</summary>
```
MIT License
Copyright (c) 2017 Yorick Koster, Securify B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
</details>

View File

@ -3,8 +3,8 @@ Version=1.0
Name=Ryujinx
Type=Application
Icon=Ryujinx
Exec=env DOTNET_EnableAlternateStackCheck=1 Ryujinx %f
Comment=A Nintendo Switch Emulator
Exec=Ryujinx.sh %f
Comment=Plays Nintendo Switch applications
GenericName=Nintendo Switch Emulator
Terminal=false
Categories=Game;Emulator;

View File

@ -0,0 +1,13 @@
[Desktop Entry]
Version=1.0
Name={0}
Type=Application
Icon={1}
Exec={2} %f
Comment=Nintendo Switch application
GenericName=Nintendo Switch Emulator
Terminal=false
Categories=Game;Emulator;
Keywords=Switch;Nintendo;Emulator;
StartupWMClass=Ryujinx
PrefersNonDefaultGPU=true

View File

@ -0,0 +1,111 @@
#!/bin/bash
set -e
if [ "$#" -lt 7 ]; then
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
exit 1
fi
mkdir -p "$1"
mkdir -p "$2"
mkdir -p "$3"
BASE_DIR=$(readlink -f "$1")
TEMP_DIRECTORY=$(readlink -f "$2")
OUTPUT_DIRECTORY=$(readlink -f "$3")
ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
VERSION=$5
SOURCE_REVISION_ID=$6
CONFIGURATION=$7
EXTRA_ARGS=$8
if [ "$VERSION" == "1.1.0" ];
then
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
else
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar
fi
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
X64_OUTPUT="$TEMP_DIRECTORY/publish_x64"
UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish"
EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL2
rm -rf "$TEMP_DIRECTORY"
mkdir -p "$TEMP_DIRECTORY"
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
dotnet restore
dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL2
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
# TODO: remove this once done
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
rm -rf "$OUTPUT_DIRECTORY"
mkdir -p "$OUTPUT_DIRECTORY"
# Let's copy one of the two different outputs and remove the executable
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
# Make it libraries universal
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
if ! [ -x "$(command -v lipo)" ];
then
if ! [ -x "$(command -v llvm-lipo-14)" ];
then
LIPO=llvm-lipo
else
LIPO=llvm-lipo-14
fi
else
LIPO=lipo
fi
# Make the executable universal
$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create
# Now sign it
if ! [ -x "$(command -v codesign)" ];
then
if ! [ -x "$(command -v rcodesign)" ];
then
echo "Cannot find rcodesign on your system, please install rcodesign."
exit 1
fi
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
echo "Using rcodesign for ad-hoc signing"
for FILE in "$UNIVERSAL_OUTPUT"/*; do
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE"
fi
done
else
echo "Using codesign for ad-hoc signing"
for FILE in "$UNIVERSAL_OUTPUT"/*; do
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE"
fi
done
fi
echo "Creating archive"
pushd "$OUTPUT_DIRECTORY"
tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2"
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME"
popd
echo "Done"

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>{0}</string>
<key>CFBundleGetInfoString</key>
<string>{1}</string>
<key>CFBundleIconFile</key>
<string>{2}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 - 2023 Ryujinx Team and Contributors.</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>UIPrerenderedIcon</key>
<true/>
<key>LSEnvironment</key>
<dict>
<key>DOTNET_DefaultStackSize</key>
<string>200000</string>
</dict>
</dict>
</plist>

40
docs/README.md Normal file
View File

@ -0,0 +1,40 @@
# Documents Index
This repo includes several documents that explain both high-level and low-level concepts about Ryujinx and its functions. These are very useful for contributors, to get context that can be very difficult to acquire from just reading code.
Intro to Ryujinx
==================
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
* The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions.
* The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
* Audio output is entirely supported via C# wrappers for SDL2, with OpenAL & libsoundio as fallbacks.
Getting Started
===============
- [Installing the .NET SDK](https://dotnet.microsoft.com/download)
- [Official .NET Docs](https://docs.microsoft.com/dotnet/core/)
Contributing (Building, testing, benchmarking, profiling, etc.)
===============
If you want to contribute a code change to this repo, start here.
- [Contributor Guide](../CONTRIBUTING.md)
Coding Guidelines
=================
- [C# coding style](coding-guidelines/coding-style.md)
- [Service Implementation Guidelines - WIP](https://gist.github.com/gdkchan/84ba88cd50efbe58d1babfaa7cd7c455)
Project Docs
=================
To be added. Many project files will contain basic XML docs for key functions and classes in the meantime.
Other Information
=================
- N/A

View File

@ -0,0 +1,116 @@
# C# Coding Style
The general rule we follow is "use Visual Studio defaults".
Using an IDE that supports the `.editorconfig` standard will make this much simpler.
1. We use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) braces, where each brace begins on a new line. A single line statement block can go without braces but the block must be properly indented on its own line and must not be nested in other statement blocks that use braces (See rule 18 for more details). One exception is that a `using` statement is permitted to be nested within another `using` statement by starting on the following line at the same indentation level, even if the nested `using` contains a controlled block.
2. We use four spaces of indentation (no tabs).
3. We use `_camelCase` for internal and private fields and use `readonly` where possible. Prefix internal and private instance fields with `_`, static fields with `s_` and thread static fields with `t_`. When used on static fields, `readonly` should come after `static` (e.g. `static readonly` not `readonly static`). Public fields should be used sparingly and should use PascalCasing with no prefix when used.
4. We avoid `this.` unless absolutely necessary.
5. We always specify the visibility, even if it's the default (e.g.
`private string _foo` not `string _foo`). Visibility should be the first modifier (e.g.
`public abstract` not `abstract public`).
6. Namespace imports should be specified at the top of the file, *outside* of `namespace` declarations.
7. Avoid more than one empty line at any time. For example, do not have two
blank lines between members of a type.
8. Avoid spurious free spaces.
For example avoid `if (someVar == 0)...`, where the dots mark the spurious free spaces.
Consider enabling "View White Space (Ctrl+R, Ctrl+W)" or "Edit -> Advanced -> View White Space" if using Visual Studio to aid detection.
9. If a file happens to differ in style from these guidelines (e.g. private members are named `m_member`
rather than `_member`), the existing style in that file takes precedence.
10. We only use `var` when the type is explicitly named on the right-hand side, typically due to either `new` or an explicit cast, e.g. `var stream = new FileStream(...)` not `var stream = OpenStandardInput()`.
- Similarly, target-typed `new()` can only be used when the type is explicitly named on the left-hand side, in a variable definition statement or a field definition statement. e.g. `FileStream stream = new(...);`, but not `stream = new(...);` (where the type was specified on a previous line).
11. We use language keywords instead of BCL types (e.g. `int, string, float` instead of `Int32, String, Single`, etc) for both type references as well as method calls (e.g. `int.Parse` instead of `Int32.Parse`). See issue [#13976](https://github.com/dotnet/runtime/issues/13976) for examples.
12. We use PascalCasing to name all our constant local variables and fields. The only exception is for interop code where the constant value should exactly match the name and value of the code you are calling via interop.
13. We use PascalCasing for all method names, including local functions.
14. We use ```nameof(...)``` instead of ```"..."``` whenever possible and relevant.
15. Fields should be specified at the top within type declarations.
16. When including non-ASCII characters in the source code use Unicode escape sequences (\uXXXX) instead of literal characters. Literal non-ASCII characters occasionally get garbled by a tool or editor.
17. When using labels (for goto), indent the label one less than the current indentation.
18. When using a single-statement if, we follow these conventions:
- Never use single-line form (for example: `if (source == null) throw new ArgumentNullException("source");`)
- Using braces is always accepted, and required if any block of an `if`/`else if`/.../`else` compound statement uses braces or if a single statement body spans multiple lines.
- Braces may be omitted only if the body of *every* block associated with an `if`/`else if`/.../`else` compound statement is placed on a single line.
19. Make all internal and private types static or sealed unless derivation from them is required. As with any implementation detail, they can be changed if/when derivation is required in the future.
20. XML docs should be used when writing interfaces or when a class/method is deemed sufficient in scope or complexity.
21. So-called [Magic Numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) should be defined as named constants before use (for example `for (int i = 56; i < 68; i++)` could read `for (int i = _currentAge; i < _retireAge; i++)`).
This may be ignored for trivial or syntactically common statements.
An [EditorConfig](https://editorconfig.org "EditorConfig homepage") file (`.editorconfig`) has been provided at the root of the runtime repository, enabling C# auto-formatting conforming to the above guidelines.
### Example File:
``ShaderCache.cs:``
```C#
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Shader
{
/// <summary>
/// Memory cache of shader code.
/// </summary>
class ShaderCache : IDisposable
{
/// <summary>
/// Default flags used on the shader translation process.
/// </summary>
public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
private readonly struct TranslatedShader
{
public readonly CachedShaderStage Shader;
public readonly ShaderProgram Program;
public TranslatedShader(CachedShaderStage shader, ShaderProgram program)
{
Shader = shader;
Program = program;
}
}
...
/// <summary>
/// Processes the queue of shaders that must save their binaries to the disk cache.
/// </summary>
public void ProcessShaderCacheQueue()
{
// Check to see if the binaries for previously compiled shaders are ready, and save them out.
while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave))
{
ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false);
if (result != ProgramLinkStatus.Incomplete)
{
if (result == ProgramLinkStatus.Success)
{
_cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
}
_programsToSaveQueue.Dequeue();
}
else
{
break;
}
}
}
}
}
```
For other languages, our current best guidance is consistency. When editing files, keep new code and changes consistent with the style in the files. For new files, it should conform to the style for that component. If there is a completely new component, anything that is reasonably broadly accepted is fine.

56
docs/workflow/pr-guide.md Normal file
View File

@ -0,0 +1,56 @@
# Pull Request Guide
## Contributing Rules
All contributions to Ryujinx/Ryujinx repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team.
To merge pull requests, you must have write permissions in the repository.
## Quick Code Review Rules
* Do not mix unrelated changes in one pull request. For example, a code style change should never be mixed with a bug fix.
* All changes should follow the existing code style. You can read more about our code style at [docs/coding-guidelines](../coding-guidelines/coding-style.md).
* Adding external dependencies is to be avoided unless not doing so would introduce _significant_ complexity. Any dependency addition should be justified and discussed before merge.
* Use Draft pull requests for changes you are still working on but want early CI loop feedback. When you think your changes are ready for review, [change the status](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request) of your pull request.
* Rebase your changes when required or directly requested. Changes should always be commited on top of the upstream branch, not the other way around.
* If you are asked to make changes during the review process do them as a new commit.
* Only resolve GitHub conversations with reviewers once they have been addressed with a commit, or via a mutual agreement.
## Pull Request Ownership
Every pull request will have automatically have labels and reviewers assigned. The label not only indicates the code segment which the change touches but also the area reviewers to be assigned.
If during the code review process a merge conflict occurs, the PR author is responsible for its resolution. Help will be provided if necessary although GitHub makes this easier by allowing simple conflict resolution using the [conflict-editor](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github).
## Pull Request Builds
When submitting a PR to the `Ryujinx/Ryujinx` repository, various builds will run validating many areas to ensure we keep developer productivity and product quality high. These various workflows can be tracked in the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of the repository. If the job continues to completion, the build artifacts will be uploaded and posted as a comment in the PR discussion.
## Review Turnaround Times
Ryujinx is a project that is maintained by volunteers on a completely free-time basis. As such we cannot guarantee any particular timeframe for pull request review and approval. Weeks to months are common for larger (>500 line) PRs but there are some additional best practises to avoid review purgatory.
* Make the reviewers life easier wherever possible. Make use of descriptive commit names, code comments and XML docs where applicable.
* If there is disagreement on feedback then always lean on the side of the development team and community over any personal opinion.
* We're human. We miss things. We forget things. If there has been radio silence on your changes for a substantial period of time then do not hesitate to reach out directly either with something simple like "bump" on GitHub or a directly on Discord.
To re-iterate, make the review as easy for us as possible, respond promptly and be comfortable to interact directly with us for anything else.
## Merging Pull Requests
Anyone with write access can merge a pull request manually when the following conditions have been met:
* The PR has been approved by two reviewers and any other objections are addressed.
* You can request follow up reviews from the original reviewers if they requested changes.
* The PR successfully builds and passes all tests in the Continuous Integration (CI) system. In case of failures, refer to the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of your PR.
Typically, PRs are merged as one commit (squash merges). It creates a simpler history than a Merge Commit. "Special circumstances" are rare, and typically mean that there are a series of cleanly separated changes that will be too hard to understand if squashed together, or for some reason we want to preserve the ability to dissect them.
## Blocking Pull Request Merging
If for whatever reason you would like to move your pull request back to an in-progress status to avoid merging it in the current form, you can turn the PR into a draft PR by selecting the option under the reviewers section. Alternatively, you can do that by adding [WIP] prefix to the pull request title.
## Old Pull Request Policy
From time to time we will review older PRs and check them for relevance. If we find the PR is inactive or no longer applies, we will close it. As the PR owner, you can simply reopen it if you feel your closed PR needs our attention.

View File

@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace ARMeilleure.Diagnostics
{
@ -33,7 +34,6 @@ namespace ARMeilleure.Diagnostics
public static string Get(ulong address)
{
if (_symbols.TryGetValue(address, out string result))
{
return result;
@ -48,13 +48,15 @@ namespace ARMeilleure.Diagnostics
ulong diff = address - symbol.Start;
ulong rem = diff % symbol.ElementSize;
result = symbol.Name + "_" + diff / symbol.ElementSize;
StringBuilder resultBuilder = new();
resultBuilder.Append($"{symbol.Name}_{diff / symbol.ElementSize}");
if (rem != 0)
{
result += "+" + rem;
resultBuilder.Append($"+{rem}");
}
result = resultBuilder.ToString();
_symbols.TryAdd(address, result);
return result;

View File

@ -189,7 +189,7 @@ namespace ARMeilleure.Translation
{
if (start.CompareTo(node.End) < 0)
{
if (overlaps.Length >= overlapCount)
if (overlaps.Length <= overlapCount)
{
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
}

View File

@ -7,14 +7,14 @@ namespace ARMeilleure.Translation
internal class TranslatorCache<T>
{
private readonly IntervalTree<ulong, T> _tree;
private readonly ReaderWriterLock _treeLock;
private readonly ReaderWriterLockSlim _treeLock;
public int Count => _tree.Count;
public TranslatorCache()
{
_tree = new IntervalTree<ulong, T>();
_treeLock = new ReaderWriterLock();
_treeLock = new ReaderWriterLockSlim();
}
public bool TryAdd(ulong address, ulong size, T value)
@ -24,70 +24,70 @@ namespace ARMeilleure.Translation
public bool AddOrUpdate(ulong address, ulong size, T value, Func<ulong, T, T> updateFactoryCallback)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
_treeLock.EnterWriteLock();
bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback);
_treeLock.ReleaseWriterLock();
_treeLock.ExitWriteLock();
return result;
}
public T GetOrAdd(ulong address, ulong size, T value)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
_treeLock.EnterWriteLock();
value = _tree.GetOrAdd(address, address + size, value);
_treeLock.ReleaseWriterLock();
_treeLock.ExitWriteLock();
return value;
}
public bool Remove(ulong address)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
_treeLock.EnterWriteLock();
bool removed = _tree.Remove(address) != 0;
_treeLock.ReleaseWriterLock();
_treeLock.ExitWriteLock();
return removed;
}
public void Clear()
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
_treeLock.EnterWriteLock();
_tree.Clear();
_treeLock.ReleaseWriterLock();
_treeLock.ExitWriteLock();
}
public bool ContainsKey(ulong address)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
_treeLock.EnterReadLock();
bool result = _tree.ContainsKey(address);
_treeLock.ReleaseReaderLock();
_treeLock.ExitReadLock();
return result;
}
public bool TryGetValue(ulong address, out T value)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
_treeLock.EnterReadLock();
bool result = _tree.TryGet(address, out value);
_treeLock.ReleaseReaderLock();
_treeLock.ExitReadLock();
return result;
}
public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
_treeLock.EnterReadLock();
int count = _tree.Get(address, address + size, ref overlaps);
_treeLock.ReleaseReaderLock();
_treeLock.ExitReadLock();
return count;
}
public List<T> AsList()
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
_treeLock.EnterReadLock();
List<T> list = _tree.AsList();
_treeLock.ReleaseReaderLock();
_treeLock.ExitReadLock();
return list;
}

View File

@ -31,9 +31,18 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public bool IsEffectEnabled { get; }
public AuxiliaryBufferCommand(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset,
ref AuxiliaryBufferAddresses sendBufferInfo, bool isEnabled, uint countMax,
CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId)
public AuxiliaryBufferCommand(
uint bufferOffset,
byte inputBufferOffset,
byte outputBufferOffset,
ref AuxiliaryBufferAddresses sendBufferInfo,
bool isEnabled,
uint countMax,
CpuAddress outputBuffer,
CpuAddress inputBuffer,
uint updateCount,
uint writeOffset,
int nodeId)
{
Enabled = true;
NodeId = nodeId;

View File

@ -21,7 +21,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private BiquadFilterParameter _parameter;
public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
public BiquadFilterCommand(
int baseIndex,
ref BiquadFilterParameter filter,
Memory<BiquadFilterState> biquadFilterStateMemory,
int inputBufferOffset,
int outputBufferOffset,
bool needInitialization,
int nodeId)
{
_parameter = filter;
BiquadFilterState = biquadFilterStateMemory;

View File

@ -77,7 +77,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ClearBuffer(int index)
{
Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount);
Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount * sizeof(float));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -89,7 +89,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex)
{
Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount);
Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount * sizeof(float));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -94,18 +94,18 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 0.0f;
float z = 1.0f;
bool unknown10OutOfRange = false;
bool unknown10OutOfRange = y >= state.Unknown10;
if (newMean < 1.0e-10f)
{
z = 1.0f;
y = -100.0f;
unknown10OutOfRange = state.Unknown10 < -100.0f;
unknown10OutOfRange = state.Unknown10 <= -100.0f;
}
if (y >= state.Unknown10 || unknown10OutOfRange)
if (unknown10OutOfRange)
{
float tmpGain;
@ -118,7 +118,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
}
z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
z = FloatingPointHelper.DecibelToLinear(tmpGain);
}
float unknown4New = z;

View File

@ -88,7 +88,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix2x2 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackBaseGain);
delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{
@ -125,9 +125,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix4x4 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
@ -172,11 +172,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix6x6 delayFeedback = new(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{

View File

@ -28,7 +28,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private LimiterParameter _parameter;
public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, Memory<EffectResultState> resultState, bool isEnabled, ulong workBuffer, int nodeId)
public LimiterCommandVersion2(
uint bufferOffset,
LimiterParameter parameter,
Memory<LimiterState> state,
Memory<EffectResultState> resultState,
bool isEnabled,
ulong workBuffer,
int nodeId)
{
Enabled = true;
NodeId = nodeId;

View File

@ -79,53 +79,57 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
ProcessReverbGeneric(ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableMono,
_targetEarlyDelayLineIndicesTableMono,
_targetOutputFeedbackIndicesTableMono,
_outputIndicesTableMono);
ProcessReverbGeneric(
ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableMono,
_targetEarlyDelayLineIndicesTableMono,
_targetOutputFeedbackIndicesTableMono,
_outputIndicesTableMono);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
ProcessReverbGeneric(ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableStereo,
_targetEarlyDelayLineIndicesTableStereo,
_targetOutputFeedbackIndicesTableStereo,
_outputIndicesTableStereo);
ProcessReverbGeneric(
ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableStereo,
_targetEarlyDelayLineIndicesTableStereo,
_targetOutputFeedbackIndicesTableStereo,
_outputIndicesTableStereo);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
ProcessReverbGeneric(ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableQuadraphonic,
_targetEarlyDelayLineIndicesTableQuadraphonic,
_targetOutputFeedbackIndicesTableQuadraphonic,
_outputIndicesTableQuadraphonic);
ProcessReverbGeneric(
ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableQuadraphonic,
_targetEarlyDelayLineIndicesTableQuadraphonic,
_targetOutputFeedbackIndicesTableQuadraphonic,
_outputIndicesTableQuadraphonic);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
ProcessReverbGeneric(ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableSurround,
_targetEarlyDelayLineIndicesTableSurround,
_targetOutputFeedbackIndicesTableSurround,
_outputIndicesTableSurround);
ProcessReverbGeneric(
ref state,
outputBuffers,
inputBuffers,
sampleCount,
_outputEarlyIndicesTableSurround,
_targetEarlyDelayLineIndicesTableSurround,
_targetOutputFeedbackIndicesTableSurround,
_outputIndicesTableSurround);
}
private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable, ReadOnlySpan<int> outputIndicesTable)

View File

@ -52,7 +52,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
{
// NOTE: Nintendo uses an approximation of log10, we don't.
// As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
return MathF.Pow(10, MathF.Max(x, 1.0e-10f));
return MathF.Log10(MathF.Max(x, 1.0e-10f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -62,7 +62,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
foreach (float input in inputs)
{
res += (input * input);
float normInput = input * (1f / 32768f);
res += normInput * normInput;
}
res /= inputs.Length;
@ -81,19 +82,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
return MathF.Pow(10.0f, db / 20.0f);
}
/// <summary>
/// Map decibel to linear in [0, 2] range.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value in [0, 2] range</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinearExtended(float db)
{
float tmp = MathF.Log2(DecibelToLinear(db));
return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{

View File

@ -3,7 +3,7 @@ using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class CompressorState
public struct CompressorState
{
public ExponentialMovingAverage InputMovingAverage;
public float Unknown4;
@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax;
Unknown10 = threshold - 1.5f;
Unknown14 = threshold + 1.5f;
OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain);
OutputGain = FloatingPointHelper.DecibelToLinear(parameter.OutputGain + makeupGain);
}
}
}

View File

@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class DelayState
public struct DelayState
{
public DelayLine[] DelayLines { get; }
public float[] LowPassZ { get; set; }
@ -53,7 +53,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
LowPassBaseGain = 1.0f - LowPassFeedbackGain;
}
public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
public readonly void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
{
for (int i = 0; i < channelCount; i++)
{

View File

@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class LimiterState
public struct LimiterState
{
public ExponentialMovingAverage[] DetectorAverage;
public ExponentialMovingAverage[] CompressionGainAverage;

View File

@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class Reverb3dState
public struct Reverb3dState
{
private readonly float[] _fdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f };
private readonly float[] _fdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f };

View File

@ -5,7 +5,7 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class ReverbState
public struct ReverbState
{
private static readonly float[] _fdnDelayTimes = new float[20]
{
@ -54,7 +54,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
// Room
0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f,
// Chamber
0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f,
0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f,
// Hall
0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f,
// Cathedral

View File

@ -104,7 +104,7 @@ namespace Ryujinx.Ava
{
"Light" => ThemeVariant.Light,
"Dark" => ThemeVariant.Dark,
_ => ThemeVariant.Default
_ => ThemeVariant.Default,
};
if (enableCustomTheme)

View File

@ -3,7 +3,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Rendering;
using Avalonia.Threading;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.Dummy;
@ -21,6 +20,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.GAL;
@ -191,6 +191,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
_gpuCancellationTokenSource = new CancellationTokenSource();
_gpuDoneEvent = new ManualResetEvent(false);
@ -412,6 +413,11 @@ namespace Ryujinx.Ava
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
}
private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs<MultiplayerMode> e)
{
Device.Configuration.MultiplayerMode = e.NewValue;
}
public void Stop()
{
_isActive = false;
@ -782,7 +788,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
ConfigurationState.Instance.Multiplayer.Mode);
Device = new Switch(configuration);
}

View File

@ -72,6 +72,8 @@
"GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)",
"GameListContextMenuExtractDataLogo": "Logo",
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
@ -261,6 +263,105 @@
"ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:",
"ControllerSettingsSave": "Save",
"ControllerSettingsClose": "Close",
"KeyUnknown": "Unknown",
"KeyShiftLeft": "Shift Left",
"KeyShiftRight": "Shift Right",
"KeyControlLeft": "Control Left",
"KeyControlRight": "Control Right",
"KeyAltLeft": "Alt Left",
"KeyAltRight": "Alt Right",
"KeyOptLeft": "⌥ Left",
"KeyOptRight": "⌥ Right",
"KeyWinLeft": "⊞ Left",
"KeyWinRight": "⊞ Right",
"KeyCmdLeft": "⌘ Left",
"KeyCmdRight": "⌘ Right",
"KeyMenu": "Menu",
"KeyUp": "Up",
"KeyDown": "Down",
"KeyLeft": "Left",
"KeyRight": "Right",
"KeyEnter": "Enter",
"KeyEscape": "Escape",
"KeySpace": "Space",
"KeyTab": "Tab",
"KeyBackSpace": "Backspace",
"KeyInsert": "Insert",
"KeyDelete": "Delete",
"KeyPageUp": "Page Up",
"KeyPageDown": "Page Down",
"KeyHome": "Home",
"KeyEnd": "End",
"KeyCapsLock": "Caps Lock",
"KeyScrollLock": "Scroll Lock",
"KeyPrintScreen": "Print Screen",
"KeyPause": "Pause",
"KeyNumLock": "Num Lock",
"KeyClear": "Clear",
"KeyKeypad0": "Keypad 0",
"KeyKeypad1": "Keypad 1",
"KeyKeypad2": "Keypad 2",
"KeyKeypad3": "Keypad 3",
"KeyKeypad4": "Keypad 4",
"KeyKeypad5": "Keypad 5",
"KeyKeypad6": "Keypad 6",
"KeyKeypad7": "Keypad 7",
"KeyKeypad8": "Keypad 8",
"KeyKeypad9": "Keypad 9",
"KeyKeypadDivide": "Keypad Divide",
"KeyKeypadMultiply": "Keypad Multiply",
"KeyKeypadSubtract": "Keypad Subtract",
"KeyKeypadAdd": "Keypad Add",
"KeyKeypadDecimal": "Keypad Decimal",
"KeyKeypadEnter": "Keypad Enter",
"KeyNumber0": "0",
"KeyNumber1": "1",
"KeyNumber2": "2",
"KeyNumber3": "3",
"KeyNumber4": "4",
"KeyNumber5": "5",
"KeyNumber6": "6",
"KeyNumber7": "7",
"KeyNumber8": "8",
"KeyNumber9": "9",
"KeyTilde": "~",
"KeyGrave": "`",
"KeyMinus": "-",
"KeyPlus": "+",
"KeyBracketLeft": "[",
"KeyBracketRight": "]",
"KeySemicolon": ";",
"KeyQuote": "\"",
"KeyComma": ",",
"KeyPeriod": ".",
"KeySlash": "/",
"KeyBackSlash": "\\",
"KeyUnbound": "Unbound",
"GamepadLeftStick": "Left Stick Button",
"GamepadRightStick": "Right Stick Button",
"GamepadLeftShoulder": "Left Shoulder",
"GamepadRightShoulder": "Right Shoulder",
"GamepadLeftTrigger": "Left Trigger",
"GamepadRightTrigger": "Right Trigger",
"GamepadDpadUp": "Up",
"GamepadDpadDown": "Down",
"GamepadDpadLeft": "Left",
"GamepadDpadRight": "Right",
"GamepadMinus": "-",
"GamepadPlus": "+",
"GamepadGuide": "Guide",
"GamepadMisc1": "Misc",
"GamepadPaddle1": "Paddle 1",
"GamepadPaddle2": "Paddle 2",
"GamepadPaddle3": "Paddle 3",
"GamepadPaddle4": "Paddle 4",
"GamepadTouchpad": "Touchpad",
"GamepadSingleLeftTrigger0": "Left Trigger 0",
"GamepadSingleRightTrigger0": "Right Trigger 0",
"GamepadSingleLeftTrigger1": "Left Trigger 1",
"GamepadSingleRightTrigger1": "Right Trigger 1",
"StickLeft": "Left Stick",
"StickRight": "Right Stick",
"UserProfilesSelectedUserProfile": "Selected User Profile:",
"UserProfilesSaveProfileName": "Save Profile Name",
"UserProfilesChangeProfileImage": "Change Profile Image",
@ -544,7 +645,7 @@
"SwkbdMinCharacters": "Must be at least {0} characters long",
"SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
"SoftwareKeyboard": "Software Keyboard",
"SoftwareKeyboardModeNumbersOnly": "Must be numbers only",
"SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only",
"SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only",
"SoftwareKeyboardModeASCII": "Must be ASCII text only",
"DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
@ -652,5 +753,8 @@
"NetworkInterfaceDefault": "Default",
"PackagingShaders": "Packaging Shaders",
"AboutChangelogButton": "View Changelog on GitHub",
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser."
}
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.",
"SettingsTabNetworkMultiplayer": "Multiplayer",
"MultiplayerMode": "Mode:",
"MultiplayerModeTooltip": "Change multiplayer mode"
}

View File

@ -15,8 +15,7 @@
<MenuItem Header="Test 2" />
<MenuItem Header="Test 3">
<MenuItem.Icon>
<CheckBox Margin="0"
IsChecked="{ReflectionBinding Checkbox, Mode=TwoWay}" />
<CheckBox Margin="0" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
@ -393,4 +392,4 @@
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
</Styles.Resources>
</Styles>
</Styles>

View File

@ -15,7 +15,6 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
@ -36,11 +35,9 @@ namespace Ryujinx.Ava.Common
private static HorizonClient _horizonClient;
private static AccountManager _accountManager;
private static VirtualFileSystem _virtualFileSystem;
private static StyleableWindow _owner;
public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, StyleableWindow owner)
public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient)
{
_owner = owner;
_virtualFileSystem = virtualFileSystem;
_horizonClient = horizonClient;
_accountManager = accountManager;
@ -148,7 +145,7 @@ namespace Ryujinx.Ava.Common
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false
AllowMultiple = false,
});
if (result.Count == 0)

View File

@ -1,6 +1,7 @@
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using System;
namespace Ryujinx.Ava.Common.Locale
@ -18,11 +19,20 @@ namespace Ryujinx.Ava.Common.Locale
{
LocaleKeys keyToUse = Key;
ReflectionBindingExtension binding = new($"[{keyToUse}]")
{
Mode = BindingMode.OneWay,
Source = LocaleManager.Instance,
};
var builder = new CompiledBindingPathBuilder();
builder.SetRawSource(LocaleManager.Instance)
.Property(new ClrPropertyInfo("Item",
obj => (LocaleManager.Instance[keyToUse]),
null,
typeof(string)), (weakRef, iPropInfo) =>
{
return PropertyInfoAccessorFactory.CreateInpcPropertyAccessor(weakRef, iPropInfo);
});
var path = builder.Build();
var binding = new CompiledBindingExtension(path);
return binding.ProvideValue(serviceProvider);
}

View File

@ -1,6 +1,5 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Input;
using System;

View File

@ -82,12 +82,9 @@ namespace Ryujinx.Modules
{
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
});
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
@ -114,10 +111,9 @@ namespace Ryujinx.Modules
{
if (showVersionUpToDate)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
@ -134,10 +130,9 @@ namespace Ryujinx.Modules
{
if (showVersionUpToDate)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
@ -149,10 +144,8 @@ namespace Ryujinx.Modules
{
Logger.Error?.Print(LogClass.Application, exception.Message);
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
});
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
_running = false;
@ -167,12 +160,9 @@ namespace Ryujinx.Modules
{
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
});
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
@ -183,10 +173,9 @@ namespace Ryujinx.Modules
{
if (showVersionUpToDate)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
@ -212,7 +201,7 @@ namespace Ryujinx.Modules
_buildSize = -1;
}
Dispatcher.UIThread.Post(async () =>
await Dispatcher.UIThread.InvokeAsync(async () =>
{
// Show a message asking the user if they want to update
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
@ -222,7 +211,7 @@ namespace Ryujinx.Modules
if (shouldUpdate)
{
UpdateRyujinx(mainWindow, _buildUrl);
await UpdateRyujinx(mainWindow, _buildUrl);
}
else
{
@ -241,7 +230,7 @@ namespace Ryujinx.Modules
return result;
}
private static async void UpdateRyujinx(Window parent, string downloadUrl)
private static async Task UpdateRyujinx(Window parent, string downloadUrl)
{
_updateSuccessful = false;
@ -579,27 +568,24 @@ namespace Ryujinx.Modules
}
}
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
{
// Extract Update
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
await Task.Run(() =>
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
}
else if (OperatingSystem.IsWindows())
{
ExtractZipFile(taskDialog, updateFile, _updateDir);
}
else
{
throw new NotSupportedException();
}
});
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
}
else if (OperatingSystem.IsWindows())
{
ExtractZipFile(taskDialog, updateFile, _updateDir);
}
else
{
throw new NotSupportedException();
}
// Delete downloaded zip
File.Delete(updateFile);
@ -613,36 +599,33 @@ namespace Ryujinx.Modules
if (!OperatingSystem.IsMacOS())
{
// Replace old files
await Task.Run(() =>
double count = 0;
foreach (string file in allFiles)
{
double count = 0;
foreach (string file in allFiles)
count++;
try
{
count++;
try
{
File.Move(file, file + ".ryuold");
File.Move(file, file + ".ryuold");
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
});
}
catch
Dispatcher.UIThread.InvokeAsync(() =>
{
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
}
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
});
}
Dispatcher.UIThread.Post(() =>
catch
{
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
});
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
}
}
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
Dispatcher.UIThread.InvokeAsync(() =>
{
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
});
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
Directory.Delete(_updateDir, true);
}
@ -658,12 +641,11 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage])
);
}
return false;
@ -673,12 +655,11 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage])
);
}
return false;
@ -688,12 +669,11 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
}
return false;
@ -705,21 +685,19 @@ namespace Ryujinx.Modules
{
if (ReleaseInformation.IsFlatHubBuild())
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage])
);
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
});
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
}
}

View File

@ -145,4 +145,4 @@
<ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" />
</ItemGroup>
</Project>
</Project>

View File

@ -106,7 +106,7 @@ namespace Ryujinx.Ava.UI.Applet
bool error = false;
string inputText = args.InitialText ?? "";
Dispatcher.UIThread.Post(async () =>
Dispatcher.UIThread.InvokeAsync(async () =>
{
try
{
@ -149,7 +149,7 @@ namespace Ryujinx.Ava.UI.Applet
bool showDetails = false;
Dispatcher.UIThread.Post(async () =>
Dispatcher.UIThread.InvokeAsync(async () =>
{
try
{

View File

@ -6,9 +6,11 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{locale:Locale ErrorWindowTitle}"
xmlns:views="using:Ryujinx.Ava.UI.Applet"
Width="450"
Height="340"
CanResize="False"
x:DataType="views:ErrorAppletWindow"
SizeToContent="Height"
mc:Ignorable="d"
Focusable="True">
@ -38,7 +40,7 @@
Grid.Column="1"
Margin="10"
VerticalAlignment="Stretch"
Text="{ReflectionBinding Message}"
Text="{Binding Message}"
TextWrapping="Wrap" />
<StackPanel
Name="ButtonStack"
@ -49,4 +51,4 @@
Orientation="Horizontal"
Spacing="10" />
</Grid>
</Window>
</Window>

View File

@ -4,7 +4,9 @@
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:views="using:Ryujinx.Ava.UI.Controls"
Width="400"
x:DataType="views:SwkbdAppletDialog"
mc:Ignorable="d"
Focusable="True">
<Grid
@ -34,13 +36,13 @@
Grid.Row="1"
Grid.Column="1"
Margin="5"
Text="{ReflectionBinding MainText}"
Text="{Binding MainText}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
Grid.Column="1"
Margin="5"
Text="{ReflectionBinding SecondaryText}"
Text="{Binding SecondaryText}"
TextWrapping="Wrap" />
<TextBox
Name="Input"
@ -50,7 +52,7 @@
VerticalAlignment="Center"
Focusable="True"
KeyUp="Message_KeyUp"
Text="{ReflectionBinding Message}"
Text="{Binding Message}"
TextInput="Message_TextInput"
TextWrapping="Wrap"
UseFloatingWatermark="True" />

View File

@ -136,10 +136,10 @@ namespace Ryujinx.Ava.UI.Controls
string localeText;
switch (mode)
{
case KeyboardMode.NumbersOnly:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumbersOnly);
case KeyboardMode.Numeric:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumeric);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(char.IsDigit);
_checkInput = text => text.All(NumericCharacterValidation.IsNumeric);
break;
case KeyboardMode.Alphabet:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet);

View File

@ -82,4 +82,9 @@
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem>
</MenuFlyout>
<MenuItem
Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}"
IsEnabled="{Binding CreateShortcutEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
</MenuFlyout>

View File

@ -54,7 +54,7 @@ namespace Ryujinx.Ava.UI.Controls
{
if (sender is MenuItem { DataContext: MainWindowViewModel viewModel })
{
OpenSaveDirectory(viewModel, SaveDataType.Account, userId: new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
}
}
@ -62,14 +62,14 @@ namespace Ryujinx.Ava.UI.Controls
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Device, userId: default);
OpenSaveDirectory(viewModel, SaveDataType.Device, default);
}
public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Bcat, userId: default);
OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
}
private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId)
@ -158,11 +158,12 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
@ -205,11 +206,12 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
@ -335,13 +337,24 @@ namespace Ryujinx.Ava.UI.Controls
}
}
public void RunApplication_Click(object sender, RoutedEventArgs args)
public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
viewModel.LoadApplication(viewModel.SelectedApplication.Path);
ApplicationData selectedApplication = viewModel.SelectedApplication;
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.TitleName, selectedApplication.TitleId, selectedApplication.Icon);
}
}
public async void RunApplication_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await viewModel.LoadApplication(viewModel.SelectedApplication.Path);
}
}
}

View File

@ -46,7 +46,7 @@
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="{ReflectionBinding $parent[UserControl].DataContext.GridItemSelectorSize}" />
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).GridItemSelectorSize}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
@ -56,10 +56,10 @@
Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}"
Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}"
Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}"
Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}"
Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}"
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
ClipToBounds="True"
CornerRadius="4">
<Grid>
@ -78,7 +78,7 @@
Margin="0,10,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{ReflectionBinding $parent[UserControl].DataContext.ShowNames}">
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
@ -101,4 +101,4 @@
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
</UserControl>

View File

@ -42,7 +42,7 @@
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="{ReflectionBinding $parent[UserControl].DataContext.ListItemSelectorSize}" />
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ListItemSelectorSize}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
@ -67,10 +67,10 @@
Grid.RowSpan="3"
Grid.Column="0"
Margin="0"
Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}"
Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}"
Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}"
Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}"
Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}"
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<Border
Grid.Column="2"
@ -157,4 +157,4 @@
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
</UserControl>

View File

@ -0,0 +1,31 @@
using Avalonia.Controls;
using Avalonia.Input;
using System;
namespace Ryujinx.Ava.UI.Controls
{
public class SliderScroll : Slider
{
protected override Type StyleKeyOverride => typeof(Slider);
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
var newValue = Value + e.Delta.Y * TickFrequency;
if (newValue < Minimum)
{
Value = Minimum;
}
else if (newValue > Maximum)
{
Value = Maximum;
}
else
{
Value = newValue;
}
e.Handled = true;
}
}
}

View File

@ -1,11 +1,8 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Threading;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers
@ -15,12 +12,12 @@ namespace Ryujinx.Ava.UI.Helpers
internal class ButtonAssignedEventArgs : EventArgs
{
public ToggleButton Button { get; }
public bool IsAssigned { get; }
public ButtonValue? ButtonValue { get; }
public ButtonAssignedEventArgs(ToggleButton button, bool isAssigned)
public ButtonAssignedEventArgs(ToggleButton button, ButtonValue? buttonValue)
{
Button = button;
IsAssigned = isAssigned;
ButtonValue = buttonValue;
}
}
@ -78,15 +75,11 @@ namespace Ryujinx.Ava.UI.Helpers
await Dispatcher.UIThread.InvokeAsync(() =>
{
string pressedButton = assigner.GetPressedButton();
ButtonValue? pressedButton = assigner.GetPressedButton();
if (_shouldUnbind)
{
SetButtonText(ToggledButton, "Unbound");
}
else if (pressedButton != "")
{
SetButtonText(ToggledButton, pressedButton);
pressedButton = null;
}
_shouldUnbind = false;
@ -94,17 +87,8 @@ namespace Ryujinx.Ava.UI.Helpers
ToggledButton.IsChecked = false;
ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton != null));
ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton));
static void SetButtonText(ToggleButton button, string text)
{
ILogical textBlock = button.GetLogicalDescendants().First(x => x is TextBlock);
if (textBlock != null && textBlock is TextBlock block)
{
block.Text = text;
}
}
});
}

View File

@ -1,7 +1,9 @@
using Avalonia.Data.Converters;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
@ -10,37 +12,158 @@ namespace Ryujinx.Ava.UI.Helpers
{
public static KeyValueConverter Instance = new();
private static readonly Dictionary<Key, LocaleKeys> _keysMap = new()
{
{ Key.Unknown, LocaleKeys.KeyUnknown },
{ Key.ShiftLeft, LocaleKeys.KeyShiftLeft },
{ Key.ShiftRight, LocaleKeys.KeyShiftRight },
{ Key.ControlLeft, LocaleKeys.KeyControlLeft },
{ Key.ControlRight, LocaleKeys.KeyControlRight },
{ Key.AltLeft, OperatingSystem.IsMacOS() ? LocaleKeys.KeyOptLeft : LocaleKeys.KeyAltLeft },
{ Key.AltRight, OperatingSystem.IsMacOS() ? LocaleKeys.KeyOptRight : LocaleKeys.KeyAltRight },
{ Key.WinLeft, OperatingSystem.IsMacOS() ? LocaleKeys.KeyCmdLeft : LocaleKeys.KeyWinLeft },
{ Key.WinRight, OperatingSystem.IsMacOS() ? LocaleKeys.KeyCmdRight : LocaleKeys.KeyWinRight },
{ Key.Up, LocaleKeys.KeyUp },
{ Key.Down, LocaleKeys.KeyDown },
{ Key.Left, LocaleKeys.KeyLeft },
{ Key.Right, LocaleKeys.KeyRight },
{ Key.Enter, LocaleKeys.KeyEnter },
{ Key.Escape, LocaleKeys.KeyEscape },
{ Key.Space, LocaleKeys.KeySpace },
{ Key.Tab, LocaleKeys.KeyTab },
{ Key.BackSpace, LocaleKeys.KeyBackSpace },
{ Key.Insert, LocaleKeys.KeyInsert },
{ Key.Delete, LocaleKeys.KeyDelete },
{ Key.PageUp, LocaleKeys.KeyPageUp },
{ Key.PageDown, LocaleKeys.KeyPageDown },
{ Key.Home, LocaleKeys.KeyHome },
{ Key.End, LocaleKeys.KeyEnd },
{ Key.CapsLock, LocaleKeys.KeyCapsLock },
{ Key.ScrollLock, LocaleKeys.KeyScrollLock },
{ Key.PrintScreen, LocaleKeys.KeyPrintScreen },
{ Key.Pause, LocaleKeys.KeyPause },
{ Key.NumLock, LocaleKeys.KeyNumLock },
{ Key.Clear, LocaleKeys.KeyClear },
{ Key.Keypad0, LocaleKeys.KeyKeypad0 },
{ Key.Keypad1, LocaleKeys.KeyKeypad1 },
{ Key.Keypad2, LocaleKeys.KeyKeypad2 },
{ Key.Keypad3, LocaleKeys.KeyKeypad3 },
{ Key.Keypad4, LocaleKeys.KeyKeypad4 },
{ Key.Keypad5, LocaleKeys.KeyKeypad5 },
{ Key.Keypad6, LocaleKeys.KeyKeypad6 },
{ Key.Keypad7, LocaleKeys.KeyKeypad7 },
{ Key.Keypad8, LocaleKeys.KeyKeypad8 },
{ Key.Keypad9, LocaleKeys.KeyKeypad9 },
{ Key.KeypadDivide, LocaleKeys.KeyKeypadDivide },
{ Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply },
{ Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract },
{ Key.KeypadAdd, LocaleKeys.KeyKeypadAdd },
{ Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal },
{ Key.KeypadEnter, LocaleKeys.KeyKeypadEnter },
{ Key.Number0, LocaleKeys.KeyNumber0 },
{ Key.Number1, LocaleKeys.KeyNumber1 },
{ Key.Number2, LocaleKeys.KeyNumber2 },
{ Key.Number3, LocaleKeys.KeyNumber3 },
{ Key.Number4, LocaleKeys.KeyNumber4 },
{ Key.Number5, LocaleKeys.KeyNumber5 },
{ Key.Number6, LocaleKeys.KeyNumber6 },
{ Key.Number7, LocaleKeys.KeyNumber7 },
{ Key.Number8, LocaleKeys.KeyNumber8 },
{ Key.Number9, LocaleKeys.KeyNumber9 },
{ Key.Tilde, LocaleKeys.KeyTilde },
{ Key.Grave, LocaleKeys.KeyGrave },
{ Key.Minus, LocaleKeys.KeyMinus },
{ Key.Plus, LocaleKeys.KeyPlus },
{ Key.BracketLeft, LocaleKeys.KeyBracketLeft },
{ Key.BracketRight, LocaleKeys.KeyBracketRight },
{ Key.Semicolon, LocaleKeys.KeySemicolon },
{ Key.Quote, LocaleKeys.KeyQuote },
{ Key.Comma, LocaleKeys.KeyComma },
{ Key.Period, LocaleKeys.KeyPeriod },
{ Key.Slash, LocaleKeys.KeySlash },
{ Key.BackSlash, LocaleKeys.KeyBackSlash },
{ Key.Unbound, LocaleKeys.KeyUnbound },
};
private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new()
{
{ GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick },
{ GamepadInputId.RightStick, LocaleKeys.GamepadRightStick },
{ GamepadInputId.LeftShoulder, LocaleKeys.GamepadLeftShoulder },
{ GamepadInputId.RightShoulder, LocaleKeys.GamepadRightShoulder },
{ GamepadInputId.LeftTrigger, LocaleKeys.GamepadLeftTrigger },
{ GamepadInputId.RightTrigger, LocaleKeys.GamepadRightTrigger },
{ GamepadInputId.DpadUp, LocaleKeys.GamepadDpadUp},
{ GamepadInputId.DpadDown, LocaleKeys.GamepadDpadDown},
{ GamepadInputId.DpadLeft, LocaleKeys.GamepadDpadLeft},
{ GamepadInputId.DpadRight, LocaleKeys.GamepadDpadRight},
{ GamepadInputId.Minus, LocaleKeys.GamepadMinus},
{ GamepadInputId.Plus, LocaleKeys.GamepadPlus},
{ GamepadInputId.Guide, LocaleKeys.GamepadGuide},
{ GamepadInputId.Misc1, LocaleKeys.GamepadMisc1},
{ GamepadInputId.Paddle1, LocaleKeys.GamepadPaddle1},
{ GamepadInputId.Paddle2, LocaleKeys.GamepadPaddle2},
{ GamepadInputId.Paddle3, LocaleKeys.GamepadPaddle3},
{ GamepadInputId.Paddle4, LocaleKeys.GamepadPaddle4},
{ GamepadInputId.Touchpad, LocaleKeys.GamepadTouchpad},
{ GamepadInputId.SingleLeftTrigger0, LocaleKeys.GamepadSingleLeftTrigger0},
{ GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0},
{ GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1},
{ GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1},
{ GamepadInputId.Unbound, LocaleKeys.KeyUnbound},
};
private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new()
{
{ StickInputId.Left, LocaleKeys.StickLeft},
{ StickInputId.Right, LocaleKeys.StickRight},
{ StickInputId.Unbound, LocaleKeys.KeyUnbound},
};
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
string keyString = "";
if (value is Key key)
{
return null;
if (_keysMap.TryGetValue(key, out LocaleKeys localeKey))
{
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = key.ToString();
}
}
else if (value is GamepadInputId gamepadInputId)
{
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out LocaleKeys localeKey))
{
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = gamepadInputId.ToString();
}
}
else if (value is StickInputId stickInputId)
{
if (_stickInputIdMap.TryGetValue(stickInputId, out LocaleKeys localeKey))
{
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = stickInputId.ToString();
}
}
return value.ToString();
return keyString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
object key = null;
if (value != null)
{
if (targetType == typeof(Key))
{
key = Enum.Parse<Key>(value.ToString());
}
else if (targetType == typeof(GamepadInputId))
{
key = Enum.Parse<GamepadInputId>(value.ToString());
}
else if (targetType == typeof(StickInputId))
{
key = Enum.Parse<StickInputId>(value.ToString());
}
}
return key;
throw new NotSupportedException();
}
}
}

View File

@ -0,0 +1,580 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using System;
namespace Ryujinx.Ava.UI.Models.Input
{
public class ControllerInputConfig : BaseModel
{
public bool EnableCemuHookMotion { get; set; }
public string DsuServerHost { get; set; }
public int DsuServerPort { get; set; }
public int Slot { get; set; }
public int AltSlot { get; set; }
public bool MirrorInput { get; set; }
public int Sensitivity { get; set; }
public double GyroDeadzone { get; set; }
public float WeakRumble { get; set; }
public float StrongRumble { get; set; }
public string Id { get; set; }
public ControllerType ControllerType { get; set; }
public PlayerIndex PlayerIndex { get; set; }
private StickInputId _leftJoystick;
public StickInputId LeftJoystick
{
get => _leftJoystick;
set
{
_leftJoystick = value;
OnPropertyChanged();
}
}
private bool _leftInvertStickX;
public bool LeftInvertStickX
{
get => _leftInvertStickX;
set
{
_leftInvertStickX = value;
OnPropertyChanged();
}
}
private bool _leftInvertStickY;
public bool LeftInvertStickY
{
get => _leftInvertStickY;
set
{
_leftInvertStickY = value;
OnPropertyChanged();
}
}
private bool _leftRotate90;
public bool LeftRotate90
{
get => _leftRotate90;
set
{
_leftRotate90 = value;
OnPropertyChanged();
}
}
private GamepadInputId _leftStickButton;
public GamepadInputId LeftStickButton
{
get => _leftStickButton;
set
{
_leftStickButton = value;
OnPropertyChanged();
}
}
private StickInputId _rightJoystick;
public StickInputId RightJoystick
{
get => _rightJoystick;
set
{
_rightJoystick = value;
OnPropertyChanged();
}
}
private bool _rightInvertStickX;
public bool RightInvertStickX
{
get => _rightInvertStickX;
set
{
_rightInvertStickX = value;
OnPropertyChanged();
}
}
private bool _rightInvertStickY;
public bool RightInvertStickY
{
get => _rightInvertStickY;
set
{
_rightInvertStickY = value;
OnPropertyChanged();
}
}
private bool _rightRotate90;
public bool RightRotate90
{
get => _rightRotate90;
set
{
_rightRotate90 = value;
OnPropertyChanged();
}
}
private GamepadInputId _rightStickButton;
public GamepadInputId RightStickButton
{
get => _rightStickButton;
set
{
_rightStickButton = value;
OnPropertyChanged();
}
}
private GamepadInputId _dpadUp;
public GamepadInputId DpadUp
{
get => _dpadUp;
set
{
_dpadUp = value;
OnPropertyChanged();
}
}
private GamepadInputId _dpadDown;
public GamepadInputId DpadDown
{
get => _dpadDown;
set
{
_dpadDown = value;
OnPropertyChanged();
}
}
private GamepadInputId _dpadLeft;
public GamepadInputId DpadLeft
{
get => _dpadLeft;
set
{
_dpadLeft = value;
OnPropertyChanged();
}
}
private GamepadInputId _dpadRight;
public GamepadInputId DpadRight
{
get => _dpadRight;
set
{
_dpadRight = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonL;
public GamepadInputId ButtonL
{
get => _buttonL;
set
{
_buttonL = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonMinus;
public GamepadInputId ButtonMinus
{
get => _buttonMinus;
set
{
_buttonMinus = value;
OnPropertyChanged();
}
}
private GamepadInputId _leftButtonSl;
public GamepadInputId LeftButtonSl
{
get => _leftButtonSl;
set
{
_leftButtonSl = value;
OnPropertyChanged();
}
}
private GamepadInputId _leftButtonSr;
public GamepadInputId LeftButtonSr
{
get => _leftButtonSr;
set
{
_leftButtonSr = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonZl;
public GamepadInputId ButtonZl
{
get => _buttonZl;
set
{
_buttonZl = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonA;
public GamepadInputId ButtonA
{
get => _buttonA;
set
{
_buttonA = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonB;
public GamepadInputId ButtonB
{
get => _buttonB;
set
{
_buttonB = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonX;
public GamepadInputId ButtonX
{
get => _buttonX;
set
{
_buttonX = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonY;
public GamepadInputId ButtonY
{
get => _buttonY;
set
{
_buttonY = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonR;
public GamepadInputId ButtonR
{
get => _buttonR;
set
{
_buttonR = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonPlus;
public GamepadInputId ButtonPlus
{
get => _buttonPlus;
set
{
_buttonPlus = value;
OnPropertyChanged();
}
}
private GamepadInputId _rightButtonSl;
public GamepadInputId RightButtonSl
{
get => _rightButtonSl;
set
{
_rightButtonSl = value;
OnPropertyChanged();
}
}
private GamepadInputId _rightButtonSr;
public GamepadInputId RightButtonSr
{
get => _rightButtonSr;
set
{
_rightButtonSr = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonZr;
public GamepadInputId ButtonZr
{
get => _buttonZr;
set
{
_buttonZr = value;
OnPropertyChanged();
}
}
private float _deadzoneLeft;
public float DeadzoneLeft
{
get => _deadzoneLeft;
set
{
_deadzoneLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private float _deadzoneRight;
public float DeadzoneRight
{
get => _deadzoneRight;
set
{
_deadzoneRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private float _rangeLeft;
public float RangeLeft
{
get => _rangeLeft;
set
{
_rangeLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private float _rangeRight;
public float RangeRight
{
get => _rangeRight;
set
{
_rangeRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private float _triggerThreshold;
public float TriggerThreshold
{
get => _triggerThreshold;
set
{
_triggerThreshold = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private bool _enableMotion;
public bool EnableMotion
{
get => _enableMotion;
set
{
_enableMotion = value;
OnPropertyChanged();
}
}
private bool _enableRumble;
public bool EnableRumble
{
get => _enableRumble;
set
{
_enableRumble = value;
OnPropertyChanged();
}
}
public ControllerInputConfig(InputConfig config)
{
if (config != null)
{
Id = config.Id;
ControllerType = config.ControllerType;
PlayerIndex = config.PlayerIndex;
if (config is not StandardControllerInputConfig controllerInput)
{
return;
}
LeftJoystick = controllerInput.LeftJoyconStick.Joystick;
LeftInvertStickX = controllerInput.LeftJoyconStick.InvertStickX;
LeftInvertStickY = controllerInput.LeftJoyconStick.InvertStickY;
LeftRotate90 = controllerInput.LeftJoyconStick.Rotate90CW;
LeftStickButton = controllerInput.LeftJoyconStick.StickButton;
RightJoystick = controllerInput.RightJoyconStick.Joystick;
RightInvertStickX = controllerInput.RightJoyconStick.InvertStickX;
RightInvertStickY = controllerInput.RightJoyconStick.InvertStickY;
RightRotate90 = controllerInput.RightJoyconStick.Rotate90CW;
RightStickButton = controllerInput.RightJoyconStick.StickButton;
DpadUp = controllerInput.LeftJoycon.DpadUp;
DpadDown = controllerInput.LeftJoycon.DpadDown;
DpadLeft = controllerInput.LeftJoycon.DpadLeft;
DpadRight = controllerInput.LeftJoycon.DpadRight;
ButtonL = controllerInput.LeftJoycon.ButtonL;
ButtonMinus = controllerInput.LeftJoycon.ButtonMinus;
LeftButtonSl = controllerInput.LeftJoycon.ButtonSl;
LeftButtonSr = controllerInput.LeftJoycon.ButtonSr;
ButtonZl = controllerInput.LeftJoycon.ButtonZl;
ButtonA = controllerInput.RightJoycon.ButtonA;
ButtonB = controllerInput.RightJoycon.ButtonB;
ButtonX = controllerInput.RightJoycon.ButtonX;
ButtonY = controllerInput.RightJoycon.ButtonY;
ButtonR = controllerInput.RightJoycon.ButtonR;
ButtonPlus = controllerInput.RightJoycon.ButtonPlus;
RightButtonSl = controllerInput.RightJoycon.ButtonSl;
RightButtonSr = controllerInput.RightJoycon.ButtonSr;
ButtonZr = controllerInput.RightJoycon.ButtonZr;
DeadzoneLeft = controllerInput.DeadzoneLeft;
DeadzoneRight = controllerInput.DeadzoneRight;
RangeLeft = controllerInput.RangeLeft;
RangeRight = controllerInput.RangeRight;
TriggerThreshold = controllerInput.TriggerThreshold;
if (controllerInput.Motion != null)
{
EnableMotion = controllerInput.Motion.EnableMotion;
GyroDeadzone = controllerInput.Motion.GyroDeadzone;
Sensitivity = controllerInput.Motion.Sensitivity;
if (controllerInput.Motion is CemuHookMotionConfigController cemuHook)
{
EnableCemuHookMotion = true;
DsuServerHost = cemuHook.DsuServerHost;
DsuServerPort = cemuHook.DsuServerPort;
Slot = cemuHook.Slot;
AltSlot = cemuHook.AltSlot;
MirrorInput = cemuHook.MirrorInput;
}
}
if (controllerInput.Rumble != null)
{
EnableRumble = controllerInput.Rumble.EnableRumble;
WeakRumble = controllerInput.Rumble.WeakRumble;
StrongRumble = controllerInput.Rumble.StrongRumble;
}
}
}
public InputConfig GetConfig()
{
var config = new StandardControllerInputConfig
{
Id = Id,
Backend = InputBackendType.GamepadSDL2,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>
{
DpadUp = DpadUp,
DpadDown = DpadDown,
DpadLeft = DpadLeft,
DpadRight = DpadRight,
ButtonL = ButtonL,
ButtonMinus = ButtonMinus,
ButtonSl = LeftButtonSl,
ButtonSr = LeftButtonSr,
ButtonZl = ButtonZl
},
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>
{
ButtonA = ButtonA,
ButtonB = ButtonB,
ButtonX = ButtonX,
ButtonY = ButtonY,
ButtonPlus = ButtonPlus,
ButtonSl = RightButtonSl,
ButtonSr = RightButtonSr,
ButtonR = ButtonR,
ButtonZr = ButtonZr
},
LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = LeftJoystick,
InvertStickX = LeftInvertStickX,
InvertStickY = LeftInvertStickY,
Rotate90CW = LeftRotate90,
StickButton = LeftStickButton
},
RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = RightJoystick,
InvertStickX = RightInvertStickX,
InvertStickY = RightInvertStickY,
Rotate90CW = RightRotate90,
StickButton = RightStickButton
},
Rumble = new RumbleConfigController
{
EnableRumble = EnableRumble,
WeakRumble = WeakRumble,
StrongRumble = StrongRumble
},
Version = InputConfig.CurrentVersion,
DeadzoneLeft = DeadzoneLeft,
DeadzoneRight = DeadzoneRight,
RangeLeft = RangeLeft,
RangeRight = RangeRight,
TriggerThreshold = TriggerThreshold
};
if (EnableCemuHookMotion)
{
config.Motion = new CemuHookMotionConfigController
{
EnableMotion = EnableMotion,
MotionBackend = MotionInputBackendType.CemuHook,
GyroDeadzone = GyroDeadzone,
Sensitivity = Sensitivity,
DsuServerHost = DsuServerHost,
DsuServerPort = DsuServerPort,
Slot = Slot,
AltSlot = AltSlot,
MirrorInput = MirrorInput
};
}
else
{
config.Motion = new MotionConfigController
{
EnableMotion = EnableMotion,
MotionBackend = MotionInputBackendType.GamepadDriver,
GyroDeadzone = GyroDeadzone,
Sensitivity = Sensitivity
};
}
return config;
}
}
}

View File

@ -0,0 +1,422 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard;
namespace Ryujinx.Ava.UI.Models.Input
{
public class KeyboardInputConfig : BaseModel
{
public string Id { get; set; }
public ControllerType ControllerType { get; set; }
public PlayerIndex PlayerIndex { get; set; }
private Key _leftStickUp;
public Key LeftStickUp
{
get => _leftStickUp;
set
{
_leftStickUp = value;
OnPropertyChanged();
}
}
private Key _leftStickDown;
public Key LeftStickDown
{
get => _leftStickDown;
set
{
_leftStickDown = value;
OnPropertyChanged();
}
}
private Key _leftStickLeft;
public Key LeftStickLeft
{
get => _leftStickLeft;
set
{
_leftStickLeft = value;
OnPropertyChanged();
}
}
private Key _leftStickRight;
public Key LeftStickRight
{
get => _leftStickRight;
set
{
_leftStickRight = value;
OnPropertyChanged();
}
}
private Key _leftStickButton;
public Key LeftStickButton
{
get => _leftStickButton;
set
{
_leftStickButton = value;
OnPropertyChanged();
}
}
private Key _rightStickUp;
public Key RightStickUp
{
get => _rightStickUp;
set
{
_rightStickUp = value;
OnPropertyChanged();
}
}
private Key _rightStickDown;
public Key RightStickDown
{
get => _rightStickDown;
set
{
_rightStickDown = value;
OnPropertyChanged();
}
}
private Key _rightStickLeft;
public Key RightStickLeft
{
get => _rightStickLeft;
set
{
_rightStickLeft = value;
OnPropertyChanged();
}
}
private Key _rightStickRight;
public Key RightStickRight
{
get => _rightStickRight;
set
{
_rightStickRight = value;
OnPropertyChanged();
}
}
private Key _rightStickButton;
public Key RightStickButton
{
get => _rightStickButton;
set
{
_rightStickButton = value;
OnPropertyChanged();
}
}
private Key _dpadUp;
public Key DpadUp
{
get => _dpadUp;
set
{
_dpadUp = value;
OnPropertyChanged();
}
}
private Key _dpadDown;
public Key DpadDown
{
get => _dpadDown;
set
{
_dpadDown = value;
OnPropertyChanged();
}
}
private Key _dpadLeft;
public Key DpadLeft
{
get => _dpadLeft;
set
{
_dpadLeft = value;
OnPropertyChanged();
}
}
private Key _dpadRight;
public Key DpadRight
{
get => _dpadRight;
set
{
_dpadRight = value;
OnPropertyChanged();
}
}
private Key _buttonL;
public Key ButtonL
{
get => _buttonL;
set
{
_buttonL = value;
OnPropertyChanged();
}
}
private Key _buttonMinus;
public Key ButtonMinus
{
get => _buttonMinus;
set
{
_buttonMinus = value;
OnPropertyChanged();
}
}
private Key _leftButtonSl;
public Key LeftButtonSl
{
get => _leftButtonSl;
set
{
_leftButtonSl = value;
OnPropertyChanged();
}
}
private Key _leftButtonSr;
public Key LeftButtonSr
{
get => _leftButtonSr;
set
{
_leftButtonSr = value;
OnPropertyChanged();
}
}
private Key _buttonZl;
public Key ButtonZl
{
get => _buttonZl;
set
{
_buttonZl = value;
OnPropertyChanged();
}
}
private Key _buttonA;
public Key ButtonA
{
get => _buttonA;
set
{
_buttonA = value;
OnPropertyChanged();
}
}
private Key _buttonB;
public Key ButtonB
{
get => _buttonB;
set
{
_buttonB = value;
OnPropertyChanged();
}
}
private Key _buttonX;
public Key ButtonX
{
get => _buttonX;
set
{
_buttonX = value;
OnPropertyChanged();
}
}
private Key _buttonY;
public Key ButtonY
{
get => _buttonY;
set
{
_buttonY = value;
OnPropertyChanged();
}
}
private Key _buttonR;
public Key ButtonR
{
get => _buttonR;
set
{
_buttonR = value;
OnPropertyChanged();
}
}
private Key _buttonPlus;
public Key ButtonPlus
{
get => _buttonPlus;
set
{
_buttonPlus = value;
OnPropertyChanged();
}
}
private Key _rightButtonSl;
public Key RightButtonSl
{
get => _rightButtonSl;
set
{
_rightButtonSl = value;
OnPropertyChanged();
}
}
private Key _rightButtonSr;
public Key RightButtonSr
{
get => _rightButtonSr;
set
{
_rightButtonSr = value;
OnPropertyChanged();
}
}
private Key _buttonZr;
public Key ButtonZr
{
get => _buttonZr;
set
{
_buttonZr = value;
OnPropertyChanged();
}
}
public KeyboardInputConfig(InputConfig config)
{
if (config != null)
{
Id = config.Id;
ControllerType = config.ControllerType;
PlayerIndex = config.PlayerIndex;
if (config is not StandardKeyboardInputConfig keyboardConfig)
{
return;
}
LeftStickUp = keyboardConfig.LeftJoyconStick.StickUp;
LeftStickDown = keyboardConfig.LeftJoyconStick.StickDown;
LeftStickLeft = keyboardConfig.LeftJoyconStick.StickLeft;
LeftStickRight = keyboardConfig.LeftJoyconStick.StickRight;
LeftStickButton = keyboardConfig.LeftJoyconStick.StickButton;
RightStickUp = keyboardConfig.RightJoyconStick.StickUp;
RightStickDown = keyboardConfig.RightJoyconStick.StickDown;
RightStickLeft = keyboardConfig.RightJoyconStick.StickLeft;
RightStickRight = keyboardConfig.RightJoyconStick.StickRight;
RightStickButton = keyboardConfig.RightJoyconStick.StickButton;
DpadUp = keyboardConfig.LeftJoycon.DpadUp;
DpadDown = keyboardConfig.LeftJoycon.DpadDown;
DpadLeft = keyboardConfig.LeftJoycon.DpadLeft;
DpadRight = keyboardConfig.LeftJoycon.DpadRight;
ButtonL = keyboardConfig.LeftJoycon.ButtonL;
ButtonMinus = keyboardConfig.LeftJoycon.ButtonMinus;
LeftButtonSl = keyboardConfig.LeftJoycon.ButtonSl;
LeftButtonSr = keyboardConfig.LeftJoycon.ButtonSr;
ButtonZl = keyboardConfig.LeftJoycon.ButtonZl;
ButtonA = keyboardConfig.RightJoycon.ButtonA;
ButtonB = keyboardConfig.RightJoycon.ButtonB;
ButtonX = keyboardConfig.RightJoycon.ButtonX;
ButtonY = keyboardConfig.RightJoycon.ButtonY;
ButtonR = keyboardConfig.RightJoycon.ButtonR;
ButtonPlus = keyboardConfig.RightJoycon.ButtonPlus;
RightButtonSl = keyboardConfig.RightJoycon.ButtonSl;
RightButtonSr = keyboardConfig.RightJoycon.ButtonSr;
ButtonZr = keyboardConfig.RightJoycon.ButtonZr;
}
}
public InputConfig GetConfig()
{
var config = new StandardKeyboardInputConfig
{
Id = Id,
Backend = InputBackendType.WindowKeyboard,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = DpadUp,
DpadDown = DpadDown,
DpadLeft = DpadLeft,
DpadRight = DpadRight,
ButtonL = ButtonL,
ButtonMinus = ButtonMinus,
ButtonZl = ButtonZl,
ButtonSl = LeftButtonSl,
ButtonSr = LeftButtonSr
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = ButtonA,
ButtonB = ButtonB,
ButtonX = ButtonX,
ButtonY = ButtonY,
ButtonPlus = ButtonPlus,
ButtonSl = RightButtonSl,
ButtonSr = RightButtonSr,
ButtonR = ButtonR,
ButtonZr = ButtonZr
},
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = LeftStickUp,
StickDown = LeftStickDown,
StickRight = LeftStickRight,
StickLeft = LeftStickLeft,
StickButton = LeftStickButton
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = RightStickUp,
StickDown = RightStickDown,
StickLeft = RightStickLeft,
StickRight = RightStickRight,
StickButton = RightStickButton
},
Version = InputConfig.CurrentVersion
};
return config;
}
}
}

View File

@ -1,456 +0,0 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using System;
namespace Ryujinx.Ava.UI.Models
{
internal class InputConfiguration<TKey, TStick> : BaseModel
{
private float _deadzoneRight;
private float _triggerThreshold;
private float _deadzoneLeft;
private double _gyroDeadzone;
private int _sensitivity;
private bool _enableMotion;
private float _weakRumble;
private float _strongRumble;
private float _rangeLeft;
private float _rangeRight;
public InputBackendType Backend { get; set; }
/// <summary>
/// Controller id
/// </summary>
public string Id { get; set; }
/// <summary>
/// Controller's Type
/// </summary>
public ControllerType ControllerType { get; set; }
/// <summary>
/// Player's Index for the controller
/// </summary>
public PlayerIndex PlayerIndex { get; set; }
public TStick LeftJoystick { get; set; }
public bool LeftInvertStickX { get; set; }
public bool LeftInvertStickY { get; set; }
public bool RightRotate90 { get; set; }
public TKey LeftControllerStickButton { get; set; }
public TStick RightJoystick { get; set; }
public bool RightInvertStickX { get; set; }
public bool RightInvertStickY { get; set; }
public bool LeftRotate90 { get; set; }
public TKey RightControllerStickButton { get; set; }
public float DeadzoneLeft
{
get => _deadzoneLeft;
set
{
_deadzoneLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public float RangeLeft
{
get => _rangeLeft;
set
{
_rangeLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public float DeadzoneRight
{
get => _deadzoneRight;
set
{
_deadzoneRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public float RangeRight
{
get => _rangeRight;
set
{
_rangeRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public float TriggerThreshold
{
get => _triggerThreshold;
set
{
_triggerThreshold = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public MotionInputBackendType MotionBackend { get; set; }
public TKey ButtonMinus { get; set; }
public TKey ButtonL { get; set; }
public TKey ButtonZl { get; set; }
public TKey LeftButtonSl { get; set; }
public TKey LeftButtonSr { get; set; }
public TKey DpadUp { get; set; }
public TKey DpadDown { get; set; }
public TKey DpadLeft { get; set; }
public TKey DpadRight { get; set; }
public TKey ButtonPlus { get; set; }
public TKey ButtonR { get; set; }
public TKey ButtonZr { get; set; }
public TKey RightButtonSl { get; set; }
public TKey RightButtonSr { get; set; }
public TKey ButtonX { get; set; }
public TKey ButtonB { get; set; }
public TKey ButtonY { get; set; }
public TKey ButtonA { get; set; }
public TKey LeftStickUp { get; set; }
public TKey LeftStickDown { get; set; }
public TKey LeftStickLeft { get; set; }
public TKey LeftStickRight { get; set; }
public TKey LeftKeyboardStickButton { get; set; }
public TKey RightStickUp { get; set; }
public TKey RightStickDown { get; set; }
public TKey RightStickLeft { get; set; }
public TKey RightStickRight { get; set; }
public TKey RightKeyboardStickButton { get; set; }
public int Sensitivity
{
get => _sensitivity;
set
{
_sensitivity = value;
OnPropertyChanged();
}
}
public double GyroDeadzone
{
get => _gyroDeadzone;
set
{
_gyroDeadzone = Math.Round(value, 3);
OnPropertyChanged();
}
}
public bool EnableMotion
{
get => _enableMotion; set
{
_enableMotion = value;
OnPropertyChanged();
}
}
public bool EnableCemuHookMotion { get; set; }
public int Slot { get; set; }
public int AltSlot { get; set; }
public bool MirrorInput { get; set; }
public string DsuServerHost { get; set; }
public int DsuServerPort { get; set; }
public bool EnableRumble { get; set; }
public float WeakRumble
{
get => _weakRumble; set
{
_weakRumble = value;
OnPropertyChanged();
}
}
public float StrongRumble
{
get => _strongRumble; set
{
_strongRumble = value;
OnPropertyChanged();
}
}
public InputConfiguration(InputConfig config)
{
if (config != null)
{
Backend = config.Backend;
Id = config.Id;
ControllerType = config.ControllerType;
PlayerIndex = config.PlayerIndex;
if (config is StandardKeyboardInputConfig keyboardConfig)
{
LeftStickUp = (TKey)(object)keyboardConfig.LeftJoyconStick.StickUp;
LeftStickDown = (TKey)(object)keyboardConfig.LeftJoyconStick.StickDown;
LeftStickLeft = (TKey)(object)keyboardConfig.LeftJoyconStick.StickLeft;
LeftStickRight = (TKey)(object)keyboardConfig.LeftJoyconStick.StickRight;
LeftKeyboardStickButton = (TKey)(object)keyboardConfig.LeftJoyconStick.StickButton;
RightStickUp = (TKey)(object)keyboardConfig.RightJoyconStick.StickUp;
RightStickDown = (TKey)(object)keyboardConfig.RightJoyconStick.StickDown;
RightStickLeft = (TKey)(object)keyboardConfig.RightJoyconStick.StickLeft;
RightStickRight = (TKey)(object)keyboardConfig.RightJoyconStick.StickRight;
RightKeyboardStickButton = (TKey)(object)keyboardConfig.RightJoyconStick.StickButton;
ButtonA = (TKey)(object)keyboardConfig.RightJoycon.ButtonA;
ButtonB = (TKey)(object)keyboardConfig.RightJoycon.ButtonB;
ButtonX = (TKey)(object)keyboardConfig.RightJoycon.ButtonX;
ButtonY = (TKey)(object)keyboardConfig.RightJoycon.ButtonY;
ButtonR = (TKey)(object)keyboardConfig.RightJoycon.ButtonR;
RightButtonSl = (TKey)(object)keyboardConfig.RightJoycon.ButtonSl;
RightButtonSr = (TKey)(object)keyboardConfig.RightJoycon.ButtonSr;
ButtonZr = (TKey)(object)keyboardConfig.RightJoycon.ButtonZr;
ButtonPlus = (TKey)(object)keyboardConfig.RightJoycon.ButtonPlus;
DpadUp = (TKey)(object)keyboardConfig.LeftJoycon.DpadUp;
DpadDown = (TKey)(object)keyboardConfig.LeftJoycon.DpadDown;
DpadLeft = (TKey)(object)keyboardConfig.LeftJoycon.DpadLeft;
DpadRight = (TKey)(object)keyboardConfig.LeftJoycon.DpadRight;
ButtonMinus = (TKey)(object)keyboardConfig.LeftJoycon.ButtonMinus;
LeftButtonSl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSl;
LeftButtonSr = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSr;
ButtonZl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonZl;
ButtonL = (TKey)(object)keyboardConfig.LeftJoycon.ButtonL;
}
else if (config is StandardControllerInputConfig controllerConfig)
{
LeftJoystick = (TStick)(object)controllerConfig.LeftJoyconStick.Joystick;
LeftInvertStickX = controllerConfig.LeftJoyconStick.InvertStickX;
LeftInvertStickY = controllerConfig.LeftJoyconStick.InvertStickY;
LeftRotate90 = controllerConfig.LeftJoyconStick.Rotate90CW;
LeftControllerStickButton = (TKey)(object)controllerConfig.LeftJoyconStick.StickButton;
RightJoystick = (TStick)(object)controllerConfig.RightJoyconStick.Joystick;
RightInvertStickX = controllerConfig.RightJoyconStick.InvertStickX;
RightInvertStickY = controllerConfig.RightJoyconStick.InvertStickY;
RightRotate90 = controllerConfig.RightJoyconStick.Rotate90CW;
RightControllerStickButton = (TKey)(object)controllerConfig.RightJoyconStick.StickButton;
ButtonA = (TKey)(object)controllerConfig.RightJoycon.ButtonA;
ButtonB = (TKey)(object)controllerConfig.RightJoycon.ButtonB;
ButtonX = (TKey)(object)controllerConfig.RightJoycon.ButtonX;
ButtonY = (TKey)(object)controllerConfig.RightJoycon.ButtonY;
ButtonR = (TKey)(object)controllerConfig.RightJoycon.ButtonR;
RightButtonSl = (TKey)(object)controllerConfig.RightJoycon.ButtonSl;
RightButtonSr = (TKey)(object)controllerConfig.RightJoycon.ButtonSr;
ButtonZr = (TKey)(object)controllerConfig.RightJoycon.ButtonZr;
ButtonPlus = (TKey)(object)controllerConfig.RightJoycon.ButtonPlus;
DpadUp = (TKey)(object)controllerConfig.LeftJoycon.DpadUp;
DpadDown = (TKey)(object)controllerConfig.LeftJoycon.DpadDown;
DpadLeft = (TKey)(object)controllerConfig.LeftJoycon.DpadLeft;
DpadRight = (TKey)(object)controllerConfig.LeftJoycon.DpadRight;
ButtonMinus = (TKey)(object)controllerConfig.LeftJoycon.ButtonMinus;
LeftButtonSl = (TKey)(object)controllerConfig.LeftJoycon.ButtonSl;
LeftButtonSr = (TKey)(object)controllerConfig.LeftJoycon.ButtonSr;
ButtonZl = (TKey)(object)controllerConfig.LeftJoycon.ButtonZl;
ButtonL = (TKey)(object)controllerConfig.LeftJoycon.ButtonL;
DeadzoneLeft = controllerConfig.DeadzoneLeft;
DeadzoneRight = controllerConfig.DeadzoneRight;
RangeLeft = controllerConfig.RangeLeft;
RangeRight = controllerConfig.RangeRight;
TriggerThreshold = controllerConfig.TriggerThreshold;
if (controllerConfig.Motion != null)
{
EnableMotion = controllerConfig.Motion.EnableMotion;
MotionBackend = controllerConfig.Motion.MotionBackend;
GyroDeadzone = controllerConfig.Motion.GyroDeadzone;
Sensitivity = controllerConfig.Motion.Sensitivity;
if (controllerConfig.Motion is CemuHookMotionConfigController cemuHook)
{
EnableCemuHookMotion = true;
DsuServerHost = cemuHook.DsuServerHost;
DsuServerPort = cemuHook.DsuServerPort;
Slot = cemuHook.Slot;
AltSlot = cemuHook.AltSlot;
MirrorInput = cemuHook.MirrorInput;
}
if (controllerConfig.Rumble != null)
{
EnableRumble = controllerConfig.Rumble.EnableRumble;
WeakRumble = controllerConfig.Rumble.WeakRumble;
StrongRumble = controllerConfig.Rumble.StrongRumble;
}
}
}
}
}
public InputConfiguration()
{
}
public InputConfig GetConfig()
{
if (Backend == InputBackendType.WindowKeyboard)
{
return new StandardKeyboardInputConfig
{
Id = Id,
Backend = Backend,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = (Key)(object)DpadUp,
DpadDown = (Key)(object)DpadDown,
DpadLeft = (Key)(object)DpadLeft,
DpadRight = (Key)(object)DpadRight,
ButtonL = (Key)(object)ButtonL,
ButtonZl = (Key)(object)ButtonZl,
ButtonSl = (Key)(object)LeftButtonSl,
ButtonSr = (Key)(object)LeftButtonSr,
ButtonMinus = (Key)(object)ButtonMinus,
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = (Key)(object)ButtonA,
ButtonB = (Key)(object)ButtonB,
ButtonX = (Key)(object)ButtonX,
ButtonY = (Key)(object)ButtonY,
ButtonPlus = (Key)(object)ButtonPlus,
ButtonSl = (Key)(object)RightButtonSl,
ButtonSr = (Key)(object)RightButtonSr,
ButtonR = (Key)(object)ButtonR,
ButtonZr = (Key)(object)ButtonZr,
},
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = (Key)(object)LeftStickUp,
StickDown = (Key)(object)LeftStickDown,
StickRight = (Key)(object)LeftStickRight,
StickLeft = (Key)(object)LeftStickLeft,
StickButton = (Key)(object)LeftKeyboardStickButton,
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = (Key)(object)RightStickUp,
StickDown = (Key)(object)RightStickDown,
StickLeft = (Key)(object)RightStickLeft,
StickRight = (Key)(object)RightStickRight,
StickButton = (Key)(object)RightKeyboardStickButton,
},
Version = InputConfig.CurrentVersion,
};
}
if (Backend == InputBackendType.GamepadSDL2)
{
var config = new StandardControllerInputConfig
{
Id = Id,
Backend = Backend,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>
{
DpadUp = (GamepadInputId)(object)DpadUp,
DpadDown = (GamepadInputId)(object)DpadDown,
DpadLeft = (GamepadInputId)(object)DpadLeft,
DpadRight = (GamepadInputId)(object)DpadRight,
ButtonL = (GamepadInputId)(object)ButtonL,
ButtonZl = (GamepadInputId)(object)ButtonZl,
ButtonSl = (GamepadInputId)(object)LeftButtonSl,
ButtonSr = (GamepadInputId)(object)LeftButtonSr,
ButtonMinus = (GamepadInputId)(object)ButtonMinus,
},
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>
{
ButtonA = (GamepadInputId)(object)ButtonA,
ButtonB = (GamepadInputId)(object)ButtonB,
ButtonX = (GamepadInputId)(object)ButtonX,
ButtonY = (GamepadInputId)(object)ButtonY,
ButtonPlus = (GamepadInputId)(object)ButtonPlus,
ButtonSl = (GamepadInputId)(object)RightButtonSl,
ButtonSr = (GamepadInputId)(object)RightButtonSr,
ButtonR = (GamepadInputId)(object)ButtonR,
ButtonZr = (GamepadInputId)(object)ButtonZr,
},
LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = (StickInputId)(object)LeftJoystick,
InvertStickX = LeftInvertStickX,
InvertStickY = LeftInvertStickY,
Rotate90CW = LeftRotate90,
StickButton = (GamepadInputId)(object)LeftControllerStickButton,
},
RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = (StickInputId)(object)RightJoystick,
InvertStickX = RightInvertStickX,
InvertStickY = RightInvertStickY,
Rotate90CW = RightRotate90,
StickButton = (GamepadInputId)(object)RightControllerStickButton,
},
Rumble = new RumbleConfigController
{
EnableRumble = EnableRumble,
WeakRumble = WeakRumble,
StrongRumble = StrongRumble,
},
Version = InputConfig.CurrentVersion,
DeadzoneLeft = DeadzoneLeft,
DeadzoneRight = DeadzoneRight,
RangeLeft = RangeLeft,
RangeRight = RangeRight,
TriggerThreshold = TriggerThreshold,
Motion = EnableCemuHookMotion
? new CemuHookMotionConfigController
{
DsuServerHost = DsuServerHost,
DsuServerPort = DsuServerPort,
Slot = Slot,
AltSlot = AltSlot,
MirrorInput = MirrorInput,
MotionBackend = MotionInputBackendType.CemuHook,
}
: new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
},
};
config.Motion.Sensitivity = Sensitivity;
config.Motion.EnableMotion = EnableMotion;
config.Motion.GyroDeadzone = GyroDeadzone;
return config;
}
return null;
}
}
}

View File

@ -327,7 +327,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image;
string usageString = "";
StringBuilder usageStringBuilder = new();
for (int i = 0; i < _amiiboList.Count; i++)
{
@ -341,20 +341,19 @@ namespace Ryujinx.Ava.UI.ViewModels
{
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
{
usageString += Environment.NewLine +
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
writable = usageItem.Write;
}
}
}
if (usageString.Length == 0)
if (usageStringBuilder.Length == 0)
{
usageString = LocaleManager.Instance[LocaleKeys.Unknown] + ".";
usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}.");
}
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageString}";
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}";
}
}

View File

@ -212,9 +212,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
}
}
MimeTypes = new[] { "application/x-nx-nsp" },
},
},
});
foreach (var file in result)

View File

@ -0,0 +1,84 @@
using Avalonia.Svg.Skia;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class ControllerInputViewModel : BaseModel
{
private ControllerInputConfig _config;
public ControllerInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private bool _isLeft;
public bool IsLeft
{
get => _isLeft;
set
{
_isLeft = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSides));
}
}
private bool _isRight;
public bool IsRight
{
get => _isRight;
set
{
_isRight = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSides));
}
}
public bool HasSides => IsLeft ^ IsRight;
private SvgImage _image;
public SvgImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
public InputViewModel parentModel;
public ControllerInputViewModel(InputViewModel model, ControllerInputConfig config)
{
parentModel = model;
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
Config = config;
}
public async void ShowMotionConfig()
{
await MotionInputView.Show(this);
}
public async void ShowRumbleConfig()
{
await RumbleInputView.Show(this);
}
public void OnParentModelChanged()
{
IsLeft = parentModel.IsLeft;
IsRight = parentModel.IsRight;
Image = parentModel.Image;
}
}
}

View File

@ -8,7 +8,7 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@ -30,9 +30,9 @@ using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.Gamepad
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.ViewModels
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class ControllerInputViewModel : BaseModel, IDisposable
public class InputViewModel : BaseModel, IDisposable
{
private const string Disabled = "disabled";
private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg";
@ -48,7 +48,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _controllerNumber;
private string _controllerImage;
private int _device;
private object _configuration;
private object _configViewModel;
private string _profileName;
private bool _isLoaded;
@ -71,13 +71,14 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsLeft { get; set; }
public bool IsModified { get; set; }
public event Action NotifyChangesEvent;
public object Configuration
public object ConfigViewModel
{
get => _configuration;
get => _configViewModel;
set
{
_configuration = value;
_configViewModel = value;
OnPropertyChanged();
}
@ -232,7 +233,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public InputConfig Config { get; set; }
public ControllerInputViewModel(UserControl owner) : this()
public InputViewModel(UserControl owner) : this()
{
if (Program.PreviewerDetached)
{
@ -244,7 +245,6 @@ namespace Ryujinx.Ava.UI.ViewModels
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
_isLoaded = false;
@ -255,7 +255,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public ControllerInputViewModel()
public InputViewModel()
{
PlayerIndexes = new ObservableCollection<PlayerModel>();
Controllers = new ObservableCollection<ControllerModel>();
@ -282,12 +282,12 @@ namespace Ryujinx.Ava.UI.ViewModels
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
{
Configuration = new InputConfiguration<Key, ConfigStickInputId>(keyboardInputConfig);
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig));
}
if (Config is StandardControllerInputConfig controllerInputConfig)
{
Configuration = new InputConfiguration<ConfigGamepadInputId, ConfigStickInputId>(controllerInputConfig);
ConfigViewModel = new ControllerInputViewModel(this, new ControllerInputConfig(controllerInputConfig));
}
}
@ -323,16 +323,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public async void ShowMotionConfig()
{
await MotionInputView.Show(this);
}
public async void ShowRumbleConfig()
{
await RumbleInputView.Show(this);
}
private void LoadInputDriver()
{
if (_device < 0)
@ -740,7 +730,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return;
}
if (Configuration == null)
if (ConfigViewModel == null)
{
return;
}
@ -751,35 +741,37 @@ namespace Ryujinx.Ava.UI.ViewModels
return;
}
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
if (validFileName)
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
InputConfig config = null;
if (IsKeyboard)
{
config = (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig();
}
else if (IsController)
{
config = (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
}
config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
await File.WriteAllTextAsync(path, jsonString);
LoadProfiles();
}
else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
if (validFileName)
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
InputConfig config = null;
if (IsKeyboard)
{
config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
}
else if (IsController)
{
config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
}
config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
await File.WriteAllTextAsync(path, jsonString);
LoadProfiles();
}
else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
}
}
}
@ -830,18 +822,18 @@ namespace Ryujinx.Ava.UI.ViewModels
if (device.Type == DeviceType.Keyboard)
{
var inputConfig = Configuration as InputConfiguration<Key, ConfigStickInputId>;
var inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
inputConfig.Id = device.Id;
}
else
{
var inputConfig = Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>;
var inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
inputConfig.Id = device.Id.Split(" ")[0];
}
var config = !IsController
? (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig()
: (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig()
: (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
config.ControllerType = Controllers[_controller].Type;
config.PlayerIndex = _playerId;
@ -872,12 +864,13 @@ namespace Ryujinx.Ava.UI.ViewModels
public void NotifyChanges()
{
OnPropertyChanged(nameof(Configuration));
OnPropertyChanged(nameof(ConfigViewModel));
OnPropertyChanged(nameof(IsController));
OnPropertyChanged(nameof(ShowSettings));
OnPropertyChanged(nameof(IsKeyboard));
OnPropertyChanged(nameof(IsRight));
OnPropertyChanged(nameof(IsLeft));
NotifyChangesEvent?.Invoke();
}
public void Dispose()

View File

@ -0,0 +1,73 @@
using Avalonia.Svg.Skia;
using Ryujinx.Ava.UI.Models.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class KeyboardInputViewModel : BaseModel
{
private KeyboardInputConfig _config;
public KeyboardInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private bool _isLeft;
public bool IsLeft
{
get => _isLeft;
set
{
_isLeft = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSides));
}
}
private bool _isRight;
public bool IsRight
{
get => _isRight;
set
{
_isRight = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSides));
}
}
public bool HasSides => IsLeft ^ IsRight;
private SvgImage _image;
public SvgImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
public InputViewModel parentModel;
public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config)
{
parentModel = model;
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
Config = config;
}
public void OnParentModelChanged()
{
IsLeft = parentModel.IsLeft;
IsRight = parentModel.IsRight;
Image = parentModel.Image;
}
}
}

View File

@ -1,4 +1,4 @@
namespace Ryujinx.Ava.UI.ViewModels
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class MotionInputViewModel : BaseModel
{

View File

@ -1,4 +1,4 @@
namespace Ryujinx.Ava.UI.ViewModels
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class RumbleInputViewModel : BaseModel
{

View File

@ -26,6 +26,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.Ui;
using Ryujinx.Input.HLE;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
@ -39,7 +40,6 @@ 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 MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
@ -356,6 +356,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild();
public string LoadHeading
{
get => _loadHeading;
@ -1068,9 +1070,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Logger.Error?.Print(LogClass.Application, ex.ToString());
static async void Action() => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys);
Dispatcher.UIThread.Post(Action);
await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys);
}
}
catch (Exception ex)
@ -1163,16 +1163,13 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost?.DisposeContext();
}
private void HandleRelaunch()
private async Task HandleRelaunch()
{
if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart)
{
UserChannelPersistence.ShouldRestart = false;
Dispatcher.UIThread.Post(() =>
{
LoadApplication(_currentEmulatedGamePath);
});
await LoadApplication(_currentEmulatedGamePath);
}
else
{
@ -1191,7 +1188,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Application.Current.Styles.TryGetResource(args.VSyncEnabled
? "VsyncEnabled"
: "VsyncDisabled",
Avalonia.Application.Current.ActualThemeVariant,
Application.Current.ActualThemeVariant,
out object color);
if (color is not null)
@ -1283,7 +1280,12 @@ namespace Ryujinx.Ava.UI.ViewModels
Glyph = Glyph.Grid;
}
public async void InstallFirmwareFromFile()
public void SetAspectRatio(AspectRatio aspectRatio)
{
ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
}
public async Task InstallFirmwareFromFile()
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
@ -1294,21 +1296,21 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Patterns = new[] { "*.xci", "*.zip" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" },
MimeTypes = new[] { "application/x-nx-xci", "application/zip" }
MimeTypes = new[] { "application/x-nx-xci", "application/zip" },
},
new("XCI")
{
Patterns = new[] { "*.xci" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
MimeTypes = new[] { "application/x-nx-xci" }
MimeTypes = new[] { "application/x-nx-xci" },
},
new("ZIP")
{
Patterns = new[] { "*.zip" },
AppleUniformTypeIdentifiers = new[] { "public.zip-archive" },
MimeTypes = new[] { "application/zip" }
MimeTypes = new[] { "application/zip" },
},
}
},
});
if (result.Count > 0)
@ -1317,11 +1319,11 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public async void InstallFirmwareFromFolder()
public async Task InstallFirmwareFromFolder()
{
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false
AllowMultiple = false,
});
if (result.Count > 0)
@ -1352,7 +1354,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public async void ExitCurrentState()
public async Task ExitCurrentState()
{
if (WindowState == WindowState.FullScreen)
{
@ -1377,7 +1379,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public async void ManageProfiles()
public async Task ManageProfiles()
{
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
}
@ -1387,7 +1389,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost.Device.System.SimulateWakeUpMessage();
}
public async void OpenFile()
public async Task OpenFile()
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
@ -1404,7 +1406,7 @@ namespace Ryujinx.Ava.UI.ViewModels
"com.ryujinx.xci",
"com.ryujinx.nca",
"com.ryujinx.nro",
"com.ryujinx.nso"
"com.ryujinx.nso",
},
MimeTypes = new[]
{
@ -1412,63 +1414,63 @@ namespace Ryujinx.Ava.UI.ViewModels
"application/x-nx-xci",
"application/x-nx-nca",
"application/x-nx-nro",
"application/x-nx-nso"
}
"application/x-nx-nso",
},
},
new("NSP")
{
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
MimeTypes = new[] { "application/x-nx-nsp" },
},
new("XCI")
{
Patterns = new[] { "*.xci" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
MimeTypes = new[] { "application/x-nx-xci" }
MimeTypes = new[] { "application/x-nx-xci" },
},
new("NCA")
{
Patterns = new[] { "*.nca" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" },
MimeTypes = new[] { "application/x-nx-nca" }
MimeTypes = new[] { "application/x-nx-nca" },
},
new("NRO")
{
Patterns = new[] { "*.nro" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" },
MimeTypes = new[] { "application/x-nx-nro" }
MimeTypes = new[] { "application/x-nx-nro" },
},
new("NSO")
{
Patterns = new[] { "*.nso" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" },
MimeTypes = new[] { "application/x-nx-nso" }
MimeTypes = new[] { "application/x-nx-nso" },
},
}
},
});
if (result.Count > 0)
{
LoadApplication(result[0].Path.LocalPath);
await LoadApplication(result[0].Path.LocalPath);
}
}
public async void OpenFolder()
public async Task OpenFolder()
{
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
AllowMultiple = false
AllowMultiple = false,
});
if (result.Count > 0)
{
LoadApplication(result[0].Path.LocalPath);
await LoadApplication(result[0].Path.LocalPath);
}
}
public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "")
public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "")
{
if (AppHost != null)
{
@ -1488,7 +1490,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime();
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path);
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
PrepareLoadScreen();
@ -1505,35 +1507,30 @@ namespace Ryujinx.Ava.UI.ViewModels
this,
TopLevel);
async void Action()
if (!await AppHost.LoadGuestApplication())
{
if (!await AppHost.LoadGuestApplication())
{
AppHost.DisposeContext();
AppHost = null;
AppHost.DisposeContext();
AppHost = null;
return;
}
CanUpdate = false;
LoadHeading = TitleName = titleName;
if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
return;
}
Dispatcher.UIThread.Post(Action);
CanUpdate = false;
LoadHeading = TitleName = titleName;
if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
}
public void SwitchToRenderer(bool startFullscreen)
@ -1596,7 +1593,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsGameRunning = false;
Dispatcher.UIThread.InvokeAsync(() =>
Dispatcher.UIThread.InvokeAsync(async () =>
{
ShowMenuAndStatusBar = true;
ShowContent = true;
@ -1609,7 +1606,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost = null;
HandleRelaunch();
await HandleRelaunch();
});
RendererHostControl.WindowCreated -= RendererHost_Created;
@ -1701,7 +1698,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
#endregion
}
}

View File

@ -10,6 +10,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Vulkan;
@ -54,6 +55,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public event Action CloseWindow;
public event Action SaveSettingsEvent;
private int _networkInterfaceIndex;
private int _multiplayerModeIndex;
public int ResolutionScale
{
@ -76,14 +78,13 @@ namespace Ryujinx.Ava.UI.ViewModels
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
"",
"",
LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]);
});
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle])
);
}
OnPropertyChanged();
@ -146,6 +147,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableTextureRecompression { get; set; }
public bool EnableMacroHLE { get; set; }
public bool EnableColorSpacePassthrough { get; set; }
public bool ColorSpacePassthroughAvailable => IsMacOS;
public bool EnableFileLog { get; set; }
public bool EnableStub { get; set; }
public bool EnableInfo { get; set; }
@ -251,6 +253,11 @@ namespace Ryujinx.Ava.UI.ViewModels
get => new(_networkInterfaces.Keys);
}
public AvaloniaList<string> MultiplayerModes
{
get => new(Enum.GetNames<MultiplayerMode>());
}
public KeyboardHotkeys KeyboardHotkeys
{
get => _keyboardHotkeys;
@ -272,6 +279,16 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public int MultiplayerModeIndex
{
get => _multiplayerModeIndex;
set
{
_multiplayerModeIndex = value;
ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex;
}
}
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{
_virtualFileSystem = virtualFileSystem;
@ -478,6 +495,8 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
}
public void SaveSettings()
@ -579,6 +598,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);

View File

@ -22,6 +22,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers;
@ -184,18 +185,12 @@ namespace Ryujinx.Ava.UI.ViewModels
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
});
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
}
}
catch (Exception ex)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
});
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)));
}
}
}
@ -207,7 +202,7 @@ namespace Ryujinx.Ava.UI.ViewModels
SortUpdates();
}
public async void Add()
public async Task Add()
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
@ -218,9 +213,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
}
}
MimeTypes = new[] { "application/x-nx-nsp" },
},
},
});
foreach (var file in result)

View File

@ -1,12 +1,11 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
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:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
@ -14,6 +13,7 @@
d:DesignWidth="800"
x:Class="Ryujinx.Ava.UI.Views.Input.ControllerInputView"
x:DataType="viewModels:ControllerInputViewModel"
x:CompileBindings="True"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
@ -33,191 +33,10 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical">
<StackPanel
Margin="0 0 0 5"
Orientation="Vertical"
Spacing="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Player Selection -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsPlayer}" />
<ComboBox
Grid.Column="1"
Name="PlayerIndexBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectionChanged="PlayerIndexBox_OnSelectionChanged"
ItemsSource="{Binding PlayerIndexes}"
SelectedIndex="{Binding PlayerId}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<!-- Profile Selection -->
<Grid
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsProfile}" />
<ui:FAComboBox
Grid.Column="1"
IsEditable="True"
Name="ProfileBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectedIndex="0"
ItemsSource="{Binding ProfilesList}"
Text="{Binding ProfileName, Mode=TwoWay}" />
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}"
Command="{ReflectionBinding LoadProfile}">
<ui:SymbolIcon
Symbol="Upload"
FontSize="15"
Height="20" />
</Button>
<Button
Grid.Column="3"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}"
Command="{ReflectionBinding SaveProfile}">
<ui:SymbolIcon
Symbol="Save"
FontSize="15"
Height="20" />
</Button>
<Button
Grid.Column="4"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}"
Command="{ReflectionBinding RemoveProfile}">
<ui:SymbolIcon
Symbol="Delete"
FontSize="15"
Height="20" />
</Button>
</Grid>
</Grid>
<Separator />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Input Device -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsInputDevice}" />
<ComboBox
Grid.Column="1"
Name="DeviceBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
ItemsSource="{Binding DeviceList}"
SelectedIndex="{Binding Device}" />
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
Command="{ReflectionBinding LoadDevices}">
<ui:SymbolIcon
Symbol="Refresh"
FontSize="15"
Height="20"/>
</Button>
</Grid>
<!-- Controller Type -->
<Grid
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsControllerType}" />
<ComboBox
Grid.Column="1"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Controllers}"
SelectedIndex="{Binding Controller}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="models:ControllerModel">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</StackPanel>
<!-- Button / JoyStick Settings -->
<Grid
Name="SettingButtons"
MinHeight="450"
IsVisible="{Binding ShowSettings}">
MinHeight="450">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
@ -256,9 +75,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZL}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonZl">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonZl, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonZl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -272,9 +91,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerL}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonL">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonL, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonL, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -288,9 +107,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonMinus}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonMinus">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonMinus, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonMinus, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -310,100 +129,8 @@
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsLStick}" />
<!-- Left Joystick Keyboard -->
<StackPanel
IsVisible="{Binding !IsController}"
Orientation="Vertical">
<!-- Left Joystick Button -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftStickUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftStickDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftStickLeft, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftStickRight, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
<!-- Left Joystick Controller -->
<StackPanel
IsVisible="{Binding IsController}"
Orientation="Vertical">
<StackPanel Orientation="Vertical">
<!-- Left Joystick Button -->
<StackPanel
Orientation="Horizontal">
@ -414,9 +141,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="LeftStickButton">
<TextBlock
Text="{ReflectionBinding Configuration.LeftControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.LeftStickButton, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -431,22 +158,22 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickStick}"
TextAlignment="Center" />
<ToggleButton Tag="stick">
<ToggleButton Name="LeftJoystick" Tag="stick">
<TextBlock
Text="{ReflectionBinding Configuration.LeftJoystick, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.LeftJoystick, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<Separator
Margin="0,8,0,8"
Height="1" />
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickX}">
<CheckBox IsChecked="{Binding Config.LeftInvertStickX}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" />
</CheckBox>
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickY}">
<CheckBox IsChecked="{Binding Config.LeftInvertStickY}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" />
</CheckBox>
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftRotate90}">
<CheckBox IsChecked="{Binding Config.LeftRotate90}">
<TextBlock Text="{locale:Locale ControllerSettingsRotate90}" />
</CheckBox>
<Separator
@ -460,18 +187,18 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="1"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
SmallChange="0.01"
Minimum="0"
Value="{ReflectionBinding Configuration.DeadzoneLeft, Mode=TwoWay}" />
Value="{Binding Config.DeadzoneLeft, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Width="25"
Text="{ReflectionBinding Configuration.DeadzoneLeft, StringFormat=\{0:0.00\}}" />
Text="{Binding Config.DeadzoneLeft, StringFormat=\{0:0.00\}}" />
</StackPanel>
<TextBlock
HorizontalAlignment="Center"
@ -480,18 +207,18 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="2"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
SmallChange="0.01"
Minimum="0"
Value="{ReflectionBinding Configuration.RangeLeft, Mode=TwoWay}" />
Value="{Binding Config.RangeLeft, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Width="25"
Text="{ReflectionBinding Configuration.RangeLeft, StringFormat=\{0:0.00\}}" />
Text="{Binding Config.RangeLeft, StringFormat=\{0:0.00\}}" />
</StackPanel>
</StackPanel>
</StackPanel>
@ -524,9 +251,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadUp}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="DpadUp">
<TextBlock
Text="{ReflectionBinding Configuration.DpadUp, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.DpadUp, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -541,9 +268,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadDown}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="DpadDown">
<TextBlock
Text="{ReflectionBinding Configuration.DpadDown, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.DpadDown, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -558,9 +285,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadLeft}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="DpadLeft">
<TextBlock
Text="{ReflectionBinding Configuration.DpadLeft, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.DpadLeft, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -575,9 +302,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadRight}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="DpadRight">
<TextBlock
Text="{ReflectionBinding Configuration.DpadRight, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.DpadRight, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -590,6 +317,13 @@
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Image
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
@ -604,99 +338,96 @@
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="1"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
SmallChange="0.01"
Minimum="0"
Value="{ReflectionBinding Configuration.TriggerThreshold, Mode=TwoWay}" />
Value="{Binding Config.TriggerThreshold, Mode=TwoWay}" />
<TextBlock
Width="25"
Text="{ReflectionBinding Configuration.TriggerThreshold, StringFormat=\{0:0.00\}}" />
Text="{Binding Config.TriggerThreshold, StringFormat=\{0:0.00\}}" />
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsRight}"
Orientation="Horizontal">
<TextBlock
Width="20"
Orientation="Vertical"
IsVisible="{Binding HasSides}">
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}"
TextAlignment="Center" />
<ToggleButton>
IsVisible="{Binding IsLeft}"
Orientation="Horizontal">
<TextBlock
Text="{ReflectionBinding Configuration.LeftButtonSr, Mode=TwoWay, Converter={StaticResource Key}}"
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsRight}"
Orientation="Horizontal">
<TextBlock
Width="20"
<ToggleButton Name="LeftButtonSr">
<TextBlock
Text="{Binding Config.LeftButtonSr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}"
TextAlignment="Center" />
<ToggleButton>
IsVisible="{Binding IsLeft}"
Orientation="Horizontal">
<TextBlock
Text="{ReflectionBinding Configuration.LeftButtonSl, Mode=TwoWay, Converter={StaticResource Key}}"
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsLeft}"
Orientation="Horizontal">
<TextBlock
Width="20"
<ToggleButton Name="LeftButtonSl">
<TextBlock
Text="{Binding Config.LeftButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}"
TextAlignment="Center" />
<ToggleButton>
IsVisible="{Binding IsRight}"
Orientation="Horizontal">
<TextBlock
Text="{ReflectionBinding Configuration.RightButtonSr, Mode=TwoWay, Converter={StaticResource Key}}"
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsLeft}"
Orientation="Horizontal">
<TextBlock
Width="20"
<ToggleButton Name="RightButtonSr">
<TextBlock
Text="{Binding Config.RightButtonSr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}"
TextAlignment="Center" />
<ToggleButton>
IsVisible="{Binding IsRight}"
Orientation="Horizontal">
<TextBlock
Text="{ReflectionBinding Configuration.RightButtonSl, Mode=TwoWay, Converter={StaticResource Key}}"
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}"
TextAlignment="Center" />
</ToggleButton>
<ToggleButton Name="RightButtonSl">
<TextBlock
Text="{Binding Config.RightButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!-- Controller Picture -->
<Image
Margin="0,10,0,0"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<!-- Motion + Rumble -->
<StackPanel
Margin="0,10,0,0"
@ -708,8 +439,7 @@
BorderThickness="1"
CornerRadius="5"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
IsVisible="{Binding IsController}">
HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@ -719,7 +449,7 @@
Margin="10"
MinWidth="0"
Grid.Column="0"
IsChecked="{ReflectionBinding Configuration.EnableMotion, Mode=TwoWay}">
IsChecked="{Binding Config.EnableMotion, Mode=TwoWay}">
<TextBlock Text="{locale:Locale ControllerSettingsMotion}" />
</CheckBox>
<Button
@ -735,7 +465,6 @@
BorderThickness="1"
CornerRadius="5"
HorizontalAlignment="Stretch"
IsVisible="{Binding IsController}"
Margin="0,-1,0,0">
<Grid>
<Grid.ColumnDefinitions>
@ -746,7 +475,7 @@
Margin="10"
MinWidth="0"
Grid.Column="0"
IsChecked="{ReflectionBinding Configuration.EnableRumble, Mode=TwoWay}">
IsChecked="{Binding Config.EnableRumble, Mode=TwoWay}">
<TextBlock Text="{locale:Locale ControllerSettingsRumble}" />
</CheckBox>
<Button
@ -792,9 +521,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZR}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonZr">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonZr, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonZr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -810,9 +539,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerR}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonR">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonR, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonR, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -828,15 +557,15 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonPlus}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonPlus">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonPlus, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonPlus, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</Grid>
</Border>
<!-- Right Joystick -->
<!-- Right Buttons -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
@ -863,9 +592,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonA}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonA">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonA, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonA, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -880,9 +609,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonB}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonB">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonB, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonB, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -897,9 +626,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonX}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonX">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonX, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonX, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -914,9 +643,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonY}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="ButtonY">
<TextBlock
Text="{ReflectionBinding Configuration.ButtonY, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.ButtonY, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -936,100 +665,8 @@
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRStick}" />
<!-- Right Joystick Keyboard -->
<StackPanel
IsVisible="{Binding !IsController}"
Orientation="Vertical">
<!-- Right Joystick Button -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightStickUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightStickDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightStickLeft, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightStickRight, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
<!-- Right Joystick Controller -->
<StackPanel
IsVisible="{Binding IsController}"
Orientation="Vertical">
<StackPanel Orientation="Vertical">
<!-- Right Joystick Button -->
<StackPanel
Orientation="Horizontal">
@ -1040,9 +677,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton>
<ToggleButton Name="RightStickButton">
<TextBlock
Text="{ReflectionBinding Configuration.RightControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.RightStickButton, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@ -1058,20 +695,20 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickStick}"
TextAlignment="Center" />
<ToggleButton Tag="stick">
<ToggleButton Name="RightJoystick" Tag="stick">
<TextBlock
Text="{ReflectionBinding Configuration.RightJoystick, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding Config.RightJoystick, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<Separator Margin="0,8,0,8" Height="1" />
<CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickX}">
<CheckBox IsChecked="{Binding Config.RightInvertStickX}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" />
</CheckBox>
<CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickY}">
<CheckBox IsChecked="{Binding Config.RightInvertStickY}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" />
</CheckBox>
<CheckBox IsChecked="{ReflectionBinding Configuration.RightRotate90}">
<CheckBox IsChecked="{Binding Config.RightRotate90}">
<TextBlock Text="{locale:Locale ControllerSettingsRotate90}" />
</CheckBox>
<Separator Margin="0,8,0,8" Height="1" />
@ -1083,7 +720,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="1"
TickFrequency="0.01"
@ -1092,11 +729,11 @@
Padding="0"
VerticalAlignment="Center"
Minimum="0"
Value="{ReflectionBinding Configuration.DeadzoneRight, Mode=TwoWay}" />
Value="{Binding Config.DeadzoneRight, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Width="25"
Text="{ReflectionBinding Configuration.DeadzoneRight, StringFormat=\{0:0.00\}}" />
Text="{Binding Config.DeadzoneRight, StringFormat=\{0:0.00\}}" />
</StackPanel>
<TextBlock
HorizontalAlignment="Center"
@ -1105,18 +742,18 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="2"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
SmallChange="0.01"
Minimum="0"
Value="{ReflectionBinding Configuration.RangeRight, Mode=TwoWay}" />
Value="{Binding Config.RangeRight, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Width="25"
Text="{ReflectionBinding Configuration.RangeRight, StringFormat=\{0:0.00\}}" />
Text="{Binding Config.RangeRight, StringFormat=\{0:0.00\}}" />
</StackPanel>
</StackPanel>
</StackPanel>
@ -1125,4 +762,4 @@
</StackPanel>
</Grid>
</StackPanel>
</UserControl>
</UserControl>

View File

@ -1,35 +1,28 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class ControllerInputView : UserControl
{
private bool _dialogOpen;
private ButtonKeyAssigner _currentAssigner;
internal ControllerInputViewModel ViewModel { get; set; }
public ControllerInputView()
{
DataContext = ViewModel = new ControllerInputViewModel(this);
InitializeComponent();
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
{
if (visual is ToggleButton button && visual is not CheckBox)
if (visual is ToggleButton button and not CheckBox)
{
button.IsCheckedChanged += Button_IsCheckedChanged;
}
@ -59,7 +52,7 @@ namespace Ryujinx.Ava.UI.Views.Input
bool isStick = button.Tag != null && button.Tag.ToString() == "stick";
if (_currentAssigner == null)
if (_currentAssigner == null && (bool)button.IsChecked)
{
_currentAssigner = new ButtonKeyAssigner(button);
@ -67,14 +60,86 @@ namespace Ryujinx.Ava.UI.Views.Input
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IKeyboard keyboard = (IKeyboard)(DataContext as ControllerInputViewModel).parentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) =>
{
if (e.IsAssigned)
if (e.ButtonValue.HasValue)
{
ViewModel.IsModified = true;
var viewModel = (DataContext as ControllerInputViewModel);
var buttonValue = e.ButtonValue.Value;
viewModel.parentModel.IsModified = true;
switch (button.Name)
{
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsGamepadButtonInputId();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsGamepadButtonInputId();
break;
case "LeftJoystick":
viewModel.Config.LeftJoystick = buttonValue.AsGamepadStickId();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsGamepadButtonInputId();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsGamepadButtonInputId();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsGamepadButtonInputId();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsGamepadButtonInputId();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsGamepadButtonInputId();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsGamepadButtonInputId();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsGamepadButtonInputId();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsGamepadButtonInputId();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsGamepadButtonInputId();
break;
case "RightJoystick":
viewModel.Config.RightJoystick = buttonValue.AsGamepadStickId();
break;
}
}
};
@ -100,82 +165,29 @@ namespace Ryujinx.Ava.UI.Views.Input
}
}
public void SaveCurrentProfile()
{
ViewModel.Save();
}
private IButtonAssigner CreateButtonAssigner(bool forStick)
{
IButtonAssigner assigner;
var device = ViewModel.Devices[ViewModel.Device];
if (device.Type == DeviceType.Keyboard)
{
assigner = new KeyboardKeyAssigner((IKeyboard)ViewModel.SelectedGamepad);
}
else if (device.Type == DeviceType.Controller)
{
assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (ViewModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick);
}
else
{
throw new Exception("Controller not supported");
}
return assigner;
}
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = false;
if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
{
shouldUnbind = true;
}
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
private IButtonAssigner CreateButtonAssigner(bool forStick)
{
if (ViewModel.IsModified && !_dialogOpen)
{
_dialogOpen = true;
IButtonAssigner assigner;
var result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage],
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
assigner = new GamepadButtonAssigner((DataContext as ControllerInputViewModel).parentModel.SelectedGamepad, ((DataContext as ControllerInputViewModel).parentModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick);
if (result == UserResult.Yes)
{
ViewModel.Save();
}
_dialogOpen = false;
ViewModel.IsModified = false;
if (e.AddedItems.Count > 0)
{
var player = (PlayerModel)e.AddedItems[0];
ViewModel.PlayerId = player.Id;
}
}
return assigner;
}
public void Dispose()
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_currentAssigner?.Cancel();
_currentAssigner = null;
ViewModel.Dispose();
}
}
}

View File

@ -0,0 +1,225 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
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:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="800"
d:DesignWidth="800"
x:Class="Ryujinx.Ava.UI.Views.Input.InputView"
x:DataType="viewModels:InputViewModel"
x:CompileBindings="True"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
<viewModels:InputViewModel />
</Design.DataContext>
<UserControl.Styles>
<Style Selector="ToggleButton">
<Setter Property="Width" Value="90" />
<Setter Property="Height" Value="27" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</UserControl.Styles>
<StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical">
<StackPanel
Margin="0 0 0 5"
Orientation="Vertical"
Spacing="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Player Selection -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsPlayer}" />
<ComboBox
Grid.Column="1"
Name="PlayerIndexBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectionChanged="PlayerIndexBox_OnSelectionChanged"
ItemsSource="{Binding PlayerIndexes}"
SelectedIndex="{Binding PlayerId}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<!-- Profile Selection -->
<Grid
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsProfile}" />
<ui:FAComboBox
Grid.Column="1"
IsEditable="True"
Name="ProfileBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectedIndex="0"
ItemsSource="{Binding ProfilesList}"
Text="{Binding ProfileName, Mode=TwoWay}" />
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}"
Command="{Binding LoadProfile}">
<ui:SymbolIcon
Symbol="Upload"
FontSize="15"
Height="20" />
</Button>
<Button
Grid.Column="3"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}"
Command="{Binding SaveProfile}">
<ui:SymbolIcon
Symbol="Save"
FontSize="15"
Height="20" />
</Button>
<Button
Grid.Column="4"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}"
Command="{Binding RemoveProfile}">
<ui:SymbolIcon
Symbol="Delete"
FontSize="15"
Height="20" />
</Button>
</Grid>
</Grid>
<Separator />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Input Device -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsInputDevice}" />
<ComboBox
Grid.Column="1"
Name="DeviceBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
ItemsSource="{Binding DeviceList}"
SelectedIndex="{Binding Device}" />
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
Command="{Binding LoadDevices}">
<ui:SymbolIcon
Symbol="Refresh"
FontSize="15"
Height="20"/>
</Button>
</Grid>
<!-- Controller Type -->
<Grid
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsControllerType}" />
<ComboBox
Grid.Column="1"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Controllers}"
SelectedIndex="{Binding Controller}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="models:ControllerModel">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</StackPanel>
<ContentControl Content="{Binding ConfigViewModel}" IsVisible="{Binding ShowSettings}">
<ContentControl.DataTemplates>
<DataTemplate DataType="viewModels:ControllerInputViewModel">
<views:ControllerInputView />
</DataTemplate>
<DataTemplate DataType="viewModels:KeyboardInputViewModel">
<views:KeyboardInputView />
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,61 @@
using Avalonia.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels.Input;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class InputView : UserControl
{
private bool _dialogOpen;
private InputViewModel ViewModel { get; set; }
public InputView()
{
DataContext = ViewModel = new InputViewModel(this);
InitializeComponent();
}
public void SaveCurrentProfile()
{
ViewModel.Save();
}
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ViewModel.IsModified && !_dialogOpen)
{
_dialogOpen = true;
var result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage],
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
ViewModel.Save();
}
_dialogOpen = false;
ViewModel.IsModified = false;
if (e.AddedItems.Count > 0)
{
var player = (PlayerModel)e.AddedItems[0];
ViewModel.PlayerId = player.Id;
}
}
}
public void Dispose()
{
ViewModel.Dispose();
}
}
}

View File

@ -0,0 +1,675 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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.Input"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="800"
d:DesignWidth="800"
x:Class="Ryujinx.Ava.UI.Views.Input.KeyboardInputView"
x:DataType="viewModels:KeyboardInputViewModel"
x:CompileBindings="True"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
<viewModels:KeyboardInputViewModel />
</Design.DataContext>
<UserControl.Resources>
<helpers:KeyValueConverter x:Key="Key" />
</UserControl.Resources>
<UserControl.Styles>
<Style Selector="ToggleButton">
<Setter Property="Width" Value="90" />
<Setter Property="Height" Value="27" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</UserControl.Styles>
<StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical">
<!-- Button / JoyStick Settings -->
<Grid
Name="SettingButtons"
MinHeight="450">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Left Controls -->
<StackPanel
Orientation="Vertical"
Margin="0,0,5,0"
Grid.Column="0">
<!-- Left Triggers -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsLeft}"
MinHeight="90"
CornerRadius="5">
<Grid
Margin="10"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel
Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZL}"
TextAlignment="Center" />
<ToggleButton Name="ButtonZl">
<TextBlock
Text="{Binding Config.ButtonZl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Grid.Column="0"
Grid.Row="1"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerL}"
TextAlignment="Center" />
<ToggleButton Name="ButtonL">
<TextBlock
Text="{Binding Config.ButtonL, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Grid.Column="1"
Grid.Row="1"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonMinus}"
TextAlignment="Center" />
<ToggleButton Name="ButtonMinus">
<TextBlock
Text="{Binding Config.ButtonMinus, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</Grid>
</Border>
<!-- Left Joystick -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsLeft}"
Margin="0,5,0,0"
CornerRadius="5">
<StackPanel
Margin="10"
Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsLStick}" />
<!-- Left Joystick Keyboard -->
<StackPanel Orientation="Vertical">
<!-- Left Joystick Button -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickButton">
<TextBlock
Text="{Binding Config.LeftStickButton, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickUp">
<TextBlock
Text="{Binding Config.LeftStickUp, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickDown">
<TextBlock
Text="{Binding Config.LeftStickDown, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickLeft">
<TextBlock
Text="{Binding Config.LeftStickLeft, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickRight">
<TextBlock
Text="{Binding Config.LeftStickRight, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!-- Left DPad -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
VerticalAlignment="Top"
IsVisible="{Binding IsLeft}"
Margin="0,5,0,0"
CornerRadius="5">
<StackPanel
Margin="10"
Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPad}" />
<StackPanel Orientation="Vertical">
<!-- Left DPad Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadUp}"
TextAlignment="Center" />
<ToggleButton Name="DpadUp">
<TextBlock
Text="{Binding Config.DpadUp, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left DPad Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadDown}"
TextAlignment="Center" />
<ToggleButton Name="DpadDown">
<TextBlock
Text="{Binding Config.DpadDown, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left DPad Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadLeft}"
TextAlignment="Center" />
<ToggleButton Name="DpadLeft">
<TextBlock
Text="{Binding Config.DpadLeft, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left DPad Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadRight}"
TextAlignment="Center" />
<ToggleButton Name="DpadRight">
<TextBlock
Text="{Binding Config.DpadRight, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
<!-- Triggers & Side Buttons -->
<StackPanel
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Image
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
MinHeight="90"
IsVisible="{Binding HasSides}">
<StackPanel
Margin="8"
Orientation="Vertical">
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsLeft}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}"
TextAlignment="Center" />
<ToggleButton Name="LeftButtonSr">
<TextBlock
Text="{Binding Config.LeftButtonSr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsLeft}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}"
TextAlignment="Center" />
<ToggleButton Name="LeftButtonSl">
<TextBlock
Text="{Binding Config.LeftButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsRight}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}"
TextAlignment="Center" />
<ToggleButton Name="RightButtonSr">
<TextBlock
Text="{Binding Config.RightButtonSr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsRight}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}"
TextAlignment="Center" />
<ToggleButton Name="RightButtonSl">
<TextBlock
Text="{Binding Config.RightButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
<!-- Right Controls -->
<StackPanel
Orientation="Vertical"
Margin="5,0,0,0"
Grid.Column="2">
<!-- Right Triggers -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsRight}"
MinHeight="90"
CornerRadius="5">
<Grid
Margin="10"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel
Grid.Column="1"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZR}"
TextAlignment="Center" />
<ToggleButton Name="ButtonZr">
<TextBlock
Text="{Binding Config.ButtonZr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerR}"
TextAlignment="Center" />
<ToggleButton Name="ButtonR">
<TextBlock
Text="{Binding Config.ButtonR, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Grid.Column="0"
Grid.Row="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonPlus}"
TextAlignment="Center" />
<ToggleButton Name="ButtonPlus">
<TextBlock
Text="{Binding Config.ButtonPlus, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</Grid>
</Border>
<!-- Right Buttons -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsRight}"
Margin="0,5,0,0"
CornerRadius="5">
<StackPanel
Margin="10"
Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtons}" />
<StackPanel
Orientation="Vertical">
<!-- Right Buttons A -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Width="120"
Margin="0,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonA}"
TextAlignment="Center" />
<ToggleButton Name="ButtonA">
<TextBlock
Text="{Binding Config.ButtonA, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Buttons B -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Width="120"
Margin="0,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonB}"
TextAlignment="Center" />
<ToggleButton Name="ButtonB">
<TextBlock
Text="{Binding Config.ButtonB, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Buttons X -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Width="120"
Margin="0,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonX}"
TextAlignment="Center" />
<ToggleButton Name="ButtonX">
<TextBlock
Text="{Binding Config.ButtonX, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Buttons Y -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Width="120"
Margin="0,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonY}"
TextAlignment="Center" />
<ToggleButton Name="ButtonY">
<TextBlock
Text="{Binding Config.ButtonY, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!-- Right DPad -->
<Border
Padding="10"
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
IsVisible="{Binding IsRight}"
Margin="0,5,0,0">
<StackPanel Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRStick}" />
<!-- Right Joystick Keyboard -->
<StackPanel Orientation="Vertical">
<!-- Right Joystick Button -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton Name="RightStickButton">
<TextBlock
Text="{Binding Config.RightStickButton, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}"
TextAlignment="Center" />
<ToggleButton Name="RightStickUp">
<TextBlock
Text="{Binding Config.RightStickUp, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}"
TextAlignment="Center" />
<ToggleButton Name="RightStickDown">
<TextBlock
Text="{Binding Config.RightStickDown, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" />
<ToggleButton Name="RightStickLeft">
<TextBlock
Text="{Binding Config.RightStickLeft, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}"
TextAlignment="Center" />
<ToggleButton Name="RightStickRight">
<TextBlock
Text="{Binding Config.RightStickRight, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,210 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class KeyboardInputView : UserControl
{
private ButtonKeyAssigner _currentAssigner;
public KeyboardInputView()
{
InitializeComponent();
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
{
if (visual is ToggleButton button and not CheckBox)
{
button.IsCheckedChanged += Button_IsCheckedChanged;
}
}
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver)
{
_currentAssigner.Cancel();
}
}
private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton button)
{
if ((bool)button.IsChecked)
{
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
return;
}
bool isStick = button.Tag != null && button.Tag.ToString() == "stick";
if (_currentAssigner == null && (bool)button.IsChecked)
{
_currentAssigner = new ButtonKeyAssigner(button);
this.Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)(DataContext as KeyboardInputViewModel).parentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) =>
{
if (e.ButtonValue.HasValue)
{
var viewModel = (DataContext as KeyboardInputViewModel);
var buttonValue = e.ButtonValue.Value;
viewModel.parentModel.IsModified = true;
switch (button.Name)
{
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsKey();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsKey();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsKey();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsKey();
break;
case "LeftStickUp":
viewModel.Config.LeftStickUp = buttonValue.AsKey();
break;
case "LeftStickDown":
viewModel.Config.LeftStickDown = buttonValue.AsKey();
break;
case "LeftStickRight":
viewModel.Config.LeftStickRight = buttonValue.AsKey();
break;
case "LeftStickLeft":
viewModel.Config.LeftStickLeft = buttonValue.AsKey();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsKey();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsKey();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsKey();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsKey();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsKey();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsKey();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsKey();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsKey();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsKey();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsKey();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsKey();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsKey();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsKey();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsKey();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsKey();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsKey();
break;
case "RightStickUp":
viewModel.Config.RightStickUp = buttonValue.AsKey();
break;
case "RightStickDown":
viewModel.Config.RightStickDown = buttonValue.AsKey();
break;
case "RightStickRight":
viewModel.Config.RightStickRight = buttonValue.AsKey();
break;
case "RightStickLeft":
viewModel.Config.RightStickLeft = buttonValue.AsKey();
break;
}
}
};
_currentAssigner.GetInputAndAssign(assigner, keyboard);
}
else
{
if (_currentAssigner != null)
{
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
}
else
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
}
}
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private IButtonAssigner CreateButtonAssigner(bool forStick)
{
IButtonAssigner assigner;
assigner = new KeyboardKeyAssigner((IKeyboard)(DataContext as KeyboardInputViewModel).parentModel.SelectedGamepad);
return assigner;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_currentAssigner?.Cancel();
_currentAssigner = null;
}
}
}

View File

@ -3,9 +3,10 @@
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:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
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"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
x:DataType="viewModels:MotionInputViewModel"
@ -23,11 +24,11 @@
Margin="0"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" />
<Slider
<controls:SliderScroll
Margin="0,-5,0,-5"
Width="150"
MaxWidth="150"
TickFrequency="0.01"
TickFrequency="1"
IsSnapToTickEnabled="True"
SmallChange="0.01"
Maximum="100"
@ -45,11 +46,11 @@
Margin="0"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" />
<Slider
<controls:SliderScroll
Margin="0,-5,0,-5"
Width="150"
MaxWidth="150"
TickFrequency="0.01"
TickFrequency="1"
IsSnapToTickEnabled="True"
SmallChange="0.01"
Maximum="100"
@ -167,4 +168,4 @@
</Grid>
</Border>
</Grid>
</UserControl>
</UserControl>

View File

@ -1,9 +1,7 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Ava.UI.ViewModels.Input;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Input
@ -19,7 +17,7 @@ namespace Ryujinx.Ava.UI.Views.Input
public MotionInputView(ControllerInputViewModel viewModel)
{
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
var config = viewModel.Config;
_viewModel = new MotionInputViewModel
{
@ -51,7 +49,7 @@ namespace Ryujinx.Ava.UI.Views.Input
};
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
var config = viewModel.Config;
config.Slot = content._viewModel.Slot;
config.Sensitivity = content._viewModel.Sensitivity;
config.GyroDeadzone = content._viewModel.GyroDeadzone;

View File

@ -1,10 +1,11 @@
<UserControl
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: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:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
x:DataType="viewModels:RumbleInputViewModel"
@ -21,7 +22,7 @@
TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" />
<Slider
<controls:SliderScroll
Margin="0,-5,0,-5"
Width="200"
TickFrequency="0.01"
@ -41,7 +42,7 @@
TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" />
<Slider
<controls:SliderScroll
Margin="0,-5,0,-5"
Width="200"
MaxWidth="200"
@ -58,4 +59,4 @@
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
</UserControl>

View File

@ -1,9 +1,7 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Ava.UI.ViewModels.Input;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Input
@ -19,7 +17,7 @@ namespace Ryujinx.Ava.UI.Views.Input
public RumbleInputView(ControllerInputViewModel viewModel)
{
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
var config = viewModel.Config;
_viewModel = new RumbleInputViewModel
{
@ -47,7 +45,7 @@ namespace Ryujinx.Ava.UI.Views.Input
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
var config = viewModel.Config;
config.StrongRumble = content._viewModel.StrongRumble;
config.WeakRumble = content._viewModel.WeakRumble;
};

View File

@ -17,7 +17,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Main
{
@ -107,20 +106,14 @@ namespace Ryujinx.Ava.UI.Views.Main
await Window.ViewModel.AppHost?.ShowExitPrompt();
}
private async void PauseEmulation_Click(object sender, RoutedEventArgs e)
private void PauseEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.Pause();
});
Window.ViewModel.AppHost?.Pause();
}
private async void ResumeEmulation_Click(object sender, RoutedEventArgs e)
private void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.Resume();
});
Window.ViewModel.AppHost?.Resume();
}
public async void OpenSettings(object sender, RoutedEventArgs e)
@ -132,13 +125,13 @@ namespace Ryujinx.Ava.UI.Views.Main
ViewModel.LoadConfigurableHotKeys();
}
public void OpenMiiApplet(object sender, RoutedEventArgs e)
public async 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");
await ViewModel.LoadApplication(contentPath, false, "Mii Applet");
}
}
@ -196,8 +189,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{
if (FileAssociationHelper.Install())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage],
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else
{
@ -209,8 +201,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{
if (FileAssociationHelper.Uninstall())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage],
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else
{

View File

@ -3,9 +3,11 @@
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:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
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"
xmlns:config="clr-namespace:Ryujinx.Common.Configuration;assembly=Ryujinx.Common"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
x:DataType="viewModels:MainWindowViewModel">
@ -112,15 +114,52 @@
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
<SplitButton
Name="AspectRatioStatus"
Margin="5,0,5,0"
Padding="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="Transparent"
BorderThickness="0"
CornerRadius="0"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="AspectRatioStatus_PointerReleased"
Text="{Binding AspectRatioStatusText}"
TextAlignment="Left" />
Content="{Binding AspectRatioStatusText}"
Click="AspectRatioStatus_OnClick"
ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
<SplitButton.Styles>
<Style Selector="Border#SeparatorBorder">
<Setter Property="Opacity" Value="0" />
</Style>
</SplitButton.Styles>
<SplitButton.Flyout>
<MenuFlyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio4x3}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed4x3}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio16x9}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed16x9}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio16x10}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed16x10}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio21x9}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed21x9}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatio32x9}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Fixed32x9}"/>
<MenuItem
Header="{locale:Locale SettingsTabGraphicsAspectRatioStretch}"
Command="{Binding SetAspectRatio}"
CommandParameter="{x:Static config:AspectRatio.Stretched}"/>
</MenuFlyout>
</SplitButton.Flyout>
</SplitButton>
<Border
Width="2"
Height="12"
@ -138,6 +177,7 @@
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}"
IsVisible="{Binding !ShowLoadProgress}"
PointerWheelChanged="VolumeStatus_OnPointerWheelChanged"
Background="Transparent"
BorderThickness="0"
CornerRadius="0">
@ -154,7 +194,7 @@
<ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0">
<Slider
<controls:SliderScroll
MaxHeight="40"
Width="150"
Margin="0"

View File

@ -43,10 +43,9 @@ namespace Ryujinx.Ava.UI.Views.Main
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
}
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
private void AspectRatioStatus_OnClick(object sender, RoutedEventArgs 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;
}
@ -54,5 +53,20 @@ namespace Ryujinx.Ava.UI.Views.Main
{
Window.LoadApplications();
}
private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e)
{
// Change the volume by 5% at a time
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
Window.ViewModel.Volume = newValue switch
{
< 0 => 0,
> 1 => 1,
_ => newValue,
};
e.Handled = true;
}
}
}

View File

@ -3,6 +3,7 @@
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:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
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"
@ -50,7 +51,7 @@
VerticalAlignment="Center"
Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
<Slider
<controls:SliderScroll
Width="150"
Height="35"
Margin="5,-10,5,0"
@ -173,4 +174,4 @@
DockPanel.Dock="Right"
Text="{locale:Locale CommonSort}" />
</DockPanel>
</UserControl>
</UserControl>

View File

@ -4,6 +4,7 @@
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:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
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"
@ -63,13 +64,13 @@
Maximum="100" />
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<Slider Value="{Binding Volume}"
<controls:SliderScroll Value="{Binding Volume}"
Margin="250,0,0,0"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Minimum="0"
Maximum="100"
SmallChange="5"
TickFrequency="5"
SmallChange="1"
TickFrequency="1"
IsSnapToTickEnabled="True"
LargeChange="10"
Width="350" />
@ -77,4 +78,4 @@
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>
</UserControl>

View File

@ -4,6 +4,7 @@
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:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
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"
@ -73,6 +74,7 @@
<TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableColorSpacePassthrough}"
IsVisible="{Binding ColorSpacePassthroughAvailable}"
ToolTip.Tip="{locale:Locale SettingsEnableColorSpacePassthroughTooltip}">
<TextBlock Text="{locale:Locale SettingsEnableColorSpacePassthrough}" />
</CheckBox>
@ -172,7 +174,7 @@
<TextBlock Text="FSR" />
</ComboBoxItem>
</ComboBox>
<Slider Value="{Binding ScalingFilterLevel}"
<controls:SliderScroll Value="{Binding ScalingFilterLevel}"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
MinWidth="150"
Margin="10,-3,0,0"
@ -296,4 +298,4 @@
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>
</UserControl>

View File

@ -27,9 +27,9 @@
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<views:ControllerInputView
<views:InputView
Grid.Row="0"
Name="ControllerSettings" />
Name="InputView" />
<StackPanel
Orientation="Vertical"
Grid.Row="2">

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
public void Dispose()
{
ControllerSettings.Dispose();
InputView.Dispose();
}
}
}

View File

@ -23,21 +23,34 @@
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkMultiplayer}" />
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale MultiplayerMode}"
ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}"
Width="200" />
<ComboBox SelectedIndex="{Binding MultiplayerModeIndex}"
ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}"
HorizontalContentAlignment="Left"
ItemsSource="{Binding MultiplayerModes}"
Width="250" />
</StackPanel>
<Separator Height="1" />
<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}" />
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</CheckBox>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabNetworkInterface}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
Width="200" />
Text="{locale:Locale SettingsTabNetworkInterface}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
Width="200" />
<ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
HorizontalContentAlignment="Left"
ItemsSource="{Binding NetworkInterfaceList}"
Width="250" />
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
HorizontalContentAlignment="Left"
ItemsSource="{Binding NetworkInterfaceList}"
Width="250" />
</StackPanel>
</StackPanel>
</Border>

View File

@ -34,7 +34,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
{
var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false
AllowMultiple = false,
});
if (result.Count > 0)
@ -75,9 +75,9 @@ namespace Ryujinx.Ava.UI.Views.Settings
{
Patterns = new[] { "*.xaml" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xaml" },
MimeTypes = new[] { "application/xaml+xml" }
}
}
MimeTypes = new[] { "application/xaml+xml" },
},
},
});
if (result.Count > 0)

View File

@ -75,9 +75,9 @@ namespace Ryujinx.Ava.UI.Views.User
{
Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" },
AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" },
MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" }
}
}
MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" },
},
},
});
if (result.Count > 0)

View File

@ -11,6 +11,7 @@
Height="500"
MinWidth="500"
MinHeight="500"
x:DataType="window:CheatWindow"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d"
Focusable="True">
@ -40,7 +41,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
LineHeight="18"
Text="{ReflectionBinding Heading}"
Text="{Binding Heading}"
TextAlignment="Center"
TextWrapping="Wrap" />
<TextBlock
@ -61,7 +62,7 @@
MinWidth="160"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{ReflectionBinding BuildId}"
Text="{Binding BuildId}"
IsReadOnly="True" />
<Border
Grid.Row="3"
@ -77,7 +78,7 @@
MinHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{ReflectionBinding LoadedCheats}">
ItemsSource="{Binding LoadedCheats}">
<TreeView.Styles>
<Styles>
<Style Selector="TreeViewItem:empty /template/ ItemsPresenter">
@ -120,18 +121,18 @@
Name="SaveButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding Save}"
IsVisible="{ReflectionBinding !NoCheatsFound}">
Command="{Binding Save}"
IsVisible="{Binding !NoCheatsFound}">
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
</Button>
<Button
Name="CancelButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding Close}">
Command="{Binding Close}">
<TextBlock Text="{locale:Locale InputDialogCancel}" />
</Button>
</DockPanel>
</DockPanel>
</Grid>
</window:StyleableWindow>
</window:StyleableWindow>

View File

@ -17,7 +17,7 @@ namespace Ryujinx.Ava.UI.Windows
private readonly string _enabledCheatsPath;
public bool NoCheatsFound { get; }
private AvaloniaList<CheatsList> LoadedCheats { get; }
public AvaloniaList<CheatsList> LoadedCheats { get; }
public string Heading { get; }
public string BuildId { get; }

View File

@ -39,14 +39,14 @@
Name="EnableAllButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding EnableAll}">
Command="{Binding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button>
<Button
Name="DisableAllButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding DisableAll}">
Command="{Binding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</StackPanel>
@ -157,14 +157,14 @@
Name="AddButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding Add}">
Command="{Binding Add}">
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
</Button>
<Button
Name="RemoveAllButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding RemoveAll}">
Command="{Binding RemoveAll}">
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
</Button>
</StackPanel>
@ -189,4 +189,4 @@
</StackPanel>
</Panel>
</Grid>
</UserControl>
</UserControl>

View File

@ -15,6 +15,7 @@ using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
@ -24,8 +25,8 @@ using Ryujinx.Ui.Common.Helper;
using System;
using System.IO;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager;
namespace Ryujinx.Ava.UI.Windows
{
@ -79,35 +80,11 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached)
{
Initialize();
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
ViewModel.Initialize(
ContentManager,
StorageProvider,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ViewModel.RefreshFirmwareStatus();
LoadGameList();
this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
this.ScalingChanged += OnScalingChanged;
}
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@ -122,36 +99,17 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.IsActive = obj;
}
public void LoadGameList()
{
if (_isLoading)
{
return;
}
_isLoading = true;
LoadApplications();
_isLoading = false;
}
private void OnScalingChanged(object sender, EventArgs e)
{
Program.DesktopScaleFactor = this.RenderScaling;
}
public void AddApplication(ApplicationData applicationData)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Add(applicationData);
});
}
private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e)
{
AddApplication(e.AppData);
Dispatcher.UIThread.Post(() =>
{
ViewModel.Applications.Add(e.AppData);
});
}
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
@ -183,7 +141,7 @@ namespace Ryujinx.Ava.UI.Windows
string path = new FileInfo(args.Application.Path).FullName;
ViewModel.LoadApplication(path);
ViewModel.LoadApplication(path).Wait();
}
args.Handled = true;
@ -202,13 +160,10 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.ShowContent = true;
ViewModel.IsLoadingIndeterminate = false;
Dispatcher.UIThread.InvokeAsync(() =>
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
ViewModel.ToggleFullscreen();
}
});
ViewModel.ToggleFullscreen();
}
}
public void ShowLoading(bool startFullscreen = false)
@ -217,13 +172,10 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.ShowLoadProgress = true;
ViewModel.IsLoadingIndeterminate = true;
Dispatcher.UIThread.InvokeAsync(() =>
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
ViewModel.ToggleFullscreen();
}
});
ViewModel.ToggleFullscreen();
}
}
private void Initialize()
@ -251,11 +203,11 @@ namespace Ryujinx.Ava.UI.Windows
VirtualFileSystem.ReloadKeySet();
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient);
}
[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountWarning()
private static async Task ShowVmMaxMapCountWarning()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);
@ -267,7 +219,7 @@ namespace Ryujinx.Ava.UI.Windows
}
[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountDialog()
private static async Task ShowVmMaxMapCountDialog()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
LinuxHelper.RecommendedVmMaxMapCount);
@ -313,33 +265,46 @@ namespace Ryujinx.Ava.UI.Windows
private void CheckLaunchState()
{
if (ShowKeyErrorOnLoad)
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
if (LinuxHelper.PkExecPath is not null)
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountDialog);
Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountDialog();
}
});
}
else
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountWarning);
Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountWarning();
}
});
}
}
if (_deferLoad)
if (!ShowKeyErrorOnLoad)
{
_deferLoad = false;
if (_deferLoad)
{
_deferLoad = false;
ViewModel.LoadApplication(_launchPath, _startFullscreen);
ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait();
}
}
else
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
@ -372,7 +337,7 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value is true ? WindowState.Maximized : WindowState.Normal;
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
if (CheckScreenBounds(savedPoint))
{
@ -415,6 +380,30 @@ namespace Ryujinx.Ava.UI.Windows
{
base.OnOpened(e);
Initialize();
ViewModel.Initialize(
ContentManager,
StorageProvider,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ViewModel.RefreshFirmwareStatus();
LoadApplications();
CheckLaunchState();
}
@ -514,18 +503,15 @@ namespace Ryujinx.Ava.UI.Windows
});
}
public async void LoadApplications()
public void LoadApplications()
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Clear();
ViewModel.Applications.Clear();
StatusBarView.LoadProgressBar.IsVisible = true;
ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0;
StatusBarView.LoadProgressBar.IsVisible = true;
ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
});
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
ReloadGameList();
}
@ -558,9 +544,17 @@ namespace Ryujinx.Ava.UI.Windows
_isLoading = true;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
Thread applicationLibraryThread = new(() =>
{
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, ConfigurationState.Instance.System.Language);
_isLoading = false;
_isLoading = false;
})
{
Name = "GUI.ApplicationLibraryThread",
IsBackground = true,
};
applicationLibraryThread.Start();
}
}
}

View File

@ -37,7 +37,7 @@ namespace Ryujinx.Ava.UI.Windows
public void SaveSettings()
{
InputPage.ControllerSettings?.SaveCurrentProfile();
InputPage.InputView?.SaveCurrentProfile();
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
{

View File

@ -3,7 +3,6 @@ using Avalonia.Controls.Primitives;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Ryujinx.Ui.Common.Configuration;
using System;
using System.IO;
using System.Reflection;
@ -25,11 +24,6 @@ namespace Ryujinx.Ava.UI.Windows
IconImage = new Bitmap(stream);
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

View File

@ -192,7 +192,7 @@ namespace Ryujinx.Common.Collections
{
if (start.CompareTo(overlap.End) < 0)
{
if (overlaps.Length >= overlapCount)
if (overlaps.Length <= overlapCount)
{
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
}

View File

@ -0,0 +1,7 @@
namespace Ryujinx.Common.Configuration.Multiplayer
{
public enum MultiplayerMode
{
Disabled,
}
}

View File

@ -756,6 +756,18 @@ namespace Ryujinx.Common.Memory
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array96<T> : IArray<T> where T : unmanaged
{
T _e0;
Array64<T> _other;
Array31<T> _other2;
public readonly int Length => 96;
public ref T this[int index] => ref AsSpan()[index];
[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array127<T> : IArray<T> where T : unmanaged
{
T _e0;
@ -791,5 +803,34 @@ namespace Ryujinx.Common.Memory
[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array140<T> : IArray<T> where T : unmanaged
{
T _e0;
Array64<T> _other;
Array64<T> _other2;
Array11<T> _other3;
public readonly int Length => 140;
public ref T this[int index] => ref AsSpan()[index];
[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array384<T> : IArray<T> where T : unmanaged
{
T _e0;
Array64<T> _other;
Array64<T> _other2;
Array64<T> _other3;
Array64<T> _other4;
Array64<T> _other5;
Array63<T> _other6;
public readonly int Length => 384;
public ref T this[int index] => ref AsSpan()[index];
[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
}
#pragma warning restore CS0169, IDE0051

View File

@ -5,7 +5,7 @@ namespace Ryujinx.Common
{
public class ReactiveObject<T>
{
private readonly ReaderWriterLock _readerWriterLock = new();
private readonly ReaderWriterLockSlim _readerWriterLock = new();
private bool _isInitialized;
private T _value;
@ -15,15 +15,15 @@ namespace Ryujinx.Common
{
get
{
_readerWriterLock.AcquireReaderLock(Timeout.Infinite);
_readerWriterLock.EnterReadLock();
T value = _value;
_readerWriterLock.ReleaseReaderLock();
_readerWriterLock.ExitReadLock();
return value;
}
set
{
_readerWriterLock.AcquireWriterLock(Timeout.Infinite);
_readerWriterLock.EnterWriteLock();
T oldValue = _value;
@ -32,7 +32,7 @@ namespace Ryujinx.Common
_isInitialized = true;
_value = value;
_readerWriterLock.ReleaseWriterLock();
_readerWriterLock.ExitWriteLock();
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
{

View File

@ -1,4 +1,6 @@
using System.Net.NetworkInformation;
using System.Buffers.Binary;
using System.Net;
using System.Net.NetworkInformation;
namespace Ryujinx.Common.Utilities
{
@ -62,5 +64,15 @@ namespace Ryujinx.Common.Utilities
return (targetProperties, targetAddressInfo);
}
public static uint ConvertIpv4Address(IPAddress ipAddress)
{
return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes());
}
public static uint ConvertIpv4Address(string ipAddress)
{
return ConvertIpv4Address(IPAddress.Parse(ipAddress));
}
}
}

View File

@ -1,9 +1,11 @@
using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory;
using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
[SupportedOSPlatform("macos")]
class HvAddressSpace : IDisposable
{
private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39));

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