nanovg/example

Code

example.odin ¶
1,727 linesSource

1package main
2
3/*
4Original Source: https://github.com/memononen/nanovg/blob/master/example/example_gl3.c
5Can be run using: odin.exe run example.odin -file
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	// window
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	// drop shadow
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	// header 1
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	// header 2
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	// text
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	// Graph background
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	// Graph line
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	// Graph sample pos
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 // half a pixel arc length in radians (2pi cancels out).
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	// Selector
484	nvg.Save(ctx)
485	nvg.Translate(ctx, cx, cy)
486	nvg.Rotate(ctx, hue * math.PI * 2)
487
488	// Marker on
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	// Center triangle
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	// Select circle on triangle
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	// Render hue label
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	// Draw first rect and set scissor to it's area.
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	// Draw second rectangle with offset and rotation.
700	nvg.Translate(ctx, 40, 0)
701	nvg.Rotate(ctx, t)
702
703	// Draw the intended second rectangle without any scissoring.
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	// Draw second rectangle with combined scissoring.
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	// Edit
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	// Edit
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	// Slot
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	// Knob Shadow
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	// Knob
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	// Drop shadow
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	// Window
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	// Hide fades
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	// Scroll bar
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	// The text break API can be used to fill a large buffer of rows,
1208	// or to iterate over the text just few lines (or just one) at a time.
1209	// The "next" variable of the last returned item tells where to continue.
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	// Fade the tooltip out when close to it.
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	// Line joints
1339	drawLines(ctx, 120, height - 50, 600, 50, t)
1340
1341	// Line caps
1342	drawWidths(ctx, 10, 50, 30)
1343
1344	// Line caps
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	// widgets
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	// Slider
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	// Thumbnails box
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		// hacky way around cstrings
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	// Unpremultiply
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	// Defringe
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		// check for results if there are any
1719		gl.GetQueryObjectiv(
1720			timer.queries[timer.ret % GPU_QUERY_COUNT],
1721			gl.QUERY_RESULT_AVAILABLE,
1722			&available,
1723		)
1724	}
1725
1726	return
1727}

Assets 4

Declarations Used 129