1 module examples.sdl; 2 3 import std.datetime : Clock; 4 import arsd.nanovega; 5 import nanogui.sdlbackend : SdlBackend; 6 import nanogui.widget : Widget; 7 import nanogui.glcanvas : GLCanvas; 8 9 struct Vertex 10 { 11 import nanogui.common; 12 Vector3f position; 13 Vector3f color; 14 } 15 16 extern(C) 17 uint timer_callback(uint interval, void *param) nothrow 18 { 19 import gfm.sdl2; 20 21 SDL_Event event; 22 SDL_UserEvent userevent; 23 24 userevent.type = SDL_USEREVENT; 25 userevent.code = 0; 26 userevent.data1 = null; 27 userevent.data2 = null; 28 29 event.type = SDL_USEREVENT; 30 event.user = userevent; 31 32 SDL_PushEvent(&event); 33 return(interval); 34 } 35 36 class MyGlCanvas : GLCanvas 37 { 38 import std.typecons : scoped; 39 import gfm.opengl; 40 import gfm.math; 41 import nanogui.common; 42 43 this(Widget parent, int w, int h) 44 { 45 super(parent, w, h); 46 47 const program_source = 48 "#version 130 49 50 #if VERTEX_SHADER 51 uniform mat4 modelViewProj; 52 in vec3 position; 53 in vec3 color; 54 out vec4 frag_color; 55 void main() { 56 frag_color = modelViewProj * vec4(0.5 * color, 1.0); 57 gl_Position = modelViewProj * vec4(position / 2, 1.0); 58 } 59 #endif 60 61 #if FRAGMENT_SHADER 62 out vec4 color; 63 in vec4 frag_color; 64 void main() { 65 color = frag_color; 66 } 67 #endif"; 68 69 _program = new GLProgram(program_source); 70 assert(_program); 71 auto vert_spec = scoped!(VertexSpecification!Vertex)(_program); 72 _rotation = Vector3f(0.25f, 0.5f, 0.33f); 73 74 int[12*3] indices = 75 [ 76 0, 1, 3, 77 3, 2, 1, 78 3, 2, 6, 79 6, 7, 3, 80 7, 6, 5, 81 5, 4, 7, 82 4, 5, 1, 83 1, 0, 4, 84 4, 0, 3, 85 3, 7, 4, 86 5, 6, 2, 87 2, 1, 5, 88 ]; 89 90 auto vertices = 91 [ 92 Vertex(Vector3f(-1, 1, 1), Vector3f(1, 0, 0)), 93 Vertex(Vector3f(-1, 1, -1), Vector3f(0, 1, 0)), 94 Vertex(Vector3f( 1, 1, -1), Vector3f(1, 1, 0)), 95 Vertex(Vector3f( 1, 1, 1), Vector3f(0, 0, 1)), 96 Vertex(Vector3f(-1, -1, 1), Vector3f(1, 0, 1)), 97 Vertex(Vector3f(-1, -1, -1), Vector3f(0, 1, 1)), 98 Vertex(Vector3f( 1, -1, -1), Vector3f(1, 1, 1)), 99 Vertex(Vector3f( 1, -1, 1), Vector3f(0.5, 0.5, 0.5)), 100 ]; 101 102 auto vbo = scoped!GLBuffer(GL_ARRAY_BUFFER, GL_STATIC_DRAW, vertices); 103 auto ibo = scoped!GLBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, indices); 104 105 _vao = scoped!GLVAO(); 106 // prepare VAO 107 { 108 _vao.bind(); 109 vbo.bind(); 110 ibo.bind(); 111 vert_spec.use(); 112 _vao.unbind(); 113 } 114 115 { 116 import gfm.sdl2 : SDL_AddTimer; 117 uint delay = 40; 118 _timer_id = SDL_AddTimer(delay, &timer_callback, null); 119 } 120 } 121 122 ~this() 123 { 124 import gfm.sdl2 : SDL_RemoveTimer; 125 SDL_RemoveTimer(_timer_id); 126 } 127 128 override void drawGL() 129 { 130 static long start_time; 131 mat4f mvp; 132 mvp = mat4f.identity; 133 134 if (start_time == 0) 135 start_time = Clock.currTime.stdTime; 136 137 auto angle = (Clock.currTime.stdTime - start_time)/10_000_000.0*rotateSpeed; 138 mvp = mvp.rotation(angle, _rotation); 139 140 GLboolean depth_test_enabled; 141 glGetBooleanv(GL_DEPTH_TEST, &depth_test_enabled); 142 if (!depth_test_enabled) 143 glEnable(GL_DEPTH_TEST); 144 scope(exit) 145 { 146 if (!depth_test_enabled) 147 glDisable(GL_DEPTH_TEST); 148 } 149 150 _program.uniform("modelViewProj").set(mvp); 151 _program.use(); 152 scope(exit) _program.unuse(); 153 154 _vao.bind(); 155 glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, cast(void *) 0); 156 _vao.unbind(); 157 } 158 159 private: 160 GLProgram _program; 161 Vector3f _rotation; 162 163 import gfm.sdl2 : SDL_TimerID; 164 SDL_TimerID _timer_id; 165 166 import std.typecons : scoped; 167 import gfm.opengl : GLVAO; 168 169 float rotateSpeed = 1.0; 170 alias ScopedGLVAO = typeof(scoped!GLVAO()); 171 ScopedGLVAO _vao; 172 } 173 174 class MyGui : SdlBackend 175 { 176 import nanogui.label; 177 import resusage : ProcessCPUWatcher; 178 179 Label lblCpuUsage; 180 ProcessCPUWatcher cpuWatcher; 181 182 this(int w, int h, string title) 183 { 184 super(w, h, title); 185 } 186 187 override void onVisibleForTheFirstTime() 188 { 189 import nanogui.screen : Screen; 190 import nanogui.widget, nanogui.theme, nanogui.checkbox, nanogui.label, 191 nanogui.common, nanogui.window, nanogui.layout, nanogui.button, 192 nanogui.popupbutton, nanogui.entypo, nanogui.popup, nanogui.vscrollpanel, 193 nanogui.combobox, nanogui.textbox, nanogui.formhelper; 194 195 { 196 auto window = new Window(screen, "Button demo", true); 197 window.position(Vector2i(15, 15)); 198 window.size = Vector2i(190, 370); 199 window.layout(new GroupLayout()); 200 201 new Label(window, "Push buttons", "sans-bold"); 202 203 auto checkbox = new CheckBox(window, "Checkbox #1", null); 204 checkbox.position = Vector2i(100, 190); 205 checkbox.size = checkbox.preferredSize(ctx); 206 checkbox.checked = true; 207 208 auto label = new Label(window, "Label"); 209 label.position = Vector2i(100, 300); 210 label.size = label.preferredSize(ctx); 211 212 Popup popup; 213 214 auto btn = new Button(window, "Button"); 215 btn.callback = () { 216 popup.children[0].visible = !popup.children[0].visible; 217 label.caption = popup.children[0].visible ? 218 "Popup label is visible" : "Popup label isn't visible"; 219 }; 220 221 auto popupBtn = new PopupButton(window, "PopupButton", Entypo.ICON_EXPORT); 222 popup = popupBtn.popup; 223 popup.layout(new GroupLayout()); 224 new Label(popup, "Arbitrary widgets can be placed here"); 225 new CheckBox(popup, "A check box", null); 226 227 window.tooltip = "Button demo tooltip"; 228 } 229 230 { 231 auto window = new Window(screen, "Button group example"); 232 window.position(Vector2i(220, 15)); 233 window.layout(new GroupLayout()); 234 235 auto buttonGroup = ButtonGroup(); 236 237 auto btn = new Button(window, "RadioButton1"); 238 btn.flags = Button.Flags.RadioButton; 239 btn.buttonGroup = buttonGroup; 240 btn.tooltip = "Radio button ONE"; 241 buttonGroup ~= btn; 242 243 btn = new Button(window, "RadioButton2"); 244 btn.flags = Button.Flags.RadioButton; 245 btn.buttonGroup = buttonGroup; 246 btn.tooltip = "Radio button TWO"; 247 buttonGroup ~= btn; 248 249 btn = new Button(window, "RadioButton3"); 250 btn.flags = Button.Flags.RadioButton; 251 btn.buttonGroup = buttonGroup; 252 btn.tooltip = "Radio button THREE"; 253 buttonGroup ~= btn; 254 255 window.tooltip = "Radio button group tooltip"; 256 } 257 258 { 259 auto window = new Window(screen, "Button with image window"); 260 window.position(Vector2i(400, 15)); 261 window.layout(new GroupLayout()); 262 263 auto image = ctx.nvg.createImage("resources/icons/start.jpeg", [NVGImageFlags.ClampToBorderX, NVGImageFlags.ClampToBorderY]); 264 auto btn = new Button(window, "Start", image); 265 // some optional height, not font size, not icon height 266 btn.fixedHeight = 130; 267 268 // yet another Button with the same image but default size 269 new Button(window, "Start", image); 270 271 window.tooltip = "Window with button that has image as an icon"; 272 } 273 274 { 275 auto window = new Window(screen, "Combobox window"); 276 window.position(Vector2i(600, 15)); 277 window.layout(new GroupLayout()); 278 279 new Label(window, "Message dialog", "sans-bold"); 280 import std.algorithm : map; 281 import std.range : iota; 282 import std.array : array; 283 import std.conv : text; 284 auto items = 15.iota.map!(a=>text("items", a)).array; 285 auto cb = new ComboBox(window, items); 286 cb.cursor = Cursor.Hand; 287 cb.tooltip = "This widget has custom cursor value - Cursor.Hand"; 288 289 window.tooltip = "Window with ComboBox tooltip"; 290 } 291 292 { 293 const width = 340; 294 const height = 350; 295 296 auto window = new Window(screen, "Virtual TreeView demo", true); 297 window.position(Vector2i(10, 400)); 298 window.size(Vector2i(width, height)); 299 window.setId = "window"; 300 auto layout = new BoxLayout(Orientation.Vertical); 301 window.layout(layout); 302 layout.margin = 5; 303 layout.setAlignment = Alignment.Fill; 304 305 version(none) 306 { 307 auto panel = new Widget(window); 308 panel.setId = "panel"; 309 auto layout2 = new BoxLayout(Orientation.Horizontal); 310 panel.layout(layout2); 311 layout2.margin = 5; 312 layout2.setAlignment = Alignment.Fill; 313 } 314 315 Item[] data; 316 enum total = 1_000_000; 317 data.reserve(total); 318 foreach(i; 0..total) 319 { 320 import std.conv : text; 321 import std.random : uniform; 322 const x = uniform(0, 6); 323 switch(x) 324 { 325 case 0: 326 float f = cast(float) i; 327 data ~= Item(f); 328 break; 329 case 1: 330 int n = cast(int) i; 331 data ~= Item(n); 332 break; 333 case 2: 334 string str = text("item #", i); 335 data ~= Item(str); 336 break; 337 case 3: 338 double d = cast(double) i; 339 data ~= Item(d); 340 break; 341 case 4: 342 Test t; 343 data ~= Item(t); 344 break; 345 default: 346 case 5: 347 Test2 t2; 348 data ~= Item(t2); 349 break; 350 } 351 } 352 353 import nanogui.experimental.list; 354 auto list = new List!(typeof(data))(window, data); 355 version(none) auto list = new List!(typeof(data))(panel, data); 356 list.collapsed = false; 357 list.setId = "virtual list"; 358 } 359 360 { 361 auto asian_theme = new Theme(ctx); 362 363 { 364 // sorta hack because loading font in nvg results in 365 // conflicting font id 366 auto nvg2 = nvgCreateContext(NVGContextFlag.Debug); 367 scope(exit) nvg2.kill; 368 nvg2.createFont("chihaya", "./resources/fonts/n_chihaya_font.ttf"); 369 ctx.nvg.addFontsFrom(nvg2); 370 asian_theme.mFontNormal = ctx.nvg.findFont("chihaya"); 371 } 372 373 auto window = new Window(screen, "Textbox window"); 374 window.position = Vector2i(750, 15); 375 window.fixedSize = Vector2i(200, 350); 376 window.layout(new GroupLayout()); 377 window.tooltip = "Window with TextBoxes"; 378 379 auto tb = new TextBox(window, "Россия"); 380 tb.editable = true; 381 382 tb = new TextBox(window, "England"); 383 tb.editable = true; 384 385 tb = new TextBox(window, "日本"); 386 tb.theme = asian_theme; 387 tb.editable = true; 388 389 tb = new TextBox(window, "中国"); 390 tb.theme = asian_theme; 391 tb.editable = true; 392 393 version(none) 394 { 395 396 // Added two arabic themes after https://forum.dlang.org/thread/sbymyafmdrsfgemlgsld@forum.dlang.org 397 // currently don't work as expected 398 399 auto arabic_theme1 = new Theme(ctx); 400 { 401 // sorta hack because loading font in nvg results in 402 // conflicting font id 403 auto nvg2 = nvgCreateContext(NVGContextFlag.Debug); 404 scope(exit) nvg2.kill; 405 nvg2.createFont("arabic1", "./resources/fonts/Amiri-Regular.ttf"); 406 ctx.nvg.addFontsFrom(nvg2); 407 arabic_theme1.mFontNormal = ctx.nvg.findFont("arabic1"); 408 } 409 410 auto arabic_theme2 = new Theme(ctx); 411 { 412 // sorta hack because loading font in nvg results in 413 // conflicting font id 414 auto nvg2 = nvgCreateContext(NVGContextFlag.Debug); 415 scope(exit) nvg2.kill; 416 nvg2.createFont("arabic2", "./resources/fonts/ElMessiri-VariableFont_wght.ttf"); 417 ctx.nvg.addFontsFrom(nvg2); 418 arabic_theme2.mFontNormal = ctx.nvg.findFont("arabic2"); 419 } 420 421 tb = new TextBox(window, "حالكم"); 422 tb.theme = arabic_theme1; 423 tb.editable = true; 424 425 tb = new TextBox(window, "حالكم"); 426 tb.theme = arabic_theme2; 427 tb.editable = true; 428 429 } // version(none) 430 } 431 432 { 433 auto window = new Window(screen, "GLCanvas Demo", true); 434 window.size(Vector2i(280, 510)); 435 window.position = Vector2i(400, 240); 436 window.layout = new GroupLayout(); 437 auto glcanvas = new MyGlCanvas(window, 250, 250); 438 glcanvas.backgroundColor = Color(0.1f, 0.1f, 0.1f, 1.0f); 439 glcanvas = new MyGlCanvas(window, 200, 200); 440 glcanvas.fixedSize = Vector2i(200, 200); 441 glcanvas.backgroundColor = Color(0.2f, 0.3f, 0.4f, 1.0f); 442 glcanvas.rotateSpeed = 0.5; 443 } 444 445 { 446 auto window = new Window(screen, "AdvancedGridLayout"); 447 auto layout = new AdvancedGridLayout( 448 [7, 0, 70, 70, 6, 70, 6], // columns width 449 [7, 0, 5, 240, 17, 0, 6], // rows height 450 ); 451 window.position = Vector2i(700, 400); 452 window.layout = layout; 453 454 auto title = new Label(window, "Advanced grid layout"); 455 auto content = new Label(window, "Some text"); 456 layout.setAnchor(title, AdvancedGridLayout.Anchor(1, 1, 5, 1, Alignment.Middle, Alignment.Middle)); 457 layout.setAnchor(content, AdvancedGridLayout.Anchor(1, 3, 5, 1, Alignment.Middle, Alignment.Middle)); 458 layout.setAnchor(new Button(window, "Help"), AdvancedGridLayout.Anchor(1, 5, 1, 1)); 459 layout.setAnchor(new Button(window, "Ok"), AdvancedGridLayout.Anchor(3, 5, 1, 1)); 460 layout.setAnchor(new Button(window, "Cancel"), AdvancedGridLayout.Anchor(5, 5, 1, 1)); 461 } 462 463 { 464 static bool bvar = true; 465 static int ivar = 12345678; 466 static double dvar = 3.1415926; 467 static float fvar = 3.1415926; 468 static string strval = "A string"; 469 470 auto gui = new FormHelper(screen); 471 gui.addWindow(Vector2i(220, 180),"Form helper example"); 472 gui.addGroup("Basic types"); 473 gui.addVariable("bool", bvar); 474 gui.addVariable("string", strval, true); 475 476 gui.addGroup("Validating fields"); 477 // Expose an integer variable by reference 478 gui.addVariable("int", ivar); 479 // Expose a float variable via setter/getter functions 480 gui.addVariable("float", 481 (float value) { fvar = value; }, 482 () { return fvar; }); 483 gui.addVariable("double", dvar).spinnable = true; 484 gui.addButton("Button", () { /* noop */ }); 485 } 486 487 { 488 auto window = new Window(screen, "CPU usage", true); 489 window.position(Vector2i(15, 225)); 490 window.size = Vector2i(100, 60); 491 window.layout(new GroupLayout()); 492 493 import std.process : thisProcessID; 494 import std.conv : text; 495 cpuWatcher = new ProcessCPUWatcher(thisProcessID); 496 497 lblCpuUsage = new Label(window, cpuWatcher.current().text, "sans-bold"); 498 } 499 500 { 501 auto window = new Window(screen, "TreeView demo", true); 502 window.position(Vector2i(600, 130)); 503 window.size = Vector2i(240, 360); 504 window.layout(new BoxLayout(Orientation.Vertical)); 505 506 import nanogui.experimental.treeview; 507 new TreeView!float(window, "TreeView_______", 10f, null); 508 new TreeView!(float[])(window, "TreeView_2_____", [11f, 22f, 33, 44], null); 509 new TreeView!Test(window, "TreeView_3_____", Test(), null); 510 new TreeView!Test2(window, "TreeView_4_____", Test2(), null); 511 512 auto items = [ 513 Item(0.3f), 514 Item(3), 515 Item("some string"), 516 Item(double.nan), 517 Item(Test(99, 100, "another text")), 518 Item(Test2(9.9, 11, Test(-1, -20, "nested Test"))), 519 ]; 520 new TreeView!(Item[])(window, "TaggedAlgebraic[]", items, null); 521 } 522 523 // now we should do layout manually yet 524 screen.performLayout(ctx); 525 } 526 } 527 528 struct Test 529 { 530 float f = 7.7; 531 int i = 8; 532 string s = "some text"; 533 } 534 535 struct Test2 536 { 537 double d = 8.8; 538 long l = 999; 539 Test t; 540 } 541 542 import taggedalgebraic : TaggedAlgebraic; 543 union Payload 544 { 545 float f; 546 int i; 547 string str; 548 double d; 549 Test t; 550 Test2 t2; 551 } 552 alias Item = TaggedAlgebraic!Payload; 553 554 void main () { 555 auto gui = new MyGui(1000, 800, "Nanogui using SDL2 backend"); 556 gui.onBeforeLoopStart = () { 557 import std.datetime : SysTime, seconds, Clock; 558 static SysTime prevStdTime; 559 const currStdTime = Clock.currTime; 560 if (currStdTime - prevStdTime > 1.seconds) 561 { 562 prevStdTime = currStdTime; 563 import std.format : format; 564 import std.parallelism : totalCPUs; 565 gui.lblCpuUsage.caption = format("%2.2f%%", gui.cpuWatcher.current*totalCPUs); 566 } 567 }; 568 gui.run(); 569 }