diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs index 15ccdff..c57b146 100644 --- a/Demos/UiDemo.cs +++ b/Demos/UiDemo.cs @@ -150,7 +150,9 @@ namespace Demos { OnPressed = element => tooltip.IsHidden = !tooltip.IsHidden }); - var slider = new Slider(Anchor.AutoLeft, new Vector2(1, 10), 5, 1); + var slider = new Slider(Anchor.AutoLeft, new Vector2(1, 10), 5, 1) { + StepPerScroll = 0.01F + }; root.AddChild(new Paragraph(Anchor.AutoLeft, 1, paragraph => "Slider is at " + (slider.CurrentValue * 100).Floor() + "%") {PositionOffset = new Vector2(0, 1)}); root.AddChild(slider); diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index 8952230..e3634ad 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using MLEM.Extensions; using MLEM.Input; +using MLEM.Misc; using MLEM.Textures; using MLEM.Ui.Style; @@ -75,7 +76,7 @@ namespace MLEM.Ui.Elements { } } public Point ScaledChildPadding => this.childPadding.Multiply(this.Scale); - + public GenericCallback OnPressed; public GenericCallback OnSecondaryPressed; public GenericCallback OnSelected; @@ -301,7 +302,7 @@ namespace MLEM.Ui.Elements { } } } - + this.area = new Rectangle(pos, actualSize); this.OnAreaUpdated?.Invoke(this); @@ -449,6 +450,14 @@ namespace MLEM.Ui.Elements { return this.CanBeMoused && this.Area.Contains(position) ? this : null; } + public virtual Element GetTabNextElement(bool backward, Element usualNext) { + return usualNext; + } + + public virtual Element GetGamepadNextElement(Direction2 dir, Element usualNext) { + return usualNext; + } + public void AndChildren(Action action) { action(this); foreach (var child in this.Children) diff --git a/MLEM.Ui/Elements/ElementHelper.cs b/MLEM.Ui/Elements/ElementHelper.cs index 703ac1c..b371ef4 100644 --- a/MLEM.Ui/Elements/ElementHelper.cs +++ b/MLEM.Ui/Elements/ElementHelper.cs @@ -16,11 +16,12 @@ namespace MLEM.Ui.Elements { public static Panel ShowInfoBox(UiSystem system, Anchor anchor, float width, string text, float buttonHeight = 10, string okText = "Okay") { var box = new Panel(anchor, new Vector2(width, 1), Vector2.Zero, true); box.AddChild(new Paragraph(Anchor.AutoLeft, 1, text)); - box.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, buttonHeight), okText) { + var button = box.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, buttonHeight), okText) { OnPressed = element => system.Remove("InfoBox"), PositionOffset = new Vector2(0, 1) }); - system.Add("InfoBox", box); + var root = system.Add("InfoBox", box); + root.SelectElement(button); return box; } diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs index 92df8b5..8279e15 100644 --- a/MLEM.Ui/Elements/Panel.cs +++ b/MLEM.Ui/Elements/Panel.cs @@ -38,7 +38,7 @@ namespace MLEM.Ui.Elements { // handle automatic element selection, the scroller needs to scroll to the right location this.OnSelectedElementChanged += (element, otherElement) => { - if (this.Controls.SelectedLastElementWithMouse) + if (!this.Controls.IsAutoNavMode) return; if (otherElement == null || !otherElement.GetParentTree().Contains(this)) return; diff --git a/MLEM.Ui/Elements/Slider.cs b/MLEM.Ui/Elements/Slider.cs index af01204..cb57c00 100644 --- a/MLEM.Ui/Elements/Slider.cs +++ b/MLEM.Ui/Elements/Slider.cs @@ -1,10 +1,31 @@ using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using MLEM.Misc; namespace MLEM.Ui.Elements { public class Slider : ScrollBar { public Slider(Anchor anchor, Vector2 size, int scrollerSize, float maxValue) : base(anchor, size, scrollerSize, maxValue, true) { + this.CanBeSelected = true; + } + + public override void Update(GameTime time) { + base.Update(time); + + if (this.IsSelected) { + if (this.Input.IsGamepadButtonDown(Buttons.DPadLeft) || this.Input.IsGamepadButtonDown(Buttons.LeftThumbstickLeft)) { + this.CurrentValue -= this.StepPerScroll; + } else if (this.Input.IsGamepadButtonDown(Buttons.DPadRight) || this.Input.IsGamepadButtonDown(Buttons.LeftThumbstickRight)) { + this.CurrentValue += this.StepPerScroll; + } + } + } + + public override Element GetGamepadNextElement(Direction2 dir, Element usualNext) { + if (dir == Direction2.Left || dir == Direction2.Right) + return null; + return base.GetGamepadNextElement(dir, usualNext); } } diff --git a/MLEM.Ui/UiControls.cs b/MLEM.Ui/UiControls.cs index 15fca11..62824c1 100644 --- a/MLEM.Ui/UiControls.cs +++ b/MLEM.Ui/UiControls.cs @@ -16,13 +16,14 @@ namespace MLEM.Ui { private readonly UiSystem system; public Element MousedElement { get; private set; } - public Element SelectedElement { get; private set; } - public bool SelectedLastElementWithMouse { get; private set; } + public Element SelectedElement => this.GetActiveRoot()?.SelectedElement; public Buttons[] GamepadButtons = {Buttons.A}; public Buttons[] SecondaryGamepadButtons = {Buttons.X}; public int GamepadIndex = -1; + public bool IsAutoNavMode { get; private set; } + public UiControls(UiSystem system, InputHandler inputHandler = null) { this.system = system; this.Input = inputHandler ?? new InputHandler(); @@ -48,17 +49,20 @@ namespace MLEM.Ui { } if (this.Input.IsMouseButtonPressed(MouseButton.Left)) { + this.IsAutoNavMode = false; var selectedNow = mousedNow != null && mousedNow.CanBeSelected ? mousedNow : null; - this.SelectElement(selectedNow, true); + this.GetActiveRoot().SelectElement(selectedNow); if (mousedNow != null) mousedNow.OnPressed?.Invoke(mousedNow); } else if (this.Input.IsMouseButtonPressed(MouseButton.Right)) { + this.IsAutoNavMode = false; if (mousedNow != null) mousedNow.OnSecondaryPressed?.Invoke(mousedNow); } // KEYBOARD INPUT else if (this.Input.IsKeyPressed(Keys.Enter) || this.Input.IsKeyPressed(Keys.Space)) { + this.IsAutoNavMode = true; if (this.SelectedElement?.Root != null) { if (this.Input.IsModifierKeyDown(ModifierKey.Shift)) { // secondary action on element using space or enter @@ -69,76 +73,50 @@ namespace MLEM.Ui { } } } else if (this.Input.IsKeyPressed(Keys.Tab)) { + this.IsAutoNavMode = true; // tab or shift-tab to next or previous element - this.SelectElement(this.GetTabNextElement(this.Input.IsModifierKeyDown(ModifierKey.Shift)), false); + var backward = this.Input.IsModifierKeyDown(ModifierKey.Shift); + var next = this.GetTabNextElement(backward); + if (this.SelectedElement?.Root != null) + next = this.SelectedElement.GetTabNextElement(backward, next); + this.GetActiveRoot().SelectElement(next); } // TOUCH INPUT else if (this.Input.GetGesture(GestureType.Tap, out var tap)) { + this.IsAutoNavMode = false; var tapped = this.GetElementUnderPos(tap.Position.ToPoint()); - this.SelectElement(tapped, true); + this.GetActiveRoot().SelectElement(tapped); if (tapped != null) tapped.OnPressed?.Invoke(tapped); } else if (this.Input.GetGesture(GestureType.Hold, out var hold)) { + this.IsAutoNavMode = false; var held = this.GetElementUnderPos(hold.Position.ToPoint()); - this.SelectElement(held, true); + this.GetActiveRoot().SelectElement(held); if (held != null) held.OnSecondaryPressed?.Invoke(held); } // GAMEPAD INPUT else if (this.GamepadButtons.Any(b => this.Input.IsGamepadButtonPressed(b, this.GamepadIndex))) { + this.IsAutoNavMode = true; if (this.SelectedElement?.Root != null) this.SelectedElement.OnPressed?.Invoke(this.SelectedElement); } else if (this.SecondaryGamepadButtons.Any(b => this.Input.IsGamepadButtonPressed(b, this.GamepadIndex))) { + this.IsAutoNavMode = true; if (this.SelectedElement?.Root != null) this.SelectedElement.OnSecondaryPressed?.Invoke(this.SelectedElement); } else if (this.Input.IsGamepadButtonPressed(Buttons.DPadDown) || this.Input.IsGamepadButtonPressed(Buttons.LeftThumbstickDown)) { - var next = this.GetGamepadNextElement(searchArea => { - searchArea.Height += this.system.Viewport.Height; - return searchArea; - }); - if (next != null) - this.SelectElement(next, false); + this.HandleGamepadNextElement(Direction2.Down); } else if (this.Input.IsGamepadButtonPressed(Buttons.DPadLeft) || this.Input.IsGamepadButtonPressed(Buttons.LeftThumbstickLeft)) { - var next = this.GetGamepadNextElement(searchArea => { - searchArea.X -= this.system.Viewport.Width; - searchArea.Width += this.system.Viewport.Width; - return searchArea; - }); - if (next != null) - this.SelectElement(next, false); + this.HandleGamepadNextElement(Direction2.Left); } else if (this.Input.IsGamepadButtonPressed(Buttons.DPadRight) || this.Input.IsGamepadButtonPressed(Buttons.LeftThumbstickRight)) { - var next = this.GetGamepadNextElement(searchArea => { - searchArea.Width += this.system.Viewport.Width; - return searchArea; - }); - if (next != null) - this.SelectElement(next, false); + this.HandleGamepadNextElement(Direction2.Right); } else if (this.Input.IsGamepadButtonPressed(Buttons.DPadUp) || this.Input.IsGamepadButtonPressed(Buttons.LeftThumbstickUp)) { - var next = this.GetGamepadNextElement(searchArea => { - searchArea.Y -= this.system.Viewport.Height; - searchArea.Height += this.system.Viewport.Height; - return searchArea; - }); - if (next != null) - this.SelectElement(next, false); + this.HandleGamepadNextElement(Direction2.Up); } } - public void SelectElement(Element element, bool mouse) { - if (this.SelectedElement == element) - return; - - if (this.SelectedElement != null) - this.SelectedElement.OnDeselected?.Invoke(this.SelectedElement); - if (element != null) - element.OnSelected?.Invoke(element); - this.SelectedElement = element; - this.SelectedLastElementWithMouse = mouse; - this.system.ApplyToAll(e => e.OnSelectedElementChanged?.Invoke(e, element)); - } - public Element GetElementUnderPos(Point position, bool transform = true) { foreach (var root in this.system.GetRootElements()) { var pos = transform ? position.Transform(root.InvTransform) : position; @@ -150,7 +128,7 @@ namespace MLEM.Ui { } private Element GetTabNextElement(bool backward) { - var currRoot = this.system.GetRootElements().FirstOrDefault(root => root.CanSelectContent); + var currRoot = this.GetActiveRoot(); if (currRoot == null) return null; var children = currRoot.Element.GetChildren(regardChildrensChildren: true).Append(currRoot.Element); @@ -178,15 +156,44 @@ namespace MLEM.Ui { } } - private Element GetGamepadNextElement(Func searchAreaFunc) { - var currRoot = this.system.GetRootElements().FirstOrDefault(root => root.CanSelectContent); + private void HandleGamepadNextElement(Direction2 dir) { + this.IsAutoNavMode = true; + Rectangle searchArea = default; + if (this.SelectedElement?.Root != null) { + searchArea = this.SelectedElement.Area; + var (_, _, width, height) = this.system.Viewport; + 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) + this.GetActiveRoot().SelectElement(next); + } + + private Element GetGamepadNextElement(Rectangle searchArea) { + var currRoot = this.GetActiveRoot(); if (currRoot == null) return null; var children = currRoot.Element.GetChildren(regardChildrensChildren: true).Append(currRoot.Element); if (this.SelectedElement?.Root != currRoot) { return children.FirstOrDefault(c => c.CanBeSelected); } else { - var searchArea = searchAreaFunc(this.SelectedElement.Area); Element closest = null; float closestDist = 0; foreach (var child in children) { @@ -202,5 +209,9 @@ namespace MLEM.Ui { } } + public RootElement GetActiveRoot() { + return this.system.GetRootElements().FirstOrDefault(root => root.CanSelectContent); + } + } } \ No newline at end of file diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index 1d402bc..b89e8db 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -75,7 +75,7 @@ namespace MLEM.Ui { }); this.OnSelectedElementDrawn = (element, time, batch, alpha) => { - if (!this.Controls.SelectedLastElementWithMouse && element.SelectionIndicator != null) { + if (this.Controls.IsAutoNavMode && element.SelectionIndicator != null) { batch.Draw(element.SelectionIndicator, element.DisplayArea, Color.White * alpha, element.Scale / 2); } }; @@ -176,12 +176,26 @@ namespace MLEM.Ui { public Matrix Transform = Matrix.Identity; public Matrix InvTransform => Matrix.Invert(this.Transform); + public Element SelectedElement { get; private set; } + public RootElement(string name, Element element, UiSystem system) { this.Name = name; this.Element = element; this.System = system; } + public void SelectElement(Element element) { + if (this.SelectedElement == element) + return; + + if (this.SelectedElement != null) + this.SelectedElement.OnDeselected?.Invoke(this.SelectedElement); + if (element != null) + element.OnSelected?.Invoke(element); + this.SelectedElement = element; + this.System.ApplyToAll(e => e.OnSelectedElementChanged?.Invoke(e, element)); + } + public void MoveToFront() { this.System.Remove(this.Name); this.System.Add(this);