Skip to content

Commit e913cee

Browse files
committed
Fix window dragging rendering issues
- Remove premature IsDirty=false for completely covered windows in Renderer.cs. Covered windows now stay dirty until actually rendered. - Update InvalidateExposedRegions to check both old AND new positions, ensuring windows are invalidated whether being uncovered or newly covered. - Add safety check in RenderCoordinator to ensure dragged window is always in the render list. - Unify keyboard and mouse move to use the same queued rendering pattern.
1 parent da3dd51 commit e913cee

3 files changed

Lines changed: 38 additions & 38 deletions

File tree

SharpConsoleUI/Renderer.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,9 @@ public void RenderWindow(Window window)
334334

335335
if (!visibleRegions.Any())
336336
{
337-
// Window is completely covered - no need to render
338-
window.IsDirty = false;
337+
// Window is completely covered - skip rendering but keep dirty.
338+
// When the covering window moves away and exposes this window,
339+
// InvalidateExposedRegions will trigger a re-render where visible regions will exist.
339340
return;
340341
}
341342

@@ -584,8 +585,9 @@ private void RenderNormalWindow(Window window)
584585

585586
if (!visibleRegions.Any())
586587
{
587-
// Window is completely covered - no need to render
588-
window.IsDirty = false;
588+
// Window is completely covered - skip rendering but keep dirty.
589+
// When the covering window moves away and exposes this window,
590+
// InvalidateExposedRegions will trigger a re-render where visible regions will exist.
589591
return;
590592
}
591593

SharpConsoleUI/Rendering/RenderCoordinator.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,19 @@ private void RenderWindows()
368368
// don't need re-rendering when a window beneath them changes.
369369
}
370370

371+
// Safety: Always ensure the window being dragged is in the render list.
372+
// This prevents the dragging window from going invisible if an edge case
373+
// causes it to be skipped (e.g., marked clean prematurely or covered check race).
374+
var dragState = _windowStateService.CurrentDrag;
375+
if (dragState != null && dragState.Window != null &&
376+
dragState.Window.State != WindowState.Minimized &&
377+
dragState.Window.Width > 0 && dragState.Window.Height > 0 &&
378+
!_windowsToRender.Contains(dragState.Window))
379+
{
380+
dragState.Window.Invalidate(true);
381+
_windowsToRender.Add(dragState.Window);
382+
}
383+
371384
// PASS 1: Render normal (non-AlwaysOnTop) windows based on their ZIndex (no LINQ)
372385
for (int i = 0; i < _sortedWindows.Count; i++)
373386
{

SharpConsoleUI/Windows/WindowPositioningManager.cs

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ public void ResizeWindowBy(Window window, int deltaWidth, int deltaHeight)
152152
/// <summary>
153153
/// Performs a move or resize operation with desktop clearing and window invalidation.
154154
/// Used for keyboard-based window operations.
155+
/// Uses the same queued rendering approach as mouse drag for consistency.
155156
/// </summary>
156157
/// <param name="window">The window to move or resize.</param>
157158
/// <param name="windowTopologyAction">The type of operation (Move or Resize).</param>
@@ -160,55 +161,37 @@ public void MoveOrResizeOperation(Window? window, WindowTopologyAction windowTop
160161
{
161162
if (window == null) return;
162163

163-
var context = _getWindowSystem();
164-
var theme = context.Theme;
165-
166164
// Store the current window bounds before any operation
167165
var oldBounds = new Rectangle(window.Left, window.Top, window.Width, window.Height);
168166

169-
// FIRST: Clear the old window position completely (same as mouse operations)
170-
_renderer.FillRect(window.Left, window.Top, window.Width, window.Height,
171-
theme.DesktopBackgroundChar, theme.DesktopBackgroundColor, theme.DesktopForegroundColor);
172-
173-
// Redraw the necessary regions that were underneath the window
174-
foreach (var w in context.Windows.Values.OrderBy(w => w.ZIndex))
175-
{
176-
// Skip minimized windows - they're invisible
177-
if (w.State == WindowState.Minimized)
178-
continue;
179-
180-
if (w != window && GeometryHelpers.DoesRectangleOverlapWindow(oldBounds, w))
181-
{
182-
// Redraw the parts of underlying windows that were covered
183-
var intersection = GeometryHelpers.GetRectangleIntersection(oldBounds,
184-
new Rectangle(w.Left, w.Top, w.Width, w.Height));
185-
186-
if (!intersection.IsEmpty)
187-
{
188-
_renderer.RenderRegion(w, intersection);
189-
}
190-
}
191-
}
167+
// Use the same queued approach as mouse move for consistent rendering.
168+
// The pending desktop clear will be processed at the start of UpdateDisplay().
169+
_renderCoordinator.AddPendingDesktopClear(oldBounds);
192170

193-
// FINALLY: Invalidate the window which will cause it to redraw at its new position
171+
// Invalidate the window which will cause it to redraw at its new position
194172
// (The actual position/size change happens in the calling HandleMoveInput method)
195-
window.Invalidate(false);
173+
window.Invalidate(true);
174+
175+
// Invalidate windows that were underneath (now exposed) and at new position
176+
InvalidateExposedRegions(window, oldBounds);
196177
}
197178

198179
#region Private Helper Methods
199180

200181
/// <summary>
201-
/// Invalidates windows that were underneath the moved window and are now exposed.
182+
/// Invalidates windows that were underneath the moved window and are now exposed,
183+
/// as well as windows at the new position that need to re-render with updated occlusion.
202184
/// </summary>
203185
/// <param name="movedWindow">The window that was moved.</param>
204186
/// <param name="oldBounds">The old bounds of the moved window.</param>
205187
private void InvalidateExposedRegions(Window movedWindow, Rectangle oldBounds)
206188
{
207-
// BRUTE FORCE FIX: Instead of trying to render tiny exposed regions (which causes blanks),
208-
// just invalidate all windows that were underneath the old position.
209-
// They'll render normally with proper visibleRegions in the next UpdateDisplay.
189+
// Invalidate windows at both OLD position (now exposed) and NEW position (now covered).
190+
// This ensures proper re-rendering in both areas affected by the move.
210191

211192
var context = _getWindowSystem();
193+
var newBounds = new Rectangle(movedWindow.Left, movedWindow.Top,
194+
movedWindow.Width, movedWindow.Height);
212195

213196
foreach (var window in context.Windows.Values)
214197
{
@@ -218,8 +201,10 @@ private void InvalidateExposedRegions(Window movedWindow, Rectangle oldBounds)
218201
if (window.ZIndex >= movedWindow.ZIndex)
219202
continue; // Only invalidate windows that were underneath
220203

221-
// Check if this window overlaps with the OLD position
222-
if (GeometryHelpers.DoesRectangleOverlapWindow(oldBounds, window))
204+
// Check overlap with OLD position (exposed regions that need re-rendering)
205+
// AND NEW position (newly covered regions that need refresh when uncovered)
206+
if (GeometryHelpers.DoesRectangleOverlapWindow(oldBounds, window) ||
207+
GeometryHelpers.DoesRectangleOverlapWindow(newBounds, window))
223208
{
224209
window.Invalidate(true);
225210
}

0 commit comments

Comments
 (0)