2019-08-28 18:27:17 +02:00
|
|
|
using System;
|
|
|
|
using System.Linq;
|
|
|
|
using Microsoft.Xna.Framework;
|
|
|
|
using Microsoft.Xna.Framework.Input;
|
2019-08-30 18:15:50 +02:00
|
|
|
using Microsoft.Xna.Framework.Input.Touch;
|
2019-09-02 19:55:26 +02:00
|
|
|
using MLEM.Extensions;
|
2019-08-28 16:38:58 +02:00
|
|
|
using MLEM.Input;
|
2019-09-09 16:25:07 +02:00
|
|
|
using MLEM.Misc;
|
2019-08-28 18:27:17 +02:00
|
|
|
using MLEM.Ui.Elements;
|
2019-08-28 16:38:58 +02:00
|
|
|
|
|
|
|
namespace MLEM.Ui {
|
|
|
|
public class UiControls {
|
|
|
|
|
2019-08-28 18:27:17 +02:00
|
|
|
public readonly InputHandler Input;
|
2019-09-11 18:44:05 +02:00
|
|
|
protected readonly bool IsInputOurs;
|
|
|
|
protected readonly UiSystem System;
|
2019-08-28 16:38:58 +02:00
|
|
|
|
2019-09-09 17:18:44 +02:00
|
|
|
public RootElement ActiveRoot { get; private set; }
|
2019-08-28 18:27:17 +02:00
|
|
|
public Element MousedElement { get; private set; }
|
2019-09-17 19:30:54 +02:00
|
|
|
public Element SelectedElement => this.ActiveRoot?.SelectedElement;
|
2019-08-28 16:38:58 +02:00
|
|
|
|
2019-09-09 16:25:07 +02:00
|
|
|
public Buttons[] GamepadButtons = {Buttons.A};
|
|
|
|
public Buttons[] SecondaryGamepadButtons = {Buttons.X};
|
2019-09-11 10:44:23 +02:00
|
|
|
public Keys[] KeyboardButtons = {Keys.Space, Keys.Enter};
|
|
|
|
public object[] UpButtons = {Buttons.DPadUp, Buttons.LeftThumbstickUp};
|
|
|
|
public object[] DownButtons = {Buttons.DPadDown, Buttons.LeftThumbstickDown};
|
|
|
|
public object[] LeftButtons = {Buttons.DPadLeft, Buttons.LeftThumbstickLeft};
|
|
|
|
public object[] RightButtons = {Buttons.DPadRight, Buttons.LeftThumbstickRight};
|
2019-09-09 16:25:07 +02:00
|
|
|
public int GamepadIndex = -1;
|
|
|
|
|
2019-09-09 17:12:36 +02:00
|
|
|
public bool IsAutoNavMode { get; private set; }
|
|
|
|
|
2019-08-28 18:27:17 +02:00
|
|
|
public UiControls(UiSystem system, InputHandler inputHandler = null) {
|
2019-09-11 18:44:05 +02:00
|
|
|
this.System = system;
|
2019-08-28 18:27:17 +02:00
|
|
|
this.Input = inputHandler ?? new InputHandler();
|
2019-09-11 18:44:05 +02:00
|
|
|
this.IsInputOurs = inputHandler == null;
|
2019-08-30 18:15:50 +02:00
|
|
|
|
|
|
|
// enable all required gestures
|
2019-08-31 19:32:22 +02:00
|
|
|
InputHandler.EnableGestures(GestureType.Tap, GestureType.Hold);
|
2019-08-28 18:27:17 +02:00
|
|
|
}
|
|
|
|
|
2019-09-11 18:44:05 +02:00
|
|
|
public virtual void Update() {
|
|
|
|
if (this.IsInputOurs)
|
2019-08-28 18:27:17 +02:00
|
|
|
this.Input.Update();
|
2019-09-11 18:44:05 +02:00
|
|
|
this.ActiveRoot = this.System.GetRootElements().FirstOrDefault(root => root.CanSelectContent);
|
2019-08-28 18:27:17 +02:00
|
|
|
|
2019-08-30 18:15:50 +02:00
|
|
|
// MOUSE INPUT
|
|
|
|
var mousedNow = this.GetElementUnderPos(this.Input.MousePosition);
|
2019-08-28 18:27:17 +02:00
|
|
|
if (mousedNow != this.MousedElement) {
|
|
|
|
if (this.MousedElement != null)
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementMouseExit?.Invoke(this.MousedElement);
|
2019-08-28 18:27:17 +02:00
|
|
|
if (mousedNow != null)
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementMouseEnter?.Invoke(mousedNow);
|
2019-08-28 18:27:17 +02:00
|
|
|
this.MousedElement = mousedNow;
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnMousedElementChanged?.Invoke(mousedNow);
|
2019-08-28 18:27:17 +02:00
|
|
|
}
|
2019-09-02 19:55:26 +02:00
|
|
|
|
2019-08-28 18:27:17 +02:00
|
|
|
if (this.Input.IsMouseButtonPressed(MouseButton.Left)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.IsAutoNavMode = false;
|
2019-08-28 18:27:17 +02:00
|
|
|
var selectedNow = mousedNow != null && mousedNow.CanBeSelected ? mousedNow : null;
|
2019-09-17 19:30:54 +02:00
|
|
|
this.ActiveRoot?.SelectElement(selectedNow);
|
2019-08-28 18:27:17 +02:00
|
|
|
if (mousedNow != null)
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementPressed?.Invoke(mousedNow);
|
2019-08-28 18:27:17 +02:00
|
|
|
} else if (this.Input.IsMouseButtonPressed(MouseButton.Right)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.IsAutoNavMode = false;
|
2019-08-28 18:27:17 +02:00
|
|
|
if (mousedNow != null)
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementSecondaryPressed?.Invoke(mousedNow);
|
2019-08-30 18:15:50 +02:00
|
|
|
}
|
2019-09-02 19:55:26 +02:00
|
|
|
|
2019-08-30 18:15:50 +02:00
|
|
|
// KEYBOARD INPUT
|
2019-09-11 10:44:23 +02:00
|
|
|
else if (this.KeyboardButtons.Any(this.Input.IsKeyPressed)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.IsAutoNavMode = true;
|
2019-09-04 15:36:47 +02:00
|
|
|
if (this.SelectedElement?.Root != null) {
|
2019-08-28 18:27:17 +02:00
|
|
|
if (this.Input.IsModifierKeyDown(ModifierKey.Shift)) {
|
|
|
|
// secondary action on element using space or enter
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementSecondaryPressed?.Invoke(this.SelectedElement);
|
2019-08-28 18:27:17 +02:00
|
|
|
} else {
|
|
|
|
// first action on element using space or enter
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementPressed?.Invoke(this.SelectedElement);
|
2019-08-28 18:27:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (this.Input.IsKeyPressed(Keys.Tab)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.IsAutoNavMode = true;
|
2019-08-28 18:27:17 +02:00
|
|
|
// tab or shift-tab to next or previous element
|
2019-09-09 17:12:36 +02:00
|
|
|
var backward = this.Input.IsModifierKeyDown(ModifierKey.Shift);
|
|
|
|
var next = this.GetTabNextElement(backward);
|
|
|
|
if (this.SelectedElement?.Root != null)
|
|
|
|
next = this.SelectedElement.GetTabNextElement(backward, next);
|
2019-09-17 19:30:54 +02:00
|
|
|
this.ActiveRoot?.SelectElement(next);
|
2019-08-28 18:27:17 +02:00
|
|
|
}
|
2019-09-02 19:55:26 +02:00
|
|
|
|
2019-08-30 18:15:50 +02:00
|
|
|
// TOUCH INPUT
|
|
|
|
else if (this.Input.GetGesture(GestureType.Tap, out var tap)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.IsAutoNavMode = false;
|
2019-08-30 18:15:50 +02:00
|
|
|
var tapped = this.GetElementUnderPos(tap.Position.ToPoint());
|
2019-09-17 19:30:54 +02:00
|
|
|
this.ActiveRoot?.SelectElement(tapped);
|
2019-08-30 18:15:50 +02:00
|
|
|
if (tapped != null)
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementPressed?.Invoke(tapped);
|
2019-08-30 18:15:50 +02:00
|
|
|
} else if (this.Input.GetGesture(GestureType.Hold, out var hold)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.IsAutoNavMode = false;
|
2019-08-30 18:15:50 +02:00
|
|
|
var held = this.GetElementUnderPos(hold.Position.ToPoint());
|
2019-09-17 19:30:54 +02:00
|
|
|
this.ActiveRoot?.SelectElement(held);
|
2019-08-30 18:15:50 +02:00
|
|
|
if (held != null)
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementSecondaryPressed?.Invoke(held);
|
2019-08-30 18:15:50 +02:00
|
|
|
}
|
2019-09-09 16:25:07 +02:00
|
|
|
|
|
|
|
// GAMEPAD INPUT
|
2019-09-11 10:44:23 +02:00
|
|
|
else if (this.GamepadButtons.Any(b => this.IsGamepadPressed(b))) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.IsAutoNavMode = true;
|
2019-09-09 16:25:07 +02:00
|
|
|
if (this.SelectedElement?.Root != null)
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementPressed?.Invoke(this.SelectedElement);
|
2019-09-11 10:44:23 +02:00
|
|
|
} else if (this.SecondaryGamepadButtons.Any(b => this.IsGamepadPressed(b))) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.IsAutoNavMode = true;
|
2019-09-09 16:25:07 +02:00
|
|
|
if (this.SelectedElement?.Root != null)
|
2019-09-13 13:57:25 +02:00
|
|
|
this.System.OnElementSecondaryPressed?.Invoke(this.SelectedElement);
|
2019-09-11 10:44:23 +02:00
|
|
|
} else if (this.DownButtons.Any(this.IsGamepadPressed)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.HandleGamepadNextElement(Direction2.Down);
|
2019-09-11 10:44:23 +02:00
|
|
|
} else if (this.LeftButtons.Any(this.IsGamepadPressed)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.HandleGamepadNextElement(Direction2.Left);
|
2019-09-11 10:44:23 +02:00
|
|
|
} else if (this.RightButtons.Any(this.IsGamepadPressed)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.HandleGamepadNextElement(Direction2.Right);
|
2019-09-11 10:44:23 +02:00
|
|
|
} else if (this.UpButtons.Any(this.IsGamepadPressed)) {
|
2019-09-09 17:12:36 +02:00
|
|
|
this.HandleGamepadNextElement(Direction2.Up);
|
2019-09-09 16:25:07 +02:00
|
|
|
}
|
2019-08-28 18:27:17 +02:00
|
|
|
}
|
|
|
|
|
2019-09-11 18:44:05 +02:00
|
|
|
public virtual Element GetElementUnderPos(Point position, bool transform = true) {
|
|
|
|
foreach (var root in this.System.GetRootElements()) {
|
2019-09-02 19:55:26 +02:00
|
|
|
var pos = transform ? position.Transform(root.InvTransform) : position;
|
|
|
|
var moused = root.Element.GetElementUnderPos(pos);
|
2019-08-28 18:27:17 +02:00
|
|
|
if (moused != null)
|
|
|
|
return moused;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-09-11 18:44:05 +02:00
|
|
|
protected virtual Element GetTabNextElement(bool backward) {
|
2019-09-09 17:18:44 +02:00
|
|
|
if (this.ActiveRoot == null)
|
2019-08-28 18:27:17 +02:00
|
|
|
return null;
|
2019-09-12 12:39:18 +02:00
|
|
|
var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element);
|
2019-09-09 17:18:44 +02:00
|
|
|
if (this.SelectedElement?.Root != this.ActiveRoot) {
|
2019-08-28 18:27:17 +02:00
|
|
|
return backward ? children.LastOrDefault(c => c.CanBeSelected) : children.FirstOrDefault(c => c.CanBeSelected);
|
|
|
|
} else {
|
|
|
|
var foundCurr = false;
|
|
|
|
Element lastFound = null;
|
|
|
|
foreach (var child in children) {
|
|
|
|
if (!child.CanBeSelected)
|
|
|
|
continue;
|
|
|
|
if (child == this.SelectedElement) {
|
|
|
|
// when going backwards, return the last element found before the current one
|
|
|
|
if (backward)
|
|
|
|
return lastFound;
|
|
|
|
foundCurr = true;
|
|
|
|
} else {
|
|
|
|
// when going forwards, return the element after the current one
|
|
|
|
if (!backward && foundCurr)
|
|
|
|
return child;
|
|
|
|
}
|
|
|
|
lastFound = child;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2019-08-28 16:38:58 +02:00
|
|
|
|
2019-09-11 18:44:05 +02:00
|
|
|
protected virtual Element GetGamepadNextElement(Rectangle searchArea) {
|
|
|
|
if (this.ActiveRoot == null)
|
|
|
|
return null;
|
2019-09-12 12:39:18 +02:00
|
|
|
var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element);
|
2019-09-11 18:44:05 +02:00
|
|
|
if (this.SelectedElement?.Root != this.ActiveRoot) {
|
|
|
|
return children.FirstOrDefault(c => c.CanBeSelected);
|
|
|
|
} else {
|
|
|
|
Element closest = null;
|
|
|
|
float closestDist = 0;
|
|
|
|
foreach (var child in children) {
|
|
|
|
if (!child.CanBeSelected || child == this.SelectedElement || !searchArea.Intersects(child.Area))
|
|
|
|
continue;
|
|
|
|
var dist = Vector2.Distance(child.Area.Center.ToVector2(), this.SelectedElement.Area.Center.ToVector2());
|
|
|
|
if (closest == null || dist < closestDist) {
|
|
|
|
closest = child;
|
|
|
|
closestDist = dist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return closest;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private bool IsGamepadPressed(object button) {
|
|
|
|
return this.Input.IsPressed(button, this.GamepadIndex);
|
|
|
|
}
|
|
|
|
|
2019-09-09 17:12:36 +02:00
|
|
|
private void HandleGamepadNextElement(Direction2 dir) {
|
|
|
|
this.IsAutoNavMode = true;
|
|
|
|
Rectangle searchArea = default;
|
|
|
|
if (this.SelectedElement?.Root != null) {
|
|
|
|
searchArea = this.SelectedElement.Area;
|
2019-09-11 18:44:05 +02:00
|
|
|
var (_, _, width, height) = this.System.Viewport;
|
2019-09-09 17:12:36 +02:00
|
|
|
switch (dir) {
|
|
|
|
case Direction2.Down:
|
|
|
|
searchArea.Height += height;
|
|
|
|
break;
|
|
|
|
case Direction2.Left:
|
|
|
|
searchArea.X -= width;
|
|
|
|
searchArea.Width += width;
|
|
|
|
break;
|
|
|
|
case Direction2.Right:
|
|
|
|
searchArea.Width += width;
|
|
|
|
break;
|
|
|
|
case Direction2.Up:
|
|
|
|
searchArea.Y -= height;
|
|
|
|
searchArea.Height += height;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var next = this.GetGamepadNextElement(searchArea);
|
|
|
|
if (this.SelectedElement != null)
|
|
|
|
next = this.SelectedElement.GetGamepadNextElement(dir, next);
|
|
|
|
if (next != null)
|
2019-09-09 17:18:44 +02:00
|
|
|
this.ActiveRoot.SelectElement(next);
|
2019-09-09 17:12:36 +02:00
|
|
|
}
|
|
|
|
|
2019-08-28 16:38:58 +02:00
|
|
|
}
|
|
|
|
}
|