mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-25 05:58:35 +01:00
Compare commits
6 commits
f166c3d256
...
fa34258bbe
Author | SHA1 | Date | |
---|---|---|---|
fa34258bbe | |||
8fa94f1186 | |||
d5f3453c71 | |||
35e3896dc1 | |||
5d97ab033f | |||
b77edd80d5 |
6 changed files with 137 additions and 54 deletions
|
@ -15,6 +15,7 @@ Additions
|
|||
- Added SoundEffectInstanceHandler.Stop
|
||||
- Added TextureRegion.OffsetCopy
|
||||
- Added RectangleF.DistanceSquared and RectangleF.Distance
|
||||
- Added GamepadExtensions.GetAnalogValue to get the analog value of any gamepad button
|
||||
|
||||
Improvements
|
||||
- Generify GenericFont's string drawing
|
||||
|
@ -22,6 +23,7 @@ Improvements
|
|||
- Added float version of GetRandomWeightedEntry
|
||||
- Allow LinkCode to specify a color to draw with
|
||||
- Allow better control over the order and layout of a Keybind's combinations
|
||||
- Allow setting a gamepad button deadzone in InputHandler
|
||||
|
||||
Fixes
|
||||
- **Fixed a formatting Code only knowing about the last Token that it is applied in**
|
||||
|
@ -34,6 +36,7 @@ Removals
|
|||
### MLEM.Ui
|
||||
Additions
|
||||
- Added Element.OnStyleInit event
|
||||
- Added UiControls.AutoNavModeChanged event
|
||||
|
||||
Improvements
|
||||
- Allow for checkboxes and radio buttons to be disabled
|
||||
|
@ -49,6 +52,7 @@ Improvements
|
|||
- Automatically select the first element when a dropdown is opened in auto nav mode
|
||||
- Improved gamepad navigation by employing angles between elements
|
||||
- Prefer elements that have the same parent as the currently selected element when using gamepad navigation
|
||||
- Allow specifying a custom position for a tooltip to snap to
|
||||
|
||||
Fixes
|
||||
- Fixed paragraph links having incorrect hover locations when using special text alignments
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Input;
|
||||
using MLEM.Ui.Style;
|
||||
|
||||
namespace MLEM.Ui.Elements {
|
||||
|
@ -22,6 +23,12 @@ namespace MLEM.Ui.Elements {
|
|||
/// The paragraph of text that this tooltip displays
|
||||
/// </summary>
|
||||
public Paragraph Paragraph;
|
||||
/// <summary>
|
||||
/// The position that this tooltip should be following (or snapped to) instead of the <see cref="InputHandler.ViewportMousePosition"/>.
|
||||
/// If this value is unset, <see cref="InputHandler.ViewportMousePosition"/> will be used as the snap position.
|
||||
/// Note that <see cref="MouseOffset"/> is still applied with this value set.
|
||||
/// </summary>
|
||||
public virtual Vector2? SnapPosition { get; set; }
|
||||
|
||||
private TimeSpan delayCountdown;
|
||||
private bool autoHidden;
|
||||
|
@ -91,7 +98,8 @@ namespace MLEM.Ui.Elements {
|
|||
/// </summary>
|
||||
public void SnapPositionToMouse() {
|
||||
var viewport = this.System.Viewport;
|
||||
var offset = (this.Input.ViewportMousePosition.ToVector2() + this.MouseOffset.Value) / this.Scale;
|
||||
var snapPosition = this.SnapPosition ?? this.Input.ViewportMousePosition.ToVector2();
|
||||
var offset = (snapPosition + this.MouseOffset.Value) / this.Scale;
|
||||
if (offset.X < viewport.X)
|
||||
offset.X = viewport.X;
|
||||
if (offset.Y < viewport.Y)
|
||||
|
|
|
@ -21,36 +21,6 @@ namespace MLEM.Ui {
|
|||
/// The input handler that is used for querying input
|
||||
/// </summary>
|
||||
public readonly InputHandler Input;
|
||||
/// <summary>
|
||||
/// This value ist true if the <see cref="InputHandler"/> was created by this ui controls instance, or if it was passed in.
|
||||
/// If the input handler was created by this instance, its <see cref="InputHandler.Update()"/> method should be called by us.
|
||||
/// </summary>
|
||||
protected readonly bool IsInputOurs;
|
||||
/// <summary>
|
||||
/// The <see cref="UiSystem"/> that this ui controls instance is controlling
|
||||
/// </summary>
|
||||
protected readonly UiSystem System;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RootElement"/> that is currently active.
|
||||
/// The active root element is the one with the highest <see cref="RootElement.Priority"/> that whose <see cref="RootElement.CanSelectContent"/> property is true.
|
||||
/// </summary>
|
||||
public RootElement ActiveRoot { get; protected set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Element"/> that the mouse is currently over.
|
||||
/// </summary>
|
||||
public Element MousedElement { get; protected set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Element"/> that is currently touched.
|
||||
/// </summary>
|
||||
public Element TouchedElement { get; protected set; }
|
||||
private readonly Dictionary<string, Element> selectedElements = new Dictionary<string, Element>();
|
||||
/// <summary>
|
||||
/// The element that is currently selected.
|
||||
/// This is the <see cref="RootElement.SelectedElement"/> of the <see cref="ActiveRoot"/>.
|
||||
/// </summary>
|
||||
public Element SelectedElement => this.GetSelectedElement(this.ActiveRoot);
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="Keys"/>, <see cref="Buttons"/> and/or <see cref="MouseButton"/> that act as the buttons on the keyboard which perform the <see cref="Element.OnPressed"/> action.
|
||||
/// If the <see cref="ModifierKey.Shift"/> is held, these buttons perform <see cref="Element.OnSecondaryPressed"/>.
|
||||
|
@ -85,6 +55,25 @@ namespace MLEM.Ui {
|
|||
/// This can be used to easily serialize and deserialize all ui keybinds.
|
||||
/// </summary>
|
||||
public readonly Keybind[] Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RootElement"/> that is currently active.
|
||||
/// The active root element is the one with the highest <see cref="RootElement.Priority"/> that whose <see cref="RootElement.CanSelectContent"/> property is true.
|
||||
/// </summary>
|
||||
public RootElement ActiveRoot { get; protected set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Element"/> that the mouse is currently over.
|
||||
/// </summary>
|
||||
public Element MousedElement { get; protected set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Element"/> that is currently touched.
|
||||
/// </summary>
|
||||
public Element TouchedElement { get; protected set; }
|
||||
/// <summary>
|
||||
/// The element that is currently selected.
|
||||
/// This is the <see cref="RootElement.SelectedElement"/> of the <see cref="ActiveRoot"/>.
|
||||
/// </summary>
|
||||
public Element SelectedElement => this.GetSelectedElement(this.ActiveRoot);
|
||||
/// <summary>
|
||||
/// The zero-based index of the <see cref="GamePad"/> used for gamepad input.
|
||||
/// If this index is lower than 0, every connected gamepad will trigger input.
|
||||
|
@ -113,9 +102,35 @@ namespace MLEM.Ui {
|
|||
/// <summary>
|
||||
/// If this value is true, the ui controls are in automatic navigation mode.
|
||||
/// This means that the <see cref="UiStyle.SelectionIndicator"/> will be drawn around the <see cref="SelectedElement"/>.
|
||||
/// To set this value, use <see cref="SelectElement"/> or <see cref="RootElement.SelectElement"/>
|
||||
/// </summary>
|
||||
public bool IsAutoNavMode { get; internal set; }
|
||||
public bool IsAutoNavMode {
|
||||
get => this.isAutoNavMode;
|
||||
set {
|
||||
if (this.isAutoNavMode != value) {
|
||||
this.isAutoNavMode = value;
|
||||
this.AutoNavModeChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event that is raised when <see cref="IsAutoNavMode"/> is changed.
|
||||
/// This can be used for custom actions like hiding the mouse cursor when automatic navigation is enabled.
|
||||
/// </summary>
|
||||
public event Action<bool> AutoNavModeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// This value ist true if the <see cref="InputHandler"/> was created by this ui controls instance, or if it was passed in.
|
||||
/// If the input handler was created by this instance, its <see cref="InputHandler.Update()"/> method should be called by us.
|
||||
/// </summary>
|
||||
protected readonly bool IsInputOurs;
|
||||
/// <summary>
|
||||
/// The <see cref="UiSystem"/> that this ui controls instance is controlling
|
||||
/// </summary>
|
||||
protected readonly UiSystem System;
|
||||
|
||||
private readonly Dictionary<string, Element> selectedElements = new Dictionary<string, Element>();
|
||||
private bool isAutoNavMode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the ui controls.
|
||||
|
@ -377,17 +392,20 @@ namespace MLEM.Ui {
|
|||
return children.FirstOrDefault(c => c.CanBeSelected);
|
||||
} else {
|
||||
Element closest = null;
|
||||
float closestDistSq = 0;
|
||||
float closestPriority = 0;
|
||||
foreach (var child in children) {
|
||||
if (!child.CanBeSelected || child == this.SelectedElement)
|
||||
continue;
|
||||
var (xOffset, yOffset) = child.Area.Center - this.SelectedElement.Area.Center;
|
||||
if (Math.Abs(direction.Angle() - Math.Atan2(yOffset, xOffset)) >= MathHelper.PiOver2 - Element.Epsilon)
|
||||
var angle = Math.Abs(direction.Angle() - (float) Math.Atan2(yOffset, xOffset));
|
||||
if (angle >= MathHelper.PiOver2 - Element.Epsilon)
|
||||
continue;
|
||||
var distSq = child.Area.DistanceSquared(this.SelectedElement.Area);
|
||||
if (closest == null || distSq < closestDistSq) {
|
||||
// both distance and angle play a role in a destination button's priority, so we combine them
|
||||
var priority = (distSq + 1) * (angle / MathHelper.PiOver2 + 1);
|
||||
if (closest == null || priority < closestPriority) {
|
||||
closest = child;
|
||||
closestDistSq = distSq;
|
||||
closestPriority = priority;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
|
|
46
MLEM/Input/GamepadExtensions.cs
Normal file
46
MLEM/Input/GamepadExtensions.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace MLEM.Input {
|
||||
/// <summary>
|
||||
/// A set of extension methods for dealing with <see cref="GamePad"/>, <see cref="GamePadState"/> and <see cref="Buttons"/>.
|
||||
/// </summary>
|
||||
public static class GamepadExtensions {
|
||||
|
||||
/// <summary>
|
||||
/// Returns the given <see cref="Buttons"/>'s value as an analog value between 0 and 1, where 1 is fully down and 0 is not down at all.
|
||||
/// For non-analog buttons, like <see cref="Buttons.X"/> or <see cref="Buttons.Start"/>, only 0 and 1 will be returned and no inbetween values are possible.
|
||||
/// </summary>
|
||||
/// <param name="state">The gamepad state to query.</param>
|
||||
/// <param name="button">The button to query.</param>
|
||||
/// <returns>The button's state as an analog value.</returns>
|
||||
public static float GetAnalogValue(this GamePadState state, Buttons button) {
|
||||
switch (button) {
|
||||
case Buttons.LeftThumbstickDown:
|
||||
return -MathHelper.Clamp(state.ThumbSticks.Left.Y, -1, 0);
|
||||
case Buttons.LeftThumbstickUp:
|
||||
return MathHelper.Clamp(state.ThumbSticks.Left.Y, 0, 1);
|
||||
case Buttons.LeftThumbstickLeft:
|
||||
return -MathHelper.Clamp(state.ThumbSticks.Left.X, -1, 0);
|
||||
case Buttons.LeftThumbstickRight:
|
||||
return MathHelper.Clamp(state.ThumbSticks.Left.X, 0, 1);
|
||||
case Buttons.RightTrigger:
|
||||
return state.Triggers.Right;
|
||||
case Buttons.LeftTrigger:
|
||||
return state.Triggers.Left;
|
||||
case Buttons.RightThumbstickDown:
|
||||
return -MathHelper.Clamp(state.ThumbSticks.Right.Y, -1, 0);
|
||||
case Buttons.RightThumbstickUp:
|
||||
return MathHelper.Clamp(state.ThumbSticks.Right.Y, 0, 1);
|
||||
case Buttons.RightThumbstickLeft:
|
||||
return -MathHelper.Clamp(state.ThumbSticks.Right.X, -1, 0);
|
||||
case Buttons.RightThumbstickRight:
|
||||
return MathHelper.Clamp(state.ThumbSticks.Right.X, 0, 1);
|
||||
default:
|
||||
return state.IsButtonDown(button) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -59,6 +59,12 @@ namespace MLEM.Input {
|
|||
/// Set this field to false to enable <see cref="InputsDown"/> and <see cref="InputsPressed"/> being calculated.
|
||||
/// </summary>
|
||||
public bool StoreAllActiveInputs;
|
||||
/// <summary>
|
||||
/// This field represents the deadzone that gamepad <see cref="Buttons"/> have when input is queried for them using this input handler.
|
||||
/// A deadzone is the percentage (between 0 and 1) that an analog value has to exceed for it to be considered down (<see cref="IsGamepadButtonDown"/>) or pressed (<see cref="IsGamepadButtonPressed"/>).
|
||||
/// Querying of analog values is done using <see cref="GamepadExtensions.GetAnalogValue"/>.
|
||||
/// </summary>
|
||||
public float GamepadButtonDeadzone;
|
||||
|
||||
/// <summary>
|
||||
/// An array of all <see cref="Keys"/>, <see cref="Buttons"/> and <see cref="MouseButton"/> values that are currently down.
|
||||
|
@ -234,13 +240,13 @@ namespace MLEM.Input {
|
|||
this.ConnectedGamepads = GamePad.MaximumGamePadCount;
|
||||
for (var i = 0; i < GamePad.MaximumGamePadCount; i++) {
|
||||
this.lastGamepads[i] = this.gamepads[i];
|
||||
var state = GamePadState.Default;
|
||||
this.gamepads[i] = GamePadState.Default;
|
||||
if (GamePad.GetCapabilities(i).IsConnected) {
|
||||
if (active) {
|
||||
state = GamePad.GetState(i);
|
||||
this.gamepads[i] = GamePad.GetState(i);
|
||||
if (this.StoreAllActiveInputs) {
|
||||
foreach (var button in EnumHelper.Buttons) {
|
||||
if (state.IsButtonDown(button))
|
||||
if (this.IsGamepadButtonDown(button, i))
|
||||
this.inputsDownAccum.Add(button);
|
||||
}
|
||||
}
|
||||
|
@ -249,7 +255,6 @@ namespace MLEM.Input {
|
|||
if (this.ConnectedGamepads > i)
|
||||
this.ConnectedGamepads = i;
|
||||
}
|
||||
this.gamepads[i] = state;
|
||||
}
|
||||
|
||||
if (this.HandleGamepadRepeats) {
|
||||
|
@ -446,48 +451,48 @@ namespace MLEM.Input {
|
|||
public bool IsGamepadButtonDown(Buttons button, int index = -1) {
|
||||
if (index < 0) {
|
||||
for (var i = 0; i < this.ConnectedGamepads; i++) {
|
||||
if (this.GetGamepadState(i).IsButtonDown(button))
|
||||
if (this.GetGamepadState(i).GetAnalogValue(button) > this.GamepadButtonDeadzone)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this.GetGamepadState(index).IsButtonDown(button);
|
||||
return this.GetGamepadState(index).GetAnalogValue(button) > this.GamepadButtonDeadzone;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GamePadState.IsButtonUp"/>
|
||||
public bool IsGamepadButtonUp(Buttons button, int index = -1) {
|
||||
if (index < 0) {
|
||||
for (var i = 0; i < this.ConnectedGamepads; i++) {
|
||||
if (this.GetGamepadState(i).IsButtonUp(button))
|
||||
if (this.GetGamepadState(i).GetAnalogValue(button) <= this.GamepadButtonDeadzone)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this.GetGamepadState(index).IsButtonUp(button);
|
||||
return this.GetGamepadState(index).GetAnalogValue(button) <= this.GamepadButtonDeadzone;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GamePadState.IsButtonDown"/>
|
||||
public bool WasGamepadButtonDown(Buttons button, int index = -1) {
|
||||
if (index < 0) {
|
||||
for (var i = 0; i < this.ConnectedGamepads; i++) {
|
||||
if (this.GetLastGamepadState(i).IsButtonDown(button))
|
||||
if (this.GetLastGamepadState(i).GetAnalogValue(button) > this.GamepadButtonDeadzone)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this.GetLastGamepadState(index).IsButtonDown(button);
|
||||
return this.GetLastGamepadState(index).GetAnalogValue(button) > this.GamepadButtonDeadzone;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GamePadState.IsButtonUp"/>
|
||||
public bool WasGamepadButtonUp(Buttons button, int index = -1) {
|
||||
if (index < 0) {
|
||||
for (var i = 0; i < this.ConnectedGamepads; i++) {
|
||||
if (this.GetLastGamepadState(i).IsButtonUp(button))
|
||||
if (this.GetLastGamepadState(i).GetAnalogValue(button) <= this.GamepadButtonDeadzone)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this.GetLastGamepadState(index).IsButtonUp(button);
|
||||
return this.GetLastGamepadState(index).GetAnalogValue(button) <= this.GamepadButtonDeadzone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -305,11 +305,13 @@ namespace Sandbox {
|
|||
newPanel.AddChild(new Button(Anchor.TopLeft, new Vector2(100, 20), "Text", "Tooltip text"));
|
||||
this.UiSystem.Add("Panel", newPanel);
|
||||
|
||||
var keybind = new Keybind(MouseButton.Left, ModifierKey.Shift);
|
||||
var keybindPanel = new Panel(Anchor.BottomRight, new Vector2(100, 100), new Vector2(5));
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var button = keybindPanel.AddChild(ElementHelper.KeybindButton(Anchor.AutoLeft, new Vector2(0.5F, 12), keybind, Input, "Press", Keys.Escape, index: i));
|
||||
button.Text.TextScale = 0.1F;
|
||||
var keybindPanel = new Panel(Anchor.BottomRight, new Vector2(130, 150), new Vector2(5));
|
||||
for (var i = 0; i < 15; i++) {
|
||||
var button = keybindPanel.AddChild(new Button(default, default, i.ToString()));
|
||||
button.Anchor = Anchor.AutoInline;
|
||||
button.Padding = new Padding(0.5F);
|
||||
button.SetHeightBasedOnChildren = false;
|
||||
button.Size = new Vector2(30, 50);
|
||||
}
|
||||
this.UiSystem.Add("Keybinds", keybindPanel);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue