1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-05-10 11:28:44 +02:00

Added a SquishingGroup element to MLEM.Ui

This commit is contained in:
Ell 2021-12-11 17:26:55 +01:00
parent 28a928ec2c
commit 103d7c7503
5 changed files with 146 additions and 22 deletions

View file

@ -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**

View file

@ -176,7 +176,7 @@ namespace MLEM.Ui.Elements {
}
/// <summary>
/// The priority of this element as part of its <see cref="Parent"/> 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.
/// </summary>
public int Priority {
get => this.priority;
@ -256,6 +256,10 @@ namespace MLEM.Ui.Elements {
/// Stores whether this element is its <see cref="Root"/>'s <see cref="RootElement.SelectedElement"/>.
/// </summary>
public bool IsSelected { get; protected set; }
/// <summary>
/// Returns whether this element's <see cref="SetAreaDirty"/> method has been recently called and its area has not been updated since then using <see cref="UpdateAreaIfDirty"/> or <see cref="ForceUpdateArea"/>.
/// </summary>
public bool AreaDirty { get; private set; }
/// <summary>
/// A style property that contains the selection indicator that is displayed on this element if it is the <see cref="RootElement.SelectedElement"/>
@ -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.
/// </summary>
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);
}
/// <summary>
/// Updates this element's <see cref="Area"/> and all of its <see cref="Children"/> by calling <see cref="ForceUpdateArea"/> if <see cref="areaDirty"/> is true.
/// Updates this element's <see cref="Area"/> and all of its <see cref="Children"/> by calling <see cref="ForceUpdateArea"/> if <see cref="AreaDirty"/> is true.
/// </summary>
/// <returns>Whether <see cref="areaDirty"/> was true and <see cref="ForceUpdateArea"/> was called</returns>
/// <returns>Whether <see cref="AreaDirty"/> was true and <see cref="ForceUpdateArea"/> was called</returns>
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 <see cref="Children"/>'s areas.
/// </summary>
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 {
}
}
/// <summary>
/// Sets this element's <see cref="Area"/> to the given <see cref="RectangleF"/> and invokes the <see cref="UiSystem.OnElementAreaUpdated"/> event.
/// This method also updates all of this element's <see cref="Children"/>'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.
/// </summary>
/// <seealso cref="ForceUpdateArea"/>
public void SetAreaDirectlyAndUpdateChildren(RectangleF area) {
this.area = area;
this.System.InvokeOnElementAreaUpdated(this);
foreach (var child in this.Children)
child.ForceUpdateArea();
}
/// <summary>
/// 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 <see cref="Size"/>'s documentation.
@ -1024,6 +1035,16 @@ namespace MLEM.Ui.Elements {
this.SecondActionSound.SetFromStyle(style.ActionSound);
}
/// <summary>
/// A method that gets called by this element's <see cref="Children"/> when their <see cref="SetAreaDirty"/> methods get called.
/// Note that the element's area might already be dirty, which will not stop this method from being called.
/// </summary>
/// <param name="child">The child whose area is being set dirty</param>
protected virtual void OnChildAreaDirty(Element child) {
if (child.Anchor >= Anchor.AutoLeft || this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren)
this.SetAreaDirty();
}
/// <summary>
/// Transforms the given <paramref name="position"/> by the inverse of this element's <see cref="Transform"/> matrix.
/// </summary>

View file

@ -0,0 +1,77 @@
using System;
using Microsoft.Xna.Framework;
using MLEM.Extensions;
using MLEM.Misc;
namespace MLEM.Ui.Elements {
/// <summary>
/// A squishing group is a <see cref="Group"/> whose <see cref="Element.Children"/> automatically get resized so that they do not overlap each other.
/// The order in which elements are squished depends on their <see cref="Element.Priority"/>, 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 <see cref="Element.Children"/>) determines squish order.
/// </summary>
public class SquishingGroup : Group {
/// <summary>
/// Creates a new squishing group with the given settings.
/// </summary>
/// <param name="anchor">The group's anchor.</param>
/// <param name="size">The group's size.</param>
public SquishingGroup(Anchor anchor, Vector2 size) : base(anchor, size, false) {
}
/// <inheritdoc />
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);
}
}
/// <inheritdoc />
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;
}
}
}

View file

@ -1,4 +1,3 @@
using System;
using System.Globalization;
using Microsoft.Xna.Framework;

View file

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