egra: some agg mini optimisations (rendering, hittest)
[iv.d.git] / nanovega_demo / example.d
blobe107c3a6d0cb873f126d47cff3021b56ed690f98
1 //
2 // Copyright (c) 2013 Mikko Mononen memon@inside.org
3 //
4 // This software is provided 'as-is', without any express or implied
5 // warranty. In no event will the authors be held liable for any damages
6 // arising from the use of this software.
7 // Permission is granted to anyone to use this software for any purpose,
8 // including commercial applications, and to alter it and redistribute it
9 // freely, subject to the following restrictions:
10 // 1. The origin of this software must not be misrepresented; you must not
11 // claim that you wrote the original software. If you use this software
12 // in a product, an acknowledgment in the product documentation would be
13 // appreciated but is not required.
14 // 2. Altered source versions must be plainly marked as such, and must not be
15 // misrepresented as being the original software.
16 // 3. This notice may not be removed or altered from any source distribution.
18 module example;
20 //import std.datetime;
22 import arsd.simpledisplay;
23 import arsd.color;
24 import arsd.image;
26 import iv.cmdcon;
27 import iv.nanovega;
28 import iv.nanovega.blendish;
29 import iv.nanovega.perf;
31 version(aliced) {
32 version(nanovg_demo_shitfonts) {
33 } else {
34 version = nanovg_demo_msfonts;
39 // ////////////////////////////////////////////////////////////////////////// //
40 public class RepaintEvent {} ///
42 __gshared RepaintEvent evRepaint;
44 shared static this () {
45 evRepaint = new RepaintEvent();
49 // ////////////////////////////////////////////////////////////////////////// //
50 enum GWidth = 1000;
51 enum GHeight = 600;
54 enum WidgetIdRaw = 1;
55 enum WidgetIdBlendish = 2;
58 // ////////////////////////////////////////////////////////////////////////// //
59 void fatal (string msg) {
60 import std.stdio;
61 stderr.writeln("FATAL: ", msg);
62 stderr.flush();
63 assert(0, msg);
67 // ////////////////////////////////////////////////////////////////////////// //
68 // demo modes
69 bool blowup = false;
70 bool premult = false;
71 bool cliptest = true;
74 // ////////////////////////////////////////////////////////////////////////// //
75 struct DemoData {
76 int fontNormal, fontBold, fontIcons;
77 NVGImage[12] images;
79 @disable this (this); // no copies
83 // ////////////////////////////////////////////////////////////////////////// //
84 // blendish window position and flags
85 float bndX = 42.0f;
86 float bndY = 100.0f;
87 float bndW = 600.0f;
88 float bndH = 420.0f;
89 bool bndMoving = false;
91 // "raw widget" position and flags
92 float wgX = 50.0f;
93 float wgY = 50.0f;
94 enum wgW = 300.0f;
95 enum wgH = 400.0f;
96 bool wgMoving = false;
98 // common "raw widget" and blendish flags
99 bool wgOnTop = false;
102 // ////////////////////////////////////////////////////////////////////////// //
103 bool loadDemoData (NVGContext nvg, ref DemoData data) {
104 import std.stdio;
105 import std.string : format;
107 if (nvg is null) return false;
109 foreach (immutable idx, ref NVGImage img; data.images[]) {
110 img = nvg.createImage("data/images/image%u.jpg".format(idx+1));
111 if (!img.valid) { stderr.writeln("Could not load image #", idx+1, "."); return false; }
114 data.fontIcons = nvg.createFont("icons", "data/entypo.ttf");
115 if (data.fontIcons == FONS_INVALID) { stderr.writeln("Could not add font icons."); return false; }
117 version(nanovg_demo_msfonts) {
118 enum FNN = "Tahoma:noaa";
119 enum FNB = "Tahoma:bold:noaa";
120 } else {
121 enum FNN = "data/Roboto-Regular.ttf";
122 enum FNB = "data/Roboto-Bold.ttf";
125 data.fontNormal = nvg.createFont("sans", FNN);
126 if (data.fontNormal == FONS_INVALID) { stderr.writeln("Could not add font italic."); return false; }
127 data.fontBold = nvg.createFont("sans-bold", FNB);
128 if (data.fontBold == FONS_INVALID) { stderr.writeln("Could not add font bold."); return false; }
130 //bndSetFont(vg.createFont("droidsans", "data/DroidSans.ttf"));
131 bndSetFont(data.fontNormal);
132 auto icons = nvg.createImage("data/images/blender_icons16.png");
133 if (!icons.valid) { stderr.writeln("Could not load icons image."); return false; }
134 bndSetIconImage(icons);
136 return true;
140 void freeDemoData (NVGContext nvg, ref DemoData data) {
141 if (nvg == null) return;
142 foreach (ref NVGImage img; data.images[]) img.clear();
143 bndClearIconImage();
147 // ////////////////////////////////////////////////////////////////////////// //
148 enum ICON_NONE = "";
149 enum ICON_SEARCH = "\u1F50";
150 enum ICON_CIRCLED_CROSS = "\u2716";
151 enum ICON_CHEVRON_RIGHT = "\uE75E";
152 enum ICON_CHECK = "\u2713";
153 enum ICON_LOGIN = "\uE740";
154 enum ICON_TRASH = "\uE729";
157 // ////////////////////////////////////////////////////////////////////////// //
158 void renderDemo (NVGContext nvg, float mx, float my, float width, float height, float t, int blowup, ref DemoData data) {
159 drawEyes(nvg, width-250, 50, 150, 100, mx, my, t);
160 drawParagraph(nvg, width-450, 50, 150, 100, mx, my);
161 drawGraph(nvg, 0, height/2, width, height/2, t);
162 drawColorWheel(nvg, width-300, height-300, 250.0f, 250.0f, t);
164 // Line joints
165 drawLines(nvg, 120, height-50, 600, 50, t);
167 // Line caps
168 drawWidths(nvg, 10, 50, 30);
170 // Line caps
171 drawCaps(nvg, 10, 300, 30);
173 drawScissor(nvg, 50, height-80, t);
175 nvg.save();
176 scope(exit) nvg.restore();
178 if (blowup) {
179 import core.stdc.math : sinf;
180 nvg.rotate(sinf(t*0.3f)*5.0f/180.0f*NVG_PI);
181 nvg.scale(2.0f, 2.0f);
184 void drawWidget () {
185 nvg.save();
186 scope(exit) nvg.restore();
188 nvg.translate(wgX-50, wgY-50);
189 nvg.globalAlpha(wgMoving ? 0.4 : 0.9);
191 // Widgets
192 drawWindow(nvg, "Widgets `n Stuff", 50, 50, 300, 400);
193 float x = 60;
194 float y = 95;
195 drawSearchBox(nvg, "Search", x, y, 280, 25); y += 40;
196 float popy = y+14;
197 drawDropDown(nvg, "Effects", x, y, 280, 28); y += 45;
199 // Form
200 drawLabel(nvg, "Login", x, y, 280, 20); y += 25;
201 drawEditBox(nvg, "Email", x, y, 280, 28); y += 35;
202 drawEditBox(nvg, "Password", x, y, 280, 28); y += 38;
203 drawCheckBox(nvg, "Remember me", x, y, 140, 28);
204 drawButton(nvg, ICON_LOGIN, "Sign in", x+138, y, 140, 28, nvgRGBA(0, 96, 128, 255)); y += 45;
206 // Slider
207 drawLabel(nvg, "Diameter", x, y, 280, 20); y += 25;
208 drawEditBoxNum(nvg, "123.00", "px", x+180, y, 100, 28);
209 drawSlider(nvg, 0.4f, x, y, 170, 28); y += 55;
211 drawButton(nvg, ICON_TRASH, "Delete", x, y, 160, 28, nvgRGBA(128, 16, 8, 255));
212 drawButton(nvg, ICON_NONE, "Cancel", x+170, y, 110, 28, nvgRGBA(0, 0, 0, 0));
214 // Thumbnails box
215 drawThumbnails(nvg, 365, popy-30, 160, 300, data.images[], t);
218 void drawBnd () {
219 drawBlendish(nvg, bndX, bndY, bndW, bndH, t);
222 if (wgOnTop) { drawBnd(); drawWidget(); } else { drawWidget(); drawBnd(); }
224 nvg.pickid = NVGNoPick;
228 // ////////////////////////////////////////////////////////////////////////// //
229 void drawWindow (NVGContext nvg, const(char)[] title, float x, float y, float w, float h) {
230 enum cornerRadius = 3.0f;
232 nvg.save();
233 scope(exit) nvg.restore();
235 // Window
236 nvg.beginPath();
237 nvg.roundedRect(x, y, w, h, cornerRadius);
238 nvg.currFillPickId = WidgetIdRaw;
239 nvg.fillColor = nvgRGBA(28, 30, 34, 192);
240 //vg.fillColor(nvgRGBA(0, 0, 0, 128));
241 nvg.fill();
243 // Drop shadow
244 nvg.beginPath();
245 nvg.rect(x-10, y-10, w+20, h+30);
246 nvg.roundedRect(x, y, w, h, cornerRadius);
247 nvg.pathWinding = NVGSolidity.Hole;
248 nvg.fillPaint = nvg.boxGradient(x, y+2, w, h, cornerRadius*2, 10, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0));
249 nvg.fill();
251 // Header
252 nvg.beginPath();
253 nvg.roundedRect(x+1, y+1, w-2, 30, cornerRadius-1);
254 nvg.fillPaint = nvg.linearGradient(x, y, x, y+15, nvgRGBA(255, 255, 255, 8), nvgRGBA(0, 0, 0, 16));
255 nvg.fill();
257 nvg.beginPath();
258 nvg.moveTo(x+0.5f, y+0.5f+30);
259 nvg.lineTo(x+0.5f+w-1, y+0.5f+30);
260 nvg.strokeColor = nvgRGBA(0, 0, 0, 32);
261 nvg.stroke();
263 nvg.fontSize = 18.0f;
264 nvg.fontFace = "sans-bold";
265 nvg.textAlign(NVGTextAlign.H.Center, NVGTextAlign.V.Middle);
267 nvg.fontBlur = 2;
268 nvg.fillColor = nvgRGBA(0, 0, 0, 128);
269 nvg.text(x+w/2, y+16+1, title);
271 nvg.fontBlur = 0;
272 nvg.fillColor = nvgRGBA(220, 220, 220, 160);
273 nvg.text(x+w/2, y+16, title);
277 void drawSearchBox (NVGContext nvg, const(char)[] text, float x, float y, float w, float h) {
278 immutable float cornerRadius = h/2-1;
280 // Edit
281 nvg.beginPath();
282 nvg.roundedRect(x, y, w, h, cornerRadius);
283 nvg.fillPaint = nvg.boxGradient(x, y+1.5f, w, h, h/2, 5, nvgRGBA(0, 0, 0, 16), nvgRGBA(0, 0, 0, 92));
284 nvg.fill();
287 vg.beginPath();
288 vg.roundedRect(x+0.5f, y+0.5f, w-1, h-1, cornerRadius-0.5f);
289 vg.strokeColor = nvgRGBA(0, 0, 0, 48);
290 vg.stroke();
293 nvg.fontSize = h*1.3f;
294 nvg.fontFace = "icons";
295 nvg.fillColor = nvgRGBA(255, 255, 255, 64);
296 nvg.textAlign = NVGTextAlign.V.Middle;
297 nvg.textAlign = NVGTextAlign.H.Center;
298 nvg.text(x+h*0.55f, y+h*0.55f, ICON_SEARCH);
300 nvg.fontSize = 20.0f;
301 nvg.fontFace = "sans";
302 nvg.fillColor = nvgRGBA(255, 255, 255, 32);
303 nvg.textAlign = NVGTextAlign.H.Left;
304 nvg.text(x+h*1.05f, y+h*0.5f, text);
306 nvg.fontSize = h*1.3f;
307 nvg.fontFace = "icons";
308 nvg.fillColor = nvgRGBA(255, 255, 255, 32);
309 nvg.textAlign = NVGTextAlign.H.Center;
310 nvg.text(x+w-h*0.55f, y+h*0.55f, ICON_CIRCLED_CROSS);
314 void drawDropDown (NVGContext nvg, const(char)[] text, float x, float y, float w, float h) {
315 enum cornerRadius = 4.0f;
317 nvg.beginPath();
318 nvg.roundedRect(x+1, y+1, w-2, h-2, cornerRadius-1);
319 nvg.fillPaint = nvg.linearGradient(x, y, x, y+h, nvgRGBA(255, 255, 255, 16), nvgRGBA(0, 0, 0, 16));
320 nvg.fill();
322 nvg.beginPath();
323 nvg.roundedRect(x+0.5f, y+0.5f, w-1, h-1, cornerRadius-0.5f);
324 nvg.strokeColor = nvgRGBA(0, 0, 0, 48);
325 nvg.stroke();
327 nvg.fontSize = 20.0f;
328 nvg.fontFace = "sans";
329 nvg.fillColor = nvgRGBA(255, 255, 255, 160);
330 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Middle);
331 nvg.text(x+h*0.3f, y+h*0.5f, text);
333 nvg.fontSize = h*1.3f;
334 nvg.fontFace = "icons";
335 nvg.fillColor = nvgRGBA(255, 255, 255, 64);
336 nvg.textAlign = NVGTextAlign.H.Center;
337 nvg.text(x+w-h*0.5f, y+h*0.5f, ICON_CHEVRON_RIGHT);
341 void drawLabel (NVGContext nvg, const(char)[] text, float x, float y, float w, float h) {
342 nvg.fontSize = 18.0f;
343 nvg.fontFace = "sans";
344 nvg.fillColor = nvgRGBA(255, 255, 255, 128);
346 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Middle);
347 nvg.text(x, y+h*0.5f, text);
351 void drawEditBoxBase (NVGContext nvg, float x, float y, float w, float h) {
352 // Edit
353 nvg.beginPath();
354 nvg.roundedRect(x+1, y+1, w-2, h-2, 4-1);
355 nvg.fillPaint = nvg.boxGradient(x+1, y+1+1.5f, w-2, h-2, 3, 4, nvgRGBA(255, 255, 255, 32), nvgRGBA(32, 32, 32, 32));
356 nvg.fill();
358 nvg.beginPath();
359 nvg.roundedRect(x+0.5f, y+0.5f, w-1, h-1, 4-0.5f);
360 nvg.strokeColor = nvgRGBA(0, 0, 0, 48);
361 nvg.stroke();
365 void drawEditBox (NVGContext nvg, const(char)[] text, float x, float y, float w, float h) {
366 drawEditBoxBase(nvg, x, y, w, h);
367 nvg.fontSize = 20.0f;
368 nvg.fontFace = "sans";
369 nvg.fillColor = nvgRGBA(255, 255, 255, 64);
370 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Middle);
371 nvg.text(x+h*0.3f, y+h*0.5f, text);
375 public void drawEditBoxNum (NVGContext nvg, const(char)[] text, const(char)[] units, float x, float y, float w, float h) {
376 drawEditBoxBase(nvg, x, y, w, h);
378 immutable float uw = nvg.textBounds(0, 0, units, null);
380 nvg.fontSize = 18.0f;
381 nvg.fontFace = "sans";
382 nvg.fillColor = nvgRGBA(255, 255, 255, 64);
383 nvg.textAlign(NVGTextAlign.H.Right, NVGTextAlign.V.Middle);
384 nvg.text(x+w-h*0.3f, y+h*0.5f, units);
386 nvg.fontSize = 20.0f;
387 nvg.fontFace = "sans";
388 nvg.fillColor = nvgRGBA(255, 255, 255, 128);
389 nvg.text(x+w-uw-h*0.5f, y+h*0.5f, text);
393 void drawCheckBox (NVGContext nvg, const(char)[] text, float x, float y, float w, float h) {
394 nvg.fontSize = 18.0f;
395 nvg.fontFace = "sans";
396 nvg.fillColor = nvgRGBA(255, 255, 255, 160);
398 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Middle);
399 nvg.text(x+28, y+h*0.5f, text);
401 nvg.beginPath();
402 nvg.roundedRect(x+1, y+cast(int)(h*0.5f)-9, 18, 18, 3);
403 nvg.fillPaint = nvg.boxGradient(x+1, y+cast(int)(h*0.5f)-9+1, 18, 18, 3, 3, nvgRGBA(0, 0, 0, 32), nvgRGBA(0, 0, 0, 92));
404 nvg.fill();
406 nvg.fontSize = 40;
407 nvg.fontFace = "icons";
408 nvg.fillColor = nvgRGBA(255, 255, 255, 128);
409 nvg.textAlign = NVGTextAlign.H.Center;
410 nvg.text(x+9+2, y+h*0.5f, ICON_CHECK);
414 void drawButton (NVGContext nvg, const(char)[] preicon, const(char)[] text, float x, float y, float w, float h, NVGColor col) {
415 enum float cornerRadius = 4.0f;
417 nvg.beginPath();
418 nvg.roundedRect(x+1, y+1, w-2, h-2, cornerRadius-1);
419 if (!col.isTransparent) {
420 nvg.fillColor = col;
421 nvg.fill();
423 nvg.fillPaint = nvg.linearGradient(x, y, x, y+h, nvgRGBA(255, 255, 255, col.isTransparent ? 16 : 32), nvgRGBA(0, 0, 0, col.isTransparent ? 16 : 32));
424 nvg.fill();
426 nvg.beginPath();
427 nvg.roundedRect(x+0.5f, y+0.5f, w-1, h-1, cornerRadius-0.5f);
428 nvg.strokeColor = nvgRGBA(0, 0, 0, 48);
429 nvg.stroke();
431 nvg.fontSize = 20.0f;
432 nvg.fontFace = "sans-bold";
434 immutable float tw = nvg.textBounds(0, 0, text, null);
435 float iw = 0;
437 if (preicon.length) {
438 nvg.fontSize = h*1.3f;
439 nvg.fontFace = "icons";
440 iw = nvg.textBounds(0, 0, preicon, null);
441 iw += h*0.15f;
444 if (preicon.length) {
445 nvg.fontSize = h*1.3f;
446 nvg.fontFace = "icons";
447 nvg.fillColor = nvgRGBA(255, 255, 255, 96);
448 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Middle);
449 nvg.text(x+w*0.5f-tw*0.5f-iw*0.75f, y+h*0.5f, preicon);
452 nvg.fontSize = 20.0f;
453 nvg.fontFace = "sans-bold";
454 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Middle);
455 nvg.fillColor = nvgRGBA(0, 0, 0, 160);
456 nvg.text(x+w*0.5f-tw*0.5f+iw*0.25f, y+h*0.5f-1, text);
457 nvg.fillColor = nvgRGBA(255, 255, 255, 160);
458 nvg.text(x+w*0.5f-tw*0.5f+iw*0.25f, y+h*0.5f, text);
462 void drawSlider (NVGContext nvg, float pos, float x, float y, float w, float h) {
463 immutable float cy = y+cast(int)(h*0.5f);
464 immutable float kr = cast(int)(h*0.25f);
466 nvg.save();
467 scope(exit) nvg.restore();
469 // Slot
470 nvg.beginPath();
471 nvg.roundedRect(x, cy-2, w, 4, 2);
472 nvg.fillPaint = nvg.boxGradient(x, cy-2+1, w, 4, 2, 2, nvgRGBA(0, 0, 0, 32), nvgRGBA(0, 0, 0, 128));
473 nvg.fill();
475 // Knob Shadow
476 nvg.beginPath();
477 nvg.rect(x+cast(int)(pos*w)-kr-5, cy-kr-5, kr*2+5+5, kr*2+5+5+3);
478 nvg.circle(x+cast(int)(pos*w), cy, kr);
479 nvg.pathWinding = NVGSolidity.Hole;
480 nvg.fillPaint = nvg.radialGradient(x+cast(int)(pos*w), cy+1, kr-3, kr+3, nvgRGBA(0, 0, 0, 64), nvgRGBA(0, 0, 0, 0));
481 nvg.fill();
483 // Knob
484 nvg.beginPath();
485 nvg.circle(x+cast(int)(pos*w), cy, kr-1);
486 nvg.fillColor = nvgRGBA(40, 43, 48, 255);
487 nvg.fill();
488 nvg.fillPaint = nvg.linearGradient(x, cy-kr, x, cy+kr, nvgRGBA(255, 255, 255, 16), nvgRGBA(0, 0, 0, 16));
489 nvg.fill();
491 nvg.beginPath();
492 nvg.circle(x+cast(int)(pos*w), cy, kr-0.5f);
493 nvg.strokeColor = nvgRGBA(0, 0, 0, 92);
494 nvg.stroke();
498 void drawEyes (NVGContext nvg, float x, float y, float w, float h, float mx, float my, float t) {
499 import core.stdc.math : pow, sinf, sqrtf;
501 immutable float ex = w*0.23f;
502 immutable float ey = h*0.5f;
503 immutable float lx = x+ex;
504 immutable float ly = y+ey;
505 immutable float rx = x+w-ex;
506 immutable float ry = y+ey;
507 immutable float br = (ex < ey ? ex : ey)*0.5f;
508 immutable float blink = 1-pow(sinf(t*0.5f), 200)*0.8f;
510 nvg.beginPath();
511 nvg.ellipse(lx+3.0f, ly+16.0f, ex, ey);
512 nvg.ellipse(rx+3.0f, ry+16.0f, ex, ey);
513 nvg.fillPaint = nvg.linearGradient(x, y+h*0.5f, x+w*0.1f, y+h, nvgRGBA(0, 0, 0, 32), nvgRGBA(0, 0, 0, 16));
514 nvg.fill();
516 nvg.beginPath();
517 nvg.ellipse(lx, ly, ex, ey);
518 nvg.ellipse(rx, ry, ex, ey);
519 nvg.fillPaint = nvg.linearGradient(x, y+h*0.25f, x+w*0.1f, y+h, nvgRGBA(220, 220, 220, 255), nvgRGBA(128, 128, 128, 255));
520 nvg.fill();
522 void drawPupil (in float px, in float py) {
523 float dx = (mx-px)/(ex*10);
524 float dy = (my-py)/(ey*10);
525 immutable float d = sqrtf(dx*dx+dy*dy);
526 if (d > 1.0f) { dx /= d; dy /= d; }
527 dx *= ex*0.4f;
528 dy *= ey*0.5f;
529 nvg.beginPath();
530 nvg.ellipse(px+dx, py+dy+ey*0.25f*(1-blink), br, br*blink);
531 nvg.fillColor = nvgRGBA(32, 32, 32, 255);
532 nvg.fill();
535 drawPupil(lx, ly);
536 drawPupil(rx, ry);
538 nvg.beginPath();
539 nvg.ellipse(lx, ly, ex, ey);
540 nvg.fillPaint = nvg.radialGradient(lx-ex*0.25f, ly-ey*0.5f, ex*0.1f, ex*0.75f, nvgRGBA(255, 255, 255, 128), nvgRGBA(255, 255, 255, 0));
541 nvg.fill();
543 nvg.beginPath();
544 nvg.ellipse(rx, ry, ex, ey);
545 nvg.fillPaint = nvg.radialGradient(rx-ex*0.25f, ry-ey*0.5f, ex*0.1f, ex*0.75f, nvgRGBA(255, 255, 255, 128), nvgRGBA(255, 255, 255, 0));
546 nvg.fill();
550 void drawGraph (NVGContext nvg, float x, float y, float w, float h, float t) {
551 import core.stdc.math : cosf, sinf;
553 immutable float[6] samples = [
554 (1+sinf(t*1.2345f+cosf(t*0.33457f)*0.44f))*0.5f,
555 (1+sinf(t*0.68363f+cosf(t*1.3f)*1.55f))*0.5f,
556 (1+sinf(t*1.1642f+cosf(t*0.33457)*1.24f))*0.5f,
557 (1+sinf(t*0.56345f+cosf(t*1.63f)*0.14f))*0.5f,
558 (1+sinf(t*1.6245f+cosf(t*0.254f)*0.3f))*0.5f,
559 (1+sinf(t*0.345f+cosf(t*0.03f)*0.6f))*0.5f,
562 immutable float dx = w/5.0f;
563 float[6] sx, sy;
564 foreach (immutable i, immutable float sm; samples[]) {
565 sx[i] = x+i*dx;
566 sy[i] = y+h*sm*0.8f;
569 // Graph background
570 nvg.beginPath();
571 nvg.moveTo(sx[0], sy[0]);
572 foreach (immutable i; 1..6) nvg.bezierTo(sx[i-1]+dx*0.5f, sy[i-1], sx[i]-dx*0.5f, sy[i], sx[i], sy[i]);
573 nvg.lineTo(x+w, y+h);
574 nvg.lineTo(x, y+h);
575 nvg.fillPaint = nvg.linearGradient(x, y, x, y+h, nvgRGBA(0, 160, 192, 0), nvgRGBA(0, 160, 192, 64));
576 nvg.fill();
578 // Graph line
579 nvg.beginPath();
580 nvg.moveTo(sx[0], sy[0]+2);
581 foreach (immutable i; 1..6) nvg.bezierTo(sx[i-1]+dx*0.5f, sy[i-1]+2, sx[i]-dx*0.5f, sy[i]+2, sx[i], sy[i]+2);
582 nvg.strokeColor = nvgRGBA(0, 0, 0, 32);
583 nvg.strokeWidth = 3.0f;
584 nvg.stroke();
586 nvg.beginPath();
587 nvg.moveTo(sx[0], sy[0]);
588 foreach (immutable i; 1..6) nvg.bezierTo(sx[i-1]+dx*0.5f, sy[i-1], sx[i]-dx*0.5f, sy[i], sx[i], sy[i]);
589 nvg.strokeColor = nvgRGBA(0, 160, 192, 255);
590 nvg.strokeWidth = 3.0f;
591 nvg.stroke();
593 // Graph sample pos
594 foreach (immutable i; 0..6) {
595 nvg.beginPath();
596 nvg.rect(sx[i]-10, sy[i]-10+2, 20, 20);
597 nvg.fillPaint = nvg.radialGradient(sx[i], sy[i]+2, 3.0f, 8.0f, nvgRGBA(0, 0, 0, 32), nvgRGBA(0, 0, 0, 0));
598 nvg.fill();
601 nvg.beginPath();
602 foreach (immutable i; 0..6) nvg.circle(sx[i], sy[i], 4.0f);
603 nvg.fillColor = nvgRGBA(0, 160, 192, 255);
604 nvg.fill();
606 nvg.beginPath();
607 foreach (immutable i; 0..6) nvg.circle(sx[i], sy[i], 2.0f);
608 nvg.fillColor = nvgRGBA(220, 220, 220, 255);
609 nvg.fill();
611 nvg.strokeWidth = 1.0f;
615 void drawSpinner (NVGContext nvg, float cx, float cy, float r, float t) {
616 import core.stdc.math : cosf, sinf;
618 immutable float a0 = 0.0f+t*6;
619 immutable float a1 = NVG_PI+t*6;
620 immutable float r0 = r;
621 immutable float r1 = r*0.75f;
623 nvg.save();
624 scope(exit) nvg.restore();
626 nvg.beginPath();
627 nvg.arc(NVGWinding.CW, cx, cy, r0, a0, a1);
628 nvg.arc(NVGWinding.CCW, cx, cy, r1, a1, a0);
629 nvg.closePath();
631 immutable float ax = cx+cosf(a0)*(r0+r1)*0.5f;
632 immutable float ay = cy+sinf(a0)*(r0+r1)*0.5f;
633 immutable float bx = cx+cosf(a1)*(r0+r1)*0.5f;
634 immutable float by = cy+sinf(a1)*(r0+r1)*0.5f;
635 nvg.fillPaint = nvg.linearGradient(ax, ay, bx, by, nvgRGBA(0, 0, 0, 0), nvgRGBA(0, 0, 0, 128));
636 nvg.fill();
640 void drawThumbnails (NVGContext nvg, float x, float y, float w, float h, NVGImage[] images, float t) {
641 import core.stdc.math : cosf;
643 enum cornerRadius = 3.0f;
644 //float ix, iy, iw, ih;
645 enum thumb = 60.0f;
646 enum arry = 30.5f;
647 //int imgw, imgh;
648 immutable float stackh = (images.length/2)*(thumb+10)+10;
649 immutable float u = (1+cosf(t*0.5f))*0.5f;
650 immutable float u2 = (1-cosf(t*0.2f))*0.5f;
651 //float scrollh, dv;
653 nvg.save();
654 scope(exit) nvg.restore();
656 // Drop shadow
657 nvg.beginPath();
658 nvg.rect(x-10, y-10, w+20, h+30);
659 nvg.roundedRect(x, y, w, h, cornerRadius);
660 nvg.pathWinding = NVGSolidity.Hole;
661 nvg.fillPaint = nvg.boxGradient(x, y+4, w, h, cornerRadius*2, 20, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0));
662 nvg.fill();
664 // Window
665 nvg.beginPath();
666 nvg.roundedRect(x, y, w, h, cornerRadius);
667 nvg.moveTo(x-10, y+arry);
668 nvg.lineTo(x+1, y+arry-11);
669 nvg.lineTo(x+1, y+arry+11);
670 //nvg.closePath();
671 nvg.currFillPickId = WidgetIdRaw;
672 nvg.fillColor = nvgRGBA(200, 200, 200, 255);
673 nvg.fill();
676 nvg.save();
677 scope(exit) nvg.restore();
679 nvg.scissor(x, y, w, h);
680 nvg.translate(0, -(stackh-h)*u);
682 immutable float dv = 1.0f/cast(float)(images.length-1);
684 foreach (immutable int i; 0..cast(int)images.length) {
685 float tx = x+10;
686 float ty = y+10;
687 tx += (i%2)*(thumb+10);
688 ty += (i/2)*(thumb+10);
690 float iw, ih, ix, iy;
691 int imgw = images[i].width;
692 int imgh = images[i].height;
693 if (imgw < imgh) {
694 iw = thumb;
695 ih = iw*cast(float)imgh/cast(float)imgw;
696 ix = 0;
697 iy = -(ih-thumb)*0.5f;
698 } else {
699 ih = thumb;
700 iw = ih*cast(float)imgw/cast(float)imgh;
701 ix = -(iw-thumb)*0.5f;
702 iy = 0;
705 immutable float v = i*dv;
706 immutable float a = nvg__clamp((u2-v)/dv, 0, 1);
708 if (a < 1.0f) drawSpinner(nvg, tx+thumb/2, ty+thumb/2, thumb*0.25f, t);
710 nvg.beginPath();
711 nvg.roundedRect(tx, ty, thumb, thumb, 5);
712 nvg.fillPaint = nvg.imagePattern(tx+ix, ty+iy, iw, ih, 0.0f/180.0f*NVG_PI, images[i], a);
713 nvg.fill();
715 nvg.beginPath();
716 nvg.rect(tx-5, ty-5, thumb+10, thumb+10);
717 nvg.roundedRect(tx, ty, thumb, thumb, 6);
718 nvg.pathWinding = NVGSolidity.Hole;
719 nvg.fillPaint = nvg.boxGradient(tx-1, ty, thumb+2, thumb+2, 5, 3, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0));
720 nvg.fill();
722 nvg.beginPath();
723 nvg.roundedRect(tx+0.5f, ty+0.5f, thumb-1, thumb-1, 4-0.5f);
724 nvg.strokeWidth = 1.0f;
725 nvg.strokeColor = nvgRGBA(255, 255, 255, 192);
726 nvg.stroke();
730 // Hide fades
731 nvg.beginPath();
732 nvg.rect(x+4, y, w-8, 6);
733 nvg.fillPaint = nvg.linearGradient(x, y, x, y+6, nvgRGBA(200, 200, 200, 255), nvgRGBA(200, 200, 200, 0));
734 nvg.fill();
736 nvg.beginPath();
737 nvg.rect(x+4, y+h-6, w-8, 6);
738 nvg.fillPaint = nvg.linearGradient(x, y+h, x, y+h-6, nvgRGBA(200, 200, 200, 255), nvgRGBA(200, 200, 200, 0));
739 nvg.fill();
741 // Scroll bar
742 nvg.beginPath();
743 nvg.roundedRect(x+w-12, y+4, 8, h-8, 3);
744 nvg.fillPaint = nvg.boxGradient(x+w-12+1, y+4+1, 8, h-8, 3, 4, nvgRGBA(0, 0, 0, 32), nvgRGBA(0, 0, 0, 92));
745 //vg.fillColor = nvgRGBA(255, 0, 0, 128);
746 nvg.fill();
748 immutable float scrollh = (h/stackh)*(h-8);
749 nvg.beginPath();
750 nvg.roundedRect(x+w-12+1, y+4+1+(h-8-scrollh)*u, 8-2, scrollh-2, 2);
751 nvg.fillPaint = nvg.boxGradient(x+w-12-1, y+4+(h-8-scrollh)*u-1, 8, scrollh, 3, 4, nvgRGBA(220, 220, 220, 255), nvgRGBA(128, 128, 128, 255));
752 //vg.fillColor = nvgRGBA(0, 0, 0, 128);
753 nvg.fill();
757 void drawColorWheel (NVGContext nvg, float x, float y, float w, float h, float t) {
758 import core.stdc.math : cosf, sinf;
760 immutable float hue = sinf(t*0.12f);
762 nvg.save();
763 scope(exit) nvg.restore();
766 vg.beginPath();
767 vg.rect(x, y, w, h);
768 vg.fillColor(nvgRGBA(255, 0, 0, 128));
769 vg.fill();
772 immutable float cx = x+w*0.5f;
773 immutable float cy = y+h*0.5f;
774 immutable float r1 = (w < h ? w : h)*0.5f-5.0f;
775 immutable float r0 = r1-20.0f;
776 immutable float aeps = 0.5f/r1; // half a pixel arc length in radians (2pi cancels out).
778 foreach (immutable int i; 0..6) {
779 float a0 = cast(float)i/6.0f*NVG_PI*2.0f-aeps;
780 float a1 = cast(float)(i+1.0f)/6.0f*NVG_PI*2.0f+aeps;
781 nvg.beginPath();
782 nvg.arc(NVGWinding.CW, cx, cy, r0, a0, a1);
783 nvg.arc(NVGWinding.CCW, cx, cy, r1, a1, a0);
784 nvg.closePath();
785 immutable float ax = cx+cosf(a0)*(r0+r1)*0.5f;
786 immutable float ay = cy+sinf(a0)*(r0+r1)*0.5f;
787 immutable float bx = cx+cosf(a1)*(r0+r1)*0.5f;
788 immutable float by = cy+sinf(a1)*(r0+r1)*0.5f;
789 nvg.fillPaint = nvg.linearGradient(ax, ay, bx, by, nvgHSLA(a0/(NVG_PI*2), 1.0f, 0.55f, 255), nvgHSLA(a1/(NVG_PI*2), 1.0f, 0.55f, 255));
790 nvg.fill();
793 nvg.beginPath();
794 nvg.circle(cx, cy, r0-0.5f);
795 nvg.circle(cx, cy, r1+0.5f);
796 nvg.strokeColor = nvgRGBA(0, 0, 0, 64);
797 nvg.strokeWidth = 1.0f;
798 nvg.stroke();
800 // Selector
802 nvg.save();
803 scope(exit) nvg.restore();
805 nvg.translate(cx, cy);
806 nvg.rotate(hue*NVG_PI*2);
808 // Marker on
809 nvg.strokeWidth = 2.0f;
810 nvg.beginPath();
811 nvg.rect(r0-1, -3, r1-r0+2, 6);
812 nvg.strokeColor = nvgRGBA(255, 255, 255, 192);
813 nvg.stroke();
815 nvg.beginPath();
816 nvg.rect(r0-2-10, -4-10, r1-r0+4+20, 8+20);
817 nvg.rect(r0-2, -4, r1-r0+4, 8);
818 nvg.pathWinding = NVGSolidity.Hole;
819 nvg.fillPaint = nvg.boxGradient(r0-3, -5, r1-r0+6, 10, 2, 4, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0));
820 nvg.fill();
822 // Center triangle
823 immutable float r = r0-6;
824 immutable float ax = cosf(120.0f/180.0f*NVG_PI)*r;
825 immutable float ay = sinf(120.0f/180.0f*NVG_PI)*r;
826 immutable float bx = cosf(-120.0f/180.0f*NVG_PI)*r;
827 immutable float by = sinf(-120.0f/180.0f*NVG_PI)*r;
829 nvg.beginPath();
830 nvg.moveTo(r, 0);
831 nvg.lineTo(ax, ay);
832 nvg.lineTo(bx, by);
833 nvg.closePath();
834 nvg.fillPaint = nvg.linearGradient(r, 0, ax, ay, nvgHSLA(hue, 1.0f, 0.5f, 255), nvgRGBA(255, 255, 255, 255));
835 nvg.fill();
836 nvg.fillPaint = nvg.linearGradient((r+ax)*0.5f, (0+ay)*0.5f, bx, by, nvgRGBA(0, 0, 0, 0), nvgRGBA(0, 0, 0, 255));
837 nvg.fill();
838 nvg.strokeColor = nvgRGBA(0, 0, 0, 64);
839 nvg.stroke();
841 // Select circle on triangle
842 immutable float aax = cosf(120.0f/180.0f*NVG_PI)*r*0.3f;
843 immutable float aay = sinf(120.0f/180.0f*NVG_PI)*r*0.4f;
844 nvg.strokeWidth = 2.0f;
845 nvg.beginPath();
846 nvg.circle(aax, aay, 5);
847 nvg.strokeColor = nvgRGBA(255, 255, 255, 192);
848 nvg.stroke();
850 nvg.beginPath();
851 nvg.rect(aax-20, aay-20, 40, 40);
852 nvg.circle(aax, aay, 7);
853 nvg.pathWinding = NVGSolidity.Hole;
854 nvg.fillPaint = nvg.radialGradient(aax, aay, 7, 9, nvgRGBA(0, 0, 0, 64), nvgRGBA(0, 0, 0, 0));
855 nvg.fill();
860 void drawLines (NVGContext nvg, float x, float y, float w, float h, float t) {
861 import core.stdc.math : cosf, sinf;
863 static immutable NVGLineCap[3] joins = [NVGLineCap.Miter, NVGLineCap.Round, NVGLineCap.Bevel];
864 static immutable NVGLineCap[3] caps = [NVGLineCap.Butt, NVGLineCap.Round, NVGLineCap.Square];
866 enum pad = 5.0f;
867 immutable float s = w/9.0f-pad*2;
869 nvg.save();
870 scope(exit) nvg.restore();
872 immutable float[4*2] pts = [
873 -s*0.25f+cosf(t*0.3f)*s*0.5f,
874 sinf(t*0.3f)*s*0.5f,
875 -s*0.25,
877 s*0.25f,
879 s*0.25f+cosf(-t*0.3f)*s*0.5f,
880 sinf(-t*0.3f)*s*0.5f,
883 foreach (immutable int i; 0..3) {
884 foreach (immutable int j; 0..3) {
885 immutable float fx = x+s*0.5f+(i*3+j)/9.0f*w+pad;
886 immutable float fy = y-s*0.5f+pad;
888 nvg.lineCap = caps[i];
889 nvg.lineJoin = joins[j];
891 nvg.strokeWidth = s*0.3f;
892 nvg.strokeColor = nvgRGBA(0, 0, 0, 160);
893 nvg.beginPath();
894 nvg.moveTo(fx+pts[0], fy+pts[1]);
895 nvg.lineTo(fx+pts[2], fy+pts[3]);
896 nvg.lineTo(fx+pts[4], fy+pts[5]);
897 nvg.lineTo(fx+pts[6], fy+pts[7]);
898 nvg.stroke();
900 nvg.lineCap = NVGLineCap.Butt;
901 nvg.lineJoin = NVGLineCap.Bevel;
903 nvg.strokeWidth = 1.0f;
904 nvg.strokeColor = nvgRGBA(0, 192, 255, 255);
905 nvg.beginPath();
906 nvg.moveTo(fx+pts[0], fy+pts[1]);
907 nvg.lineTo(fx+pts[2], fy+pts[3]);
908 nvg.lineTo(fx+pts[4], fy+pts[5]);
909 nvg.lineTo(fx+pts[6], fy+pts[7]);
910 nvg.stroke();
916 void drawParagraph (NVGContext nvg, float x, float y, float width, float height, float mx, float my) {
917 static immutable string 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.";
919 float lineh;
920 //float caretx, px;
921 //float[4] bounds;
922 //float a;
923 //float gx, gy;
924 int gutter = 0;
926 nvg.save();
927 scope(exit) nvg.restore();
929 nvg.fontSize = 18.0f;
930 nvg.fontFace = "sans";
931 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Top);
932 nvg.textMetrics(null, null, &lineh);
934 float gx = 0, gy = 0;
935 int lnum = 0;
936 nvg.textBreakLines(text, width, (in ref NVGTextRow!char row) {
937 NVGGlyphPosition[100] glyphs = void;
938 immutable bool hit = (mx > x && mx < x+width && my >= y && my < y+lineh);
940 nvg.beginPath();
941 nvg.fillColor(nvgRGBA(255, 255, 255, (hit ? 64 : 16)));
942 nvg.rect(x, y, row.width, lineh);
943 nvg.fill();
945 nvg.fillColor(nvgRGBA(255, 255, 255, 255));
946 nvg.text(x, y, row.row);
948 if (hit) {
949 float caretx = (mx < x+row.width/2 ? x : x+row.width);
950 float px = x;
951 auto rglyphs = nvg.textGlyphPositions(x, y, row.row, glyphs[]);
952 foreach (immutable j, const ref glx; rglyphs) {
953 float x0 = glx.x;
954 float x1 = (j+1 < rglyphs.length ? glyphs[j+1].x : x+row.width);
955 float gx_ = x0*0.3f+x1*0.7f;
956 if (mx >= px && mx < gx_) caretx = glx.x;
957 px = gx_;
959 nvg.beginPath();
960 nvg.rect(caretx, y, 1, lineh);
961 nvg.fillColor = nvgRGBA(255, 192, 0, 255);
962 nvg.fill();
964 gutter = lnum+1;
965 gx = x-10;
966 gy = y+lineh/2;
968 ++lnum;
969 y += lineh;
970 // return false; // to stop
973 float[4] bounds;
975 if (gutter) {
976 import core.stdc.stdio : snprintf;
977 char[16] txt;
978 auto len = snprintf(txt.ptr, (txt).sizeof, "%d", gutter);
980 nvg.fontSize = 13.0f;
981 nvg.textAlign(NVGTextAlign.H.Right, NVGTextAlign.V.Middle);
982 nvg.textBounds(gx, gy, txt[0..len], bounds[]);
984 nvg.beginPath();
985 nvg.roundedRect(cast(int)bounds[0]-4, cast(int)bounds[1]-2, cast(int)(bounds[2]-bounds[0])+8, cast(int)(bounds[3]-bounds[1])+4, (cast(int)(bounds[3]-bounds[1])+4)/2-1);
986 nvg.fillColor = nvgRGBA(255, 192, 0, 255);
987 nvg.fill();
989 nvg.fillColor = nvgRGBA(32, 32, 32, 255);
990 nvg.text(gx, gy, txt[0..len]);
993 y += 20.0f;
995 nvg.fontSize = 13.0f;
996 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Top);
997 nvg.textLineHeight = 1.2f;
999 nvg.textBoxBounds(x, y, 150, "Hover your mouse over the text to see calculated caret position.", bounds[]);
1001 // Fade the tooltip out when close to it.
1003 immutable float ggx = nvg__absf((mx-(bounds[0]+bounds[2])*0.5f)/(bounds[0]-bounds[2]));
1004 immutable float ggy = nvg__absf((my-(bounds[1]+bounds[3])*0.5f)/(bounds[1]-bounds[3]));
1006 immutable float a = nvg__clamp(nvg__max(ggx, ggy)-0.5f, 0, 1);
1007 nvg.globalAlpha(a);
1009 nvg.beginPath();
1010 nvg.fillColor = nvgRGBA(220, 220, 220, 255);
1011 nvg.roundedRect(bounds[0]-2, bounds[1]-2, cast(int)(bounds[2]-bounds[0])+4, cast(int)(bounds[3]-bounds[1])+4, 3);
1012 immutable int px = cast(int)((bounds[2]+bounds[0])/2);
1013 nvg.moveTo(px, bounds[1]-10);
1014 nvg.lineTo(px+7, bounds[1]+1);
1015 nvg.lineTo(px-7, bounds[1]+1);
1016 nvg.fill();
1018 nvg.fillColor = nvgRGBA(0, 0, 0, 220);
1019 nvg.textBox(x, y, 150, "Hover your mouse over the text to see calculated caret position.");
1024 void drawWidths(NVGContext nvg, float x, float y, float width) {
1025 nvg.save();
1026 scope(exit) nvg.restore();
1028 nvg.strokeColor = nvgRGBA(0, 0, 0, 255);
1029 foreach (int i; 0..20) {
1030 nvg.strokeWidth = (i+0.5f)*0.1f;
1031 nvg.beginPath();
1032 nvg.moveTo(x, y);
1033 nvg.lineTo(x+width, y+width*0.3f);
1034 nvg.stroke();
1035 y += 10;
1040 void drawCaps (NVGContext nvg, float x, float y, float width) {
1041 static immutable NVGLineCap[3] caps = [NVGLineCap.Butt, NVGLineCap.Round, NVGLineCap.Square];
1042 enum lineWidth = 8.0f;
1044 nvg.save();
1045 scope(exit) nvg.restore();
1047 nvg.beginPath();
1048 nvg.rect(x-lineWidth/2, y, width+lineWidth, 40);
1049 nvg.fillColor = nvgRGBA(255, 255, 255, 32);
1050 nvg.fill();
1052 nvg.beginPath();
1053 nvg.rect(x, y, width, 40);
1054 nvg.fillColor = nvgRGBA(255, 255, 255, 32);
1055 nvg.fill();
1057 nvg.strokeWidth = lineWidth;
1058 foreach (int i; 0..3) {
1059 nvg.lineCap = caps[i];
1060 nvg.strokeColor = nvgRGBA(0, 0, 0, 255);
1061 nvg.beginPath();
1062 nvg.moveTo(x, y+i*10+5);
1063 nvg.lineTo(x+width, y+i*10+5);
1064 nvg.stroke();
1069 void drawScissor (NVGContext nvg, float x, float y, float t) {
1070 nvg.save();
1071 scope(exit) nvg.restore();
1073 // Draw first rect and set scissor to it's area.
1074 nvg.translate(x, y);
1075 nvg.rotate(5.nvgDegrees);
1076 nvg.beginPath();
1077 nvg.rect(-20, -20, 60, 40);
1078 nvg.fillColor = nvgRGBA(255, 0, 0, 255);
1079 nvg.fill();
1080 nvg.scissor(-20, -20, 60, 40);
1082 // Draw second rectangle with offset and rotation.
1083 nvg.translate(40, 0);
1084 nvg.rotate(t);
1086 // Draw the intended second rectangle without any scissoring.
1088 nvg.save();
1089 scope(exit) nvg.restore();
1090 nvg.resetScissor();
1091 nvg.beginPath();
1092 nvg.rect(-20, -10, 60, 30);
1093 nvg.fillColor = nvgRGBA(255, 128, 0, 64);
1094 nvg.fill();
1097 // Draw second rectangle with combined scissoring.
1098 nvg.intersectScissor(-20, -10, 60, 30);
1099 nvg.beginPath();
1100 nvg.rect(-20, -10, 60, 30);
1101 nvg.fillColor = nvgRGBA(255, 128, 0, 255);
1102 nvg.fill();
1106 // ////////////////////////////////////////////////////////////////////////// //
1107 void drawBlendish (NVGContext nvg, float _x, float _y, float _w, float _h, float _t) {
1108 import core.stdc.math : fmodf, cosf, sinf;
1109 import core.stdc.stdio : printf, snprintf;
1111 float x = _x;
1112 float y = _y;
1114 version(nanovega_debug_clipping) {
1115 nvg.nvgClipDumpOn();
1116 scope(exit) nvg.nvgClipDumpOff();
1117 //{ import core.stdc.stdio; printf("==========================\n"); }
1120 nvg.save();
1121 scope(exit) nvg.restore();
1123 if (cliptest) {
1124 nvg.newPath();
1125 //nvg.rect(_x+50, _y+80, 160, 140);
1126 //nvg.roundedRect(_x+50, _y+80, 160, 140, 8);
1127 nvg.ellipse(_x+150, _y+180, 120, 90);
1128 nvg.clip();
1129 //nvg.clipStroke();
1131 version(all) {
1132 nvg.newPath();
1133 nvg.ellipse(_x+150, _y+180, 90, 120);
1134 nvg.clip(NVGClipMode.Union);
1137 version(all) {{
1138 nvg.save();
1139 scope(exit) nvg.restore();
1141 nvg.newPath();
1142 nvg.ellipse(_x+150, _y+180, 60, 160);
1143 nvg.clip(NVGClipMode.Xor);
1146 nvg.newPath();
1147 nvg.rect(0, 0, 1000, 1000);
1148 nvg.fillColor = NVGColor.yellow;
1149 nvg.fill();
1153 scope(exit) {
1154 nvg.newPath();
1155 //nvg.rect(_x+50, _y+80, 160, 140);
1156 //nvg.roundedRect(_x+50, _y+80, 160, 140, 8);
1157 //nvg.ellipse(_x+150, _y+180, 120, 90);
1158 nvg.rect(_x+30, _y+30, 400, 400);
1159 version(none) {
1160 nvg.strokeWidth = 1;
1161 nvg.strokeColor = NVGColor.yellow;
1162 //nvg.stroke();
1163 } else {
1164 nvg.fillColor = NVGColor.yellow;
1165 nvg.fill();
1170 nvg.globalAlpha(bndMoving ? 0.4 : 0.9);
1172 nvg.bndBackground(_x-10.0f, _y-10.0f, _w, _h);
1173 nvg.currFillPickId = WidgetIdBlendish;
1175 nvg.bndToolButton(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_DEFAULT, BND_ICONID!(6, 3), "Default"); y += 25.0f;
1176 nvg.bndToolButton(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_HOVER, BND_ICONID!(6, 3), "Hovered item"); y += 25.0f;
1177 nvg.bndToolButton(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_ACTIVE, BND_ICONID!(6, 3), "Active"); y += 40.0f;
1179 nvg.bndRadioButton(x, y, 80.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_DEFAULT, -1, "Default"); y += 25.0f;
1180 nvg.bndRadioButton(x, y, 80.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_HOVER, -1, "Hovered item"); y += 25.0f;
1181 nvg.bndRadioButton(x, y, 80.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_ACTIVE, -1, "Active"); y += 25.0f;
1183 nvg.bndLabel(x, y, 120.0f, BND_WIDGET_HEIGHT, -1, "Label:"); y += BND_WIDGET_HEIGHT;
1184 nvg.bndChoiceButton(x, y, 80.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_DEFAULT, -1, "Default"); y += 25.0f;
1185 nvg.bndChoiceButton(x, y, 80.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_HOVER, -1, "Hovered item"); y += 25.0f;
1186 nvg.bndChoiceButton(x, y, 80.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_ACTIVE, -1, "Active"); y += 25.0f;
1188 float ry = y;
1189 float rx = x;
1191 y = _y;
1192 x += 130.0f;
1193 nvg.bndOptionButton(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_DEFAULT, "Default"); y += 25.0f;
1194 nvg.bndOptionButton(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_HOVER, "Hovered item"); y += 25.0f;
1195 nvg.bndOptionButton(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_ACTIVE, "Active"); y += 40.0f;
1197 nvg.bndNumberField(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_DOWN, BND_DEFAULT, "Top", "100"); y += BND_WIDGET_HEIGHT-2.0f;
1198 nvg.bndNumberField(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, "Center", "100"); y += BND_WIDGET_HEIGHT-2.0f;
1199 nvg.bndNumberField(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_TOP, BND_DEFAULT, "Bottom", "100");
1201 float mx = x-30.0f;
1202 float my = y-12.0f;
1203 float mw = 120.0f;
1205 nvg.bndMenuBackground(mx, my, mw, 120.0f, BND_CORNER_TOP);
1206 nvg.bndMenuLabel(mx, my, mw, BND_WIDGET_HEIGHT, -1, "Menu Title"); my += BND_WIDGET_HEIGHT-2.0f;
1207 nvg.bndMenuItem(mx, my, mw, BND_WIDGET_HEIGHT, BND_DEFAULT, BND_ICONID!(17, 3), "Default"); my += BND_WIDGET_HEIGHT-2.0f;
1208 nvg.bndMenuItem(mx, my, mw, BND_WIDGET_HEIGHT, BND_HOVER, BND_ICONID!(18, 3), "Hovered item!"); my += BND_WIDGET_HEIGHT-2.0f;
1209 nvg.bndMenuItem(mx, my, mw, BND_WIDGET_HEIGHT, BND_ACTIVE, BND_ICONID!(19, 3), "Active");
1211 y = _y;
1212 x += 130.0f;
1213 float ox = x;
1214 nvg.bndNumberField(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_DEFAULT, "Default", "100"); y += 25.0f;
1215 nvg.bndNumberField(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_HOVER, "Hovered", "100"); y += 25.0f;
1216 nvg.bndNumberField(x, y, 120.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_ACTIVE, "Active", "100"); y += 40.0f;
1218 nvg.bndRadioButton(x, y, 60.0f, BND_WIDGET_HEIGHT, BND_CORNER_RIGHT, BND_DEFAULT, -1, "One"); x += 60.0f-1.0f;
1219 nvg.bndRadioButton(x, y, 60.0f, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, -1, "Two"); x += 60.0f-1.0f;
1220 nvg.bndRadioButton(x, y, 60.0f, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, -1, "Three"); x += 60.0f-1.0f;
1221 nvg.bndRadioButton(x, y, 60.0f, BND_WIDGET_HEIGHT, BND_CORNER_LEFT, BND_ACTIVE, -1, "Butts");
1223 x = ox;
1224 y += 40.0f;
1225 float progress_value = fmodf(_t/10.0f, 1.0f);
1226 char[32] progressLabel;
1227 int len = cast(int)snprintf(progressLabel.ptr, progressLabel.length, "%d%%", cast(int)(progress_value*100+0.5f));
1229 nvg.bndSlider(x, y, 240, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_DEFAULT, progress_value, "Default", progressLabel[0..len]); y += 25.0f;
1230 nvg.bndSlider(x, y, 240, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_HOVER, progress_value, "Hovered", progressLabel[0..len]); y += 25.0f;
1231 nvg.bndSlider(x, y, 240, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_ACTIVE, progress_value, "Active", progressLabel[0..len]);
1233 float rw = x+240.0f-rx;
1234 float s_offset = sinf(_t/2.0f)*0.5f+0.5f;
1235 float s_size = cosf(_t/3.11f)*0.5f+0.5f;
1237 nvg.bndScrollBar(rx, ry, rw, BND_SCROLLBAR_HEIGHT, BND_DEFAULT, s_offset, s_size); ry += 20.0f;
1238 nvg.bndScrollBar(rx, ry, rw, BND_SCROLLBAR_HEIGHT, BND_HOVER, s_offset, s_size); ry += 20.0f;
1239 nvg.bndScrollBar(rx, ry, rw, BND_SCROLLBAR_HEIGHT, BND_ACTIVE, s_offset, s_size);
1241 static immutable string edit_text = "The quick brown fox";
1242 int textlen = cast(int)edit_text.length+1;
1243 int t = cast(int)(_t*2);
1244 int idx1 = (t/textlen)%textlen;
1245 int idx2 = idx1+(t%(textlen-idx1));
1247 ry += 25.0f;
1248 nvg.bndTextField(rx, ry, 240.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_DEFAULT, -1, edit_text, idx1, idx2); ry += 25.0f;
1249 nvg.bndTextField(rx, ry, 240.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_HOVER, -1, edit_text, idx1, idx2); ry += 25.0f;
1250 nvg.bndTextField(rx, ry, 240.0f, BND_WIDGET_HEIGHT, BND_CORNER_NONE, BND_ACTIVE, -1, edit_text, idx1, idx2);
1252 rx += rw+20.0f;
1253 ry = _y;
1254 nvg.bndScrollBar(rx, ry, BND_SCROLLBAR_WIDTH, 240.0f, BND_DEFAULT, s_offset, s_size); rx += 20.0f;
1255 nvg.bndScrollBar(rx, ry, BND_SCROLLBAR_WIDTH, 240.0f, BND_HOVER, s_offset, s_size); rx += 20.0f;
1256 nvg.bndScrollBar(rx, ry, BND_SCROLLBAR_WIDTH, 240.0f, BND_ACTIVE, s_offset, s_size);
1258 x = ox;
1259 y += 40.0f;
1260 nvg.bndToolButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_RIGHT, BND_DEFAULT, BND_ICONID!(0, 10), null); x += BND_TOOL_WIDTH-1;
1261 nvg.bndToolButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, BND_ICONID!(1, 10), null); x += BND_TOOL_WIDTH-1;
1262 nvg.bndToolButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, BND_ICONID!(2, 10), null); x += BND_TOOL_WIDTH-1;
1263 nvg.bndToolButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, BND_ICONID!(3, 10), null); x += BND_TOOL_WIDTH-1;
1264 nvg.bndToolButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, BND_ICONID!(4, 10), null); x += BND_TOOL_WIDTH-1;
1265 nvg.bndToolButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_LEFT, BND_DEFAULT, BND_ICONID!(5, 10), null); x += BND_TOOL_WIDTH-1;
1267 x += 5.0f;
1268 nvg.bndRadioButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_RIGHT, BND_DEFAULT, BND_ICONID!(0, 11), null); x += BND_TOOL_WIDTH-1;
1269 nvg.bndRadioButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, BND_ICONID!(1, 11), null); x += BND_TOOL_WIDTH-1;
1270 nvg.bndRadioButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, BND_ICONID!(2, 11), null); x += BND_TOOL_WIDTH-1;
1271 nvg.bndRadioButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, BND_ICONID!(3, 11), null); x += BND_TOOL_WIDTH-1;
1272 nvg.bndRadioButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_ACTIVE, BND_ICONID!(4, 11), null); x += BND_TOOL_WIDTH-1;
1273 nvg.bndRadioButton(x, y, BND_TOOL_WIDTH, BND_WIDGET_HEIGHT, BND_CORNER_LEFT, BND_DEFAULT, BND_ICONID!(5, 11), null);
1277 // ////////////////////////////////////////////////////////////////////////// //
1278 void main () {
1279 import core.time;
1281 DemoData data;
1282 NVGContext nvg = null;
1283 PerfGraph fpsStats;
1285 double mx = 0, my = 0;
1286 bool doQuit = false;
1287 int fps = 30, prevfps = 0;
1288 auto nextFrameTime = MonoTime.currTime;
1290 setOpenGLContextVersion(3, 0); // it's enough
1292 sdpyWindowClass = "NANOVEGA_EXAMPLE";
1293 auto sdwindow = new SimpleWindow(GWidth, GHeight, "NanoVega", OpenGlOptions.yes, Resizability.fixedSize);
1295 void postRepaint (int tout=0) {
1296 if (sdwindow !is null && !sdwindow.eventQueued!RepaintEvent) {
1297 if (tout < 0) tout = 0;
1298 sdwindow.postTimeout(evRepaint, tout);
1302 void postNextFrame () {
1303 if (sdwindow is null || sdwindow.eventQueued!RepaintEvent) return;
1304 int tout = 0;
1305 if (fps > 0) {
1306 auto stt = MonoTime.currTime;
1307 if (prevfps < 1) {
1308 nextFrameTime = stt+(1000/fps).msecs;
1309 } else if (nextFrameTime <= stt) {
1310 while (nextFrameTime <= stt) nextFrameTime += (1000/fps).msecs;
1311 //nextFrameTime = stt+(1000/fps).msecs;
1312 } else {
1313 tout = cast(int)((nextFrameTime-stt).total!"msecs");
1316 //conwriteln("prevfps=", prevfps, "; fps=", fps, "; tout=", tout);
1317 prevfps = fps;
1318 sdwindow.postTimeout(evRepaint, tout);
1321 version(X11) sdwindow.closeQuery = delegate () { doQuit = true; };
1323 sdwindow.onClosing = delegate () {
1324 if (nvg !is null) {
1325 freeDemoData(nvg, data);
1326 bndClearIconImage();
1327 nvg.kill();
1331 auto stt = MonoTime.currTime;
1332 auto prevt = MonoTime.currTime;
1334 sdwindow.visibleForTheFirstTime = delegate () {
1335 sdwindow.vsync = false;
1337 nvg = nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);
1338 if (nvg is null) fatal("cannot init NanoVega");
1339 if (!nvg.loadDemoData(data)) fatal("cannot load demo data");
1341 fpsStats = new PerfGraph("Frame Time", PerfGraph.Style.FPS, "sans");
1344 int frc = 0, totalFrames = 0;
1346 sdwindow.redrawOpenGlScene = delegate () {
1347 if (fps != 0) postNextFrame();
1349 // Update and render
1350 glViewport(0, 0, sdwindow.width, sdwindow.height);
1351 if (premult) glClearColor(0, 0, 0, 0); else glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
1352 glClear(glNVGClearFlags);
1354 // timers
1355 auto curt = MonoTime.currTime;
1356 double dt = cast(double)((curt-prevt).total!"msecs")/1000.0;
1357 double secs = cast(double)((curt-stt).total!"msecs")/1000.0;
1358 prevt = curt;
1360 //{ import core.stdc.stdio; printf("frame time: %d\n", cast(int)(dt*1000)); }
1362 if (nvg !is null) {
1363 ++totalFrames;
1364 if (fpsStats !is null) fpsStats.update(dt);
1365 nvg.beginFrame(GWidth, GHeight);
1366 scope(exit) nvg.endFrame();
1367 renderDemo(nvg, mx, my, GWidth, GHeight, secs, blowup, data);
1368 if (fpsStats !is null) fpsStats.render(nvg, 5, 5);
1370 ++frc;
1371 if (frc >= 60) {
1373 import core.stdc.stdio : snprintf;
1374 frc = 0;
1375 char[128] buf = void;
1376 auto t = buf[0..snprintf(buf.ptr, buf.length, "NanoVega: %d msecs per frame", cast(int)(secs*1000/totalFrames))].idup;
1377 sdwindow.title = t;
1383 sdwindow.addEventListener((RepaintEvent evt) {
1384 sdwindow.redrawOpenGlSceneNow();
1387 sdwindow.eventLoop(0,
1388 delegate () {
1390 if (sdwindow.closed) return;
1391 if (doQuit) { sdwindow.close(); return; }
1392 sdwindow.redrawOpenGlSceneNow();
1396 delegate (KeyEvent event) {
1397 if (sdwindow.closed) return;
1398 scope(exit) if (event.pressed) postNextFrame();
1399 if (event == "D-*-Q" || event == "D-Escape") { sdwindow.close(); return; }
1400 if (event == "D-Space") { blowup = !blowup; return; }
1401 if (event == "D-P") { premult = !premult; return; }
1402 if (event == "D-T") {
1403 nvg.tesselation = cast(NVGTesselation)(nvg.tesselation == NVGTesselation.max ? NVGTesselation.min : nvg.tesselation+1);
1404 { import iv.vfs.io; writeln("bezier tesselator: ", nvg.tesselation); }
1405 return;
1407 if (event == "D-C") { cliptest = !cliptest; return; }
1408 int newfps = -666;
1409 if (event == "D-1") newfps = 10;
1410 if (event == "D-2") newfps = 20;
1411 if (event == "D-3") newfps = 30;
1412 if (event == "D-6") newfps = 60;
1413 if (event == "D-9") newfps = 120;
1414 if (event == "D-0") newfps = -1;
1415 if (event == "D-X") newfps = 0;
1416 if (newfps == -666) return;
1417 fps = newfps;
1418 conwriteln("FPS: ", fps);
1419 totalFrames = frc = 0;
1422 delegate (MouseEvent event) {
1423 scope(exit) postNextFrame();
1425 mx = event.x;
1426 my = event.y;
1428 int wid = nvg.hitTest(mx, my, NVGPickKind.Fill);
1430 if (event == "RMB-Down") {
1431 if (wid == WidgetIdRaw) { wgOnTop = true; wgMoving = true; bndMoving = false; }
1432 else if (wid == WidgetIdBlendish) { wgOnTop = false; wgMoving = false; bndMoving = true; }
1435 if (event == "RMB-Up") {
1436 wgMoving = false;
1437 bndMoving = false;
1440 if (event == "LMB-Down") {
1441 if (wid == WidgetIdRaw) wgOnTop = true;
1442 else if (wid == WidgetIdBlendish) wgOnTop = false;
1445 if (event == "Motion") {
1446 if (bndMoving) { bndX += event.dx; bndY += event.dy; }
1447 if (wgMoving) { wgX += event.dx; wgY += event.dy; }
1450 if (wid == WidgetIdRaw || wid == WidgetIdBlendish) mx = my = -666;