1package main
2
3
4
5
6
7
8import "core:fmt"
9import "core:math"
10import "core:strings"
11import "core:unicode/utf8"
12import gl "vendor:OpenGL"
13import glfw "vendor:glfw"
14import nvg "vendor:nanovg"
15import nvg_gl "vendor:nanovg/gl"
16import stbi "vendor:stb/image"
17
18DEMO_MSAA :: true
19
20DemoData :: struct {
21 fontNormal, fontBold, fontIcons, fontEmoji: int,
22 images: [12]int,
23}
24
25blowup: bool
26screenshot: bool
27premult: bool
28
29key_callback :: proc "c" (window: glfw.WindowHandle, key, scancode, action, mods: i32) {
30 if action != glfw.PRESS {
31 return
32 }
33
34 switch key {
35 case glfw.KEY_ESCAPE:
36 {
37 glfw.SetWindowShouldClose(window, true)
38 }
39
40 case glfw.KEY_SPACE:
41 {
42 blowup = !blowup
43 }
44
45 case glfw.KEY_S:
46 {
47 screenshot = true
48 }
49
50 case glfw.KEY_P:
51 {
52 premult = !premult
53 }
54 }
55}
56
57main :: proc() {
58 if !glfw.Init() {
59 panic("glfw failed")
60 }
61
62 glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3)
63 glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 2)
64 glfw.WindowHint(glfw.OPENGL_FORWARD_COMPAT, 1)
65 glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
66 glfw.WindowHint(glfw.OPENGL_DEBUG_CONTEXT, 1)
67
68 when DEMO_MSAA {
69 glfw.WindowHint(glfw.SAMPLES, 4)
70 }
71
72 window := glfw.CreateWindow(1000, 600, "NanoVG", nil, nil)
73
74 if window == nil {
75 glfw.Terminate()
76 panic("glfw window failed")
77 }
78
79 glfw.SetKeyCallback(window, key_callback)
80 glfw.MakeContextCurrent(window)
81 gl.load_up_to(3, 2, glfw.gl_set_proc_address)
82
83 when DEMO_MSAA {
84 ctx := nvg_gl.Create({.STENCIL_STROKES, .DEBUG})
85 } else {
86 ctx := nvg_gl.Create({.ANTI_ALIAS, .STENCIL_STROKES, .DEBUG})
87 }
88 defer nvg_gl.Destroy(ctx)
89
90 demo: DemoData
91 loadDemoData(ctx, &demo)
92 defer freeDemoData(ctx, &demo)
93
94 gpuTimer: GPUtimer
95 fps, cpuGraph, gpuGraph: PerfGraph
96
97 initGraph(&fps, .FPS, "Frame Time")
98 initGraph(&cpuGraph, .MS, "CPU Time")
99 initGraph(&gpuGraph, .MS, "GPU Time")
100
101 glfw.SetTime(0)
102 prevt := glfw.GetTime()
103
104 for !glfw.WindowShouldClose(window) {
105 t := glfw.GetTime()
106 dt := t - prevt
107 prevt = t
108
109 startGPUTimer(&gpuTimer)
110
111 fw, fh := glfw.GetFramebufferSize(window)
112 w, h := glfw.GetWindowSize(window)
113 gl.Viewport(0, 0, fw, fh)
114
115 if premult {
116 gl.ClearColor(0, 0, 0, 0)
117 } else {
118 gl.ClearColor(0.3, 0.3, 0.32, 1.0)
119 }
120
121 gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
122 px_ratio := f32(fw) / f32(fw)
123
124 {
125 xx, yy := glfw.GetCursorPos(window)
126 x := f32(xx)
127 y := f32(yy)
128
129 nvg.BeginFrame(ctx, f32(w), f32(h), px_ratio)
130 defer nvg.EndFrame(ctx)
131
132 renderDemo(ctx, x, y, f32(w), f32(h), f32(t), blowup, &demo)
133 renderGraph(ctx, 5, 5, &fps)
134 renderGraph(ctx, 5 + 200 + 5, 5, &cpuGraph)
135
136 if gpuTimer.supported {
137 renderGraph(ctx, 5 + 200 + 5 + 200 + 5, 5, &gpuGraph)
138 }
139 }
140
141 cpuTime := glfw.GetTime() - t
142 updateGraph(&fps, f32(dt))
143 updateGraph(&cpuGraph, f32(cpuTime))
144 stopGPUTimer(&gpuTimer)
145
146 if screenshot {
147 saveScreenshot(int(fw), int(fh), premult, "dump.png")
148 screenshot = false
149 }
150
151 glfw.SwapBuffers(window)
152 glfw.PollEvents()
153 }
154}
155
156drawWindow :: proc(ctx: ^nvg.Context, title: string, x, y, w, h: f32) {
157 corner_radius := f32(3)
158 nvg.SaveScoped(ctx)
159
160
161 {
162 nvg.FillScoped(ctx)
163 nvg.RoundedRect(ctx, x, y, w, h, corner_radius)
164 nvg.FillColor(ctx, nvg.RGBA(28, 30, 34, 192))
165 }
166
167
168 {
169 nvg.FillScoped(ctx)
170 nvg.Rect(ctx, x - 10, y - 10, w + 20, h + 30)
171 nvg.RoundedRect(ctx, x, y, w, h, corner_radius)
172 nvg.PathSolidity(ctx, .HOLE)
173 shadow_paint := nvg.BoxGradient(
174 x,
175 y + 2,
176 w,
177 h,
178 corner_radius * 2,
179 10,
180 nvg.RGBA(0, 0, 0, 128),
181 nvg.RGBA(0, 0, 0, 0),
182 )
183 nvg.FillPaint(ctx, shadow_paint)
184 }
185
186
187 {
188 nvg.FillScoped(ctx)
189 nvg.RoundedRect(ctx, x + 1, y + 1, w - 2, 30, corner_radius - 1)
190 header_paint := nvg.LinearGradient(
191 x,
192 y,
193 x,
194 y + 15,
195 nvg.RGBA(255, 255, 255, 8),
196 nvg.RGBA(0, 0, 0, 8),
197 )
198 nvg.FillPaint(ctx, header_paint)
199 }
200
201
202 {
203 nvg.StrokeScoped(ctx)
204 nvg.MoveTo(ctx, x + 0.5, y + 0.5 + 30)
205 nvg.LineTo(ctx, x + 0.5 + w - 1, y + 0.5 + 30)
206 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 32))
207 }
208
209
210 nvg.FontSize(ctx, 15)
211 nvg.FontFace(ctx, "sans-bold")
212 nvg.TextAlignHorizontal(ctx, .CENTER)
213 nvg.TextAlignVertical(ctx, .MIDDLE)
214
215 nvg.FontBlur(ctx, 2)
216 nvg.FillColor(ctx, nvg.RGBA(0, 0, 0, 128))
217 nvg.Text(ctx, x + w / 2, y + 16 + 1, title)
218
219 nvg.FontBlur(ctx, 0)
220 nvg.FillColor(ctx, nvg.RGBA(220, 220, 220, 220))
221 nvg.Text(ctx, x + w / 2, y + 16, title)
222}
223
224drawEyes :: proc(ctx: ^nvg.Context, x, y: f32, w, h: f32, mx, my: f32, t: f32) {
225 ex := w * 0.23
226 ey := h * 0.5
227 lx := x + ex
228 ly := y + ey
229 rx := x + w - ex
230 ry := y + ey
231 br := (ex < ey ? ex : ey) * 0.5
232 blink := 1 - math.pow(math.sin(t * 0.5), 200) * 0.8
233
234 {
235 bg := nvg.LinearGradient(
236 x,
237 y + h * 0.5,
238 x + w * 0.1,
239 y + h,
240 nvg.RGBA(0, 0, 0, 32),
241 nvg.RGBA(0, 0, 0, 16),
242 )
243 nvg.FillScoped(ctx)
244 nvg.Ellipse(ctx, lx + 3.0, ly + 16.0, ex, ey)
245 nvg.Ellipse(ctx, rx + 3.0, ry + 16.0, ex, ey)
246 nvg.FillPaint(ctx, bg)
247 }
248
249 {
250 bg := nvg.LinearGradient(
251 x,
252 y + h * 0.25,
253 x + w * 0.1,
254 y + h,
255 nvg.RGBA(220, 220, 220, 255),
256 nvg.RGBA(128, 128, 128, 255),
257 )
258 nvg.FillScoped(ctx)
259 nvg.Ellipse(ctx, lx, ly, ex, ey)
260 nvg.Ellipse(ctx, rx, ry, ex, ey)
261 nvg.FillPaint(ctx, bg)
262 }
263
264 dx := (mx - rx) / (ex * 10)
265 dy := (my - ry) / (ey * 10)
266 d := math.sqrt(dx * dx + dy * dy)
267 if d > 1.0 {
268 dx /= d
269 dy /= d
270 }
271 dx *= ex * 0.4
272 dy *= ey * 0.5
273
274 {
275 nvg.FillScoped(ctx)
276 nvg.Ellipse(ctx, lx + dx, ly + dy + ey * 0.25 * (1 - blink), br, br * blink)
277 nvg.FillColor(ctx, nvg.RGBA(32, 32, 32, 255))
278 }
279
280 dx = (mx - rx) / (ex * 10)
281 dy = (my - ry) / (ey * 10)
282 d = math.sqrt(dx * dx + dy * dy)
283 if d > 1.0 {
284 dx /= d
285 dy /= d
286 }
287 dx *= ex * 0.4
288 dy *= ey * 0.5
289
290 {
291 nvg.FillScoped(ctx)
292 nvg.Ellipse(ctx, rx + dx, ry + dy + ey * 0.25 * (1 - blink), br, br * blink)
293 nvg.FillColor(ctx, nvg.RGBA(32, 32, 32, 255))
294 }
295
296 {
297 gloss := nvg.RadialGradient(
298 lx - ex * 0.25,
299 ly - ey * 0.5,
300 ex * 0.1,
301 ex * 0.75,
302 nvg.RGBA(255, 255, 255, 128),
303 nvg.RGBA(255, 255, 255, 0),
304 )
305 nvg.FillScoped(ctx)
306 nvg.Ellipse(ctx, lx, ly, ex, ey)
307 nvg.FillPaint(ctx, gloss)
308 }
309
310 {
311 gloss := nvg.RadialGradient(
312 rx - ex * 0.25,
313 ry - ey * 0.5,
314 ex * 0.1,
315 ex * 0.75,
316 nvg.RGBA(255, 255, 255, 128),
317 nvg.RGBA(255, 255, 255, 0),
318 )
319 nvg.FillScoped(ctx)
320 nvg.Ellipse(ctx, rx, ry, ex, ey)
321 nvg.FillPaint(ctx, gloss)
322 }
323}
324
325drawGraph :: proc(ctx: ^nvg.Context, x, y, w, h, t: f32) {
326 sx: [6]f32
327 sy: [6]f32
328 dx := w / 5.0
329 samples: [6]f32
330 samples[0] = (1 + math.sin(t * 1.2345 + math.cos(t * 0.33457) * 0.44)) * 0.5
331 samples[1] = (1 + math.sin(t * 0.68363 + math.cos(t * 1.3) * 1.55)) * 0.5
332 samples[2] = (1 + math.sin(t * 1.1642 + math.cos(t * 0.33457) * 1.24)) * .5
333 samples[3] = (1 + math.sin(t * 0.56345 + math.cos(t * 1.63) * 0.14)) * 0.
334 samples[4] = (1 + math.sin(t * 1.6245 + math.cos(t * 0.254) * 0.3)) * 0.5
335 samples[5] = (1 + math.sin(t * 0.345 + math.cos(t * 0.03) * 0.6)) * 0.5
336
337 for i in 0 ..< 6 {
338 sx[i] = x + f32(i) * dx
339 sy[i] = y + h * samples[i] * 0.8
340 }
341
342
343 {
344 bg := nvg.LinearGradient(
345 x,
346 y,
347 x,
348 y + h,
349 nvg.RGBA(0, 160, 192, 0),
350 nvg.RGBA(0, 160, 192, 64),
351 )
352 nvg.FillScoped(ctx)
353 nvg.MoveTo(ctx, sx[0], sy[0])
354 for i in 1 ..< 6 {
355 nvg.BezierTo(
356 ctx,
357 sx[i - 1] + dx * 0.5,
358 sy[i - 1],
359 sx[i] - dx * 0.5,
360 sy[i],
361 sx[i],
362 sy[i],
363 )
364 }
365 nvg.LineTo(ctx, x + w, y + h)
366 nvg.LineTo(ctx, x, y + h)
367 nvg.FillPaint(ctx, bg)
368 }
369
370
371 {
372 nvg.StrokeScoped(ctx)
373 nvg.MoveTo(ctx, sx[0], sy[0] + 2)
374 for i in 1 ..< 6 {
375 nvg.BezierTo(
376 ctx,
377 sx[i - 1] + dx * 0.5,
378 sy[i - 1] + 2,
379 sx[i] - dx * 0.5,
380 sy[i] + 2,
381 sx[i],
382 sy[i] + 2,
383 )
384 }
385 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 32))
386 nvg.StrokeWidth(ctx, 3.0)
387 }
388
389 {
390 nvg.StrokeScoped(ctx)
391 nvg.MoveTo(ctx, sx[0], sy[0])
392 for i in 1 ..< 6 {
393 nvg.BezierTo(
394 ctx,
395 sx[i - 1] + dx * 0.5,
396 sy[i - 1],
397 sx[i] - dx * 0.5,
398 sy[i],
399 sx[i],
400 sy[i],
401 )
402 }
403 nvg.StrokeColor(ctx, nvg.RGBA(0, 160, 192, 255))
404 nvg.StrokeWidth(ctx, 3.0)
405 }
406
407
408 for i in 0 ..< 6 {
409 bg := nvg.RadialGradient(
410 sx[i],
411 sy[i] + 2,
412 3.0,
413 8.0,
414 nvg.RGBA(0, 0, 0, 32),
415 nvg.RGBA(0, 0, 0, 0),
416 )
417 nvg.FillScoped(ctx)
418 nvg.Rect(ctx, sx[i] - 10, sy[i] - 10 + 2, 20, 20)
419 nvg.FillPaint(ctx, bg)
420 }
421
422 {
423 nvg.FillScoped(ctx)
424 for i in 0 ..< 6 {
425 nvg.Circle(ctx, sx[i], sy[i], 4.0)
426 }
427 nvg.FillColor(ctx, nvg.RGBA(0, 160, 192, 255))
428 }
429
430 {
431 nvg.FillScoped(ctx)
432 for i in 0 ..< 6 {
433 nvg.Circle(ctx, sx[i], sy[i], 2.0)
434 }
435 nvg.FillColor(ctx, nvg.RGBA(220, 220, 220, 255))
436 }
437
438 nvg.StrokeWidth(ctx, 1.0)
439}
440
441drawColorwheel :: proc(ctx: ^nvg.Context, x, y, w, h, t: f32) {
442 r0, r1, ax, ay, bx, by, cx, cy, aeps, r: f32
443 hue := math.sin(t * 0.12)
444 paint: nvg.Paint
445 nvg.Save(ctx)
446
447 cx = x + w * 0.5
448 cy = y + h * 0.5
449 r1 = (w < h ? w : h) * 0.5 - 5.0
450 r0 = r1 - 20.0
451 aeps = 0.5 / r1
452
453 for i in 0 ..< 6 {
454 a0 := f32(i) / 6.0 * math.PI * 2.0 - aeps
455 a1 := f32(i + 1.0) / 6.0 * math.PI * 2.0 + aeps
456 nvg.BeginPath(ctx)
457 nvg.Arc(ctx, cx, cy, r0, a0, a1, .CW)
458 nvg.Arc(ctx, cx, cy, r1, a1, a0, .CCW)
459 nvg.ClosePath(ctx)
460 ax = cx + math.cos(a0) * (r0 + r1) * 0.5
461 ay = cy + math.sin(a0) * (r0 + r1) * 0.5
462 bx = cx + math.cos(a1) * (r0 + r1) * 0.5
463 by = cy + math.sin(a1) * (r0 + r1) * 0.5
464 paint = nvg.LinearGradient(
465 ax,
466 ay,
467 bx,
468 by,
469 nvg.HSLA(a0 / (math.PI * 2), 1.0, 0.55, 255),
470 nvg.HSLA(a1 / (math.PI * 2), 1.0, 0.55, 255),
471 )
472 nvg.FillPaint(ctx, paint)
473 nvg.Fill(ctx)
474 }
475
476 nvg.BeginPath(ctx)
477 nvg.Circle(ctx, cx, cy, r0 - 0.5)
478 nvg.Circle(ctx, cx, cy, r1 + 0.5)
479 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 64))
480 nvg.StrokeWidth(ctx, 1.0)
481 nvg.Stroke(ctx)
482
483
484 nvg.Save(ctx)
485 nvg.Translate(ctx, cx, cy)
486 nvg.Rotate(ctx, hue * math.PI * 2)
487
488
489 nvg.StrokeWidth(ctx, 2.0)
490 nvg.BeginPath(ctx)
491 nvg.Rect(ctx, r0 - 1, -3, r1 - r0 + 2, 6)
492 nvg.StrokeColor(ctx, nvg.RGBA(255, 255, 255, 192))
493 nvg.Stroke(ctx)
494
495 paint = nvg.BoxGradient(
496 r0 - 3,
497 -5,
498 r1 - r0 + 6,
499 10,
500 2,
501 4,
502 nvg.RGBA(0, 0, 0, 128),
503 nvg.RGBA(0, 0, 0, 0),
504 )
505 nvg.BeginPath(ctx)
506 nvg.Rect(ctx, r0 - 2 - 10, -4 - 10, r1 - r0 + 4 + 20, 8 + 20)
507 nvg.Rect(ctx, r0 - 2, -4, r1 - r0 + 4, 8)
508 nvg.PathSolidity(ctx, .HOLE)
509 nvg.FillPaint(ctx, paint)
510 nvg.Fill(ctx)
511
512
513 r = r0 - 6
514 ax = math.cos(f32(120.0 / 180.0 * math.PI)) * r
515 ay = math.sin(f32(120.0 / 180.0 * math.PI)) * r
516 bx = math.cos(f32(-120.0 / 180.0 * math.PI)) * r
517 by = math.sin(f32(-120.0 / 180.0 * math.PI)) * r
518 nvg.BeginPath(ctx)
519 nvg.MoveTo(ctx, r, 0)
520 nvg.LineTo(ctx, ax, ay)
521 nvg.LineTo(ctx, bx, by)
522 nvg.ClosePath(ctx)
523 paint = nvg.LinearGradient(
524 r,
525 0,
526 ax,
527 ay,
528 nvg.HSLA(hue, 1.0, 0.5, 255),
529 nvg.RGBA(255, 255, 255, 255),
530 )
531 nvg.FillPaint(ctx, paint)
532 nvg.Fill(ctx)
533 paint = nvg.LinearGradient(
534 (r + ax) * 0.5,
535 (0 + ay) * 0.5,
536 bx,
537 by,
538 nvg.RGBA(0, 0, 0, 0),
539 nvg.RGBA(0, 0, 0, 255),
540 )
541 nvg.FillPaint(ctx, paint)
542 nvg.Fill(ctx)
543 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 64))
544 nvg.Stroke(ctx)
545
546
547 ax = math.cos(f32(120.0 / 180.0 * math.PI)) * r * 0.3
548 ay = math.sin(f32(120.0 / 180.0 * math.PI)) * r * 0.4
549 nvg.StrokeWidth(ctx, 2.0)
550 nvg.BeginPath(ctx)
551 nvg.Circle(ctx, ax, ay, 5)
552 nvg.StrokeColor(ctx, nvg.RGBA(255, 255, 255, 192))
553 nvg.Stroke(ctx)
554
555 paint = nvg.RadialGradient(ax, ay, 7, 9, nvg.RGBA(0, 0, 0, 64), nvg.RGBA(0, 0, 0, 0))
556 nvg.BeginPath(ctx)
557 nvg.Rect(ctx, ax - 20, ay - 20, 40, 40)
558 nvg.Circle(ctx, ax, ay, 7)
559 nvg.PathSolidity(ctx, .HOLE)
560 nvg.FillPaint(ctx, paint)
561 nvg.Fill(ctx)
562
563 nvg.Restore(ctx)
564
565
566 tw := f32(50)
567 th := f32(25)
568 r1 += 0.5 * math.sqrt(tw * tw + th * th)
569 nvg.BeginPath(ctx)
570 nvg.FillColor(ctx, nvg.RGB(32, 32, 32))
571 ax = cx + r1 * math.cos(hue * math.PI * 2)
572 ay = cy + r1 * math.sin(hue * math.PI * 2)
573 nvg.RoundedRect(ctx, ax - tw * 0.5, ay - th * 0.5, tw, th, 5.0)
574 nvg.Fill(ctx)
575
576 nvg.TextAlign(ctx, .CENTER, .MIDDLE)
577 nvg.FontSize(ctx, th)
578 nvg.FontFace(ctx, "sans")
579 nvg.FillColor(ctx, nvg.RGB(255, 255, 255))
580 text := fmt.tprintf("%d%%", int(100.0 * (hue + 1.0)) % 100)
581 nvg.BeginPath(ctx)
582 nvg.Text(ctx, ax, ay + 2.0, text)
583 nvg.Fill(ctx)
584
585 nvg.Restore(ctx)
586}
587
588drawLines :: proc(ctx: ^nvg.Context, x, y, w, h, t: f32) {
589 pad := f32(5.0)
590 s := w / 9.0 - pad * 2
591 pts: [4 * 2]f32
592 fx, fy: f32
593 joins := [3]nvg.LineCapType{.MITER, .ROUND, .BEVEL}
594 caps := [3]nvg.LineCapType{.BUTT, .ROUND, .SQUARE}
595
596 nvg.Save(ctx)
597 pts[0] = -s * 0.25 + math.cos(t * 0.3) * s * 0.5
598 pts[1] = math.sin(t * 0.3) * s * 0.5
599 pts[2] = -s * 0.25
600 pts[3] = 0
601 pts[4] = s * 0.25
602 pts[5] = 0
603 pts[6] = s * 0.25 + math.cos(-t * 0.3) * s * 0.5
604 pts[7] = math.sin(-t * 0.3) * s * 0.5
605
606 for i in 0 ..< 3 {
607 for j in 0 ..< 3 {
608 fx = x + s * 0.5 + f32(i * 3 + j) / 9.0 * w + pad
609 fy = y - s * 0.5 + pad
610
611 nvg.LineCap(ctx, caps[i])
612 nvg.LineJoin(ctx, joins[j])
613
614 nvg.StrokeWidth(ctx, s * 0.3)
615 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 160))
616 nvg.BeginPath(ctx)
617 nvg.MoveTo(ctx, fx + pts[0], fy + pts[1])
618 nvg.LineTo(ctx, fx + pts[2], fy + pts[3])
619 nvg.LineTo(ctx, fx + pts[4], fy + pts[5])
620 nvg.LineTo(ctx, fx + pts[6], fy + pts[7])
621 nvg.Stroke(ctx)
622
623 nvg.LineCap(ctx, .BUTT)
624 nvg.LineJoin(ctx, .BEVEL)
625
626 nvg.StrokeWidth(ctx, 1.0)
627 nvg.StrokeColor(ctx, nvg.RGBA(0, 192, 255, 255))
628 nvg.BeginPath(ctx)
629 nvg.MoveTo(ctx, fx + pts[0], fy + pts[1])
630 nvg.LineTo(ctx, fx + pts[2], fy + pts[3])
631 nvg.LineTo(ctx, fx + pts[4], fy + pts[5])
632 nvg.LineTo(ctx, fx + pts[6], fy + pts[7])
633 nvg.Stroke(ctx)
634 }
635 }
636
637 nvg.Restore(ctx)
638}
639
640drawWidths :: proc(ctx: ^nvg.Context, x, y, width: f32) {
641 nvg.Save(ctx)
642 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 255))
643 y := y
644
645 for i in 0 ..< 20 {
646 w := (f32(i) + 0.5) * 0.1
647 nvg.StrokeWidth(ctx, w)
648 nvg.BeginPath(ctx)
649 nvg.MoveTo(ctx, x, y)
650 nvg.LineTo(ctx, x + width, y + width * 0.3)
651 nvg.Stroke(ctx)
652 y += 10
653 }
654
655 nvg.Restore(ctx)
656}
657
658drawCaps :: proc(ctx: ^nvg.Context, x, y, width: f32) {
659 caps := [3]nvg.LineCapType{.BUTT, .ROUND, .SQUARE}
660 line_width := f32(8.0)
661
662 nvg.Save(ctx)
663
664 nvg.BeginPath(ctx)
665 nvg.Rect(ctx, x - line_width / 2, y, width + line_width, 40)
666 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 32))
667 nvg.Fill(ctx)
668
669 nvg.BeginPath(ctx)
670 nvg.Rect(ctx, x, y, width, 40)
671 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 32))
672 nvg.Fill(ctx)
673
674 nvg.StrokeWidth(ctx, line_width)
675 for i in 0 ..< 3 {
676 nvg.LineCap(ctx, caps[i])
677 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 255))
678 nvg.BeginPath(ctx)
679 nvg.MoveTo(ctx, x, y + f32(i) * 10 + 5)
680 nvg.LineTo(ctx, x + width, y + f32(i) * 10 + 5)
681 nvg.Stroke(ctx)
682 }
683
684 nvg.Restore(ctx)
685}
686
687drawScissor :: proc(ctx: ^nvg.Context, x, y, t: f32) {
688 nvg.Save(ctx)
689
690
691 nvg.Translate(ctx, x, y)
692 nvg.Rotate(ctx, nvg.DegToRad(5))
693 nvg.BeginPath(ctx)
694 nvg.Rect(ctx, -20, -20, 60, 40)
695 nvg.FillColor(ctx, nvg.RGBA(255, 0, 0, 255))
696 nvg.Fill(ctx)
697 nvg.Scissor(ctx, -20, -20, 60, 40)
698
699
700 nvg.Translate(ctx, 40, 0)
701 nvg.Rotate(ctx, t)
702
703
704 nvg.Save(ctx)
705 nvg.ResetScissor(ctx)
706 nvg.BeginPath(ctx)
707 nvg.Rect(ctx, -20, -10, 60, 30)
708 nvg.FillColor(ctx, nvg.RGBA(255, 128, 0, 64))
709 nvg.Fill(ctx)
710 nvg.Restore(ctx)
711
712
713 nvg.IntersectScissor(ctx, -20, -10, 60, 30)
714 nvg.BeginPath(ctx)
715 nvg.Rect(ctx, -20, -10, 60, 30)
716 nvg.FillColor(ctx, nvg.RGBA(255, 128, 0, 255))
717 nvg.Fill(ctx)
718
719 nvg.Restore(ctx)
720}
721
722Icon :: enum {
723 None = 0x0,
724 Search = 0x1F50D,
725 Circled_Cross = 0x2716,
726 Chevron_Right = 0xE75E,
727 Check = 0x2713,
728 Login = 0xE740,
729 Trash = 0xE729,
730}
731
732cpToUTF8 :: proc(icon: Icon, str: ^[8]u8) -> string {
733 cp := rune(icon)
734 bytes, size := utf8.encode_rune(cp)
735 for i in 0 ..< size {
736 str[i] = bytes[i]
737 }
738 return string(str[:size])
739}
740
741drawSearchBox :: proc(ctx: ^nvg.Context, text: string, x, y, w, h: f32) {
742 cornerRadius := f32(h) / 2 - 1
743
744
745 bg := nvg.BoxGradient(x, y + 1.5, w, h, h / 2, 5, nvg.RGBA(0, 0, 0, 16), nvg.RGBA(0, 0, 0, 92))
746 nvg.BeginPath(ctx)
747 nvg.RoundedRect(ctx, x, y, w, h, cornerRadius)
748 nvg.FillPaint(ctx, bg)
749 nvg.Fill(ctx)
750
751 nvg.FontSize(ctx, h * 1.3)
752 nvg.FontFace(ctx, "icons")
753 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 64))
754 nvg.TextAlign(ctx, .CENTER, .MIDDLE)
755 nvg.TextIcon(ctx, x + h * 0.55, y + h * 0.55, rune(Icon.Search))
756
757 nvg.FontSize(ctx, 17.0)
758 nvg.FontFace(ctx, "sans")
759 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 32))
760 nvg.TextAlign(ctx, .LEFT, .MIDDLE)
761 nvg.Text(ctx, x + h * 1.05, y + h * 0.5, text)
762
763 nvg.FontSize(ctx, h * 1.3)
764 nvg.FontFace(ctx, "icons")
765 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 32))
766 nvg.TextAlign(ctx, .CENTER, .MIDDLE)
767 nvg.TextIcon(ctx, x + w - h * 0.55, y + h * 0.55, rune(Icon.Circled_Cross))
768}
769
770drawDropDown :: proc(ctx: ^nvg.Context, text: string, x, y, w, h: f32) {
771 cornerRadius := f32(4.0)
772
773 bg := nvg.LinearGradient(x, y, x, y + h, nvg.RGBA(255, 255, 255, 16), nvg.RGBA(0, 0, 0, 16))
774 nvg.BeginPath(ctx)
775 nvg.RoundedRect(ctx, x + 1, y + 1, w - 2, h - 2, cornerRadius - 1)
776 nvg.FillPaint(ctx, bg)
777 nvg.Fill(ctx)
778
779 nvg.BeginPath(ctx)
780 nvg.RoundedRect(ctx, x + 0.5, y + 0.5, w - 1, h - 1, cornerRadius - 0.5)
781 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 48))
782 nvg.Stroke(ctx)
783
784 nvg.FontSize(ctx, 17.0)
785 nvg.FontFace(ctx, "sans")
786 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 160))
787 nvg.TextAlign(ctx, .LEFT, .MIDDLE)
788 nvg.Text(ctx, x + h * 0.3, y + h * 0.5, text)
789
790 nvg.FontSize(ctx, h * 1.3)
791 nvg.FontFace(ctx, "icons")
792 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 64))
793 nvg.TextAlign(ctx, .CENTER, .MIDDLE)
794 nvg.TextIcon(ctx, x + w - h * 0.5, y + h * 0.5, rune(Icon.Chevron_Right))
795}
796
797drawLabel :: proc(ctx: ^nvg.Context, text: string, x, y, w, h: f32) {
798 nvg.FontSize(ctx, 15.0)
799 nvg.FontFace(ctx, "sans")
800 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 128))
801
802 nvg.TextAlign(ctx, .LEFT, .MIDDLE)
803 nvg.Text(ctx, x, y + h * 0.5, text)
804}
805
806drawEditBoxBase :: proc(ctx: ^nvg.Context, x, y, w, h: f32) {
807
808 bg := nvg.BoxGradient(
809 x + 1,
810 y + 1 + 1.5,
811 w - 2,
812 h - 2,
813 3,
814 4,
815 nvg.RGBA(255, 255, 255, 32),
816 nvg.RGBA(32, 32, 32, 32),
817 )
818 nvg.BeginPath(ctx)
819 nvg.RoundedRect(ctx, x + 1, y + 1, w - 2, h - 2, 4 - 1)
820 nvg.FillPaint(ctx, bg)
821 nvg.Fill(ctx)
822
823 nvg.BeginPath(ctx)
824 nvg.RoundedRect(ctx, x + 0.5, y + 0.5, w - 1, h - 1, 4 - 0.5)
825 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 48))
826 nvg.Stroke(ctx)
827}
828
829drawEditBox :: proc(ctx: ^nvg.Context, text: string, x, y, w, h: f32) {
830
831 drawEditBoxBase(ctx, x, y, w, h)
832
833 nvg.FontSize(ctx, 17.0)
834 nvg.FontFace(ctx, "sans")
835 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 64))
836 nvg.TextAlign(ctx, .LEFT, .MIDDLE)
837 nvg.Text(ctx, x + h * 0.3, y + h * 0.5, text)
838}
839
840drawEditBoxNum :: proc(ctx: ^nvg.Context, text: string, units: string, x, y, w, h: f32) {
841 drawEditBoxBase(ctx, x, y, w, h)
842
843 uw := nvg.TextBounds(ctx, 0, 0, units)
844
845 nvg.FontSize(ctx, 15.0)
846 nvg.FontFace(ctx, "sans")
847 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 64))
848 nvg.TextAlign(ctx, .RIGHT, .MIDDLE)
849 nvg.Text(ctx, x + w - h * 0.3, y + h * 0.5, units)
850
851 nvg.FontSize(ctx, 17.0)
852 nvg.FontFace(ctx, "sans")
853 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 128))
854 nvg.TextAlign(ctx, .RIGHT, .MIDDLE)
855 nvg.Text(ctx, x + w - uw - h * 0.5, y + h * 0.5, text)
856}
857
858drawCheckBox :: proc(ctx: ^nvg.Context, text: string, x, y, w, h: f32) {
859 nvg.FontSize(ctx, 15.0)
860 nvg.FontFace(ctx, "sans")
861 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 160))
862
863 nvg.TextAlign(ctx, .LEFT, .MIDDLE)
864 nvg.Text(ctx, x + 28, y + h * 0.5, text)
865
866 bg := nvg.BoxGradient(
867 x + 1,
868 y + (h * 0.5) - 9 + 1,
869 18,
870 18,
871 3,
872 3,
873 nvg.RGBA(0, 0, 0, 32),
874 nvg.RGBA(0, 0, 0, 92),
875 )
876 nvg.BeginPath(ctx)
877 nvg.RoundedRect(ctx, x + 1, y + (h * 0.5) - 9, 18, 18, 3)
878 nvg.FillPaint(ctx, bg)
879 nvg.Fill(ctx)
880
881 nvg.FontSize(ctx, 33)
882 nvg.FontFace(ctx, "icons")
883 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 128))
884 nvg.TextAlign(ctx, .CENTER, .MIDDLE)
885 nvg.TextIcon(ctx, x + 9 + 2, y + h * 0.5, rune(Icon.Check))
886}
887
888isBlack :: #force_inline proc(col: nvg.Color) -> bool {
889 return col == {}
890}
891
892drawButton :: proc(
893 ctx: ^nvg.Context,
894 preicon: Icon,
895 text: string,
896 x, y, w, h: f32,
897 col: nvg.Color,
898) {
899 icon: [8]u8
900 cornerRadius := f32(4.0)
901 tw, iw: f32
902
903 bg := nvg.LinearGradient(
904 x,
905 y,
906 x,
907 y + h,
908 nvg.RGBA(255, 255, 255, isBlack(col) ? 16 : 32),
909 nvg.RGBA(0, 0, 0, isBlack(col) ? 16 : 32),
910 )
911 nvg.BeginPath(ctx)
912 nvg.RoundedRect(ctx, x + 1, y + 1, w - 2, h - 2, cornerRadius - 1)
913 if (!isBlack(col)) {
914 nvg.FillColor(ctx, col)
915 nvg.Fill(ctx)
916 }
917 nvg.FillPaint(ctx, bg)
918 nvg.Fill(ctx)
919
920 nvg.BeginPath(ctx)
921 nvg.RoundedRect(ctx, x + 0.5, y + 0.5, w - 1, h - 1, cornerRadius - 0.5)
922 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 48))
923 nvg.Stroke(ctx)
924
925 nvg.FontSize(ctx, 17.0)
926 nvg.FontFace(ctx, "sans-bold")
927 tw = nvg.TextBounds(ctx, 0, 0, text)
928 if preicon != .None {
929 nvg.FontSize(ctx, h * 1.3)
930 nvg.FontFace(ctx, "icons")
931 iw = nvg.TextBounds(ctx, 0, 0, cpToUTF8(preicon, &icon))
932 iw += h * 0.15
933 }
934
935 if preicon != .None {
936 nvg.FontSize(ctx, h * 1.3)
937 nvg.FontFace(ctx, "icons")
938 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 96))
939 nvg.TextAlign(ctx, .LEFT, .MIDDLE)
940 nvg.TextIcon(ctx, x + w * 0.5 - tw * 0.5 - iw * 0.75, y + h * 0.5, rune(preicon))
941 }
942
943 nvg.FontSize(ctx, 17.0)
944 nvg.FontFace(ctx, "sans-bold")
945 nvg.TextAlign(ctx, .LEFT, .MIDDLE)
946 nvg.FillColor(ctx, nvg.RGBA(0, 0, 0, 160))
947 nvg.Text(ctx, x + w * 0.5 - tw * 0.5 + iw * 0.25, y + h * 0.5 - 1, text)
948 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 160))
949 nvg.Text(ctx, x + w * 0.5 - tw * 0.5 + iw * 0.25, y + h * 0.5, text)
950}
951
952drawSlider :: proc(ctx: ^nvg.Context, pos, x, y, w, h: f32) {
953 cy := y + math.round(h * 0.5)
954 kr := math.round(h * 0.25)
955
956 nvg.Save(ctx)
957
958
959 bg := nvg.BoxGradient(x, cy - 2 + 1, w, 4, 2, 2, nvg.RGBA(0, 0, 0, 32), nvg.RGBA(0, 0, 0, 128))
960 nvg.BeginPath(ctx)
961 nvg.RoundedRect(ctx, x, cy - 2, w, 4, 2)
962 nvg.FillPaint(ctx, bg)
963 nvg.Fill(ctx)
964
965
966 bg = nvg.RadialGradient(
967 x + math.round(pos * w),
968 cy + 1,
969 kr - 3,
970 kr + 3,
971 nvg.RGBA(0, 0, 0, 64),
972 nvg.RGBA(0, 0, 0, 0),
973 )
974 nvg.BeginPath(ctx)
975 nvg.Rect(
976 ctx,
977 x + math.round(pos * w) - kr - 5,
978 cy - kr - 5,
979 kr * 2 + 5 + 5,
980 kr * 2 + 5 + 5 + 3,
981 )
982 nvg.Circle(ctx, x + math.round(pos * w), cy, kr)
983 nvg.PathSolidity(ctx, .HOLE)
984 nvg.FillPaint(ctx, bg)
985 nvg.Fill(ctx)
986
987
988 knob := nvg.LinearGradient(
989 x,
990 cy - kr,
991 x,
992 cy + kr,
993 nvg.RGBA(255, 255, 255, 16),
994 nvg.RGBA(0, 0, 0, 16),
995 )
996 nvg.BeginPath(ctx)
997 nvg.Circle(ctx, x + math.round(pos * w), cy, kr - 1)
998 nvg.FillColor(ctx, nvg.RGBA(40, 43, 48, 255))
999 nvg.Fill(ctx)
1000 nvg.FillPaint(ctx, knob)
1001 nvg.Fill(ctx)
1002
1003 nvg.BeginPath(ctx)
1004 nvg.Circle(ctx, x + math.round(pos * w), cy, kr - 0.5)
1005 nvg.StrokeColor(ctx, nvg.RGBA(0, 0, 0, 92))
1006 nvg.Stroke(ctx)
1007
1008 nvg.Restore(ctx)
1009}
1010
1011drawSpinner :: proc(ctx: ^nvg.Context, cx, cy, r, t: f32) {
1012 a0 := 0.0 + t * 6
1013 a1 := math.PI + t * 6
1014 r0 := r
1015 r1 := r * 0.75
1016
1017 nvg.Save(ctx)
1018
1019 nvg.BeginPath(ctx)
1020 nvg.Arc(ctx, cx, cy, r0, a0, a1, .CW)
1021 nvg.Arc(ctx, cx, cy, r1, a1, a0, .CCW)
1022 nvg.ClosePath(ctx)
1023 ax := cx + math.cos(a0) * (r0 + r1) * 0.5
1024 ay := cy + math.sin(a0) * (r0 + r1) * 0.5
1025 bx := cx + math.cos(a1) * (r0 + r1) * 0.5
1026 by := cy + math.sin(a1) * (r0 + r1) * 0.5
1027 paint := nvg.LinearGradient(ax, ay, bx, by, nvg.RGBA(0, 0, 0, 0), nvg.RGBA(0, 0, 0, 128))
1028 nvg.FillPaint(ctx, paint)
1029 nvg.Fill(ctx)
1030
1031 nvg.Restore(ctx)
1032}
1033
1034drawThumbnails :: proc(ctx: ^nvg.Context, x, y, w, h: f32, images: []int, t: f32) {
1035 cornerRadius := f32(3.0)
1036 thumb := f32(60.0)
1037 arry := f32(30.5)
1038 stackh := (f32(len(images)) / 2) * (thumb + 10) + 10
1039 u := (1 + math.cos(t * 0.5)) * 0.5
1040 u2 := (1 - math.cos(t * 0.2)) * 0.5
1041
1042 nvg.Save(ctx)
1043
1044
1045 shadowPaint := nvg.BoxGradient(
1046 x,
1047 y + 4,
1048 w,
1049 h,
1050 cornerRadius * 2,
1051 20,
1052 nvg.RGBA(0, 0, 0, 128),
1053 nvg.RGBA(0, 0, 0, 0),
1054 )
1055 nvg.BeginPath(ctx)
1056 nvg.Rect(ctx, x - 10, y - 10, w + 20, h + 30)
1057 nvg.RoundedRect(ctx, x, y, w, h, cornerRadius)
1058 nvg.PathSolidity(ctx, .HOLE)
1059 nvg.FillPaint(ctx, shadowPaint)
1060 nvg.Fill(ctx)
1061
1062
1063 nvg.BeginPath(ctx)
1064 nvg.RoundedRect(ctx, x, y, w, h, cornerRadius)
1065 nvg.MoveTo(ctx, x - 10, y + arry)
1066 nvg.LineTo(ctx, x + 1, y + arry - 11)
1067 nvg.LineTo(ctx, x + 1, y + arry + 11)
1068 nvg.FillColor(ctx, nvg.RGBA(200, 200, 200, 255))
1069 nvg.Fill(ctx)
1070
1071 nvg.Save(ctx)
1072 nvg.Scissor(ctx, x, y, w, h)
1073 nvg.Translate(ctx, 0, -(stackh - h) * u)
1074
1075 dv := 1.0 / f32(len(images) - 1)
1076 ix, iy, iw, ih: f32
1077
1078 for i in 0 ..< len(images) {
1079 tx := x + 10
1080 ty := y + 10
1081 tx += f32(i % 2) * (thumb + 10)
1082 ty += f32(i / 2) * (thumb + 10)
1083 imgw, imgh := nvg.ImageSize(ctx, images[i])
1084 if imgw < imgh {
1085 iw = thumb
1086 ih = iw * f32(imgh) / f32(imgw)
1087 ix = 0
1088 iy = -(ih - thumb) * 0.5
1089 } else {
1090 ih = thumb
1091 iw = ih * f32(imgw) / f32(imgh)
1092 ix = -(iw - thumb) * 0.5
1093 iy = 0
1094 }
1095
1096 v := f32(i) * dv
1097 a := clamp((u2 - v) / dv, 0, 1)
1098
1099 if a < 1.0 {
1100 drawSpinner(ctx, tx + thumb / 2, ty + thumb / 2, thumb * 0.25, t)
1101 }
1102
1103 imgPaint := nvg.ImagePattern(tx + ix, ty + iy, iw, ih, 0.0 / 180.0 * math.PI, images[i], a)
1104 nvg.BeginPath(ctx)
1105 nvg.RoundedRect(ctx, tx, ty, thumb, thumb, 5)
1106 nvg.FillPaint(ctx, imgPaint)
1107 nvg.Fill(ctx)
1108
1109 shadowPaint = nvg.BoxGradient(
1110 tx - 1,
1111 ty,
1112 thumb + 2,
1113 thumb + 2,
1114 5,
1115 3,
1116 nvg.RGBA(0, 0, 0, 128),
1117 nvg.RGBA(0, 0, 0, 0),
1118 )
1119 nvg.BeginPath(ctx)
1120 nvg.Rect(ctx, tx - 5, ty - 5, thumb + 10, thumb + 10)
1121 nvg.RoundedRect(ctx, tx, ty, thumb, thumb, 6)
1122 nvg.PathSolidity(ctx, .HOLE)
1123 nvg.FillPaint(ctx, shadowPaint)
1124 nvg.Fill(ctx)
1125
1126 nvg.BeginPath(ctx)
1127 nvg.RoundedRect(ctx, tx + 0.5, ty + 0.5, thumb - 1, thumb - 1, 4 - 0.5)
1128 nvg.StrokeWidth(ctx, 1.0)
1129 nvg.StrokeColor(ctx, nvg.RGBA(255, 255, 255, 192))
1130 nvg.Stroke(ctx)
1131 }
1132 nvg.Restore(ctx)
1133
1134
1135 fadePaint := nvg.LinearGradient(
1136 x,
1137 y,
1138 x,
1139 y + 6,
1140 nvg.RGBA(200, 200, 200, 255),
1141 nvg.RGBA(200, 200, 200, 0),
1142 )
1143 nvg.BeginPath(ctx)
1144 nvg.Rect(ctx, x + 4, y, w - 8, 6)
1145 nvg.FillPaint(ctx, fadePaint)
1146 nvg.Fill(ctx)
1147
1148 fadePaint = nvg.LinearGradient(
1149 x,
1150 y + h,
1151 x,
1152 y + h - 6,
1153 nvg.RGBA(200, 200, 200, 255),
1154 nvg.RGBA(200, 200, 200, 0),
1155 )
1156 nvg.BeginPath(ctx)
1157 nvg.Rect(ctx, x + 4, y + h - 6, w - 8, 6)
1158 nvg.FillPaint(ctx, fadePaint)
1159 nvg.Fill(ctx)
1160
1161
1162 shadowPaint = nvg.BoxGradient(
1163 x + w - 12 + 1,
1164 y + 4 + 1,
1165 8,
1166 h - 8,
1167 3,
1168 4,
1169 nvg.RGBA(0, 0, 0, 32),
1170 nvg.RGBA(0, 0, 0, 92),
1171 )
1172 nvg.BeginPath(ctx)
1173 nvg.RoundedRect(ctx, x + w - 12, y + 4, 8, h - 8, 3)
1174 nvg.FillPaint(ctx, shadowPaint)
1175 nvg.Fill(ctx)
1176
1177 scrollh := (h / stackh) * (h - 8)
1178 shadowPaint = nvg.BoxGradient(
1179 x + w - 12 - 1,
1180 y + 4 + (h - 8 - scrollh) * u - 1,
1181 8,
1182 scrollh,
1183 3,
1184 4,
1185 nvg.RGBA(220, 220, 220, 255),
1186 nvg.RGBA(128, 128, 128, 255),
1187 )
1188 nvg.BeginPath(ctx)
1189 nvg.RoundedRect(ctx, x + w - 12 + 1, y + 4 + 1 + (h - 8 - scrollh) * u, 8 - 2, scrollh - 2, 2)
1190 nvg.FillPaint(ctx, shadowPaint)
1191 nvg.Fill(ctx)
1192
1193 nvg.Restore(ctx)
1194}
1195
1196drawParagraph :: proc(ctx: ^nvg.Context, x, y, width, height: f32, mx, my: f32) {
1197 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.🎉"
1198 hoverText := "Hover your mouse over the text to see calculated caret position."
1199 nvg.Save(ctx)
1200 defer nvg.Restore(ctx)
1201
1202 nvg.FontSize(ctx, 15.0)
1203 nvg.FontFace(ctx, "sans")
1204 nvg.TextAlign(ctx, .LEFT, .TOP)
1205 _, _, lineh := nvg.TextMetrics(ctx)
1206
1207
1208
1209
1210 input := text
1211 rows: [3]nvg.Text_Row
1212 rows_mod := rows[:]
1213 lnum, gutter: int
1214 y := y
1215 gx, gy: f32
1216
1217 positions: [100]nvg.Glyph_Position
1218 glyphs := positions[:]
1219
1220 for nrows, input_last in nvg.TextBreakLines(ctx, &input, width, &rows_mod) {
1221 for i in 0 ..< nrows {
1222 row := &rows[i]
1223 hit := mx > x && mx < (x + width) && my >= y && my < (y + lineh)
1224
1225 nvg.BeginPath(ctx)
1226 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, hit ? 64 : 16))
1227 nvg.Rect(ctx, x + row.minx, y, row.maxx - row.minx, lineh)
1228 nvg.Fill(ctx)
1229
1230 nvg.FillColor(ctx, nvg.RGBA(255, 255, 255, 255))
1231 text = input_last[row.start:row.end]
1232 nvg.Text(ctx, x, y, text)
1233
1234 if hit {
1235 caretx := (mx < x + row.width / 2) ? x : x + row.width
1236 px := x
1237 nglyphs := nvg.TextGlyphPositions(ctx, x, y, text, &glyphs)
1238
1239 for j in 0 ..< nglyphs {
1240 x0 := glyphs[j].x
1241 x1 := (j + 1 < nglyphs) ? glyphs[j + 1].x : x + row.width
1242 gx = x0 * 0.3 + x1 * 0.7
1243 if mx >= px && mx < gx {
1244 caretx = glyphs[j].x
1245 }
1246
1247 px = gx
1248 }
1249
1250 nvg.BeginPath(ctx)
1251 nvg.FillColor(ctx, nvg.RGBA(255, 192, 0, 255))
1252 nvg.Rect(ctx, caretx, y, 1, lineh)
1253 nvg.Fill(ctx)
1254
1255 gutter = lnum + 1
1256 gx = x - 10
1257 gy = y + lineh / 2
1258 }
1259
1260 lnum += 1
1261 y += lineh
1262 }
1263 }
1264
1265 bounds: [4]f32
1266
1267 if false {
1268 txt := fmt.tprintf("%d", gutter)
1269 nvg.FontSize(ctx, 12.0)
1270 nvg.TextAlign(ctx, .RIGHT, .MIDDLE)
1271
1272 nvg.TextBounds(ctx, gx, gy, txt, &bounds)
1273
1274 nvg.BeginPath(ctx)
1275 nvg.FillColor(ctx, nvg.RGBA(255, 192, 0, 255))
1276 nvg.RoundedRect(
1277 ctx,
1278 math.round(bounds[0]) - 4,
1279 math.round(bounds[1]) - 2,
1280 math.round(bounds[2] - bounds[0]) + 8,
1281 math.round(bounds[3] - bounds[1]) + 4,
1282 (math.round(bounds[3] - bounds[1]) + 4) / 2 - 1,
1283 )
1284 nvg.Fill(ctx)
1285
1286 nvg.FillColor(ctx, nvg.RGBA(32, 32, 32, 255))
1287 nvg.Text(ctx, gx, gy, txt)
1288 }
1289
1290 y += 20.0
1291
1292 nvg.FontSize(ctx, 11.0)
1293 nvg.TextAlign(ctx, .LEFT, .TOP)
1294 nvg.TextLineHeight(ctx, 1.2)
1295
1296 nvg.TextBoxBounds(ctx, x, y, 150, hoverText, &bounds)
1297
1298
1299 gx = clamp(mx, bounds[0], bounds[2]) - mx
1300 gy = clamp(my, bounds[1], bounds[3]) - my
1301 a := math.sqrt(gx * gx + gy * gy) / 30.0
1302 a = clamp(a, 0, 1)
1303 nvg.GlobalAlpha(ctx, a)
1304
1305 nvg.BeginPath(ctx)
1306 nvg.FillColor(ctx, nvg.RGBA(220, 220, 220, 255))
1307 nvg.RoundedRect(
1308 ctx,
1309 bounds[0] - 2,
1310 bounds[1] - 2,
1311 math.round(bounds[2] - bounds[0]) + 4,
1312 math.round(bounds[3] - bounds[1]) + 4,
1313 3,
1314 )
1315 px := math.round((bounds[2] + bounds[0]) / 2)
1316 nvg.MoveTo(ctx, px, bounds[1] - 10)
1317 nvg.LineTo(ctx, px + 7, bounds[1] + 1)
1318 nvg.LineTo(ctx, px - 7, bounds[1] + 1)
1319 nvg.Fill(ctx)
1320
1321 nvg.FillColor(ctx, nvg.RGBA(0, 0, 0, 220))
1322 nvg.TextBox(ctx, x, y, 150, hoverText)
1323}
1324
1325renderDemo :: proc(
1326 ctx: ^nvg.Context,
1327 mx, my: f32,
1328 width, height: f32,
1329 t: f32,
1330 blowup: bool,
1331 data: ^DemoData,
1332) {
1333 drawEyes(ctx, width - 250, 50, 150, 100, mx, my, t)
1334 drawParagraph(ctx, width - 450, 50, 150, 100, mx, my)
1335 drawGraph(ctx, 0, height / 2, width, height / 2, t)
1336 drawColorwheel(ctx, width - 300, height - 300, 250.0, 250.0, t)
1337
1338
1339 drawLines(ctx, 120, height - 50, 600, 50, t)
1340
1341
1342 drawWidths(ctx, 10, 50, 30)
1343
1344
1345 drawCaps(ctx, 10, 300, 30)
1346
1347 drawScissor(ctx, 50, height - 80, t)
1348
1349 nvg.Save(ctx)
1350 if blowup {
1351 nvg.Rotate(ctx, math.sin(t * 0.3) * 5.0 / 180.0 * math.PI)
1352 nvg.Scale(ctx, 2.0, 2.0)
1353 }
1354
1355
1356 drawWindow(ctx, "Widgets `n Stuff", 50, 50, 300, 400)
1357 x := f32(60)
1358 y := f32(95)
1359 drawSearchBox(ctx, "Search", x, y, 280, 25)
1360 y += 40
1361 drawDropDown(ctx, "Effects", x, y, 280, 28)
1362
1363 popy := y + 14
1364 y += 45
1365
1366 drawLabel(ctx, "Login", x, y, 280, 20)
1367 y += 25
1368 drawEditBox(ctx, "Email", x, y, 280, 28)
1369 y += 35
1370 drawEditBox(ctx, "Password", x, y, 280, 28)
1371 y += 38
1372 drawCheckBox(ctx, "Remember me", x, y, 140, 28)
1373 drawButton(ctx, .Login, "Sign in", x + 138, y, 140, 28, nvg.RGBA(0, 96, 128, 255))
1374 y += 45
1375
1376
1377 drawLabel(ctx, "Diameter", x, y, 280, 20)
1378 y += 25
1379 drawEditBoxNum(ctx, "123.00", "px", x + 180, y, 100, 28)
1380 drawSlider(ctx, 0.4, x, y, 170, 28)
1381 y += 55
1382
1383 drawButton(ctx, .Trash, "Delete", x, y, 160, 28, nvg.RGBA(128, 16, 8, 255))
1384 drawButton(ctx, .None, "Cancel", x + 170, y, 110, 28, nvg.RGBA(0, 0, 0, 0))
1385
1386
1387 drawThumbnails(ctx, 365, popy - 30, 160, 300, data.images[:], t)
1388
1389 nvg.Restore(ctx)
1390}
1391
1392loadDemoData :: proc(ctx: ^nvg.Context, data: ^DemoData) {
1393 if ctx == nil {
1394 panic("loadDemoData failed! ctx was nil")
1395 }
1396
1397 builder := strings.builder_make(0, 128, context.temp_allocator)
1398 for i in 0 ..< 12 {
1399
1400 strings.builder_reset(&builder)
1401 fmt.sbprintf(&builder, "images/image%d.jpg", i + 1)
1402 strings.write_byte(&builder, 0)
1403 file := strings.unsafe_string_to_cstring(strings.to_string(builder))
1404
1405 data.images[i] = nvg.CreateImage(ctx, file, {})
1406 if data.images[i] == 0 {
1407 fmt.panicf("Could not load %s.", file)
1408 }
1409 }
1410
1411 data.fontIcons = nvg.CreateFont(ctx, "icons", "fonts/entypo.ttf")
1412 if (data.fontIcons == -1) {
1413 panic("Could not add font icons.")
1414 }
1415 data.fontNormal = nvg.CreateFont(ctx, "sans", "fonts/Roboto-Regular.ttf")
1416 if (data.fontNormal == -1) {
1417 panic("Could not add font italic.")
1418 }
1419 data.fontBold = nvg.CreateFont(ctx, "sans-bold", "fonts/Roboto-Bold.ttf")
1420 if (data.fontBold == -1) {
1421 panic("Could not add font bold.")
1422 }
1423 data.fontEmoji = nvg.CreateFont(ctx, "emoji", "fonts/NotoEmoji-Regular.ttf")
1424 if (data.fontEmoji == -1) {
1425 panic("Could not add font emoji.")
1426 }
1427
1428 nvg.AddFallbackFontId(ctx, data.fontNormal, data.fontEmoji)
1429 nvg.AddFallbackFontId(ctx, data.fontBold, data.fontEmoji)
1430}
1431
1432freeDemoData :: proc(ctx: ^nvg.Context, data: ^DemoData) {
1433 if ctx == nil {
1434 return
1435 }
1436
1437 for i in 0 ..< 12 {
1438 nvg.DeleteImage(ctx, data.images[i])
1439 }
1440}
1441
1442mini :: #force_inline proc(a, b: int) -> int {
1443 return a < b ? a : b
1444}
1445
1446unpremultiplyAlpha :: proc(image: []byte, w, h, stride: int) {
1447
1448 for y in 0 ..< h {
1449 row := image[y * stride:]
1450
1451 for _ in 0 ..< w {
1452 r := row[0]
1453 g := row[1]
1454 b := row[2]
1455 a := row[3]
1456
1457 if a != 0 {
1458 row[0] = u8(mini(int(r) * 255 / int(a), 255))
1459 row[1] = u8(mini(int(g) * 255 / int(a), 255))
1460 row[2] = u8(mini(int(b) * 255 / int(a), 255))
1461 }
1462
1463 row = row[4:]
1464 }
1465 }
1466
1467
1468 for y in 0 ..< h {
1469 row_index := y * stride
1470
1471 for x in 0 ..< w {
1472 row := image[row_index:]
1473 r := 0
1474 g := 0
1475 b := 0
1476 a := row[3]
1477 n := 0
1478
1479 if a == 0 {
1480 if x - 1 > 0 && image[row_index - 1] != 0 {
1481 r += int(image[row_index - 4])
1482 g += int(image[row_index - 3])
1483 b += int(image[row_index - 2])
1484 n += 1
1485 }
1486 if x + 1 < w && row[7] != 0 {
1487 r += int(row[4])
1488 g += int(row[5])
1489 b += int(row[6])
1490 n += 1
1491 }
1492 if y - 1 > 0 && image[row_index - stride + 3] != 0 {
1493 r += int(image[row_index - stride])
1494 g += int(image[row_index - stride + 1])
1495 b += int(image[row_index - stride + 2])
1496 n += 1
1497 }
1498 if y + 1 < h && row[stride + 3] != 0 {
1499 r += int(row[stride])
1500 g += int(row[stride + 1])
1501 b += int(row[stride + 2])
1502 n += 1
1503 }
1504 if n > 0 {
1505 row[0] = u8(f32(r) / f32(n))
1506 row[1] = u8(f32(g) / f32(n))
1507 row[2] = u8(f32(b) / f32(n))
1508 }
1509 }
1510
1511 row_index += 4
1512 }
1513 }
1514}
1515
1516setAlpha :: proc(image: []byte, w, h, stride: int, a: u8) {
1517 for y in 0 ..< h {
1518 row := image[y * stride:]
1519
1520 for x in 0 ..< w {
1521 row[x * 4 + 3] = a
1522 }
1523 }
1524}
1525
1526flipHorizontal :: proc(image: []byte, w, h, stride: int) {
1527 i: int
1528 j := h - 1
1529
1530 for i < j {
1531 ri := image[i * stride:]
1532 rj := image[j * stride:]
1533
1534 for k in 0 ..< (w * 4) {
1535 ri[k], rj[k] = rj[k], ri[k]
1536 }
1537
1538 i += 1
1539 j -= 1
1540 }
1541}
1542
1543saveScreenshot :: proc(w, h: int, premult: bool, name: cstring) {
1544 image := make([]byte, w * h * 4)
1545 defer delete(image)
1546
1547 gl.ReadPixels(0, 0, i32(w), i32(h), gl.RGBA, gl.UNSIGNED_BYTE, raw_data(image))
1548 if premult {
1549 unpremultiplyAlpha(image, w, h, w * 4)
1550 } else {
1551 setAlpha(image, w, h, w * 4, 255)
1552 }
1553 flipHorizontal(image, w, h, w * 4)
1554 stbi.write_png(name, i32(w), i32(h), 4, raw_data(image), i32(w) * 4)
1555}
1556
1557GRAPH_HISTORY_COUNT :: 100
1558
1559GraphrenderStyle :: enum {
1560 FPS,
1561 MS,
1562 PERCENT,
1563}
1564
1565PerfGraph :: struct {
1566 style: GraphrenderStyle,
1567 name: string,
1568 values: [GRAPH_HISTORY_COUNT]f32,
1569 head: int,
1570}
1571
1572initGraph :: proc(fps: ^PerfGraph, style: GraphrenderStyle, name: string) {
1573 fps.style = style
1574 fps.name = name
1575}
1576
1577updateGraph :: proc(fps: ^PerfGraph, frameTime: f32) {
1578 fps.head = (fps.head + 1) % GRAPH_HISTORY_COUNT
1579 fps.values[fps.head] = frameTime
1580}
1581
1582renderGraph :: proc(ctx: ^nvg.Context, x, y: f32, fps: ^PerfGraph) {
1583 avg := getGraphAverage(fps)
1584 w := f32(200)
1585 h := f32(35)
1586
1587 nvg.BeginPath(ctx)
1588 nvg.Rect(ctx, x, y, w, h)
1589 nvg.FillColor(ctx, nvg.RGBA(0, 0, 0, 128))
1590 nvg.Fill(ctx)
1591
1592 nvg.BeginPath(ctx)
1593 nvg.MoveTo(ctx, x, y + h)
1594
1595 switch fps.style {
1596 case .FPS:
1597 {
1598 for i in 0 ..< GRAPH_HISTORY_COUNT {
1599 v := 1.0 / (0.00001 + fps.values[(fps.head + i) % GRAPH_HISTORY_COUNT])
1600 if v > 80.0 {
1601 v = 80.0
1602 }
1603 vx := x + (f32(i) / (GRAPH_HISTORY_COUNT - 1)) * w
1604 vy := y + h - ((v / 80.0) * h)
1605 nvg.LineTo(ctx, vx, vy)
1606 }
1607 }
1608
1609 case .PERCENT:
1610 {
1611 for i in 0 ..< GRAPH_HISTORY_COUNT {
1612 v := fps.values[(fps.head + i) % GRAPH_HISTORY_COUNT] * 1.0
1613 if v > 100.0 {
1614 v = 100.0
1615 }
1616 vx := x + (f32(i) / (GRAPH_HISTORY_COUNT - 1)) * w
1617 vy := y + h - ((v / 100.0) * h)
1618 nvg.LineTo(ctx, vx, vy)
1619 }
1620 }
1621
1622 case .MS:
1623 {
1624 for i in 0 ..< GRAPH_HISTORY_COUNT {
1625 v := fps.values[(fps.head + i) % GRAPH_HISTORY_COUNT] * 1000.0
1626 if v > 20.0 {
1627 v = 20.0
1628 }
1629 vx := x + (f32(i) / (GRAPH_HISTORY_COUNT - 1)) * w
1630 vy := y + h - ((v / 20.0) * h)
1631 nvg.LineTo(ctx, vx, vy)
1632 }
1633 }
1634 }
1635
1636 nvg.LineTo(ctx, x + w, y + h)
1637 nvg.FillColor(ctx, nvg.RGBA(255, 192, 0, 128))
1638 nvg.Fill(ctx)
1639
1640 nvg.FontFace(ctx, "sans")
1641
1642 if fps.name != "" {
1643 nvg.FontSize(ctx, 12.0)
1644 nvg.TextAlign(ctx, .LEFT, .TOP)
1645 nvg.FillColor(ctx, nvg.RGBA(240, 240, 240, 192))
1646 nvg.Text(ctx, x + 3, y + 3, fps.name)
1647 }
1648
1649 switch fps.style {
1650 case .FPS:
1651 {
1652 nvg.FontSize(ctx, 15.0)
1653 nvg.TextAlign(ctx, .RIGHT, .TOP)
1654 nvg.FillColor(ctx, nvg.RGBA(240, 240, 240, 255))
1655 str := fmt.tprintf("%.2f FPS", 1.0 / avg)
1656 nvg.Text(ctx, x + w - 3, y + 3, str)
1657
1658 nvg.FontSize(ctx, 13.0)
1659 nvg.TextAlign(ctx, .RIGHT, .BASELINE)
1660 nvg.FillColor(ctx, nvg.RGBA(240, 240, 240, 160))
1661 str = fmt.tprintf("%.2f ms", avg * 1000.0)
1662 nvg.Text(ctx, x + w - 3, y + h - 3, str)
1663 }
1664
1665 case .PERCENT:
1666 {
1667 nvg.FontSize(ctx, 15.0)
1668 nvg.TextAlign(ctx, .RIGHT, .TOP)
1669 nvg.FillColor(ctx, nvg.RGBA(240, 240, 240, 255))
1670 str := fmt.tprintf("%.1f %%", avg * 1.0)
1671 nvg.Text(ctx, x + w - 3, y + 3, str)
1672 }
1673
1674 case .MS:
1675 {
1676 nvg.FontSize(ctx, 15.0)
1677 nvg.TextAlign(ctx, .RIGHT, .TOP)
1678 nvg.FillColor(ctx, nvg.RGBA(240, 240, 240, 255))
1679 str := fmt.tprintf("%.2f ms", avg * 1000.0)
1680 nvg.Text(ctx, x + w - 3, y + 3, str)
1681 }
1682 }
1683}
1684
1685getGraphAverage :: proc(fps: ^PerfGraph) -> f32 {
1686 avg := f32(0)
1687 for i in 0 ..< GRAPH_HISTORY_COUNT {
1688 avg += fps.values[i]
1689 }
1690 return avg / f32(GRAPH_HISTORY_COUNT)
1691}
1692
1693GPU_QUERY_COUNT :: 5
1694
1695GPUtimer :: struct {
1696 supported: bool,
1697 cur, ret: int,
1698 queries: [GPU_QUERY_COUNT]u32,
1699}
1700
1701startGPUTimer :: proc(timer: ^GPUtimer) {
1702 if !timer.supported {
1703 return
1704 }
1705
1706 gl.BeginQuery(gl.TIME_ELAPSED, timer.queries[timer.cur % GPU_QUERY_COUNT])
1707 timer.cur += 1
1708}
1709
1710stopGPUTimer :: proc(timer: ^GPUtimer) {
1711 available := i32(1)
1712 if !timer.supported {
1713 return
1714 }
1715
1716 gl.EndQuery(gl.TIME_ELAPSED)
1717 for timer.ret <= timer.cur && available == 1 {
1718
1719 gl.GetQueryObjectiv(
1720 timer.queries[timer.ret % GPU_QUERY_COUNT],
1721 gl.QUERY_RESULT_AVAILABLE,
1722 &available,
1723 )
1724 }
1725
1726 return
1727}