diff --git a/CHANGELOG.md b/CHANGELOG.md
index fea90c7..7316032 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,10 @@ Additions
Fixes
- Fixed TextInput not working correctly when using surrogate pairs
+### MLEM.Ui
+Improvements
+- Allow scrolling panels to contain other scrolling panels
+
## 6.2.0
### MLEM
diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs
index a3381c0..934d010 100644
--- a/Demos/UiDemo.cs
+++ b/Demos/UiDemo.cs
@@ -223,6 +223,15 @@ namespace Demos {
PositionOffset = new Vector2(0, 1)
});
+ var subPanel = this.root.AddChild(new Panel(Anchor.AutoLeft, new Vector2(1, 25), Vector2.Zero, false, true) {
+ PositionOffset = new Vector2(0, 1),
+ Texture = null,
+ ChildPadding = Padding.Empty
+ });
+ subPanel.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a nested scrolling panel!"));
+ for (var i = 1; i <= 5; i++)
+ subPanel.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 10), $"Button {i}") {PositionOffset = new Vector2(0, 1)});
+
const string alignText = "Paragraphs can have left aligned text, right aligned text and center aligned text.";
this.root.AddChild(new VerticalSpace(3));
var alignPar = this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, alignText));
diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs
index c40a565..526426d 100644
--- a/MLEM.Ui/Elements/Panel.cs
+++ b/MLEM.Ui/Elements/Panel.cs
@@ -61,6 +61,7 @@ namespace MLEM.Ui.Elements {
private bool relevantChildrenDirty;
private float scrollBarChildOffset;
private StyleProp scrollBarOffset;
+ private float lastScrollOffset;
///
/// Creates a new panel with the given settings.
@@ -70,7 +71,7 @@ namespace MLEM.Ui.Elements {
/// The panel's offset from its anchor point
/// Whether the panel should automatically calculate its height based on its children's size
/// Whether this panel should automatically add a scroll bar to scroll towards elements that are beyond the area this panel covers
- /// Whether the scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling
+ /// Whether the scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if is .
public Panel(Anchor anchor, Vector2 size, Vector2 positionOffset, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, bool autoHideScrollbar = true) : base(anchor, size) {
this.PositionOffset = positionOffset;
this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
@@ -278,9 +279,9 @@ namespace MLEM.Ui.Elements {
// update child padding based on whether the scroll bar is visible
var childOffset = this.ScrollBar.IsHidden ? 0 : this.ScrollerSize.Value.X + this.ScrollBarOffset;
if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) {
+ // this implicitly sets our area dirty!
this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0);
this.scrollBarChildOffset = childOffset;
- this.SetAreaDirty();
}
// the scroller height has the same relation to the scroll bar height as the visible area has to the total height of the panel's content
@@ -288,15 +289,15 @@ namespace MLEM.Ui.Elements {
this.ScrollBar.ScrollerSize = new Vector2(this.ScrollerSize.Value.X, Math.Max(this.ScrollerSize.Value.Y, scrollerHeight));
// update the render target
- var targetArea = (Rectangle) this.GetRenderTargetArea();
- if (targetArea.Width <= 0 || targetArea.Height <= 0) {
+ var (_, _, width, height) = (Rectangle) this.GetRenderTargetArea();
+ if (width <= 0 || height <= 0) {
this.renderTarget?.Dispose();
this.renderTarget = null;
return;
}
- if (this.renderTarget == null || targetArea.Width != this.renderTarget.Width || targetArea.Height != this.renderTarget.Height) {
+ if (this.renderTarget == null || width != this.renderTarget.Width || height != this.renderTarget.Height) {
this.renderTarget?.Dispose();
- this.renderTarget = targetArea.IsEmpty ? null : new RenderTarget2D(this.System.Game.GraphicsDevice, targetArea.Width, targetArea.Height, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
+ this.renderTarget = new RenderTarget2D(this.System.Game.GraphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
this.relevantChildrenDirty = true;
}
}
@@ -328,7 +329,7 @@ namespace MLEM.Ui.Elements {
}
private RectangleF GetRenderTargetArea() {
- var area = this.ChildPaddedArea;
+ var area = this.ChildPaddedArea.OffsetCopy(this.ScaledScrollOffset);
area.X = this.DisplayArea.X;
area.Width = this.DisplayArea.Width;
return area;
@@ -339,7 +340,8 @@ namespace MLEM.Ui.Elements {
return;
// we ignore false grandchildren so that the children of the scroll bar stay in place
foreach (var child in this.GetChildren(c => c != this.ScrollBar, true, true))
- child.ScrollOffset.Y = -this.ScrollBar.CurrentValue;
+ child.ScrollOffset.Y += (this.lastScrollOffset - this.ScrollBar.CurrentValue);
+ this.lastScrollOffset = this.ScrollBar.CurrentValue;
this.relevantChildrenDirty = true;
}
diff --git a/MLEM.Ui/Elements/ScrollBar.cs b/MLEM.Ui/Elements/ScrollBar.cs
index 6fce5bc..0b2e2d7 100644
--- a/MLEM.Ui/Elements/ScrollBar.cs
+++ b/MLEM.Ui/Elements/ScrollBar.cs
@@ -158,7 +158,7 @@ namespace MLEM.Ui.Elements {
if (this.isMouseScrolling)
this.ScrollToPos(this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()));
if (!this.Horizontal) {
- if (moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) {
+ if (this.IsMousedForScrolling(moused)) {
var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel;
if (scroll != 0)
this.CurrentValue += this.StepPerScroll * Math.Sign(scroll);
@@ -244,6 +244,23 @@ namespace MLEM.Ui.Elements {
this.SmoothScrollFactor = this.SmoothScrollFactor.OrStyle(style.ScrollBarSmoothScrollFactor);
}
+ private bool IsMousedForScrolling(Element moused) {
+ if (moused == null || (moused != this.Parent && !moused.GetParentTree().Contains(this.Parent)))
+ return false;
+ // if we're moused, check if there are any scroll bars deeper than us that should take precedence
+ var foundMe = false;
+ foreach (var child in this.Parent.GetChildren(regardGrandchildren: true)) {
+ if (foundMe) {
+ if (child is ScrollBar b && !b.Horizontal && b.IsMousedForScrolling(moused))
+ return false;
+ } else if (child == this) {
+ // once we found ourselves, all subsequent children are deeper/older!
+ foundMe = true;
+ }
+ }
+ return true;
+ }
+
///
/// A delegate method used for
///