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) {