1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-25 14:08:34 +01:00

Compare commits

..

No commits in common. "fa34258bbefe879fb1f297a9eb5351b1e13c424a" and "f166c3d25607692d65a4b10a06ca8dfc0b22b0ea" have entirely different histories.

6 changed files with 54 additions and 137 deletions

View file

@ -15,7 +15,6 @@ 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
@ -23,7 +22,6 @@ 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**
@ -36,7 +34,6 @@ Removals
### MLEM.Ui
Additions
- Added Element.OnStyleInit event
- Added UiControls.AutoNavModeChanged event
Improvements
- Allow for checkboxes and radio buttons to be disabled
@ -52,7 +49,6 @@ 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

View file

@ -1,6 +1,5 @@
using System;
using Microsoft.Xna.Framework;
using MLEM.Input;
using MLEM.Ui.Style;
namespace MLEM.Ui.Elements {
@ -23,12 +22,6 @@ 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;
@ -98,8 +91,7 @@ namespace MLEM.Ui.Elements {
/// </summary>
public void SnapPositionToMouse() {
var viewport = this.System.Viewport;
var snapPosition = this.SnapPosition ?? this.Input.ViewportMousePosition.ToVector2();
var offset = (snapPosition + this.MouseOffset.Value) / this.Scale;
var offset = (this.Input.ViewportMousePosition.ToVector2() + this.MouseOffset.Value) / this.Scale;
if (offset.X < viewport.X)
offset.X = viewport.X;
if (offset.Y < viewport.Y)

View file

@ -21,6 +21,36 @@ 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"/>.
@ -55,25 +85,6 @@ 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.
@ -102,35 +113,9 @@ 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 => 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;
public bool IsAutoNavMode { get; internal set; }
/// <summary>
/// Creates a new instance of the ui controls.
@ -392,20 +377,17 @@ namespace MLEM.Ui {
return children.FirstOrDefault(c => c.CanBeSelected);
} else {
Element closest = null;
float closestPriority = 0;
float closestDistSq = 0;
foreach (var child in children) {
if (!child.CanBeSelected || child == this.SelectedElement)
continue;
var (xOffset, yOffset) = child.Area.Center - this.SelectedElement.Area.Center;
var angle = Math.Abs(direction.Angle() - (float) Math.Atan2(yOffset, xOffset));
if (angle >= MathHelper.PiOver2 - Element.Epsilon)
if (Math.Abs(direction.Angle() - Math.Atan2(yOffset, xOffset)) >= MathHelper.PiOver2 - Element.Epsilon)
continue;
var distSq = child.Area.DistanceSquared(this.SelectedElement.Area);
// 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) {
if (closest == null || distSq < closestDistSq) {
closest = child;
closestPriority = priority;
closestDistSq = distSq;
}
}
return closest;

View file

@ -1,46 +0,0 @@
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;
}
}
}
}

View file

@ -59,12 +59,6 @@ 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.
@ -240,13 +234,13 @@ namespace MLEM.Input {
this.ConnectedGamepads = GamePad.MaximumGamePadCount;
for (var i = 0; i < GamePad.MaximumGamePadCount; i++) {
this.lastGamepads[i] = this.gamepads[i];
this.gamepads[i] = GamePadState.Default;
var state = GamePadState.Default;
if (GamePad.GetCapabilities(i).IsConnected) {
if (active) {
this.gamepads[i] = GamePad.GetState(i);
state = GamePad.GetState(i);
if (this.StoreAllActiveInputs) {
foreach (var button in EnumHelper.Buttons) {
if (this.IsGamepadButtonDown(button, i))
if (state.IsButtonDown(button))
this.inputsDownAccum.Add(button);
}
}
@ -255,6 +249,7 @@ namespace MLEM.Input {
if (this.ConnectedGamepads > i)
this.ConnectedGamepads = i;
}
this.gamepads[i] = state;
}
if (this.HandleGamepadRepeats) {
@ -451,48 +446,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).GetAnalogValue(button) > this.GamepadButtonDeadzone)
if (this.GetGamepadState(i).IsButtonDown(button))
return true;
}
return false;
}
return this.GetGamepadState(index).GetAnalogValue(button) > this.GamepadButtonDeadzone;
return this.GetGamepadState(index).IsButtonDown(button);
}
/// <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).GetAnalogValue(button) <= this.GamepadButtonDeadzone)
if (this.GetGamepadState(i).IsButtonUp(button))
return true;
}
return false;
}
return this.GetGamepadState(index).GetAnalogValue(button) <= this.GamepadButtonDeadzone;
return this.GetGamepadState(index).IsButtonUp(button);
}
/// <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).GetAnalogValue(button) > this.GamepadButtonDeadzone)
if (this.GetLastGamepadState(i).IsButtonDown(button))
return true;
}
return false;
}
return this.GetLastGamepadState(index).GetAnalogValue(button) > this.GamepadButtonDeadzone;
return this.GetLastGamepadState(index).IsButtonDown(button);
}
/// <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).GetAnalogValue(button) <= this.GamepadButtonDeadzone)
if (this.GetLastGamepadState(i).IsButtonUp(button))
return true;
}
return false;
}
return this.GetLastGamepadState(index).GetAnalogValue(button) <= this.GamepadButtonDeadzone;
return this.GetLastGamepadState(index).IsButtonUp(button);
}
/// <summary>

View file

@ -305,13 +305,11 @@ namespace Sandbox {
newPanel.AddChild(new Button(Anchor.TopLeft, new Vector2(100, 20), "Text", "Tooltip text"));
this.UiSystem.Add("Panel", newPanel);
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);
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;
}
this.UiSystem.Add("Keybinds", keybindPanel);
}