|
| 1 | +using SharpConsoleUI; |
| 2 | +using SharpConsoleUI.Builders; |
| 3 | +using SharpConsoleUI.Controls; |
| 4 | +using SharpConsoleUI.Helpers; |
| 5 | +using SharpConsoleUI.Layout; |
| 6 | +using SharpConsoleUI.Rendering; |
| 7 | + |
| 8 | +namespace DemoApp.DemoWindows; |
| 9 | + |
| 10 | +internal static class AlphaBlendingDemoWindow |
| 11 | +{ |
| 12 | + private const int WindowWidth = 110; |
| 13 | + private const int WindowHeight = 38; |
| 14 | + |
| 15 | + public static Window Create(ConsoleWindowSystem ws) |
| 16 | + { |
| 17 | + var animToggle = Controls.Checkbox("Animate background gradient") |
| 18 | + .WithName("animToggle") |
| 19 | + .Checked(true) |
| 20 | + .Build(); |
| 21 | + |
| 22 | + var topBar = Controls.HorizontalGrid() |
| 23 | + .WithAlignment(HorizontalAlignment.Stretch) |
| 24 | + .Column(col => col |
| 25 | + .Flex(3) |
| 26 | + .Add(Controls.Markup() |
| 27 | + .AddLine("[bold]Alpha Blending Showcase[/] [dim]— five zones, one compositor[/]") |
| 28 | + .Build())) |
| 29 | + .Column(col => col |
| 30 | + .Flex(1) |
| 31 | + .Add(animToggle)) |
| 32 | + .Build(); |
| 33 | + |
| 34 | + var zone1Heading = Controls.Markup() |
| 35 | + .AddLine("[bold]1. Alpha Ladder[/] [dim]same color, eight opacity levels[/]") |
| 36 | + .Build(); |
| 37 | + |
| 38 | + byte[] alphaLevels = { 0, 36, 73, 109, 146, 182, 219, 255 }; |
| 39 | + |
| 40 | + var zone1GridBuilder = Controls.HorizontalGrid() |
| 41 | + .WithAlignment(HorizontalAlignment.Stretch); |
| 42 | + |
| 43 | + foreach (var alpha in alphaLevels) |
| 44 | + { |
| 45 | + var capturedAlpha = alpha; |
| 46 | + var panel = Controls.ScrollablePanel() |
| 47 | + .WithBackgroundColor(new Color(255, 140, 0, capturedAlpha)) |
| 48 | + .WithBorderStyle(BorderStyle.None) |
| 49 | + .Build(); |
| 50 | + panel.AddControl(Controls.Markup() |
| 51 | + .AddLine($"α={capturedAlpha}") |
| 52 | + .Build()); |
| 53 | + zone1GridBuilder = zone1GridBuilder.Column(col => col.Flex(1).Add(panel)); |
| 54 | + } |
| 55 | + |
| 56 | + var zone1Grid = zone1GridBuilder.Build(); |
| 57 | + |
| 58 | + var zone2Heading = Controls.Markup() |
| 59 | + .AddLine("[bold]2. Fade to Transparent[/] [dim]ColorGradient interpolates alpha, not just RGB[/]") |
| 60 | + .Build(); |
| 61 | + |
| 62 | + // Build the fade strip: 60 '█' chars sampled from opaque→transparent cyan gradient |
| 63 | + var fadeGradient = ColorGradient.FromColors( |
| 64 | + new Color(0, 220, 220, 255), // opaque cyan |
| 65 | + new Color(0, 220, 220, 0)); // transparent cyan |
| 66 | + |
| 67 | + var sb = new System.Text.StringBuilder(); |
| 68 | + for (int i = 0; i < 60; i++) |
| 69 | + { |
| 70 | + var c = fadeGradient.Interpolate((double)i / 59); |
| 71 | + sb.Append($"[#{c.R:X2}{c.G:X2}{c.B:X2}{c.A:X2}]█[/]"); |
| 72 | + } |
| 73 | + |
| 74 | + var zone2Strip = Controls.Markup() |
| 75 | + .AddLine(sb.ToString()) |
| 76 | + .Build(); |
| 77 | + |
| 78 | + var zone3Heading = Controls.Markup() |
| 79 | + .AddLine("[bold]3. Glass Panels[/] [dim]Color.WithAlpha() at 25 / 50 / 75 / 100 %[/]") |
| 80 | + .Build(); |
| 81 | + |
| 82 | + var zone3Grid = Controls.HorizontalGrid() |
| 83 | + .WithAlignment(HorizontalAlignment.Stretch) |
| 84 | + .WithVerticalAlignment(VerticalAlignment.Fill) |
| 85 | + .Column(col => col.Flex(1).Add(MakeGlassPanel(64, "25%"))) |
| 86 | + .Column(col => col.Flex(1).Add(MakeGlassPanel(128, "50%"))) |
| 87 | + .Column(col => col.Flex(1).Add(MakeGlassPanel(192, "75%"))) |
| 88 | + .Column(col => col.Flex(1).Add(MakeGlassPanel(255, "100%"))) |
| 89 | + .Build(); |
| 90 | + |
| 91 | + var zone4Heading = Controls.Markup() |
| 92 | + .AddLine("[bold]4. Live Compositor[/] [dim]Color.Blend(src, dst) — drag to change source alpha[/]") |
| 93 | + .Build(); |
| 94 | + |
| 95 | + var alphaSlider = Controls.Slider() |
| 96 | + .Horizontal() |
| 97 | + .WithRange(0, 255) |
| 98 | + .WithValue(128) |
| 99 | + .WithName("alphaSlider") |
| 100 | + .WithStep(1) |
| 101 | + .Build(); |
| 102 | + |
| 103 | + var alphaLabel = Controls.Markup() |
| 104 | + .WithName("alphaLabel") |
| 105 | + .Build(); |
| 106 | + |
| 107 | + var blendPreview = Controls.Markup() |
| 108 | + .WithName("blendPreview") |
| 109 | + .Build(); |
| 110 | + |
| 111 | + // Helper to compute and format the three-row blend preview. |
| 112 | + // Each string is one line — MarkupControl renders each list element as a separate row. |
| 113 | + static List<string> MakeBlendPreview(int alpha) |
| 114 | + { |
| 115 | + var src = new Color(255, 100, 50, (byte)alpha); |
| 116 | + var dst = new Color(30, 144, 255, 255); |
| 117 | + var blended = Color.Blend(src, dst); |
| 118 | + |
| 119 | + string Swatch(Color c) => $"[#{c.R:X2}{c.G:X2}{c.B:X2}{c.A:X2}]████[/]"; |
| 120 | + |
| 121 | + return new List<string> |
| 122 | + { |
| 123 | + $"src {Swatch(src)} rgba(255,100,50,{alpha})", |
| 124 | + $"dst {Swatch(dst)} rgba(30,144,255,255)", |
| 125 | + $"out {Swatch(blended)} Color.Blend(src, dst)", |
| 126 | + }; |
| 127 | + } |
| 128 | + |
| 129 | + // Initial state |
| 130 | + alphaLabel.SetContent(new List<string> { "Alpha: 128 / 255" }); |
| 131 | + blendPreview.SetContent(MakeBlendPreview(128)); |
| 132 | + |
| 133 | + // Wire value-changed after controls are built |
| 134 | + alphaSlider.ValueChanged += (_, e) => |
| 135 | + { |
| 136 | + int a = (int)alphaSlider.Value; |
| 137 | + alphaLabel.SetContent(new List<string> { $"Alpha: {a} / 255" }); |
| 138 | + blendPreview.SetContent(MakeBlendPreview(a)); |
| 139 | + }; |
| 140 | + |
| 141 | + var zone4Controls = Controls.HorizontalGrid() |
| 142 | + .WithAlignment(HorizontalAlignment.Stretch) |
| 143 | + .Column(col => col |
| 144 | + .Flex(1) |
| 145 | + .Add(alphaSlider) |
| 146 | + .Add(alphaLabel)) |
| 147 | + .Column(col => col |
| 148 | + .Flex(2) |
| 149 | + .Add(blendPreview)) |
| 150 | + .Build(); |
| 151 | + |
| 152 | + var zone5Heading = Controls.Markup() |
| 153 | + .AddLine("[bold]5. Pulse Panel[/] [dim]alpha animated 0 → 255 → 0 via async thread[/]") |
| 154 | + .Build(); |
| 155 | + |
| 156 | + var pulsePanel = Controls.ScrollablePanel() |
| 157 | + .WithName("pulsePanel") |
| 158 | + .WithBorderStyle(BorderStyle.Rounded) |
| 159 | + .WithVerticalAlignment(VerticalAlignment.Fill) |
| 160 | + .Build(); |
| 161 | + pulsePanel.AddControl(Controls.Markup().AddLine("background alpha pulses").Build()); |
| 162 | + |
| 163 | + // Left column: zone 3 |
| 164 | + var leftCol = Controls.ScrollablePanel() |
| 165 | + .WithVerticalAlignment(VerticalAlignment.Fill) |
| 166 | + .Build(); |
| 167 | + leftCol.AddControl(zone3Heading); |
| 168 | + leftCol.AddControl(zone3Grid); |
| 169 | + |
| 170 | + // Right column: zones 4 and 5 |
| 171 | + var rightCol = Controls.ScrollablePanel() |
| 172 | + .WithVerticalAlignment(VerticalAlignment.Fill) |
| 173 | + .Build(); |
| 174 | + rightCol.AddControl(zone4Heading); |
| 175 | + rightCol.AddControl(zone4Controls); |
| 176 | + rightCol.AddControl(zone5Heading); |
| 177 | + rightCol.AddControl(pulsePanel); |
| 178 | + |
| 179 | + var zonesBottomRow = Controls.HorizontalGrid() |
| 180 | + .WithAlignment(HorizontalAlignment.Stretch) |
| 181 | + .WithVerticalAlignment(VerticalAlignment.Fill) |
| 182 | + .Column(col => col.Flex(1).Add(leftCol)) |
| 183 | + .Column(col => col.Flex(1).Add(rightCol)) |
| 184 | + .Build(); |
| 185 | + |
| 186 | + var window = new WindowBuilder(ws) |
| 187 | + .WithTitle("Alpha Blending") |
| 188 | + .WithSize(WindowWidth, WindowHeight) |
| 189 | + .Centered() |
| 190 | + .WithBackgroundGradient( |
| 191 | + ColorGradient.FromColors(Color.Blue, Color.MediumPurple, Color.Orange1), |
| 192 | + GradientDirection.DiagonalDown) |
| 193 | + .OnKeyPressed((s, e) => |
| 194 | + { |
| 195 | + if (e.KeyInfo.Key == ConsoleKey.Escape) |
| 196 | + { |
| 197 | + ws.CloseWindow((Window)s!); |
| 198 | + e.Handled = true; |
| 199 | + } |
| 200 | + }) |
| 201 | + .WithAsyncWindowThread(async (win, ct) => |
| 202 | + { |
| 203 | + var directions = new[] |
| 204 | + { |
| 205 | + GradientDirection.Horizontal, |
| 206 | + GradientDirection.DiagonalDown, |
| 207 | + GradientDirection.Vertical, |
| 208 | + GradientDirection.DiagonalUp, |
| 209 | + }; |
| 210 | + int dirIndex = 0; |
| 211 | + var lastDirChange = DateTime.Now; |
| 212 | + var startTime = DateTime.Now; |
| 213 | + |
| 214 | + while (!ct.IsCancellationRequested) |
| 215 | + { |
| 216 | + await Task.Delay(50, ct); |
| 217 | + |
| 218 | + // Pulse panel |
| 219 | + var pulse = win.FindControl<ScrollablePanelControl>("pulsePanel"); |
| 220 | + if (pulse != null) |
| 221 | + { |
| 222 | + double t = (DateTime.Now - startTime).TotalSeconds; |
| 223 | + byte a = (byte)((Math.Sin(t * Math.PI) + 1.0) / 2.0 * 255); |
| 224 | + pulse.BackgroundColor = new Color(255, 50, 100, a); |
| 225 | + } |
| 226 | + |
| 227 | + // Background animation |
| 228 | + var toggle = win.FindControl<CheckboxControl>("animToggle"); |
| 229 | + if (toggle?.Checked == true && |
| 230 | + (DateTime.Now - lastDirChange).TotalSeconds >= 1.5) |
| 231 | + { |
| 232 | + dirIndex = (dirIndex + 1) % directions.Length; |
| 233 | + win.BackgroundGradient = new GradientBackground( |
| 234 | + ColorGradient.FromColors(Color.Blue, Color.MediumPurple, Color.Orange1), |
| 235 | + directions[dirIndex]); |
| 236 | + lastDirChange = DateTime.Now; |
| 237 | + } |
| 238 | + } |
| 239 | + }) |
| 240 | + .AddControl(topBar) |
| 241 | + .AddControl(zone1Heading) |
| 242 | + .AddControl(zone1Grid) |
| 243 | + .AddControl(zone2Heading) |
| 244 | + .AddControl(zone2Strip) |
| 245 | + .AddControl(zonesBottomRow) |
| 246 | + .BuildAndShow(); |
| 247 | + |
| 248 | + return window; |
| 249 | + } |
| 250 | + |
| 251 | + private static ScrollablePanelControl MakeGlassPanel(byte alpha, string label) |
| 252 | + { |
| 253 | + var panel = Controls.ScrollablePanel() |
| 254 | + .WithBackgroundColor(new Color(30, 144, 255, alpha)) |
| 255 | + .WithBorderStyle(BorderStyle.Rounded) |
| 256 | + .WithVerticalAlignment(VerticalAlignment.Fill) |
| 257 | + .Build(); |
| 258 | + panel.AddControl(Controls.Markup().AddLine(label).Build()); |
| 259 | + return panel; |
| 260 | + } |
| 261 | +} |
0 commit comments