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