Avalonia UI - Part 1 (#3270)

* avalonia part 1

* remove vulkan ui backend

* move ui common files to ui common project

* get name for oading screen from device

* rebase.

* review 1

* review 1.1

* review

* cleanup

* addressed review

* use cancellation token

* review

* review

* rebased

* cancel library loading when closing window

* remove star  image, use fonticon instead

* delete render control frame buffer when game ends. change position of fav star

* addressed @Thog review

* ensure the right ui is downloaded in updates

* fix crash when showing not supported dialog during controller request

* add prefix to artifact names

* Auto-format Avalonia project

* Fix input

* Fix build, simplify app disposal

* remove nv stutter thread

* addressed review

* add missing change

* maintain window size if new size is zero length

* add game, handheld, docked to local

* reverse scale main window

* Update de_DE.json

* Update de_DE.json

* Update de_DE.json

* Update italian json

* Update it_IT.json

* let render timer poll with no wait

* remove unused code

* more unused code

* enabled tiered compilation and trimming

* check if window event is not closed before signaling

* fix atmospher case

* locale fix

* locale fix

* remove explicit tiered compilation declarations

* Remove ) it_IT.json

* Remove ) de_DE.json

* Update it_IT.json

* Update pt_BR locale with latest strings

* Remove ')'

* add more strings to locale

* update locale

* remove extra slash

* remove extra slash

* set firmware version to 0 if key's not found

* fix

* revert timer changes

* lock  on object instead

* Update it_IT.json

* remove unused method

* add load screen text to locale

* drop swap event

* Update de_DE.json

* Update de_DE.json

* do null check when stopping emulator

* Update de_DE.json

* Create tr_TR.json

* Add tr_TR

* Add tr_TR + Turkish

* Update it_IT.json

* Update Ryujinx.Ava/Input/AvaloniaMappingHelper.cs

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

* Apply suggestions from code review

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

* Apply suggestions from code review

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

* addressed review

* Update Ryujinx.Ava/Ui/Backend/OpenGl/OpenGlRenderTarget.cs

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

* use avalonia's inbuilt renderer on linux

* removed whitespace

* workaround for queue render crash with vsync off

* drop custom backend

* format files

* fix not closing issue

* remove warnings

* rebase

* update avalonia library

* Reposition the Text and Button on About Page

* Assign build version

* Remove appveyor text

Co-authored-by: gdk <gab.dark.100@gmail.com>
Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com>
Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com>
Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: MostlyWhat <78652091+MostlyWhat@users.noreply.github.com>
This commit is contained in:
Emmanuel Hansen 2022-05-15 11:30:15 +00:00 committed by GitHub
parent 9ba73ffbe5
commit deb99d2cae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
161 changed files with 17179 additions and 855 deletions

View file

@ -0,0 +1,205 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using System.Numerics;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Input
{
public class AvaloniaKeyboard : IKeyboard
{
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly AvaloniaKeyboardDriver _driver;
private readonly object _userMappingLock = new();
private StandardKeyboardInputConfig _configuration;
private bool HasConfiguration => _configuration != null;
public string Id { get; }
public string Name { get; }
public bool IsConnected => true;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
{
_driver = driver;
Id = id;
Name = name;
_buttonsUserMapping = new List<ButtonMappingEntry>();
}
public void Dispose() { }
public KeyboardStateSnapshot GetKeyboardStateSnapshot()
{
return IKeyboard.GetStateSnapshot(this);
}
public GamepadStateSnapshot GetMappedStateSnapshot()
{
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
GamepadStateSnapshot result = default;
lock (_userMappingLock)
{
if (!HasConfiguration)
{
return result;
}
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
{
if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
{
continue;
}
// Do not touch state of the button already pressed
if (!result.IsPressed(entry.To))
{
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
}
}
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
}
return result;
}
public GamepadStateSnapshot GetStateSnapshot()
{
throw new NotSupportedException();
}
public (float, float) GetStick(StickInputId inputId)
{
throw new NotSupportedException();
}
public bool IsPressed(GamepadButtonInputId inputId)
{
throw new NotSupportedException();
}
public bool IsPressed(Key key)
{
try
{
return _driver.IsPressed(key);
}
catch
{
return false;
}
}
public void SetConfiguration(InputConfig configuration)
{
lock (_userMappingLock)
{
_configuration = (StandardKeyboardInputConfig)configuration;
_buttonsUserMapping.Clear();
// Left joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
// Finally right joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
}
}
public void SetTriggerThreshold(float triggerThreshold) { }
public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { }
public Vector3 GetMotionData(MotionInputId inputId) => Vector3.Zero;
private static float ConvertRawStickValue(short value)
{
const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
return value * ConvertRate;
}
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
{
short stickX = 0;
short stickY = 0;
if (snapshot.IsPressed((Key)stickConfig.StickUp))
{
stickY += 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickDown))
{
stickY -= 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickRight))
{
stickX += 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
{
stickX -= 1;
}
Vector2 stick = new(stickX, stickY);
stick = Vector2.Normalize(stick);
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
}
public void Clear()
{
_driver?.ResetKeys();
}
private class ButtonMappingEntry
{
public readonly Key From;
public readonly GamepadButtonInputId To;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
}
}
}

View file

@ -0,0 +1,113 @@
using Avalonia.Controls;
using Avalonia.Input;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using AvaKey = Avalonia.Input.Key;
using Key = Ryujinx.Input.Key;
using TextInputEventArgs = OpenTK.Windowing.Common.TextInputEventArgs;
namespace Ryujinx.Ava.Input
{
public class AvaloniaKeyboardDriver : IGamepadDriver
{
private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
private readonly Control _control;
private readonly HashSet<AvaKey> _pressedKeys;
public event EventHandler<KeyEventArgs> KeyPressed;
public event EventHandler<KeyEventArgs> KeyRelease;
public event EventHandler<TextInputEventArgs> TextInput;
public string DriverName => "Avalonia";
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
public AvaloniaKeyboardDriver(Control control)
{
_control = control;
_pressedKeys = new HashSet<AvaKey>();
_control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease;
_control.TextInput += Control_TextInput;
}
private void Control_TextInput(object sender, Avalonia.Input.TextInputEventArgs e)
{
TextInput?.Invoke(this, new TextInputEventArgs(e.Text.First()));
}
public event Action<string> OnGamepadConnected
{
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
remove { }
}
public void Dispose()
{
Dispose(true);
}
public IGamepad GetGamepad(string id)
{
if (!_keyboardIdentifers[0].Equals(id))
{
return null;
}
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance["AllKeyboards"]);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_control.KeyUp -= OnKeyPress;
_control.KeyDown -= OnKeyRelease;
}
}
protected void OnKeyPress(object sender, KeyEventArgs args)
{
AvaKey key = args.Key;
_pressedKeys.Add(args.Key);
KeyPressed?.Invoke(this, args);
}
protected void OnKeyRelease(object sender, KeyEventArgs args)
{
_pressedKeys.Remove(args.Key);
KeyRelease?.Invoke(this, args);
}
internal bool IsPressed(Key key)
{
if (key == Key.Unbound || key == Key.Unknown)
{
return false;
}
AvaloniaMappingHelper.TryGetAvaKey(key, out var nativeKey);
return _pressedKeys.Contains(nativeKey);
}
public void ResetKeys()
{
_pressedKeys.Clear();
}
}
}

View file

@ -0,0 +1,188 @@
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using AvaKey = Avalonia.Input.Key;
namespace Ryujinx.Ava.Input
{
public static class AvaloniaMappingHelper
{
private static readonly AvaKey[] _keyMapping = new AvaKey[(int)Key.Count]
{
// NOTE: Invalid
AvaKey.None,
AvaKey.LeftShift,
AvaKey.RightShift,
AvaKey.LeftCtrl,
AvaKey.RightCtrl,
AvaKey.LeftAlt,
AvaKey.RightAlt,
AvaKey.LWin,
AvaKey.RWin,
AvaKey.Apps,
AvaKey.F1,
AvaKey.F2,
AvaKey.F3,
AvaKey.F4,
AvaKey.F5,
AvaKey.F6,
AvaKey.F7,
AvaKey.F8,
AvaKey.F9,
AvaKey.F10,
AvaKey.F11,
AvaKey.F12,
AvaKey.F13,
AvaKey.F14,
AvaKey.F15,
AvaKey.F16,
AvaKey.F17,
AvaKey.F18,
AvaKey.F19,
AvaKey.F20,
AvaKey.F21,
AvaKey.F22,
AvaKey.F23,
AvaKey.F24,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.None,
AvaKey.Up,
AvaKey.Down,
AvaKey.Left,
AvaKey.Right,
AvaKey.Return,
AvaKey.Escape,
AvaKey.Space,
AvaKey.Tab,
AvaKey.Back,
AvaKey.Insert,
AvaKey.Delete,
AvaKey.PageUp,
AvaKey.PageDown,
AvaKey.Home,
AvaKey.End,
AvaKey.CapsLock,
AvaKey.Scroll,
AvaKey.Print,
AvaKey.Pause,
AvaKey.NumLock,
AvaKey.Clear,
AvaKey.NumPad0,
AvaKey.NumPad1,
AvaKey.NumPad2,
AvaKey.NumPad3,
AvaKey.NumPad4,
AvaKey.NumPad5,
AvaKey.NumPad6,
AvaKey.NumPad7,
AvaKey.NumPad8,
AvaKey.NumPad9,
AvaKey.Divide,
AvaKey.Multiply,
AvaKey.Subtract,
AvaKey.Add,
AvaKey.Decimal,
AvaKey.Enter,
AvaKey.A,
AvaKey.B,
AvaKey.C,
AvaKey.D,
AvaKey.E,
AvaKey.F,
AvaKey.G,
AvaKey.H,
AvaKey.I,
AvaKey.J,
AvaKey.K,
AvaKey.L,
AvaKey.M,
AvaKey.N,
AvaKey.O,
AvaKey.P,
AvaKey.Q,
AvaKey.R,
AvaKey.S,
AvaKey.T,
AvaKey.U,
AvaKey.V,
AvaKey.W,
AvaKey.X,
AvaKey.Y,
AvaKey.Z,
AvaKey.D0,
AvaKey.D1,
AvaKey.D2,
AvaKey.D3,
AvaKey.D4,
AvaKey.D5,
AvaKey.D6,
AvaKey.D7,
AvaKey.D8,
AvaKey.D9,
AvaKey.OemTilde,
AvaKey.OemTilde,AvaKey.OemMinus,
AvaKey.OemPlus,
AvaKey.OemOpenBrackets,
AvaKey.OemCloseBrackets,
AvaKey.OemSemicolon,
AvaKey.OemQuotes,
AvaKey.OemComma,
AvaKey.OemPeriod,
AvaKey.OemQuestion,
AvaKey.OemBackslash,
// NOTE: invalid
AvaKey.None
};
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
static AvaloniaMappingHelper()
{
var inputKeys = Enum.GetValues(typeof(Key));
// Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
_avaKeyMapping = new Dictionary<AvaKey, Key>();
foreach (var key in inputKeys)
{
if (TryGetAvaKey((Key)key, out var index))
{
_avaKeyMapping[index] = (Key)key;
}
}
}
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
{
var keyExist = (int)key < _keyMapping.Length;
if (keyExist)
{
avaKey = _keyMapping[(int)key];
}
else
{
avaKey = AvaKey.None;
}
return keyExist;
}
public static Key ToInputKey(AvaKey key)
{
return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown);
}
}
}

View file

@ -0,0 +1,90 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Input;
using System;
using System.Drawing;
using System.Numerics;
namespace Ryujinx.Ava.Input
{
public class AvaloniaMouse : IMouse
{
private AvaloniaMouseDriver _driver;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public string Id => "0";
public string Name => "AvaloniaMouse";
public bool IsConnected => true;
public bool[] Buttons => _driver.PressedButtons;
public AvaloniaMouse(AvaloniaMouseDriver driver)
{
_driver = driver;
}
public Size ClientSize => _driver.GetClientSize();
public Vector2 GetPosition()
{
return _driver.CurrentPosition;
}
public Vector2 GetScroll()
{
return _driver.Scroll;
}
public GamepadStateSnapshot GetMappedStateSnapshot()
{
throw new NotImplementedException();
}
public Vector3 GetMotionData(MotionInputId inputId)
{
throw new NotImplementedException();
}
public GamepadStateSnapshot GetStateSnapshot()
{
throw new NotImplementedException();
}
public (float, float) GetStick(StickInputId inputId)
{
throw new NotImplementedException();
}
public bool IsButtonPressed(MouseButton button)
{
return _driver.IsButtonPressed(button);
}
public bool IsPressed(GamepadButtonInputId inputId)
{
throw new NotImplementedException();
}
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
throw new NotImplementedException();
}
public void SetConfiguration(InputConfig configuration)
{
throw new NotImplementedException();
}
public void SetTriggerThreshold(float triggerThreshold)
{
throw new NotImplementedException();
}
public void Dispose()
{
_driver = null;
}
}
}

View file

@ -0,0 +1,129 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Threading;
using Ryujinx.Input;
using System;
using System.Numerics;
using MouseButton = Ryujinx.Input.MouseButton;
using Size = System.Drawing.Size;
namespace Ryujinx.Ava.Input
{
public class AvaloniaMouseDriver : IGamepadDriver
{
private Control _widget;
private bool _isDisposed;
public bool[] PressedButtons { get; }
public Vector2 CurrentPosition { get; private set; }
public Vector2 Scroll { get; private set; }
public AvaloniaMouseDriver(Control parent)
{
_widget = parent;
_widget.PointerMoved += Parent_PointerMovedEvent;
_widget.PointerPressed += Parent_PointerPressEvent;
_widget.PointerReleased += Parent_PointerReleaseEvent;
_widget.PointerWheelChanged += Parent_ScrollEvent;
PressedButtons = new bool[(int)MouseButton.Count];
}
private void Parent_ScrollEvent(object o, PointerWheelEventArgs args)
{
Scroll = new Vector2((float)args.Delta.X, (float)args.Delta.Y);
}
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
{
var pointerProperties = args.GetCurrentPoint(_widget).Properties;
PressedButtons[(int)args.InitialPressMouseButton - 1] = false;
}
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
{
var pointerProperties = args.GetCurrentPoint(_widget).Properties;
PressedButtons[(int)pointerProperties.PointerUpdateKind] = true;
}
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
{
var position = args.GetPosition(_widget);
CurrentPosition = new Vector2((float)position.X, (float)position.Y);
}
public void SetMousePressed(MouseButton button)
{
PressedButtons[(int)button] = true;
}
public void SetMouseReleased(MouseButton button)
{
PressedButtons[(int)button] = false;
}
public void SetPosition(double x, double y)
{
CurrentPosition = new Vector2((float)x, (float)y);
}
public bool IsButtonPressed(MouseButton button)
{
return PressedButtons[(int)button];
}
public Size GetClientSize()
{
Size size = new();
Dispatcher.UIThread.InvokeAsync(() =>
{
size = new Size((int)_widget.Bounds.Width, (int)_widget.Bounds.Height);
}).Wait();
return size;
}
public string DriverName => "Avalonia";
public event Action<string> OnGamepadConnected
{
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
remove { }
}
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public IGamepad GetGamepad(string id)
{
return new AvaloniaMouse(this);
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
_widget.PointerMoved -= Parent_PointerMovedEvent;
_widget.PointerPressed -= Parent_PointerPressEvent;
_widget.PointerReleased -= Parent_PointerReleaseEvent;
_widget.PointerWheelChanged -= Parent_ScrollEvent;
_widget = null;
}
}
}