mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-25 05:58:35 +01:00
Allow elements to auto-adjust their size even when their children are aligned oddly
This commit is contained in:
parent
b78465c054
commit
f0cc4b0c80
5 changed files with 82 additions and 31 deletions
|
@ -10,6 +10,12 @@ Jump to version:
|
||||||
- [5.0.0](#500)
|
- [5.0.0](#500)
|
||||||
|
|
||||||
## 6.1.0
|
## 6.1.0
|
||||||
|
### MLEM.Ui
|
||||||
|
Additions
|
||||||
|
- Added some extension methods for querying Anchor types
|
||||||
|
|
||||||
|
Improvements
|
||||||
|
- Allow elements to auto-adjust their size even when their children are aligned oddly
|
||||||
|
|
||||||
## 6.0.0
|
## 6.0.0
|
||||||
### MLEM
|
### MLEM
|
||||||
|
|
|
@ -633,7 +633,7 @@ namespace MLEM.Ui.Elements {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.Anchor >= Anchor.AutoLeft) {
|
if (this.Anchor.IsAuto()) {
|
||||||
Element previousChild;
|
Element previousChild;
|
||||||
if (this.Anchor == Anchor.AutoInline || this.Anchor == Anchor.AutoInlineIgnoreOverflow) {
|
if (this.Anchor == Anchor.AutoInline || this.Anchor == Anchor.AutoInlineIgnoreOverflow) {
|
||||||
previousChild = this.GetOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach);
|
previousChild = this.GetOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach);
|
||||||
|
@ -687,11 +687,13 @@ namespace MLEM.Ui.Elements {
|
||||||
if (this.SetHeightBasedOnChildren) {
|
if (this.SetHeightBasedOnChildren) {
|
||||||
var lowest = this.GetLowestChild(e => !e.IsHidden);
|
var lowest = this.GetLowestChild(e => !e.IsHidden);
|
||||||
if (lowest != null) {
|
if (lowest != null) {
|
||||||
|
if (lowest.Anchor.IsTopAligned()) {
|
||||||
autoSize.Y = lowest.UnscrolledArea.Bottom - pos.Y + this.ScaledChildPadding.Bottom;
|
autoSize.Y = lowest.UnscrolledArea.Bottom - pos.Y + this.ScaledChildPadding.Bottom;
|
||||||
|
} else {
|
||||||
|
autoSize.Y = lowest.UnscrolledArea.Height + this.ScaledChildPadding.Height;
|
||||||
|
}
|
||||||
foundChild = lowest;
|
foundChild = lowest;
|
||||||
} else {
|
} else {
|
||||||
if (this.Children.Any(e => !e.IsHidden))
|
|
||||||
throw new InvalidOperationException($"{this} with root {this.Root.Name} sets its height based on children but it only has visible children anchored too low ({string.Join(", ", this.Children.Where(c => !c.IsHidden).Select(c => c.Anchor))})");
|
|
||||||
autoSize.Y = 0;
|
autoSize.Y = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -699,11 +701,13 @@ namespace MLEM.Ui.Elements {
|
||||||
if (this.SetWidthBasedOnChildren) {
|
if (this.SetWidthBasedOnChildren) {
|
||||||
var rightmost = this.GetRightmostChild(e => !e.IsHidden);
|
var rightmost = this.GetRightmostChild(e => !e.IsHidden);
|
||||||
if (rightmost != null) {
|
if (rightmost != null) {
|
||||||
|
if (rightmost.Anchor.IsLeftAligned()) {
|
||||||
autoSize.X = rightmost.UnscrolledArea.Right - pos.X + this.ScaledChildPadding.Right;
|
autoSize.X = rightmost.UnscrolledArea.Right - pos.X + this.ScaledChildPadding.Right;
|
||||||
|
} else {
|
||||||
|
autoSize.X = rightmost.UnscrolledArea.Width + this.ScaledChildPadding.Width;
|
||||||
|
}
|
||||||
foundChild = rightmost;
|
foundChild = rightmost;
|
||||||
} else {
|
} else {
|
||||||
if (this.Children.Any(e => !e.IsHidden))
|
|
||||||
throw new InvalidOperationException($"{this} with root {this.Root.Name} sets its width based on children but it only has visible children anchored too far right ({string.Join(", ", this.Children.Where(c => !c.IsHidden).Select(c => c.Anchor))})");
|
|
||||||
autoSize.X = 0;
|
autoSize.X = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -717,15 +721,13 @@ namespace MLEM.Ui.Elements {
|
||||||
// we want to leave some leeway to prevent float rounding causing an infinite loop
|
// we want to leave some leeway to prevent float rounding causing an infinite loop
|
||||||
if (!autoSize.Equals(this.UnscrolledArea.Size, Element.Epsilon)) {
|
if (!autoSize.Equals(this.UnscrolledArea.Size, Element.Epsilon)) {
|
||||||
recursion++;
|
recursion++;
|
||||||
if (recursion >= 16) {
|
if (recursion >= 16)
|
||||||
throw new ArithmeticException($"The area of {this} with root {this.Root.Name} has recursively updated too often. Does its child {foundChild} contain any conflicting auto-sizing settings?");
|
throw new ArithmeticException($"The area of {this} with root {this.Root.Name} has recursively updated too often. Does its child {foundChild} contain any conflicting auto-sizing settings?");
|
||||||
} else {
|
|
||||||
UpdateDisplayArea(autoSize);
|
UpdateDisplayArea(autoSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets this element's <see cref="Area"/> to the given <see cref="RectangleF"/> and invokes the <see cref="UiSystem.OnElementAreaUpdated"/> event.
|
/// Sets this element's <see cref="Area"/> to the given <see cref="RectangleF"/> and invokes the <see cref="UiSystem.OnElementAreaUpdated"/> event.
|
||||||
|
@ -773,13 +775,15 @@ namespace MLEM.Ui.Elements {
|
||||||
/// <returns>The lowest element, or null if no such element exists</returns>
|
/// <returns>The lowest element, or null if no such element exists</returns>
|
||||||
public Element GetLowestChild(Func<Element, bool> condition = null) {
|
public Element GetLowestChild(Func<Element, bool> condition = null) {
|
||||||
Element lowest = null;
|
Element lowest = null;
|
||||||
|
var lowestX = float.MinValue;
|
||||||
foreach (var child in this.Children) {
|
foreach (var child in this.Children) {
|
||||||
if (condition != null && !condition(child))
|
if (condition != null && !condition(child))
|
||||||
continue;
|
continue;
|
||||||
if (child.Anchor > Anchor.TopRight && child.Anchor < Anchor.AutoLeft)
|
var x = !child.Anchor.IsTopAligned() ? child.UnscrolledArea.Height : child.UnscrolledArea.Bottom;
|
||||||
continue;
|
if (x >= lowestX) {
|
||||||
if (lowest == null || child.UnscrolledArea.Bottom >= lowest.UnscrolledArea.Bottom)
|
|
||||||
lowest = child;
|
lowest = child;
|
||||||
|
lowestX = x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return lowest;
|
return lowest;
|
||||||
}
|
}
|
||||||
|
@ -791,13 +795,15 @@ namespace MLEM.Ui.Elements {
|
||||||
/// <returns>The rightmost element, or null if no such element exists</returns>
|
/// <returns>The rightmost element, or null if no such element exists</returns>
|
||||||
public Element GetRightmostChild(Func<Element, bool> condition = null) {
|
public Element GetRightmostChild(Func<Element, bool> condition = null) {
|
||||||
Element rightmost = null;
|
Element rightmost = null;
|
||||||
|
var rightmostX = float.MinValue;
|
||||||
foreach (var child in this.Children) {
|
foreach (var child in this.Children) {
|
||||||
if (condition != null && !condition(child))
|
if (condition != null && !condition(child))
|
||||||
continue;
|
continue;
|
||||||
if (child.Anchor < Anchor.AutoLeft && child.Anchor != Anchor.TopLeft && child.Anchor != Anchor.CenterLeft && child.Anchor != Anchor.BottomLeft)
|
var x = !child.Anchor.IsLeftAligned() ? child.UnscrolledArea.Width : child.UnscrolledArea.Right;
|
||||||
continue;
|
if (child.UnscrolledArea.Right >= rightmostX) {
|
||||||
if (rightmost == null || child.UnscrolledArea.Right >= rightmost.UnscrolledArea.Right)
|
|
||||||
rightmost = child;
|
rightmost = child;
|
||||||
|
rightmostX = x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return rightmost;
|
return rightmost;
|
||||||
}
|
}
|
||||||
|
@ -1119,7 +1125,7 @@ namespace MLEM.Ui.Elements {
|
||||||
/// <param name="grandchild">Whether the <paramref name="child"/> is a grandchild of this element, rather than a direct child.</param>
|
/// <param name="grandchild">Whether the <paramref name="child"/> is a grandchild of this element, rather than a direct child.</param>
|
||||||
protected virtual void OnChildAreaDirty(Element child, bool grandchild) {
|
protected virtual void OnChildAreaDirty(Element child, bool grandchild) {
|
||||||
if (!grandchild) {
|
if (!grandchild) {
|
||||||
if (child.Anchor >= Anchor.AutoLeft || this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren)
|
if (child.Anchor.IsAuto() || this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren)
|
||||||
this.SetAreaDirty();
|
this.SetAreaDirty();
|
||||||
}
|
}
|
||||||
this.Parent?.OnChildAreaDirty(child, true);
|
this.Parent?.OnChildAreaDirty(child, true);
|
||||||
|
|
|
@ -214,5 +214,32 @@ namespace MLEM.Ui.Elements {
|
||||||
return tooltip;
|
return tooltip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <see cref="Anchor"/> is automatic. The anchors <see cref="Anchor.AutoLeft"/>, <see cref="Anchor.AutoCenter"/>, <see cref="Anchor.AutoRight"/>, <see cref="Anchor.AutoInline"/> and <see cref="Anchor.AutoInlineIgnoreOverflow"/> will return true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anchor">The anchor to query.</param>
|
||||||
|
/// <returns>Whether the given anchor is automatic.</returns>
|
||||||
|
public static bool IsAuto(this Anchor anchor) {
|
||||||
|
return anchor == Anchor.AutoLeft || anchor == Anchor.AutoCenter || anchor == Anchor.AutoRight || anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineIgnoreOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <see cref="Anchor"/> is left-aligned for the purpose of <see cref="Element.GetRightmostChild"/>. The anchors <see cref="Anchor.TopLeft"/>, <see cref="Anchor.CenterLeft"/>, <see cref="Anchor.BottomLeft"/>, <see cref="Anchor.AutoLeft"/>, <see cref="Anchor.AutoInline"/> and <see cref="Anchor.AutoInlineIgnoreOverflow"/> will return true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anchor">The anchor to query.</param>
|
||||||
|
/// <returns>Whether the given anchor is left-aligned.</returns>
|
||||||
|
public static bool IsLeftAligned(this Anchor anchor) {
|
||||||
|
return anchor == Anchor.TopLeft || anchor == Anchor.CenterLeft || anchor == Anchor.BottomLeft || anchor == Anchor.AutoLeft || anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineIgnoreOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <see cref="Anchor"/> is top-aligned for the purpose of <see cref="Element.GetLowestChild"/>. The anchors <see cref="Anchor.TopLeft"/>, <see cref="Anchor.TopCenter"/>, <see cref="Anchor.TopRight"/>, <see cref="Anchor.AutoLeft"/>, <see cref="Anchor.AutoCenter"/>, <see cref="Anchor.AutoRight"/>, <see cref="Anchor.AutoInline"/> and <see cref="Anchor.AutoInlineIgnoreOverflow"/> will return true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anchor">The anchor to query.</param>
|
||||||
|
/// <returns>Whether the given anchor is top-aligned.</returns>
|
||||||
|
public static bool IsTopAligned(this Anchor anchor) {
|
||||||
|
return anchor == Anchor.TopLeft || anchor == Anchor.TopCenter || anchor == Anchor.TopRight || anchor == Anchor.AutoLeft || anchor == Anchor.AutoCenter || anchor == Anchor.AutoRight || anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineIgnoreOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ namespace MLEM.Ui.Elements {
|
||||||
if (this.SetHeightBasedOnChildren)
|
if (this.SetHeightBasedOnChildren)
|
||||||
throw new NotSupportedException("A panel can't both set height based on children and scroll overflow");
|
throw new NotSupportedException("A panel can't both set height based on children and scroll overflow");
|
||||||
foreach (var child in this.Children) {
|
foreach (var child in this.Children) {
|
||||||
if (child != this.ScrollBar && child.Anchor < Anchor.AutoLeft)
|
if (child != this.ScrollBar && !child.Anchor.IsAuto())
|
||||||
throw new NotSupportedException($"A panel that handles overflow can't contain non-automatic anchors ({child})");
|
throw new NotSupportedException($"A panel that handles overflow can't contain non-automatic anchors ({child})");
|
||||||
if (child is Panel panel && panel.scrollOverflow)
|
if (child is Panel panel && panel.scrollOverflow)
|
||||||
throw new NotSupportedException($"A panel that scrolls overflow cannot contain another panel that scrolls overflow ({child})");
|
throw new NotSupportedException($"A panel that scrolls overflow cannot contain another panel that scrolls overflow ({child})");
|
||||||
|
|
|
@ -32,9 +32,9 @@ namespace Sandbox;
|
||||||
public class GameImpl : MlemGame {
|
public class GameImpl : MlemGame {
|
||||||
|
|
||||||
private Camera camera;
|
private Camera camera;
|
||||||
private TiledMap map;
|
/*private TiledMap map;
|
||||||
private IndividualTiledMapRenderer mapRenderer;
|
private IndividualTiledMapRenderer mapRenderer;
|
||||||
private TiledMapCollisions collisions;
|
private TiledMapCollisions collisions;*/
|
||||||
private RawContentManager rawContent;
|
private RawContentManager rawContent;
|
||||||
private TokenizedString tokenized;
|
private TokenizedString tokenized;
|
||||||
|
|
||||||
|
@ -50,14 +50,14 @@ public class GameImpl : MlemGame {
|
||||||
|
|
||||||
this.Components.Add(this.rawContent = new RawContentManager(this.Services));
|
this.Components.Add(this.rawContent = new RawContentManager(this.Services));
|
||||||
|
|
||||||
this.map = MlemGame.LoadContent<TiledMap>("Tiled/Map");
|
/*this.map = MlemGame.LoadContent<TiledMap>("Tiled/Map");
|
||||||
this.mapRenderer = new IndividualTiledMapRenderer(this.map);
|
this.mapRenderer = new IndividualTiledMapRenderer(this.map);
|
||||||
this.collisions = new TiledMapCollisions(this.map);
|
this.collisions = new TiledMapCollisions(this.map);*/
|
||||||
|
|
||||||
this.camera = new Camera(this.GraphicsDevice) {
|
this.camera = new Camera(this.GraphicsDevice) {
|
||||||
AutoScaleWithScreen = true,
|
AutoScaleWithScreen = true,
|
||||||
Scale = 2,
|
Scale = 2,
|
||||||
LookingPosition = new Vector2(25, 25) * this.map.GetTileSize(),
|
/*LookingPosition = new Vector2(25, 25) * this.map.GetTileSize(),*/
|
||||||
MinScale = 0.25F,
|
MinScale = 0.25F,
|
||||||
MaxScale = 4
|
MaxScale = 4
|
||||||
};
|
};
|
||||||
|
@ -296,7 +296,7 @@ public class GameImpl : MlemGame {
|
||||||
this.SpriteBatch.End();
|
this.SpriteBatch.End();
|
||||||
};
|
};
|
||||||
|
|
||||||
var viewport = new BoxingViewportAdapter(this.Window, this.GraphicsDevice, 1280, 720);
|
/*var viewport = new BoxingViewportAdapter(this.Window, this.GraphicsDevice, 1280, 720);
|
||||||
var newPanel = new Panel(Anchor.TopLeft, new Vector2(200, 100), new Vector2(10, 10));
|
var newPanel = new Panel(Anchor.TopLeft, new Vector2(200, 100), new Vector2(10, 10));
|
||||||
newPanel.AddChild(new Button(Anchor.TopLeft, new Vector2(100, 20), "Text", "Tooltip text"));
|
newPanel.AddChild(new Button(Anchor.TopLeft, new Vector2(100, 20), "Text", "Tooltip text"));
|
||||||
this.UiSystem.Add("Panel", newPanel);
|
this.UiSystem.Add("Panel", newPanel);
|
||||||
|
@ -341,7 +341,19 @@ public class GameImpl : MlemGame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.SpriteBatch.End();
|
this.SpriteBatch.End();
|
||||||
};
|
};*/
|
||||||
|
|
||||||
|
var widthPanel = new Panel(Anchor.Center, Vector2.One, Vector2.Zero, true) {SetWidthBasedOnChildren = true};
|
||||||
|
for (var i = 0; i < 5; i++)
|
||||||
|
widthPanel.AddChild(new Paragraph(Anchor.AutoCenter, 100000, "Test String " + Math.Pow(10, i), true) {
|
||||||
|
OnUpdated = (e, time) => {
|
||||||
|
if (Input.IsPressed(Keys.A)) {
|
||||||
|
e.Anchor = (Anchor) (((int) e.Anchor + 1) % EnumHelper.GetValues<Anchor>().Count());
|
||||||
|
Console.WriteLine(e.Anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.UiSystem.Add("WidthTest", widthPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DoUpdate(GameTime gameTime) {
|
protected override void DoUpdate(GameTime gameTime) {
|
||||||
|
@ -356,12 +368,12 @@ public class GameImpl : MlemGame {
|
||||||
|
|
||||||
/*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 (MlemGame.Input.InputsPressed.Length > 0)
|
/*if (MlemGame.Input.InputsPressed.Length > 0)
|
||||||
Console.WriteLine("Pressed: " + string.Join(", ", MlemGame.Input.InputsPressed));
|
Console.WriteLine("Pressed: " + string.Join(", ", MlemGame.Input.InputsPressed));
|
||||||
MlemGame.Input.HandleKeyboardRepeats = false;
|
MlemGame.Input.HandleKeyboardRepeats = false;
|
||||||
Console.WriteLine("Down time: " + MlemGame.Input.GetDownTime(Keys.A));
|
Console.WriteLine("Down time: " + MlemGame.Input.GetDownTime(Keys.A));
|
||||||
Console.WriteLine("Time since press: " + MlemGame.Input.GetTimeSincePress(Keys.A));
|
Console.WriteLine("Time since press: " + MlemGame.Input.GetTimeSincePress(Keys.A));
|
||||||
Console.WriteLine("Up time: " + MlemGame.Input.GetUpTime(Keys.A));
|
Console.WriteLine("Up time: " + MlemGame.Input.GetUpTime(Keys.A));*/
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DoDraw(GameTime gameTime) {
|
protected override void DoDraw(GameTime gameTime) {
|
||||||
|
|
Loading…
Reference in a new issue