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 }