Compare commits

...

10 Commits

Author SHA1 Message Date
Marco Carvalho
22fb8c9d4f Update to new standard for volatility operations (#6682) 2024-04-19 09:03:52 -03:00
gdkchan
2f93ae9a19 Fix unmapped address check when reading texture handles (#6679) 2024-04-18 19:28:16 -03:00
Marco Carvalho
3224ddeeb4 Update "SixLabors.ImageSharp" (#6680) 2024-04-18 19:52:57 +02:00
Isaac Marovitz
446f2854a5 Ava UI: Input Menu Refactor (#5826)
* Refactor

* Apply suggestions from code review

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

* Update src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs

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

* Update src/Ryujinx.Input/ButtonValueType.cs

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

* Add empty line

* Requested renames

* Update src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs

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

* Make parent models private readonly

* Fix ControllerInputView

* Make line shorter

* Mac keys in locale

* Double line break

* Fix build

* Get rid of _isValid

* Fix potential race condition

* Rename HasAnyButtonPressed to IsAnyButtonPressed

* Use switches

* Simplify enumeration

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2024-04-17 18:52:12 -03:00
Luke
8884d1fd73 Fix crash when changing controller config (#6654)
* fix needsMotionInputUpdate conditions

* Fix formatting

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

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-04-15 18:02:09 -03:00
jhorv
268c9aecf8 Texture loading: reduce memory allocations (#6623)
* rebase

* add methods Ryyjinx.Common EmbeddedResources and SteamUtils

* GAL changes - change SetData() methods and ThreadedTexture commands to use IMemoryOwner<byte> instead of SpanOrArray<byte>

* Ryujinx.Graphics.Texture: change texture conversion methods to return IMemoryOwner<byte> and allocate from ByteMemoryPool

* Ryujinx.Graphics.OpenGL: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>

* Ryujinx.Graphics.Vulkan: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>

* Ryujinx.Graphics.Gpu: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>

* Remove now-unused SpanOrArray<T>

* post-rebase cleanup

* PixelConverter: remove unsafe modifier on safe methods, and remove one unnecessary cast

* use ByteMemoryPool.Rent() in GetWritableRegion() impls

* fix formatting, rename `ReadRentedMemory()` to `ReadFileToRentedMemory()``

* Texture.ConvertToHostCompatibleFormat(): dispose of `result` in Astc decode branch
2024-04-14 17:06:14 -03:00
gdkchan
e916662b0f Account for swapchain image count change after re-creation (#6652) 2024-04-11 17:24:19 -03:00
gdkchan
2ddd3dd4a7 Allow BSD sockets Poll to exit when emulation ends (#6650) 2024-04-11 14:56:21 +02:00
gdkchan
a8f7ababb5 Revert "Update StoreConstantToMemory to match StoreConstantToAddress on value…" (#6649)
This reverts commit 22e3ff06b5.
2024-04-10 21:50:06 -03:00
WilliamWsyHK
22e3ff06b5 Update StoreConstantToMemory to match StoreConstantToAddress on value read (#6642) 2024-04-11 00:03:37 +02:00
73 changed files with 3852 additions and 1610 deletions

View File

@@ -42,7 +42,7 @@
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.7" /> <PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" /> <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />

View File

@@ -1,7 +1,5 @@
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
// NOTE: Please don't change this to struct.
// This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys.
public class KeyboardHotkeys public class KeyboardHotkeys
{ {
public Key ToggleVsync { get; set; } public Key ToggleVsync { get; set; }

View File

@@ -1,89 +0,0 @@
using System;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// A struct that can represent both a Span and Array.
/// This is useful to keep the Array representation when possible to avoid copies.
/// </summary>
/// <typeparam name="T">Element Type</typeparam>
public readonly ref struct SpanOrArray<T> where T : unmanaged
{
public readonly T[] Array;
public readonly ReadOnlySpan<T> Span;
/// <summary>
/// Create a new SpanOrArray from an array.
/// </summary>
/// <param name="array">Array to store</param>
public SpanOrArray(T[] array)
{
Array = array;
Span = ReadOnlySpan<T>.Empty;
}
/// <summary>
/// Create a new SpanOrArray from a readonly span.
/// </summary>
/// <param name="array">Span to store</param>
public SpanOrArray(ReadOnlySpan<T> span)
{
Array = null;
Span = span;
}
/// <summary>
/// Return the contained array, or convert the span if necessary.
/// </summary>
/// <returns>An array containing the data</returns>
public T[] ToArray()
{
return Array ?? Span.ToArray();
}
/// <summary>
/// Return a ReadOnlySpan from either the array or ReadOnlySpan.
/// </summary>
/// <returns>A ReadOnlySpan containing the data</returns>
public ReadOnlySpan<T> AsSpan()
{
return Array ?? Span;
}
/// <summary>
/// Cast an array to a SpanOrArray.
/// </summary>
/// <param name="array">Source array</param>
public static implicit operator SpanOrArray<T>(T[] array)
{
return new SpanOrArray<T>(array);
}
/// <summary>
/// Cast a ReadOnlySpan to a SpanOrArray.
/// </summary>
/// <param name="span">Source ReadOnlySpan</param>
public static implicit operator SpanOrArray<T>(ReadOnlySpan<T> span)
{
return new SpanOrArray<T>(span);
}
/// <summary>
/// Cast a Span to a SpanOrArray.
/// </summary>
/// <param name="span">Source Span</param>
public static implicit operator SpanOrArray<T>(Span<T> span)
{
return new SpanOrArray<T>(span);
}
/// <summary>
/// Cast a SpanOrArray to a ReadOnlySpan
/// </summary>
/// <param name="spanOrArray">Source SpanOrArray</param>
public static implicit operator ReadOnlySpan<T>(SpanOrArray<T> spanOrArray)
{
return spanOrArray.AsSpan();
}
}
}

View File

@@ -1,5 +1,6 @@
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -41,6 +42,22 @@ namespace Ryujinx.Common
return StreamUtils.StreamToBytes(stream); return StreamUtils.StreamToBytes(stream);
} }
public static IMemoryOwner<byte> ReadFileToRentedMemory(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
return ReadFileToRentedMemory(assembly, path);
}
public static IMemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
{
using var stream = GetStream(assembly, filename);
return stream is null
? null
: StreamUtils.StreamToRentedMemory(stream);
}
public async static Task<byte[]> ReadAsync(Assembly assembly, string filename) public async static Task<byte[]> ReadAsync(Assembly assembly, string filename)
{ {
using var stream = GetStream(assembly, filename); using var stream = GetStream(assembly, filename);

View File

@@ -1,4 +1,6 @@
using Microsoft.IO;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using System.Buffers;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -9,12 +11,50 @@ namespace Ryujinx.Common.Utilities
{ {
public static byte[] StreamToBytes(Stream input) public static byte[] StreamToBytes(Stream input)
{ {
using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input);
return output.ToArray();
}
input.CopyTo(stream); public static IMemoryOwner<byte> StreamToRentedMemory(Stream input)
{
if (input is MemoryStream inputMemoryStream)
{
return MemoryStreamToRentedMemory(inputMemoryStream);
}
else if (input.CanSeek)
{
long bytesExpected = input.Length;
return stream.ToArray(); IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(bytesExpected);
var destSpan = ownedMemory.Memory.Span;
int totalBytesRead = 0;
while (totalBytesRead < bytesExpected)
{
int bytesRead = input.Read(destSpan[totalBytesRead..]);
if (bytesRead == 0)
{
ownedMemory.Dispose();
throw new IOException($"Tried reading {bytesExpected} but the stream closed after reading {totalBytesRead}.");
}
totalBytesRead += bytesRead;
}
return ownedMemory;
}
else
{
// If input is (non-seekable) then copy twice: first into a RecyclableMemoryStream, then to a rented IMemoryOwner<byte>.
using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input);
return MemoryStreamToRentedMemory(output);
}
} }
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default) public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
@@ -25,5 +65,26 @@ namespace Ryujinx.Common.Utilities
return stream.ToArray(); return stream.ToArray();
} }
private static IMemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input)
{
input.Position = 0;
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(input.Length);
// Discard the return value because we assume reading a MemoryStream always succeeds completely.
_ = input.Read(ownedMemory.Memory.Span);
return ownedMemory;
}
private static RecyclableMemoryStream StreamToRecyclableMemoryStream(Stream input)
{
RecyclableMemoryStream stream = MemoryStreamManager.Shared.GetStream();
input.CopyTo(stream);
return stream;
}
} }
} }

View File

@@ -1,5 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -143,11 +145,11 @@ namespace Ryujinx.Graphics.Device
} }
else else
{ {
Memory<byte> memory = new byte[size]; IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
GetSpan(va, size).CopyTo(memory.Span); GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory, tracked: true); return new WritableRegion(this, va, memoryOwner, tracked: true);
} }
} }

View File

@@ -1,4 +1,4 @@
using Ryujinx.Common.Memory; using System.Buffers;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
@@ -17,10 +17,34 @@ namespace Ryujinx.Graphics.GAL
PinnedSpan<byte> GetData(); PinnedSpan<byte> GetData();
PinnedSpan<byte> GetData(int layer, int level); PinnedSpan<byte> GetData(int layer, int level);
void SetData(SpanOrArray<byte> data); /// <summary>
void SetData(SpanOrArray<byte> data, int layer, int level); /// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region); /// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
void SetData(IMemoryOwner<byte> data);
/// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
/// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
/// <param name="layer">Target layer</param>
/// <param name="level">Target level</param>
void SetData(IMemoryOwner<byte> data, int layer, int level);
/// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
/// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
/// <param name="layer">Target layer</param>
/// <param name="level">Target level</param>
/// <param name="region">Target sub-region of the texture to update</param>
void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region);
void SetStorage(BufferRange buffer); void SetStorage(BufferRange buffer);
void Release(); void Release();
} }
} }

View File

@@ -1,6 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System; using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetData; public readonly CommandType CommandType => CommandType.TextureSetData;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<byte[]> _data; private TableRef<IMemoryOwner<byte>> _data;
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data) public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;
@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ThreadedTexture texture = command._texture.Get(threaded); ThreadedTexture texture = command._texture.Get(threaded);
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded))); texture.Base.SetData(command._data.Get(threaded));
} }
} }
} }

View File

@@ -1,6 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System; using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetDataSlice; public readonly CommandType CommandType => CommandType.TextureSetDataSlice;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<byte[]> _data; private TableRef<IMemoryOwner<byte>> _data;
private int _layer; private int _layer;
private int _level; private int _level;
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data, int layer, int level) public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;
@@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ThreadedTexture texture = command._texture.Get(threaded); ThreadedTexture texture = command._texture.Get(threaded);
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)), command._layer, command._level); texture.Base.SetData(command._data.Get(threaded), command._layer, command._level);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System; using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion; public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<byte[]> _data; private TableRef<IMemoryOwner<byte>> _data;
private int _layer; private int _layer;
private int _level; private int _level;
private Rectangle<int> _region; private Rectangle<int> _region;
public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data, int layer, int level, Rectangle<int> region) public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level, Rectangle<int> region)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;
@@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ThreadedTexture texture = command._texture.Get(threaded); ThreadedTexture texture = command._texture.Get(threaded);
texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)), command._layer, command._level, command._region); texture.Base.SetData(command._data.Get(threaded), command._layer, command._level, command._region);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Resources namespace Ryujinx.Graphics.GAL.Multithreading.Resources
{ {
@@ -110,21 +110,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetData(SpanOrArray<byte> data) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
{ {
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data.ToArray())); _renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level); _renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data), layer, level);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level, region); _renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data), layer, level, region);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }

View File

@@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -308,7 +309,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
if (target != null) if (target != null)
{ {
byte[] data; IMemoryOwner<byte> data;
if (srcLinear) if (srcLinear)
{ {
data = LayoutConverter.ConvertLinearStridedToLinear( data = LayoutConverter.ConvertLinearStridedToLinear(

View File

@@ -1,4 +1,5 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using System; using System;
@@ -198,7 +199,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
if (target != null) if (target != null)
{ {
target.SynchronizeMemory(); target.SynchronizeMemory();
target.SetData(data, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); var dataCopy = ByteMemoryPool.RentCopy(data);
target.SetData(dataCopy, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount));
target.SignalModified(); target.SignalModified();
return; return;

View File

@@ -1,5 +1,4 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
@@ -7,6 +6,7 @@ using Ryujinx.Graphics.Texture.Astc;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@@ -661,7 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
SpanOrArray<byte> result = ConvertToHostCompatibleFormat(data); IMemoryOwner<byte> result = ConvertToHostCompatibleFormat(data);
if (ScaleFactor != 1f && AllowScaledSetData()) if (ScaleFactor != 1f && AllowScaledSetData())
{ {
@@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Uploads new texture data to the host GPU. /// Uploads new texture data to the host GPU.
/// </summary> /// </summary>
/// <param name="data">New data</param> /// <param name="data">New data</param>
public void SetData(SpanOrArray<byte> data) public void SetData(IMemoryOwner<byte> data)
{ {
BlacklistScale(); BlacklistScale();
@@ -703,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="data">New data</param> /// <param name="data">New data</param>
/// <param name="layer">Target layer</param> /// <param name="layer">Target layer</param>
/// <param name="level">Target level</param> /// <param name="level">Target level</param>
public void SetData(SpanOrArray<byte> data, int layer, int level) public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
BlacklistScale(); BlacklistScale();
@@ -721,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="layer">Target layer</param> /// <param name="layer">Target layer</param>
/// <param name="level">Target level</param> /// <param name="level">Target level</param>
/// <param name="region">Target sub-region of the texture to update</param> /// <param name="region">Target sub-region of the texture to update</param>
public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region) public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
BlacklistScale(); BlacklistScale();
@@ -739,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="level">Mip level to convert</param> /// <param name="level">Mip level to convert</param>
/// <param name="single">True to convert a single slice</param> /// <param name="single">True to convert a single slice</param>
/// <returns>Converted data</returns> /// <returns>Converted data</returns>
public SpanOrArray<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false) public IMemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
{ {
int width = Info.Width; int width = Info.Width;
int height = Info.Height; int height = Info.Height;
@@ -754,11 +754,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int sliceDepth = single ? 1 : depth; int sliceDepth = single ? 1 : depth;
SpanOrArray<byte> result; IMemoryOwner<byte> linear;
if (Info.IsLinear) if (Info.IsLinear)
{ {
result = LayoutConverter.ConvertLinearStridedToLinear( linear = LayoutConverter.ConvertLinearStridedToLinear(
width, width,
height, height,
Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockWidth,
@@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
result = LayoutConverter.ConvertBlockLinearToLinear( linear = LayoutConverter.ConvertBlockLinearToLinear(
width, width,
height, height,
depth, depth,
@@ -787,33 +787,41 @@ namespace Ryujinx.Graphics.Gpu.Image
data); data);
} }
IMemoryOwner<byte> result = linear;
// Handle compressed cases not supported by the host: // Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards. // - ASTC is usually not supported on desktop cards.
// - BC4/BC5 is not supported on 3D textures. // - BC4/BC5 is not supported on 3D textures.
if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc())
{ {
if (!AstcDecoder.TryDecodeToRgba8P( using (result)
result.ToArray(),
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
width,
height,
sliceDepth,
levels,
layers,
out byte[] decoded))
{ {
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; if (!AstcDecoder.TryDecodeToRgba8P(
result.Memory,
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
width,
height,
sliceDepth,
levels,
layers,
out IMemoryOwner<byte> decoded))
{
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo}).");
}
if (GraphicsConfig.EnableTextureRecompression)
{
using (decoded)
{
return BCnEncoder.EncodeBC7(decoded.Memory, width, height, sliceDepth, levels, layers);
}
}
return decoded;
} }
if (GraphicsConfig.EnableTextureRecompression)
{
decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers);
}
result = decoded;
} }
else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2()) else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2())
{ {
@@ -821,16 +829,22 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
case Format.Etc2RgbaSrgb: case Format.Etc2RgbaSrgb:
case Format.Etc2RgbaUnorm: case Format.Etc2RgbaUnorm:
result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Etc2RgbPtaSrgb: case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbPtaUnorm: case Format.Etc2RgbPtaUnorm:
result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Etc2RgbSrgb: case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm: case Format.Etc2RgbUnorm:
result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
} }
} }
else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities)) else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
@@ -839,48 +853,75 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
case Format.Bc1RgbaSrgb: case Format.Bc1RgbaSrgb:
case Format.Bc1RgbaUnorm: case Format.Bc1RgbaUnorm:
result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc2Srgb: case Format.Bc2Srgb:
case Format.Bc2Unorm: case Format.Bc2Unorm:
result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc3Srgb: case Format.Bc3Srgb:
case Format.Bc3Unorm: case Format.Bc3Unorm:
result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc4Snorm: case Format.Bc4Snorm:
case Format.Bc4Unorm: case Format.Bc4Unorm:
result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); using (result)
break; {
return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
}
case Format.Bc5Snorm: case Format.Bc5Snorm:
case Format.Bc5Unorm: case Format.Bc5Unorm:
result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); using (result)
break; {
return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
}
case Format.Bc6HSfloat: case Format.Bc6HSfloat:
case Format.Bc6HUfloat: case Format.Bc6HUfloat:
result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); using (result)
break; {
return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
}
case Format.Bc7Srgb: case Format.Bc7Srgb:
case Format.Bc7Unorm: case Format.Bc7Unorm:
result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers); using (result)
break; {
return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers);
}
} }
} }
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
{ {
result = PixelConverter.ConvertR4G4ToR4G4B4A4(result, width); using (result)
if (!_context.Capabilities.SupportsR4G4B4A4Format)
{ {
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width);
if (_context.Capabilities.SupportsR4G4B4A4Format)
{
return converted;
}
else
{
using (converted)
{
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width);
}
}
} }
} }
else if (Format == Format.R4G4B4A4Unorm) else if (Format == Format.R4G4B4A4Unorm)
{ {
if (!_context.Capabilities.SupportsR4G4B4A4Format) if (!_context.Capabilities.SupportsR4G4B4A4Format)
{ {
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); using (result)
{
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
}
} }
} }
else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked()) else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked())
@@ -889,19 +930,27 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
case Format.B5G6R5Unorm: case Format.B5G6R5Unorm:
case Format.R5G6B5Unorm: case Format.R5G6B5Unorm:
result = PixelConverter.ConvertR5G6B5ToR8G8B8A8(result, width); using (result)
break; {
return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width);
}
case Format.B5G5R5A1Unorm: case Format.B5G5R5A1Unorm:
case Format.R5G5B5X1Unorm: case Format.R5G5B5X1Unorm:
case Format.R5G5B5A1Unorm: case Format.R5G5B5A1Unorm:
result = PixelConverter.ConvertR5G5B5ToR8G8B8A8(result, width, Format == Format.R5G5B5X1Unorm); using (result)
break; {
return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm);
}
case Format.A1B5G5R5Unorm: case Format.A1B5G5R5Unorm:
result = PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result, width); using (result)
break; {
return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width);
}
case Format.R4G4B4A4Unorm: case Format.R4G4B4A4Unorm:
result = PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result, width); using (result)
break; {
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
}
} }
} }

View File

@@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex) ? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex); : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
int handle = textureBufferAddress != 0 int handle = textureBufferAddress != MemoryManager.PteUnmapped
? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4) ? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
: 0; : 0;
@@ -790,7 +790,7 @@ namespace Ryujinx.Graphics.Gpu.Image
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex) ? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex); : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
samplerHandle = samplerBufferAddress != 0 samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped
? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4) ? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
: 0; : 0;
} }

View File

@@ -1,4 +1,3 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
@@ -6,6 +5,7 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..]; ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..];
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); IMemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level); Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
} }

View File

@@ -1,6 +1,8 @@
using Ryujinx.Common.Memory;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -240,11 +242,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
Memory<byte> memory = new byte[size]; IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
GetSpan(va, size).CopyTo(memory.Span); GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory, tracked); return new WritableRegion(this, va, memoryOwner, tracked);
} }
} }

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Image;
@@ -6,6 +7,7 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -190,7 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
Memory<byte> memory = new byte[range.GetSize()]; IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(range.GetSize());
Memory<byte> memory = memoryOwner.Memory;
int offset = 0; int offset = 0;
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
@@ -204,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
offset += size; offset += size;
} }
return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked); return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memoryOwner, tracked);
} }
} }

View File

@@ -33,7 +33,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
public int Quality public int Quality
{ {
get => _quality; set get => _quality;
set
{ {
_quality = Math.Clamp(value, 0, _qualities.Length - 1); _quality = Math.Clamp(value, 0, _qualities.Length - 1);
} }
@@ -150,8 +151,8 @@ namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
_areaTexture = new TextureStorage(_renderer, areaInfo); _areaTexture = new TextureStorage(_renderer, areaInfo);
_searchTexture = new TextureStorage(_renderer, searchInfo); _searchTexture = new TextureStorage(_renderer, searchInfo);
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin"); var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin");
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin"); var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin");
var areaView = _areaTexture.CreateDefaultView(); var areaView = _areaTexture.CreateDefaultView();
var searchView = _searchTexture.CreateDefaultView(); var searchView = _searchTexture.CreateDefaultView();

View File

@@ -1,4 +1,6 @@
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@@ -8,9 +10,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
{ {
static class FormatConverter static class FormatConverter
{ {
public unsafe static byte[] ConvertS8D24ToD24S8(ReadOnlySpan<byte> data) public unsafe static IMemoryOwner<byte> ConvertS8D24ToD24S8(ReadOnlySpan<byte> data)
{ {
byte[] output = new byte[data.Length]; IMemoryOwner<byte> outputMemory = ByteMemoryPool.Rent(data.Length);
Span<byte> output = outputMemory.Memory.Span;
int start = 0; int start = 0;
@@ -74,7 +78,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8); outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8);
} }
return output; return outputMemory;
} }
public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan<byte> data) public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan<byte> data)

View File

@@ -1,7 +1,7 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using System; using System;
using System.Buffers;
namespace Ryujinx.Graphics.OpenGL.Image namespace Ryujinx.Graphics.OpenGL.Image
{ {
@@ -54,19 +54,24 @@ namespace Ryujinx.Graphics.OpenGL.Image
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void SetData(SpanOrArray<byte> data) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
{ {
var dataSpan = data.AsSpan(); var dataSpan = data.Memory.Span;
Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]); Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]);
data.Dispose();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View File

@@ -1,8 +1,8 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
namespace Ryujinx.Graphics.OpenGL.Image namespace Ryujinx.Graphics.OpenGL.Image
@@ -448,70 +448,59 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
public void SetData(SpanOrArray<byte> data) public void SetData(IMemoryOwner<byte> data)
{ {
var dataSpan = data.AsSpan(); using (data = EnsureDataFormat(data))
if (Format == Format.S8UintD24Unorm)
{ {
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); unsafe
}
unsafe
{
fixed (byte* ptr = dataSpan)
{ {
ReadFrom((IntPtr)ptr, dataSpan.Length); var dataSpan = data.Memory.Span;
fixed (byte* ptr = dataSpan)
{
ReadFrom((IntPtr)ptr, dataSpan.Length);
}
} }
} }
} }
public void SetData(SpanOrArray<byte> data, int layer, int level) public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
var dataSpan = data.AsSpan(); using (data = EnsureDataFormat(data))
if (Format == Format.S8UintD24Unorm)
{ {
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); unsafe
}
unsafe
{
fixed (byte* ptr = dataSpan)
{ {
int width = Math.Max(Info.Width >> level, 1); fixed (byte* ptr = data.Memory.Span)
int height = Math.Max(Info.Height >> level, 1); {
int width = Math.Max(Info.Width >> level, 1);
int height = Math.Max(Info.Height >> level, 1);
ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height); ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height);
}
} }
} }
} }
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region) public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
var dataSpan = data.AsSpan(); using (data = EnsureDataFormat(data))
if (Format == Format.S8UintD24Unorm)
{ {
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth);
} int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight);
int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); unsafe
int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight);
unsafe
{
fixed (byte* ptr = dataSpan)
{ {
ReadFrom2D( fixed (byte* ptr = data.Memory.Span)
(IntPtr)ptr, {
layer, ReadFrom2D(
level, (IntPtr)ptr,
region.X, layer,
region.Y, level,
region.Width, region.X,
region.Height, region.Y,
BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks); region.Width,
region.Height,
BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks);
}
} }
} }
} }
@@ -533,6 +522,19 @@ namespace Ryujinx.Graphics.OpenGL.Image
ReadFrom2D(data, layer, level, x, y, width, height, mipSize); ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
} }
private IMemoryOwner<byte> EnsureDataFormat(IMemoryOwner<byte> data)
{
if (Format == Format.S8UintD24Unorm)
{
using (data)
{
return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span);
}
}
return data;
}
private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize) private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize)
{ {
TextureTarget target = Target.Convert(); TextureTarget target = Target.Convert();

View File

@@ -1,5 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -291,16 +293,14 @@ namespace Ryujinx.Graphics.Texture.Astc
int depth, int depth,
int levels, int levels,
int layers, int layers,
out byte[] decoded) out IMemoryOwner<byte> decoded)
{ {
byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels, layers)]; decoded = ByteMemoryPool.Rent(QueryDecompressedSize(width, height, depth, levels, layers));
AstcDecoder decoder = new(data, output, blockWidth, blockHeight, width, height, depth, levels, layers); AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers);
Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x));
decoded = output;
return decoder.Success; return decoder.Success;
} }

View File

@@ -1,5 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@@ -12,7 +14,7 @@ namespace Ryujinx.Graphics.Texture
private const int BlockWidth = 4; private const int BlockWidth = 4;
private const int BlockHeight = 4; private const int BlockHeight = 4;
public static byte[] DecodeBC1(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeBC1(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -21,12 +23,12 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile); Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile); Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@@ -100,7 +102,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC2(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeBC2(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -109,12 +111,12 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile); Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile); Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@@ -195,7 +197,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC3(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeBC3(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -204,13 +206,13 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<byte> rPal = stackalloc byte[8]; Span<byte> rPal = stackalloc byte[8];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile); Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile); Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@@ -292,7 +294,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC4(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed) public static IMemoryOwner<byte> DecodeBC4(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{ {
int size = 0; int size = 0;
@@ -304,8 +306,8 @@ namespace Ryujinx.Graphics.Texture
// Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned.
int alignedWidth = BitUtils.AlignUp(width, 4); int alignedWidth = BitUtils.AlignUp(width, 4);
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = new(output); Span<byte> outputSpan = output.Memory.Span;
ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data); ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data);
@@ -400,7 +402,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC5(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed) public static IMemoryOwner<byte> DecodeBC5(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{ {
int size = 0; int size = 0;
@@ -412,7 +414,7 @@ namespace Ryujinx.Graphics.Texture
// Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned.
int alignedWidth = BitUtils.AlignUp(width, 2); int alignedWidth = BitUtils.AlignUp(width, 2);
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data); ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data);
@@ -421,7 +423,7 @@ namespace Ryujinx.Graphics.Texture
Span<byte> rPal = stackalloc byte[8]; Span<byte> rPal = stackalloc byte[8];
Span<byte> gPal = stackalloc byte[8]; Span<byte> gPal = stackalloc byte[8];
Span<ushort> outputAsUshort = MemoryMarshal.Cast<byte, ushort>(output); Span<ushort> outputAsUshort = MemoryMarshal.Cast<byte, ushort>(output.Memory.Span);
Span<uint> rTileAsUint = MemoryMarshal.Cast<byte, uint>(rTile); Span<uint> rTileAsUint = MemoryMarshal.Cast<byte, uint>(rTile);
Span<uint> gTileAsUint = MemoryMarshal.Cast<byte, uint>(gTile); Span<uint> gTileAsUint = MemoryMarshal.Cast<byte, uint>(gTile);
@@ -525,7 +527,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC6(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed) public static IMemoryOwner<byte> DecodeBC6(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{ {
int size = 0; int size = 0;
@@ -534,7 +536,8 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = output.Memory.Span;
int inputOffset = 0; int inputOffset = 0;
int outputOffset = 0; int outputOffset = 0;
@@ -548,7 +551,7 @@ namespace Ryujinx.Graphics.Texture
{ {
for (int z = 0; z < depth; z++) for (int z = 0; z < depth; z++)
{ {
BC6Decoder.Decode(output.AsSpan()[outputOffset..], data[inputOffset..], width, height, signed); BC6Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height, signed);
inputOffset += w * h * 16; inputOffset += w * h * 16;
outputOffset += width * height * 8; outputOffset += width * height * 8;
@@ -563,7 +566,7 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeBC7(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeBC7(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -572,7 +575,8 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = output.Memory.Span;
int inputOffset = 0; int inputOffset = 0;
int outputOffset = 0; int outputOffset = 0;
@@ -586,7 +590,7 @@ namespace Ryujinx.Graphics.Texture
{ {
for (int z = 0; z < depth; z++) for (int z = 0; z < depth; z++)
{ {
BC7Decoder.Decode(output.AsSpan()[outputOffset..], data[inputOffset..], width, height); BC7Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height);
inputOffset += w * h * 16; inputOffset += w * h * 16;
outputOffset += width * height * 4; outputOffset += width * height * 4;

View File

@@ -1,6 +1,8 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Texture.Encoders; using Ryujinx.Graphics.Texture.Encoders;
using System; using System;
using System.Buffers;
namespace Ryujinx.Graphics.Texture namespace Ryujinx.Graphics.Texture
{ {
@@ -9,7 +11,7 @@ namespace Ryujinx.Graphics.Texture
private const int BlockWidth = 4; private const int BlockWidth = 4;
private const int BlockHeight = 4; private const int BlockHeight = 4;
public static byte[] EncodeBC7(byte[] data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> EncodeBC7(Memory<byte> data, int width, int height, int depth, int levels, int layers)
{ {
int size = 0; int size = 0;
@@ -21,7 +23,8 @@ namespace Ryujinx.Graphics.Texture
size += w * h * 16 * Math.Max(1, depth >> l) * layers; size += w * h * 16 * Math.Max(1, depth >> l) * layers;
} }
byte[] output = new byte[size]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Memory<byte> outputMemory = output.Memory;
int imageBaseIOffs = 0; int imageBaseIOffs = 0;
int imageBaseOOffs = 0; int imageBaseOOffs = 0;
@@ -36,8 +39,8 @@ namespace Ryujinx.Graphics.Texture
for (int z = 0; z < depth; z++) for (int z = 0; z < depth; z++)
{ {
BC7Encoder.Encode( BC7Encoder.Encode(
output.AsMemory()[imageBaseOOffs..], outputMemory[imageBaseOOffs..],
data.AsMemory()[imageBaseIOffs..], data[imageBaseIOffs..],
width, width,
height, height,
EncodeMode.Fast | EncodeMode.Multithreaded); EncodeMode.Fast | EncodeMode.Multithreaded);

View File

@@ -1,5 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -49,15 +51,15 @@ namespace Ryujinx.Graphics.Texture
new int[] { -3, -5, -7, -9, 2, 4, 6, 8 }, new int[] { -3, -5, -7, -9, 2, 4, 6, 8 },
}; };
public static byte[] DecodeRgb(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeRgb(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data); ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
int inputOffset = 0; int inputOffset = 0;
byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight]; Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
int imageBaseOOffs = 0; int imageBaseOOffs = 0;
@@ -111,15 +113,15 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodePta(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodePta(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data); ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
int inputOffset = 0; int inputOffset = 0;
byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight]; Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
int imageBaseOOffs = 0; int imageBaseOOffs = 0;
@@ -168,15 +170,15 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public static byte[] DecodeRgba(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) public static IMemoryOwner<byte> DecodeRgba(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{ {
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data); ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
int inputOffset = 0; int inputOffset = 0;
byte[] output = new byte[CalculateOutputSize(width, height, depth, levels, layers)]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight]; Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
int imageBaseOOffs = 0; int imageBaseOOffs = 0;

View File

@@ -1,5 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using static Ryujinx.Graphics.Texture.BlockLinearConstants; using static Ryujinx.Graphics.Texture.BlockLinearConstants;
@@ -93,7 +95,7 @@ namespace Ryujinx.Graphics.Texture
}; };
} }
public static byte[] ConvertBlockLinearToLinear( public static IMemoryOwner<byte> ConvertBlockLinearToLinear(
int width, int width,
int height, int height,
int depth, int depth,
@@ -119,7 +121,8 @@ namespace Ryujinx.Graphics.Texture
blockHeight, blockHeight,
bytesPerPixel); bytesPerPixel);
byte[] output = new byte[outSize]; IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(outSize);
Span<byte> output = outputOwner.Memory.Span;
int outOffs = 0; int outOffs = 0;
@@ -243,10 +246,10 @@ namespace Ryujinx.Graphics.Texture
_ => throw new NotSupportedException($"Unable to convert ${bytesPerPixel} bpp pixel format."), _ => throw new NotSupportedException($"Unable to convert ${bytesPerPixel} bpp pixel format."),
}; };
} }
return output; return outputOwner;
} }
public static byte[] ConvertLinearStridedToLinear( public static IMemoryOwner<byte> ConvertLinearStridedToLinear(
int width, int width,
int height, int height,
int blockWidth, int blockWidth,
@@ -262,8 +265,8 @@ namespace Ryujinx.Graphics.Texture
int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment);
lineSize = Math.Min(lineSize, outStride); lineSize = Math.Min(lineSize, outStride);
byte[] output = new byte[h * outStride]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(h * outStride);
Span<byte> outSpan = output; Span<byte> outSpan = output.Memory.Span;
int outOffs = 0; int outOffs = 0;
int inOffs = 0; int inOffs = 0;

View File

@@ -1,5 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
@@ -19,13 +21,13 @@ namespace Ryujinx.Graphics.Texture
return (remainder, outRemainder, length / stride); return (remainder, outRemainder, length / stride);
} }
public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data, int width) public unsafe static IMemoryOwner<byte> ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data, int width)
{ {
byte[] output = new byte[data.Length * 2]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2); (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2);
Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output); Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output.Memory.Span);
if (remainder == 0) if (remainder == 0)
{ {
@@ -36,7 +38,7 @@ namespace Ryujinx.Graphics.Texture
int sizeTrunc = data.Length & ~7; int sizeTrunc = data.Length & ~7;
start = sizeTrunc; start = sizeTrunc;
fixed (byte* inputPtr = data, outputPtr = output) fixed (byte* inputPtr = data, outputPtr = output.Memory.Span)
{ {
for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8) for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8)
{ {
@@ -47,7 +49,7 @@ namespace Ryujinx.Graphics.Texture
for (int i = start; i < data.Length; i++) for (int i = start; i < data.Length; i++)
{ {
outputSpan[i] = (ushort)data[i]; outputSpan[i] = data[i];
} }
} }
else else
@@ -70,16 +72,16 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public unsafe static byte[] ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width) public static IMemoryOwner<byte> ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{ {
byte[] output = new byte[data.Length * 2]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
int offset = 0; int offset = 0;
int outOffset = 0; int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data); ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@@ -107,16 +109,16 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public unsafe static byte[] ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width, bool forceAlpha) public static IMemoryOwner<byte> ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width, bool forceAlpha)
{ {
byte[] output = new byte[data.Length * 2]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
int offset = 0; int offset = 0;
int outOffset = 0; int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data); ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@@ -144,16 +146,16 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public unsafe static byte[] ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan<byte> data, int width) public static IMemoryOwner<byte> ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{ {
byte[] output = new byte[data.Length * 2]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
int offset = 0; int offset = 0;
int outOffset = 0; int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data); ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@@ -181,16 +183,16 @@ namespace Ryujinx.Graphics.Texture
return output; return output;
} }
public unsafe static byte[] ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan<byte> data, int width) public static IMemoryOwner<byte> ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{ {
byte[] output = new byte[data.Length * 2]; IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
int offset = 0; int offset = 0;
int outOffset = 0; int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data); ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output); Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {

View File

@@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using CompareOp = Ryujinx.Graphics.GAL.CompareOp; using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
@@ -216,7 +217,7 @@ namespace Ryujinx.Graphics.Vulkan
public void Initialize() public void Initialize()
{ {
Span<byte> dummyTextureData = stackalloc byte[4]; IMemoryOwner<byte> dummyTextureData = ByteMemoryPool.RentCleared(4);
_dummyTexture.SetData(dummyTextureData); _dummyTexture.SetData(dummyTextureData);
} }

View File

@@ -174,8 +174,8 @@ namespace Ryujinx.Graphics.Vulkan.Effects
SwizzleComponent.Blue, SwizzleComponent.Blue,
SwizzleComponent.Alpha); SwizzleComponent.Alpha);
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin"); var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin");
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin"); var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin");
_areaTexture = _renderer.CreateTexture(areaInfo) as TextureView; _areaTexture = _renderer.CreateTexture(areaInfo) as TextureView;
_searchTexture = _renderer.CreateTexture(searchInfo) as TextureView; _searchTexture = _renderer.CreateTexture(searchInfo) as TextureView;

View File

@@ -1,7 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using Format = Ryujinx.Graphics.GAL.Format; using Format = Ryujinx.Graphics.GAL.Format;
using VkFormat = Silk.NET.Vulkan.Format; using VkFormat = Silk.NET.Vulkan.Format;
@@ -94,17 +94,21 @@ namespace Ryujinx.Graphics.Vulkan
_bufferView = null; _bufferView = null;
} }
public void SetData(SpanOrArray<byte> data) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
{ {
_gd.SetBufferData(_bufferHandle, _offset, data); _gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
data.Dispose();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View File

@@ -1,7 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -702,19 +702,25 @@ namespace Ryujinx.Graphics.Vulkan
return GetDataFromBuffer(result, size, result); return GetDataFromBuffer(result, size, result);
} }
public void SetData(SpanOrArray<byte> data) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
{ {
SetData(data, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); SetData(data.Memory.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
data.Dispose();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
{ {
SetData(data, layer, level, 1, 1, singleSlice: true); SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true);
data.Dispose();
} }
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region) /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
SetData(data, layer, level, 1, 1, singleSlice: true, region); SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true, region);
data.Dispose();
} }
private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null) private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)

View File

@@ -330,6 +330,7 @@ namespace Ryujinx.Graphics.Vulkan
_swapchainIsDirty) _swapchainIsDirty)
{ {
RecreateSwapchain(); RecreateSwapchain();
semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length;
} }
else else
{ {

View File

@@ -0,0 +1,158 @@
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using Key = Ryujinx.Common.Configuration.Hid.Key;
using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
namespace Ryujinx.UI.Helper
{
public static class ButtonHelper
{
private static readonly Dictionary<Key, string> _keysMap = new()
{
{ Key.Unknown, "Unknown" },
{ Key.ShiftLeft, "ShiftLeft" },
{ Key.ShiftRight, "ShiftRight" },
{ Key.ControlLeft, "CtrlLeft" },
{ Key.ControlRight, "CtrlRight" },
{ Key.AltLeft, OperatingSystem.IsMacOS() ? "OptLeft" : "AltLeft" },
{ Key.AltRight, OperatingSystem.IsMacOS() ? "OptRight" : "AltRight" },
{ Key.WinLeft, OperatingSystem.IsMacOS() ? "CmdLeft" : "WinLeft" },
{ Key.WinRight, OperatingSystem.IsMacOS() ? "CmdRight" : "WinRight" },
{ Key.Up, "Up" },
{ Key.Down, "Down" },
{ Key.Left, "Left" },
{ Key.Right, "Right" },
{ Key.Enter, "Enter" },
{ Key.Escape, "Escape" },
{ Key.Space, "Space" },
{ Key.Tab, "Tab" },
{ Key.BackSpace, "Backspace" },
{ Key.Insert, "Insert" },
{ Key.Delete, "Delete" },
{ Key.PageUp, "PageUp" },
{ Key.PageDown, "PageDown" },
{ Key.Home, "Home" },
{ Key.End, "End" },
{ Key.CapsLock, "CapsLock" },
{ Key.ScrollLock, "ScrollLock" },
{ Key.PrintScreen, "PrintScreen" },
{ Key.Pause, "Pause" },
{ Key.NumLock, "NumLock" },
{ Key.Clear, "Clear" },
{ Key.Keypad0, "Keypad0" },
{ Key.Keypad1, "Keypad1" },
{ Key.Keypad2, "Keypad2" },
{ Key.Keypad3, "Keypad3" },
{ Key.Keypad4, "Keypad4" },
{ Key.Keypad5, "Keypad5" },
{ Key.Keypad6, "Keypad6" },
{ Key.Keypad7, "Keypad7" },
{ Key.Keypad8, "Keypad8" },
{ Key.Keypad9, "Keypad9" },
{ Key.KeypadDivide, "KeypadDivide" },
{ Key.KeypadMultiply, "KeypadMultiply" },
{ Key.KeypadSubtract, "KeypadSubtract" },
{ Key.KeypadAdd, "KeypadAdd" },
{ Key.KeypadDecimal, "KeypadDecimal" },
{ Key.KeypadEnter, "KeypadEnter" },
{ Key.Number0, "0" },
{ Key.Number1, "1" },
{ Key.Number2, "2" },
{ Key.Number3, "3" },
{ Key.Number4, "4" },
{ Key.Number5, "5" },
{ Key.Number6, "6" },
{ Key.Number7, "7" },
{ Key.Number8, "8" },
{ Key.Number9, "9" },
{ Key.Tilde, "~" },
{ Key.Grave, "`" },
{ Key.Minus, "-" },
{ Key.Plus, "+" },
{ Key.BracketLeft, "[" },
{ Key.BracketRight, "]" },
{ Key.Semicolon, ";" },
{ Key.Quote, "'" },
{ Key.Comma, "," },
{ Key.Period, "." },
{ Key.Slash, "/" },
{ Key.BackSlash, "\\" },
{ Key.Unbound, "Unbound" },
};
private static readonly Dictionary<GamepadInputId, string> _gamepadInputIdMap = new()
{
{ GamepadInputId.LeftStick, "LeftStick" },
{ GamepadInputId.RightStick, "RightStick" },
{ GamepadInputId.LeftShoulder, "LeftShoulder" },
{ GamepadInputId.RightShoulder, "RightShoulder" },
{ GamepadInputId.LeftTrigger, "LeftTrigger" },
{ GamepadInputId.RightTrigger, "RightTrigger" },
{ GamepadInputId.DpadUp, "DpadUp" },
{ GamepadInputId.DpadDown, "DpadDown" },
{ GamepadInputId.DpadLeft, "DpadLeft" },
{ GamepadInputId.DpadRight, "DpadRight" },
{ GamepadInputId.Minus, "Minus" },
{ GamepadInputId.Plus, "Plus" },
{ GamepadInputId.Guide, "Guide" },
{ GamepadInputId.Misc1, "Misc1" },
{ GamepadInputId.Paddle1, "Paddle1" },
{ GamepadInputId.Paddle2, "Paddle2" },
{ GamepadInputId.Paddle3, "Paddle3" },
{ GamepadInputId.Paddle4, "Paddle4" },
{ GamepadInputId.Touchpad, "Touchpad" },
{ GamepadInputId.SingleLeftTrigger0, "SingleLeftTrigger0" },
{ GamepadInputId.SingleRightTrigger0, "SingleRightTrigger0" },
{ GamepadInputId.SingleLeftTrigger1, "SingleLeftTrigger1" },
{ GamepadInputId.SingleRightTrigger1, "SingleRightTrigger1" },
{ GamepadInputId.Unbound, "Unbound" },
};
private static readonly Dictionary<StickInputId, string> _stickInputIdMap = new()
{
{ StickInputId.Left, "StickLeft" },
{ StickInputId.Right, "StickRight" },
{ StickInputId.Unbound, "Unbound" },
};
public static string ToString(Button button)
{
string keyString = "";
switch (button.Type)
{
case ButtonType.Key:
var key = button.AsHidType<Key>();
if (!_keysMap.TryGetValue(button.AsHidType<Key>(), out keyString))
{
keyString = key.ToString();
}
break;
case ButtonType.GamepadButtonInputId:
var gamepadButton = button.AsHidType<GamepadInputId>();
if (!_gamepadInputIdMap.TryGetValue(button.AsHidType<GamepadInputId>(), out keyString))
{
keyString = gamepadButton.ToString();
}
break;
case ButtonType.StickId:
var stickInput = button.AsHidType<StickInputId>();
if (!_stickInputIdMap.TryGetValue(button.AsHidType<StickInputId>(), out keyString))
{
keyString = stickInput.ToString();
}
break;
}
return keyString;
}
}
}

View File

@@ -10,6 +10,7 @@ using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using Ryujinx.Input.GTK3; using Ryujinx.Input.GTK3;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Helper;
using Ryujinx.UI.Widgets; using Ryujinx.UI.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -17,6 +18,7 @@ using System.IO;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using Button = Ryujinx.Input.Button;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using GUI = Gtk.Builder.ObjectAttribute; using GUI = Gtk.Builder.ObjectAttribute;
@@ -887,13 +889,13 @@ namespace Ryujinx.UI.Windows
Thread.Sleep(10); Thread.Sleep(10);
assigner.ReadInput(); assigner.ReadInput();
if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.HasAnyButtonPressed() || assigner.ShouldCancel()) if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.IsAnyButtonPressed() || assigner.ShouldCancel())
{ {
break; break;
} }
} }
string pressedButton = assigner.GetPressedButton(); string pressedButton = ButtonHelper.ToString(assigner.GetPressedButton() ?? new Button(Input.Key.Unknown));
Application.Invoke(delegate Application.Invoke(delegate
{ {

View File

@@ -440,8 +440,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
// If we are here, that mean nothing was available, sleep for 50ms // If we are here, that mean nothing was available, sleep for 50ms
context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000);
context.Thread.HandlePostSyscall();
} }
while (PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); while (context.Thread.Context.Running && PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds);
} }
else if (timeout == -1) else if (timeout == -1)
{ {

View File

@@ -143,7 +143,7 @@ namespace Ryujinx.HLE.HOS
try try
{ {
ControllerKeys pressedKeys = (ControllerKeys)Thread.VolatileRead(ref _pressedKeys); ControllerKeys pressedKeys = (ControllerKeys)Volatile.Read(ref _pressedKeys);
program.Process.TamperedCodeMemory = false; program.Process.TamperedCodeMemory = false;
program.Execute(pressedKeys); program.Execute(pressedKeys);
@@ -175,14 +175,14 @@ namespace Ryujinx.HLE.HOS
{ {
if (input.PlayerId == PlayerIndex.Player1 || input.PlayerId == PlayerIndex.Handheld) if (input.PlayerId == PlayerIndex.Player1 || input.PlayerId == PlayerIndex.Handheld)
{ {
Thread.VolatileWrite(ref _pressedKeys, (long)input.Buttons); Volatile.Write(ref _pressedKeys, (long)input.Buttons);
return; return;
} }
} }
// Clear the input because player one is not conected. // Clear the input because player one is not conected.
Thread.VolatileWrite(ref _pressedKeys, 0); Volatile.Write(ref _pressedKeys, 0);
} }
} }
} }

View File

@@ -49,9 +49,9 @@ namespace Ryujinx.Input.Assigner
CollectButtonStats(); CollectButtonStats();
} }
public bool HasAnyButtonPressed() public bool IsAnyButtonPressed()
{ {
return _detector.HasAnyButtonPressed(); return _detector.IsAnyButtonPressed();
} }
public bool ShouldCancel() public bool ShouldCancel()
@@ -59,16 +59,11 @@ namespace Ryujinx.Input.Assigner
return _gamepad == null || !_gamepad.IsConnected; return _gamepad == null || !_gamepad.IsConnected;
} }
public string GetPressedButton() public Button? GetPressedButton()
{ {
IEnumerable<GamepadButtonInputId> pressedButtons = _detector.GetPressedButtons(); IEnumerable<GamepadButtonInputId> pressedButtons = _detector.GetPressedButtons();
if (pressedButtons.Any()) return !_forStick ? new(pressedButtons.FirstOrDefault()) : new((StickInputId)pressedButtons.FirstOrDefault());
{
return !_forStick ? pressedButtons.First().ToString() : ((StickInputId)pressedButtons.First()).ToString();
}
return "";
} }
private void CollectButtonStats() private void CollectButtonStats()
@@ -123,7 +118,7 @@ namespace Ryujinx.Input.Assigner
_stats = new Dictionary<GamepadButtonInputId, InputSummary>(); _stats = new Dictionary<GamepadButtonInputId, InputSummary>();
} }
public bool HasAnyButtonPressed() public bool IsAnyButtonPressed()
{ {
return _stats.Values.Any(CheckButtonPressed); return _stats.Values.Any(CheckButtonPressed);
} }

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.Input.Assigner
/// Check if a button was pressed. /// Check if a button was pressed.
/// </summary> /// </summary>
/// <returns>True if a button was pressed</returns> /// <returns>True if a button was pressed</returns>
bool HasAnyButtonPressed(); bool IsAnyButtonPressed();
/// <summary> /// <summary>
/// Indicate if the user of this API should cancel operations. This is triggered for example when a gamepad get disconnected or when a user cancel assignation operations. /// Indicate if the user of this API should cancel operations. This is triggered for example when a gamepad get disconnected or when a user cancel assignation operations.
@@ -31,6 +31,6 @@ namespace Ryujinx.Input.Assigner
/// Get the pressed button that was read in <see cref="ReadInput"/> by the button assigner. /// Get the pressed button that was read in <see cref="ReadInput"/> by the button assigner.
/// </summary> /// </summary>
/// <returns>The pressed button that was read</returns> /// <returns>The pressed button that was read</returns>
string GetPressedButton(); Button? GetPressedButton();
} }
} }

View File

@@ -21,9 +21,9 @@ namespace Ryujinx.Input.Assigner
_keyboardState = _keyboard.GetKeyboardStateSnapshot(); _keyboardState = _keyboard.GetKeyboardStateSnapshot();
} }
public bool HasAnyButtonPressed() public bool IsAnyButtonPressed()
{ {
return GetPressedButton().Length != 0; return GetPressedButton() is not null;
} }
public bool ShouldCancel() public bool ShouldCancel()
@@ -31,20 +31,20 @@ namespace Ryujinx.Input.Assigner
return _keyboardState.IsPressed(Key.Escape); return _keyboardState.IsPressed(Key.Escape);
} }
public string GetPressedButton() public Button? GetPressedButton()
{ {
string keyPressed = ""; Button? keyPressed = null;
for (Key key = Key.Unknown; key < Key.Count; key++) for (Key key = Key.Unknown; key < Key.Count; key++)
{ {
if (_keyboardState.IsPressed(key)) if (_keyboardState.IsPressed(key))
{ {
keyPressed = key.ToString(); keyPressed = new(key);
break; break;
} }
} }
return !ShouldCancel() ? keyPressed : ""; return !ShouldCancel() ? keyPressed : null;
} }
} }
} }

View File

@@ -0,0 +1,33 @@
using System;
namespace Ryujinx.Input
{
public readonly struct Button
{
public readonly ButtonType Type;
private readonly uint _rawValue;
public Button(Key key)
{
Type = ButtonType.Key;
_rawValue = (uint)key;
}
public Button(GamepadButtonInputId gamepad)
{
Type = ButtonType.GamepadButtonInputId;
_rawValue = (uint)gamepad;
}
public Button(StickInputId stick)
{
Type = ButtonType.StickId;
_rawValue = (uint)stick;
}
public T AsHidType<T>() where T : Enum
{
return (T)Enum.ToObject(typeof(T), _rawValue);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.Input
{
public enum ButtonType
{
Key,
GamepadButtonInputId,
StickId,
}
}

View File

@@ -203,8 +203,6 @@ namespace Ryujinx.Input.HLE
new(Key.NumLock, 10), new(Key.NumLock, 10),
}; };
private bool _isValid;
private MotionInput _leftMotionInput; private MotionInput _leftMotionInput;
private MotionInput _rightMotionInput; private MotionInput _rightMotionInput;
@@ -222,7 +220,6 @@ namespace Ryujinx.Input.HLE
{ {
State = default; State = default;
Id = null; Id = null;
_isValid = false;
_cemuHookClient = cemuHookClient; _cemuHookClient = cemuHookClient;
} }
@@ -234,20 +231,19 @@ namespace Ryujinx.Input.HLE
Id = config.Id; Id = config.Id;
_gamepad = GamepadDriver.GetGamepad(Id); _gamepad = GamepadDriver.GetGamepad(Id);
_isValid = _gamepad != null;
UpdateUserConfiguration(config); UpdateUserConfiguration(config);
return _isValid; return _gamepad != null;
} }
public void UpdateUserConfiguration(InputConfig config) public void UpdateUserConfiguration(InputConfig config)
{ {
if (config is StandardControllerInputConfig controllerConfig) if (config is StandardControllerInputConfig controllerConfig)
{ {
bool needsMotionInputUpdate = _config == null || (_config is StandardControllerInputConfig oldControllerConfig && bool needsMotionInputUpdate = _config is not StandardControllerInputConfig oldControllerConfig ||
(oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) && ((oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) &&
(oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend)); (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend));
if (needsMotionInputUpdate) if (needsMotionInputUpdate)
{ {
@@ -262,10 +258,7 @@ namespace Ryujinx.Input.HLE
_config = config; _config = config;
if (_isValid) _gamepad?.SetConfiguration(config);
{
_gamepad.SetConfiguration(config);
}
} }
private void UpdateMotionInput(MotionConfigController motionConfig) private void UpdateMotionInput(MotionConfigController motionConfig)
@@ -282,18 +275,21 @@ namespace Ryujinx.Input.HLE
public void Update() public void Update()
{ {
if (_isValid && GamepadDriver != null) // _gamepad may be altered by other threads
var gamepad = _gamepad;
if (gamepad != null && GamepadDriver != null)
{ {
State = _gamepad.GetMappedStateSnapshot(); State = gamepad.GetMappedStateSnapshot();
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion) if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion)
{ {
if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver) if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
{ {
if (_gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion))
{ {
Vector3 accelerometer = _gamepad.GetMotionData(MotionInputId.Accelerometer); Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer);
Vector3 gyroscope = _gamepad.GetMotionData(MotionInputId.Gyroscope); Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope);
accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y); accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y);
gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y); gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y);

View File

@@ -388,14 +388,14 @@ namespace Ryujinx.Tests.Memory
{ {
rwLock.AcquireReaderLock(); rwLock.AcquireReaderLock();
int originalValue = Thread.VolatileRead(ref value); int originalValue = Volatile.Read(ref value);
count++; count++;
// Spin a bit. // Spin a bit.
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
if (Thread.VolatileRead(ref readersAllowed) == 0) if (Volatile.Read(ref readersAllowed) == 0)
{ {
error = true; error = true;
running = false; running = false;
@@ -403,7 +403,7 @@ namespace Ryujinx.Tests.Memory
} }
// Should not change while the lock is held. // Should not change while the lock is held.
if (Thread.VolatileRead(ref value) != originalValue) if (Volatile.Read(ref value) != originalValue)
{ {
error = true; error = true;
running = false; running = false;

View File

@@ -266,6 +266,107 @@
"ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:",
"ControllerSettingsSave": "Save", "ControllerSettingsSave": "Save",
"ControllerSettingsClose": "Close", "ControllerSettingsClose": "Close",
"KeyUnknown": "Unknown",
"KeyShiftLeft": "Shift Left",
"KeyShiftRight": "Shift Right",
"KeyControlLeft": "Ctrl Left",
"KeyMacControlLeft": "⌃ Left",
"KeyControlRight": "Ctrl Right",
"KeyMacControlRight": "⌃ Right",
"KeyAltLeft": "Alt Left",
"KeyMacAltLeft": "⌥ Left",
"KeyAltRight": "Alt Right",
"KeyMacAltRight": "⌥ Right",
"KeyWinLeft": "⊞ Left",
"KeyMacWinLeft": "⌘ Left",
"KeyWinRight": "⊞ Right",
"KeyMacWinRight": "⌘ 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": "L Stick Button",
"GamepadRightStick": "R 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:", "UserProfilesSelectedUserProfile": "Selected User Profile:",
"UserProfilesSaveProfileName": "Save Profile Name", "UserProfilesSaveProfileName": "Save Profile Name",
"UserProfilesChangeProfileImage": "Change Profile Image", "UserProfilesChangeProfileImage": "Change Profile Image",

View File

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

View File

@@ -1,11 +1,8 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers namespace Ryujinx.Ava.UI.Helpers
@@ -15,12 +12,12 @@ namespace Ryujinx.Ava.UI.Helpers
internal class ButtonAssignedEventArgs : EventArgs internal class ButtonAssignedEventArgs : EventArgs
{ {
public ToggleButton Button { get; } public ToggleButton Button { get; }
public bool IsAssigned { get; } public Button? ButtonValue { get; }
public ButtonAssignedEventArgs(ToggleButton button, bool isAssigned) public ButtonAssignedEventArgs(ToggleButton button, Button? buttonValue)
{ {
Button = button; Button = button;
IsAssigned = isAssigned; ButtonValue = buttonValue;
} }
} }
@@ -69,7 +66,7 @@ namespace Ryujinx.Ava.UI.Helpers
assigner.ReadInput(); assigner.ReadInput();
if (assigner.HasAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Key.Escape))) if (assigner.IsAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Key.Escape)))
{ {
break; break;
} }
@@ -78,15 +75,11 @@ namespace Ryujinx.Ava.UI.Helpers
await Dispatcher.UIThread.InvokeAsync(() => await Dispatcher.UIThread.InvokeAsync(() =>
{ {
string pressedButton = assigner.GetPressedButton(); Button? pressedButton = assigner.GetPressedButton();
if (_shouldUnbind) if (_shouldUnbind)
{ {
SetButtonText(ToggledButton, "Unbound"); pressedButton = null;
}
else if (pressedButton != "")
{
SetButtonText(ToggledButton, pressedButton);
} }
_shouldUnbind = false; _shouldUnbind = false;
@@ -94,17 +87,8 @@ namespace Ryujinx.Ava.UI.Helpers
ToggledButton.IsChecked = false; 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 Avalonia.Data.Converters;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers namespace Ryujinx.Ava.UI.Helpers
@@ -10,37 +12,173 @@ namespace Ryujinx.Ava.UI.Helpers
{ {
public static KeyValueConverter Instance = new(); 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, LocaleKeys.KeyControlLeft },
{ Key.AltRight, LocaleKeys.KeyControlRight },
{ Key.WinLeft, LocaleKeys.KeyWinLeft },
{ Key.WinRight, 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) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
if (value == null) string keyString = "";
LocaleKeys localeKey;
switch (value)
{ {
return null; case Key key:
if (_keysMap.TryGetValue(key, out localeKey))
{
if (OperatingSystem.IsMacOS())
{
localeKey = localeKey switch
{
LocaleKeys.KeyControlLeft => LocaleKeys.KeyMacControlLeft,
LocaleKeys.KeyControlRight => LocaleKeys.KeyMacControlRight,
LocaleKeys.KeyAltLeft => LocaleKeys.KeyMacAltLeft,
LocaleKeys.KeyAltRight => LocaleKeys.KeyMacAltRight,
LocaleKeys.KeyWinLeft => LocaleKeys.KeyMacWinLeft,
LocaleKeys.KeyWinRight => LocaleKeys.KeyMacWinRight,
_ => localeKey
};
}
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = key.ToString();
}
break;
case GamepadInputId gamepadInputId:
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey))
{
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = gamepadInputId.ToString();
}
break;
case StickInputId stickInputId:
if (_stickInputIdMap.TryGetValue(stickInputId, out localeKey))
{
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = stickInputId.ToString();
}
break;
} }
return value.ToString(); return keyString;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ {
object key = null; throw new NotSupportedException();
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;
} }
} }
} }

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 GamepadInputConfig : 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 GamepadInputConfig(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 StandardMotionConfigController
{
EnableMotion = EnableMotion,
MotionBackend = MotionInputBackendType.GamepadDriver,
GyroDeadzone = GyroDeadzone,
Sensitivity = Sensitivity,
};
}
return config;
}
}
}

View File

@@ -0,0 +1,141 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
namespace Ryujinx.Ava.UI.Models.Input
{
public class HotkeyConfig : BaseModel
{
private Key _toggleVsync;
public Key ToggleVsync
{
get => _toggleVsync;
set
{
_toggleVsync = value;
OnPropertyChanged();
}
}
private Key _screenshot;
public Key Screenshot
{
get => _screenshot;
set
{
_screenshot = value;
OnPropertyChanged();
}
}
private Key _showUI;
public Key ShowUI
{
get => _showUI;
set
{
_showUI = value;
OnPropertyChanged();
}
}
private Key _pause;
public Key Pause
{
get => _pause;
set
{
_pause = value;
OnPropertyChanged();
}
}
private Key _toggleMute;
public Key ToggleMute
{
get => _toggleMute;
set
{
_toggleMute = value;
OnPropertyChanged();
}
}
private Key _resScaleUp;
public Key ResScaleUp
{
get => _resScaleUp;
set
{
_resScaleUp = value;
OnPropertyChanged();
}
}
private Key _resScaleDown;
public Key ResScaleDown
{
get => _resScaleDown;
set
{
_resScaleDown = value;
OnPropertyChanged();
}
}
private Key _volumeUp;
public Key VolumeUp
{
get => _volumeUp;
set
{
_volumeUp = value;
OnPropertyChanged();
}
}
private Key _volumeDown;
public Key VolumeDown
{
get => _volumeDown;
set
{
_volumeDown = value;
OnPropertyChanged();
}
}
public HotkeyConfig(KeyboardHotkeys config)
{
if (config != null)
{
ToggleVsync = config.ToggleVsync;
Screenshot = config.Screenshot;
ShowUI = config.ShowUI;
Pause = config.Pause;
ToggleMute = config.ToggleMute;
ResScaleUp = config.ResScaleUp;
ResScaleDown = config.ResScaleDown;
VolumeUp = config.VolumeUp;
VolumeDown = config.VolumeDown;
}
}
public KeyboardHotkeys GetConfig()
{
var config = new KeyboardHotkeys
{
ToggleVsync = ToggleVsync,
Screenshot = Screenshot,
ShowUI = ShowUI,
Pause = Pause,
ToggleMute = ToggleMute,
ResScaleUp = ResScaleUp,
ResScaleDown = ResScaleDown,
VolumeUp = VolumeUp,
VolumeDown = VolumeDown,
};
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

@@ -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 GamepadInputConfig _config;
public GamepadInputConfig 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 readonly InputViewModel ParentModel;
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig 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.Input;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; 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 ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key; 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 Disabled = "disabled";
private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg"; private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg";
@@ -48,7 +48,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _controllerNumber; private int _controllerNumber;
private string _controllerImage; private string _controllerImage;
private int _device; private int _device;
private object _configuration; private object _configViewModel;
private string _profileName; private string _profileName;
private bool _isLoaded; private bool _isLoaded;
@@ -71,13 +71,14 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsLeft { get; set; } public bool IsLeft { get; set; }
public bool IsModified { get; set; } public bool IsModified { get; set; }
public event Action NotifyChangesEvent;
public object Configuration public object ConfigViewModel
{ {
get => _configuration; get => _configViewModel;
set set
{ {
_configuration = value; _configViewModel = value;
OnPropertyChanged(); OnPropertyChanged();
} }
@@ -232,7 +233,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public InputConfig Config { get; set; } public InputConfig Config { get; set; }
public ControllerInputViewModel(UserControl owner) : this() public InputViewModel(UserControl owner) : this()
{ {
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
@@ -255,7 +256,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public ControllerInputViewModel() public InputViewModel()
{ {
PlayerIndexes = new ObservableCollection<PlayerModel>(); PlayerIndexes = new ObservableCollection<PlayerModel>();
Controllers = new ObservableCollection<ControllerModel>(); Controllers = new ObservableCollection<ControllerModel>();
@@ -282,12 +283,12 @@ namespace Ryujinx.Ava.UI.ViewModels
if (Config is StandardKeyboardInputConfig keyboardInputConfig) if (Config is StandardKeyboardInputConfig keyboardInputConfig)
{ {
Configuration = new InputConfiguration<Key, ConfigStickInputId>(keyboardInputConfig); ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig));
} }
if (Config is StandardControllerInputConfig controllerInputConfig) if (Config is StandardControllerInputConfig controllerInputConfig)
{ {
Configuration = new InputConfiguration<ConfigGamepadInputId, ConfigStickInputId>(controllerInputConfig); ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig));
} }
} }
@@ -323,16 +324,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() private void LoadInputDriver()
{ {
if (_device < 0) if (_device < 0)
@@ -740,7 +731,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return; return;
} }
if (Configuration == null) if (ConfigViewModel == null)
{ {
return; return;
} }
@@ -751,35 +742,37 @@ namespace Ryujinx.Ava.UI.ViewModels
return; 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 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 +823,18 @@ namespace Ryujinx.Ava.UI.ViewModels
if (device.Type == DeviceType.Keyboard) if (device.Type == DeviceType.Keyboard)
{ {
var inputConfig = Configuration as InputConfiguration<Key, ConfigStickInputId>; var inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
inputConfig.Id = device.Id; inputConfig.Id = device.Id;
} }
else else
{ {
var inputConfig = Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>; var inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
inputConfig.Id = device.Id.Split(" ")[0]; inputConfig.Id = device.Id.Split(" ")[0];
} }
var config = !IsController var config = !IsController
? (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig() ? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig()
: (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig(); : (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
config.ControllerType = Controllers[_controller].Type; config.ControllerType = Controllers[_controller].Type;
config.PlayerIndex = _playerId; config.PlayerIndex = _playerId;
@@ -872,12 +865,13 @@ namespace Ryujinx.Ava.UI.ViewModels
public void NotifyChanges() public void NotifyChanges()
{ {
OnPropertyChanged(nameof(Configuration)); OnPropertyChanged(nameof(ConfigViewModel));
OnPropertyChanged(nameof(IsController)); OnPropertyChanged(nameof(IsController));
OnPropertyChanged(nameof(ShowSettings)); OnPropertyChanged(nameof(ShowSettings));
OnPropertyChanged(nameof(IsKeyboard)); OnPropertyChanged(nameof(IsKeyboard));
OnPropertyChanged(nameof(IsRight)); OnPropertyChanged(nameof(IsRight));
OnPropertyChanged(nameof(IsLeft)); OnPropertyChanged(nameof(IsLeft));
NotifyChangesEvent?.Invoke();
} }
public void Dispose() 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 readonly 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 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 public class RumbleInputViewModel : BaseModel
{ {

View File

@@ -7,9 +7,9 @@ using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@@ -46,7 +46,6 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _isVulkanAvailable = true; private bool _isVulkanAvailable = true;
private bool _directoryChanged; private bool _directoryChanged;
private readonly List<string> _gpuIds = new(); private readonly List<string> _gpuIds = new();
private KeyboardHotkeys _keyboardHotkeys;
private int _graphicsBackendIndex; private int _graphicsBackendIndex;
private int _scalingFilter; private int _scalingFilter;
private int _scalingFilterLevel; private int _scalingFilterLevel;
@@ -237,16 +236,7 @@ namespace Ryujinx.Ava.UI.ViewModels
get => new(_networkInterfaces.Keys); get => new(_networkInterfaces.Keys);
} }
public KeyboardHotkeys KeyboardHotkeys public HotkeyConfig KeyboardHotkey { get; set; }
{
get => _keyboardHotkeys;
set
{
_keyboardHotkeys = value;
OnPropertyChanged();
}
}
public int NetworkInterfaceIndex public int NetworkInterfaceIndex
{ {
@@ -413,7 +403,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableMouse = config.Hid.EnableMouse; EnableMouse = config.Hid.EnableMouse;
// Keyboard Hotkeys // Keyboard Hotkeys
KeyboardHotkeys = config.Hid.Hotkeys.Value; KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
// System // System
Region = (int)config.System.Region.Value; Region = (int)config.System.Region.Value;
@@ -500,7 +490,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Hid.EnableMouse.Value = EnableMouse; config.Hid.EnableMouse.Value = EnableMouse;
// Keyboard Hotkeys // Keyboard Hotkeys
config.Hid.Hotkeys.Value = KeyboardHotkeys; config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();
// System // System
config.System.Region.Value = (Region)Region; config.System.Region.Value = (Region)Region;

View File

@@ -1,13 +1,11 @@
<UserControl <UserControl
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
@@ -15,6 +13,7 @@
d:DesignWidth="800" d:DesignWidth="800"
x:Class="Ryujinx.Ava.UI.Views.Input.ControllerInputView" x:Class="Ryujinx.Ava.UI.Views.Input.ControllerInputView"
x:DataType="viewModels:ControllerInputViewModel" x:DataType="viewModels:ControllerInputViewModel"
x:CompileBindings="True"
mc:Ignorable="d" mc:Ignorable="d"
Focusable="True"> Focusable="True">
<Design.DataContext> <Design.DataContext>
@@ -34,192 +33,10 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Orientation="Vertical"> 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 --> <!-- Button / JoyStick Settings -->
<Grid <Grid
Name="SettingButtons" Name="SettingButtons"
MinHeight="450" MinHeight="450">
FlowDirection="LeftToRight"
IsVisible="{Binding ShowSettings}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@@ -258,9 +75,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZL}" Text="{locale:Locale ControllerSettingsTriggerZL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonZl">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonZl, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonZl, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -274,9 +91,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerL}" Text="{locale:Locale ControllerSettingsTriggerL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonL">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonL, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonL, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -290,9 +107,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonMinus}" Text="{locale:Locale ControllerSettingsButtonMinus}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonMinus">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonMinus, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonMinus, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -312,100 +129,8 @@
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsLStick}" /> 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 --> <!-- Left Joystick Controller -->
<StackPanel <StackPanel Orientation="Vertical">
IsVisible="{Binding IsController}"
Orientation="Vertical">
<!-- Left Joystick Button --> <!-- Left Joystick Button -->
<StackPanel <StackPanel
Orientation="Horizontal"> Orientation="Horizontal">
@@ -416,9 +141,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}" Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="LeftStickButton">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.LeftControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.LeftStickButton, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -433,22 +158,22 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickStick}" Text="{locale:Locale ControllerSettingsStickStick}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Tag="stick"> <ToggleButton Name="LeftJoystick" Tag="stick">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.LeftJoystick, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.LeftJoystick, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<Separator <Separator
Margin="0,8,0,8" Margin="0,8,0,8"
Height="1" /> Height="1" />
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickX}"> <CheckBox IsChecked="{Binding Config.LeftInvertStickX}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" /> <TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickY}"> <CheckBox IsChecked="{Binding Config.LeftInvertStickY}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" /> <TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftRotate90}"> <CheckBox IsChecked="{Binding Config.LeftRotate90}">
<TextBlock Text="{locale:Locale ControllerSettingsRotate90}" /> <TextBlock Text="{locale:Locale ControllerSettingsRotate90}" />
</CheckBox> </CheckBox>
<Separator <Separator
@@ -469,11 +194,11 @@
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
SmallChange="0.01" SmallChange="0.01"
Minimum="0" Minimum="0"
Value="{ReflectionBinding Configuration.DeadzoneLeft, Mode=TwoWay}" /> Value="{Binding Config.DeadzoneLeft, Mode=TwoWay}" />
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Width="25" Width="25"
Text="{ReflectionBinding Configuration.DeadzoneLeft, StringFormat=\{0:0.00\}}" /> Text="{Binding Config.DeadzoneLeft, StringFormat=\{0:0.00\}}" />
</StackPanel> </StackPanel>
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
@@ -489,11 +214,11 @@
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
SmallChange="0.01" SmallChange="0.01"
Minimum="0" Minimum="0"
Value="{ReflectionBinding Configuration.RangeLeft, Mode=TwoWay}" /> Value="{Binding Config.RangeLeft, Mode=TwoWay}" />
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Width="25" Width="25"
Text="{ReflectionBinding Configuration.RangeLeft, StringFormat=\{0:0.00\}}" /> Text="{Binding Config.RangeLeft, StringFormat=\{0:0.00\}}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
@@ -526,9 +251,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadUp}" Text="{locale:Locale ControllerSettingsDPadUp}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="DpadUp">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.DpadUp, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.DpadUp, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -543,9 +268,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadDown}" Text="{locale:Locale ControllerSettingsDPadDown}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="DpadDown">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.DpadDown, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.DpadDown, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -560,9 +285,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadLeft}" Text="{locale:Locale ControllerSettingsDPadLeft}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="DpadLeft">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.DpadLeft, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.DpadLeft, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -577,9 +302,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadRight}" Text="{locale:Locale ControllerSettingsDPadRight}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="DpadRight">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.DpadRight, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.DpadRight, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -592,6 +317,13 @@
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Image
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1" BorderThickness="1"
@@ -613,92 +345,89 @@
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
SmallChange="0.01" SmallChange="0.01"
Minimum="0" Minimum="0"
Value="{ReflectionBinding Configuration.TriggerThreshold, Mode=TwoWay}" /> Value="{Binding Config.TriggerThreshold, Mode=TwoWay}" />
<TextBlock <TextBlock
Width="25" Width="25"
Text="{ReflectionBinding Configuration.TriggerThreshold, StringFormat=\{0:0.00\}}" /> Text="{Binding Config.TriggerThreshold, StringFormat=\{0:0.00\}}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Margin="0,4,0,0" Orientation="Vertical"
HorizontalAlignment="Center" IsVisible="{Binding HasSides}">
VerticalAlignment="Center" <StackPanel
IsVisible="{Binding !IsRight}" Margin="0,4,0,0"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}" IsVisible="{Binding IsLeft}"
TextAlignment="Center" /> Orientation="Horizontal">
<ToggleButton>
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.LeftButtonSr, Mode=TwoWay, Converter={StaticResource Key}}" Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> <ToggleButton Name="LeftButtonSr">
</StackPanel> <TextBlock
<StackPanel Text="{Binding Config.LeftButtonSr, Converter={StaticResource Key}}"
Margin="0,4,0,0" TextAlignment="Center" />
HorizontalAlignment="Center" </ToggleButton>
VerticalAlignment="Center" </StackPanel>
IsVisible="{Binding !IsRight}" <StackPanel
Orientation="Horizontal"> Margin="0,4,0,0"
<TextBlock
Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}" IsVisible="{Binding IsLeft}"
TextAlignment="Center" /> Orientation="Horizontal">
<ToggleButton>
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.LeftButtonSl, Mode=TwoWay, Converter={StaticResource Key}}" Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> <ToggleButton Name="LeftButtonSl">
</StackPanel> <TextBlock
<StackPanel Text="{Binding Config.LeftButtonSl, Converter={StaticResource Key}}"
Margin="0,4,0,0" TextAlignment="Center" />
HorizontalAlignment="Center" </ToggleButton>
VerticalAlignment="Center" </StackPanel>
IsVisible="{Binding !IsLeft}" <StackPanel
Orientation="Horizontal"> Margin="0,4,0,0"
<TextBlock
Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}" IsVisible="{Binding IsRight}"
TextAlignment="Center" /> Orientation="Horizontal">
<ToggleButton>
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.RightButtonSr, Mode=TwoWay, Converter={StaticResource Key}}" Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> <ToggleButton Name="RightButtonSr">
</StackPanel> <TextBlock
<StackPanel Text="{Binding Config.RightButtonSr, Converter={StaticResource Key}}"
Margin="0,4,0,0" TextAlignment="Center" />
HorizontalAlignment="Center" </ToggleButton>
VerticalAlignment="Center" </StackPanel>
IsVisible="{Binding !IsLeft}" <StackPanel
Orientation="Horizontal"> Margin="0,4,0,0"
<TextBlock
Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}" IsVisible="{Binding IsRight}"
TextAlignment="Center" /> Orientation="Horizontal">
<ToggleButton>
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.RightButtonSl, Mode=TwoWay, Converter={StaticResource Key}}" Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> <ToggleButton Name="RightButtonSl">
<TextBlock
Text="{Binding Config.RightButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- Controller Picture -->
<Image
Margin="0,10,0,0"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<!-- Motion + Rumble --> <!-- Motion + Rumble -->
<StackPanel <StackPanel
Margin="0,10,0,0" Margin="0,10,0,0"
@@ -710,8 +439,7 @@
BorderThickness="1" BorderThickness="1"
CornerRadius="5" CornerRadius="5"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch">
IsVisible="{Binding IsController}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@@ -721,7 +449,7 @@
Margin="10" Margin="10"
MinWidth="0" MinWidth="0"
Grid.Column="0" Grid.Column="0"
IsChecked="{ReflectionBinding Configuration.EnableMotion, Mode=TwoWay}"> IsChecked="{Binding Config.EnableMotion, Mode=TwoWay}">
<TextBlock Text="{locale:Locale ControllerSettingsMotion}" /> <TextBlock Text="{locale:Locale ControllerSettingsMotion}" />
</CheckBox> </CheckBox>
<Button <Button
@@ -737,7 +465,6 @@
BorderThickness="1" BorderThickness="1"
CornerRadius="5" CornerRadius="5"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
IsVisible="{Binding IsController}"
Margin="0,-1,0,0"> Margin="0,-1,0,0">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -748,7 +475,7 @@
Margin="10" Margin="10"
MinWidth="0" MinWidth="0"
Grid.Column="0" Grid.Column="0"
IsChecked="{ReflectionBinding Configuration.EnableRumble, Mode=TwoWay}"> IsChecked="{Binding Config.EnableRumble, Mode=TwoWay}">
<TextBlock Text="{locale:Locale ControllerSettingsRumble}" /> <TextBlock Text="{locale:Locale ControllerSettingsRumble}" />
</CheckBox> </CheckBox>
<Button <Button
@@ -794,9 +521,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZR}" Text="{locale:Locale ControllerSettingsTriggerZR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonZr">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonZr, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonZr, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -812,9 +539,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerR}" Text="{locale:Locale ControllerSettingsTriggerR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonR">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonR, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonR, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -830,15 +557,15 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonPlus}" Text="{locale:Locale ControllerSettingsButtonPlus}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonPlus">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonPlus, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonPlus, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
<!-- Right Joystick --> <!-- Right Buttons -->
<Border <Border
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1" BorderThickness="1"
@@ -865,9 +592,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonA}" Text="{locale:Locale ControllerSettingsButtonA}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonA">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonA, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonA, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -882,9 +609,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonB}" Text="{locale:Locale ControllerSettingsButtonB}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonB">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonB, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonB, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -899,9 +626,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonX}" Text="{locale:Locale ControllerSettingsButtonX}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonX">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonX, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonX, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -916,9 +643,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonY}" Text="{locale:Locale ControllerSettingsButtonY}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="ButtonY">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.ButtonY, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.ButtonY, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -938,100 +665,8 @@
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRStick}" /> 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 --> <!-- Right Joystick Controller -->
<StackPanel <StackPanel Orientation="Vertical">
IsVisible="{Binding IsController}"
Orientation="Vertical">
<!-- Right Joystick Button --> <!-- Right Joystick Button -->
<StackPanel <StackPanel
Orientation="Horizontal"> Orientation="Horizontal">
@@ -1042,9 +677,9 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}" Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton> <ToggleButton Name="RightStickButton">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.RightControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.RightStickButton, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
@@ -1060,20 +695,20 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickStick}" Text="{locale:Locale ControllerSettingsStickStick}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Tag="stick"> <ToggleButton Name="RightJoystick" Tag="stick">
<TextBlock <TextBlock
Text="{ReflectionBinding Configuration.RightJoystick, Mode=TwoWay, Converter={StaticResource Key}}" Text="{Binding Config.RightJoystick, Converter={StaticResource Key}}"
TextAlignment="Center" /> TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<Separator Margin="0,8,0,8" Height="1" /> <Separator Margin="0,8,0,8" Height="1" />
<CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickX}"> <CheckBox IsChecked="{Binding Config.RightInvertStickX}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" /> <TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickY}"> <CheckBox IsChecked="{Binding Config.RightInvertStickY}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" /> <TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{ReflectionBinding Configuration.RightRotate90}"> <CheckBox IsChecked="{Binding Config.RightRotate90}">
<TextBlock Text="{locale:Locale ControllerSettingsRotate90}" /> <TextBlock Text="{locale:Locale ControllerSettingsRotate90}" />
</CheckBox> </CheckBox>
<Separator Margin="0,8,0,8" Height="1" /> <Separator Margin="0,8,0,8" Height="1" />
@@ -1094,11 +729,11 @@
Padding="0" Padding="0"
VerticalAlignment="Center" VerticalAlignment="Center"
Minimum="0" Minimum="0"
Value="{ReflectionBinding Configuration.DeadzoneRight, Mode=TwoWay}" /> Value="{Binding Config.DeadzoneRight, Mode=TwoWay}" />
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Width="25" Width="25"
Text="{ReflectionBinding Configuration.DeadzoneRight, StringFormat=\{0:0.00\}}" /> Text="{Binding Config.DeadzoneRight, StringFormat=\{0:0.00\}}" />
</StackPanel> </StackPanel>
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
@@ -1114,11 +749,11 @@
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
SmallChange="0.01" SmallChange="0.01"
Minimum="0" Minimum="0"
Value="{ReflectionBinding Configuration.RangeRight, Mode=TwoWay}" /> Value="{Binding Config.RangeRight, Mode=TwoWay}" />
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Width="25" Width="25"
Text="{ReflectionBinding Configuration.RangeRight, StringFormat=\{0:0.00\}}" /> Text="{Binding Config.RangeRight, StringFormat=\{0:0.00\}}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

View File

@@ -1,35 +1,29 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System; using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
namespace Ryujinx.Ava.UI.Views.Input namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class ControllerInputView : UserControl public partial class ControllerInputView : UserControl
{ {
private bool _dialogOpen;
private ButtonKeyAssigner _currentAssigner; private ButtonKeyAssigner _currentAssigner;
internal ControllerInputViewModel ViewModel { get; set; }
public ControllerInputView() public ControllerInputView()
{ {
DataContext = ViewModel = new ControllerInputViewModel(this);
InitializeComponent(); InitializeComponent();
foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) 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; button.IsCheckedChanged += Button_IsCheckedChanged;
} }
@@ -67,14 +61,87 @@ namespace Ryujinx.Ava.UI.Views.Input
PointerPressed += MouseClick; PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. var viewModel = (DataContext as ControllerInputViewModel);
IKeyboard keyboard = (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick); IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) => _currentAssigner.ButtonAssigned += (sender, e) =>
{ {
if (e.IsAssigned) if (e.ButtonValue.HasValue)
{ {
ViewModel.IsModified = true; var buttonValue = e.ButtonValue.Value;
viewModel.ParentModel.IsModified = true;
switch (button.Name)
{
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsHidType<GamepadInputId>();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsHidType<GamepadInputId>();
break;
case "LeftJoystick":
viewModel.Config.LeftJoystick = buttonValue.AsHidType<StickInputId>();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsHidType<GamepadInputId>();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsHidType<GamepadInputId>();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsHidType<GamepadInputId>();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsHidType<GamepadInputId>();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsHidType<GamepadInputId>();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsHidType<GamepadInputId>();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsHidType<GamepadInputId>();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsHidType<GamepadInputId>();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsHidType<GamepadInputId>();
break;
case "RightJoystick":
viewModel.Config.RightJoystick = buttonValue.AsHidType<StickInputId>();
break;
}
} }
}; };
@@ -84,8 +151,6 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
if (_currentAssigner != null) if (_currentAssigner != null)
{ {
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel(); _currentAssigner.Cancel();
_currentAssigner = null; _currentAssigner = null;
button.IsChecked = false; button.IsChecked = false;
@@ -100,82 +165,34 @@ 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) private void MouseClick(object sender, PointerPressedEventArgs e)
{ {
bool shouldUnbind = false; bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
{
shouldUnbind = true;
}
_currentAssigner?.Cancel(shouldUnbind); _currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick; PointerPressed -= MouseClick;
} }
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) private IButtonAssigner CreateButtonAssigner(bool forStick)
{ {
if (ViewModel.IsModified && !_dialogOpen) IButtonAssigner assigner;
{
_dialogOpen = true;
var result = await ContentDialogHelper.CreateConfirmationDialog( var controllerInputViewModel = DataContext as ControllerInputViewModel;
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage],
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes) assigner = new GamepadButtonAssigner(
{ controllerInputViewModel.ParentModel.SelectedGamepad,
ViewModel.Save(); (controllerInputViewModel.ParentModel.Config as StandardControllerInputConfig).TriggerThreshold,
} forStick);
_dialogOpen = false; return assigner;
ViewModel.IsModified = false;
if (e.AddedItems.Count > 0)
{
var player = (PlayerModel)e.AddedItems[0];
ViewModel.PlayerId = player.Id;
}
}
} }
public void Dispose() protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e);
_currentAssigner?.Cancel(); _currentAssigner?.Cancel();
_currentAssigner = null; _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,208 @@
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;
using Key = Ryujinx.Common.Configuration.Hid.Key;
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;
}
if (_currentAssigner == null)
{
_currentAssigner = new ButtonKeyAssigner(button);
Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick;
var viewModel = (DataContext as KeyboardInputViewModel);
IKeyboard keyboard = (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner();
_currentAssigner.ButtonAssigned += (sender, e) =>
{
if (e.ButtonValue.HasValue)
{
var buttonValue = e.ButtonValue.Value;
viewModel.ParentModel.IsModified = true;
switch (button.Name)
{
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
break;
case "LeftStickUp":
viewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
break;
case "LeftStickDown":
viewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
break;
case "LeftStickRight":
viewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
break;
case "LeftStickLeft":
viewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
break;
case "RightStickUp":
viewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
break;
case "RightStickDown":
viewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
break;
case "RightStickRight":
viewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
break;
case "RightStickLeft":
viewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
break;
}
}
};
_currentAssigner.GetInputAndAssign(assigner, keyboard);
}
else
{
if (_currentAssigner != null)
{
_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()
{
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

@@ -6,7 +6,7 @@
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
mc:Ignorable="d" mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView" x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
x:DataType="viewModels:MotionInputViewModel" x:DataType="viewModels:MotionInputViewModel"

View File

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

View File

@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
mc:Ignorable="d" mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView" x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
x:DataType="viewModels:RumbleInputViewModel" x:DataType="viewModels:RumbleInputViewModel"

View File

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

View File

@@ -9,6 +9,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
mc:Ignorable="d" mc:Ignorable="d"
x:DataType="viewModels:SettingsViewModel" x:DataType="viewModels:SettingsViewModel"
x:CompileBindings="True"
Focusable="True"> Focusable="True">
<Design.DataContext> <Design.DataContext>
<viewModels:SettingsViewModel /> <viewModels:SettingsViewModel />
@@ -16,6 +17,23 @@
<UserControl.Resources> <UserControl.Resources>
<helpers:KeyValueConverter x:Key="Key" /> <helpers:KeyValueConverter x:Key="Key" />
</UserControl.Resources> </UserControl.Resources>
<UserControl.Styles>
<Style Selector="StackPanel > StackPanel">
<Setter Property="Margin" Value="10, 0, 0, 0" />
<Setter Property="Orientation" Value="Horizontal" />
</Style>
<Style Selector="StackPanel > StackPanel > TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Width" Value="230" />
</Style>
<Style Selector="ToggleButton">
<Setter Property="Width" Value="90" />
<Setter Property="Height" Value="27" />
</Style>
<Style Selector="ToggleButton > TextBlock">
<Setter Property="TextAlignment" Value="Center" />
</Style>
</UserControl.Styles>
<ScrollViewer <ScrollViewer
Name="HotkeysPage" Name="HotkeysPage"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@@ -23,81 +41,69 @@
HorizontalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<Border Classes="settings"> <Border Classes="settings">
<StackPanel Margin="10" Orientation="Vertical" Spacing="10"> <StackPanel
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabHotkeysHotkeys}" /> Name="SettingButtons"
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> Margin="10"
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" Width="230" /> Orientation="Vertical"
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> Spacing="10">
<TextBlock <TextBlock
Text="{Binding KeyboardHotkeys.ToggleVsync, Mode=TwoWay, Converter={StaticResource Key}}" Classes="h1"
TextAlignment="Center" /> Text="{locale:Locale SettingsTabHotkeysHotkeys}" />
<StackPanel>
<TextBlock Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" />
<ToggleButton Name="ToggleVsync">
<TextBlock Text="{Binding KeyboardHotkey.ToggleVsync, Converter={StaticResource Key}}" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel>
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" Width="230" /> <TextBlock Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> <ToggleButton Name="Screenshot">
<TextBlock <TextBlock Text="{Binding KeyboardHotkey.Screenshot, Converter={StaticResource Key}}" />
Text="{Binding KeyboardHotkeys.Screenshot, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel>
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" /> <TextBlock Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> <ToggleButton Name="ShowUI">
<TextBlock <TextBlock Text="{Binding KeyboardHotkey.ShowUI, Converter={StaticResource Key}}" />
Text="{Binding KeyboardHotkeys.ShowUI, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel>
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" Width="230" /> <TextBlock Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> <ToggleButton Name="Pause">
<TextBlock <TextBlock Text="{Binding KeyboardHotkey.Pause, Converter={StaticResource Key}}" />
Text="{Binding KeyboardHotkeys.Pause, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel>
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" Width="230" /> <TextBlock Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> <ToggleButton Name="ToggleMute">
<TextBlock <TextBlock Text="{Binding KeyboardHotkey.ToggleMute, Converter={StaticResource Key}}" />
Text="{Binding KeyboardHotkeys.ToggleMute, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel>
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" Width="230" /> <TextBlock Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> <ToggleButton Name="ResScaleUp">
<TextBlock <TextBlock Text="{Binding KeyboardHotkey.ResScaleUp, Converter={StaticResource Key}}" />
Text="{Binding KeyboardHotkeys.ResScaleUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel>
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" Width="230" /> <TextBlock Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> <ToggleButton Name="ResScaleDown">
<TextBlock <TextBlock Text="{Binding KeyboardHotkey.ResScaleDown, Converter={StaticResource Key}}" />
Text="{Binding KeyboardHotkeys.ResScaleDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel>
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" Width="230" /> <TextBlock Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> <ToggleButton Name="VolumeUp">
<TextBlock <TextBlock Text="{Binding KeyboardHotkey.VolumeUp, Converter={StaticResource Key}}" />
Text="{Binding KeyboardHotkeys.VolumeUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel>
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" Width="230" /> <TextBlock Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked"> <ToggleButton Name="VolumeDown">
<TextBlock <TextBlock Text="{Binding KeyboardHotkey.VolumeDown, Converter={StaticResource Key}}" />
Text="{Binding KeyboardHotkeys.VolumeDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>

View File

@@ -2,10 +2,13 @@ using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Ryujinx.Ava.Input; using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.Views.Settings namespace Ryujinx.Ava.UI.Views.Settings
{ {
@@ -17,9 +20,28 @@ namespace Ryujinx.Ava.UI.Views.Settings
public SettingsHotkeysView() public SettingsHotkeysView()
{ {
InitializeComponent(); InitializeComponent();
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
{
if (visual is ToggleButton button and not CheckBox)
{
button.IsCheckedChanged += Button_IsCheckedChanged;
}
}
_avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this); _avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this);
} }
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (!_currentAssigner?.ToggledButton?.IsPointerOver ?? false)
{
_currentAssigner.Cancel();
}
}
private void MouseClick(object sender, PointerPressedEventArgs e) private void MouseClick(object sender, PointerPressedEventArgs e)
{ {
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
@@ -29,53 +51,94 @@ namespace Ryujinx.Ava.UI.Views.Settings
PointerPressed -= MouseClick; PointerPressed -= MouseClick;
} }
private void Button_Checked(object sender, RoutedEventArgs e) private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
{ {
if (sender is ToggleButton button) if (sender is ToggleButton button)
{ {
if (_currentAssigner != null && button == _currentAssigner.ToggledButton) if ((bool)button.IsChecked)
{ {
return; if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
} {
return;
}
if (_currentAssigner == null && button.IsChecked != null && (bool)button.IsChecked) if (_currentAssigner == null)
{ {
_currentAssigner = new ButtonKeyAssigner(button); _currentAssigner = new ButtonKeyAssigner(button);
this.Focus(NavigationMethod.Pointer); this.Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick; PointerPressed += MouseClick;
var keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad(_avaloniaKeyboardDriver.GamepadsIds[0]); var keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad("0");
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
_currentAssigner.GetInputAndAssign(assigner); _currentAssigner.ButtonAssigned += (sender, e) =>
{
if (e.ButtonValue.HasValue)
{
var viewModel = (DataContext) as SettingsViewModel;
var buttonValue = e.ButtonValue.Value;
switch (button.Name)
{
case "ToggleVsync":
viewModel.KeyboardHotkey.ToggleVsync = buttonValue.AsHidType<Key>();
break;
case "Screenshot":
viewModel.KeyboardHotkey.Screenshot = buttonValue.AsHidType<Key>();
break;
case "ShowUI":
viewModel.KeyboardHotkey.ShowUI = buttonValue.AsHidType<Key>();
break;
case "Pause":
viewModel.KeyboardHotkey.Pause = buttonValue.AsHidType<Key>();
break;
case "ToggleMute":
viewModel.KeyboardHotkey.ToggleMute = buttonValue.AsHidType<Key>();
break;
case "ResScaleUp":
viewModel.KeyboardHotkey.ResScaleUp = buttonValue.AsHidType<Key>();
break;
case "ResScaleDown":
viewModel.KeyboardHotkey.ResScaleDown = buttonValue.AsHidType<Key>();
break;
case "VolumeUp":
viewModel.KeyboardHotkey.VolumeUp = buttonValue.AsHidType<Key>();
break;
case "VolumeDown":
viewModel.KeyboardHotkey.VolumeDown = buttonValue.AsHidType<Key>();
break;
}
}
};
_currentAssigner.GetInputAndAssign(assigner, keyboard);
}
else
{
if (_currentAssigner != null)
{
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
} }
else else
{ {
if (_currentAssigner != null) _currentAssigner?.Cancel();
{ _currentAssigner = null;
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
} }
} }
} }
private void Button_Unchecked(object sender, RoutedEventArgs e)
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
public void Dispose() public void Dispose()
{ {
_currentAssigner?.Cancel(); _currentAssigner?.Cancel();
_currentAssigner = null; _currentAssigner = null;
_avaloniaKeyboardDriver.Dispose();
} }
} }
} }

View File

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

View File

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

View File

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