Multi-platform .NET bindings to the Ultralight project.
1using System;
2using System.Diagnostics;
3using System.IO;
4using System.Runtime.InteropServices;
5using System.Text;
6using System.Threading;
7using SixLabors.ImageSharp.PixelFormats;
8using ImpromptuNinjas.UltralightSharp.Enums;
9
10namespace ImpromptuNinjas.UltralightSharp.Demo {
11
12 public static partial class DemoProgram {
13
14 public static void Main(string[] args) {
15 var isNotInteractive = !Environment.UserInteractive || Console.IsInputRedirected;
16
17 // setup logging
18 Safe.LoggerLogMessageCallback cb = LoggerCallback;
19 Safe.Ultralight.SetLogger(new Safe.Logger {LogMessage = cb});
20
21 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
22 Console.OutputEncoding = Encoding.UTF8;
23 Ansi.WindowsConsole.TryEnableVirtualTerminalProcessing();
24 }
25
26 var asmPath = new Uri(typeof(DemoProgram).Assembly.CodeBase!).LocalPath;
27 var asmDir = Path.GetDirectoryName(asmPath)!;
28 var tempDir = Path.GetTempPath();
29 // find a place to stash instance storage
30 string storagePath;
31 do
32 storagePath = Path.Combine(tempDir, Guid.NewGuid().ToString());
33 while (Directory.Exists(storagePath) || File.Exists(storagePath));
34
35 {
36 using var cfg = new Safe.Config();
37
38 var cachePath = Path.Combine(storagePath, "Cache");
39 cfg.SetCachePath(cachePath);
40
41 var resourcePath = Path.Combine(asmDir, "resources");
42 cfg.SetResourcePath(resourcePath);
43
44 cfg.SetUseGpuRenderer(false);
45 cfg.SetEnableImages(true);
46 cfg.SetEnableJavaScript(true);
47
48 Safe.AppCore.EnablePlatformFontLoader();
49
50 {
51 var assetsPath = Path.Combine(asmDir, "assets");
52 Safe.AppCore.EnablePlatformFileSystem(assetsPath);
53 }
54
55 using var renderer = new Safe.Renderer(cfg);
56 var sessionName = "Demo";
57 using var session = new Safe.Session(renderer, false, sessionName);
58
59 using var view = new Safe.View(renderer, 640, 480, false, session);
60
61 //var htmlString = "<i>Loading...</i>";
62 //Console.WriteLine($"Loading HTML: {htmlString}");
63 //view.LoadHtml(htmlString);
64 view.LoadUrl("file:///index.html");
65
66 var nextStep = false;
67
68 view.SetFinishLoadingCallback((data, caller, frameId, isMainFrame, url) => {
69 //Console.WriteLine($"Loading Finished, URL: {url}");
70
71 nextStep = true;
72 }, default);
73
74 if (isNotInteractive)
75 do {
76 renderer.Update();
77 renderer.Render();
78 } while (nextStep == false);
79
80 const double wm = 4;
81 const double hm = wm * 2;
82
83 GetConsoleSize(out var cw, out var ch);
84 view.Resize((uint) (cw * wm), (uint) (ch * hm));
85
86 var ditherIndex = 0;
87 var customDither = Dithers[ditherIndex];
88
89 var ansiColors = (AnsiColors) 0;
90
91 var step = 0;
92 var escInstrBytes = Encoding.UTF8.GetBytes("Press C to change color mode, D to change dither mode, ESC to exit.");
93 var key = new ConsoleKeyInfo();
94 //Console.TreatControlCAsInput = true;
95
96 using var stdOut = Console.OpenStandardOutput(256);
97 using var o = new BufferedStream(stdOut, 8 * 1024 * 1024);
98
99 byte[]? urlStrBytes = null;
100 // alt buffer
101 if (!isNotInteractive) {
102 o.WriteByte(0x1B);
103 o.WriteByte((byte) '[');
104 o.WriteByte((byte) '1');
105 o.WriteByte((byte) '0');
106 o.WriteByte((byte) '4');
107 o.WriteByte((byte) '9');
108 o.WriteByte((byte) 'h');
109 o.Flush();
110 }
111
112 try { Console.CursorVisible = false; }
113 catch {
114 /* ok */
115 }
116
117 try { Console.Clear(); }
118 catch {
119 /* ok */
120 }
121
122 try {
123 Console.SetCursorPosition(0, 0);
124 }
125 catch {
126 o.WriteByte(0x1B);
127 o.WriteByte((byte) '7');
128 }
129
130 do {
131 GetConsoleSize(out var newCw, out var newCh);
132 if (cw != newCw || ch != newCh) {
133 cw = newCw;
134 ch = newCh;
135
136 try { Console.Clear(); }
137 catch {
138 /* ok */
139 }
140
141 view.Resize((uint) (cw * wm), (uint) (ch * hm));
142 }
143
144 try {
145 Console.SetCursorPosition(0, 0);
146 }
147 catch {
148 o.WriteByte(0x1B);
149 o.WriteByte((byte) '8');
150 }
151
152 var urlStr = view.GetUrl();
153 var urlStrSize = Encoding.UTF8.GetByteCount(urlStr ?? "");
154 if (urlStrBytes == null || urlStrBytes.Length < urlStrSize)
155 urlStrBytes = new byte[urlStrSize];
156 var urlStrBytesLen = Encoding.UTF8.GetBytes(urlStr, urlStrBytes);
157 o.Write(urlStrBytes, 0, urlStrBytesLen);
158 o.WriteByte((byte) '\n');
159
160 renderer.Update();
161 renderer.Render();
162
163 var surface = view.GetSurface();
164 var bitmap = surface.GetBitmap();
165 var pixels = bitmap.LockPixels();
166 RenderAnsi<Bgra32>(o, pixels,
167 bitmap.GetWidth(), bitmap.GetHeight(),
168 2,
169 borderless: true,
170 colors: ansiColors, // isNotInteractive
171 customDither: customDither
172 );
173 bitmap.UnlockPixels();
174
175 if (isNotInteractive)
176 return;
177
178 o.Write(escInstrBytes);
179 o.Flush();
180
181 if (nextStep)
182 switch (step) {
183 case 0: {
184 view.EvaluateScript("rotateBgColor()");
185 nextStep = false;
186 ++step;
187 // ReSharper disable once ObjectCreationAsStatement
188 new Timer(state => {
189 nextStep = true;
190 }, null, 10000, Timeout.Infinite);
191 break;
192 }
193 case 1: {
194 var urlString = "https://cristurm.github.io/nyan-cat/";
195 view.LoadUrl(urlString);
196 nextStep = false;
197 ++step;
198 break;
199 }
200 case 2:
201 view.EvaluateScript(
202 "const m = document.createElement('marquee');"
203 + "const d = document.createElement('div');"
204 + "document.body.appendChild(d);"
205 + "d.appendChild(m);"
206 + "m.setAttribute('width', '100%');"
207 + "m.setAttribute('scrollamount', 4);"
208 + "m.setAttribute('scrolldelay', 1);"
209 + "m.setAttribute('truespeed', 1);"
210 + "m.innerText = 'UltralightSharp';"
211 + "m.style.cssText = '"
212 + "position: absolute;"
213 + "bottom: 0;"
214 + "left: 0;"
215 + "z-index: 999;"
216 + "padding: 4px 8px;"
217 + "font-size: 2.5em;"
218 + "font-weight: bold;"
219 + "width: 100vw;"
220 + "color: #fff"
221 + "';");
222 nextStep = false;
223 ++step;
224 break;
225 }
226
227 if (!Console.KeyAvailable)
228 continue;
229
230 key = Console.ReadKey(true);
231 Console.WriteLine();
232 if (key.Key == ConsoleKey.D) {
233 ditherIndex = (ditherIndex + 1) % Dithers.Length;
234 customDither = Dithers[ditherIndex];
235 continue;
236 }
237
238 if (key.Key == ConsoleKey.C) {
239 ansiColors = (AnsiColors) (((int) ansiColors + 1) % 3);
240 continue;
241 }
242 } while (key.Key != ConsoleKey.Escape);
243 }
244
245 try {
246 Directory.Delete(storagePath, true);
247 }
248 catch {
249 /* ok */
250 }
251 }
252
253 private static void LoggerCallback(LogLevel logLevel, string? msg) {
254 Debug.WriteLine($"{logLevel.ToString()}: {msg}");
255 }
256
257 }
258
259}