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, OpenGL gl)
44 	{
45 		super(parent);
46 
47 		_gl = gl;
48 
49 		const program_source = 
50 			"#version 130
51 
52 			#if VERTEX_SHADER
53 			uniform mat4 modelViewProj;
54 			in vec3 position;
55 			in vec3 color;
56 			out vec4 frag_color;
57 			void main() {
58 				frag_color  = modelViewProj * vec4(0.5 * color, 1.0);
59 				gl_Position = modelViewProj * vec4(position / 2, 1.0);
60 			}
61 			#endif
62 
63 			#if FRAGMENT_SHADER
64 			out vec4 color;
65 			in vec4 frag_color;
66 			void main() {
67 				color = frag_color;
68 			}
69 			#endif";
70 
71 		_program = new GLProgram(_gl, program_source);
72 		assert(_program);
73 		auto vert_spec = scoped!(VertexSpecification!Vertex)(_program);
74 		_rotation = Vector3f(0.25f, 0.5f, 0.33f);
75 
76 		int[12*3] indices =
77 		[
78 			0, 1, 3,
79 			3, 2, 1,
80 			3, 2, 6,
81 			6, 7, 3,
82 			7, 6, 5,
83 			5, 4, 7,
84 			4, 5, 1,
85 			1, 0, 4,
86 			4, 0, 3,
87 			3, 7, 4,
88 			5, 6, 2,
89 			2, 1, 5,
90 		];
91 
92 		auto vertices = 
93 		[
94 			Vertex(Vector3f(-1,  1,  1), Vector3f(1, 0, 0)),
95 			Vertex(Vector3f(-1,  1, -1), Vector3f(0, 1, 0)),
96 			Vertex(Vector3f( 1,  1, -1), Vector3f(1, 1, 0)),
97 			Vertex(Vector3f( 1,  1,  1), Vector3f(0, 0, 1)),
98 			Vertex(Vector3f(-1, -1,  1), Vector3f(1, 0, 1)),
99 			Vertex(Vector3f(-1, -1, -1), Vector3f(0, 1, 1)),
100 			Vertex(Vector3f( 1, -1, -1), Vector3f(1, 1, 1)),
101 			Vertex(Vector3f( 1, -1,  1), Vector3f(0.5, 0.5, 0.5)),
102 		];
103 
104 		auto vbo = scoped!GLBuffer(gl, GL_ARRAY_BUFFER, GL_STATIC_DRAW, vertices);
105 		auto ibo = scoped!GLBuffer(gl, GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, indices);
106 
107 		_vao = scoped!GLVAO(gl);
108 		// prepare VAO
109 		{
110 			_vao.bind();
111 			vbo.bind();
112 			ibo.bind();
113 			vert_spec.use();
114 			_vao.unbind();
115 		}
116 
117 		{
118 			import gfm.sdl2 : SDL_AddTimer;
119 			uint delay = 40;
120 			_timer_id = SDL_AddTimer(delay, &timer_callback, null);
121 		}
122 	}
123 
124 	~this()
125 	{
126 		import gfm.sdl2 : SDL_RemoveTimer;
127 		SDL_RemoveTimer(_timer_id);
128 	}
129 
130 	override void drawGL()
131 	{
132 		static long start_time;
133 		mat4f mvp;
134 		mvp = mat4f.identity;
135 
136 		if (start_time == 0)
137 			start_time = Clock.currTime.stdTime;
138 
139 		auto angle = (Clock.currTime.stdTime - start_time)/10_000_000.0;
140 		mvp = mvp.rotation(angle, _rotation);
141 
142 		GLboolean depth_test_enabled;
143 		glGetBooleanv(GL_DEPTH_TEST, &depth_test_enabled);
144 		if (!depth_test_enabled)
145 			glEnable(GL_DEPTH_TEST);
146 		scope(exit)
147 		{
148 			if (!depth_test_enabled)
149 				glDisable(GL_DEPTH_TEST);
150 		}
151 
152 		_program.uniform("modelViewProj").set(mvp);
153 		_program.use();
154 		scope(exit) _program.unuse();
155 
156 		_vao.bind();
157 		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, cast(void *) 0);
158 		_vao.unbind();
159 	}
160 
161 private:
162 	OpenGL    _gl;
163 	GLProgram _program;
164 	Vector3f  _rotation;
165 
166 	import gfm.sdl2 : SDL_TimerID;
167 	SDL_TimerID _timer_id;
168 
169 	import std.typecons : scoped;
170 	import gfm.opengl : GLVAO;
171 
172 	alias ScopedGLVAO = typeof(scoped!GLVAO(OpenGL.init));
173 	ScopedGLVAO    _vao;
174 }
175 
176 class MyGui : SdlBackend
177 {
178 	this(int w, int h, string title)
179 	{
180 		super(w, h, title);
181 	}
182 
183 	override void onVisibleForTheFirstTime()
184 	{
185 		import nanogui.screen : Screen;
186 		import nanogui.widget, nanogui.theme, nanogui.checkbox, nanogui.label, 
187 			nanogui.common, nanogui.window, nanogui.layout, nanogui.button,
188 			nanogui.popupbutton, nanogui.entypo, nanogui.popup, nanogui.vscrollpanel,
189 			nanogui.combobox, nanogui.textbox;
190 		
191 		{
192 			auto window = new Window(screen, "Button demo");
193 			window.position(Vector2i(15, 15));
194 			window.size = Vector2i(screen.size.x - 30, screen.size.y - 30);
195 			window.layout(new GroupLayout());
196 
197 			new Label(window, "Push buttons", "sans-bold");
198 
199 			auto checkbox = new CheckBox(window, "Checkbox #1", null);
200 			checkbox.position = Vector2i(100, 190);
201 			checkbox.size = checkbox.preferredSize(nvg);
202 			checkbox.checked = true;
203 
204 			auto label = new Label(window, "Label");
205 			label.position = Vector2i(100, 300);
206 			label.size = label.preferredSize(nvg);
207 
208 			Popup popup;
209 
210 			auto btn = new Button(window, "Button");
211 			btn.callback = () { 
212 				popup.children[0].visible = !popup.children[0].visible; 
213 				label.caption = popup.children[0].visible ? 
214 					"Popup label is visible" : "Popup label isn't visible";
215 			};
216 
217 			auto popupBtn = new PopupButton(window, "PopupButton", Entypo.ICON_EXPORT);
218 			popup = popupBtn.popup;
219 			popup.layout(new GroupLayout());
220 			new Label(popup, "Arbitrary widgets can be placed here");
221 			new CheckBox(popup, "A check box", null);
222 
223 			window.tooltip = "Button demo tooltip";
224 		}
225 
226 		{
227 			auto window = new Window(screen, "Button group example");
228 			window.position(Vector2i(220, 15));
229 			window.layout(new GroupLayout());
230 
231 			auto buttonGroup = ButtonGroup();
232 
233 			auto btn = new Button(window, "RadioButton1");
234 			btn.flags = Button.Flags.RadioButton;
235 			btn.buttonGroup = buttonGroup;
236 			btn.tooltip = "Radio button ONE";
237 			buttonGroup ~= btn;
238 
239 			btn = new Button(window, "RadioButton2");
240 			btn.flags = Button.Flags.RadioButton;
241 			btn.buttonGroup = buttonGroup;
242 			btn.tooltip = "Radio button TWO";
243 			buttonGroup ~= btn;
244 
245 			btn = new Button(window, "RadioButton3");
246 			btn.flags = Button.Flags.RadioButton;
247 			btn.buttonGroup = buttonGroup;
248 			btn.tooltip = "Radio button THREE";
249 			buttonGroup ~= btn;
250 
251 			window.tooltip = "Radio button group tooltip";
252 		}
253 
254 		{
255 			auto window = new Window(screen, "Button with image window");
256 			window.position(Vector2i(400, 15));
257 			window.layout(new GroupLayout());
258 
259 			auto image = nvg.createImage("resources/icons/start.jpeg", [NVGImageFlags.ClampToBorderX, NVGImageFlags.ClampToBorderY]);
260 			auto btn = new Button(window, "Start", image);
261 			// some optional height, not font size, not icon height
262 			btn.fixedHeight = 130;
263 
264 			// yet another Button with the same image but default size
265 			new Button(window, "Start", image);
266 
267 			window.tooltip = "Window with button that has image as an icon";
268 		}
269 
270 		{
271 			auto window = new Window(screen, "Combobox window");
272 			window.position(Vector2i(600, 15));
273 			window.layout(new GroupLayout());
274 
275 			new Label(window, "Message dialog", "sans-bold");
276 			import std.algorithm : map;
277 			import std.range : iota;
278 			import std.array : array;
279 			import std.conv : text;
280 			auto items = 15.iota.map!(a=>text("items", a)).array;
281 			auto cb = new ComboBox(window, items);
282 			cb.cursor = Cursor.Hand;
283 			cb.tooltip = "This widget has custom cursor value - Cursor.Hand";
284 
285 			window.tooltip = "Window with ComboBox tooltip";
286 		}
287 
288 		{
289 			int width      = 400;
290 			int half_width = width / 2;
291 			int height     = 200;
292 
293 			auto window = new Window(screen, "All Icons");
294 			window.position(Vector2i(0, 400));
295 			window.fixedSize(Vector2i(width, height));
296 
297 			// attach a vertical scroll panel
298 			auto vscroll = new VScrollPanel(window);
299 			vscroll.fixedSize(Vector2i(width, height));
300 
301 			// vscroll should only have *ONE* child. this is what `wrapper` is for
302 			auto wrapper = new Widget(vscroll);
303 			wrapper.fixedSize(Vector2i(width, height));
304 			wrapper.layout(new GridLayout());// defaults: 2 columns
305 
306 			foreach(i; 0..100)
307 			{
308 				import std.conv : text;
309 				auto item = new Button(wrapper, "item" ~ i.text, Entypo.ICON_AIRCRAFT_TAKE_OFF);
310 				item.iconPosition(Button.IconPosition.Left);
311 				item.fixedWidth(half_width);
312 			}
313 		}
314 
315 		{
316 			auto asian_theme = new Theme(nvg);
317 
318 			{
319 				// sorta hack because loading font in nvg results in
320 				// conflicting font id
321 				auto nvg2 = nvgCreateContext(NVGContextFlag.Debug);
322 				scope(exit) nvg2.kill;
323 				nvg2.createFont("chihaya", "./resources/fonts/n_chihaya_font.ttf");
324 				nvg.addFontsFrom(nvg2);
325 				asian_theme.mFontNormal = nvg.findFont("chihaya");
326 			}
327 
328 			auto window = new Window(screen, "Textbox window");
329 			window.position = Vector2i(750, 15);
330 			window.fixedSize = Vector2i(200, 350);
331 			window.layout(new GroupLayout());
332 			window.tooltip = "Window with TextBoxes";
333 
334 			auto tb = new TextBox(window, "Россия");
335 			tb.editable = true;
336 
337 			tb = new TextBox(window, "England");
338 			tb.editable = true;
339 
340 			tb = new TextBox(window, "日本");
341 			tb.theme = asian_theme;
342 			tb.editable = true;
343 
344 			tb = new TextBox(window, "中国");
345 			tb.theme = asian_theme;
346 			tb.editable = true;
347 		}
348 
349 		{
350 			auto window = new Window(screen, "GLCanvas Demo");
351 			window.position = Vector2i(450, 400);
352 			window.layout = new GroupLayout();
353 			auto glcanvas = new MyGlCanvas(window, gl);
354 			glcanvas.size = Vector2i(300, 300);
355 			glcanvas.backgroundColor = Color(0.1f, 0.1f, 0.1f, 1.0f);
356 		}
357 		
358 		// now we should do layout manually yet
359 		screen.performLayout(nvg);
360 	}
361 }
362 
363 void main () {
364 	
365 	auto gui = new MyGui(1000, 800, "Nanogui using SDL2 backend");
366 	gui.run();
367 }