Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions TUI.Test/UIContainerBaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,54 @@ public void AMovedChildNoLongerInvalidatesItsPreviousContainer()

Assert.AreEqual(0, firstInvalidations);
}

/// <summary>
/// A change deep in the tree reaches the root once, not once per route at every level
/// (ktsu-dev/TUI#148)
/// </summary>
[TestMethod]
public void AChangeNestedManyLevelsDeepInvalidatesTheRootOnce()
{
StackPanel root = [];
StackPanel current = root;
for (int i = 0; i < 15; i++)
{
StackPanel next = [];
current.AddChild(next);
current = next;
}

TextElement leaf = new() { Text = "x" };
current.AddChild(leaf);

int count = 0;
root.Invalidated += (_, _) => count++;

leaf.Text = "y";

Assert.AreEqual(1, count);
}

/// <summary>
/// Each ancestor between the change and the root is invalidated exactly once
/// </summary>
[TestMethod]
public void AChangeInvalidatesEachAncestorOnce()
{
StackPanel root = [];
StackPanel middle = [];
TextElement leaf = new() { Text = "x" };
root.AddChild(middle);
middle.AddChild(leaf);

int rootCount = 0;
int middleCount = 0;
root.Invalidated += (_, _) => rootCount++;
middle.Invalidated += (_, _) => middleCount++;

leaf.Text = "y";

Assert.AreEqual(1, middleCount);
Assert.AreEqual(1, rootCount);
}
}
4 changes: 2 additions & 2 deletions TUI/Contracts/IUIElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ public interface IUIElement
public Dimensions CalculateRequiredDimensions(int availableWidth) => CalculateRequiredDimensions();

/// <summary>
/// Marks the element as changed since its last draw, notifies its parent, and raises the
/// invalidated event
/// Marks the element as changed since its last draw and raises the invalidated event, which is
/// how the containing parent hears about the change
/// </summary>
/// <remarks>
/// Rendering redraws every visible element on every pass, so this does not decide whether an
Expand Down
6 changes: 5 additions & 1 deletion TUI/Elements/UIElementBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,14 @@ public virtual void Render(IConsoleProvider provider)
public virtual Dimensions CalculateRequiredDimensions(int availableWidth) => OnCalculateRequiredDimensions(availableWidth);

/// <inheritdoc />
/// <remarks>
/// The parent hears about this through <see cref="Invalidated"/>, which every container
/// subscribes to when it adopts a child. Calling the parent directly as well would reach it by
/// two routes, doubling the count at every level (ktsu-dev/TUI#148).
/// </remarks>
public void Invalidate()
{
IsDirty = true;
Parent?.Invalidate();
Invalidated?.Invoke(this, EventArgs.Empty);
}

Expand Down
Loading