1 /*
2   Copyright 2012-2019 David Robillard <http://drobilla.net>
3 
4   Permission to use, copy, modify, and/or distribute this software for any
5   purpose with or without fee is hereby granted, provided that the above
6   copyright notice and this permission notice appear in all copies.
7 
8   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 
17 /**
18    @file pugl_test.c A simple Pugl test that creates a top-level window.
19 */
20 
21 #define GL_SILENCE_DEPRECATION 1
22 
23 #include "test_utils.h"
24 
25 #include "pugl/gl.h"
26 #include "pugl/pugl.h"
27 #include "pugl/pugl_gl.h"
28 
29 #include <math.h>
30 #include <stdbool.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <string.h>
34 
35 static const int borderWidth = 64;
36 
37 typedef struct
38 {
39 	PuglWorld* world;
40 	PuglView*  parent;
41 	PuglView*  child;
42 	bool       continuous;
43 	int        quit;
44 	float      xAngle;
45 	float      yAngle;
46 	float      dist;
47 	double     lastMouseX;
48 	double     lastMouseY;
49 	double     lastDrawTime;
50 	unsigned   framesDrawn;
51 	bool       mouseEntered;
52 	bool       verbose;
53 } PuglTestApp;
54 
55 static PuglRect
getChildFrame(const PuglRect parentFrame)56 getChildFrame(const PuglRect parentFrame)
57 {
58 	const PuglRect childFrame = {
59 		borderWidth,
60 		borderWidth,
61 		parentFrame.width - 2 * borderWidth,
62 		parentFrame.height - 2 * borderWidth
63 	};
64 
65 	return childFrame;
66 }
67 
68 static void
onReshape(PuglView * view,int width,int height)69 onReshape(PuglView* view, int width, int height)
70 {
71 	(void)view;
72 
73 	const float aspect = (float)width / (float)height;
74 
75 	glEnable(GL_DEPTH_TEST);
76 	glDepthFunc(GL_LESS);
77 	glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
78 
79 	glMatrixMode(GL_PROJECTION);
80 	glLoadIdentity();
81 	glViewport(0, 0, width, height);
82 
83 	float projection[16];
84 	perspective(projection, 1.8f, aspect, 1.0f, 100.0f);
85 	glLoadMatrixf(projection);
86 }
87 
88 static void
onDisplay(PuglView * view)89 onDisplay(PuglView* view)
90 {
91 	PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
92 
93 	const double thisTime = puglGetTime(app->world);
94 	if (app->continuous) {
95 		const double dTime = thisTime - app->lastDrawTime;
96 		app->xAngle = fmodf((float)(app->xAngle + dTime * 100.0f), 360.0f);
97 		app->yAngle = fmodf((float)(app->yAngle + dTime * 100.0f), 360.0f);
98 	}
99 
100 	glMatrixMode(GL_MODELVIEW);
101 	glLoadIdentity();
102 	glTranslatef(0.0f, 0.0f, app->dist * -1);
103 	glRotatef(app->xAngle, 0.0f, 1.0f, 0.0f);
104 	glRotatef(app->yAngle, 1.0f, 0.0f, 0.0f);
105 
106 	const float bg = app->mouseEntered ? 0.2f : 0.1f;
107 	glClearColor(bg, bg, bg, 1.0f);
108 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
109 
110 	if (puglHasFocus(app->child)) {
111 		// Draw cube surfaces
112 		glEnableClientState(GL_VERTEX_ARRAY);
113 		glEnableClientState(GL_COLOR_ARRAY);
114 		glVertexPointer(3, GL_FLOAT, 0, cubeStripVertices);
115 		glColorPointer(3, GL_FLOAT, 0, cubeStripVertices);
116 		glDrawArrays(GL_TRIANGLE_STRIP, 0, 14);
117 		glDisableClientState(GL_COLOR_ARRAY);
118 		glDisableClientState(GL_VERTEX_ARRAY);
119 
120 		glColor3f(0.0f, 0.0f, 0.0f);
121 	} else {
122 		glColor3f(1.0f, 1.0f, 1.0f);
123 	}
124 
125 	// Draw cube wireframe
126 	glEnableClientState(GL_VERTEX_ARRAY);
127 	glVertexPointer(3, GL_FLOAT, 0, cubeFrontLineLoop);
128 	glDrawArrays(GL_LINE_LOOP, 0, 4);
129 	glVertexPointer(3, GL_FLOAT, 0, cubeBackLineLoop);
130 	glDrawArrays(GL_LINE_LOOP, 0, 4);
131 	glVertexPointer(3, GL_FLOAT, 0, cubeSideLines);
132 	glDrawArrays(GL_LINES, 0, 8);
133 	glDisableClientState(GL_VERTEX_ARRAY);
134 
135 	app->lastDrawTime = thisTime;
136 	++app->framesDrawn;
137 }
138 
139 static void
swapFocus(PuglTestApp * app)140 swapFocus(PuglTestApp* app)
141 {
142 	if (puglHasFocus(app->parent)) {
143 		puglGrabFocus(app->child);
144 	} else {
145 		puglGrabFocus(app->parent);
146 	}
147 
148 	puglPostRedisplay(app->parent);
149 	puglPostRedisplay(app->child);
150 }
151 
152 static void
onKeyPress(PuglView * view,const PuglEventKey * event,const char * prefix)153 onKeyPress(PuglView* view, const PuglEventKey* event, const char* prefix)
154 {
155 	PuglTestApp* app   = (PuglTestApp*)puglGetHandle(view);
156 	PuglRect     frame = puglGetFrame(view);
157 
158 	if (event->key == '\t') {
159 		swapFocus(app);
160 	} else if (event->key == 'q' || event->key == PUGL_KEY_ESCAPE) {
161 		app->quit = 1;
162 	} else if (event->state & PUGL_MOD_CTRL && event->key == 'c') {
163 		puglSetClipboard(view, NULL, "Pugl test", strlen("Pugl test") + 1);
164 		fprintf(stderr, "%sCopy \"Pugl test\"\n", prefix);
165 	} else if (event->state & PUGL_MOD_CTRL && event->key == 'v') {
166 		const char* type = NULL;
167 		size_t      len  = 0;
168 		const char* text = (const char*)puglGetClipboard(view, &type, &len);
169 		fprintf(stderr, "%sPaste \"%s\"\n", prefix, text);
170 	} else if (event->state & PUGL_MOD_SHIFT) {
171 		if (event->key == PUGL_KEY_UP) {
172 			frame.height += 10;
173 		} else if (event->key == PUGL_KEY_DOWN) {
174 			frame.height -= 10;
175 		} else if (event->key == PUGL_KEY_LEFT) {
176 			frame.width -= 10;
177 		} else if (event->key == PUGL_KEY_RIGHT) {
178 			frame.width += 10;
179 		} else {
180 			return;
181 		}
182 		puglSetFrame(view, frame);
183 	} else {
184 		if (event->key == PUGL_KEY_UP) {
185 			frame.y -= 10;
186 		} else if (event->key == PUGL_KEY_DOWN) {
187 			frame.y += 10;
188 		} else if (event->key == PUGL_KEY_LEFT) {
189 			frame.x -= 10;
190 		} else if (event->key == PUGL_KEY_RIGHT) {
191 			frame.x += 10;
192 		} else {
193 			return;
194 		}
195 		puglSetFrame(view, frame);
196 	}
197 }
198 
199 static PuglStatus
onParentEvent(PuglView * view,const PuglEvent * event)200 onParentEvent(PuglView* view, const PuglEvent* event)
201 {
202 	PuglTestApp*   app         = (PuglTestApp*)puglGetHandle(view);
203 	const PuglRect parentFrame = puglGetFrame(view);
204 
205 	printEvent(event, "Parent: ", app->verbose);
206 
207 	switch (event->type) {
208 	case PUGL_CONFIGURE:
209 		onReshape(view,
210 		          (int)event->configure.width,
211 		          (int)event->configure.height);
212 
213 		puglSetFrame(app->child, getChildFrame(parentFrame));
214 		break;
215 	case PUGL_EXPOSE:
216 		if (puglHasFocus(app->parent)) {
217 			glMatrixMode(GL_MODELVIEW);
218 			glLoadIdentity();
219 			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
220 
221 			glEnableClientState(GL_VERTEX_ARRAY);
222 			glEnableClientState(GL_COLOR_ARRAY);
223 			glVertexPointer(3, GL_FLOAT, 0, cubeStripVertices);
224 			glColorPointer(3, GL_FLOAT, 0, cubeStripVertices);
225 			glDrawArrays(GL_TRIANGLE_STRIP, 0, 14);
226 			glDisableClientState(GL_COLOR_ARRAY);
227 			glDisableClientState(GL_VERTEX_ARRAY);
228 		} else {
229 			glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
230 			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
231 		}
232 		break;
233 	case PUGL_KEY_PRESS:
234 		onKeyPress(view, &event->key, "Parent: ");
235 		break;
236 	case PUGL_MOTION_NOTIFY:
237 		break;
238 	case PUGL_CLOSE:
239 		app->quit = 1;
240 		break;
241 	default:
242 		break;
243 	}
244 
245 	return PUGL_SUCCESS;
246 }
247 
248 static PuglStatus
onEvent(PuglView * view,const PuglEvent * event)249 onEvent(PuglView* view, const PuglEvent* event)
250 {
251 	PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
252 
253 	printEvent(event, "Child:  ", app->verbose);
254 
255 	switch (event->type) {
256 	case PUGL_CONFIGURE:
257 		onReshape(view, (int)event->configure.width, (int)event->configure.height);
258 		break;
259 	case PUGL_EXPOSE:
260 		onDisplay(view);
261 		break;
262 	case PUGL_CLOSE:
263 		app->quit = 1;
264 		break;
265 	case PUGL_KEY_PRESS:
266 		onKeyPress(view, &event->key, "Child:  ");
267 		break;
268 	case PUGL_MOTION_NOTIFY:
269 		app->xAngle = fmodf(app->xAngle - (float)(event->motion.x - app->lastMouseX), 360.0f);
270 		app->yAngle = fmodf(app->yAngle + (float)(event->motion.y - app->lastMouseY), 360.0f);
271 		app->lastMouseX = event->motion.x;
272 		app->lastMouseY = event->motion.y;
273 		puglPostRedisplay(view);
274 		puglPostRedisplay(app->parent);
275 		break;
276 	case PUGL_SCROLL:
277 		app->dist = fmaxf(10.0f, app->dist + (float)event->scroll.dy);
278 		puglPostRedisplay(view);
279 		break;
280 	case PUGL_ENTER_NOTIFY:
281 		app->mouseEntered = true;
282 		break;
283 	case PUGL_LEAVE_NOTIFY:
284 		app->mouseEntered = false;
285 		break;
286 	default:
287 		break;
288 	}
289 
290 	return PUGL_SUCCESS;
291 }
292 
293 int
main(int argc,char ** argv)294 main(int argc, char** argv)
295 {
296 	PuglTestApp app = {0};
297 	app.dist = 10;
298 
299 	const PuglTestOptions opts = puglParseTestOptions(&argc, &argv);
300 	if (opts.help) {
301 		puglPrintTestUsage("pugl_test", "");
302 		return 1;
303 	}
304 
305 	app.continuous = opts.continuous;
306 	app.verbose    = opts.verbose;
307 
308 	app.world  = puglNewWorld();
309 	app.parent = puglNewView(app.world);
310 	app.child  = puglNewView(app.world);
311 
312 	puglSetClassName(app.world, "Pugl Test");
313 
314 	const PuglRect parentFrame = { 0, 0, 512, 512 };
315 	puglSetFrame(app.parent, parentFrame);
316 	puglSetMinSize(app.parent, borderWidth * 3, borderWidth * 3);
317 	puglSetAspectRatio(app.parent, 1, 1, 16, 9);
318 	puglSetBackend(app.parent, puglGlBackend());
319 
320 	puglSetViewHint(app.parent, PUGL_USE_DEBUG_CONTEXT, opts.errorChecking);
321 	puglSetViewHint(app.parent, PUGL_RESIZABLE, opts.resizable);
322 	puglSetViewHint(app.parent, PUGL_SAMPLES, opts.samples);
323 	puglSetViewHint(app.parent, PUGL_DOUBLE_BUFFER, opts.doubleBuffer);
324 	puglSetViewHint(app.parent, PUGL_SWAP_INTERVAL, opts.doubleBuffer);
325 	puglSetViewHint(app.parent, PUGL_IGNORE_KEY_REPEAT, opts.ignoreKeyRepeat);
326 	puglSetHandle(app.parent, &app);
327 	puglSetEventFunc(app.parent, onParentEvent);
328 
329 	PuglStatus st         = PUGL_SUCCESS;
330 	const uint8_t title[] = { 'P', 'u', 'g', 'l', ' ',
331 	                          'P', 'r', 0xC3, 0xBC, 'f', 'u', 'n', 'g', 0 };
332 	if ((st = puglCreateWindow(app.parent, (const char*)title))) {
333 		return logError("Failed to create parent window (%s)\n",
334 		                puglStrerror(st));
335 	}
336 
337 	puglSetFrame(app.child, getChildFrame(parentFrame));
338 	puglSetParentWindow(app.child, puglGetNativeWindow(app.parent));
339 
340 	puglSetViewHint(app.child, PUGL_USE_DEBUG_CONTEXT, opts.errorChecking);
341 	puglSetViewHint(app.child, PUGL_SAMPLES, opts.samples);
342 	puglSetViewHint(app.child, PUGL_DOUBLE_BUFFER, opts.doubleBuffer);
343 	puglSetViewHint(app.child, PUGL_SWAP_INTERVAL, 0);
344 	puglSetBackend(app.child, puglGlBackend());
345 	puglSetViewHint(app.child, PUGL_IGNORE_KEY_REPEAT, opts.ignoreKeyRepeat);
346 	puglSetHandle(app.child, &app);
347 	puglSetEventFunc(app.child, onEvent);
348 
349 	if ((st = puglCreateWindow(app.child, NULL))) {
350 		return logError("Failed to create child window (%s)\n",
351 		                puglStrerror(st));
352 	}
353 
354 	puglShowWindow(app.parent);
355 	puglShowWindow(app.child);
356 
357 	PuglFpsPrinter fpsPrinter         = { puglGetTime(app.world) };
358 	bool           requestedAttention = false;
359 	while (!app.quit) {
360 		const double thisTime = puglGetTime(app.world);
361 
362 		if (app.continuous) {
363 			puglPostRedisplay(app.parent);
364 			puglPostRedisplay(app.child);
365 		} else {
366 			puglPollEvents(app.world, -1);
367 		}
368 
369 		puglDispatchEvents(app.world);
370 
371 		if (!requestedAttention && thisTime > 5.0) {
372 			puglRequestAttention(app.parent);
373 			requestedAttention = true;
374 		}
375 
376 		if (app.continuous) {
377 			puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
378 		}
379 	}
380 
381 	puglFreeView(app.child);
382 	puglFreeView(app.parent);
383 	puglFreeWorld(app.world);
384 
385 	return 0;
386 }
387