diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs index f9ef3e5..9903270 100644 --- a/Demos/UiDemo.cs +++ b/Demos/UiDemo.cs @@ -185,6 +185,13 @@ namespace Demos { var bar4 = root.AddChild(new ProgressBar(Anchor.AutoInline, new Vector2(8, 30), Direction2.Up, 10) {PositionOffset = new Vector2(1, 1)}); CoroutineHandler.Start(this.WobbleProgressBar(bar4)); + root.AddChild(new VerticalSpace(3)); + var dropdown = root.AddChild(new Dropdown(Anchor.AutoLeft, new Vector2(1, 10), "Dropdown Menu")); + dropdown.AddElement("First Option"); + dropdown.AddElement("Second Option"); + dropdown.AddElement("Third Option"); + dropdown.AddElement(new Button(Anchor.AutoLeft, new Vector2(1, 10), "Button Option")); + root.AddChild(new VerticalSpace(3)); root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "There are also some additional \"components\" which are created as combinations of other components. You can find all of them in the ElementHelper class. Here are some examples:")); root.AddChild(ElementHelper.NumberField(Anchor.AutoLeft, new Vector2(1, 10))).PositionOffset = new Vector2(0, 1); @@ -205,14 +212,14 @@ namespace Demos { // Below are some querying examples that help you find certain elements easily var children = root.GetChildren(); - var totalChildren = root.GetChildren(regardChildrensChildren: true); + var totalChildren = root.GetChildren(regardGrandchildren: true); Console.WriteLine($"The root has {children.Count()} children, but there are {totalChildren.Count()} when regarding children's children"); var textFields = root.GetChildren(); Console.WriteLine($"The root has {textFields.Count()} text fields"); var paragraphs = root.GetChildren(); - var totalParagraphs = root.GetChildren(regardChildrensChildren: true); + var totalParagraphs = root.GetChildren(regardGrandchildren: true); Console.WriteLine($"The root has {paragraphs.Count()} paragraphs, but there are {totalParagraphs.Count()} when regarding children's children"); var autoWidthChildren = root.GetChildren(e => e.Size.X == 1); diff --git a/MLEM.Ui/Elements/Dropdown.cs b/MLEM.Ui/Elements/Dropdown.cs new file mode 100644 index 0000000..e7c73e4 --- /dev/null +++ b/MLEM.Ui/Elements/Dropdown.cs @@ -0,0 +1,43 @@ +using Microsoft.Xna.Framework; + +namespace MLEM.Ui.Elements { + public class Dropdown : Button { + + public readonly Panel Panel; + public bool IsOpen { + get => !this.Panel.IsHidden; + set => this.Panel.IsHidden = !value; + } + + public Dropdown(Anchor anchor, Vector2 size, string text = null, string tooltipText = null, float tooltipWidth = 50) : base(anchor, size, text, tooltipText, tooltipWidth) { + this.Panel = this.AddChild(new Panel(Anchor.TopCenter, size, Vector2.Zero, true) { + IsHidden = true + }); + this.OnAreaUpdated += e => this.Panel.PositionOffset = new Vector2(0, e.Area.Height / this.Scale); + this.Priority = 10000; + this.OnPressed += e => this.IsOpen = !this.IsOpen; + } + + public void AddElement(Element element) { + this.Panel.AddChild(element); + } + + public void AddElement(string text, GenericCallback pressed = null) { + this.AddElement(p => text, pressed); + } + + public void AddElement(Paragraph.TextCallback text, GenericCallback pressed = null) { + var paragraph = new Paragraph(Anchor.AutoLeft, 1, text) { + CanBeMoused = true, + CanBeSelected = true, + PositionOffset = new Vector2(0, 1) + }; + if (pressed != null) + paragraph.OnPressed += pressed; + paragraph.OnMouseEnter += e => paragraph.TextColor = Color.LightGray; + paragraph.OnMouseExit += e => paragraph.TextColor = Color.White; + this.AddElement(paragraph); + } + + } +} \ No newline at end of file diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index 1870185..8c1c901 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -268,9 +268,9 @@ namespace MLEM.Ui.Elements { if (this.Anchor >= Anchor.AutoLeft) { Element previousChild; if (this.Anchor == Anchor.AutoInline || this.Anchor == Anchor.AutoInlineIgnoreOverflow) { - previousChild = this.GetOlderSibling(false, false); + previousChild = this.GetOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach); } else { - previousChild = this.GetLowestOlderSibling(false, false); + previousChild = this.GetLowestOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach); } if (previousChild != null) { var prevArea = previousChild.GetAreaForAutoAnchors(); @@ -304,12 +304,11 @@ namespace MLEM.Ui.Elements { child.ForceUpdateArea(); if (this.SetHeightBasedOnChildren && this.Children.Count > 0) { - var lowest = this.GetLowestChild(false, true); + var lowest = this.GetLowestChild(e => !e.IsHidden); var newHeight = (lowest.UnscrolledArea.Bottom - pos.Y + this.ScaledChildPadding.Y) / this.Scale; if (newHeight != this.size.Y) { this.size.Y = newHeight; - if (this.Anchor > Anchor.TopRight) - this.ForceUpdateArea(); + this.ForceUpdateArea(); } } } @@ -324,12 +323,12 @@ namespace MLEM.Ui.Elements { return this.UnscrolledArea; } - public Element GetLowestChild(bool hiddenAlso, bool unattachableAlso) { + public Element GetLowestChild(Func condition = null) { Element lowest = null; // the lowest child is expected to be towards the back, so search is usually faster if done backwards for (var i = this.Children.Count - 1; i >= 0; i--) { var child = this.Children[i]; - if (!hiddenAlso && child.IsHidden || !unattachableAlso && !child.CanAutoAnchorsAttach) + if (condition != null && !condition(child)) continue; if (child.Anchor > Anchor.TopRight && child.Anchor < Anchor.AutoLeft) continue; @@ -339,14 +338,14 @@ namespace MLEM.Ui.Elements { return lowest; } - public Element GetLowestOlderSibling(bool hiddenAlso, bool unattachableAlso) { + public Element GetLowestOlderSibling(Func condition = null) { if (this.Parent == null) return null; Element lowest = null; foreach (var child in this.Parent.Children) { if (child == this) break; - if (!hiddenAlso && child.IsHidden || !unattachableAlso && !child.CanAutoAnchorsAttach) + if (condition != null && !condition(child)) continue; if (lowest == null || child.UnscrolledArea.Bottom >= lowest.UnscrolledArea.Bottom) lowest = child; @@ -354,50 +353,52 @@ namespace MLEM.Ui.Elements { return lowest; } - public Element GetOlderSibling(bool hiddenAlso, bool unattachableAlso) { + public Element GetOlderSibling(Func condition = null) { if (this.Parent == null) return null; Element older = null; foreach (var child in this.Parent.Children) { if (child == this) break; - if (!hiddenAlso && child.IsHidden || !unattachableAlso && !child.CanAutoAnchorsAttach) + if (condition != null && !condition(child)) continue; older = child; } return older; } - public IEnumerable GetSiblings(bool hiddenAlso) { + public IEnumerable GetSiblings(Func condition = null) { if (this.Parent == null) yield break; foreach (var child in this.Parent.Children) { - if (!hiddenAlso && child.IsHidden) + if (condition != null && !condition(child)) continue; if (child != this) yield return child; } } - public IEnumerable GetChildren(Func condition = null, bool regardChildrensChildren = false) { + public IEnumerable GetChildren(Func condition = null, bool regardGrandchildren = false, bool ignoreFalseGrandchildren = false) { foreach (var child in this.Children) { - if (regardChildrensChildren) { - foreach (var cc in child.GetChildren(condition, true)) + var applies = condition == null || condition(child); + if (applies) + yield return child; + if (regardGrandchildren && (!ignoreFalseGrandchildren || applies)) { + foreach (var cc in child.GetChildren(condition, true, ignoreFalseGrandchildren)) yield return cc; } - if (condition == null || condition(child)) - yield return child; } } - public IEnumerable GetChildren(Func condition = null, bool regardChildrensChildren = false) where T : Element { + public IEnumerable GetChildren(Func condition = null, bool regardGrandchildren = false, bool ignoreFalseGrandchildren = false) where T : Element { foreach (var child in this.Children) { - if (regardChildrensChildren) { - foreach (var cc in child.GetChildren(condition, true)) + var applies = child is T t && (condition == null || condition(t)); + if (applies) + yield return (T) child; + if (regardGrandchildren && (!ignoreFalseGrandchildren || applies)) { + foreach (var cc in child.GetChildren(condition, true, ignoreFalseGrandchildren)) yield return cc; } - if (child is T t && (condition == null || condition(t))) - yield return t; } } diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs index 8279e15..c874d7f 100644 --- a/MLEM.Ui/Elements/Panel.cs +++ b/MLEM.Ui/Elements/Panel.cs @@ -69,7 +69,7 @@ namespace MLEM.Ui.Elements { return; // the "real" first child is the scroll bar, which we want to ignore var firstChild = this.Children[1]; - var lowestChild = this.GetLowestChild(false, true); + var lowestChild = this.GetLowestChild(e => !e.IsHidden); // the max value of the scrollbar is the amount of non-scaled pixels taken up by overflowing components var childrenHeight = lowestChild.Area.Bottom - firstChild.Area.Top; this.ScrollBar.MaxValue = ((childrenHeight - this.Area.Height) / this.Scale + this.ChildPadding.Y * 2).Ceil(); diff --git a/MLEM.Ui/Elements/RadioButton.cs b/MLEM.Ui/Elements/RadioButton.cs index cbb443b..42229c6 100644 --- a/MLEM.Ui/Elements/RadioButton.cs +++ b/MLEM.Ui/Elements/RadioButton.cs @@ -14,7 +14,7 @@ namespace MLEM.Ui.Elements { // don't += because we want to override the checking + unchecking behavior of Checkbox this.OnPressed = element => { this.Checked = true; - foreach (var sib in this.GetSiblings(true)) { + foreach (var sib in this.GetSiblings()) { if (sib is RadioButton radio && radio.Group == this.Group) radio.Checked = false; } diff --git a/MLEM.Ui/UiControls.cs b/MLEM.Ui/UiControls.cs index 1fae991..f06d423 100644 --- a/MLEM.Ui/UiControls.cs +++ b/MLEM.Ui/UiControls.cs @@ -137,7 +137,7 @@ namespace MLEM.Ui { protected virtual Element GetTabNextElement(bool backward) { if (this.ActiveRoot == null) return null; - var children = this.ActiveRoot.Element.GetChildren(regardChildrensChildren: true).Append(this.ActiveRoot.Element); + var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element); if (this.SelectedElement?.Root != this.ActiveRoot) { return backward ? children.LastOrDefault(c => c.CanBeSelected) : children.FirstOrDefault(c => c.CanBeSelected); } else { @@ -165,7 +165,7 @@ namespace MLEM.Ui { protected virtual Element GetGamepadNextElement(Rectangle searchArea) { if (this.ActiveRoot == null) return null; - var children = this.ActiveRoot.Element.GetChildren(regardChildrensChildren: true).Append(this.ActiveRoot.Element); + var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element); if (this.SelectedElement?.Root != this.ActiveRoot) { return children.FirstOrDefault(c => c.CanBeSelected); } else {