|
| 1 | +using SharpConsoleUI; |
| 2 | +using SharpConsoleUI.Controls; |
| 3 | +using SharpConsoleUI.Layout; |
| 4 | +using Spectre.Console; |
| 5 | + |
| 6 | +namespace CompositorEffectsExample; |
| 7 | + |
| 8 | +/// <summary> |
| 9 | +/// Demonstrates PreBufferPaint hook with iconic "Matrix" falling characters effect. |
| 10 | +/// Green characters cascade down columns at varying speeds, creating depth and movement. |
| 11 | +/// </summary> |
| 12 | +public class MatrixRainWindow : Window |
| 13 | +{ |
| 14 | + private int[] _columnPositions = Array.Empty<int>(); |
| 15 | + private float[] _columnSpeeds = Array.Empty<float>(); |
| 16 | + private char[][] _columnTrails = Array.Empty<char[]>(); |
| 17 | + private const int TrailLength = 15; |
| 18 | + private readonly Random _random = new(); |
| 19 | + private System.Timers.Timer? _animationTimer; |
| 20 | + |
| 21 | + public MatrixRainWindow(ConsoleWindowSystem windowSystem) : base(windowSystem) |
| 22 | + { |
| 23 | + Title = "Matrix Rain Effect (PreBufferPaint)"; |
| 24 | + Width = 80; |
| 25 | + Height = 30; |
| 26 | + |
| 27 | + InitializeColumns(); |
| 28 | + |
| 29 | + |
| 30 | + // Add info panel |
| 31 | + AddControl(new MarkupControl(new List<string> |
| 32 | + { |
| 33 | + "[bold green]╔════════════════════════════════════════════════════╗[/]", |
| 34 | + "[bold green]║ MATRIX RAIN - PreBufferPaint Demo ║[/]", |
| 35 | + "[bold green]╚════════════════════════════════════════════════════╝[/]", |
| 36 | + "", |
| 37 | + "[white]This demonstrates PreBufferPaint hook which fires[/]", |
| 38 | + "[white]BEFORE controls are painted, allowing custom animated[/]", |
| 39 | + "[white]backgrounds that don't interfere with UI elements.[/]", |
| 40 | + "", |
| 41 | + "[dim]• Green characters cascade at varying speeds[/]", |
| 42 | + "[dim]• Each column has independent position and trail[/]", |
| 43 | + "[dim]• Brightness fades from head to tail[/]", |
| 44 | + "[dim]• Runs at 30fps with minimal overhead[/]", |
| 45 | + "", |
| 46 | + "[yellow]Press Esc to close this window[/]" |
| 47 | + })); |
| 48 | + |
| 49 | + // Hook into PreBufferPaint AFTER all controls are added |
| 50 | + if (Renderer != null) |
| 51 | + { |
| 52 | + Renderer.PreBufferPaint += RenderMatrixRain; |
| 53 | + } |
| 54 | + |
| 55 | + // Start animation (exactly like FadeInWindow) |
| 56 | + _animationTimer = new System.Timers.Timer(33); // ~30 FPS |
| 57 | + _animationTimer.AutoReset = true; |
| 58 | + _animationTimer.Elapsed += (sender, e) => |
| 59 | + { |
| 60 | + UpdateColumnPositions(); |
| 61 | + this.Invalidate(redrawAll: true); |
| 62 | + }; |
| 63 | + _animationTimer.Start(); |
| 64 | + |
| 65 | + // Cleanup on window close |
| 66 | + OnClosing += (sender, e) => |
| 67 | + { |
| 68 | + _animationTimer?.Stop(); |
| 69 | + _animationTimer?.Dispose(); |
| 70 | + |
| 71 | + if (Renderer != null) |
| 72 | + { |
| 73 | + Renderer.PreBufferPaint -= RenderMatrixRain; |
| 74 | + } |
| 75 | + }; |
| 76 | + } |
| 77 | + |
| 78 | + private void InitializeColumns() |
| 79 | + { |
| 80 | + int cols = Width; |
| 81 | + _columnPositions = new int[cols]; |
| 82 | + _columnSpeeds = new float[cols]; |
| 83 | + _columnTrails = new char[cols][]; |
| 84 | + |
| 85 | + for (int i = 0; i < cols; i++) |
| 86 | + { |
| 87 | + _columnPositions[i] = _random.Next(-TrailLength, Height); |
| 88 | + _columnSpeeds[i] = 0.5f + (float)_random.NextDouble() * 1.5f; |
| 89 | + _columnTrails[i] = GenerateRandomTrail(); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + private void RenderMatrixRain(CharacterBuffer buffer, LayoutRect dirtyRegion, LayoutRect clipRect) |
| 94 | + { |
| 95 | + // Clear to black first |
| 96 | + buffer.FillRect(new LayoutRect(0, 0, buffer.Width, buffer.Height), |
| 97 | + ' ', Color.Black, Color.Black); |
| 98 | + |
| 99 | + // Render each column's trail |
| 100 | + for (int x = 0; x < _columnPositions.Length && x < buffer.Width; x++) |
| 101 | + { |
| 102 | + int headY = _columnPositions[x]; |
| 103 | + |
| 104 | + for (int i = 0; i < TrailLength; i++) |
| 105 | + { |
| 106 | + int y = headY - i; |
| 107 | + if (y < 0 || y >= buffer.Height) continue; |
| 108 | + |
| 109 | + // Fade from bright green (head) to dark green (tail) |
| 110 | + float brightness = 1.0f - (i / (float)TrailLength); |
| 111 | + Color color = new Color( |
| 112 | + 0, |
| 113 | + (byte)(255 * brightness), |
| 114 | + (byte)(128 * brightness) |
| 115 | + ); |
| 116 | + |
| 117 | + char ch = i < _columnTrails[x].Length ? _columnTrails[x][i] : ' '; |
| 118 | + buffer.SetCell(x, y, ch, color, Color.Black); |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + private void UpdateColumnPositions() |
| 124 | + { |
| 125 | + for (int i = 0; i < _columnPositions.Length; i++) |
| 126 | + { |
| 127 | + _columnPositions[i] += (int)_columnSpeeds[i]; |
| 128 | + |
| 129 | + // Reset at bottom with random delay |
| 130 | + if (_columnPositions[i] - TrailLength > Height) |
| 131 | + { |
| 132 | + _columnPositions[i] = -TrailLength - _random.Next(0, 20); |
| 133 | + _columnTrails[i] = GenerateRandomTrail(); |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + private char[] GenerateRandomTrail() |
| 139 | + { |
| 140 | + char[] trail = new char[TrailLength]; |
| 141 | + for (int i = 0; i < TrailLength; i++) |
| 142 | + { |
| 143 | + // Mix of katakana-like characters, numbers, and symbols |
| 144 | + trail[i] = _random.Next(0, 100) switch |
| 145 | + { |
| 146 | + < 40 => (char)_random.Next('A', 'Z' + 1), // Uppercase letters |
| 147 | + < 70 => (char)_random.Next('0', '9' + 1), // Numbers |
| 148 | + < 90 => "!@#$%^&*+-=<>?"[_random.Next(15)], // Symbols |
| 149 | + _ => ' ' // Space |
| 150 | + }; |
| 151 | + } |
| 152 | + return trail; |
| 153 | + } |
| 154 | + |
| 155 | +} |
0 commit comments