Skip to content

Commit 186d3c6

Browse files
committed
Fix missing dirty rectangles for animations
1 parent 4c4ad06 commit 186d3c6

File tree

2 files changed

+79
-22
lines changed

2 files changed

+79
-22
lines changed

src/XenoAtom.Terminal.UI.Tests/SpinnerTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,44 @@ public void Spinner_Bound_IsActive_Starts_Animating_When_Switched_To_True()
5656
StringAssert.Contains(after, "b");
5757
}
5858

59+
[TestMethod]
60+
public void Spinner_Animation_Redraws_When_Same_Frame_Uses_Partial_Repaint()
61+
{
62+
var marker = new Spinner("Marker")
63+
.IsActive(false);
64+
marker.Style(new SpinnerStyle("Test", TimeSpan.FromMilliseconds(10), "x", "y")
65+
{
66+
TextStyle = TextStyle.None,
67+
});
68+
var spinner = new Spinner("Loading");
69+
spinner.Style(new SpinnerStyle("Test", TimeSpan.FromMilliseconds(10), "a", "b")
70+
{
71+
TextStyle = TextStyle.None,
72+
});
73+
74+
var root = new VStack(
75+
marker,
76+
new HStack(
77+
"Depth:",
78+
new VStack(
79+
new HStack(
80+
"Node",
81+
spinner).Spacing(1))).Spacing(1));
82+
83+
using var driver = new TerminalAppTestDriver(root, TerminalHostKind.Fullscreen, new TerminalSize(30, 5));
84+
driver.Tick();
85+
86+
marker.Tone = ControlTone.Success;
87+
driver.Tick();
88+
89+
var screen = new AnsiTestScreen(30, 5);
90+
screen.Apply(driver.Backend.GetOutText());
91+
var rendered = screen.GetText();
92+
93+
StringAssert.Contains(rendered, "x Marker");
94+
StringAssert.Contains(rendered, "b Loading");
95+
}
96+
5997
[TestMethod]
6098
public void SpinnerStyle_Computes_MaxWidth_For_Different_FrameWidths()
6199
{
@@ -75,4 +113,5 @@ public void SpinnerStyle_Uses_MaxFrameWidth_For_WideRuneProfiles()
75113
Assert.AreEqual(2, style.GetFrameWidth(TerminalWideRuneResolvers.NerdFontDoubleWidth));
76114
Assert.AreEqual(1, style.GetFrameWidth(TerminalWideRuneResolvers.NerdFontMono));
77115
}
116+
78117
}

src/XenoAtom.Terminal.UI/TerminalApp.cs

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,19 @@ private void AdvanceAnimations(long? timestamp = null)
12971297
var visual = _animatedVisuals[i];
12981298
if (now >= visual.NextAnimationTick)
12991299
{
1300-
changed |= visual.AdvanceAnimation(now);
1300+
var visualChanged = visual.AdvanceAnimation(now);
1301+
changed |= visualChanged;
1302+
if (visualChanged)
1303+
{
1304+
if (visual is Visual renderVisual)
1305+
{
1306+
AddRenderDirtyRect(renderVisual);
1307+
}
1308+
else
1309+
{
1310+
_pendingRenderDirtyRectValid = false;
1311+
}
1312+
}
13011313
}
13021314

13031315
next = Math.Min(next, visual.NextAnimationTick);
@@ -1413,27 +1425,7 @@ private void ProcessBindingWrites()
14131425
{
14141426
foreach (var v in renderVisuals)
14151427
{
1416-
var bounds = v.Bounds;
1417-
if (bounds.Width <= 0 || bounds.Height <= 0)
1418-
{
1419-
continue;
1420-
}
1421-
1422-
// Expand by 1 cell horizontally to reduce artifacts with wide glyphs clipped at region boundaries.
1423-
var x = Math.Max(0, bounds.X - 1);
1424-
var right = Math.Min(LayoutConstants.MaxFinite, bounds.Right + 1);
1425-
var expanded = new Rectangle(x, bounds.Y, Math.Max(0, right - x), bounds.Height);
1426-
metrics?.AddDirtyRect(expanded);
1427-
1428-
if (!_pendingRenderDirtyRectValid)
1429-
{
1430-
_pendingRenderDirtyRect = expanded;
1431-
_pendingRenderDirtyRectValid = true;
1432-
}
1433-
else
1434-
{
1435-
_pendingRenderDirtyRect = Rectangle.Union(_pendingRenderDirtyRect, expanded);
1436-
}
1428+
AddRenderDirtyRect(v);
14371429
}
14381430
}
14391431
}
@@ -1442,6 +1434,32 @@ private void ProcessBindingWrites()
14421434
_renderRequested = true;
14431435
}
14441436

1437+
private void AddRenderDirtyRect(Visual visual)
1438+
{
1439+
var bounds = visual.Bounds;
1440+
if (bounds.Width <= 0 || bounds.Height <= 0)
1441+
{
1442+
return;
1443+
}
1444+
1445+
// Expand by 1 cell horizontally to reduce artifacts with wide glyphs clipped at region boundaries.
1446+
var x = Math.Max(0, bounds.X - 1);
1447+
var right = Math.Min(LayoutConstants.MaxFinite, bounds.Right + 1);
1448+
var expanded = new Rectangle(x, bounds.Y, Math.Max(0, right - x), bounds.Height);
1449+
1450+
_debugOverlayMetrics?.AddDirtyRect(expanded);
1451+
1452+
if (!_pendingRenderDirtyRectValid)
1453+
{
1454+
_pendingRenderDirtyRect = expanded;
1455+
_pendingRenderDirtyRectValid = true;
1456+
}
1457+
else
1458+
{
1459+
_pendingRenderDirtyRect = Rectangle.Union(_pendingRenderDirtyRect, expanded);
1460+
}
1461+
}
1462+
14451463
private void Render()
14461464
{
14471465
EnsureFocusInScope();

0 commit comments

Comments
 (0)