From 103d7c7503f328c71eb2623d2ae7d8d8dd894718 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sat, 11 Dec 2021 17:26:55 +0100 Subject: [PATCH] Added a SquishingGroup element to MLEM.Ui --- CHANGELOG.md | 1 + MLEM.Ui/Elements/Element.cs | 49 +++++++++++++------ MLEM.Ui/Elements/SquishingGroup.cs | 77 ++++++++++++++++++++++++++++++ MLEM/Extensions/ColorExtensions.cs | 1 - Sandbox/GameImpl.cs | 40 +++++++++++++--- 5 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 MLEM.Ui/Elements/SquishingGroup.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5679acc..b2a5f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Additions - Allow specifying a maximum amount of characters for a TextField - Added a multiline editing mode to TextField - Added a formatting code to allow for inline font changes +- Added a SquishingGroup element Improvements - **Made Image ScaleToImage take ui scale into account** diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index 1398e05..faeee87 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -176,7 +176,7 @@ namespace MLEM.Ui.Elements { } /// /// The priority of this element as part of its element. - /// A higher priority means the element will be drawn first and, if auto-anchoring is used, anchored higher up within its parent. + /// A higher priority means the element will be drawn first, but not anchored higher up if auto-anchoring is used. /// public int Priority { get => this.priority; @@ -256,6 +256,10 @@ namespace MLEM.Ui.Elements { /// Stores whether this element is its 's . /// public bool IsSelected { get; protected set; } + /// + /// Returns whether this element's method has been recently called and its area has not been updated since then using or . + /// + public bool AreaDirty { get; private set; } /// /// A style property that contains the selection indicator that is displayed on this element if it is the @@ -398,7 +402,6 @@ namespace MLEM.Ui.Elements { private Vector2 size; private Vector2 offset; private RectangleF area; - private bool areaDirty; private bool isHidden; private int priority; private UiStyle style; @@ -515,17 +518,16 @@ namespace MLEM.Ui.Elements { /// If this element is auto-anchored or its parent automatically changes its size based on its children, this element's parent's area is also marked dirty. /// public void SetAreaDirty() { - this.areaDirty = true; - if (this.Parent != null && (this.Anchor >= Anchor.AutoLeft || this.Parent.SetWidthBasedOnChildren || this.Parent.SetHeightBasedOnChildren)) - this.Parent.SetAreaDirty(); + this.AreaDirty = true; + this.Parent?.OnChildAreaDirty(this); } /// - /// Updates this element's and all of its by calling if is true. + /// Updates this element's and all of its by calling if is true. /// - /// Whether was true and was called + /// Whether was true and was called public bool UpdateAreaIfDirty() { - if (this.areaDirty) { + if (this.AreaDirty) { this.ForceUpdateArea(); return true; } @@ -537,7 +539,7 @@ namespace MLEM.Ui.Elements { /// This method also updates all of this element's 's areas. /// public virtual void ForceUpdateArea() { - this.areaDirty = false; + this.AreaDirty = false; if (this.IsHidden) return; // if the parent's area is dirty, it would get updated anyway when querying its ChildPaddedArea, @@ -644,11 +646,7 @@ namespace MLEM.Ui.Elements { newSize.Y = parentArea.Bottom - pos.Y; } - this.area = new RectangleF(pos, newSize); - this.System.InvokeOnElementAreaUpdated(this); - - foreach (var child in this.Children) - child.ForceUpdateArea(); + this.SetAreaDirectlyAndUpdateChildren(new RectangleF(pos, newSize)); if (this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren) { Element foundChild = null; @@ -697,6 +695,19 @@ namespace MLEM.Ui.Elements { } } + /// + /// Sets this element's to the given and invokes the event. + /// This method also updates all of this element's 's areas. + /// Note that this method does not take into account any auto-sizing, anchoring or positioning, and so it should be used sparingly, if at all. + /// + /// + public void SetAreaDirectlyAndUpdateChildren(RectangleF area) { + this.area = area; + this.System.InvokeOnElementAreaUpdated(this); + foreach (var child in this.Children) + child.ForceUpdateArea(); + } + /// /// Calculates the actual size that this element should take up, based on the area that its parent encompasses. /// By default, this is based on the information specified in 's documentation. @@ -1024,6 +1035,16 @@ namespace MLEM.Ui.Elements { this.SecondActionSound.SetFromStyle(style.ActionSound); } + /// + /// A method that gets called by this element's when their methods get called. + /// Note that the element's area might already be dirty, which will not stop this method from being called. + /// + /// The child whose area is being set dirty + protected virtual void OnChildAreaDirty(Element child) { + if (child.Anchor >= Anchor.AutoLeft || this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren) + this.SetAreaDirty(); + } + /// /// Transforms the given by the inverse of this element's matrix. /// diff --git a/MLEM.Ui/Elements/SquishingGroup.cs b/MLEM.Ui/Elements/SquishingGroup.cs new file mode 100644 index 0000000..7d29fa6 --- /dev/null +++ b/MLEM.Ui/Elements/SquishingGroup.cs @@ -0,0 +1,77 @@ +using System; +using Microsoft.Xna.Framework; +using MLEM.Extensions; +using MLEM.Misc; + +namespace MLEM.Ui.Elements { + /// + /// A squishing group is a whose automatically get resized so that they do not overlap each other. + /// The order in which elements are squished depends on their , where elements with a lower priority will move out of the way of elements with a higher priority. + /// If all elements have the same priority, their addition order (their order in ) determines squish order. + /// + public class SquishingGroup : Group { + + /// + /// Creates a new squishing group with the given settings. + /// + /// The group's anchor. + /// The group's size. + public SquishingGroup(Anchor anchor, Vector2 size) : base(anchor, size, false) { + } + + /// + public override void ForceUpdateArea() { + base.ForceUpdateArea(); + // we squish children in order of priority, since auto-anchoring is based on addition order + for (var i = 0; i < this.SortedChildren.Count; i++) { + var child = this.SortedChildren[i]; + if (SquishChild(child, out var squished)) + child.SetAreaDirectlyAndUpdateChildren(squished); + } + } + + /// + protected override void OnChildAreaDirty(Element child) { + base.OnChildAreaDirty(child); + this.SetAreaDirty(); + } + + private static bool SquishChild(Element element, out RectangleF squishedArea) { + squishedArea = default; + if (element.IsHidden) + return false; + var pos = element.Area.Location; + var size = element.Area.Size; + foreach (var sibling in element.GetSiblings(e => !e.IsHidden)) { + var siblingArea = sibling.Area; + var leftIntersect = siblingArea.Right - pos.X; + var rightIntersect = pos.X + size.X - siblingArea.Left; + var bottomIntersect = siblingArea.Bottom - pos.Y; + var topIntersect = pos.Y + size.Y - siblingArea.Top; + if (leftIntersect > 0 && rightIntersect > 0 && bottomIntersect > 0 && topIntersect > 0) { + if (rightIntersect + leftIntersect < topIntersect + bottomIntersect) { + if (rightIntersect > leftIntersect) { + size.X -= siblingArea.Right - pos.X; + pos.X = Math.Max(pos.X, siblingArea.Right); + } else { + size.X = Math.Min(pos.X + size.X, siblingArea.Left) - pos.X; + } + } else { + if (topIntersect > bottomIntersect) { + size.Y -= siblingArea.Bottom - pos.Y; + pos.Y = Math.Max(pos.Y, siblingArea.Bottom); + } else { + size.Y = Math.Min(pos.Y + size.Y, siblingArea.Top) - pos.Y; + } + } + } + } + if (!pos.Equals(element.Area.Location, Epsilon) || !size.Equals(element.Area.Size, Epsilon)) { + squishedArea = new RectangleF(pos, size); + return true; + } + return false; + } + + } +} \ No newline at end of file diff --git a/MLEM/Extensions/ColorExtensions.cs b/MLEM/Extensions/ColorExtensions.cs index 5fee79d..e14f5b0 100644 --- a/MLEM/Extensions/ColorExtensions.cs +++ b/MLEM/Extensions/ColorExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using Microsoft.Xna.Framework; diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index 70e4f18..3354521 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -8,7 +8,6 @@ using Microsoft.Xna.Framework.Input; using MLEM.Cameras; using MLEM.Data; using MLEM.Data.Content; -using MLEM.Extended.Extensions; using MLEM.Extended.Font; using MLEM.Extended.Tiled; using MLEM.Extensions; @@ -22,9 +21,7 @@ using MLEM.Textures; using MLEM.Ui; using MLEM.Ui.Elements; using MLEM.Ui.Style; -using MonoGame.Extended; using MonoGame.Extended.Tiled; -using Group = MLEM.Ui.Elements.Group; namespace Sandbox { public class GameImpl : MlemGame { @@ -216,7 +213,7 @@ namespace Sandbox { invalidPanel.AddChild(new VerticalSpace(1)); this.UiSystem.Add("Invalid", invalidPanel);*/ - var loadGroup = new Group(Anchor.TopLeft, Vector2.One, false); + /*var loadGroup = new Group(Anchor.TopLeft, Vector2.One, false); var loadPanel = loadGroup.AddChild(new Panel(Anchor.Center, new Vector2(150, 150), Vector2.Zero, false, true, false) { ChildPadding = new Padding(5, 10, 5, 5) }); @@ -240,7 +237,36 @@ namespace Sandbox { } }; par.OnDrawn = (e, time, batch, a) => batch.DrawRectangle(e.DisplayArea.ToExtended(), Color.Red); - this.UiSystem.Add("Load", loadGroup); + this.UiSystem.Add("Load", loadGroup);*/ + + var spillPanel = new Panel(Anchor.Center, new Vector2(100), Vector2.Zero); + var squishingGroup = spillPanel.AddChild(new SquishingGroup(Anchor.TopLeft, Vector2.One)); + squishingGroup.AddChild(new Button(Anchor.TopLeft, new Vector2(30), "TL") { + OnUpdated = (e, time) => e.IsHidden = Input.IsKeyDown(Keys.D1), + Priority = 10 + }).SetData("Ref", "TL"); + squishingGroup.AddChild(new Button(Anchor.TopRight, new Vector2(30), "TR") { + OnUpdated = (e, time) => e.IsHidden = Input.IsKeyDown(Keys.D2), + Priority = 20 + }).SetData("Ref", "TR"); + squishingGroup.AddChild(new Button(Anchor.BottomLeft, new Vector2(30), "BL") { + OnUpdated = (e, time) => e.IsHidden = Input.IsKeyDown(Keys.D3), + Priority = 30 + }).SetData("Ref", "BL"); + squishingGroup.AddChild(new Button(Anchor.BottomRight, new Vector2(30), "BR") { + OnUpdated = (e, time) => e.IsHidden = Input.IsKeyDown(Keys.D4), + Priority = 40 + }).SetData("Ref", "BR"); + squishingGroup.AddChild(new Button(Anchor.Center, Vector2.Zero, "0") { + PositionOffset = new Vector2(-10, -5), + Size = new Vector2(60, 55), + OnPressed = e => { + e.Priority = 100 - e.Priority; + ((Button) e).Text.Text = e.Priority.ToString(); + e.SetAreaDirty(); + } + }).SetData("Ref", "Main"); + this.UiSystem.Add("SpillTest", spillPanel); } protected override void DoUpdate(GameTime gameTime) { @@ -253,10 +279,10 @@ namespace Sandbox { this.camera.Zoom(0.1F * Math.Sign(delta), this.InputHandler.MousePosition.ToVector2()); } - if (Input.InputsDown.Length > 0) + /*if (Input.InputsDown.Length > 0) Console.WriteLine("Down: " + string.Join(", ", Input.InputsDown)); if (Input.InputsPressed.Length > 0) - Console.WriteLine("Pressed: " + string.Join(", ", Input.InputsPressed)); + Console.WriteLine("Pressed: " + string.Join(", ", Input.InputsPressed));*/ } protected override void DoDraw(GameTime gameTime) {