nanovgjs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/example.js ADDED
@@ -0,0 +1,1386 @@
1
+ let ctx = null;
2
+ let premult = 0;
3
+ let canvas = null;
4
+ let gl = null;
5
+ let mx = null;
6
+ let my = null;
7
+ let demoFontsReady = false;
8
+ let demoFontsLoading = null;
9
+ let demoImages = [];
10
+ let perfGraph = {
11
+ style: 0,
12
+ name: "Frame Time",
13
+ values: new Array(100).fill(0),
14
+ head: 0
15
+ };
16
+ let prevFrameTime = 0;
17
+ let inputControls = [];
18
+ let inputMeta = {};
19
+ let inputState = {
20
+ activeId: null,
21
+ caret: 0,
22
+ blink: 0,
23
+ values: {
24
+ "login:email": "",
25
+ "login:password": "",
26
+ "diameter": "123.00",
27
+ },
28
+ };
29
+ const ICON_SEARCH = 0x1F50D;
30
+ const ICON_CIRCLED_CROSS = 0x2716;
31
+ const ICON_CHEVRON_RIGHT = 0xE75E;
32
+ const ICON_CHECK = 0x2713;
33
+ const ICON_LOGIN = 0xE740;
34
+ const ICON_TRASH = 0xE729;
35
+ const GRAPH_RENDER_FPS = 0;
36
+ const GRAPH_HISTORY_COUNT = 100;
37
+
38
+ function renderOriginalDemo(mx, my, width, height, t, blowup) {
39
+ drawEyes(width - 250, 50, 150, 100, mx, my, t);
40
+ drawParagraph(width - 450, 50, 150, 100, mx, my);
41
+ drawGraph(0, height / 2, width, height / 2, t);
42
+ drawColorwheel(width - 300, height - 300, 250, 250, t);
43
+ drawLines(120, height - 50, 600, 50, t);
44
+ drawWidths(10, 50, 30);
45
+ drawCaps(10, 300, 30);
46
+ drawScissor(50, height - 80, t);
47
+ ctx.nvgSave();
48
+ if (blowup) {
49
+ ctx.nvgRotate(Math.sin(t * 0.3) * 5.0 / 180.0 * Math.PI);
50
+ ctx.nvgScale(2.0, 2.0);
51
+ }
52
+ drawWidgetDemo(50, 50, t);
53
+ ctx.nvgRestore();
54
+ }
55
+
56
+ function ensureDemoFonts() {
57
+ if (!ctx) return Promise.resolve(false);
58
+ if (demoFontsReady) return Promise.resolve(true);
59
+ if (demoFontsLoading) return demoFontsLoading;
60
+
61
+ const sansId = ctx.nvgCreateFont("sans", "example/Roboto-Regular.ttf");
62
+ const boldId = ctx.nvgCreateFont("sans-bold", "example/Roboto-Bold.ttf");
63
+ const iconsId = ctx.nvgCreateFont("icons", "example/entypo.ttf");
64
+ const fontIds = [sansId, boldId, iconsId].filter((id) => id >= 0);
65
+ const loaders = fontIds
66
+ .map((id) => ctx.fs && ctx.fs.fonts[id] ? ctx.fs.fonts[id].loading : null)
67
+ .filter(Boolean);
68
+
69
+ demoFontsLoading = Promise.all(loaders).then(() => {
70
+ demoFontsReady = true;
71
+ demoFontsLoading = null;
72
+ return true;
73
+ }).catch(() => {
74
+ demoFontsLoading = null;
75
+ return false;
76
+ });
77
+
78
+ return demoFontsLoading;
79
+ }
80
+
81
+ function clampf(v, mn, mx) {
82
+ return Math.max(mn, Math.min(mx, v));
83
+ }
84
+
85
+ function pointInRect(px, py, x, y, w, h) {
86
+ return px >= x && px <= x + w && py >= y && py <= y + h;
87
+ }
88
+
89
+ function beginInputFrame(now) {
90
+ inputControls = [];
91
+ inputMeta = {};
92
+ inputState.blink = now;
93
+ }
94
+
95
+ function registerInput(control) {
96
+ inputControls.push(control);
97
+ inputMeta[control.id] = control;
98
+ }
99
+
100
+ function hitInputControl(px, py) {
101
+ for (let i = inputControls.length - 1; i >= 0; i--) {
102
+ const c = inputControls[i];
103
+ if (pointInRect(px, py, c.x, c.y, c.w, c.h)) return c;
104
+ }
105
+ return null;
106
+ }
107
+
108
+ function setActiveInput(control) {
109
+ if (!control) {
110
+ inputState.activeId = null;
111
+ return;
112
+ }
113
+ inputState.activeId = control.id;
114
+ const value = inputState.values[control.id] ?? "";
115
+ inputState.caret = value.length;
116
+ }
117
+
118
+ function insertInputText(id, textValue) {
119
+ const value = inputState.values[id] ?? "";
120
+ const caret = inputState.caret;
121
+ inputState.values[id] = value.slice(0, caret) + textValue + value.slice(caret);
122
+ inputState.caret = caret + textValue.length;
123
+ }
124
+
125
+ function deleteInputLeft(id) {
126
+ const value = inputState.values[id] ?? "";
127
+ const caret = inputState.caret;
128
+ if (caret <= 0) return;
129
+ inputState.values[id] = value.slice(0, caret - 1) + value.slice(caret);
130
+ inputState.caret = caret - 1;
131
+ }
132
+
133
+ function deleteInputRight(id) {
134
+ const value = inputState.values[id] ?? "";
135
+ const caret = inputState.caret;
136
+ if (caret >= value.length) return;
137
+ inputState.values[id] = value.slice(0, caret) + value.slice(caret + 1);
138
+ }
139
+
140
+ function adjustNumericInput(control, dir, scale = 1) {
141
+ const id = control.id;
142
+ const step = control.step ?? 1;
143
+ const decimals = control.decimals ?? 0;
144
+ const min = control.min ?? -Infinity;
145
+ const max = control.max ?? Infinity;
146
+ const current = parseFloat(inputState.values[id] ?? "0");
147
+ const base = Number.isFinite(current) ? current : 0;
148
+ const next = clampf(base + step * dir * scale, min, max);
149
+ inputState.values[id] = decimals > 0 ? next.toFixed(decimals) : `${Math.round(next)}`;
150
+ inputState.caret = inputState.values[id].length;
151
+ }
152
+
153
+ function handleInputKeydown(event) {
154
+ const id = inputState.activeId;
155
+ if (!id) return;
156
+ const control = inputMeta[id];
157
+ if (!control) return;
158
+
159
+ if (event.key === "Escape") {
160
+ inputState.activeId = null;
161
+ event.preventDefault();
162
+ return;
163
+ }
164
+
165
+ if (control.type === "number" && (event.key === "ArrowUp" || event.key === "ArrowDown")) {
166
+ adjustNumericInput(control, event.key === "ArrowUp" ? 1 : -1, event.shiftKey ? 10 : 1);
167
+ event.preventDefault();
168
+ return;
169
+ }
170
+
171
+ if (event.key === "ArrowLeft") {
172
+ inputState.caret = Math.max(0, inputState.caret - 1);
173
+ event.preventDefault();
174
+ return;
175
+ }
176
+
177
+ if (event.key === "ArrowRight") {
178
+ const value = inputState.values[id] ?? "";
179
+ inputState.caret = Math.min(value.length, inputState.caret + 1);
180
+ event.preventDefault();
181
+ return;
182
+ }
183
+
184
+ if (event.key === "Home") {
185
+ inputState.caret = 0;
186
+ event.preventDefault();
187
+ return;
188
+ }
189
+
190
+ if (event.key === "End") {
191
+ const value = inputState.values[id] ?? "";
192
+ inputState.caret = value.length;
193
+ event.preventDefault();
194
+ return;
195
+ }
196
+
197
+ if (event.key === "Backspace") {
198
+ deleteInputLeft(id);
199
+ event.preventDefault();
200
+ return;
201
+ }
202
+
203
+ if (event.key === "Delete") {
204
+ deleteInputRight(id);
205
+ event.preventDefault();
206
+ return;
207
+ }
208
+
209
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
210
+ if (control.type === "number") {
211
+ const value = inputState.values[id] ?? "";
212
+ if (event.key === "-" && inputState.caret === 0 && !value.includes("-")) {
213
+ insertInputText(id, event.key);
214
+ event.preventDefault();
215
+ } else if (event.key === "." && !value.includes(".")) {
216
+ insertInputText(id, event.key);
217
+ event.preventDefault();
218
+ } else if ("0123456789".includes(event.key)) {
219
+ insertInputText(id, event.key);
220
+ event.preventDefault();
221
+ }
222
+ return;
223
+ }
224
+ insertInputText(id, event.key);
225
+ event.preventDefault();
226
+ }
227
+ }
228
+
229
+ function updateGraph(graph, frameTime) {
230
+ graph.head = (graph.head + 1) % GRAPH_HISTORY_COUNT;
231
+ graph.values[graph.head] = frameTime;
232
+ }
233
+
234
+ function getGraphAverage(graph) {
235
+ let avg = 0;
236
+ for (let i = 0; i < GRAPH_HISTORY_COUNT; i++) avg += graph.values[i];
237
+ return avg / GRAPH_HISTORY_COUNT;
238
+ }
239
+
240
+ function renderGraph(x, y, graph) {
241
+ const avg = getGraphAverage(graph);
242
+ const w = 200;
243
+ const h = 35;
244
+
245
+ ctx.nvgBeginPath();
246
+ ctx.nvgRect(x, y, w, h);
247
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(0, 0, 0, 128));
248
+ ctx.nvgFill();
249
+
250
+ ctx.nvgBeginPath();
251
+ for (let i = 0; i < GRAPH_HISTORY_COUNT; i++) {
252
+ let v = 1.0 / (0.00001 + graph.values[(graph.head + i) % GRAPH_HISTORY_COUNT]);
253
+ if (v > 80.0) v = 80.0;
254
+ const vx = x + (i / (GRAPH_HISTORY_COUNT - 1)) * w;
255
+ const vy = y + h - (v / 80.0) * h;
256
+ if (i === 0) ctx.nvgMoveTo(vx, vy);
257
+ else ctx.nvgLineTo(vx, vy);
258
+ }
259
+ ctx.nvgLineTo(x + w, y + h);
260
+ ctx.nvgLineTo(x, y + h);
261
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 192, 0, 128));
262
+ ctx.nvgFill();
263
+
264
+ ctx.nvgFontFace("sans");
265
+ ctx.nvgFontSize(12.0);
266
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
267
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(240, 240, 240, 192));
268
+ ctx.nvgText(x + 3, y + 3, graph.name, null);
269
+
270
+ ctx.nvgFontSize(15.0);
271
+ ctx.nvgTextAlign(NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
272
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(240, 240, 240, 255));
273
+ ctx.nvgText(x + w - 3, y + 3, `${(1.0 / Math.max(avg, 0.00001)).toFixed(2)} FPS`, null);
274
+
275
+ ctx.nvgFontSize(13.0);
276
+ ctx.nvgTextAlign(NVG_ALIGN_RIGHT | NVG_ALIGN_BASELINE);
277
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(240, 240, 240, 160));
278
+ ctx.nvgText(x + w - 3, y + h - 3, `${(avg * 1000.0).toFixed(2)} ms`, null);
279
+ }
280
+
281
+ function loadImageTexture(path) {
282
+ return new Promise((resolve) => {
283
+ const img = new Image();
284
+ img.crossOrigin = "anonymous";
285
+ img.onload = function() {
286
+ const id = ctx.params.renderCreateTexture(
287
+ ctx.params.userPtr,
288
+ NVG_TEXTURE_RGBA,
289
+ img.naturalWidth,
290
+ img.naturalHeight,
291
+ 0,
292
+ img
293
+ );
294
+ resolve(id);
295
+ };
296
+ img.onerror = function() {
297
+ resolve(0);
298
+ };
299
+ img.src = path;
300
+ });
301
+ }
302
+
303
+ function loadDemoImages() {
304
+ if (!ctx) return;
305
+ demoImages = new Array(12).fill(0);
306
+ for (let i = 0; i < 12; i++) {
307
+ const index = i;
308
+ loadImageTexture(`example/images/image${i + 1}.jpg`).then((id) => {
309
+ demoImages[index] = id;
310
+ });
311
+ }
312
+ }
313
+
314
+ function cpToUTF8(cp) {
315
+ return String.fromCodePoint(cp);
316
+ }
317
+
318
+ function isBlack(col) {
319
+ return col.r === 0 && col.g === 0 && col.b === 0 && col.a === 0;
320
+ }
321
+
322
+ function drawWindow(title, x, y, w, h) {
323
+ const cornerRadius = 3.0;
324
+ ctx.nvgSave();
325
+ ctx.nvgBeginPath();
326
+ ctx.nvgRoundedRect(x, y, w, h, cornerRadius);
327
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(28, 30, 34, 192));
328
+ ctx.nvgFill();
329
+
330
+ let shadowPaint = ctx.nvgBoxGradient(x, y + 2, w, h, cornerRadius * 2, 10, NVGcolor.nvgRGBA(0, 0, 0, 128), NVGcolor.nvgRGBA(0, 0, 0, 0));
331
+ ctx.nvgBeginPath();
332
+ ctx.nvgRect(x - 10, y - 10, w + 20, h + 30);
333
+ ctx.nvgRoundedRect(x, y, w, h, cornerRadius);
334
+ ctx.nvgPathWinding(NVG_HOLE);
335
+ ctx.nvgFillPaint(shadowPaint);
336
+ ctx.nvgFill();
337
+
338
+ let headerPaint = ctx.nvgLinearGradient(x, y, x, y + 15, NVGcolor.nvgRGBA(255, 255, 255, 8), NVGcolor.nvgRGBA(0, 0, 0, 16));
339
+ ctx.nvgBeginPath();
340
+ ctx.nvgRoundedRect(x + 1, y + 1, w - 2, 30, cornerRadius - 1);
341
+ ctx.nvgFillPaint(headerPaint);
342
+ ctx.nvgFill();
343
+ ctx.nvgBeginPath();
344
+ ctx.nvgMoveTo(x + 0.5, y + 30.5);
345
+ ctx.nvgLineTo(x + w - 0.5, y + 30.5);
346
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 32));
347
+ ctx.nvgStroke();
348
+
349
+ ctx.nvgFontSize(15);
350
+ ctx.nvgFontFace("sans-bold");
351
+ ctx.nvgTextAlign(NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
352
+ ctx.nvgFontBlur(2);
353
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(0, 0, 0, 128));
354
+ ctx.nvgText(x + w * 0.5, y + 17, title, null);
355
+ ctx.nvgFontBlur(0);
356
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(220, 220, 220, 160));
357
+ ctx.nvgText(x + w * 0.5, y + 16, title, null);
358
+ ctx.nvgRestore();
359
+ }
360
+
361
+ function drawSearchBox(text, x, y, w, h) {
362
+ const cornerRadius = h * 0.5 - 1;
363
+ const bg = ctx.nvgBoxGradient(x, y + 1.5, w, h, h * 0.5, 5, NVGcolor.nvgRGBA(0, 0, 0, 16), NVGcolor.nvgRGBA(0, 0, 0, 92));
364
+ ctx.nvgBeginPath();
365
+ ctx.nvgRoundedRect(x, y, w, h, cornerRadius);
366
+ ctx.nvgFillPaint(bg);
367
+ ctx.nvgFill();
368
+
369
+ ctx.nvgFontSize(h * 1.3);
370
+ ctx.nvgFontFace("icons");
371
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 64));
372
+ ctx.nvgTextAlign(NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
373
+ ctx.nvgText(x + h * 0.55, y + h * 0.55, cpToUTF8(ICON_SEARCH), null);
374
+
375
+ ctx.nvgFontSize(17);
376
+ ctx.nvgFontFace("sans");
377
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 32));
378
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
379
+ ctx.nvgText(x + h * 1.05, y + h * 0.5, text, null);
380
+
381
+ ctx.nvgFontSize(h * 1.3);
382
+ ctx.nvgFontFace("icons");
383
+ ctx.nvgTextAlign(NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
384
+ ctx.nvgText(x + w - h * 0.55, y + h * 0.55, cpToUTF8(ICON_CIRCLED_CROSS), null);
385
+ }
386
+
387
+ function drawDropDown(text, x, y, w, h) {
388
+ const cornerRadius = 4.0;
389
+ const bg = ctx.nvgLinearGradient(x, y, x, y + h, NVGcolor.nvgRGBA(255, 255, 255, 16), NVGcolor.nvgRGBA(0, 0, 0, 16));
390
+ ctx.nvgBeginPath();
391
+ ctx.nvgRoundedRect(x + 1, y + 1, w - 2, h - 2, cornerRadius - 1);
392
+ ctx.nvgFillPaint(bg);
393
+ ctx.nvgFill();
394
+ ctx.nvgBeginPath();
395
+ ctx.nvgRoundedRect(x + 0.5, y + 0.5, w - 1, h - 1, cornerRadius - 0.5);
396
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 48));
397
+ ctx.nvgStroke();
398
+
399
+ ctx.nvgFontSize(17);
400
+ ctx.nvgFontFace("sans");
401
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 160));
402
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
403
+ ctx.nvgText(x + h * 0.3, y + h * 0.5, text, null);
404
+
405
+ ctx.nvgFontSize(h * 1.3);
406
+ ctx.nvgFontFace("icons");
407
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 64));
408
+ ctx.nvgTextAlign(NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
409
+ ctx.nvgText(x + w - h * 0.5, y + h * 0.5, cpToUTF8(ICON_CHEVRON_RIGHT), null);
410
+ }
411
+
412
+ function drawLabel(text, x, y, w, h) {
413
+ ctx.nvgFontSize(15);
414
+ ctx.nvgFontFace("sans");
415
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 128));
416
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
417
+ ctx.nvgText(x, y + h * 0.5, text, null);
418
+ }
419
+
420
+ function drawEditBoxBase(x, y, w, h, strokeColor = null) {
421
+ const bg = ctx.nvgBoxGradient(x + 1, y + 2.5, w - 2, h - 2, 3, 4, NVGcolor.nvgRGBA(255, 255, 255, 32), NVGcolor.nvgRGBA(32, 32, 32, 32));
422
+ ctx.nvgBeginPath();
423
+ ctx.nvgRoundedRect(x + 1, y + 1, w - 2, h - 2, 3);
424
+ ctx.nvgFillPaint(bg);
425
+ ctx.nvgFill();
426
+ ctx.nvgBeginPath();
427
+ ctx.nvgRoundedRect(x + 0.5, y + 0.5, w - 1, h - 1, 3.5);
428
+ ctx.nvgStrokeColor(strokeColor ?? NVGcolor.nvgRGBA(0, 0, 0, 48));
429
+ ctx.nvgStroke();
430
+ }
431
+
432
+ function drawEditBox(id, placeholder, x, y, w, h, options = {}) {
433
+ const isActive = inputState.activeId === id;
434
+ drawEditBoxBase(x, y, w, h, isActive ? NVGcolor.nvgRGBA(126, 188, 255, 160) : null);
435
+ const value = inputState.values[id] ?? "";
436
+ const display = value.length ?
437
+ (options.password ? "•".repeat(value.length) : value) :
438
+ placeholder;
439
+ ctx.nvgFontSize(17);
440
+ ctx.nvgFontFace("sans");
441
+ ctx.nvgFillColor(value.length ? NVGcolor.nvgRGBA(255, 255, 255, 160) : NVGcolor.nvgRGBA(255, 255, 255, 64));
442
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
443
+ ctx.nvgText(x + h * 0.3, y + h * 0.5, display, null);
444
+
445
+ if (isActive && Math.floor(inputState.blink * 2) % 2 === 0) {
446
+ const caretText = (options.password ? "•".repeat(value.length) : value).slice(0, inputState.caret);
447
+ const caretW = caretText ? ctx.nvgTextBounds(0, 0, caretText, null, null) : 0;
448
+ const caretX = Math.min(x + w - 6, x + h * 0.3 + caretW);
449
+ ctx.nvgBeginPath();
450
+ ctx.nvgMoveTo(caretX, y + 5);
451
+ ctx.nvgLineTo(caretX, y + h - 5);
452
+ ctx.nvgStrokeWidth(1);
453
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(240, 240, 240, 200));
454
+ ctx.nvgStroke();
455
+ }
456
+
457
+ registerInput({
458
+ id,
459
+ type: options.password ? "password" : "text",
460
+ x,
461
+ y,
462
+ w,
463
+ h
464
+ });
465
+ }
466
+
467
+ function drawEditBoxNum(id, units, x, y, w, h, options = {}) {
468
+ const isActive = inputState.activeId === id;
469
+ drawEditBoxBase(x, y, w, h, isActive ? NVGcolor.nvgRGBA(126, 188, 255, 160) : null);
470
+ ctx.nvgFontSize(15);
471
+ ctx.nvgFontFace("sans");
472
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 64));
473
+ ctx.nvgTextAlign(NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
474
+ const unitsW = ctx.nvgTextBounds(0, 0, units, null, null);
475
+ ctx.nvgText(x + w - h * 0.3, y + h * 0.5, units, null);
476
+ ctx.nvgFontSize(17);
477
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 128));
478
+ const value = inputState.values[id] ?? "";
479
+ const showValue = value.length ? value : "0";
480
+ const textRight = x + w - unitsW - h * 0.5;
481
+ ctx.nvgTextAlign(NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
482
+ ctx.nvgText(textRight, y + h * 0.5, showValue, null);
483
+
484
+ if (isActive && Math.floor(inputState.blink * 2) % 2 === 0) {
485
+ const valueW = showValue ? ctx.nvgTextBounds(0, 0, showValue, null, null) : 0;
486
+ const caretText = showValue.slice(0, inputState.caret);
487
+ const caretW = caretText ? ctx.nvgTextBounds(0, 0, caretText, null, null) : 0;
488
+ const textLeft = textRight - valueW;
489
+ const caretX = Math.min(x + w - unitsW - 6, textLeft + caretW);
490
+ ctx.nvgBeginPath();
491
+ ctx.nvgMoveTo(caretX, y + 5);
492
+ ctx.nvgLineTo(caretX, y + h - 5);
493
+ ctx.nvgStrokeWidth(1);
494
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(240, 240, 240, 200));
495
+ ctx.nvgStroke();
496
+ }
497
+
498
+ registerInput({
499
+ id,
500
+ type: "number",
501
+ x,
502
+ y,
503
+ w,
504
+ h,
505
+ step: options.step ?? 1,
506
+ min: options.min ?? -Infinity,
507
+ max: options.max ?? Infinity,
508
+ decimals: options.decimals ?? 0,
509
+ });
510
+ }
511
+
512
+ function drawCheckBox(text, x, y, w, h) {
513
+ drawLabel(text, x + 28, y, w, h);
514
+ const bg = ctx.nvgBoxGradient(x + 1, y + Math.floor(h * 0.5) - 8, 18, 18, 3, 3, NVGcolor.nvgRGBA(0, 0, 0, 32), NVGcolor.nvgRGBA(0, 0, 0, 92));
515
+ ctx.nvgBeginPath();
516
+ ctx.nvgRoundedRect(x + 1, y + Math.floor(h * 0.5) - 9, 18, 18, 3);
517
+ ctx.nvgFillPaint(bg);
518
+ ctx.nvgFill();
519
+ ctx.nvgFontSize(33);
520
+ ctx.nvgFontFace("icons");
521
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 128));
522
+ ctx.nvgTextAlign(NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
523
+ ctx.nvgText(x + 11, y + h * 0.5, cpToUTF8(ICON_CHECK), null);
524
+ }
525
+
526
+ function drawButton(preicon, text, x, y, w, h, col) {
527
+ const cornerRadius = 4.0;
528
+ const bg = ctx.nvgLinearGradient(x, y, x, y + h, NVGcolor.nvgRGBA(255, 255, 255, isBlack(col) ? 16 : 32), NVGcolor.nvgRGBA(0, 0, 0, isBlack(col) ? 16 : 32));
529
+ ctx.nvgBeginPath();
530
+ ctx.nvgRoundedRect(x + 1, y + 1, w - 2, h - 2, cornerRadius - 1);
531
+ if (!isBlack(col)) {
532
+ ctx.nvgFillColor(col);
533
+ ctx.nvgFill();
534
+ }
535
+ ctx.nvgFillPaint(bg);
536
+ ctx.nvgFill();
537
+ ctx.nvgBeginPath();
538
+ ctx.nvgRoundedRect(x + 0.5, y + 0.5, w - 1, h - 1, cornerRadius - 0.5);
539
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 48));
540
+ ctx.nvgStroke();
541
+
542
+ ctx.nvgFontSize(17);
543
+ ctx.nvgFontFace("sans-bold");
544
+ const tw = ctx.nvgTextBounds(0, 0, text, null, null);
545
+ let iw = 0;
546
+ if (preicon !== 0) {
547
+ ctx.nvgFontSize(h * 1.3);
548
+ ctx.nvgFontFace("icons");
549
+ iw = ctx.nvgTextBounds(0, 0, cpToUTF8(preicon), null, null) + h * 0.15;
550
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 96));
551
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
552
+ ctx.nvgText(x + w * 0.5 - tw * 0.5 - iw * 0.75, y + h * 0.5, cpToUTF8(preicon), null);
553
+ }
554
+
555
+ ctx.nvgFontSize(17);
556
+ ctx.nvgFontFace("sans-bold");
557
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
558
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(0, 0, 0, 160));
559
+ ctx.nvgText(x + w * 0.5 - tw * 0.5 + iw * 0.25, y + h * 0.5 - 1, text, null);
560
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 160));
561
+ ctx.nvgText(x + w * 0.5 - tw * 0.5 + iw * 0.25, y + h * 0.5, text, null);
562
+ }
563
+
564
+ function drawSlider(pos, x, y, w, h) {
565
+ const cy = y + Math.floor(h * 0.5);
566
+ const kr = Math.floor(h * 0.25);
567
+ ctx.nvgSave();
568
+ let bg = ctx.nvgBoxGradient(x, cy - 1, w, 4, 2, 2, NVGcolor.nvgRGBA(0, 0, 0, 32), NVGcolor.nvgRGBA(0, 0, 0, 128));
569
+ ctx.nvgBeginPath();
570
+ ctx.nvgRoundedRect(x, cy - 2, w, 4, 2);
571
+ ctx.nvgFillPaint(bg);
572
+ ctx.nvgFill();
573
+
574
+ bg = ctx.nvgRadialGradient(x + Math.floor(pos * w), cy + 1, kr - 3, kr + 3, NVGcolor.nvgRGBA(0, 0, 0, 64), NVGcolor.nvgRGBA(0, 0, 0, 0));
575
+ ctx.nvgBeginPath();
576
+ ctx.nvgRect(x + Math.floor(pos * w) - kr - 5, cy - kr - 5, kr * 2 + 10, kr * 2 + 13);
577
+ ctx.nvgCircle(x + Math.floor(pos * w), cy, kr);
578
+ ctx.nvgPathWinding(NVG_HOLE);
579
+ ctx.nvgFillPaint(bg);
580
+ ctx.nvgFill();
581
+
582
+ let knob = ctx.nvgLinearGradient(x, cy - kr, x, cy + kr, NVGcolor.nvgRGBA(255, 255, 255, 16), NVGcolor.nvgRGBA(0, 0, 0, 16));
583
+ ctx.nvgBeginPath();
584
+ ctx.nvgCircle(x + Math.floor(pos * w), cy, kr - 1);
585
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(40, 43, 48, 255));
586
+ ctx.nvgFill();
587
+ ctx.nvgFillPaint(knob);
588
+ ctx.nvgFill();
589
+ ctx.nvgBeginPath();
590
+ ctx.nvgCircle(x + Math.floor(pos * w), cy, kr - 0.5);
591
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 92));
592
+ ctx.nvgStroke();
593
+ ctx.nvgRestore();
594
+ }
595
+
596
+ function drawThumbnails(x, y, w, h, images, nimages, t) {
597
+ const cornerRadius = 3.0;
598
+ const thumb = 60.0;
599
+ const arry = 30.5;
600
+ const stackh = (nimages / 2) * (thumb + 10) + 10;
601
+ const u = (1 + Math.cos(t * 0.5)) * 0.5;
602
+ const u2 = (1 - Math.cos(t * 0.2)) * 0.5;
603
+
604
+ ctx.nvgSave();
605
+ let shadowPaint = ctx.nvgBoxGradient(x, y + 4, w, h, cornerRadius * 2, 20, NVGcolor.nvgRGBA(0, 0, 0, 128), NVGcolor.nvgRGBA(0, 0, 0, 0));
606
+ ctx.nvgBeginPath();
607
+ ctx.nvgRect(x - 10, y - 10, w + 20, h + 30);
608
+ ctx.nvgRoundedRect(x, y, w, h, cornerRadius);
609
+ ctx.nvgPathWinding(NVG_HOLE);
610
+ ctx.nvgFillPaint(shadowPaint);
611
+ ctx.nvgFill();
612
+
613
+ ctx.nvgBeginPath();
614
+ ctx.nvgRoundedRect(x, y, w, h, cornerRadius);
615
+ ctx.nvgMoveTo(x - 10, y + arry);
616
+ ctx.nvgLineTo(x + 1, y + arry - 11);
617
+ ctx.nvgLineTo(x + 1, y + arry + 11);
618
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(200, 200, 200, 255));
619
+ ctx.nvgFill();
620
+
621
+ ctx.nvgSave();
622
+ ctx.nvgScissor(x, y, w, h);
623
+ ctx.nvgTranslate(0, -(stackh - h) * u);
624
+
625
+ const dv = nimages > 1 ? 1.0 / (nimages - 1) : 1.0;
626
+ for (let i = 0; i < nimages; i++) {
627
+ const imageId = images[i];
628
+ let tx = x + 10 + (i % 2) * (thumb + 10);
629
+ let ty = y + 10 + Math.floor(i / 2) * (thumb + 10);
630
+ let ix = 0;
631
+ let iy = 0;
632
+ let iw = thumb;
633
+ let ih = thumb;
634
+ let imgw = thumb;
635
+ let imgh = thumb;
636
+ if (imageId) {
637
+ [imgw, imgh] = ctx.nvgImageSize(imageId);
638
+ if (imgw > 0 && imgh > 0) {
639
+ if (imgw < imgh) {
640
+ iw = thumb;
641
+ ih = iw * imgh / imgw;
642
+ iy = -(ih - thumb) * 0.5;
643
+ } else {
644
+ ih = thumb;
645
+ iw = ih * imgw / imgh;
646
+ ix = -(iw - thumb) * 0.5;
647
+ }
648
+ }
649
+ }
650
+
651
+ const v = i * dv;
652
+ const a = Math.max(0, Math.min(1, (u2 - v) / dv));
653
+ if (a < 1.0) {
654
+ drawSpinner(tx + thumb * 0.5, ty + thumb * 0.5, thumb * 0.25, t);
655
+ }
656
+
657
+ if (imageId) {
658
+ const imgPaint = ctx.nvgImagePattern(tx + ix, ty + iy, iw, ih, 0, imageId, a);
659
+ ctx.nvgBeginPath();
660
+ ctx.nvgRoundedRect(tx, ty, thumb, thumb, 5);
661
+ ctx.nvgFillPaint(imgPaint);
662
+ ctx.nvgFill();
663
+ } else {
664
+ ctx.nvgBeginPath();
665
+ ctx.nvgRoundedRect(tx, ty, thumb, thumb, 5);
666
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(150, 150, 150, 80));
667
+ ctx.nvgFill();
668
+ }
669
+
670
+ shadowPaint = ctx.nvgBoxGradient(tx - 1, ty, thumb + 2, thumb + 2, 5, 3, NVGcolor.nvgRGBA(0, 0, 0, 128), NVGcolor.nvgRGBA(0, 0, 0, 0));
671
+ ctx.nvgBeginPath();
672
+ ctx.nvgRect(tx - 5, ty - 5, thumb + 10, thumb + 10);
673
+ ctx.nvgRoundedRect(tx, ty, thumb, thumb, 6);
674
+ ctx.nvgPathWinding(NVG_HOLE);
675
+ ctx.nvgFillPaint(shadowPaint);
676
+ ctx.nvgFill();
677
+
678
+ ctx.nvgBeginPath();
679
+ ctx.nvgRoundedRect(tx + 0.5, ty + 0.5, thumb - 1, thumb - 1, 3.5);
680
+ ctx.nvgStrokeWidth(1.0);
681
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(255, 255, 255, 192));
682
+ ctx.nvgStroke();
683
+ }
684
+ ctx.nvgRestore();
685
+
686
+ let fadePaint = ctx.nvgLinearGradient(x, y, x, y + 6, NVGcolor.nvgRGBA(200, 200, 200, 255), NVGcolor.nvgRGBA(200, 200, 200, 0));
687
+ ctx.nvgBeginPath();
688
+ ctx.nvgRect(x + 4, y, w - 8, 6);
689
+ ctx.nvgFillPaint(fadePaint);
690
+ ctx.nvgFill();
691
+
692
+ fadePaint = ctx.nvgLinearGradient(x, y + h, x, y + h - 6, NVGcolor.nvgRGBA(200, 200, 200, 255), NVGcolor.nvgRGBA(200, 200, 200, 0));
693
+ ctx.nvgBeginPath();
694
+ ctx.nvgRect(x + 4, y + h - 6, w - 8, 6);
695
+ ctx.nvgFillPaint(fadePaint);
696
+ ctx.nvgFill();
697
+
698
+ shadowPaint = ctx.nvgBoxGradient(x + w - 11, y + 5, 8, h - 8, 3, 4, NVGcolor.nvgRGBA(0, 0, 0, 32), NVGcolor.nvgRGBA(0, 0, 0, 92));
699
+ ctx.nvgBeginPath();
700
+ ctx.nvgRoundedRect(x + w - 12, y + 4, 8, h - 8, 3);
701
+ ctx.nvgFillPaint(shadowPaint);
702
+ ctx.nvgFill();
703
+
704
+ const scrollh = (h / stackh) * (h - 8);
705
+ shadowPaint = ctx.nvgBoxGradient(x + w - 13, y + 4 + (h - 8 - scrollh) * u - 1, 8, scrollh, 3, 4, NVGcolor.nvgRGBA(220, 220, 220, 255), NVGcolor.nvgRGBA(128, 128, 128, 255));
706
+ ctx.nvgBeginPath();
707
+ ctx.nvgRoundedRect(x + w - 11, y + 5 + (h - 8 - scrollh) * u, 6, scrollh - 2, 2);
708
+ ctx.nvgFillPaint(shadowPaint);
709
+ ctx.nvgFill();
710
+ ctx.nvgRestore();
711
+ }
712
+
713
+ function drawWidgetDemo(x, y, t) {
714
+ if (!demoFontsReady) {
715
+ drawWindow("Widgets `n Stuff", x, y, 300, 400);
716
+ return;
717
+ }
718
+ drawWindow("Widgets `n Stuff", x, y, 300, 400);
719
+ drawSearchBox("Search", x + 10, y + 45, 280, 25);
720
+ drawDropDown("Effects", x + 10, y + 85, 280, 28);
721
+ const popy = y + 99;
722
+ let cy = y + 130;
723
+ drawLabel("Login", x + 10, cy, 280, 20);
724
+ cy += 25;
725
+ drawEditBox("login:email", "Email", x + 10, cy, 280, 28);
726
+ cy += 35;
727
+ drawEditBox("login:password", "Password", x + 10, cy, 280, 28, {
728
+ password: true
729
+ });
730
+ cy += 38;
731
+ drawCheckBox("Remember me", x + 10, cy, 140, 28);
732
+ drawButton(ICON_LOGIN, "Sign in", x + 148, cy, 140, 28, NVGcolor.nvgRGBA(0, 96, 128, 255));
733
+ cy += 45;
734
+ drawLabel("Diameter", x + 10, cy, 280, 20);
735
+ cy += 25;
736
+ drawEditBoxNum("diameter", "px", x + 190, cy, 100, 28, {
737
+ step: 1,
738
+ min: 0,
739
+ max: 9999,
740
+ decimals: 2
741
+ });
742
+ drawSlider(0.4, x + 10, cy, 170, 28);
743
+ cy += 55;
744
+ drawButton(ICON_TRASH, "Delete", x + 10, cy, 160, 28, NVGcolor.nvgRGBA(128, 16, 8, 255));
745
+ drawButton(0, "Cancel", x + 180, cy, 110, 28, NVGcolor.nvgRGBA(0, 0, 0, 0));
746
+ drawThumbnails(x + 315, popy - 30, 160, 300, demoImages, 12, t);
747
+ }
748
+
749
+ function drawEyes(x, y, w, h, mx, my, t) {
750
+ let ex = w * 0.23;
751
+ let ey = h * 0.5;
752
+ let lx = x + ex;
753
+ let ly = y + ey;
754
+ let rx = x + w - ex;
755
+ let ry = y + ey;
756
+ let br = (ex < ey ? ex : ey) * 0.5;
757
+ let blink = 1 - Math.pow(Math.sin(t * 0.5), 200) * 0.8;
758
+
759
+ let bg = ctx.nvgLinearGradient(x, y + h * 0.5, x + w * 0.1, y + h, NVGcolor.nvgRGBA(0, 0, 0, 32), NVGcolor.nvgRGBA(0, 0, 0, 16));
760
+ ctx.nvgBeginPath();
761
+ ctx.nvgEllipse(lx + 3.0, ly + 16.0, ex, ey);
762
+ ctx.nvgEllipse(rx + 3.0, ry + 16.0, ex, ey);
763
+ ctx.nvgFillPaint(bg);
764
+ ctx.nvgFill();
765
+
766
+ bg = ctx.nvgLinearGradient(x, y + h * 0.25, x + w * 0.1, y + h, NVGcolor.nvgRGBA(220, 220, 220, 255), NVGcolor.nvgRGBA(128, 128, 128, 255));
767
+ ctx.nvgBeginPath();
768
+ ctx.nvgEllipse(lx, ly, ex, ey);
769
+ ctx.nvgEllipse(rx, ry, ex, ey);
770
+ ctx.nvgFillPaint(bg);
771
+ ctx.nvgFill();
772
+
773
+ let dx = (mx - rx) / (ex * 10);
774
+ let dy = (my - ry) / (ey * 10);
775
+ let d = Math.sqrt(dx * dx + dy * dy);
776
+ if (d > 1.0) {
777
+ dx /= d;
778
+ dy /= d;
779
+ }
780
+ dx *= ex * 0.4;
781
+ dy *= ey * 0.5;
782
+ ctx.nvgBeginPath();
783
+ ctx.nvgEllipse(lx + dx, ly + dy + ey * 0.25 * (1 - blink), br, br * blink);
784
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(32, 32, 32, 255));
785
+ ctx.nvgFill();
786
+
787
+ dx = (mx - rx) / (ex * 10);
788
+ dy = (my - ry) / (ey * 10);
789
+ d = Math.sqrt(dx * dx + dy * dy);
790
+ if (d > 1.0) {
791
+ dx /= d;
792
+ dy /= d;
793
+ }
794
+ dx *= ex * 0.4;
795
+ dy *= ey * 0.5;
796
+ ctx.nvgBeginPath();
797
+ ctx.nvgEllipse(rx + dx, ry + dy + ey * 0.25 * (1 - blink), br, br * blink);
798
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(32, 32, 32, 255));
799
+ ctx.nvgFill();
800
+
801
+ let gloss = ctx.nvgRadialGradient(lx - ex * 0.25, ly - ey * 0.5, ex * 0.1, ex * 0.75, NVGcolor.nvgRGBA(255, 255, 255, 128), NVGcolor.nvgRGBA(255, 255, 255, 0));
802
+ ctx.nvgBeginPath();
803
+ ctx.nvgEllipse(lx, ly, ex, ey);
804
+ ctx.nvgFillPaint(gloss);
805
+ ctx.nvgFill();
806
+
807
+ gloss = ctx.nvgRadialGradient(rx - ex * 0.25, ry - ey * 0.5, ex * 0.1, ex * 0.75, NVGcolor.nvgRGBA(255, 255, 255, 128), NVGcolor.nvgRGBA(255, 255, 255, 0));
808
+ ctx.nvgBeginPath();
809
+ ctx.nvgEllipse(rx, ry, ex, ey);
810
+ ctx.nvgFillPaint(gloss);
811
+ ctx.nvgFill();
812
+ }
813
+
814
+ function drawGraph(x, y, w, h, t) {
815
+ let samples = new Array(6);
816
+ let sx = new Array(6);
817
+ let sy = new Array(6);
818
+ let dx = w / 5.0;
819
+
820
+ samples[0] = (1 + Math.sin(t * 1.2345 + Math.cos(t * 0.33457) * 0.44)) * 0.5;
821
+ samples[1] = (1 + Math.sin(t * 0.68363 + Math.cos(t * 1.3) * 1.55)) * 0.5;
822
+ samples[2] = (1 + Math.sin(t * 1.1642 + Math.cos(t * 0.33457) * 1.24)) * 0.5;
823
+ samples[3] = (1 + Math.sin(t * 0.56345 + Math.cos(t * 1.63) * 0.14)) * 0.5;
824
+ samples[4] = (1 + Math.sin(t * 1.6245 + Math.cos(t * 0.254) * 0.3)) * 0.5;
825
+ samples[5] = (1 + Math.sin(t * 0.345 + Math.cos(t * 0.03) * 0.6)) * 0.5;
826
+
827
+ for (let i = 0; i < 6; i++) {
828
+ sx[i] = x + i * dx;
829
+ sy[i] = y + h * samples[i] * 0.8;
830
+ }
831
+
832
+ let bg = ctx.nvgLinearGradient(x, y, x, y + h, NVGcolor.nvgRGBA(0, 160, 192, 0), NVGcolor.nvgRGBA(0, 160, 192, 64));
833
+
834
+ ctx.nvgBeginPath();
835
+ ctx.nvgMoveTo(sx[0], sy[0]);
836
+ for (let i = 1; i < 6; i++) {
837
+ ctx.nvgBezierTo(sx[i - 1] + dx * 0.5, sy[i - 1], sx[i] - dx * 0.5, sy[i], sx[i], sy[i]);
838
+ }
839
+ ctx.nvgLineTo(x + w, y + h);
840
+ ctx.nvgLineTo(x, y + h);
841
+ ctx.nvgFillPaint(bg);
842
+ ctx.nvgFill();
843
+
844
+ ctx.nvgBeginPath();
845
+ ctx.nvgMoveTo(sx[0], sy[0] + 2);
846
+ for (let i = 1; i < 6; i++) {
847
+ ctx.nvgBezierTo(sx[i - 1] + dx * 0.5, sy[i - 1] + 2, sx[i] - dx * 0.5, sy[i] + 2, sx[i], sy[i] + 2);
848
+ }
849
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 32));
850
+ ctx.nvgStrokeWidth(3.0);
851
+ ctx.nvgStroke();
852
+
853
+ ctx.nvgBeginPath();
854
+ ctx.nvgMoveTo(sx[0], sy[0]);
855
+ for (let i = 1; i < 6; i++) {
856
+ ctx.nvgBezierTo(sx[i - 1] + dx * 0.5, sy[i - 1], sx[i] - dx * 0.5, sy[i], sx[i], sy[i]);
857
+ }
858
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 160, 192, 255));
859
+ ctx.nvgStrokeWidth(3.0);
860
+ ctx.nvgStroke();
861
+
862
+ for (let i = 0; i < 6; i++) {
863
+ bg = ctx.nvgRadialGradient(sx[i], sy[i] + 2, 3.0, 8.0, NVGcolor.nvgRGBA(0, 0, 0, 32), NVGcolor.nvgRGBA(0, 0, 0, 0));
864
+ ctx.nvgBeginPath();
865
+ ctx.nvgRect(sx[i] - 10, sy[i] - 10 + 2, 20, 20);
866
+ ctx.nvgFillPaint(bg);
867
+ ctx.nvgFill();
868
+ }
869
+ ctx.nvgBeginPath();
870
+ for (let i = 0; i < 6; i++) ctx.nvgCircle(sx[i], sy[i], 4.0);
871
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(0, 160, 192, 255));
872
+ ctx.nvgFill();
873
+ ctx.nvgBeginPath();
874
+ for (let i = 0; i < 6; i++) ctx.nvgCircle(sx[i], sy[i], 2.0);
875
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(220, 220, 220, 255));
876
+ ctx.nvgFill();
877
+ ctx.nvgStrokeWidth(1.0);
878
+ }
879
+
880
+ function drawLines(x, y, w, h, t) {
881
+ const pad = 5.0;
882
+ const s = w / 9.0 - pad * 2;
883
+ const pts = new Float32Array(8);
884
+ const joins = [NVG_MITER, NVG_ROUND, NVG_BEVEL];
885
+ //const joins = [NVG_BEVEL, NVG_BEVEL, NVG_BEVEL];
886
+ const caps = [NVG_BUTT, NVG_ROUND, NVG_SQUARE];
887
+
888
+ pts[0] = -s * 0.25 + Math.cos(t * 0.3) * s * 0.5;
889
+ pts[1] = Math.sin(t * 0.3) * s * 0.5;
890
+ pts[2] = -s * 0.25;
891
+ pts[3] = 0;
892
+ pts[4] = s * 0.25;
893
+ pts[5] = 0;
894
+ pts[6] = s * 0.25 + Math.cos(-t * 0.3) * s * 0.5;
895
+ pts[7] = Math.sin(-t * 0.3) * s * 0.5;
896
+
897
+ ctx.nvgSave();
898
+ for (let i = 0; i < 3; i++) {
899
+ for (let j = 0; j < 3; j++) {
900
+ const fx = x + s * 0.5 + (i * 3 + j) / 9.0 * w + pad;
901
+ const fy = y - s * 0.5 + pad;
902
+
903
+ ctx.nvgLineCap(caps[i]);
904
+ ctx.nvgLineJoin(joins[j]);
905
+ ctx.nvgStrokeWidth(s * 0.3);
906
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 160));
907
+ ctx.nvgBeginPath();
908
+ ctx.nvgMoveTo(fx + pts[0], fy + pts[1]);
909
+ ctx.nvgLineTo(fx + pts[2], fy + pts[3]);
910
+ ctx.nvgLineTo(fx + pts[4], fy + pts[5]);
911
+ ctx.nvgLineTo(fx + pts[6], fy + pts[7]);
912
+ ctx.nvgStroke();
913
+
914
+ ctx.nvgLineCap(NVG_BUTT);
915
+ ctx.nvgLineJoin(NVG_BEVEL);
916
+ ctx.nvgStrokeWidth(1.0);
917
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 192, 255, 255));
918
+ ctx.nvgBeginPath();
919
+ ctx.nvgMoveTo(fx + pts[0], fy + pts[1]);
920
+ ctx.nvgLineTo(fx + pts[2], fy + pts[3]);
921
+ ctx.nvgLineTo(fx + pts[4], fy + pts[5]);
922
+ ctx.nvgLineTo(fx + pts[6], fy + pts[7]);
923
+ ctx.nvgStroke();
924
+ }
925
+ }
926
+ ctx.nvgRestore();
927
+ }
928
+
929
+ function drawParagraph(x, y, width, height, mx, my) {
930
+ const text = "This is longer chunk of text.\n \n Would have used lorem ipsum but she was busy jumping over the lazy dog with the fox and all the men who came to the aid of the party.";
931
+ const hoverText = "Hover your mouse over the text to see calculated caret position.";
932
+ const rows = [];
933
+ const glyphs = new Array(100);
934
+ const bounds = [0, 0, 0, 0];
935
+ const lineh = {
936
+ value: 0
937
+ };
938
+ let lnum = 0;
939
+ let gutter = 0;
940
+ let gx = 0;
941
+ let gy = 0;
942
+
943
+ ctx.nvgSave();
944
+ ctx.nvgFontSize(15.0);
945
+ ctx.nvgFontFace("sans");
946
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
947
+ ctx.nvgTextMetrics(null, null, lineh);
948
+
949
+ const allRows = [];
950
+ for (const chunk of text.split("\n")) {
951
+ if (chunk.trim().length === 0) {
952
+ allRows.push({
953
+ start: "",
954
+ end: "",
955
+ next: null,
956
+ width: 0,
957
+ minx: 0,
958
+ maxx: 0
959
+ });
960
+ continue;
961
+ }
962
+ const part = [];
963
+ ctx.nvgTextBreakLines(chunk, null, width, part, 32);
964
+ if (part.length === 0) {
965
+ allRows.push({
966
+ start: chunk,
967
+ end: chunk,
968
+ next: null,
969
+ width: ctx.nvgTextBounds(0, 0, chunk, null, null),
970
+ minx: 0,
971
+ maxx: ctx.nvgTextBounds(0, 0, chunk, null, null)
972
+ });
973
+ } else {
974
+ for (const row of part) allRows.push(row);
975
+ }
976
+ }
977
+
978
+ for (const row of allRows) {
979
+ const hit = mx > x && mx < x + width && my >= y && my < y + lineh.value;
980
+
981
+ ctx.nvgBeginPath();
982
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, hit ? 64 : 16));
983
+ ctx.nvgRect(x + (row.minx || 0), y, (row.maxx || row.width || 0) - (row.minx || 0), lineh.value);
984
+ ctx.nvgFill();
985
+
986
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 255));
987
+ ctx.nvgText(x, y, row.start || "", null);
988
+
989
+ if (hit && row.start && row.start.length > 0) {
990
+ let caretx = mx < x + (row.width || 0) * 0.5 ? x : x + (row.width || 0);
991
+ let px = x;
992
+ const nglyphs = ctx.nvgTextGlyphPositions(x, y, row.start, null, glyphs, glyphs.length);
993
+ for (let j = 0; j < nglyphs; j++) {
994
+ const x0 = glyphs[j].x;
995
+ const x1 = j + 1 < nglyphs ? glyphs[j + 1].x : x + (row.width || 0);
996
+ const mxSplit = x0 * 0.3 + x1 * 0.7;
997
+ if (mx >= px && mx < mxSplit) {
998
+ caretx = glyphs[j].x;
999
+ break;
1000
+ }
1001
+ px = mxSplit;
1002
+ }
1003
+
1004
+ ctx.nvgBeginPath();
1005
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 192, 0, 255));
1006
+ ctx.nvgRect(caretx, y, 1, lineh.value);
1007
+ ctx.nvgFill();
1008
+
1009
+ gutter = lnum + 1;
1010
+ gx = x - 10;
1011
+ gy = y + lineh.value * 0.5;
1012
+ }
1013
+
1014
+ lnum++;
1015
+ y += lineh.value;
1016
+ }
1017
+
1018
+ if (gutter) {
1019
+ const txt = String(gutter);
1020
+ ctx.nvgFontSize(12.0);
1021
+ ctx.nvgTextAlign(NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
1022
+ ctx.nvgTextBounds(gx, gy, txt, null, bounds);
1023
+
1024
+ ctx.nvgBeginPath();
1025
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 192, 0, 255));
1026
+ ctx.nvgRoundedRect(Math.floor(bounds[0]) - 4, Math.floor(bounds[1]) - 2, Math.floor(bounds[2] - bounds[0]) + 8, Math.floor(bounds[3] - bounds[1]) + 4, (Math.floor(bounds[3] - bounds[1]) + 4) / 2 - 1);
1027
+ ctx.nvgFill();
1028
+
1029
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(32, 32, 32, 255));
1030
+ ctx.nvgText(gx, gy, txt, null);
1031
+ }
1032
+
1033
+ y += 20.0;
1034
+ ctx.nvgFontSize(11.0);
1035
+ ctx.nvgTextAlign(NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
1036
+ ctx.nvgTextLineHeight(1.2);
1037
+ ctx.nvgTextBoxBounds(x, y, 150, hoverText, null, bounds);
1038
+
1039
+ const dx = clampf(mx, bounds[0], bounds[2]) - mx;
1040
+ const dy = clampf(my, bounds[1], bounds[3]) - my;
1041
+ let a = Math.sqrt(dx * dx + dy * dy) / 30.0;
1042
+ a = clampf(a, 0, 1);
1043
+ ctx.nvgGlobalAlpha(a);
1044
+
1045
+ ctx.nvgBeginPath();
1046
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(220, 220, 220, 255));
1047
+ ctx.nvgRoundedRect(bounds[0] - 2, bounds[1] - 2, Math.floor(bounds[2] - bounds[0]) + 4, Math.floor(bounds[3] - bounds[1]) + 4, 3);
1048
+ const px = Math.floor((bounds[2] + bounds[0]) / 2);
1049
+ ctx.nvgMoveTo(px, bounds[1] - 10);
1050
+ ctx.nvgLineTo(px + 7, bounds[1] + 1);
1051
+ ctx.nvgLineTo(px - 7, bounds[1] + 1);
1052
+ ctx.nvgFill();
1053
+
1054
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(0, 0, 0, 220));
1055
+ ctx.nvgTextBox(x, y, 150, hoverText, null);
1056
+ ctx.nvgRestore();
1057
+ }
1058
+
1059
+ function drawSpinner(cx, cy, r, t) {
1060
+ let a0 = t * 6.0;
1061
+ let a1 = Math.PI + t * 6.0;
1062
+ let r0 = r;
1063
+ let r1 = r * 0.75;
1064
+ let ax = cx + Math.cos(a0) * (r0 + r1) * 0.5;
1065
+ let ay = cy + Math.sin(a0) * (r0 + r1) * 0.5;
1066
+ let bx = cx + Math.cos(a1) * (r0 + r1) * 0.5;
1067
+ let by = cy + Math.sin(a1) * (r0 + r1) * 0.5;
1068
+
1069
+ ctx.nvgSave();
1070
+ ctx.nvgBeginPath();
1071
+ ctx.nvgArc(cx, cy, r0, a0, a1, NVG_CW);
1072
+ ctx.nvgArc(cx, cy, r1, a1, a0, NVG_CCW);
1073
+ ctx.nvgClosePath();
1074
+ ctx.nvgFillPaint(ctx.nvgLinearGradient(ax, ay, bx, by, NVGcolor.nvgRGBA(0, 0, 0, 0), NVGcolor.nvgRGBA(0, 0, 0, 128)));
1075
+ ctx.nvgFill();
1076
+ ctx.nvgRestore();
1077
+ }
1078
+
1079
+ function drawWidths(x, y, width) {
1080
+ ctx.nvgSave();
1081
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 255));
1082
+ for (let i = 0; i < 20; i++) {
1083
+ let w = (i + 0.5) * 0.1;
1084
+ ctx.nvgStrokeWidth(w);
1085
+ ctx.nvgBeginPath();
1086
+ ctx.nvgMoveTo(x, y);
1087
+ ctx.nvgLineTo(x + width, y + width * 0.3);
1088
+ ctx.nvgStroke();
1089
+ y += 10;
1090
+ }
1091
+ ctx.nvgRestore();
1092
+ }
1093
+
1094
+ function drawCaps(x, y, width) {
1095
+ const caps = [NVG_BUTT, NVG_ROUND, NVG_SQUARE];
1096
+ const lineWidth = 8.0;
1097
+
1098
+ ctx.nvgSave();
1099
+ ctx.nvgBeginPath();
1100
+ ctx.nvgRect(x - lineWidth / 2, y, width + lineWidth, 40);
1101
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 32));
1102
+ ctx.nvgFill();
1103
+
1104
+ ctx.nvgBeginPath();
1105
+ ctx.nvgRect(x, y, width, 40);
1106
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 255, 255, 32));
1107
+ ctx.nvgFill();
1108
+
1109
+ ctx.nvgStrokeWidth(lineWidth);
1110
+ for (let i = 0; i < 3; i++) {
1111
+ ctx.nvgLineCap(caps[i]);
1112
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 255));
1113
+ ctx.nvgBeginPath();
1114
+ ctx.nvgMoveTo(x, y + i * 10 + 5);
1115
+ ctx.nvgLineTo(x + width, y + i * 10 + 5);
1116
+ ctx.nvgStroke();
1117
+ }
1118
+ ctx.nvgRestore();
1119
+ }
1120
+
1121
+ function drawScissor(x, y, t) {
1122
+ ctx.nvgSave();
1123
+ ctx.nvgTranslate(x, y);
1124
+ ctx.nvgRotate(nvgDegToRad(5));
1125
+
1126
+ ctx.nvgBeginPath();
1127
+ ctx.nvgRect(-20, -20, 60, 40);
1128
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 0, 0, 255));
1129
+ ctx.nvgFill();
1130
+ ctx.nvgScissor(-20, -20, 60, 40);
1131
+
1132
+ ctx.nvgTranslate(40, 0);
1133
+ ctx.nvgRotate(t);
1134
+
1135
+ ctx.nvgSave();
1136
+ ctx.nvgResetScissor();
1137
+ ctx.nvgBeginPath();
1138
+ ctx.nvgRect(-20, -10, 60, 30);
1139
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 128, 0, 64));
1140
+ ctx.nvgFill();
1141
+ ctx.nvgRestore();
1142
+
1143
+ ctx.nvgIntersectScissor(-20, -10, 60, 30);
1144
+ ctx.nvgBeginPath();
1145
+ ctx.nvgRect(-20, -10, 60, 30);
1146
+ ctx.nvgFillColor(NVGcolor.nvgRGBA(255, 128, 0, 255));
1147
+ ctx.nvgFill();
1148
+ ctx.nvgRestore();
1149
+ }
1150
+
1151
+ function drawColorwheel(x, y, w, h, t) {
1152
+ let cx = x + w * 0.5;
1153
+ let cy = y + h * 0.5;
1154
+ let r1 = Math.min(w, h) * 0.5 - 5.0;
1155
+ let r0 = r1 - 20.0;
1156
+ let aeps = 0.5 / r1;
1157
+ let hue = Math.sin(t * 0.12);
1158
+
1159
+ ctx.nvgSave();
1160
+
1161
+ for (let i = 0; i < 6; i++) {
1162
+ let a0 = i / 6.0 * Math.PI * 2.0 - aeps;
1163
+ let a1 = (i + 1.0) / 6.0 * Math.PI * 2.0 + aeps;
1164
+ let ax = cx + Math.cos(a0) * (r0 + r1) * 0.5;
1165
+ let ay = cy + Math.sin(a0) * (r0 + r1) * 0.5;
1166
+ let bx = cx + Math.cos(a1) * (r0 + r1) * 0.5;
1167
+ let by = cy + Math.sin(a1) * (r0 + r1) * 0.5;
1168
+
1169
+ ctx.nvgBeginPath();
1170
+ ctx.nvgArc(cx, cy, r0, a0, a1, NVG_CW);
1171
+ ctx.nvgArc(cx, cy, r1, a1, a0, NVG_CCW);
1172
+ ctx.nvgClosePath();
1173
+ ctx.nvgFillPaint(ctx.nvgLinearGradient(ax, ay, bx, by, NVGcolor.nvgHSLA(a0 / (Math.PI * 2), 1.0, 0.55, 255), NVGcolor.nvgHSLA(a1 / (Math.PI * 2), 1.0, 0.55, 255)));
1174
+ ctx.nvgFill();
1175
+ }
1176
+
1177
+ ctx.nvgBeginPath();
1178
+ ctx.nvgCircle(cx, cy, r0 - 0.5);
1179
+ ctx.nvgCircle(cx, cy, r1 + 0.5);
1180
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 64));
1181
+ ctx.nvgStrokeWidth(1.0);
1182
+ ctx.nvgStroke();
1183
+
1184
+ ctx.nvgSave();
1185
+ ctx.nvgTranslate(cx, cy);
1186
+ ctx.nvgRotate(hue * Math.PI * 2);
1187
+
1188
+ ctx.nvgStrokeWidth(2.0);
1189
+ ctx.nvgBeginPath();
1190
+ ctx.nvgRect(r0 - 1, -3, r1 - r0 + 2, 6);
1191
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(255, 255, 255, 192));
1192
+ ctx.nvgStroke();
1193
+
1194
+ ctx.nvgFillPaint(ctx.nvgBoxGradient(r0 - 3, -5, r1 - r0 + 6, 10, 2, 4, NVGcolor.nvgRGBA(0, 0, 0, 128), NVGcolor.nvgRGBA(0, 0, 0, 0)));
1195
+ ctx.nvgBeginPath();
1196
+ ctx.nvgRect(r0 - 12, -14, r1 - r0 + 24, 28);
1197
+ ctx.nvgRect(r0 - 2, -4, r1 - r0 + 4, 8);
1198
+ ctx.nvgPathWinding(NVG_HOLE);
1199
+ ctx.nvgFill();
1200
+
1201
+ let r = r0 - 6;
1202
+ let ax = Math.cos(120.0 / 180.0 * Math.PI) * r;
1203
+ let ay = Math.sin(120.0 / 180.0 * Math.PI) * r;
1204
+ let bx = Math.cos(-120.0 / 180.0 * Math.PI) * r;
1205
+ let by = Math.sin(-120.0 / 180.0 * Math.PI) * r;
1206
+
1207
+ ctx.nvgBeginPath();
1208
+ ctx.nvgMoveTo(r, 0);
1209
+ ctx.nvgLineTo(ax, ay);
1210
+ ctx.nvgLineTo(bx, by);
1211
+ ctx.nvgClosePath();
1212
+ ctx.nvgFillPaint(ctx.nvgLinearGradient(r, 0, ax, ay, NVGcolor.nvgHSLA(hue, 1.0, 0.5, 255), NVGcolor.nvgRGBA(255, 255, 255, 255)));
1213
+ ctx.nvgFill();
1214
+ ctx.nvgFillPaint(ctx.nvgLinearGradient((r + ax) * 0.5, (ay) * 0.5, bx, by, NVGcolor.nvgRGBA(0, 0, 0, 0), NVGcolor.nvgRGBA(0, 0, 0, 255)));
1215
+ ctx.nvgFill();
1216
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(0, 0, 0, 64));
1217
+ ctx.nvgStroke();
1218
+
1219
+ ax = Math.cos(120.0 / 180.0 * Math.PI) * r * 0.3;
1220
+ ay = Math.sin(120.0 / 180.0 * Math.PI) * r * 0.4;
1221
+ ctx.nvgStrokeWidth(2.0);
1222
+ ctx.nvgBeginPath();
1223
+ ctx.nvgCircle(ax, ay, 5);
1224
+ ctx.nvgStrokeColor(NVGcolor.nvgRGBA(255, 255, 255, 192));
1225
+ ctx.nvgStroke();
1226
+
1227
+ ctx.nvgFillPaint(ctx.nvgRadialGradient(ax, ay, 7, 9, NVGcolor.nvgRGBA(0, 0, 0, 64), NVGcolor.nvgRGBA(0, 0, 0, 0)));
1228
+ ctx.nvgBeginPath();
1229
+ ctx.nvgRect(ax - 20, ay - 20, 40, 40);
1230
+ ctx.nvgCircle(ax, ay, 7);
1231
+ ctx.nvgPathWinding(NVG_HOLE);
1232
+ ctx.nvgFill();
1233
+
1234
+ ctx.nvgRestore();
1235
+ ctx.nvgRestore();
1236
+ }
1237
+
1238
+ function checkGLError(context = "unknown") {
1239
+ const error = gl.getError();
1240
+ if (error !== gl.NO_ERROR) {
1241
+ console.error(`WebGL error (${context}):`, error);
1242
+ }
1243
+ }
1244
+
1245
+
1246
+ //
1247
+ // start here
1248
+ //
1249
+ function main() {
1250
+
1251
+ canvas = document.querySelector("#glcanvas");
1252
+
1253
+ function resizeCanvas() {
1254
+ const dpr = window.devicePixelRatio || 1;
1255
+ canvas.width = Math.max(1, Math.floor(window.innerWidth * dpr));
1256
+ canvas.height = Math.max(1, Math.floor(window.innerHeight * dpr));
1257
+ canvas.style.width = `${window.innerWidth}px`;
1258
+ canvas.style.height = `${window.innerHeight}px`;
1259
+ }
1260
+ resizeCanvas();
1261
+ // Initialize the GL context
1262
+ gl = canvas.getContext("experimental-webgl", {
1263
+ stencil: true,
1264
+ alpha: true,
1265
+ antialias: true
1266
+ });
1267
+
1268
+ // Only continue if WebGL is available and working
1269
+ if (gl === null) {
1270
+ alert(
1271
+ "Unable to initialize WebGL. Your browser or machine may not support it."
1272
+ );
1273
+ return;
1274
+ }
1275
+
1276
+ canvas.addEventListener("mousemove", (event) => {
1277
+ const rect = canvas.getBoundingClientRect();
1278
+ mx = event.clientX - rect.left;
1279
+ my = event.clientY - rect.top;
1280
+ });
1281
+
1282
+ canvas.tabIndex = 0;
1283
+ canvas.addEventListener("mousedown", (event) => {
1284
+ const rect = canvas.getBoundingClientRect();
1285
+ mx = event.clientX - rect.left;
1286
+ my = event.clientY - rect.top;
1287
+ const control = hitInputControl(mx, my);
1288
+ setActiveInput(control);
1289
+ canvas.focus();
1290
+ });
1291
+ canvas.addEventListener("keydown", handleInputKeydown);
1292
+
1293
+ let blowup = false;
1294
+ window.addEventListener("keydown", (event) => {
1295
+ if (event.code === "Space") blowup = !blowup;
1296
+ });
1297
+
1298
+ let winWidth, winHeight;
1299
+ let fbWidth, fbHeight;
1300
+ let pxRatio;
1301
+
1302
+ let params = new NVGparams();
1303
+
1304
+ params.renderCreate = function(userPtr) {
1305
+ return glnvg__renderCreate(userPtr);
1306
+ };
1307
+ params.renderCreateTexture = function(userPtr, type, width, height, imageFlags, data) {
1308
+ return glnvg__renderCreateTexture(userPtr, type, width, height, imageFlags, data);
1309
+ };
1310
+ params.renderDeleteTexture = function(userPtr, image) {
1311
+ return glnvg__renderDeleteTexture(userPtr, image);
1312
+ };
1313
+ params.renderUpdateTexture = function(userPtr, image, x, y, w, h, data) {
1314
+ return glnvg__renderUpdateTexture(userPtr, image, x, y, w, h, data);
1315
+ };
1316
+ params.renderGetTextureSize = function(userPtr, image) {
1317
+ return glnvg__renderGetTextureSize(userPtr, image);
1318
+ };
1319
+ params.renderViewport = function(userPtr, width, height, devicePixelRatio) {
1320
+ glnvg__renderViewport(userPtr, width, height, devicePixelRatio);
1321
+ };
1322
+ params.renderCancel = function(userPtr) {
1323
+ glnvg__renderCancel(userPtr);
1324
+ };
1325
+ params.renderFlush = function(ctx) {
1326
+ glnvg__renderFlush(ctx);
1327
+ };
1328
+ params.renderFill = function(userPtr, fillPaint, compositeOperation, scissor, fringeWidth, bounds, paths, npaths) {
1329
+ glnvg__renderFill(userPtr, fillPaint, compositeOperation, scissor, fringeWidth, bounds, paths, npaths);
1330
+ };
1331
+ params.renderStroke = function(userPtr, strokePaint, compositeOperation, scissor, fringe, strokeWidth, paths, npaths) {
1332
+ glnvg__renderStroke(userPtr, strokePaint, compositeOperation, scissor, fringe, strokeWidth, paths, npaths);
1333
+ };
1334
+ params.renderTriangles = function(userPtr, paint, compositeOperation, scissor, verts, nverts, fringe) {
1335
+ glnvg__renderTriangles(userPtr, paint, compositeOperation, scissor, verts, nverts, fringe);
1336
+ };
1337
+ params.renderDelete = function(userPtr) {
1338
+ glnvg__renderDelete(userPtr);
1339
+ };
1340
+ params.userPtr = gl;
1341
+ params.edgeAntiAlias = true;
1342
+
1343
+ gl.edgeAntiAlias = params.edgeAntiAlias;
1344
+
1345
+ ctx = nvgCreateInternal(params);
1346
+ ensureDemoFonts();
1347
+ loadDemoImages();
1348
+
1349
+ window.addEventListener("resize", resizeCanvas);
1350
+
1351
+ // Render loop
1352
+ function render() {
1353
+ resizeCanvas();
1354
+ winWidth = Math.max(1, canvas.clientWidth);
1355
+ winHeight = Math.max(1, canvas.clientHeight);
1356
+ fbWidth = gl.drawingBufferWidth;
1357
+ fbHeight = gl.drawingBufferHeight;
1358
+ pxRatio = fbWidth / winWidth;
1359
+ const now = performance.now() * 0.001;
1360
+ if (prevFrameTime === 0) prevFrameTime = now;
1361
+ updateGraph(perfGraph, now - prevFrameTime);
1362
+ prevFrameTime = now;
1363
+
1364
+ if (premult)
1365
+ gl.clearColor(0.0, 0.0, 0.0, 0.0);
1366
+ else
1367
+ gl.clearColor(0.3, 0.3, 0.32, 1.0);
1368
+
1369
+ gl.viewport(0, 0, fbWidth, fbHeight);
1370
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
1371
+
1372
+ beginInputFrame(now);
1373
+ ctx.nvgBeginFrame(winWidth, winHeight, pxRatio);
1374
+ renderOriginalDemo(mx ?? winWidth * 0.5, my ?? winHeight * 0.5, winWidth, winHeight, now, blowup);
1375
+ renderGraph(5, 5, perfGraph);
1376
+ ctx.nvgEndFrame();
1377
+
1378
+ // Call the render function again for the next frame
1379
+ requestAnimationFrame(render);
1380
+ }
1381
+ // Start the render loop
1382
+ render();
1383
+ }
1384
+ document.addEventListener("DOMContentLoaded", function() {
1385
+ main();
1386
+ });