From f0cc4b0c801bd89fe5c44dc3dfdcd4893cb44704 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 27 Jul 2022 11:19:40 +0200 Subject: [PATCH] Allow elements to auto-adjust their size even when their children are aligned oddly --- CHANGELOG.md | 6 +++++ MLEM.Ui/Elements/Element.cs | 42 ++++++++++++++++++------------- MLEM.Ui/Elements/ElementHelper.cs | 27 ++++++++++++++++++++ MLEM.Ui/Elements/Panel.cs | 4 +-- Sandbox/GameImpl.cs | 34 +++++++++++++++++-------- 5 files changed, 82 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d5d64..58f86fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ Jump to version: - [5.0.0](#500) ## 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 ### MLEM diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index c0ef4a7..b0a84b9 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -633,7 +633,7 @@ namespace MLEM.Ui.Elements { break; } - if (this.Anchor >= Anchor.AutoLeft) { + if (this.Anchor.IsAuto()) { Element previousChild; if (this.Anchor == Anchor.AutoInline || this.Anchor == Anchor.AutoInlineIgnoreOverflow) { previousChild = this.GetOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach); @@ -687,11 +687,13 @@ namespace MLEM.Ui.Elements { if (this.SetHeightBasedOnChildren) { var lowest = this.GetLowestChild(e => !e.IsHidden); if (lowest != null) { - autoSize.Y = lowest.UnscrolledArea.Bottom - pos.Y + this.ScaledChildPadding.Bottom; + if (lowest.Anchor.IsTopAligned()) { + autoSize.Y = lowest.UnscrolledArea.Bottom - pos.Y + this.ScaledChildPadding.Bottom; + } else { + autoSize.Y = lowest.UnscrolledArea.Height + this.ScaledChildPadding.Height; + } foundChild = lowest; } 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; } } @@ -699,11 +701,13 @@ namespace MLEM.Ui.Elements { if (this.SetWidthBasedOnChildren) { var rightmost = this.GetRightmostChild(e => !e.IsHidden); if (rightmost != null) { - autoSize.X = rightmost.UnscrolledArea.Right - pos.X + this.ScaledChildPadding.Right; + if (rightmost.Anchor.IsLeftAligned()) { + autoSize.X = rightmost.UnscrolledArea.Right - pos.X + this.ScaledChildPadding.Right; + } else { + autoSize.X = rightmost.UnscrolledArea.Width + this.ScaledChildPadding.Width; + } foundChild = rightmost; } 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; } } @@ -717,11 +721,9 @@ namespace MLEM.Ui.Elements { // we want to leave some leeway to prevent float rounding causing an infinite loop if (!autoSize.Equals(this.UnscrolledArea.Size, Element.Epsilon)) { 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?"); - } else { - UpdateDisplayArea(autoSize); - } + UpdateDisplayArea(autoSize); } } } @@ -773,13 +775,15 @@ namespace MLEM.Ui.Elements { /// The lowest element, or null if no such element exists public Element GetLowestChild(Func condition = null) { Element lowest = null; + var lowestX = float.MinValue; foreach (var child in this.Children) { if (condition != null && !condition(child)) continue; - if (child.Anchor > Anchor.TopRight && child.Anchor < Anchor.AutoLeft) - continue; - if (lowest == null || child.UnscrolledArea.Bottom >= lowest.UnscrolledArea.Bottom) + var x = !child.Anchor.IsTopAligned() ? child.UnscrolledArea.Height : child.UnscrolledArea.Bottom; + if (x >= lowestX) { lowest = child; + lowestX = x; + } } return lowest; } @@ -791,13 +795,15 @@ namespace MLEM.Ui.Elements { /// The rightmost element, or null if no such element exists public Element GetRightmostChild(Func condition = null) { Element rightmost = null; + var rightmostX = float.MinValue; foreach (var child in this.Children) { if (condition != null && !condition(child)) continue; - if (child.Anchor < Anchor.AutoLeft && child.Anchor != Anchor.TopLeft && child.Anchor != Anchor.CenterLeft && child.Anchor != Anchor.BottomLeft) - continue; - if (rightmost == null || child.UnscrolledArea.Right >= rightmost.UnscrolledArea.Right) + var x = !child.Anchor.IsLeftAligned() ? child.UnscrolledArea.Width : child.UnscrolledArea.Right; + if (child.UnscrolledArea.Right >= rightmostX) { rightmost = child; + rightmostX = x; + } } return rightmost; } @@ -1119,7 +1125,7 @@ namespace MLEM.Ui.Elements { /// Whether the is a grandchild of this element, rather than a direct child. protected virtual void OnChildAreaDirty(Element child, bool grandchild) { if (!grandchild) { - if (child.Anchor >= Anchor.AutoLeft || this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren) + if (child.Anchor.IsAuto() || this.SetWidthBasedOnChildren || this.SetHeightBasedOnChildren) this.SetAreaDirty(); } this.Parent?.OnChildAreaDirty(child, true); diff --git a/MLEM.Ui/Elements/ElementHelper.cs b/MLEM.Ui/Elements/ElementHelper.cs index e919beb..47d565d 100644 --- a/MLEM.Ui/Elements/ElementHelper.cs +++ b/MLEM.Ui/Elements/ElementHelper.cs @@ -214,5 +214,32 @@ namespace MLEM.Ui.Elements { return tooltip; } + /// + /// Returns whether the given is automatic. The anchors , , , and will return true. + /// + /// The anchor to query. + /// Whether the given anchor is automatic. + public static bool IsAuto(this Anchor anchor) { + return anchor == Anchor.AutoLeft || anchor == Anchor.AutoCenter || anchor == Anchor.AutoRight || anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineIgnoreOverflow; + } + + /// + /// Returns whether the given is left-aligned for the purpose of . The anchors , , , , and will return true. + /// + /// The anchor to query. + /// Whether the given anchor is left-aligned. + 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; + } + + /// + /// Returns whether the given is top-aligned for the purpose of . The anchors , , , , , , and will return true. + /// + /// The anchor to query. + /// Whether the given anchor is top-aligned. + 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; + } + } } diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs index 75cb704..2d015f3 100644 --- a/MLEM.Ui/Elements/Panel.cs +++ b/MLEM.Ui/Elements/Panel.cs @@ -98,7 +98,7 @@ namespace MLEM.Ui.Elements { if (this.SetHeightBasedOnChildren) throw new NotSupportedException("A panel can't both set height based on children and scroll overflow"); 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})"); if (child is Panel panel && panel.scrollOverflow) throw new NotSupportedException($"A panel that scrolls overflow cannot contain another panel that scrolls overflow ({child})"); @@ -161,7 +161,7 @@ namespace MLEM.Ui.Elements { /// protected override void OnChildAreaDirty(Element child, bool grandchild) { base.OnChildAreaDirty(child, grandchild); - // we only need to scroll when a grandchild changes, since all of our children are forced + // we only need to scroll when a grandchild changes, since all of our children are forced // to be auto-anchored and so will automatically propagate their changes up to us if (grandchild) this.ScrollChildren(); diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index d83f808..738af60 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -27,14 +27,14 @@ using MonoGame.Extended; using MonoGame.Extended.Tiled; using MonoGame.Extended.ViewportAdapters; -namespace Sandbox; +namespace Sandbox; public class GameImpl : MlemGame { private Camera camera; - private TiledMap map; + /*private TiledMap map; private IndividualTiledMapRenderer mapRenderer; - private TiledMapCollisions collisions; + private TiledMapCollisions collisions;*/ private RawContentManager rawContent; private TokenizedString tokenized; @@ -50,14 +50,14 @@ public class GameImpl : MlemGame { this.Components.Add(this.rawContent = new RawContentManager(this.Services)); - this.map = MlemGame.LoadContent("Tiled/Map"); + /*this.map = MlemGame.LoadContent("Tiled/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) { AutoScaleWithScreen = true, Scale = 2, - LookingPosition = new Vector2(25, 25) * this.map.GetTileSize(), + /*LookingPosition = new Vector2(25, 25) * this.map.GetTileSize(),*/ MinScale = 0.25F, MaxScale = 4 }; @@ -296,7 +296,7 @@ public class GameImpl : MlemGame { 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)); newPanel.AddChild(new Button(Anchor.TopLeft, new Vector2(100, 20), "Text", "Tooltip text")); this.UiSystem.Add("Panel", newPanel); @@ -341,7 +341,19 @@ public class GameImpl : MlemGame { } } 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().Count()); + Console.WriteLine(e.Anchor); + } + } + }); + this.UiSystem.Add("WidthTest", widthPanel); } protected override void DoUpdate(GameTime gameTime) { @@ -356,12 +368,12 @@ public class GameImpl : MlemGame { /*if (Input.InputsDown.Length > 0) 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)); MlemGame.Input.HandleKeyboardRepeats = false; Console.WriteLine("Down time: " + MlemGame.Input.GetDownTime(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) { @@ -396,4 +408,4 @@ public class GameImpl : MlemGame { } -} \ No newline at end of file +}