Finalised new InputManagerHelper, which translates input based on the operating input system, new or old
Updated CardStack2D to have defined keyboard input or specific gamepad input over the older axisname for new input system.pull/413/head
parent
0b0d03cdb7
commit
38740bfef0
|
@ -3,118 +3,121 @@
|
||||||
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace UnityEngine.UI.Extensions
|
namespace UnityEngine.UI.Extensions
|
||||||
{
|
{
|
||||||
|
|
||||||
public class CardStack2D : MonoBehaviour
|
public class CardStack2D : MonoBehaviour
|
||||||
{
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
private float cardMoveSpeed = 8f;
|
|
||||||
[SerializeField]
|
|
||||||
private float buttonCooldownTime = 0.125f;
|
|
||||||
[SerializeField]
|
|
||||||
private int cardZMultiplier = 32;
|
|
||||||
[SerializeField]
|
|
||||||
private bool useDefaultUsedXPos = true;
|
|
||||||
[SerializeField]
|
|
||||||
private int usedCardXPos = 1280;
|
|
||||||
[SerializeField]
|
|
||||||
private Transform[] cards = null;
|
|
||||||
|
|
||||||
private int cardArrayOffset;
|
|
||||||
private Vector3[] cardPositions;
|
|
||||||
private int xPowerDifference;
|
|
||||||
|
|
||||||
///Static variables can be used across the scene if this script is in it.
|
|
||||||
///Thankfully it doesn't matter if another script attempts to use the variable and this script isn't in the scene.
|
|
||||||
public static bool canUseHorizontalAxis = true;
|
|
||||||
|
|
||||||
void Start()
|
|
||||||
{
|
{
|
||||||
///I've found that 9 is a good number for this.
|
|
||||||
///I wouldn't really recommend changing it, but go ahead if you want to.
|
|
||||||
xPowerDifference = 9 - cards.Length;
|
|
||||||
|
|
||||||
///This is optional, but makes it super easy to figure out the off screen position for cards.
|
[SerializeField]
|
||||||
///Unfortunately, it's only really useful if the cards are the same width.
|
private float cardMoveSpeed = 8f;
|
||||||
if (useDefaultUsedXPos)
|
[SerializeField]
|
||||||
|
private float buttonCooldownTime = 0.125f;
|
||||||
|
[SerializeField]
|
||||||
|
private int cardZMultiplier = 32;
|
||||||
|
[SerializeField]
|
||||||
|
private bool useDefaultUsedXPos = true;
|
||||||
|
[SerializeField]
|
||||||
|
private int usedCardXPos = 1280;
|
||||||
|
[SerializeField]
|
||||||
|
private KeyCode leftButton = KeyCode.LeftArrow;
|
||||||
|
[SerializeField]
|
||||||
|
private KeyCode rightButton = KeyCode.RightArrow;
|
||||||
|
[SerializeField]
|
||||||
|
private Transform[] cards = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private int cardArrayOffset;
|
||||||
|
private Vector3[] cardPositions;
|
||||||
|
private int xPowerDifference;
|
||||||
|
|
||||||
|
///Static variables can be used across the scene if this script is in it.
|
||||||
|
///Thankfully it doesn't matter if another script attempts to use the variable and this script isn't in the scene.
|
||||||
|
public static bool canUseHorizontalAxis = true;
|
||||||
|
|
||||||
|
void Start()
|
||||||
{
|
{
|
||||||
int cardWidth = (int)(cards[0].GetComponent<RectTransform>().rect.width);
|
///I've found that 9 is a good number for this.
|
||||||
usedCardXPos = (int)(Screen.width * 0.5f + cardWidth);
|
///I wouldn't really recommend changing it, but go ahead if you want to.
|
||||||
}
|
xPowerDifference = 9 - cards.Length;
|
||||||
|
|
||||||
cardPositions = new Vector3[cards.Length * 2 - 1];
|
///This is optional, but makes it super easy to figure out the off screen position for cards.
|
||||||
|
///Unfortunately, it's only really useful if the cards are the same width.
|
||||||
///This loop is for cards still in the stack.
|
if (useDefaultUsedXPos)
|
||||||
for (int i = cards.Length; i > -1; i--)
|
|
||||||
{
|
|
||||||
if (i < cards.Length - 1)
|
|
||||||
{
|
{
|
||||||
cardPositions[i] = new Vector3(-Mathf.Pow(2, i + xPowerDifference) + cardPositions[i + 1].x, 0, cardZMultiplier * Mathf.Abs(i + 1 - cards.Length));
|
int cardWidth = (int)(cards[0].GetComponent<RectTransform>().rect.width);
|
||||||
|
usedCardXPos = (int)(Screen.width * 0.5f + cardWidth);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
cardPositions[i] = Vector3.zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///This loop is for cards outside of the stack.
|
cardPositions = new Vector3[cards.Length * 2 - 1];
|
||||||
for (int i = cards.Length; i < cardPositions.Length; i++)
|
|
||||||
{
|
|
||||||
cardPositions[i] = new Vector3(usedCardXPos + 4 * (i - cards.Length), 0, -2 + -2 * (i - cards.Length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Update()
|
///This loop is for cards still in the stack.
|
||||||
{
|
for (int i = cards.Length; i > -1; i--)
|
||||||
if (canUseHorizontalAxis)
|
|
||||||
{
|
|
||||||
///Controls for the cards.
|
|
||||||
if (UIExtensionsInputManager.GetAxisRaw("Horizontal") < 0 && cardArrayOffset > 0)
|
|
||||||
{
|
{
|
||||||
cardArrayOffset--;
|
if (i < cards.Length - 1)
|
||||||
StartCoroutine(ButtonCooldown());
|
|
||||||
}
|
|
||||||
else if (UIExtensionsInputManager.GetAxisRaw("Horizontal") > 0 && cardArrayOffset < cards.Length - 1)
|
|
||||||
{
|
|
||||||
cardArrayOffset++;
|
|
||||||
StartCoroutine(ButtonCooldown());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///This loop moves the cards. I know that none of my lerps are the "right way," but it looks much nicer.
|
|
||||||
for (int i = 0; i < cards.Length; i++)
|
|
||||||
{
|
|
||||||
cards[i].localPosition = Vector3.Lerp(cards[i].localPosition, cardPositions[i + cardArrayOffset], Time.deltaTime * cardMoveSpeed);
|
|
||||||
if (Mathf.Abs(cards[i].localPosition.x - cardPositions[i + cardArrayOffset].x) < 0.01f)
|
|
||||||
{
|
|
||||||
cards[i].localPosition = cardPositions[i + cardArrayOffset];
|
|
||||||
|
|
||||||
///This disables interaction with cards that are not on top of the stack.
|
|
||||||
if (cards[i].localPosition.x == 0)
|
|
||||||
{
|
{
|
||||||
cards[i].gameObject.GetComponent<CanvasGroup>().interactable = true;
|
cardPositions[i] = new Vector3(-Mathf.Pow(2, i + xPowerDifference) + cardPositions[i + 1].x, 0, cardZMultiplier * Mathf.Abs(i + 1 - cards.Length));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cards[i].gameObject.GetComponent<CanvasGroup>().interactable = false;
|
cardPositions[i] = Vector3.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///This loop is for cards outside of the stack.
|
||||||
|
for (int i = cards.Length; i < cardPositions.Length; i++)
|
||||||
|
{
|
||||||
|
cardPositions[i] = new Vector3(usedCardXPos + 4 * (i - cards.Length), 0, -2 + -2 * (i - cards.Length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
if (canUseHorizontalAxis)
|
||||||
|
{
|
||||||
|
///Controls for the cards.
|
||||||
|
if ((UIExtensionsInputManager.GetAxisRaw("Horizontal") < 0 || UIExtensionsInputManager.GetKey(leftButton)) && cardArrayOffset > 0)
|
||||||
|
{
|
||||||
|
cardArrayOffset--;
|
||||||
|
StartCoroutine(ButtonCooldown());
|
||||||
|
}
|
||||||
|
else if ((UIExtensionsInputManager.GetAxisRaw("Horizontal") > 0 || UIExtensionsInputManager.GetKey(rightButton)) && cardArrayOffset < cards.Length - 1)
|
||||||
|
{
|
||||||
|
cardArrayOffset++;
|
||||||
|
StartCoroutine(ButtonCooldown());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///This loop moves the cards. I know that none of my lerps are the "right way," but it looks much nicer.
|
||||||
|
for (int i = 0; i < cards.Length; i++)
|
||||||
|
{
|
||||||
|
cards[i].localPosition = Vector3.Lerp(cards[i].localPosition, cardPositions[i + cardArrayOffset], Time.deltaTime * cardMoveSpeed);
|
||||||
|
if (Mathf.Abs(cards[i].localPosition.x - cardPositions[i + cardArrayOffset].x) < 0.01f)
|
||||||
|
{
|
||||||
|
cards[i].localPosition = cardPositions[i + cardArrayOffset];
|
||||||
|
|
||||||
|
///This disables interaction with cards that are not on top of the stack.
|
||||||
|
if (cards[i].localPosition.x == 0)
|
||||||
|
{
|
||||||
|
cards[i].gameObject.GetComponent<CanvasGroup>().interactable = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cards[i].gameObject.GetComponent<CanvasGroup>().interactable = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
///Stops the cards from scrolling super quickly if a button on the horizontal axis is held down.
|
///Stops the cards from scrolling super quickly if a button on the horizontal axis is held down.
|
||||||
IEnumerator ButtonCooldown()
|
IEnumerator ButtonCooldown()
|
||||||
{
|
{
|
||||||
canUseHorizontalAxis = false;
|
canUseHorizontalAxis = false;
|
||||||
yield return new WaitForSeconds(buttonCooldownTime);
|
yield return new WaitForSeconds(buttonCooldownTime);
|
||||||
canUseHorizontalAxis = true;
|
canUseHorizontalAxis = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,66 +1,272 @@
|
||||||
/// Credit SimonDarksideJ
|
/// Credit SimonDarksideJ
|
||||||
/// Sourced from: https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/issues/348/menu-manager-does-not-work-with-the-new
|
/// Sourced from: https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/issues/348/menu-manager-does-not-work-with-the-new
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
|
using UnityEngine.InputSystem.Controls;
|
||||||
|
|
||||||
namespace UnityEngine.UI.Extensions
|
namespace UnityEngine.UI.Extensions
|
||||||
{
|
{
|
||||||
public static class UIExtensionsInputManager
|
public static class UIExtensionsInputManager
|
||||||
{
|
{
|
||||||
|
#if !ENABLE_LEGACY_INPUT_MANAGER
|
||||||
|
private static bool[] mouseButtons = new bool[3] { false, false, false };
|
||||||
|
private static Dictionary<KeyCode, bool> keys = new Dictionary<KeyCode, bool>();
|
||||||
|
private static Dictionary<String, bool> buttons = new Dictionary<String, bool>();
|
||||||
|
#endif
|
||||||
|
|
||||||
public static bool GetMouseButton(int button)
|
public static bool GetMouseButton(int button)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetMouseButton(button);
|
return Input.GetMouseButton(button);
|
||||||
|
#else
|
||||||
|
if (Mouse.current == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mouse.current.leftButton.isPressed;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetMouseButtonDown(int button)
|
public static bool GetMouseButtonDown(int button)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetMouseButtonDown(button);
|
return Input.GetMouseButtonDown(button);
|
||||||
|
#else
|
||||||
|
if (Mouse.current == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Mouse.current.leftButton.isPressed)
|
||||||
|
{
|
||||||
|
if (!mouseButtons[button])
|
||||||
|
{
|
||||||
|
mouseButtons[button] = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetMouseButtonUp(int button)
|
public static bool GetMouseButtonUp(int button)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetMouseButtonUp(button);
|
return Input.GetMouseButtonUp(button);
|
||||||
|
#else
|
||||||
|
if (Mouse.current == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseButtons[button] && !Mouse.current.leftButton.isPressed)
|
||||||
|
{
|
||||||
|
mouseButtons[button] = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetButton(string input)
|
public static bool GetButton(string input)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetButton(input);
|
return Input.GetButton(input);
|
||||||
|
#else
|
||||||
|
ButtonControl buttonPressed = GetButtonControlFromString(input);
|
||||||
|
|
||||||
|
if (!buttons.ContainsKey(input))
|
||||||
|
{
|
||||||
|
buttons.Add(input, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttonPressed != null ? buttonPressed.isPressed : false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ButtonControl GetButtonControlFromString(string input)
|
||||||
|
{
|
||||||
|
if (Gamepad.current == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (input)
|
||||||
|
{
|
||||||
|
case "Submit":
|
||||||
|
return Gamepad.current.aButton;
|
||||||
|
case "Cancel":
|
||||||
|
return Gamepad.current.bButton;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetButtonDown(string input)
|
public static bool GetButtonDown(string input)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetButtonDown(input);
|
return Input.GetButtonDown(input);
|
||||||
|
#else
|
||||||
|
ButtonControl buttonPressed = GetButtonControlFromString(input);
|
||||||
|
|
||||||
|
if (buttonPressed.isPressed)
|
||||||
|
{
|
||||||
|
if (!buttons.ContainsKey(input))
|
||||||
|
{
|
||||||
|
buttons.Add(input, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buttons[input])
|
||||||
|
{
|
||||||
|
buttons[input] = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buttons[input] = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetButtonUp(string input)
|
public static bool GetButtonUp(string input)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetButtonUp(input);
|
return Input.GetButtonUp(input);
|
||||||
|
#else
|
||||||
|
ButtonControl buttonPressed = GetButtonControlFromString(input);
|
||||||
|
|
||||||
|
if (buttons[input] && !buttonPressed.isPressed)
|
||||||
|
{
|
||||||
|
buttons[input] = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetKey(KeyCode key)
|
public static bool GetKey(KeyCode key)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetKey(key);
|
return Input.GetKey(key);
|
||||||
|
#else
|
||||||
|
KeyControl keyPressed = GetKeyControlFromKeyCode(key);
|
||||||
|
if (!keys.ContainsKey(key))
|
||||||
|
{
|
||||||
|
keys.Add(key, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPressed != null ? keyPressed.isPressed : false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyControl GetKeyControlFromKeyCode(KeyCode key)
|
||||||
|
{
|
||||||
|
if (Keyboard.current == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case KeyCode.Escape:
|
||||||
|
return Keyboard.current.escapeKey;
|
||||||
|
case KeyCode.KeypadEnter:
|
||||||
|
return Keyboard.current.numpadEnterKey;
|
||||||
|
case KeyCode.UpArrow:
|
||||||
|
return Keyboard.current.upArrowKey;
|
||||||
|
case KeyCode.DownArrow:
|
||||||
|
return Keyboard.current.downArrowKey;
|
||||||
|
case KeyCode.RightArrow:
|
||||||
|
return Keyboard.current.rightArrowKey;
|
||||||
|
case KeyCode.LeftArrow:
|
||||||
|
return Keyboard.current.leftArrowKey;
|
||||||
|
case KeyCode.LeftShift:
|
||||||
|
return Keyboard.current.leftShiftKey;
|
||||||
|
case KeyCode.Tab:
|
||||||
|
return Keyboard.current.tabKey;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetKeyDown(KeyCode key)
|
public static bool GetKeyDown(KeyCode key)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetKeyDown(key);
|
return Input.GetKeyDown(key);
|
||||||
|
#else
|
||||||
|
KeyControl keyPressed = GetKeyControlFromKeyCode(key);
|
||||||
|
if (keyPressed.isPressed)
|
||||||
|
{
|
||||||
|
if (!keys.ContainsKey(key))
|
||||||
|
{
|
||||||
|
keys.Add(key, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keys[key])
|
||||||
|
{
|
||||||
|
keys[key] = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keys[key] = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GetKeyUp(KeyCode key)
|
public static bool GetKeyUp(KeyCode key)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetKeyUp(key);
|
return Input.GetKeyUp(key);
|
||||||
|
#else
|
||||||
|
KeyControl keyPressed = GetKeyControlFromKeyCode(key);
|
||||||
|
if (keys[key] && !keyPressed.isPressed)
|
||||||
|
{
|
||||||
|
keys[key] = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float GetAxisRaw(string axis)
|
public static float GetAxisRaw(string axis)
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.GetAxisRaw(axis);
|
return Input.GetAxisRaw(axis);
|
||||||
|
#else
|
||||||
|
if (Gamepad.current == null)
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (axis)
|
||||||
|
{
|
||||||
|
case "Horizontal":
|
||||||
|
return Gamepad.current.leftStick.x.ReadValue();
|
||||||
|
case "Vertical":
|
||||||
|
return Gamepad.current.leftStick.y.ReadValue();
|
||||||
|
|
||||||
|
}
|
||||||
|
return 0f;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector3 MousePosition
|
public static Vector3 MousePosition
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||||
return Input.mousePosition;
|
return Input.mousePosition;
|
||||||
|
#else
|
||||||
|
return Mouse.current.position.ReadValue();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "UnityUIExtensions",
|
"name": "UnityUIExtensions",
|
||||||
"references": [
|
"references": [
|
||||||
"GUID:2bafac87e7f4b9b418d9448d219b01ab"
|
"GUID:2bafac87e7f4b9b418d9448d219b01ab",
|
||||||
|
"GUID:75469ad4d38634e559750d17036d5f7c"
|
||||||
],
|
],
|
||||||
"includePlatforms": [],
|
"includePlatforms": [],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
|
@ -10,5 +11,6 @@
|
||||||
"precompiledReferences": [],
|
"precompiledReferences": [],
|
||||||
"autoReferenced": true,
|
"autoReferenced": true,
|
||||||
"defineConstraints": [],
|
"defineConstraints": [],
|
||||||
"versionDefines": []
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
}
|
}
|
Loading…
Reference in New Issue