Skip to content

Commit 0357f12

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 8ca8395 + 67cf55b commit 0357f12

File tree

4 files changed

+69
-28
lines changed

4 files changed

+69
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ MLEM uses [semantic versioning](https://semver.org/). Potentially breaking chang
77
Additions
88
- Added UiSystem.OnElementAreaDirty and Element.OnAreaDirty events
99
- Added Element.DebugName to allow describing ui elements in ToString
10+
- Added ILayoutItem.OnLayoutRecursionSettled which is called when recursive ui layouting has finished
1011

1112
Improvements
1213
- Made the exception thrown after the element recursion limit is reached more helpful

MLEM.Ui/Elements/Element.cs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,32 @@ public Vector2 TransformInverseAll(Vector2 position) {
11041104
return this.TransformInverse(position);
11051105
}
11061106

1107+
/// <summary>
1108+
/// A method called by <see cref="UiLayouter.Layout{T}"/> when a layout item's size is being recalculated based on its children.
1109+
/// </summary>
1110+
/// <param name="recursion">The current recursion depth.</param>
1111+
/// <param name="relevantChild">The child that triggered the layout recursion. May be <see langword="null"/> in case the source of the layout recursion is unknown.</param>
1112+
protected virtual void OnLayoutRecursion(int recursion, ILayoutItem relevantChild) {
1113+
this.System.Metrics.SummedRecursionDepth++;
1114+
if (recursion > this.System.Metrics.MaxRecursionDepth)
1115+
this.System.Metrics.MaxRecursionDepth = recursion;
1116+
1117+
if (recursion >= Element.RecursionLimit) {
1118+
var exceptionText = $"The area of {this} has recursively updated more often than the configured Element.RecursionLimit. This issue may occur due to this element or one of its children containing conflicting auto-sizing settings, a custom element setting its area dirty too frequently, or this element being part of a complex layout tree that should be split up into multiple groups.";
1119+
if (relevantChild != null)
1120+
exceptionText += $" Does its child {relevantChild} contain any conflicting auto-sizing settings?";
1121+
throw new ArithmeticException(exceptionText);
1122+
}
1123+
}
1124+
1125+
/// <summary>
1126+
/// A method called by <see cref="UiLayouter.Layout{T}"/> when a layout item's size is being calculated, but recursive calculations have settled.
1127+
/// Also see <see cref="OnLayoutRecursion"/>, which is called for every recursive operation during element layouting.
1128+
/// </summary>
1129+
/// <param name="totalRecursion">The total reached recursion depth.</param>
1130+
/// <param name="elementInternal"><see langword="true"/> if the settled recursive operation was element-internal (ie related to properties like <see cref="SetWidthBasedOnChildren"/> and <see cref="SetHeightBasedOnChildren"/>); <see langword="false"/> if the settled recursive operation was related to recursively updated children or parents of this element.</param>
1131+
protected virtual void OnLayoutRecursionSettled(int totalRecursion, bool elementInternal) {}
1132+
11071133
/// <summary>
11081134
/// Called when this element is added to a <see cref="UiSystem"/> and, optionally, a given <see cref="RootElement"/>.
11091135
/// This method is called in <see cref="AddChild{T}"/> for a parent whose <see cref="System"/> is set, as well as <see cref="UiSystem.Add"/>.
@@ -1131,16 +1157,11 @@ protected internal virtual void RemovedFromUi() {
11311157
}
11321158

11331159
void ILayoutItem.OnLayoutRecursion(int recursion, ILayoutItem relevantChild) {
1134-
this.System.Metrics.SummedRecursionDepth++;
1135-
if (recursion > this.System.Metrics.MaxRecursionDepth)
1136-
this.System.Metrics.MaxRecursionDepth = recursion;
1160+
this.OnLayoutRecursion(recursion, relevantChild);
1161+
}
11371162

1138-
if (recursion >= Element.RecursionLimit) {
1139-
var exceptionText = $"The area of {this} has recursively updated more often than the configured Element.RecursionLimit. This issue may occur due to this element or one of its children containing conflicting auto-sizing settings, a custom element setting its area dirty too frequently, or this element being part of a complex layout tree that should be split up into multiple groups.";
1140-
if (relevantChild != null)
1141-
exceptionText += $" Does its child {relevantChild} contain any conflicting auto-sizing settings?";
1142-
throw new ArithmeticException(exceptionText);
1143-
}
1163+
void ILayoutItem.OnLayoutRecursionSettled(int totalRecursion, bool elementInternal) {
1164+
this.OnLayoutRecursionSettled(totalRecursion, elementInternal);
11441165
}
11451166

11461167
Vector2 ILayoutItem.CalcActualSize(RectangleF parentArea) {

MLEM.Ui/Elements/Panel.cs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public StyleProp<float> ScrollBarOffset {
5757

5858
private readonly List<Element> relevantChildren = new List<Element>();
5959
private readonly HashSet<Element> scrolledChildren = new HashSet<Element>();
60-
private readonly float[] scrollBarMaxHistory;
6160
private readonly bool scrollOverflow;
6261

6362
private RenderTarget2D renderTarget;
@@ -67,6 +66,7 @@ public StyleProp<float> ScrollBarOffset {
6766
private float lastScrollOffset;
6867
private bool childrenDirtyForScroll;
6968
private bool scrollBarMaxHistoryDirty;
69+
private float[] scrollBarMaxHistory;
7070

7171
/// <summary>
7272
/// Creates a new panel with the given settings.
@@ -85,10 +85,6 @@ public Panel(Anchor anchor, Vector2 size, Vector2 positionOffset, bool setHeight
8585
base.CanBeSelected = false;
8686

8787
if (scrollOverflow) {
88-
this.scrollBarMaxHistory = new float[3];
89-
this.scrollBarMaxHistoryDirty = true;
90-
this.ResetScrollBarMaxHistory();
91-
9288
this.ScrollBar = new ScrollBar(Anchor.TopRight, Vector2.Zero, 0, 0) {
9389
OnValueChanged = (element, value) => this.ScrollChildren(),
9490
CanAutoAnchorsAttach = false,
@@ -337,17 +333,29 @@ protected virtual void ScrollSetup() {
337333
childrenHeight = 0;
338334
}
339335

336+
// height doesn't change when modifying ChildPadding below, so avoid a needless update later
337+
var areaHeight = this.ChildPaddedArea.Height;
340338
// the max value of the scroll bar is the amount of non-scaled pixels taken up by overflowing components
341-
var scrollBarMax = Math.Max(0, (childrenHeight - this.ChildPaddedArea.Height) / this.Scale);
339+
var scrollBarMax = Math.Max(0, (childrenHeight - areaHeight) / this.Scale);
340+
342341
// avoid an infinite show/hide oscillation that occurs while updating our area by simply using the maximum recent height in that case
343-
if (this.scrollBarMaxHistory[0].Equals(this.scrollBarMaxHistory[2], Element.Epsilon) && this.scrollBarMaxHistory[1].Equals(scrollBarMax, Element.Epsilon))
344-
scrollBarMax = Math.Max(scrollBarMax, this.scrollBarMaxHistory.Max());
345-
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) {
346-
this.scrollBarMaxHistory[0] = this.scrollBarMaxHistory[1];
347-
this.scrollBarMaxHistory[1] = this.scrollBarMaxHistory[2];
348-
this.scrollBarMaxHistory[2] = scrollBarMax;
349-
this.scrollBarMaxHistoryDirty = true;
342+
if (this.ScrollBar.AutoHideWhenEmpty && this.ScrollBar.MaxValue > Element.Epsilon != scrollBarMax > Element.Epsilon) {
343+
if (this.scrollBarMaxHistory == null) {
344+
this.scrollBarMaxHistory = new float[3];
345+
this.scrollBarMaxHistoryDirty = true;
346+
this.ResetScrollBarMaxHistory();
347+
}
348+
if (this.scrollBarMaxHistory[0].Equals(this.scrollBarMaxHistory[2], Element.Epsilon) && this.scrollBarMaxHistory[1].Equals(scrollBarMax, Element.Epsilon))
349+
scrollBarMax = Math.Max(scrollBarMax, this.scrollBarMaxHistory.Max());
350+
if (!scrollBarMax.Equals(this.scrollBarMaxHistory[2], Element.Epsilon)) {
351+
this.scrollBarMaxHistory[0] = this.scrollBarMaxHistory[1];
352+
this.scrollBarMaxHistory[1] = this.scrollBarMaxHistory[2];
353+
this.scrollBarMaxHistory[2] = scrollBarMax;
354+
this.scrollBarMaxHistoryDirty = true;
355+
}
356+
}
350357

358+
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) {
351359
this.ScrollBar.MaxValue = scrollBarMax;
352360
this.relevantChildrenDirty = true;
353361
}
@@ -361,7 +369,7 @@ protected virtual void ScrollSetup() {
361369
}
362370

363371
// 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
364-
var scrollerHeight = Math.Min(this.ChildPaddedArea.Height / childrenHeight / this.Scale, 1) * this.ScrollBar.Area.Height;
372+
var scrollerHeight = Math.Min(areaHeight / childrenHeight / this.Scale, 1) * this.ScrollBar.Area.Height;
365373
this.ScrollBar.ScrollerSize = new Vector2(this.ScrollerSize.Value.X, Math.Max(this.ScrollerSize.Value.Y, scrollerHeight));
366374

367375
// update the render target

MLEM.Ui/UiLayouter.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ public static void Layout<T>(T item, ref int layoutRecursionTracker, float epsil
2626

2727
var internalRecursion = 0;
2828
UpdateDisplayArea();
29+
item.OnLayoutRecursionSettled(internalRecursion, true);
2930

30-
if (initiatedLayouting)
31+
if (initiatedLayouting) {
32+
item.OnLayoutRecursionSettled(layoutRecursionTracker, false);
3133
layoutRecursionTracker = 0;
34+
}
3235

3336
void UpdateDisplayArea(Vector2? overrideSize = null) {
3437
var parentArea = item.ParentArea;
@@ -376,11 +379,10 @@ public interface ILayoutItem {
376379
bool CanAutoAnchorsAttach { get; }
377380

378381
/// <summary>
379-
/// Calculates the actual size that this layout item should take up, based on the area that its parent encompasses.
380-
/// By default, this is based on the information specified in <see cref="System.Drawing.Size"/>'s documentation.
382+
/// Calculates the actual total size that this element should take up.
381383
/// </summary>
382-
/// <param name="parentArea">This parent's area, or the ui system's viewport if it has no parent</param>
383-
/// <returns>The actual size of this layout item, any scaling into account</returns>
384+
/// <param name="parentArea">This parent's area, or the ui system's viewport if it has no parent.</param>
385+
/// <returns>The actual size of this element.</returns>
384386
Vector2 CalcActualSize(RectangleF parentArea);
385387

386388
/// <summary>
@@ -401,10 +403,19 @@ public interface ILayoutItem {
401403

402404
/// <summary>
403405
/// A method called by <see cref="UiLayouter.Layout{T}"/> when a layout item's size is being recalculated based on its children.
406+
/// Also see <see cref="OnLayoutRecursionSettled"/>, which is called after recursive operations during element layouting have completed.
404407
/// </summary>
405408
/// <param name="recursion">The current recursion depth.</param>
406409
/// <param name="relevantChild">The child that triggered the layout recursion. May be <see langword="null"/> in case the source of the layout recursion is unknown.</param>
407410
void OnLayoutRecursion(int recursion, ILayoutItem relevantChild);
408411

412+
/// <summary>
413+
/// A method called by <see cref="UiLayouter.Layout{T}"/> when a layout item's size is being calculated, but recursive calculations have settled.
414+
/// Also see <see cref="OnLayoutRecursion"/>, which is called for every recursive operation during element layouting.
415+
/// </summary>
416+
/// <param name="totalRecursion">The total reached recursion depth.</param>
417+
/// <param name="elementInternal"><see langword="true"/> if the settled recursive operation was element-internal (ie related to properties like <see cref="SetWidthBasedOnChildren"/> and <see cref="SetHeightBasedOnChildren"/>); <see langword="false"/> if the settled recursive operation was related to recursively updated children or parents of this element.</param>
418+
void OnLayoutRecursionSettled(int totalRecursion, bool elementInternal);
419+
409420
}
410421
}

0 commit comments

Comments
 (0)