Skip to content

Commit b4efd03

Browse files
committed
Add FractalWindow to CompositorEffectsExample
- Add animated Mandelbrot/Julia fractal explorer window - Update Program.cs with new menu option for fractal explorer - Use ClassicTheme for consistent blue modals - Improve ScreenshotWindow rendering - Remove redundant README (content moved to main docs)
1 parent d8902dc commit b4efd03

6 files changed

Lines changed: 388 additions & 127 deletions

File tree

Examples/CompositorEffectsExample/FadeInWindow.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ public FadeInWindow(ConsoleWindowSystem windowSystem) : base(windowSystem)
2020
Title = "Fade-In Effect Demo";
2121
Width = 60;
2222
Height = 20;
23-
BackgroundColor = Color.DarkBlue;
24-
ForegroundColor = Color.White;
2523

2624
// Add content
2725
var markup = new MarkupControl(new List<string>
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
using SharpConsoleUI;
2+
using SharpConsoleUI.Controls;
3+
using SharpConsoleUI.Layout;
4+
using Spectre.Console;
5+
6+
namespace CompositorEffectsExample;
7+
8+
/// <summary>
9+
/// Renders an animated Mandelbrot fractal with cycling colors.
10+
/// Uses Window's thread support with CancellationToken for clean shutdown.
11+
/// </summary>
12+
public class FractalWindow : Window
13+
{
14+
private double _time = 0;
15+
private double _zoom = 1.0;
16+
private double _centerX = -0.5;
17+
private double _centerY = 0.0;
18+
private bool _isJulia = false;
19+
private double _juliaReal = -0.7;
20+
private double _juliaImag = 0.27015;
21+
22+
// Animation modes
23+
private int _animMode = 0;
24+
private const int AnimModeCount = 4;
25+
26+
// Color palette (pre-computed for speed)
27+
private readonly Color[] _palette;
28+
private const int PaletteSize = 256;
29+
private const int MaxIterations = 50;
30+
31+
public FractalWindow(ConsoleWindowSystem windowSystem)
32+
: base(windowSystem, FractalAnimationLoop)
33+
{
34+
Title = "Fractal Explorer";
35+
Width = 80;
36+
Height = 40;
37+
IsResizable = true;
38+
39+
// Pre-compute color palette
40+
_palette = GeneratePalette();
41+
42+
// Add title at top
43+
var title = new MarkupControl(new List<string>
44+
{
45+
"[bold yellow] FRACTAL EXPLORER [/]"
46+
});
47+
title.Margin = new Margin(1, 1, 0, 0);
48+
title.StickyPosition = StickyPosition.Top;
49+
AddControl(title);
50+
51+
// Add key instructions at bottom
52+
var help = new MarkupControl(new List<string>
53+
{
54+
"[cyan]Space[/]:[white]Mode[/] [cyan]M[/]:[white]Julia/Mandel[/] [cyan]+/-[/]:[white]Zoom[/] [cyan]Arrows[/]:[white]Pan[/] [cyan]R[/]:[white]Reset[/] [cyan]Esc[/]:[white]Close[/]"
55+
});
56+
help.Margin = new Margin(1, 0, 1, 0);
57+
help.StickyPosition = StickyPosition.Bottom;
58+
AddControl(help);
59+
60+
// Hook into rendering - PreBufferPaint so controls render ON TOP of fractal
61+
if (Renderer != null)
62+
{
63+
Renderer.PreBufferPaint += RenderFractal;
64+
}
65+
66+
// Keyboard controls
67+
KeyPressed += HandleKey;
68+
69+
// Reset fractal on resize
70+
OnResize += (s, e) => Reset();
71+
72+
OnClosing += (s, e) =>
73+
{
74+
if (Renderer != null)
75+
{
76+
Renderer.PreBufferPaint -= RenderFractal;
77+
}
78+
};
79+
}
80+
81+
/// <summary>
82+
/// Animation loop running on window's background thread.
83+
/// Automatically cancelled when window closes.
84+
/// </summary>
85+
private static async Task FractalAnimationLoop(Window window, CancellationToken ct)
86+
{
87+
var fractalWindow = (FractalWindow)window;
88+
89+
while (!ct.IsCancellationRequested)
90+
{
91+
fractalWindow._time += 0.05;
92+
fractalWindow.UpdateAnimation();
93+
fractalWindow.Invalidate(redrawAll: true);
94+
95+
try
96+
{
97+
await Task.Delay(33, ct); // ~30 FPS
98+
}
99+
catch (OperationCanceledException)
100+
{
101+
break; // Clean exit
102+
}
103+
}
104+
}
105+
106+
private Color[] GeneratePalette()
107+
{
108+
var palette = new Color[PaletteSize];
109+
for (int i = 0; i < PaletteSize; i++)
110+
{
111+
double t = (double)i / PaletteSize;
112+
// Create a vibrant cycling palette
113+
int r = (int)(Math.Sin(t * Math.PI * 2 + 0) * 127 + 128);
114+
int g = (int)(Math.Sin(t * Math.PI * 2 + 2.094) * 127 + 128); // +2π/3
115+
int b = (int)(Math.Sin(t * Math.PI * 2 + 4.189) * 127 + 128); // +4π/3
116+
palette[i] = new Color((byte)r, (byte)g, (byte)b);
117+
}
118+
return palette;
119+
}
120+
121+
private void UpdateAnimation()
122+
{
123+
switch (_animMode)
124+
{
125+
case 0: // Color cycling only
126+
break;
127+
case 1: // Slow zoom into interesting region
128+
_zoom *= 1.005;
129+
_centerX = -0.743643887037151;
130+
_centerY = 0.131825904205330;
131+
if (_zoom > 1000) _zoom = 1.0;
132+
break;
133+
case 2: // Julia set morphing
134+
_isJulia = true;
135+
_juliaReal = -0.7 + Math.Sin(_time * 0.3) * 0.2;
136+
_juliaImag = 0.27015 + Math.Cos(_time * 0.4) * 0.15;
137+
break;
138+
case 3: // Panning exploration
139+
_isJulia = false;
140+
_centerX = -0.5 + Math.Sin(_time * 0.2) * 0.5;
141+
_centerY = Math.Cos(_time * 0.15) * 0.5;
142+
_zoom = 2.0 + Math.Sin(_time * 0.1) * 1.5;
143+
break;
144+
}
145+
}
146+
147+
private void HandleKey(object? sender, KeyPressedEventArgs e)
148+
{
149+
switch (e.KeyInfo.Key)
150+
{
151+
case ConsoleKey.Escape:
152+
Close();
153+
e.Handled = true;
154+
break;
155+
156+
case ConsoleKey.Spacebar:
157+
_animMode = (_animMode + 1) % AnimModeCount;
158+
if (_animMode == 0) Reset();
159+
e.Handled = true;
160+
break;
161+
162+
case ConsoleKey.M:
163+
_isJulia = !_isJulia;
164+
e.Handled = true;
165+
break;
166+
167+
case ConsoleKey.R:
168+
Reset();
169+
e.Handled = true;
170+
break;
171+
172+
case ConsoleKey.Add:
173+
case ConsoleKey.OemPlus:
174+
_zoom *= 1.5;
175+
e.Handled = true;
176+
break;
177+
178+
case ConsoleKey.Subtract:
179+
case ConsoleKey.OemMinus:
180+
_zoom /= 1.5;
181+
if (_zoom < 0.5) _zoom = 0.5;
182+
e.Handled = true;
183+
break;
184+
185+
case ConsoleKey.UpArrow:
186+
_centerY -= 0.1 / _zoom;
187+
e.Handled = true;
188+
break;
189+
190+
case ConsoleKey.DownArrow:
191+
_centerY += 0.1 / _zoom;
192+
e.Handled = true;
193+
break;
194+
195+
case ConsoleKey.LeftArrow:
196+
_centerX -= 0.1 / _zoom;
197+
e.Handled = true;
198+
break;
199+
200+
case ConsoleKey.RightArrow:
201+
_centerX += 0.1 / _zoom;
202+
e.Handled = true;
203+
break;
204+
}
205+
}
206+
207+
private void Reset()
208+
{
209+
_zoom = 1.0;
210+
_centerX = -0.5;
211+
_centerY = 0.0;
212+
_isJulia = false;
213+
_juliaReal = -0.7;
214+
_juliaImag = 0.27015;
215+
}
216+
217+
private void RenderFractal(CharacterBuffer buffer, LayoutRect dirtyRegion, LayoutRect clipRect)
218+
{
219+
// PreBufferPaint: Paint full buffer, controls will render ON TOP
220+
int width = buffer.Width;
221+
int height = buffer.Height;
222+
223+
// Aspect ratio correction (console chars are ~2:1)
224+
double aspectRatio = (double)width / height * 0.5;
225+
226+
// Calculate view bounds
227+
double scale = 2.0 / _zoom;
228+
double xMin = _centerX - scale * aspectRatio;
229+
double xMax = _centerX + scale * aspectRatio;
230+
double yMin = _centerY - scale;
231+
double yMax = _centerY + scale;
232+
233+
double xStep = (xMax - xMin) / width;
234+
double yStep = (yMax - yMin) / height;
235+
236+
// Color offset for animation
237+
int colorOffset = (int)(_time * 20) % PaletteSize;
238+
239+
// Render fractal to entire buffer
240+
for (int py = 0; py < height; py++)
241+
{
242+
double y0 = yMin + py * yStep;
243+
244+
for (int px = 0; px < width; px++)
245+
{
246+
double x0 = xMin + px * xStep;
247+
248+
int iterations;
249+
if (_isJulia)
250+
{
251+
iterations = ComputeJulia(x0, y0, _juliaReal, _juliaImag);
252+
}
253+
else
254+
{
255+
iterations = ComputeMandelbrot(x0, y0);
256+
}
257+
258+
// Map iterations to color
259+
Color color;
260+
char ch;
261+
262+
if (iterations == MaxIterations)
263+
{
264+
// Inside the set - deep color
265+
color = Color.Black;
266+
ch = ' ';
267+
}
268+
else
269+
{
270+
// Outside - colorful gradient with animation
271+
int colorIndex = (iterations * 8 + colorOffset) % PaletteSize;
272+
color = _palette[colorIndex];
273+
274+
// Use different chars based on iteration density
275+
ch = iterations switch
276+
{
277+
< 5 => '░',
278+
< 10 => '▒',
279+
< 20 => '▓',
280+
_ => '█'
281+
};
282+
}
283+
284+
buffer.SetCell(px, py, ch, color, Color.Black);
285+
}
286+
}
287+
}
288+
289+
private int ComputeMandelbrot(double x0, double y0)
290+
{
291+
double x = 0, y = 0;
292+
int iteration = 0;
293+
294+
while (x * x + y * y <= 4 && iteration < MaxIterations)
295+
{
296+
double xTemp = x * x - y * y + x0;
297+
y = 2 * x * y + y0;
298+
x = xTemp;
299+
iteration++;
300+
}
301+
302+
return iteration;
303+
}
304+
305+
private int ComputeJulia(double x0, double y0, double cReal, double cImag)
306+
{
307+
double x = x0, y = y0;
308+
int iteration = 0;
309+
310+
while (x * x + y * y <= 4 && iteration < MaxIterations)
311+
{
312+
double xTemp = x * x - y * y + cReal;
313+
y = 2 * x * y + cImag;
314+
x = xTemp;
315+
iteration++;
316+
}
317+
318+
return iteration;
319+
}
320+
}

Examples/CompositorEffectsExample/ModalBlurWindow.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ public ModalBlurWindow(ConsoleWindowSystem windowSystem) : base(windowSystem)
1919
Title = "Blur Effect Demo";
2020
Width = 70;
2121
Height = 22;
22-
BackgroundColor = Color.Navy;
23-
ForegroundColor = Color.White;
2422

2523
// Add colorful content
2624
var markup = new MarkupControl(new List<string>

0 commit comments

Comments
 (0)