forked from Mirror/Ryujinx
Window related changes (#308)
* Use integer math for touch screen * Sleep polling thread * Rework host input * Add fullscreen with F11 or Alt+Enter * Address feedback
This commit is contained in:
parent
51605fafc0
commit
7a308d9e73
4 changed files with 376 additions and 206 deletions
|
@ -14,11 +14,6 @@ namespace Ryujinx
|
||||||
public static JoyConKeyboard JoyConKeyboard { get; private set; }
|
public static JoyConKeyboard JoyConKeyboard { get; private set; }
|
||||||
public static JoyConController JoyConController { get; private set; }
|
public static JoyConController JoyConController { get; private set; }
|
||||||
|
|
||||||
public static float GamePadDeadzone { get; private set; }
|
|
||||||
public static bool GamePadEnable { get; private set; }
|
|
||||||
public static int GamePadIndex { get; private set; }
|
|
||||||
public static float GamePadTriggerThreshold { get; private set; }
|
|
||||||
|
|
||||||
public static void Read(Logger Log)
|
public static void Read(Logger Log)
|
||||||
{
|
{
|
||||||
string IniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
string IniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||||
|
@ -37,11 +32,6 @@ namespace Ryujinx
|
||||||
Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn")));
|
Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn")));
|
||||||
Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error")));
|
Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error")));
|
||||||
|
|
||||||
GamePadEnable = Convert.ToBoolean(Parser.Value("GamePad_Enable"));
|
|
||||||
GamePadIndex = Convert.ToInt32 (Parser.Value("GamePad_Index"));
|
|
||||||
GamePadDeadzone = (float)Convert.ToDouble (Parser.Value("GamePad_Deadzone"), CultureInfo.InvariantCulture);
|
|
||||||
GamePadTriggerThreshold = (float)Convert.ToDouble (Parser.Value("GamePad_Trigger_Threshold"), CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries);
|
string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
//When the classes are specified on the list, we only
|
//When the classes are specified on the list, we only
|
||||||
|
@ -70,9 +60,9 @@ namespace Ryujinx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JoyConKeyboard = new JoyConKeyboard
|
JoyConKeyboard = new JoyConKeyboard(
|
||||||
{
|
|
||||||
Left = new JoyConKeyboardLeft
|
new JoyConKeyboardLeft
|
||||||
{
|
{
|
||||||
StickUp = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Up")),
|
StickUp = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Up")),
|
||||||
StickDown = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Down")),
|
StickDown = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Down")),
|
||||||
|
@ -88,7 +78,7 @@ namespace Ryujinx
|
||||||
ButtonZL = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Button_ZL"))
|
ButtonZL = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Button_ZL"))
|
||||||
},
|
},
|
||||||
|
|
||||||
Right = new JoyConKeyboardRight
|
new JoyConKeyboardRight
|
||||||
{
|
{
|
||||||
StickUp = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Up")),
|
StickUp = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Up")),
|
||||||
StickDown = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Down")),
|
StickDown = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Down")),
|
||||||
|
@ -102,37 +92,69 @@ namespace Ryujinx
|
||||||
ButtonPlus = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_Plus")),
|
ButtonPlus = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_Plus")),
|
||||||
ButtonR = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_R")),
|
ButtonR = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_R")),
|
||||||
ButtonZR = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_ZR"))
|
ButtonZR = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_ZR"))
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
JoyConController = new JoyConController
|
JoyConController = new JoyConController(
|
||||||
{
|
|
||||||
Left = new JoyConControllerLeft
|
Convert.ToBoolean(Parser.Value("GamePad_Enable")),
|
||||||
|
Convert.ToInt32 (Parser.Value("GamePad_Index")),
|
||||||
|
(float)Convert.ToDouble (Parser.Value("GamePad_Deadzone"), CultureInfo.InvariantCulture),
|
||||||
|
(float)Convert.ToDouble (Parser.Value("GamePad_Trigger_Threshold"), CultureInfo.InvariantCulture),
|
||||||
|
|
||||||
|
new JoyConControllerLeft
|
||||||
{
|
{
|
||||||
Stick = Parser.Value("Controls_Left_JoyConController_Stick"),
|
Stick = ToID(Parser.Value("Controls_Left_JoyConController_Stick")),
|
||||||
StickButton = Parser.Value("Controls_Left_JoyConController_Stick_Button"),
|
StickButton = ToID(Parser.Value("Controls_Left_JoyConController_Stick_Button")),
|
||||||
DPadUp = Parser.Value("Controls_Left_JoyConController_DPad_Up"),
|
DPadUp = ToID(Parser.Value("Controls_Left_JoyConController_DPad_Up")),
|
||||||
DPadDown = Parser.Value("Controls_Left_JoyConController_DPad_Down"),
|
DPadDown = ToID(Parser.Value("Controls_Left_JoyConController_DPad_Down")),
|
||||||
DPadLeft = Parser.Value("Controls_Left_JoyConController_DPad_Left"),
|
DPadLeft = ToID(Parser.Value("Controls_Left_JoyConController_DPad_Left")),
|
||||||
DPadRight = Parser.Value("Controls_Left_JoyConController_DPad_Right"),
|
DPadRight = ToID(Parser.Value("Controls_Left_JoyConController_DPad_Right")),
|
||||||
ButtonMinus = Parser.Value("Controls_Left_JoyConController_Button_Minus"),
|
ButtonMinus = ToID(Parser.Value("Controls_Left_JoyConController_Button_Minus")),
|
||||||
ButtonL = Parser.Value("Controls_Left_JoyConController_Button_L"),
|
ButtonL = ToID(Parser.Value("Controls_Left_JoyConController_Button_L")),
|
||||||
ButtonZL = Parser.Value("Controls_Left_JoyConController_Button_ZL")
|
ButtonZL = ToID(Parser.Value("Controls_Left_JoyConController_Button_ZL"))
|
||||||
},
|
},
|
||||||
|
|
||||||
Right = new JoyConControllerRight
|
new JoyConControllerRight
|
||||||
{
|
{
|
||||||
Stick = Parser.Value("Controls_Right_JoyConController_Stick"),
|
Stick = ToID(Parser.Value("Controls_Right_JoyConController_Stick")),
|
||||||
StickButton = Parser.Value("Controls_Right_JoyConController_Stick_Button"),
|
StickButton = ToID(Parser.Value("Controls_Right_JoyConController_Stick_Button")),
|
||||||
ButtonA = Parser.Value("Controls_Right_JoyConController_Button_A"),
|
ButtonA = ToID(Parser.Value("Controls_Right_JoyConController_Button_A")),
|
||||||
ButtonB = Parser.Value("Controls_Right_JoyConController_Button_B"),
|
ButtonB = ToID(Parser.Value("Controls_Right_JoyConController_Button_B")),
|
||||||
ButtonX = Parser.Value("Controls_Right_JoyConController_Button_X"),
|
ButtonX = ToID(Parser.Value("Controls_Right_JoyConController_Button_X")),
|
||||||
ButtonY = Parser.Value("Controls_Right_JoyConController_Button_Y"),
|
ButtonY = ToID(Parser.Value("Controls_Right_JoyConController_Button_Y")),
|
||||||
ButtonPlus = Parser.Value("Controls_Right_JoyConController_Button_Plus"),
|
ButtonPlus = ToID(Parser.Value("Controls_Right_JoyConController_Button_Plus")),
|
||||||
ButtonR = Parser.Value("Controls_Right_JoyConController_Button_R"),
|
ButtonR = ToID(Parser.Value("Controls_Right_JoyConController_Button_R")),
|
||||||
ButtonZR = Parser.Value("Controls_Right_JoyConController_Button_ZR")
|
ButtonZR = ToID(Parser.Value("Controls_Right_JoyConController_Button_ZR"))
|
||||||
}
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
private static ControllerInputID ToID(string Key)
|
||||||
|
{
|
||||||
|
switch (Key.ToUpper())
|
||||||
|
{
|
||||||
|
case "LSTICK": return ControllerInputID.LStick;
|
||||||
|
case "DPADUP": return ControllerInputID.DPadUp;
|
||||||
|
case "DPADDOWN": return ControllerInputID.DPadDown;
|
||||||
|
case "DPADLEFT": return ControllerInputID.DPadLeft;
|
||||||
|
case "DPADRIGHT": return ControllerInputID.DPadRight;
|
||||||
|
case "BACK": return ControllerInputID.Back;
|
||||||
|
case "LSHOULDER": return ControllerInputID.LShoulder;
|
||||||
|
case "LTRIGGER": return ControllerInputID.LTrigger;
|
||||||
|
|
||||||
|
case "RSTICK": return ControllerInputID.RStick;
|
||||||
|
case "A": return ControllerInputID.A;
|
||||||
|
case "B": return ControllerInputID.B;
|
||||||
|
case "X": return ControllerInputID.X;
|
||||||
|
case "Y": return ControllerInputID.Y;
|
||||||
|
case "START": return ControllerInputID.Start;
|
||||||
|
case "RSHOULDER": return ControllerInputID.RShoulder;
|
||||||
|
case "RTRIGGER": return ControllerInputID.RTrigger;
|
||||||
|
|
||||||
|
case "LJOYSTICK": return ControllerInputID.LJoystick;
|
||||||
|
case "RJOYSTICK": return ControllerInputID.RJoystick;
|
||||||
|
|
||||||
|
default: return ControllerInputID.Invalid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ using OpenTK.Input;
|
||||||
using Ryujinx.Graphics.Gal;
|
using Ryujinx.Graphics.Gal;
|
||||||
using Ryujinx.HLE;
|
using Ryujinx.HLE;
|
||||||
using Ryujinx.HLE.Input;
|
using Ryujinx.HLE.Input;
|
||||||
|
using Ryujinx.UI.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
|
@ -16,9 +17,6 @@ namespace Ryujinx
|
||||||
private const int TouchScreenWidth = 1280;
|
private const int TouchScreenWidth = 1280;
|
||||||
private const int TouchScreenHeight = 720;
|
private const int TouchScreenHeight = 720;
|
||||||
|
|
||||||
private const float TouchScreenRatioX = (float)TouchScreenWidth / TouchScreenHeight;
|
|
||||||
private const float TouchScreenRatioY = (float)TouchScreenHeight / TouchScreenWidth;
|
|
||||||
|
|
||||||
private const int TargetFPS = 60;
|
private const int TargetFPS = 60;
|
||||||
|
|
||||||
private Switch Ns;
|
private Switch Ns;
|
||||||
|
@ -49,10 +47,6 @@ namespace Ryujinx
|
||||||
Location = new Point(
|
Location = new Point(
|
||||||
(DisplayDevice.Default.Width / 2) - (Width / 2),
|
(DisplayDevice.Default.Width / 2) - (Width / 2),
|
||||||
(DisplayDevice.Default.Height / 2) - (Height / 2));
|
(DisplayDevice.Default.Height / 2) - (Height / 2));
|
||||||
|
|
||||||
ResizeEvent = false;
|
|
||||||
|
|
||||||
TitleEvent = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderLoop()
|
private void RenderLoop()
|
||||||
|
@ -127,60 +121,9 @@ namespace Ryujinx
|
||||||
Title = NewTitle;
|
Title = NewTitle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsGamePadButtonPressedFromString(GamePadState GamePad, string Button)
|
|
||||||
{
|
|
||||||
if (Button.ToUpper() == "LTRIGGER" || Button.ToUpper() == "RTRIGGER")
|
|
||||||
{
|
|
||||||
return GetGamePadTriggerFromString(GamePad, Button) >= Config.GamePadTriggerThreshold;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return (GetGamePadButtonFromString(GamePad, Button) == ButtonState.Pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ButtonState GetGamePadButtonFromString(GamePadState GamePad, string Button)
|
//Polling becomes expensive if it's not slept
|
||||||
{
|
Thread.Sleep(1);
|
||||||
switch (Button.ToUpper())
|
|
||||||
{
|
|
||||||
case "A": return GamePad.Buttons.A;
|
|
||||||
case "B": return GamePad.Buttons.B;
|
|
||||||
case "X": return GamePad.Buttons.X;
|
|
||||||
case "Y": return GamePad.Buttons.Y;
|
|
||||||
case "LSTICK": return GamePad.Buttons.LeftStick;
|
|
||||||
case "RSTICK": return GamePad.Buttons.RightStick;
|
|
||||||
case "LSHOULDER": return GamePad.Buttons.LeftShoulder;
|
|
||||||
case "RSHOULDER": return GamePad.Buttons.RightShoulder;
|
|
||||||
case "DPADUP": return GamePad.DPad.Up;
|
|
||||||
case "DPADDOWN": return GamePad.DPad.Down;
|
|
||||||
case "DPADLEFT": return GamePad.DPad.Left;
|
|
||||||
case "DPADRIGHT": return GamePad.DPad.Right;
|
|
||||||
case "START": return GamePad.Buttons.Start;
|
|
||||||
case "BACK": return GamePad.Buttons.Back;
|
|
||||||
default: throw new ArgumentException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float GetGamePadTriggerFromString(GamePadState GamePad, string Trigger)
|
|
||||||
{
|
|
||||||
switch (Trigger.ToUpper())
|
|
||||||
{
|
|
||||||
case "LTRIGGER": return GamePad.Triggers.Left;
|
|
||||||
case "RTRIGGER": return GamePad.Triggers.Right;
|
|
||||||
default: throw new ArgumentException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector2 GetJoystickAxisFromString(GamePadState GamePad, string Joystick)
|
|
||||||
{
|
|
||||||
switch (Joystick.ToUpper())
|
|
||||||
{
|
|
||||||
case "LJOYSTICK": return GamePad.ThumbSticks.Left;
|
|
||||||
case "RJOYSTICK": return new Vector2(-GamePad.ThumbSticks.Right.Y, -GamePad.ThumbSticks.Right.X);
|
|
||||||
default: throw new ArgumentException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,95 +133,37 @@ namespace Ryujinx
|
||||||
HidJoystickPosition LeftJoystick;
|
HidJoystickPosition LeftJoystick;
|
||||||
HidJoystickPosition RightJoystick;
|
HidJoystickPosition RightJoystick;
|
||||||
|
|
||||||
int LeftJoystickDX = 0;
|
int LeftJoystickDX = 0;
|
||||||
int LeftJoystickDY = 0;
|
int LeftJoystickDY = 0;
|
||||||
int RightJoystickDX = 0;
|
int RightJoystickDX = 0;
|
||||||
int RightJoystickDY = 0;
|
int RightJoystickDY = 0;
|
||||||
float AnalogStickDeadzone = Config.GamePadDeadzone;
|
|
||||||
|
|
||||||
//Keyboard Input
|
//Keyboard Input
|
||||||
if (Keyboard.HasValue)
|
if (Keyboard.HasValue)
|
||||||
{
|
{
|
||||||
KeyboardState Keyboard = this.Keyboard.Value;
|
KeyboardState Keyboard = this.Keyboard.Value;
|
||||||
|
|
||||||
if (Keyboard[Key.Escape]) this.Exit();
|
CurrentButton = Config.JoyConKeyboard.GetButtons(Keyboard);
|
||||||
|
|
||||||
//LeftJoystick
|
(LeftJoystickDX, LeftJoystickDY) = Config.JoyConKeyboard.GetLeftStick(Keyboard);
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickUp]) LeftJoystickDY = short.MaxValue;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickDown]) LeftJoystickDY = -short.MaxValue;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickLeft]) LeftJoystickDX = -short.MaxValue;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickRight]) LeftJoystickDX = short.MaxValue;
|
|
||||||
|
|
||||||
//LeftButtons
|
(RightJoystickDX, RightJoystickDY) = Config.JoyConKeyboard.GetRightStick(Keyboard);
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickButton]) CurrentButton |= HidControllerButtons.KEY_LSTICK;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadUp]) CurrentButton |= HidControllerButtons.KEY_DUP;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadDown]) CurrentButton |= HidControllerButtons.KEY_DDOWN;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadLeft]) CurrentButton |= HidControllerButtons.KEY_DLEFT;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadRight]) CurrentButton |= HidControllerButtons.KEY_DRIGHT;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonMinus]) CurrentButton |= HidControllerButtons.KEY_MINUS;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonL]) CurrentButton |= HidControllerButtons.KEY_L;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonZL]) CurrentButton |= HidControllerButtons.KEY_ZL;
|
|
||||||
|
|
||||||
//RightJoystick
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickUp]) RightJoystickDY = short.MaxValue;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickDown]) RightJoystickDY = -short.MaxValue;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickLeft]) RightJoystickDX = -short.MaxValue;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickRight]) RightJoystickDX = short.MaxValue;
|
|
||||||
|
|
||||||
//RightButtons
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickButton]) CurrentButton |= HidControllerButtons.KEY_RSTICK;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonA]) CurrentButton |= HidControllerButtons.KEY_A;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonB]) CurrentButton |= HidControllerButtons.KEY_B;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonX]) CurrentButton |= HidControllerButtons.KEY_X;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonY]) CurrentButton |= HidControllerButtons.KEY_Y;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonPlus]) CurrentButton |= HidControllerButtons.KEY_PLUS;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonR]) CurrentButton |= HidControllerButtons.KEY_R;
|
|
||||||
if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonZR]) CurrentButton |= HidControllerButtons.KEY_ZR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Controller Input
|
//Controller Input
|
||||||
if (Config.GamePadEnable)
|
CurrentButton |= Config.JoyConController.GetButtons();
|
||||||
|
|
||||||
|
//Keyboard has priority stick-wise
|
||||||
|
if (LeftJoystickDX == 0 && LeftJoystickDY == 0)
|
||||||
{
|
{
|
||||||
GamePadState GamePad = OpenTK.Input.GamePad.GetState(Config.GamePadIndex);
|
(LeftJoystickDX, LeftJoystickDY) = Config.JoyConController.GetLeftStick();
|
||||||
//LeftButtons
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadUp)) CurrentButton |= HidControllerButtons.KEY_DUP;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadDown)) CurrentButton |= HidControllerButtons.KEY_DDOWN;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadLeft)) CurrentButton |= HidControllerButtons.KEY_DLEFT;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadRight)) CurrentButton |= HidControllerButtons.KEY_DRIGHT;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.StickButton)) CurrentButton |= HidControllerButtons.KEY_LSTICK;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonMinus)) CurrentButton |= HidControllerButtons.KEY_MINUS;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonL)) CurrentButton |= HidControllerButtons.KEY_L;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonZL)) CurrentButton |= HidControllerButtons.KEY_ZL;
|
|
||||||
|
|
||||||
//RightButtons
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonA)) CurrentButton |= HidControllerButtons.KEY_A;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonB)) CurrentButton |= HidControllerButtons.KEY_B;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonX)) CurrentButton |= HidControllerButtons.KEY_X;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonY)) CurrentButton |= HidControllerButtons.KEY_Y;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.StickButton)) CurrentButton |= HidControllerButtons.KEY_RSTICK;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonPlus)) CurrentButton |= HidControllerButtons.KEY_PLUS;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonR)) CurrentButton |= HidControllerButtons.KEY_R;
|
|
||||||
if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonZR)) CurrentButton |= HidControllerButtons.KEY_ZR;
|
|
||||||
|
|
||||||
//LeftJoystick
|
|
||||||
if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X >= AnalogStickDeadzone
|
|
||||||
|| GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X <= -AnalogStickDeadzone)
|
|
||||||
LeftJoystickDX = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X * short.MaxValue);
|
|
||||||
|
|
||||||
if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y >= AnalogStickDeadzone
|
|
||||||
|| GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y <= -AnalogStickDeadzone)
|
|
||||||
LeftJoystickDY = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y * short.MaxValue);
|
|
||||||
|
|
||||||
//RightJoystick
|
|
||||||
if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X >= AnalogStickDeadzone
|
|
||||||
|| GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X <= -AnalogStickDeadzone)
|
|
||||||
RightJoystickDX = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X * short.MaxValue);
|
|
||||||
|
|
||||||
if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y >= AnalogStickDeadzone
|
|
||||||
|| GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y <= -AnalogStickDeadzone)
|
|
||||||
RightJoystickDY = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y * short.MaxValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RightJoystickDX == 0 && RightJoystickDY == 0)
|
||||||
|
{
|
||||||
|
(RightJoystickDX, RightJoystickDY) = Config.JoyConController.GetRightStick();
|
||||||
|
}
|
||||||
|
|
||||||
LeftJoystick = new HidJoystickPosition
|
LeftJoystick = new HidJoystickPosition
|
||||||
{
|
{
|
||||||
DX = LeftJoystickDX,
|
DX = LeftJoystickDX,
|
||||||
|
@ -302,13 +187,13 @@ namespace Ryujinx
|
||||||
int ScrnWidth = Width;
|
int ScrnWidth = Width;
|
||||||
int ScrnHeight = Height;
|
int ScrnHeight = Height;
|
||||||
|
|
||||||
if (Width > Height * TouchScreenRatioX)
|
if (Width > (Height * TouchScreenWidth) / TouchScreenHeight)
|
||||||
{
|
{
|
||||||
ScrnWidth = (int)(Height * TouchScreenRatioX);
|
ScrnWidth = (Height * TouchScreenWidth) / TouchScreenHeight;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ScrnHeight = (int)(Width * TouchScreenRatioY);
|
ScrnHeight = (Width * TouchScreenHeight) / TouchScreenWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int StartX = (Width - ScrnWidth) >> 1;
|
int StartX = (Width - ScrnWidth) >> 1;
|
||||||
|
@ -325,8 +210,8 @@ namespace Ryujinx
|
||||||
int ScrnMouseX = Mouse.X - StartX;
|
int ScrnMouseX = Mouse.X - StartX;
|
||||||
int ScrnMouseY = Mouse.Y - StartY;
|
int ScrnMouseY = Mouse.Y - StartY;
|
||||||
|
|
||||||
int MX = (int)(((float)ScrnMouseX / ScrnWidth) * TouchScreenWidth);
|
int MX = (ScrnMouseX * TouchScreenWidth) / ScrnWidth;
|
||||||
int MY = (int)(((float)ScrnMouseY / ScrnHeight) * TouchScreenHeight);
|
int MY = (ScrnMouseY * TouchScreenHeight) / ScrnHeight;
|
||||||
|
|
||||||
HidTouchPoint CurrentPoint = new HidTouchPoint
|
HidTouchPoint CurrentPoint = new HidTouchPoint
|
||||||
{
|
{
|
||||||
|
@ -397,6 +282,29 @@ namespace Ryujinx
|
||||||
|
|
||||||
protected override void OnKeyDown(KeyboardKeyEventArgs e)
|
protected override void OnKeyDown(KeyboardKeyEventArgs e)
|
||||||
{
|
{
|
||||||
|
bool ToggleFullscreen = e.Key == Key.F11 ||
|
||||||
|
(e.Modifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Enter);
|
||||||
|
|
||||||
|
if (WindowState == WindowState.Fullscreen)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Escape || ToggleFullscreen)
|
||||||
|
{
|
||||||
|
WindowState = WindowState.Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Escape)
|
||||||
|
{
|
||||||
|
Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ToggleFullscreen)
|
||||||
|
{
|
||||||
|
WindowState = WindowState.Fullscreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Keyboard = e.Keyboard;
|
Keyboard = e.Keyboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,216 @@
|
||||||
using System;
|
using OpenTK;
|
||||||
using System.Collections.Generic;
|
using OpenTK.Input;
|
||||||
using System.Text;
|
using Ryujinx.HLE.Input;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.UI.Input
|
namespace Ryujinx.UI.Input
|
||||||
{
|
{
|
||||||
|
public enum ControllerInputID
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
|
||||||
|
LStick,
|
||||||
|
DPadUp,
|
||||||
|
DPadDown,
|
||||||
|
DPadLeft,
|
||||||
|
DPadRight,
|
||||||
|
Back,
|
||||||
|
LShoulder,
|
||||||
|
|
||||||
|
RStick,
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
Start,
|
||||||
|
RShoulder,
|
||||||
|
|
||||||
|
LTrigger,
|
||||||
|
RTrigger,
|
||||||
|
|
||||||
|
LJoystick,
|
||||||
|
RJoystick
|
||||||
|
}
|
||||||
|
|
||||||
public struct JoyConControllerLeft
|
public struct JoyConControllerLeft
|
||||||
{
|
{
|
||||||
public string Stick;
|
public ControllerInputID Stick;
|
||||||
public string StickButton;
|
public ControllerInputID StickButton;
|
||||||
public string DPadUp;
|
public ControllerInputID DPadUp;
|
||||||
public string DPadDown;
|
public ControllerInputID DPadDown;
|
||||||
public string DPadLeft;
|
public ControllerInputID DPadLeft;
|
||||||
public string DPadRight;
|
public ControllerInputID DPadRight;
|
||||||
public string ButtonMinus;
|
public ControllerInputID ButtonMinus;
|
||||||
public string ButtonL;
|
public ControllerInputID ButtonL;
|
||||||
public string ButtonZL;
|
public ControllerInputID ButtonZL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct JoyConControllerRight
|
public struct JoyConControllerRight
|
||||||
{
|
{
|
||||||
public string Stick;
|
public ControllerInputID Stick;
|
||||||
public string StickButton;
|
public ControllerInputID StickButton;
|
||||||
public string ButtonA;
|
public ControllerInputID ButtonA;
|
||||||
public string ButtonB;
|
public ControllerInputID ButtonB;
|
||||||
public string ButtonX;
|
public ControllerInputID ButtonX;
|
||||||
public string ButtonY;
|
public ControllerInputID ButtonY;
|
||||||
public string ButtonPlus;
|
public ControllerInputID ButtonPlus;
|
||||||
public string ButtonR;
|
public ControllerInputID ButtonR;
|
||||||
public string ButtonZR;
|
public ControllerInputID ButtonZR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct JoyConController
|
public class JoyConController
|
||||||
{
|
{
|
||||||
public JoyConControllerLeft Left;
|
public bool Enabled { private set; get; }
|
||||||
public JoyConControllerRight Right;
|
public int Index { private set; get; }
|
||||||
|
public float Deadzone { private set; get; }
|
||||||
|
public float TriggerThreshold { private set; get; }
|
||||||
|
|
||||||
|
public JoyConControllerLeft Left { private set; get; }
|
||||||
|
public JoyConControllerRight Right { private set; get; }
|
||||||
|
|
||||||
|
public JoyConController(
|
||||||
|
bool Enabled,
|
||||||
|
int Index,
|
||||||
|
float Deadzone,
|
||||||
|
float TriggerThreshold,
|
||||||
|
JoyConControllerLeft Left,
|
||||||
|
JoyConControllerRight Right)
|
||||||
|
{
|
||||||
|
this.Enabled = Enabled;
|
||||||
|
this.Index = Index;
|
||||||
|
this.Deadzone = Deadzone;
|
||||||
|
this.TriggerThreshold = TriggerThreshold;
|
||||||
|
this.Left = Left;
|
||||||
|
this.Right = Right;
|
||||||
|
|
||||||
|
//Unmapped controllers are problematic, skip them
|
||||||
|
if (GamePad.GetName(Index) == "Unmapped Controller")
|
||||||
|
{
|
||||||
|
this.Enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HidControllerButtons GetButtons()
|
||||||
|
{
|
||||||
|
if (!Enabled)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
GamePadState GpState = GamePad.GetState(Index);
|
||||||
|
|
||||||
|
HidControllerButtons Buttons = 0;
|
||||||
|
|
||||||
|
if (IsPressed(GpState, Left.DPadUp)) Buttons |= HidControllerButtons.KEY_DUP;
|
||||||
|
if (IsPressed(GpState, Left.DPadDown)) Buttons |= HidControllerButtons.KEY_DDOWN;
|
||||||
|
if (IsPressed(GpState, Left.DPadLeft)) Buttons |= HidControllerButtons.KEY_DLEFT;
|
||||||
|
if (IsPressed(GpState, Left.DPadRight)) Buttons |= HidControllerButtons.KEY_DRIGHT;
|
||||||
|
if (IsPressed(GpState, Left.StickButton)) Buttons |= HidControllerButtons.KEY_LSTICK;
|
||||||
|
if (IsPressed(GpState, Left.ButtonMinus)) Buttons |= HidControllerButtons.KEY_MINUS;
|
||||||
|
if (IsPressed(GpState, Left.ButtonL)) Buttons |= HidControllerButtons.KEY_L;
|
||||||
|
if (IsPressed(GpState, Left.ButtonZL)) Buttons |= HidControllerButtons.KEY_ZL;
|
||||||
|
|
||||||
|
if (IsPressed(GpState, Right.ButtonA)) Buttons |= HidControllerButtons.KEY_A;
|
||||||
|
if (IsPressed(GpState, Right.ButtonB)) Buttons |= HidControllerButtons.KEY_B;
|
||||||
|
if (IsPressed(GpState, Right.ButtonX)) Buttons |= HidControllerButtons.KEY_X;
|
||||||
|
if (IsPressed(GpState, Right.ButtonY)) Buttons |= HidControllerButtons.KEY_Y;
|
||||||
|
if (IsPressed(GpState, Right.StickButton)) Buttons |= HidControllerButtons.KEY_RSTICK;
|
||||||
|
if (IsPressed(GpState, Right.ButtonPlus)) Buttons |= HidControllerButtons.KEY_PLUS;
|
||||||
|
if (IsPressed(GpState, Right.ButtonR)) Buttons |= HidControllerButtons.KEY_R;
|
||||||
|
if (IsPressed(GpState, Right.ButtonZR)) Buttons |= HidControllerButtons.KEY_ZR;
|
||||||
|
|
||||||
|
return Buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (short, short) GetLeftStick()
|
||||||
|
{
|
||||||
|
if (!Enabled)
|
||||||
|
{
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetStick(Left.Stick);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (short, short) GetRightStick()
|
||||||
|
{
|
||||||
|
if (!Enabled)
|
||||||
|
{
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetStick(Right.Stick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (short, short) GetStick(ControllerInputID Joystick)
|
||||||
|
{
|
||||||
|
GamePadState GpState = GamePad.GetState(Index);
|
||||||
|
|
||||||
|
switch (Joystick)
|
||||||
|
{
|
||||||
|
case ControllerInputID.LJoystick:
|
||||||
|
return ApplyDeadzone(GpState.ThumbSticks.Left);
|
||||||
|
|
||||||
|
case ControllerInputID.RJoystick:
|
||||||
|
return ApplyDeadzone(GpState.ThumbSticks.Right);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (short, short) ApplyDeadzone(Vector2 Axis)
|
||||||
|
{
|
||||||
|
return (ClampAxis(MathF.Abs(Axis.X) > Deadzone ? Axis.X : 0f),
|
||||||
|
ClampAxis(MathF.Abs(Axis.Y) > Deadzone ? Axis.Y : 0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short ClampAxis(float Value)
|
||||||
|
{
|
||||||
|
if (Value <= -short.MaxValue)
|
||||||
|
{
|
||||||
|
return -short.MaxValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (short)(Value * short.MaxValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPressed(GamePadState GpState, ControllerInputID Button)
|
||||||
|
{
|
||||||
|
switch (Button)
|
||||||
|
{
|
||||||
|
case ControllerInputID.A: return GpState.Buttons.A == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.B: return GpState.Buttons.B == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.X: return GpState.Buttons.X == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.Y: return GpState.Buttons.Y == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.LStick: return GpState.Buttons.LeftStick == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.RStick: return GpState.Buttons.RightStick == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.LShoulder: return GpState.Buttons.LeftShoulder == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.RShoulder: return GpState.Buttons.RightShoulder == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.DPadUp: return GpState.DPad.Up == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.DPadDown: return GpState.DPad.Down == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.DPadLeft: return GpState.DPad.Left == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.DPadRight: return GpState.DPad.Right == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.Start: return GpState.Buttons.Start == ButtonState.Pressed;
|
||||||
|
case ControllerInputID.Back: return GpState.Buttons.Back == ButtonState.Pressed;
|
||||||
|
|
||||||
|
case ControllerInputID.LTrigger: return GpState.Triggers.Left >= TriggerThreshold;
|
||||||
|
case ControllerInputID.RTrigger: return GpState.Triggers.Right >= TriggerThreshold;
|
||||||
|
|
||||||
|
//Using thumbsticks as buttons is not common, but it would be nice not to ignore them
|
||||||
|
case ControllerInputID.LJoystick:
|
||||||
|
return GpState.ThumbSticks.Left.X >= Deadzone ||
|
||||||
|
GpState.ThumbSticks.Left.Y >= Deadzone;
|
||||||
|
|
||||||
|
case ControllerInputID.RJoystick:
|
||||||
|
return GpState.ThumbSticks.Right.X >= Deadzone ||
|
||||||
|
GpState.ThumbSticks.Right.Y >= Deadzone;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
using OpenTK.Input;
|
||||||
|
using Ryujinx.HLE.Input;
|
||||||
|
|
||||||
namespace Ryujinx.UI.Input
|
namespace Ryujinx.UI.Input
|
||||||
{
|
{
|
||||||
public struct JoyConKeyboardLeft
|
public struct JoyConKeyboardLeft
|
||||||
|
@ -32,9 +35,68 @@ namespace Ryujinx.UI.Input
|
||||||
public int ButtonZR;
|
public int ButtonZR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct JoyConKeyboard
|
public class JoyConKeyboard
|
||||||
{
|
{
|
||||||
public JoyConKeyboardLeft Left;
|
public JoyConKeyboardLeft Left;
|
||||||
public JoyConKeyboardRight Right;
|
public JoyConKeyboardRight Right;
|
||||||
|
|
||||||
|
public JoyConKeyboard(
|
||||||
|
JoyConKeyboardLeft Left,
|
||||||
|
JoyConKeyboardRight Right)
|
||||||
|
{
|
||||||
|
this.Left = Left;
|
||||||
|
this.Right = Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HidControllerButtons GetButtons(KeyboardState Keyboard)
|
||||||
|
{
|
||||||
|
HidControllerButtons Buttons = 0;
|
||||||
|
|
||||||
|
if (Keyboard[(Key)Left.StickButton]) Buttons |= HidControllerButtons.KEY_LSTICK;
|
||||||
|
if (Keyboard[(Key)Left.DPadUp]) Buttons |= HidControllerButtons.KEY_DUP;
|
||||||
|
if (Keyboard[(Key)Left.DPadDown]) Buttons |= HidControllerButtons.KEY_DDOWN;
|
||||||
|
if (Keyboard[(Key)Left.DPadLeft]) Buttons |= HidControllerButtons.KEY_DLEFT;
|
||||||
|
if (Keyboard[(Key)Left.DPadRight]) Buttons |= HidControllerButtons.KEY_DRIGHT;
|
||||||
|
if (Keyboard[(Key)Left.ButtonMinus]) Buttons |= HidControllerButtons.KEY_MINUS;
|
||||||
|
if (Keyboard[(Key)Left.ButtonL]) Buttons |= HidControllerButtons.KEY_L;
|
||||||
|
if (Keyboard[(Key)Left.ButtonZL]) Buttons |= HidControllerButtons.KEY_ZL;
|
||||||
|
|
||||||
|
if (Keyboard[(Key)Right.StickButton]) Buttons |= HidControllerButtons.KEY_RSTICK;
|
||||||
|
if (Keyboard[(Key)Right.ButtonA]) Buttons |= HidControllerButtons.KEY_A;
|
||||||
|
if (Keyboard[(Key)Right.ButtonB]) Buttons |= HidControllerButtons.KEY_B;
|
||||||
|
if (Keyboard[(Key)Right.ButtonX]) Buttons |= HidControllerButtons.KEY_X;
|
||||||
|
if (Keyboard[(Key)Right.ButtonY]) Buttons |= HidControllerButtons.KEY_Y;
|
||||||
|
if (Keyboard[(Key)Right.ButtonPlus]) Buttons |= HidControllerButtons.KEY_PLUS;
|
||||||
|
if (Keyboard[(Key)Right.ButtonR]) Buttons |= HidControllerButtons.KEY_R;
|
||||||
|
if (Keyboard[(Key)Right.ButtonZR]) Buttons |= HidControllerButtons.KEY_ZR;
|
||||||
|
|
||||||
|
return Buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (short, short) GetLeftStick(KeyboardState Keyboard)
|
||||||
|
{
|
||||||
|
short DX = 0;
|
||||||
|
short DY = 0;
|
||||||
|
|
||||||
|
if (Keyboard[(Key)Left.StickUp]) DY = short.MaxValue;
|
||||||
|
if (Keyboard[(Key)Left.StickDown]) DY = -short.MaxValue;
|
||||||
|
if (Keyboard[(Key)Left.StickLeft]) DX = -short.MaxValue;
|
||||||
|
if (Keyboard[(Key)Left.StickRight]) DX = short.MaxValue;
|
||||||
|
|
||||||
|
return (DX, DY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (short, short) GetRightStick(KeyboardState Keyboard)
|
||||||
|
{
|
||||||
|
short DX = 0;
|
||||||
|
short DY = 0;
|
||||||
|
|
||||||
|
if (Keyboard[(Key)Right.StickUp]) DY = short.MaxValue;
|
||||||
|
if (Keyboard[(Key)Right.StickDown]) DY = -short.MaxValue;
|
||||||
|
if (Keyboard[(Key)Right.StickLeft]) DX = -short.MaxValue;
|
||||||
|
if (Keyboard[(Key)Right.StickRight]) DX = short.MaxValue;
|
||||||
|
|
||||||
|
return (DX, DY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue