mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-22 20:58:34 +01:00
Added a SquishingGroup element to MLEM.Ui
This commit is contained in:
parent
28a928ec2c
commit
103d7c7503
5 changed files with 146 additions and 22 deletions
|
@ -37,6 +37,7 @@ Additions
|
||||||
- Allow specifying a maximum amount of characters for a TextField
|
- Allow specifying a maximum amount of characters for a TextField
|
||||||
- Added a multiline editing mode to TextField
|
- Added a multiline editing mode to TextField
|
||||||
- Added a formatting code to allow for inline font changes
|
- Added a formatting code to allow for inline font changes
|
||||||
|
- Added a SquishingGroup element
|
||||||
|
|
||||||
Improvements
|
Improvements
|
||||||
- **Made Image ScaleToImage take ui scale into account**
|
- **Made Image ScaleToImage take ui scale into account**
|
||||||
|
|
|
@ -176,7 +176,7 @@ namespace MLEM.Ui.Elements {
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The priority of this element as part of its <see cref="Parent"/> element.
|
/// 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>
|
/// </summary>
|
||||||
public int Priority {
|
public int Priority {
|
||||||
get => this.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"/>.
|
/// Stores whether this element is its <see cref="Root"/>'s <see cref="RootElement.SelectedElement"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsSelected { get; protected set; }
|
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>
|
/// <summary>
|
||||||
/// A style property that contains the selection indicator that is displayed on this element if it is the <see cref="RootElement.SelectedElement"/>
|
/// 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 size;
|
||||||
private Vector2 offset;
|
private Vector2 offset;
|
||||||
private RectangleF area;
|
private RectangleF area;
|
||||||
private bool areaDirty;
|
|
||||||
private bool isHidden;
|
private bool isHidden;
|
||||||
private int priority;
|
private int priority;
|
||||||
private UiStyle style;
|
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.
|
/// 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>
|
/// </summary>
|
||||||
public void SetAreaDirty() {
|
public void SetAreaDirty() {
|
||||||
this.areaDirty = true;
|
this.AreaDirty = true;
|
||||||
if (this.Parent != null && (this.Anchor >= Anchor.AutoLeft || this.Parent.SetWidthBasedOnChildren || this.Parent.SetHeightBasedOnChildren))
|
this.Parent?.OnChildAreaDirty(this);
|
||||||
this.Parent.SetAreaDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </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() {
|
public bool UpdateAreaIfDirty() {
|
||||||
if (this.areaDirty) {
|
if (this.AreaDirty) {
|
||||||
this.ForceUpdateArea();
|
this.ForceUpdateArea();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -537,7 +539,7 @@ namespace MLEM.Ui.Elements {
|
||||||
/// This method also updates all of this element's <see cref="Children"/>'s areas.
|
/// This method also updates all of this element's <see cref="Children"/>'s areas.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void ForceUpdateArea() {
|
public virtual void ForceUpdateArea() {
|
||||||
this.areaDirty = false;
|
this.AreaDirty = false;
|
||||||
if (this.IsHidden)
|
if (this.IsHidden)
|
||||||
return;
|
return;
|
||||||
// if the parent's area is dirty, it would get updated anyway when querying its ChildPaddedArea,
|
// 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;
|
newSize.Y = parentArea.Bottom - pos.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.area = new RectangleF(pos, newSize);
|
this.SetAreaDirectlyAndUpdateChildren(new RectangleF(pos, newSize));
|
||||||
this.System.InvokeOnElementAreaUpdated(this);
|
|
||||||
|
|
||||||
foreach (var child in this.Children)
|
|
||||||
child.ForceUpdateArea();
|
|
||||||
|
|
||||||
if (this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren) {
|
if (this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren) {
|
||||||
Element foundChild = null;
|
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>
|
/// <summary>
|
||||||
/// Calculates the actual size that this element should take up, based on the area that its parent encompasses.
|
/// 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.
|
/// 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);
|
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>
|
/// <summary>
|
||||||
/// Transforms the given <paramref name="position"/> by the inverse of this element's <see cref="Transform"/> matrix.
|
/// Transforms the given <paramref name="position"/> by the inverse of this element's <see cref="Transform"/> matrix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
77
MLEM.Ui/Elements/SquishingGroup.cs
Normal file
77
MLEM.Ui/Elements/SquishingGroup.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ using Microsoft.Xna.Framework.Input;
|
||||||
using MLEM.Cameras;
|
using MLEM.Cameras;
|
||||||
using MLEM.Data;
|
using MLEM.Data;
|
||||||
using MLEM.Data.Content;
|
using MLEM.Data.Content;
|
||||||
using MLEM.Extended.Extensions;
|
|
||||||
using MLEM.Extended.Font;
|
using MLEM.Extended.Font;
|
||||||
using MLEM.Extended.Tiled;
|
using MLEM.Extended.Tiled;
|
||||||
using MLEM.Extensions;
|
using MLEM.Extensions;
|
||||||
|
@ -22,9 +21,7 @@ using MLEM.Textures;
|
||||||
using MLEM.Ui;
|
using MLEM.Ui;
|
||||||
using MLEM.Ui.Elements;
|
using MLEM.Ui.Elements;
|
||||||
using MLEM.Ui.Style;
|
using MLEM.Ui.Style;
|
||||||
using MonoGame.Extended;
|
|
||||||
using MonoGame.Extended.Tiled;
|
using MonoGame.Extended.Tiled;
|
||||||
using Group = MLEM.Ui.Elements.Group;
|
|
||||||
|
|
||||||
namespace Sandbox {
|
namespace Sandbox {
|
||||||
public class GameImpl : MlemGame {
|
public class GameImpl : MlemGame {
|
||||||
|
@ -216,7 +213,7 @@ namespace Sandbox {
|
||||||
invalidPanel.AddChild(new VerticalSpace(1));
|
invalidPanel.AddChild(new VerticalSpace(1));
|
||||||
this.UiSystem.Add("Invalid", invalidPanel);*/
|
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) {
|
var loadPanel = loadGroup.AddChild(new Panel(Anchor.Center, new Vector2(150, 150), Vector2.Zero, false, true, false) {
|
||||||
ChildPadding = new Padding(5, 10, 5, 5)
|
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);
|
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) {
|
protected override void DoUpdate(GameTime gameTime) {
|
||||||
|
@ -253,10 +279,10 @@ namespace Sandbox {
|
||||||
this.camera.Zoom(0.1F * Math.Sign(delta), this.InputHandler.MousePosition.ToVector2());
|
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));
|
Console.WriteLine("Down: " + string.Join(", ", Input.InputsDown));
|
||||||
if (Input.InputsPressed.Length > 0)
|
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) {
|
protected override void DoDraw(GameTime gameTime) {
|
||||||
|
|
Loading…
Reference in a new issue