Skip to content

Commit d8902dc

Browse files
committed
Add PreBufferPaint event and minor improvements
- Add PreBufferPaint event to WindowRenderer for rendering content behind controls (games, animations, custom backgrounds) - Rename PostBufferPaintDelegate to BufferPaintDelegate for clarity - Minor updates to WindowStateService, FileDialogs, and ClassicTheme
1 parent e913cee commit d8902dc

4 files changed

Lines changed: 64 additions & 43 deletions

File tree

SharpConsoleUI/Core/WindowStateService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,13 @@ public bool CloseWindow(Window? window, bool activateParent = true, bool force =
10171017
{
10181018
w.Invalidate(true);
10191019
}
1020+
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+
}
10201027
}
10211028

10221029
return true;

SharpConsoleUI/Dialogs/FileDialogs.cs

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ public static class FileDialogs
7171
if (!Directory.Exists(currentPath))
7272
currentPath = Environment.CurrentDirectory;
7373

74-
var theme = windowSystem.Theme;
75-
7674
// Create modal window
7775
var builder = new WindowBuilder(windowSystem)
7876
.WithTitle(title)
@@ -82,8 +80,7 @@ public static class FileDialogs
8280
.Resizable(true)
8381
.Minimizable(false)
8482
.Maximizable(true)
85-
.Movable(true)
86-
.WithColors(theme.ModalBackgroundColor, theme.WindowForegroundColor);
83+
.Movable(true);
8784

8885
if (parentWindow != null)
8986
builder.WithParent(parentWindow);
@@ -101,17 +98,13 @@ public static class FileDialogs
10198

10299
// Separator
103100
modal.AddControl(Ctl.RuleBuilder()
104-
.WithColor(Color.Grey23)
105-
.Build());
101+
.Build());
106102

107103
// Create folder list
108104
var folderList = Ctl.List()
109105
.WithAlignment(HorizontalAlignment.Stretch)
110106
.WithVerticalAlignment(VerticalAlignment.Fill)
111-
.WithColors(theme.ModalBackgroundColor, theme.WindowForegroundColor)
112-
.WithFocusedColors(theme.ModalBackgroundColor, theme.WindowForegroundColor)
113-
.WithHighlightColors(Color.Grey35, Color.White)
114-
.SimpleMode()
107+
.SimpleMode()
115108
.WithDoubleClickActivation(true)
116109
.WithName("FolderList")
117110
.Build();
@@ -123,9 +116,6 @@ public static class FileDialogs
123116
fileList = Ctl.List()
124117
.WithAlignment(HorizontalAlignment.Stretch)
125118
.WithVerticalAlignment(VerticalAlignment.Fill)
126-
.WithColors(theme.ModalBackgroundColor, theme.WindowForegroundColor)
127-
.WithFocusedColors(theme.ModalBackgroundColor, theme.WindowForegroundColor)
128-
.WithHighlightColors(Color.Grey35, Color.White)
129119
.SimpleMode()
130120
.WithDoubleClickActivation(true)
131121
.WithName("FileList")
@@ -256,8 +246,7 @@ void PopulateFileList(string path)
256246

257247
// Bottom separator
258248
modal.AddControl(Ctl.RuleBuilder()
259-
.WithColor(Color.Grey23)
260-
.StickyBottom()
249+
.StickyBottom()
261250
.Build());
262251

263252
// Footer with instructions
@@ -367,8 +356,6 @@ void PopulateFileList(string path)
367356
if (!Directory.Exists(currentPath))
368357
currentPath = Environment.CurrentDirectory;
369358

370-
var theme = windowSystem.Theme;
371-
372359
// Create modal window
373360
var builder = new WindowBuilder(windowSystem)
374361
.WithTitle(title)
@@ -378,8 +365,7 @@ void PopulateFileList(string path)
378365
.Resizable(true)
379366
.Minimizable(false)
380367
.Maximizable(true)
381-
.Movable(true)
382-
.WithColors(theme.ModalBackgroundColor, theme.WindowForegroundColor);
368+
.Movable(true);
383369

384370
if (parentWindow != null)
385371
builder.WithParent(parentWindow);
@@ -397,17 +383,13 @@ void PopulateFileList(string path)
397383

398384
// Separator
399385
modal.AddControl(Ctl.RuleBuilder()
400-
.WithColor(Color.Grey23)
401-
.Build());
386+
.Build());
402387

403388
// Folder list (left panel)
404389
var folderList = Ctl.List()
405390
.WithAlignment(HorizontalAlignment.Stretch)
406391
.WithVerticalAlignment(VerticalAlignment.Fill)
407-
.WithColors(theme.ModalBackgroundColor, theme.WindowForegroundColor)
408-
.WithFocusedColors(theme.ModalBackgroundColor, theme.WindowForegroundColor)
409-
.WithHighlightColors(Color.Grey35, Color.White)
410-
.SimpleMode()
392+
.SimpleMode()
411393
.WithDoubleClickActivation(true)
412394
.WithName("FolderList")
413395
.Build();
@@ -416,10 +398,7 @@ void PopulateFileList(string path)
416398
var fileList = Ctl.List()
417399
.WithAlignment(HorizontalAlignment.Stretch)
418400
.WithVerticalAlignment(VerticalAlignment.Fill)
419-
.WithColors(theme.ModalBackgroundColor, theme.WindowForegroundColor)
420-
.WithFocusedColors(theme.ModalBackgroundColor, theme.WindowForegroundColor)
421-
.WithHighlightColors(Color.Grey35, Color.White)
422-
.SimpleMode()
401+
.SimpleMode()
423402
.WithDoubleClickActivation(true)
424403
.WithName("FileList")
425404
.Build();
@@ -538,8 +517,7 @@ void PopulateFileList(string path)
538517

539518
// Bottom separator
540519
modal.AddControl(Ctl.RuleBuilder()
541-
.WithColor(Color.Grey23)
542-
.StickyBottom()
520+
.StickyBottom()
543521
.Build());
544522

545523
// Filename label and input
@@ -553,8 +531,7 @@ void PopulateFileList(string path)
553531

554532
// Footer separator
555533
modal.AddControl(Ctl.RuleBuilder()
556-
.WithColor(Color.Grey23)
557-
.StickyBottom()
534+
.StickyBottom()
558535
.Build());
559536

560537
// Footer with instructions

SharpConsoleUI/Themes/ClassicTheme.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public ClassicTheme()
113113
/// <summary>
114114
/// Gets or sets the background color for the desktop area behind all windows.
115115
/// </summary>
116-
public Color DesktopBackgroundColor { get; set; } = Color.Black;
116+
public Color DesktopBackgroundColor { get; set; } = Color.Grey15;
117117

118118
/// <summary>
119119
/// Gets or sets the character used to fill the desktop background area.
@@ -123,7 +123,7 @@ public ClassicTheme()
123123
/// <summary>
124124
/// Gets or sets the foreground color for the desktop background character pattern.
125125
/// </summary>
126-
public Color DesktopForegroundColor { get; set; } = Color.White;
126+
public Color DesktopForegroundColor { get; set; } = Color.Grey23;
127127

128128
/// <summary>
129129
/// Gets or sets the foreground color for the border of inactive (unfocused) windows.

SharpConsoleUI/Windows/WindowRenderer.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,45 @@ public WindowRenderer(
5252
#region Public Events
5353

5454
/// <summary>
55-
/// Delegate for buffer post-processing after painting but before ANSI conversion.
55+
/// Delegate for buffer painting events.
5656
/// </summary>
57-
/// <param name="buffer">The character buffer that was just painted.</param>
58-
/// <param name="dirtyRegion">The region that was painted (or full bounds if entire buffer).</param>
57+
/// <param name="buffer">The character buffer to paint to.</param>
58+
/// <param name="dirtyRegion">The region being painted (or full bounds if entire buffer).</param>
5959
/// <param name="clipRect">The clipping rectangle used during paint.</param>
60-
public delegate void PostBufferPaintDelegate(
60+
public delegate void BufferPaintDelegate(
6161
CharacterBuffer buffer,
6262
LayoutRect dirtyRegion,
6363
LayoutRect clipRect);
6464

6565
/// <summary>
66-
/// Raised after painting controls to the buffer but before converting to ANSI strings.
66+
/// Raised BEFORE painting controls to the buffer.
67+
/// </summary>
68+
/// <remarks>
69+
/// This event allows painting backgrounds, game graphics, or other content
70+
/// that should appear BEHIND the controls. Controls will paint on top.
71+
///
72+
/// Example use cases:
73+
/// - Game rendering (fractals, animations, sprites)
74+
/// - Custom backgrounds
75+
/// - Gradients or patterns behind UI
76+
/// </remarks>
77+
public event BufferPaintDelegate? PreBufferPaint;
78+
79+
/// <summary>
80+
/// Raised AFTER painting controls to the buffer but before converting to ANSI strings.
6781
/// </summary>
6882
/// <remarks>
6983
/// This event allows custom effects, transitions, filters, or compositor-style
7084
/// manipulations on the rendered buffer. The buffer can be safely modified here.
85+
/// Content painted here will appear ON TOP of controls.
7186
///
7287
/// Example use cases:
7388
/// - Fade in/out transitions
7489
/// - Blur effects for modal backgrounds
7590
/// - Glow effects around focused controls
7691
/// - Custom overlays and effects
7792
/// </remarks>
78-
public event PostBufferPaintDelegate? PostBufferPaint;
93+
public event BufferPaintDelegate? PostBufferPaint;
7994

8095
#endregion
8196

@@ -332,6 +347,18 @@ public void PaintDOM(LayoutRect clipRect, Color backgroundColor)
332347
// Clear buffer (could optimize to only clear clipRect region, but full clear is simpler)
333348
_buffer.Clear(backgroundColor);
334349

350+
PaintDOMWithoutClear(clipRect);
351+
}
352+
353+
/// <summary>
354+
/// Paints the DOM tree to the character buffer WITHOUT clearing first.
355+
/// Used internally when PreBufferPaint has already painted content to preserve.
356+
/// </summary>
357+
/// <param name="clipRect">The clipping rectangle in window-space coordinates.</param>
358+
private void PaintDOMWithoutClear(LayoutRect clipRect)
359+
{
360+
if (_rootNode == null || _buffer == null) return;
361+
335362
// Paint the tree with the provided clip rect
336363
_rootNode.Paint(_buffer, clipRect);
337364

@@ -529,8 +556,18 @@ public List<string> RebuildContentCacheDOM(
529556
clipRect = new LayoutRect(0, 0, availableWidth, availableHeight);
530557
}
531558

532-
// Paint to buffer with clip rect
533-
PaintDOM(clipRect, backgroundColor);
559+
// Clear buffer first (before any painting)
560+
_buffer.Clear(backgroundColor);
561+
562+
// Fire pre-paint event for background rendering (games, custom backgrounds)
563+
if (PreBufferPaint != null)
564+
{
565+
var dirtyRegion = new LayoutRect(0, 0, _buffer.Width, _buffer.Height);
566+
PreBufferPaint.Invoke(_buffer, dirtyRegion, clipRect);
567+
}
568+
569+
// Paint controls to buffer with clip rect (on top of pre-paint content)
570+
PaintDOMWithoutClear(clipRect);
534571

535572
// Fire post-paint event for custom effects (e.g., transitions, filters)
536573
if (PostBufferPaint != null && _buffer != null)

0 commit comments

Comments
 (0)