Skip to content

Commit 7634c1e

Browse files
committed
Fix window close rendering bug with DesktopNeedsRender flag
Problem: Closing last window didn't clear from desktop/taskbar Root cause: Run() loop's shouldRender check returned false when no windows exist, preventing UpdateDisplay() from being called to render the clearing. Solution: - Add DesktopNeedsRender flag to RenderCoordinator - Set flag in WindowStateService.CloseWindow() after FillRect - Check flag in ConsoleWindowSystem.Run() shouldRender logic - Clear flag after rendering in RenderCoordinator.UpdateDisplay() This forces UpdateDisplay() to run even when AnyWindowDirty() returns false, ensuring desktop clearing is rendered when last window closes. Verified with ForceRenderTests (4 tests, all passing)
1 parent 39dac5c commit 7634c1e

3 files changed

Lines changed: 59 additions & 19 deletions

File tree

SharpConsoleUI/ConsoleWindowSystem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,8 @@ public int Run()
536536
);
537537
}
538538

539-
// Frame pacing: render if windows are dirty OR metrics need update
540-
bool shouldRender = AnyWindowDirty() || metricsNeedUpdate;
539+
// Frame pacing: render if windows are dirty OR metrics need update OR desktop needs render
540+
bool shouldRender = AnyWindowDirty() || metricsNeedUpdate || Render.DesktopNeedsRender;
541541

542542
// Calculate recommended sleep duration once (used in both branches)
543543
var recommendedSleep = _inputStateService.GetRecommendedSleepDuration(

SharpConsoleUI/Core/WindowStateService.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,23 +1007,27 @@ public bool CloseWindow(Window? window, bool activateParent = true, bool force =
10071007
// STEP 3: Complete the close (fire OnClosed, dispose controls)
10081008
window.CompleteClose();
10091009

1010-
// Redraw the screen
1010+
// Clear only the closed window's area (not entire screen!)
10111011
if (_renderer != null && _consoleDriver != null)
10121012
{
10131013
var theme = context.Theme;
1014-
_renderer.FillRect(0, 0, _consoleDriver.ScreenSize.Width, _consoleDriver.ScreenSize.Height,
1014+
// BUG FIX: Only clear the window's rectangle, not entire desktop
1015+
_renderer.FillRect(window.Left, window.Top, window.Width, window.Height,
10151016
theme.DesktopBackgroundChar, theme.DesktopBackgroundColor, theme.DesktopForegroundColor);
1017+
1018+
// Invalidate remaining windows in case they were partially occluded
10161019
foreach (var w in context.Windows.Values)
10171020
{
10181021
w.Invalidate(true);
10191022
}
10201023

1021-
// Force flush when no windows remain - the main loop won't render
1022-
// because AnyWindowDirty() returns false with an empty window collection
1023-
if (context.Windows.Count == 0)
1024-
{
1025-
_consoleDriver.Flush();
1026-
}
1024+
// BUG FIX: Removed immediate Flush() - let normal render cycle handle it
1025+
// The next UpdateDisplay() will detect the changes and output clearing
1026+
// This maintains frame-based rendering consistency
1027+
1028+
// CRITICAL: Force next render even if no windows remain
1029+
// Without this, Run() loop won't call UpdateDisplay() when AnyWindowDirty() returns false
1030+
context.Render.DesktopNeedsRender = true;
10271031
}
10281032

10291033
return true;

SharpConsoleUI/Rendering/RenderCoordinator.cs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using SharpConsoleUI.Performance;
1515
using SharpConsoleUI.Themes;
1616
using System.Drawing;
17+
using System.Linq;
1718

1819
namespace SharpConsoleUI.Rendering
1920
{
@@ -40,6 +41,21 @@ public class RenderCoordinator
4041
private readonly Dictionary<string, bool> _coverageCache = new Dictionary<string, bool>();
4142
private readonly List<Rectangle> _pendingDesktopClears = new List<Rectangle>();
4243

44+
// Desktop rendering flag - forces render even when no windows are dirty
45+
// Used when desktop background changes (e.g., after closing last window)
46+
private bool _desktopNeedsRender = false;
47+
48+
/// <summary>
49+
/// Gets or sets whether the desktop needs to render on the next frame.
50+
/// This forces UpdateDisplay() to run even when no windows are dirty.
51+
/// Automatically cleared after rendering.
52+
/// </summary>
53+
public bool DesktopNeedsRender
54+
{
55+
get => _desktopNeedsRender;
56+
set => _desktopNeedsRender = value;
57+
}
58+
4359
// Status bar caching
4460
private string? _cachedBottomStatus;
4561
private string? _cachedTopStatus;
@@ -191,22 +207,38 @@ public void UpdateDisplay()
191207

192208
lock (_renderLock)
193209
{
194-
// ATOMIC DESKTOP CLEARING: Clear old window positions before rendering
195-
// This prevents traces from rapid moves between frames
196-
if (_pendingDesktopClears.Count > 0)
197-
{
198-
// Copy list to avoid race condition (mouse events can add during iteration)
199-
var clearsCopy = _pendingDesktopClears.ToList();
200-
_pendingDesktopClears.Clear();
210+
// ATOMIC DESKTOP CLEARING: Clear old window positions before rendering
211+
// FIX: Calculate visible regions to avoid overwriting windows below (prevents empty regions bug)
212+
if (_pendingDesktopClears.Count > 0)
213+
{
214+
// Copy list to avoid race condition (mouse events can add during iteration)
215+
var clearsCopy = _pendingDesktopClears.ToList();
216+
_pendingDesktopClears.Clear();
201217

202-
foreach (var rect in clearsCopy)
218+
foreach (var clearRect in clearsCopy)
219+
{
220+
// Find all visible windows that overlap with clear area
221+
var overlappingWindows = _windowSystemContext.Windows.Values
222+
.Where(w =>
223+
w.State != WindowState.Minimized &&
224+
GeometryHelpers.DoesRectangleIntersect(clearRect,
225+
new Rectangle(w.Left, w.Top, w.Width, w.Height)))
226+
.ToList();
227+
228+
// Calculate visible regions (areas NOT covered by windows)
229+
var visibleRegions = _windowSystemContext.VisibleRegions
230+
.CalculateVisibleRegions(clearRect, overlappingWindows);
231+
232+
// Only clear visible regions (never overwrite windows!)
233+
foreach (var region in visibleRegions)
203234
{
204-
_renderer.FillRect(rect.Left, rect.Top, rect.Width, rect.Height,
235+
_renderer.FillRect(region.Left, region.Top, region.Width, region.Height,
205236
_windowSystemContext.Theme.DesktopBackgroundChar,
206237
_windowSystemContext.Theme.DesktopBackgroundColor,
207238
_windowSystemContext.Theme.DesktopForegroundColor);
208239
}
209240
}
241+
}
210242

211243
// RENDERING ORDER:
212244
// 1. Windows first (so we can measure their dirty chars)
@@ -233,6 +265,10 @@ public void UpdateDisplay()
233265
// Clear the region update set for next frame
234266
_windowsNeedingRegionUpdate.Clear();
235267
_consoleDriver.Flush();
268+
269+
// Clear desktop render flag now that we've rendered
270+
// (always clear it, as it was used to trigger this render if no windows were dirty)
271+
_desktopNeedsRender = false;
236272
}
237273
}
238274

0 commit comments

Comments
 (0)