aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHanspeter Portner <dev@open-music-kontrollers.ch>2020-10-02 13:40:43 +0200
committerHanspeter Portner <dev@open-music-kontrollers.ch>2020-10-02 13:40:43 +0200
commitdb0673f0185cd6b72fd87d3c0fc95ae453c5d1e5 (patch)
tree71ffadad63329096fd47a52aff16225d1197d39c
parent11bac5914fa6b90c1f6cbc6cc37830c586ef14ad (diff)
downloadsynthpod-db0673f0185cd6b72fd87d3c0fc95ae453c5d1e5.tar.xz
Squashed 'subprojects/d2tk/' changes from ea894bf8..9cc8e978
9cc8e978 pty: export HOME and USER env vars. 79505464 pty: do not call putenv, use excvpe instead. 786d4b69 util: fixes for util spawn. d6cc5a54 prototype util header with spawn method (unix only). 0279eda2 Merge commit 'e4017ca6e56aacae7121efe7e3a22b9f424c3f9b' into master e4017ca6 Squashed 'pugl/' changes from 1b1a1c3a..2452869d a656868a pugl: implement clipboard. 02beb92c add support for absolute paths in image widgets. 30b62a49 Merge commit '4e78a74d86e49010467e8c3b2ce3b1b0ace540a8' into pugl-next 4e78a74d Squashed 'pugl/' changes from 8f28d8c9..1b1a1c3a b5fb57ee custom: use hash data hash to discover changes. 98c991e7 nanovg/cairo: simplify custom backends. fe78d43a core: add rect argument to custom callback. 72728639 example: prototype custom widget. ad0a6a2c nanovg: use nvgFontFaceId, should be faster. 54962410 pugl: return correct number of file descriptors. 0a3ebe3f base: do not call poll on mingw. 4b4acefa base: add hooks to get vpty file descriptors. f6eec01f glfw: support enter/tab/backspace/escape keys. 21a7d0a9 glfw: preliminary working version. b7a951c5 glfw: add prototype skeleton. bab3da77 debug: fix wrong usage of D2TK_DEBUG define. 4d4f8b1f frontend: only get file descriptor on X11. 14e3a15c frontend: add d2tk_frontend_get_file_descriptor. 13db56c9 put debug overlay flag into config header. f7a9b6d5 Merge branch 'master' of /media/sdext/omk/d2tk 9b4ad8a7 meson: add missing dependency on glu. git-subtree-dir: subprojects/d2tk git-subtree-split: 9cc8e978137ba4f2ae198c36f52b5f07dd2b073e
-rw-r--r--VERSION2
-rw-r--r--d2tk/base.h5
-rw-r--r--d2tk/config.h.in2
-rw-r--r--d2tk/core.h6
-rw-r--r--d2tk/frontend.h11
-rw-r--r--d2tk/frontend_glfw.h47
-rw-r--r--d2tk/util.h43
-rw-r--r--example/custom_cairo.c25
-rw-r--r--example/custom_nanovg.c28
-rw-r--r--example/d2tk_fbdev.c2
-rw-r--r--example/d2tk_glfw.c118
-rw-r--r--example/d2tk_pugl.c2
-rw-r--r--example/example.c130
-rw-r--r--example/example.h4
-rw-r--r--meson.build66
-rw-r--r--meson_options.txt4
-rw-r--r--pugl/.clang-format4
-rw-r--r--pugl/.clang-tidy9
-rw-r--r--pugl/.editorconfig18
-rw-r--r--pugl/.gitlab-ci.yml28
-rw-r--r--pugl/AUTHORS3
-rw-r--r--pugl/COPYING2
-rw-r--r--pugl/README.md20
-rw-r--r--pugl/doc/layout.xml3
-rw-r--r--pugl/doc/mainpage.md4
-rw-r--r--pugl/doc/reference.doxygen.in28
-rw-r--r--pugl/doc/style.css73
-rw-r--r--pugl/examples/cube_view.h2
-rw-r--r--pugl/examples/demo_utils.h4
-rw-r--r--pugl/examples/pugl_cairo_demo.c12
-rw-r--r--pugl/examples/pugl_cursor_demo.c171
-rw-r--r--pugl/examples/pugl_cxx_demo.cpp146
-rw-r--r--pugl/examples/pugl_embed_demo.c14
-rw-r--r--pugl/examples/pugl_print_events.c5
-rw-r--r--pugl/examples/pugl_shader_demo.c (renamed from pugl/examples/pugl_gl3_demo.c)123
-rw-r--r--pugl/examples/pugl_window_demo.c28
-rw-r--r--pugl/examples/rects.h82
-rw-r--r--pugl/examples/shader_utils.h31
-rw-r--r--pugl/pugl/detail/implementation.c24
-rw-r--r--pugl/pugl/detail/implementation.h29
-rw-r--r--pugl/pugl/detail/mac.h20
-rw-r--r--pugl/pugl/detail/mac.m363
-rw-r--r--pugl/pugl/detail/mac_cairo.m29
-rw-r--r--pugl/pugl/detail/mac_gl.m20
-rw-r--r--pugl/pugl/detail/mac_stub.m13
-rw-r--r--pugl/pugl/detail/stub.h75
-rw-r--r--pugl/pugl/detail/types.h44
-rw-r--r--pugl/pugl/detail/win.c104
-rw-r--r--pugl/pugl/detail/win.h36
-rw-r--r--pugl/pugl/detail/win_cairo.c8
-rw-r--r--pugl/pugl/detail/win_gl.c11
-rw-r--r--pugl/pugl/detail/x11.c297
-rw-r--r--pugl/pugl/detail/x11.h13
-rw-r--r--pugl/pugl/detail/x11_cairo.c5
-rw-r--r--pugl/pugl/detail/x11_gl.c53
-rw-r--r--pugl/pugl/gl.h5
-rw-r--r--pugl/pugl/glu.h5
-rw-r--r--pugl/pugl/pugl.h155
-rw-r--r--pugl/pugl/pugl.hpp674
-rw-r--r--pugl/pugl/pugl.ipp154
-rw-r--r--pugl/pugl/pugl_cairo.h7
-rw-r--r--pugl/pugl/pugl_cairo.hpp50
-rw-r--r--pugl/pugl/pugl_gl.h7
-rw-r--r--pugl/pugl/pugl_gl.hpp60
-rw-r--r--pugl/pugl/pugl_stub.h51
-rw-r--r--pugl/pugl/pugl_stub.hpp (renamed from pugl/pugl/pugl_stub_backend.h)37
-rw-r--r--pugl/shaders/header_330.glsl5
-rw-r--r--pugl/shaders/header_420.glsl5
-rw-r--r--pugl/shaders/rect.frag8
-rw-r--r--pugl/shaders/rect.vert15
-rw-r--r--pugl/test/test_build.c (renamed from pugl/pugl/pugl_cairo_backend.h)21
-rw-r--r--pugl/test/test_build.cpp (renamed from pugl/pugl/pugl_gl_backend.h)25
-rw-r--r--pugl/test/test_redisplay.c27
-rw-r--r--pugl/test/test_show_hide.c11
-rw-r--r--pugl/test/test_timer.c9
-rw-r--r--pugl/test/test_update.c9
-rw-r--r--pugl/test/test_utils.h60
-rw-r--r--pugl/wscript285
-rw-r--r--src/backend_cairo.c119
-rw-r--r--src/backend_nanovg.c118
-rw-r--r--src/base.c70
-rw-r--r--src/base_custom.c6
-rw-r--r--src/base_internal.h2
-rw-r--r--src/base_pty.c51
-rw-r--r--src/base_vkb.c2
-rw-r--r--src/core.c20
-rw-r--r--src/core_internal.h2
-rw-r--r--src/frontend_fbdev.c27
-rw-r--r--src/frontend_glfw.c436
-rw-r--r--src/frontend_pugl.c40
-rw-r--r--src/util_spawn.c125
-rw-r--r--test/base.c12
-rw-r--r--test/core.c20
93 files changed, 4264 insertions, 920 deletions
diff --git a/VERSION b/VERSION
index 735baec8..94728b9f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.1089
+0.1.1143
diff --git a/d2tk/base.h b/d2tk/base.h
index 0926b6d1..b7fb9c8c 100644
--- a/d2tk/base.h
+++ b/d2tk/base.h
@@ -447,7 +447,7 @@ d2tk_base_bitmap(d2tk_base_t *base, uint32_t w, uint32_t h, uint32_t stride,
d2tk_align_t align);
D2TK_API void
-d2tk_base_custom(d2tk_base_t *base, uint32_t size, const void *data,
+d2tk_base_custom(d2tk_base_t *base, uint64_t dhash, const void *data,
const d2tk_rect_t *rect, d2tk_core_custom_t custom);
D2TK_API d2tk_state_t
@@ -713,6 +713,9 @@ d2tk_base_post(d2tk_base_t *base);
D2TK_API void
d2tk_base_probe(d2tk_base_t *base);
+D2TK_API int
+d2tk_base_get_file_descriptors(d2tk_base_t *base, int *fds, int numfds);
+
D2TK_API bool
d2tk_base_set_again(d2tk_base_t *base);
diff --git a/d2tk/config.h.in b/d2tk/config.h.in
index c1d746ec..01ea6b4d 100644
--- a/d2tk/config.h.in
+++ b/d2tk/config.h.in
@@ -1,6 +1,8 @@
#define D2TK_PTY @D2TK_PTY@
+#define D2TK_SPAWN @D2TK_SPAWN@
#define D2TK_EVDEV @D2TK_EVDEV@
#define D2TK_INPUT_1_15 @D2TK_INPUT_1_15@
#define D2TK_FONTCONFIG @D2TK_FONTCONFIG@
#define D2TK_VFORK @D2TK_VFORK@
#define D2TK_CLONE @D2TK_CLONE@
+#define D2TK_DEBUG @D2TK_DEBUG@
diff --git a/d2tk/core.h b/d2tk/core.h
index 0b8049b0..97567f5a 100644
--- a/d2tk/core.h
+++ b/d2tk/core.h
@@ -20,6 +20,7 @@
#include <stdint.h>
#include <stdbool.h>
+#include <stdlib.h>
#include "config.h"
#include <d2tk/d2tk.h>
@@ -34,7 +35,8 @@ typedef struct _d2tk_widget_t d2tk_widget_t;
typedef struct _d2tk_point_t d2tk_point_t;
typedef struct _d2tk_core_t d2tk_core_t;
typedef struct _d2tk_core_driver_t d2tk_core_driver_t;
-typedef void (*d2tk_core_custom_t)(void *ctx, uint32_t size, const void *data);
+typedef void (*d2tk_core_custom_t)(void *ctx, const d2tk_rect_t *rect,
+ const void *data);
typedef enum _d2tk_align_t {
D2TK_ALIGN_NONE = 0,
@@ -180,7 +182,7 @@ d2tk_core_bitmap(d2tk_core_t *core, const d2tk_rect_t *rect, uint32_t w,
d2tk_align_t align);
D2TK_API void
-d2tk_core_custom(d2tk_core_t *core, const d2tk_rect_t *rect, uint32_t size,
+d2tk_core_custom(d2tk_core_t *core, const d2tk_rect_t *rect, uint64_t dhash,
const void *data, d2tk_core_custom_t custom);
D2TK_API void
diff --git a/d2tk/frontend.h b/d2tk/frontend.h
index eaa2b4a5..716bad5c 100644
--- a/d2tk/frontend.h
+++ b/d2tk/frontend.h
@@ -38,6 +38,9 @@ d2tk_frontend_step(d2tk_frontend_t *dpugl);
D2TK_API int
d2tk_frontend_poll(d2tk_frontend_t *dpugl, double timeout);
+D2TK_API int
+d2tk_frontend_get_file_descriptors(d2tk_frontend_t *dpugl, int *fds, int numfds);
+
D2TK_API void
d2tk_frontend_run(d2tk_frontend_t *dpugl, const sig_atomic_t *done);
@@ -53,6 +56,14 @@ d2tk_frontend_get_size(d2tk_frontend_t *dpugl, d2tk_coord_t *w, d2tk_coord_t *h)
D2TK_API d2tk_base_t *
d2tk_frontend_get_base(d2tk_frontend_t *dpugl);
+D2TK_API int
+d2tk_frontend_set_clipboard(d2tk_frontend_t *dpugl, const char *type,
+ const void *buf, size_t buf_len);
+
+D2TK_API const void *
+d2tk_frontend_get_clipboard(d2tk_frontend_t *dpugl, const char **type,
+ size_t *buf_len);
+
D2TK_API float
d2tk_frontend_get_scale();
diff --git a/d2tk/frontend_glfw.h b/d2tk/frontend_glfw.h
new file mode 100644
index 00000000..b7b934fb
--- /dev/null
+++ b/d2tk/frontend_glfw.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _D2TK_FRONTEND_GLFW_H
+#define _D2TK_FRONTEND_GLFW_H
+
+#include <signal.h>
+
+#include <d2tk/base.h>
+#include <d2tk/frontend.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _d2tk_glfw_config_t d2tk_glfw_config_t;
+
+struct _d2tk_glfw_config_t {
+ d2tk_coord_t w;
+ d2tk_coord_t h;
+ const char *bundle_path;
+ d2tk_frontend_expose_t expose;
+ void *data;
+};
+
+D2TK_API d2tk_frontend_t *
+d2tk_glfw_new(const d2tk_glfw_config_t *config);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _D2TK_FRONTEND_GLFW_H
diff --git a/d2tk/util.h b/d2tk/util.h
new file mode 100644
index 00000000..1c3b27d8
--- /dev/null
+++ b/d2tk/util.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _D2TK_UTIL_H
+#define _D2TK_UTIL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "config.h"
+#include <d2tk/d2tk.h>
+
+#if D2TK_SPAWN
+D2TK_API int
+d2tk_util_spawn(char **argv);
+
+D2TK_API int
+d2tk_util_kill(int *kid);
+
+D2TK_API int
+d2tk_util_wait(int *kid);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _D2TK_UTIL_H
diff --git a/example/custom_cairo.c b/example/custom_cairo.c
new file mode 100644
index 00000000..56904ed5
--- /dev/null
+++ b/example/custom_cairo.c
@@ -0,0 +1,25 @@
+#include <stdint.h>
+
+#include <d2tk/core.h>
+
+#include <cairo.h>
+
+static void
+_draw_custom(void *_ctx, const d2tk_rect_t *rect, const void *data)
+{
+ cairo_t *ctx = _ctx;
+ (void)data;
+
+ d2tk_rect_t bnd = *rect;
+ bnd.x += bnd.w/4;
+ bnd.y += bnd.h/4;
+ bnd.w /= 2;
+ bnd.h /= 2;
+
+ cairo_new_sub_path(ctx);
+ cairo_rectangle(ctx, bnd.x, bnd.y, bnd.w, bnd.h);
+ cairo_set_source_rgba(ctx, 1.f, 1.f, 1.f, 0.5f);
+ cairo_fill(ctx);
+}
+
+d2tk_core_custom_t draw_custom = _draw_custom;
diff --git a/example/custom_nanovg.c b/example/custom_nanovg.c
new file mode 100644
index 00000000..7bd2db3b
--- /dev/null
+++ b/example/custom_nanovg.c
@@ -0,0 +1,28 @@
+#include <stdint.h>
+#include <math.h>
+
+#include <d2tk/core.h>
+
+#include <nanovg.h>
+
+static void
+_draw_custom(void *_ctx, const d2tk_rect_t *rect, const void *data)
+{
+ NVGcontext *ctx = _ctx;
+ (void)data;
+
+ d2tk_rect_t bnd = *rect;
+ bnd.x += bnd.w/4;
+ bnd.y += bnd.h/4;
+ bnd.w /= 2;
+ bnd.h /= 2;
+
+ const NVGcolor col = nvgRGBA(0xff, 0xff, 0xff, 0x7f);
+
+ nvgBeginPath(ctx);
+ nvgRect(ctx, bnd.x, bnd.y, bnd.w, bnd.h);
+ nvgFillColor(ctx, col);
+ nvgFill(ctx);
+}
+
+d2tk_core_custom_t draw_custom = _draw_custom;
diff --git a/example/d2tk_fbdev.c b/example/d2tk_fbdev.c
index f44e47d5..47baed87 100644
--- a/example/d2tk_fbdev.c
+++ b/example/d2tk_fbdev.c
@@ -51,7 +51,7 @@ _expose(void *data, d2tk_coord_t w, d2tk_coord_t h)
d2tk_frontend_t *fbdev = app->fbdev;
d2tk_base_t *base = d2tk_frontend_get_base(fbdev);
- d2tk_example_run(base, w, h);
+ d2tk_example_run(fbdev, base, w, h);
if(app->show_cursor)
{
diff --git a/example/d2tk_glfw.c b/example/d2tk_glfw.c
new file mode 100644
index 00000000..31ce9056
--- /dev/null
+++ b/example/d2tk_glfw.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <signal.h>
+
+#include <d2tk/frontend_glfw.h>
+#include "example/example.h"
+
+typedef struct _app_t app_t;
+
+struct _app_t {
+ d2tk_frontend_t *dglfw;
+};
+
+static sig_atomic_t done = false;
+
+static void
+_sig(int signum __attribute__((unused)))
+{
+ done = true;
+}
+
+static inline int
+_expose(void *data, d2tk_coord_t w, d2tk_coord_t h)
+{
+ app_t *app = data;
+ d2tk_frontend_t *dglfw = app->dglfw;
+ d2tk_base_t *base = d2tk_frontend_get_base(dglfw);
+
+ d2tk_example_run(dglfw, base, w, h);
+
+ return EXIT_SUCCESS;
+}
+
+int
+main(int argc, char **argv)
+{
+ static app_t app;
+
+ const float scale = d2tk_frontend_get_scale();
+ d2tk_coord_t w = scale * 1280;
+ d2tk_coord_t h = scale * 720;
+
+ int c;
+ while( (c = getopt(argc, argv, "w:h:")) != -1)
+ {
+ switch(c)
+ {
+ case 'w':
+ {
+ w = atoi(optarg);
+ } break;
+ case 'h':
+ {
+ h = atoi(optarg);
+ } break;
+
+ default:
+ {
+ fprintf(stderr, "Usage: %s\n"
+ " -w width\n"
+ " -h height\n\n",
+ argv[0]);
+ } return EXIT_FAILURE;
+ }
+ }
+
+ const d2tk_glfw_config_t config = {
+ .w = w,
+ .h = h,
+ .bundle_path = "./",
+ .expose = _expose,
+ .data = &app
+ };
+
+ signal(SIGINT, _sig);
+ signal(SIGTERM, _sig);
+#if !defined(_WIN32)
+ signal(SIGQUIT, _sig);
+ signal(SIGKILL, _sig);
+#endif
+
+ app.dglfw = d2tk_glfw_new(&config);
+ if(app.dglfw)
+ {
+ d2tk_example_init();
+
+ d2tk_frontend_run(app.dglfw, &done);
+
+ d2tk_frontend_free(app.dglfw);
+
+ d2tk_example_deinit();
+
+ return EXIT_SUCCESS;
+ }
+
+ return EXIT_FAILURE;
+}
diff --git a/example/d2tk_pugl.c b/example/d2tk_pugl.c
index 70ab9673..41325476 100644
--- a/example/d2tk_pugl.c
+++ b/example/d2tk_pugl.c
@@ -45,7 +45,7 @@ _expose(void *data, d2tk_coord_t w, d2tk_coord_t h)
d2tk_frontend_t *dpugl = app->dpugl;
d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
- d2tk_example_run(base, w, h);
+ d2tk_example_run(dpugl, base, w, h);
return EXIT_SUCCESS;
}
diff --git a/example/example.c b/example/example.c
index a0294696..4b6a0c47 100644
--- a/example/example.c
+++ b/example/example.c
@@ -23,9 +23,10 @@
#include <dirent.h>
#include <string.h>
-#include <d2tk/frontend_pugl.h>
#include "example/example.h"
+#include <d2tk/util.h>
+
typedef union _val_t val_t;
union _val_t {
@@ -50,9 +51,14 @@ typedef enum _bar_t {
BAR_METER,
BAR_FRAME,
BAR_UTF8,
+ BAR_CUSTOM,
+ BAR_COPYPASTE,
#if D2TK_PTY
BAR_PTY,
#endif
+#if D2TK_SPAWN
+ BAR_SPAWN,
+#endif
#if !defined(_WIN32) && !defined(__APPLE__)
BAR_BROWSER,
#endif
@@ -76,9 +82,14 @@ static const char *bar_lbl [BAR_MAX] = {
[BAR_FLOWMATRIX] = "Flowmatrix",
[BAR_METER] = "Meter",
[BAR_FRAME] = "Frame",
+ [BAR_CUSTOM] = "Custom",
+ [BAR_COPYPASTE] = "Copy&Paste",
[BAR_UTF8] = "UTF-8",
#if D2TK_PTY
- [BAR_PTY] = "PTY",
+ [BAR_PTY] = "PTY",
+#endif
+#if D2TK_SPAWN
+ [BAR_SPAWN] = "SPAWN",
#endif
#if !defined(_WIN32) && !defined(__APPLE__)
[BAR_BROWSER] = "Browser",
@@ -919,6 +930,74 @@ _render_c_utf8(d2tk_base_t *base, const d2tk_rect_t *rect)
#undef N
}
+extern d2tk_core_custom_t draw_custom;
+
+static inline void
+_render_c_custom(d2tk_base_t *base, const d2tk_rect_t *rect)
+{
+ static uint32_t dummy [1] = { 0 };
+
+ d2tk_base_custom(base, sizeof(dummy), dummy, rect, draw_custom);
+}
+
+static inline void
+_render_c_copypaste_set(d2tk_frontend_t *frontend, d2tk_base_t *base,
+ const d2tk_rect_t *rect)
+{
+ if(d2tk_base_button_is_changed(base, D2TK_ID, rect))
+ {
+ static unsigned int count = 0;
+ static const char *type = "text/plain";
+ char buf [32];
+
+ const size_t buf_len = snprintf(buf, sizeof(buf), "d2tk #%u", count++);
+
+ d2tk_frontend_set_clipboard(frontend, type, buf, buf_len);
+ }
+}
+
+static inline void
+_render_c_copypaste_get(d2tk_frontend_t *frontend, d2tk_base_t *base,
+ const d2tk_rect_t *rect)
+{
+ if(d2tk_base_button_is_changed(base, D2TK_ID, rect))
+ {
+ const char *type = NULL;
+ size_t lbl_len = 0;
+ const char *lbl = d2tk_frontend_get_clipboard(frontend, &type, &lbl_len);
+
+ if(lbl && lbl_len && type)
+ {
+ fprintf(stderr, "clip: (%zu) %s (%s)\n", lbl_len, lbl, type);
+ }
+ }
+}
+
+static inline void
+_render_c_copypaste(d2tk_frontend_t *frontend, d2tk_base_t *base,
+ const d2tk_rect_t *rect)
+{
+ static d2tk_coord_t hfrac [2] = { 1, 1 };
+
+ D2TK_BASE_LAYOUT(rect, 2, hfrac, D2TK_FLAG_LAYOUT_X_REL, hlay)
+ {
+ const d2tk_rect_t *hrect = d2tk_layout_get_rect(hlay);
+ const d2tk_coord_t x = d2tk_layout_get_index(hlay);
+
+ switch(x)
+ {
+ case 0:
+ _render_c_copypaste_set(frontend, base, hrect);
+ {
+ } break;
+ case 1:
+ {
+ _render_c_copypaste_get(frontend, base, hrect);
+ } break;
+ }
+ }
+}
+
#if D2TK_PTY
static inline void
_render_c_pty(d2tk_base_t *base, const d2tk_rect_t *rect)
@@ -981,6 +1060,36 @@ _render_c_pty(d2tk_base_t *base, const d2tk_rect_t *rect)
}
#endif
+#if D2TK_SPAWN
+static inline void
+_render_c_spawn(d2tk_base_t *base, const d2tk_rect_t *rect)
+{
+ static char lbl [] = "spawn";
+ static int kid = -1;
+
+ if(d2tk_base_button_label_is_changed(
+ base, D2TK_ID, sizeof(lbl), lbl, D2TK_ALIGN_CENTERED, rect))
+ {
+ char *argv [] = {
+ "xdg-open",
+ "https://git.open-music-kontrollers.ch/lad/d2tk/about",
+ NULL
+ };
+ kid = d2tk_util_spawn(argv);
+
+ if(kid <= 0)
+ {
+ fprintf(stderr, "failed to spawn kid\n");
+ }
+ }
+
+ if(d2tk_util_wait(&kid))
+ {
+ fprintf(stderr, "kid still running\n");
+ }
+}
+#endif
+
#if !defined(_WIN32) && !defined(__APPLE__)
static int
strcasenumcmp(const char *s1, const char *s2)
@@ -1249,7 +1358,8 @@ d2tk_example_deinit(void)
}
D2TK_API void
-d2tk_example_run(d2tk_base_t *base, d2tk_coord_t w, d2tk_coord_t h)
+d2tk_example_run(d2tk_frontend_t *frontend, d2tk_base_t *base,
+ d2tk_coord_t w, d2tk_coord_t h)
{
static d2tk_coord_t vfrac [2] = { 1, 19 };
@@ -1330,12 +1440,26 @@ d2tk_example_run(d2tk_base_t *base, d2tk_coord_t w, d2tk_coord_t h)
{
_render_c_utf8(base, vrect);
} break;
+ case BAR_CUSTOM:
+ {
+ _render_c_custom(base, vrect);
+ } break;
+ case BAR_COPYPASTE:
+ {
+ _render_c_copypaste(frontend, base, vrect);
+ } break;
#if D2TK_PTY
case BAR_PTY:
{
_render_c_pty(base, vrect);
} break;
#endif
+#if D2TK_SPAWN
+ case BAR_SPAWN:
+ {
+ _render_c_spawn(base, vrect);
+ } break;
+#endif
#if !defined(_WIN32) && !defined(__APPLE__)
case BAR_BROWSER:
{
diff --git a/example/example.h b/example/example.h
index 84f430c2..bbb013b0 100644
--- a/example/example.h
+++ b/example/example.h
@@ -19,6 +19,7 @@
#define _D2TK_EXAMPLE_H
#include <d2tk/base.h>
+#include <d2tk/frontend.h>
#ifdef __cplusplus
extern "C" {
@@ -31,7 +32,8 @@ D2TK_API void
d2tk_example_deinit();
D2TK_API void
-d2tk_example_run(d2tk_base_t *base, d2tk_coord_t w, d2tk_coord_t h);
+d2tk_example_run(d2tk_frontend_t *frontend, d2tk_base_t *base,
+ d2tk_coord_t w, d2tk_coord_t h);
#ifdef __cplusplus
}
diff --git a/meson.build b/meson.build
index 33e49c56..316edb2e 100644
--- a/meson.build
+++ b/meson.build
@@ -14,7 +14,8 @@ build_tests = get_option('build-tests')
use_backend_cairo = get_option('use-backend-cairo')
use_backend_nanovg = get_option('use-backend-nanovg')
use_frontend_fbdev = get_option('use-frontend-fbdev')
-use_frontend_pugl= get_option('use-frontend-pugl')
+use_frontend_pugl = get_option('use-frontend-pugl')
+use_frontend_glfw = get_option('use-frontend-glfw')
use_vterm = get_option('use-vterm')
use_evdev = get_option('use-evdev')
@@ -70,6 +71,10 @@ evdev_dep = dependency('libevdev',
required : use_frontend_fbdev)
# dependencies for backend_nanovg
+glu_dep = dependency('glu',
+ version : '>=9.0.0',
+ static : static_link,
+ required : use_backend_nanovg)
glew_dep = dependency('glew',
version : '>=2.1.0',
static : static_link,
@@ -81,6 +86,12 @@ if use_backend_nanovg.enabled() and not glew_dep.found()
include_directories : include_directories('glew-2.1.0'),
sources : join_paths('glew-2.1.0', 'glew.c'))
endif
+if use_frontend_glfw.enabled()
+ glfw_dep = dependency('glfw3',
+ version : '>=3.3.0',
+ static : static_link,
+ required : use_backend_nanovg)
+endif
# optional dependencies
util_dep = cc.find_library('util',
@@ -118,7 +129,9 @@ conf_data.set('MICRO_VERSION', version[2])
add_project_arguments('-D_GNU_SOURCE', language : 'c')
if build_debug_overlay
- add_project_arguments('-DD2TK_DEBUG', language : 'c')
+ conf_data.set('D2TK_DEBUG', 1)
+else
+ conf_data.set('D2TK_DEBUG', 0)
endif
lib_srcs = [
@@ -186,10 +199,27 @@ else
conf_data.set('D2TK_CLONE', 0)
endif
+if host_machine.system() == 'windows'
+ conf_data.set('D2TK_SPAWN', 0)
+elif host_machine.system() == 'darwin'
+ conf_data.set('D2TK_SPAWN', 0)
+else
+ conf_data.set('D2TK_SPAWN', 1)
+ lib_srcs += join_paths('src', 'util_spawn.c')
+endif
+
example_srcs = [
join_paths('example', 'example.c')
]
+example_cairo_srcs = [
+ join_paths('example', 'custom_cairo.c')
+]
+
+example_nanovg_srcs = [
+ join_paths('example', 'custom_nanovg.c')
+]
+
example_pugl_srcs = [
join_paths('example', 'd2tk_pugl.c')
]
@@ -198,6 +228,10 @@ example_fbdev_srcs = [
join_paths('example', 'd2tk_fbdev.c')
]
+example_glfw_srcs = [
+ join_paths('example', 'd2tk_glfw.c')
+]
+
pugl_srcs = [
join_paths('src', 'frontend_pugl.c'),
join_paths('pugl', 'pugl', 'detail', 'implementation.c')
@@ -220,6 +254,10 @@ fbdev_srcs = [
join_paths('src', 'frontend_fbdev.c')
]
+glfw_srcs = [
+ join_paths('src', 'frontend_glfw.c')
+]
+
test_core_srcs = [
join_paths('test', 'core.c'),
join_paths('test', 'mock.c')
@@ -266,7 +304,7 @@ if use_backend_cairo.enabled()
sources : [lib_srcs, cairo_srcs, pugl_srcs, pugl_cairo_srcs])
if build_examples
- executable('d2tk.cairo', [example_srcs, example_pugl_srcs],
+ executable('d2tk.cairo', [example_srcs, example_pugl_srcs, example_cairo_srcs],
c_args : c_args,
include_directories : inc_dir,
dependencies: d2tk_cairo,
@@ -282,7 +320,7 @@ if use_backend_cairo.enabled()
sources : [lib_srcs, cairo_srcs, fbdev_srcs])
if build_examples
- executable('d2tk.fbdev', [example_srcs, example_fbdev_srcs],
+ executable('d2tk.fbdev', [example_srcs, example_fbdev_srcs, example_cairo_srcs],
c_args : c_args,
include_directories : inc_dir,
dependencies: d2tk_fbdev,
@@ -295,18 +333,34 @@ if use_backend_nanovg.enabled()
if use_frontend_pugl.enabled()
d2tk_nanovg = declare_dependency(
include_directories : inc_dir,
- dependencies : [deps, glew_dep],
+ dependencies : [deps, glu_dep, glew_dep],
link_args : links,
sources : [lib_srcs, nanovg_srcs, pugl_srcs, pugl_gl_srcs])
if build_examples
- executable('d2tk.nanovg', [example_srcs, example_pugl_srcs],
+ executable('d2tk.nanovg', [example_srcs, example_pugl_srcs, example_nanovg_srcs],
c_args : c_args,
include_directories : inc_dir,
dependencies: d2tk_nanovg,
install : false)
endif
endif
+
+ if use_frontend_glfw.enabled()
+ d2tk_glfw = declare_dependency(
+ include_directories : inc_dir,
+ dependencies : [deps, glfw_dep, glew_dep],
+ link_args : links,
+ sources : [lib_srcs, nanovg_srcs, glfw_srcs])
+
+ if build_examples
+ executable('d2tk.glfw', [example_srcs, example_glfw_srcs, example_nanovg_srcs],
+ c_args : c_args,
+ include_directories : inc_dir,
+ dependencies: d2tk_glfw,
+ install : false)
+ endif
+ endif
endif
config_h = configure_file(
diff --git a/meson_options.txt b/meson_options.txt
index 63965032..c3247149 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -28,6 +28,10 @@ option('use-frontend-pugl',
type : 'feature',
value : 'disabled',
yield : true)
+option('use-frontend-glfw',
+ type : 'feature',
+ value : 'disabled',
+ yield : true)
option('use-vterm',
type : 'feature',
diff --git a/pugl/.clang-format b/pugl/.clang-format
index b7886761..043fd1fc 100644
--- a/pugl/.clang-format
+++ b/pugl/.clang-format
@@ -59,7 +59,7 @@ Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
-FixNamespaceComments: false
+FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
@@ -104,7 +104,7 @@ SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
-SpaceBeforeCpp11BracedList: true
+SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
diff --git a/pugl/.clang-tidy b/pugl/.clang-tidy
index 055d63b3..c8039e17 100644
--- a/pugl/.clang-tidy
+++ b/pugl/.clang-tidy
@@ -1,5 +1,6 @@
Checks: >
*,
+ -*avoid-c-arrays,
-*magic-numbers,
-*uppercase-literal-suffix,
-android-cloexec-fopen,
@@ -7,10 +8,14 @@ Checks: >
-cert-flp30-c,
-clang-analyzer-alpha.*,
-clang-analyzer-security.FloatLoopCounter,
+ -google-runtime-references,
-hicpp-multiway-paths-covered,
-hicpp-signed-bitwise,
-llvm-header-guard,
- -readability-else-after-return
+ -modernize-use-trailing-return-type,
+ -readability-else-after-return,
+ -readability-implicit-bool-conversion,
+ -readability-named-parameter,
WarningsAsErrors: ''
-HeaderFilterRegex: 'pugl/.*|test/.*'
+HeaderFilterRegex: 'pugl/.*|test/.*|examples/.*'
FormatStyle: file
diff --git a/pugl/.editorconfig b/pugl/.editorconfig
new file mode 100644
index 00000000..5213b6bf
--- /dev/null
+++ b/pugl/.editorconfig
@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{c,h,m,cpp,hpp,mm,glsl,frag,vert}]
+indent_style = tab
+
+[wscript]
+indent_style = space
+indent_size = 4
+
+[doc/**.{html,xml,css}]
+indent_style = space
+indent_size = 2
diff --git a/pugl/.gitlab-ci.yml b/pugl/.gitlab-ci.yml
index 29dc8bea..42d14cd7 100644
--- a/pugl/.gitlab-ci.yml
+++ b/pugl/.gitlab-ci.yml
@@ -11,7 +11,7 @@ variables:
arm32_dbg:
<<: *build_definition
image: lv2plugin/debian-arm32
- script: python ./waf configure build -dsT --no-coverage
+ script: python ./waf configure build -dST --werror --no-coverage
variables:
CC: "arm-linux-gnueabihf-gcc"
CXX: "arm-linux-gnueabihf-g++"
@@ -19,7 +19,7 @@ arm32_dbg:
arm32_rel:
<<: *build_definition
image: lv2plugin/debian-arm32
- script: python ./waf configure build -sT --no-coverage
+ script: python ./waf configure build -ST --werror --no-coverage
variables:
CC: "arm-linux-gnueabihf-gcc"
CXX: "arm-linux-gnueabihf-g++"
@@ -27,7 +27,7 @@ arm32_rel:
arm64_dbg:
<<: *build_definition
image: lv2plugin/debian-arm64
- script: python ./waf configure build -dsT --no-coverage
+ script: python ./waf configure build -dST --werror --no-coverage
variables:
CC: "aarch64-linux-gnu-gcc"
CXX: "aarch64-linux-gnu-g++"
@@ -35,7 +35,7 @@ arm64_dbg:
arm64_rel:
<<: *build_definition
image: lv2plugin/debian-arm64
- script: python ./waf configure build -sT --no-coverage
+ script: python ./waf configure build -ST --werror --no-coverage
variables:
CC: "aarch64-linux-gnu-gcc"
CXX: "aarch64-linux-gnu-g++"
@@ -43,7 +43,7 @@ arm64_rel:
x64_dbg:
<<: *build_definition
image: lv2plugin/debian-x64
- script: python ./waf configure build -dsT --no-coverage --docs
+ script: python ./waf configure build -dST --werror --no-coverage --docs
artifacts:
paths:
- build/doc
@@ -51,12 +51,12 @@ x64_dbg:
x64_rel:
<<: *build_definition
image: lv2plugin/debian-x64
- script: python ./waf configure build -sT --no-coverage
+ script: python ./waf configure build -ST --werror --no-coverage
mingw32_dbg:
<<: *build_definition
image: lv2plugin/debian-mingw32
- script: python ./waf configure build -dsT --no-coverage --target=win32
+ script: python ./waf configure build -dST --werror --no-coverage --target=win32
variables:
CC: "i686-w64-mingw32-gcc"
CXX: "i686-w64-mingw32-g++"
@@ -64,7 +64,7 @@ mingw32_dbg:
mingw32_rel:
<<: *build_definition
image: lv2plugin/debian-mingw32
- script: python ./waf configure build -sT --no-coverage --target=win32
+ script: python ./waf configure build -ST --werror --no-coverage --target=win32
variables:
CC: "i686-w64-mingw32-gcc"
CXX: "i686-w64-mingw32-g++"
@@ -72,7 +72,7 @@ mingw32_rel:
mingw64_dbg:
<<: *build_definition
image: lv2plugin/debian-mingw64
- script: python ./waf configure build -dsT --no-coverage --target=win32
+ script: python ./waf configure build -dST --werror --no-coverage --target=win32
variables:
CC: "x86_64-w64-mingw32-gcc"
CXX: "x86_64-w64-mingw32-g++"
@@ -80,30 +80,30 @@ mingw64_dbg:
mingw64_rel:
<<: *build_definition
image: lv2plugin/debian-mingw64
- script: python ./waf configure build -sT --no-coverage --target=win32
+ script: python ./waf configure build -ST --werror --no-coverage --target=win32
variables:
CC: "x86_64-w64-mingw32-gcc"
CXX: "x86_64-w64-mingw32-g++"
mac_dbg:
<<: *build_definition
- script: python ./waf configure build -dsT --no-coverage
+ script: python ./waf configure build -dST --werror --no-coverage
tags: [macos]
mac_rel:
<<: *build_definition
- script: python ./waf configure build -sT --no-coverage
+ script: python ./waf configure build -ST --werror --no-coverage
tags: [macos]
win_dbg:
<<: *build_definition
script:
- - python ./waf configure build -dT --no-coverage
+ - python ./waf configure build -dST --werror --no-coverage
tags: [windows,msvc,python]
win_rel:
<<: *build_definition
- script: python ./waf configure build -T --no-coverage
+ script: python ./waf configure build -ST --werror --no-coverage
tags: [windows,msvc,python]
pages:
diff --git a/pugl/AUTHORS b/pugl/AUTHORS
index 1470491c..18aadafa 100644
--- a/pugl/AUTHORS
+++ b/pugl/AUTHORS
@@ -8,4 +8,5 @@ Hanspeter Portner <dev@open-music-kontrollers.ch>
Stefan Westerfeld <stefan@space.twc.de>
Jordan Halase <jordan@halase.me>
Oliver Schmidt <oliver@luced.de>
-Zoë Sparks <zoe@milky.flowers> \ No newline at end of file
+Zoë Sparks <zoe@milky.flowers>
+Jean Pierre Cimalando <jp-dev@inbox.ru>
diff --git a/pugl/COPYING b/pugl/COPYING
index 4a287b92..63e6829e 100644
--- a/pugl/COPYING
+++ b/pugl/COPYING
@@ -1,4 +1,4 @@
-Copyright 2011-2020 David Robillard <http://drobilla.net>
+Copyright 2011-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
diff --git a/pugl/README.md b/pugl/README.md
index ae9c420e..e8794bc2 100644
--- a/pugl/README.md
+++ b/pugl/README.md
@@ -70,8 +70,16 @@ developers can build portable plugin binaries.
Testing
-------
-There are a few unit tests included which can be run with `python waf test
---gui-tests`, but unfortunately manual testing is still required.
+There are a few unit tests included, but unfortunately manual testing is still
+required. The tests and example programs will be built if you pass the
+`--test` option when configuring:
+
+ ./waf configure --test
+
+Then, after building, the unit tests can be run:
+
+ ./waf
+ ./waf test --gui-tests
Several example programs are included that serve as both manual tests and
demonstrations:
@@ -84,9 +92,9 @@ demonstrations:
* `pugl_window_demo` demonstrates multiple top-level windows.
- * `pugl_gl3_demo` demonstrates using more modern OpenGL where dynamic loading
- and shaders are required. It can also be used to test performance by
- passing the number of rectangles to draw on the command line.
+ * `pugl_shader_demo` demonstrates using more modern OpenGL (version 3 or 4)
+ where dynamic loading and shaders are required. It can also be used to test
+ performance by passing the number of rectangles to draw on the command line.
* `pugl_cairo_demo` demonstrates using Cairo on top of the native windowing
system (without OpenGL), and partial redrawing.
@@ -94,6 +102,8 @@ demonstrations:
* `pugl_print_events` is a utility that prints all received events to the
console in a human readable format.
+ * `pugl_cxx_demo` is a simple cube demo that uses the C++ API.
+
All example programs support several command line options to control various
behaviours, see the output of `--help` for details. Please file an issue if
any of these programs do not work as expected on your system.
diff --git a/pugl/doc/layout.xml b/pugl/doc/layout.xml
index 18893027..2995c0ed 100644
--- a/pugl/doc/layout.xml
+++ b/pugl/doc/layout.xml
@@ -25,6 +25,7 @@
<!-- Layout definition for a class page -->
<class>
<briefdescription visible="yes"/>
+ <detaileddescription title=""/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<inheritancegraph visible="$CLASS_GRAPH"/>
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
@@ -107,6 +108,7 @@
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
+ <detaileddescription title=""/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includegraph visible="$INCLUDE_GRAPH"/>
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
@@ -122,7 +124,6 @@
<variables title=""/>
<membergroups visible="yes"/>
</memberdecl>
- <detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<defines title=""/>
diff --git a/pugl/doc/mainpage.md b/pugl/doc/mainpage.md
index aa6f925a..c04bf9e7 100644
--- a/pugl/doc/mainpage.md
+++ b/pugl/doc/mainpage.md
@@ -1,6 +1,6 @@
This is the API documentation for Pugl.
-This documentation is based around the [C API](@ref pugl_api),
-there is also a [C++ API](@ref pugl) in the `pugl` namespace.
+This page refers to the [C API](@ref pugl_c),
+there is also a [C++ API](@ref pugl_cxx) in the `pugl` namespace.
The Pugl API revolves around two main objects:
the [World](@ref world) and the [View](@ref view).
diff --git a/pugl/doc/reference.doxygen.in b/pugl/doc/reference.doxygen.in
index 1357fe4a..4e91ca20 100644
--- a/pugl/doc/reference.doxygen.in
+++ b/pugl/doc/reference.doxygen.in
@@ -243,12 +243,6 @@ TAB_SIZE = 4
ALIASES =
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST =
-
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@@ -447,7 +441,7 @@ LOOKUP_CACHE_SIZE = 0
# normally produced when WARNINGS is set to YES.
# The default value is: NO.
-EXTRACT_ALL = YES
+EXTRACT_ALL = NO
# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
# be included in the documentation.
@@ -498,7 +492,7 @@ EXTRACT_ANON_NSPACES = NO
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.
-HIDE_UNDOC_MEMBERS = YES
+HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set
@@ -506,7 +500,7 @@ HIDE_UNDOC_MEMBERS = YES
# has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.
-HIDE_UNDOC_CLASSES = YES
+HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# (class|struct|union) declarations. If set to NO, these declarations will be
@@ -543,7 +537,7 @@ CASE_SENSE_NAMES = YES
# scope will be hidden.
# The default value is: NO.
-HIDE_SCOPE_NAMES = NO
+HIDE_SCOPE_NAMES = YES
# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
# append additional text to a page's title, such as Class Reference. If set to
@@ -752,7 +746,7 @@ WARNINGS = YES
# will automatically be disabled.
# The default value is: YES.
-WARN_IF_UNDOCUMENTED = YES
+WARN_IF_UNDOCUMENTED = NO
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some parameters
@@ -769,7 +763,7 @@ WARN_IF_DOC_ERROR = YES
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# The default value is: NO.
-WARN_NO_PARAMDOC = YES
+WARN_NO_PARAMDOC = NO
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered.
@@ -1165,7 +1159,7 @@ HTML_EXTRA_FILES =
# Minimum value: 0, maximum value: 359, default value: 220.
# This tag requires that the tag GENERATE_HTML is set to YES.
-HTML_COLORSTYLE_HUE = 160
+HTML_COLORSTYLE_HUE = 120
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use grayscales only. A
@@ -1173,7 +1167,7 @@ HTML_COLORSTYLE_HUE = 160
# Minimum value: 0, maximum value: 255, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.
-HTML_COLORSTYLE_SAT = 100
+HTML_COLORSTYLE_SAT = 32
# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
# luminance component of the colors in the HTML output. Values below 100
@@ -1685,7 +1679,7 @@ COMPACT_LATEX = NO
# The default value is: a4.
# This tag requires that the tag GENERATE_LATEX is set to YES.
-PAPER_TYPE = a4wide
+PAPER_TYPE = a4
# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
# that should be included in the LaTeX output. The package can be specified just
@@ -1930,7 +1924,7 @@ MAN_LINKS = NO
# captures the structure of the code including all documentation.
# The default value is: NO.
-GENERATE_XML = NO
+GENERATE_XML = YES
# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
@@ -2090,7 +2084,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-PREDEFINED = PUGL_API PUGL_DISABLE_DEPRECATED
+PREDEFINED = PUGL_API PUGL_DISABLE_DEPRECATED PUGL_CONST_FUNC=
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
diff --git a/pugl/doc/style.css b/pugl/doc/style.css
index 28f0519d..680fe77f 100644
--- a/pugl/doc/style.css
+++ b/pugl/doc/style.css
@@ -233,6 +233,14 @@ div.groupHeader {
font-weight: 700;
}
+h2.groupheader {
+ line-height: 1.18em;
+ font-size: 1em;
+ font-weight: 600;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ margin: 1.25em 0 0.5em 0;
+}
+
a + h2.groupheader {
display: none;
}
@@ -387,6 +395,18 @@ table.memberdecls {
line-height: 1.3em;
}
+table.memberdecls h3 {
+ line-height: 1.18em;
+ font-size: 1em;
+ font-weight: 600;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ margin: 1.25em 0 0.5em 0;
+}
+
+tr.inherit_header td {
+ padding: 1em 0 0.5em 0;
+}
+
.mdescLeft,.mdescRight,.memItemLeft,.memItemRight,.memTemplItemLeft,.memTemplItemRight,.memTemplParams {
margin: 0;
padding: 0;
@@ -419,13 +439,28 @@ td.memSeparator {
display: none;
}
+td.mlabels-left {
+ margin-left: 0;
+ padding-left: 0;
+}
+
td.mlabels-right {
- vertical-align: top;
- padding-top: 4px;
color: #B4C342;
+ font-weight: normal;
+ margin-left: 1em;
+ vertical-align: bottom;
}
.memtitle {
+ border-bottom: 1px solid #EEE;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ font-size: 1.18em;
+ font-weight: 600;
+ line-height: 1.41em;
+ margin: 1.5em 0 0 0;
+}
+
+.permalink {
display: none;
}
@@ -446,18 +481,20 @@ td.mlabels-right {
}
.memitem {
- padding: 0.5em 0.5em 0.25em 0.5em;
- margin: 1em 0 2em 0;
+ padding: 0;
+ margin: 0 0 3em 0;
}
.memproto {
border-bottom: 1px solid #EEE;
+ border-left: 1px solid #EEE;
+ color: #444;
+ float: right;
font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
- font-size: 1.09em;
- font-weight: 600;
- line-height: 1.41em;
- margin-bottom: 0.25em;
- padding-bottom: 0.125em;
+ font-size: small;
+ margin-bottom: 1em;
+ margin-left: 1em;
+ padding: 0.25em 0 0.25em 0.25em;
}
.memproto .paramname {
@@ -465,6 +502,11 @@ td.mlabels-right {
padding-right: 0.25em;
}
+.mlabels {
+ padding-left: 0;
+ padding-right: 0;
+}
+
.memdoc {
padding: 0;
}
@@ -473,6 +515,19 @@ td.mlabels-right {
font-style: italic;
color: #444;
margin-bottom: 0.75em;
+ margin-top: 0;
+ padding-top: 0.25em;
+ font-weight: normal;
+}
+
+.memdoc > p:first-child, .memdoc .textblock > h3:first-child {
+ color: #444;
+ margin-bottom: 0.75em;
+ margin-top: 0;
+ padding-top: 0.25em;
+ font-weight: normal;
+ color: #444;
+ font-size: 0.9em;
}
.paramkey {
diff --git a/pugl/examples/cube_view.h b/pugl/examples/cube_view.h
index 9fd23495..8a81f488 100644
--- a/pugl/examples/cube_view.h
+++ b/pugl/examples/cube_view.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
diff --git a/pugl/examples/demo_utils.h b/pugl/examples/demo_utils.h
index 9a1cb7a1..6d3bb66f 100644
--- a/pugl/examples/demo_utils.h
+++ b/pugl/examples/demo_utils.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2019 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -135,7 +135,7 @@ mat4Ortho(mat4 m,
m[3][3] = 1.0f;
}
-/** Calculate a projection matrix for a given perspective. */
+/// Calculate a projection matrix for a given perspective
static inline void
perspective(float* m, float fov, float aspect, float zNear, float zFar)
{
diff --git a/pugl/examples/pugl_cairo_demo.c b/pugl/examples/pugl_cairo_demo.c
index 483446fb..5fe06618 100644
--- a/pugl/examples/pugl_cairo_demo.c
+++ b/pugl/examples/pugl_cairo_demo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl_cairo_demo.c An example of drawing with Cairo.
+ @file pugl_cairo_demo.c
+ @brief An example of drawing with Cairo.
*/
#include "demo_utils.h"
@@ -32,8 +33,8 @@
#include <string.h>
typedef struct {
- PuglTestOptions opts;
PuglWorld* world;
+ PuglTestOptions opts;
unsigned framesDrawn;
int quit;
bool entered;
@@ -235,11 +236,10 @@ main(int argc, char** argv)
app.world = puglNewWorld(PUGL_PROGRAM, 0);
puglSetClassName(app.world, "PuglCairoTest");
- PuglRect frame = { 0, 0, 512, 512 };
- PuglView* view = puglNewView(app.world);
+ PuglView* view = puglNewView(app.world);
puglSetWindowTitle(view, "Pugl Cairo Demo");
- puglSetFrame(view, frame);
+ puglSetDefaultSize(view, 512, 512);
puglSetMinSize(view, 256, 256);
puglSetViewHint(view, PUGL_RESIZABLE, app.opts.resizable);
puglSetHandle(view, &app);
diff --git a/pugl/examples/pugl_cursor_demo.c b/pugl/examples/pugl_cursor_demo.c
new file mode 100644
index 00000000..03ab5da5
--- /dev/null
+++ b/pugl/examples/pugl_cursor_demo.c
@@ -0,0 +1,171 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_cursor_demo.c
+ @brief An example of changing the mouse cursor.
+*/
+
+#include "test/test_utils.h"
+
+#include "pugl/gl.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl_gl.h"
+
+#include <stdbool.h>
+
+static const int N_CURSORS = 7;
+static const int N_ROWS = 2;
+static const int N_COLS = 4;
+
+typedef struct {
+ PuglWorld* world;
+ PuglTestOptions opts;
+ bool quit;
+} PuglTestApp;
+
+static void
+onConfigure(const double width, const double height)
+{
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LESS);
+ glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glViewport(0, 0, (int)width, (int)height);
+}
+
+static void
+onExpose(void)
+{
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glColor3f(0.6f, 0.6f, 0.6f);
+
+ for (int row = 1; row < N_ROWS; ++row) {
+ const float y = (float)row * (2.0f / (float)N_ROWS) - 1.0f;
+ glBegin(GL_LINES);
+ glVertex2f(-1.0f, y);
+ glVertex2f(1.0f, y);
+ glEnd();
+ }
+
+ for (int col = 1; col < N_COLS; ++col) {
+ const float x = (float)col * (2.0f / (float)N_COLS) - 1.0f;
+ glBegin(GL_LINES);
+ glVertex2f(x, -1.0f);
+ glVertex2f(x, 1.0f);
+ glEnd();
+ }
+}
+
+static void
+onMotion(PuglView* view, double x, double y)
+{
+ const PuglRect frame = puglGetFrame(view);
+ int row = (int)(y * N_ROWS / frame.height);
+ int col = (int)(x * N_COLS / frame.width);
+
+ row = (row < 0) ? 0 : (row >= N_ROWS) ? (N_ROWS - 1) : row;
+ col = (col < 0) ? 0 : (col >= N_COLS) ? (N_COLS - 1) : col;
+
+ const PuglCursor cursor = (PuglCursor)((row * N_COLS + col) % N_CURSORS);
+ puglSetCursor(view, cursor);
+}
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+
+ printEvent(event, "Event: ", app->opts.verbose);
+
+ switch (event->type) {
+ case PUGL_CONFIGURE:
+ onConfigure(event->configure.width, event->configure.height);
+ break;
+ case PUGL_KEY_PRESS:
+ if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
+ app->quit = 1;
+ }
+ break;
+ case PUGL_MOTION:
+ onMotion(view, event->motion.x, event->motion.y);
+ break;
+ case PUGL_EXPOSE:
+ onExpose();
+ break;
+ case PUGL_CLOSE:
+ app->quit = 1;
+ break;
+ default:
+ break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTestApp app = {0};
+
+ app.opts = puglParseTestOptions(&argc, &argv);
+ if (app.opts.help) {
+ puglPrintTestUsage(argv[0], "");
+ return 1;
+ }
+
+ app.world = puglNewWorld(PUGL_PROGRAM, 0);
+
+ puglSetWorldHandle(app.world, &app);
+ puglSetClassName(app.world, "Pugl Test");
+
+ PuglView* view = puglNewView(app.world);
+
+ puglSetWindowTitle(view, "Pugl Window Demo");
+ puglSetDefaultSize(view, 512, 256);
+ puglSetMinSize(view, 128, 64);
+ puglSetBackend(view, puglGlBackend());
+
+ puglSetViewHint(view, PUGL_USE_DEBUG_CONTEXT, app.opts.errorChecking);
+ puglSetViewHint(view, PUGL_RESIZABLE, app.opts.resizable);
+ puglSetViewHint(view, PUGL_SAMPLES, app.opts.samples);
+ puglSetViewHint(view, PUGL_DOUBLE_BUFFER, app.opts.doubleBuffer);
+ puglSetViewHint(view, PUGL_SWAP_INTERVAL, app.opts.sync);
+ puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, app.opts.ignoreKeyRepeat);
+ puglSetHandle(view, &app);
+ puglSetEventFunc(view, onEvent);
+
+ const PuglStatus st = puglRealize(view);
+ if (st) {
+ return logError("Failed to create window (%s)\n", puglStrerror(st));
+ }
+
+ puglShowWindow(view);
+
+ while (!app.quit) {
+ puglUpdate(app.world, -1.0);
+ }
+
+ puglFreeView(view);
+ puglFreeWorld(app.world);
+
+ return 0;
+}
diff --git a/pugl/examples/pugl_cxx_demo.cpp b/pugl/examples/pugl_cxx_demo.cpp
new file mode 100644
index 00000000..4addee27
--- /dev/null
+++ b/pugl/examples/pugl_cxx_demo.cpp
@@ -0,0 +1,146 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_cxx_demo.cpp
+ @brief A simple demo of the Pugl C++ API.
+*/
+
+#include "cube_view.h"
+#include "demo_utils.h"
+#include "test/test_utils.h"
+
+#include "pugl/gl.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl.hpp"
+#include "pugl/pugl.ipp"
+#include "pugl/pugl_gl.hpp"
+
+#include <cmath>
+
+class CubeView : public pugl::View
+{
+public:
+ explicit CubeView(pugl::World& world)
+ : pugl::View{world}
+ {}
+
+ pugl::Status onConfigure(const pugl::ConfigureEvent& event) override;
+ pugl::Status onUpdate(const pugl::UpdateEvent& event) override;
+ pugl::Status onExpose(const pugl::ExposeEvent& event) override;
+ pugl::Status onKeyPress(const pugl::KeyPressEvent& event) override;
+ pugl::Status onClose(const pugl::CloseEvent& event) override;
+
+ bool quit() const { return _quit; }
+
+private:
+ double _xAngle{0.0};
+ double _yAngle{0.0};
+ double _lastDrawTime{0.0};
+ bool _quit{false};
+};
+
+pugl::Status
+CubeView::onConfigure(const pugl::ConfigureEvent& event)
+{
+ reshapeCube(static_cast<float>(event.width),
+ static_cast<float>(event.height));
+
+ return pugl::Status::success;
+}
+
+pugl::Status
+CubeView::onUpdate(const pugl::UpdateEvent&)
+{
+ return postRedisplay();
+}
+
+pugl::Status
+CubeView::onExpose(const pugl::ExposeEvent&)
+{
+ const double thisTime = world().time();
+ const double dTime = thisTime - _lastDrawTime;
+ const double dAngle = dTime * 100.0;
+
+ _xAngle = fmod(_xAngle + dAngle, 360.0);
+ _yAngle = fmod(_yAngle + dAngle, 360.0);
+ displayCube(cobj(),
+ 8.0f,
+ static_cast<float>(_xAngle),
+ static_cast<float>(_yAngle),
+ false);
+
+ _lastDrawTime = thisTime;
+
+ return pugl::Status::success;
+}
+
+pugl::Status
+CubeView::onKeyPress(const pugl::KeyPressEvent& event)
+{
+ if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') {
+ _quit = true;
+ }
+
+ return pugl::Status::success;
+}
+
+pugl::Status
+CubeView::onClose(const pugl::CloseEvent&)
+{
+ _quit = true;
+
+ return pugl::Status::success;
+}
+
+int
+main(int argc, char** argv)
+{
+ const PuglTestOptions opts = puglParseTestOptions(&argc, &argv);
+ if (opts.help) {
+ puglPrintTestUsage("pugl_cxx_demo", "");
+ return 1;
+ }
+
+ pugl::World world{pugl::WorldType::program};
+ CubeView view{world};
+ PuglFpsPrinter fpsPrinter{};
+
+ world.setClassName("PuglCppTest");
+
+ view.setWindowTitle("Pugl C++ Test");
+ view.setDefaultSize(512, 512);
+ view.setMinSize(64, 64);
+ view.setAspectRatio(1, 1, 16, 9);
+ view.setBackend(pugl::glBackend());
+ view.setHint(pugl::ViewHint::resizable, opts.resizable);
+ view.setHint(pugl::ViewHint::samples, opts.samples);
+ view.setHint(pugl::ViewHint::doubleBuffer, opts.doubleBuffer);
+ view.setHint(pugl::ViewHint::swapInterval, opts.sync);
+ view.setHint(pugl::ViewHint::ignoreKeyRepeat, opts.ignoreKeyRepeat);
+ view.realize();
+ view.showWindow();
+
+ unsigned framesDrawn = 0;
+ while (!view.quit()) {
+ world.update(0.0);
+
+ ++framesDrawn;
+ puglPrintFps(world.cobj(), &fpsPrinter, &framesDrawn);
+ }
+
+ return 0;
+}
diff --git a/pugl/examples/pugl_embed_demo.c b/pugl/examples/pugl_embed_demo.c
index 3a7b051e..774ac772 100644
--- a/pugl/examples/pugl_embed_demo.c
+++ b/pugl/examples/pugl_embed_demo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl_embed_demo.c An example of embedding a view in another.
+ @file pugl_embed_demo.c
+ @brief An example of embedding a view in another.
*/
#include "cube_view.h"
@@ -40,14 +41,14 @@ typedef struct
PuglWorld* world;
PuglView* parent;
PuglView* child;
- bool continuous;
- int quit;
double xAngle;
double yAngle;
- float dist;
double lastMouseX;
double lastMouseY;
double lastDrawTime;
+ float dist;
+ int quit;
+ bool continuous;
bool mouseEntered;
bool verbose;
bool reversing;
@@ -292,8 +293,9 @@ main(int argc, char** argv)
puglSetClassName(app.world, "Pugl Test");
const PuglRect parentFrame = { 0, 0, 512, 512 };
- puglSetFrame(app.parent, parentFrame);
+ puglSetDefaultSize(app.parent, 512, 512);
puglSetMinSize(app.parent, borderWidth * 3, borderWidth * 3);
+ puglSetMaxSize(app.parent, 1024, 1024);
puglSetAspectRatio(app.parent, 1, 1, 16, 9);
puglSetBackend(app.parent, puglGlBackend());
diff --git a/pugl/examples/pugl_print_events.c b/pugl/examples/pugl_print_events.c
index 52b58c4b..08a4a86d 100644
--- a/pugl/examples/pugl_print_events.c
+++ b/pugl/examples/pugl_print_events.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl_print_events.c A utility program that prints view events.
+ @file pugl_print_events.c
+ @brief A utility program that prints view events.
*/
#include "test/test_utils.h"
diff --git a/pugl/examples/pugl_gl3_demo.c b/pugl/examples/pugl_shader_demo.c
index c49ed3d6..50afb37c 100644
--- a/pugl/examples/pugl_gl3_demo.c
+++ b/pugl/examples/pugl_shader_demo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl_gl3_demo.c An example of drawing with OpenGL 3.
+ @file pugl_shader_demo.c
+ @brief An example of drawing with OpenGL 3/4.
This is an example of using OpenGL for pixel-perfect 2D drawing. It uses
pixel coordinates for positions and sizes so that things work roughly like a
@@ -35,6 +36,7 @@
*/
#include "demo_utils.h"
+#include "rects.h"
#include "shader_utils.h"
#include "test/test_utils.h"
@@ -44,7 +46,6 @@
#include "pugl/pugl.h"
#include "pugl/pugl_gl.h"
-#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@@ -55,27 +56,14 @@ static const int defaultHeight = 512;
typedef struct
{
- float pos[2];
- float size[2];
- float fillColor[4];
-} Rect;
-
-// clang-format off
-static const GLfloat rectVertices[] = {
- 0.0f, 0.0f, // TL
- 1.0f, 0.0f, // TR
- 0.0f, 1.0f, // BL
- 1.0f, 1.0f, // BR
-};
-// clang-format on
-
-static const GLuint rectIndices[4] = {0, 1, 2, 3};
+ mat4 projection;
+} RectUniforms;
typedef struct
{
- PuglTestOptions opts;
PuglWorld* world;
PuglView* view;
+ PuglTestOptions opts;
size_t numRects;
Rect* rects;
Program drawRect;
@@ -83,8 +71,9 @@ typedef struct
GLuint vbo;
GLuint instanceVbo;
GLuint ibo;
- GLint u_projection;
unsigned framesDrawn;
+ int glMajorVersion;
+ int glMinorVersion;
int quit;
} PuglTestApp;
@@ -100,7 +89,8 @@ onConfigure(PuglView* view, double width, double height)
(void)view;
glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
+ glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glViewport(0, 0, (int)width, (int)height);
}
@@ -129,25 +119,12 @@ onExpose(PuglView* view)
glUseProgram(app->drawRect.program);
glBindVertexArray(app->vao);
- // Set projection matrix uniform
- glUniformMatrix4fv(app->u_projection, 1, GL_FALSE, (const GLfloat*)&proj);
-
for (size_t i = 0; i < app->numRects; ++i) {
- Rect* rect = &app->rects[i];
- const float normal = i / (float)app->numRects;
- const float offset[2] = {normal * 128.0f, normal * 128.0f};
-
- // Move rect around in an arbitrary way that looks cool
- rect->pos[0] = (width - rect->size[0] + offset[0]) *
- (sinf((float)time * rect->size[0] / 64.0f + normal) +
- 1.0f) /
- 2.0f;
- rect->pos[1] = (height - rect->size[1] + offset[1]) *
- (cosf((float)time * rect->size[1] / 64.0f + normal) +
- 1.0f) /
- 2.0f;
+ moveRect(&app->rects[i], i, app->numRects, width, height, time);
}
+ glBufferData(GL_UNIFORM_BUFFER, sizeof(proj), &proj, GL_STREAM_DRAW);
+
glBufferSubData(GL_ARRAY_BUFFER,
0,
(GLsizeiptr)(app->numRects * sizeof(Rect)),
@@ -198,20 +175,9 @@ onEvent(PuglView* view, const PuglEvent* event)
static Rect*
makeRects(const size_t numRects)
{
- const float minSize = (float)defaultWidth / 64.0f;
- const float maxSize = (float)defaultWidth / 6.0f;
- const float boxAlpha = 0.2f;
-
Rect* rects = (Rect*)calloc(numRects, sizeof(Rect));
for (size_t i = 0; i < numRects; ++i) {
- const float s = (sinf((float)i) / 2.0f + 0.5f);
- const float c = (cosf((float)i) / 2.0f + 0.5f);
-
- rects[i].size[0] = minSize + s * maxSize;
- rects[i].size[1] = minSize + c * maxSize;
- rects[i].fillColor[1] = s / 2.0f + 0.25f;
- rects[i].fillColor[2] = c / 2.0f + 0.25f;
- rects[i].fillColor[3] = boxAlpha;
+ rects[i] = makeRect(i, (float)defaultWidth);
}
return rects;
@@ -241,6 +207,8 @@ loadShader(const char* const path)
static int
parseOptions(PuglTestApp* app, int argc, char** argv)
{
+ char* endptr = NULL;
+
// Parse command line options
app->numRects = 1024;
app->opts = puglParseTestOptions(&argc, &argv);
@@ -249,11 +217,24 @@ parseOptions(PuglTestApp* app, int argc, char** argv)
}
// Parse number of rectangles argument, if given
- if (argc == 1) {
- char* endptr = NULL;
-
+ if (argc >= 1) {
app->numRects = (size_t)strtol(argv[0], &endptr, 10);
if (endptr != argv[0] + strlen(argv[0])) {
+ logError("Invalid number of rectangles: %s\n", argv[0]);
+ return 1;
+ }
+ }
+
+ // Parse OpenGL major version argument, if given
+ if (argc >= 2) {
+ app->glMajorVersion = (int)strtol(argv[1], &endptr, 10);
+ if (endptr != argv[1] + strlen(argv[1])) {
+ logError("Invalid GL major version: %s\n", argv[1]);
+ return 1;
+ } else if (app->glMajorVersion == 4) {
+ app->glMinorVersion = 2;
+ } else if (app->glMajorVersion != 3) {
+ logError("Unsupported GL major version %d\n", app->glMajorVersion);
return 1;
}
}
@@ -262,7 +243,7 @@ parseOptions(PuglTestApp* app, int argc, char** argv)
}
static void
-setupPugl(PuglTestApp* app, const PuglRect frame)
+setupPugl(PuglTestApp* app)
{
// Create world, view, and rect data
app->world = puglNewWorld(PUGL_PROGRAM, 0);
@@ -272,14 +253,14 @@ setupPugl(PuglTestApp* app, const PuglRect frame)
// Set up world and view
puglSetClassName(app->world, "PuglGL3Demo");
puglSetWindowTitle(app->view, "Pugl OpenGL 3");
- puglSetFrame(app->view, frame);
+ puglSetDefaultSize(app->view, defaultWidth, defaultHeight);
puglSetMinSize(app->view, defaultWidth / 4, defaultHeight / 4);
puglSetAspectRatio(app->view, 1, 1, 16, 9);
puglSetBackend(app->view, puglGlBackend());
puglSetViewHint(app->view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE);
puglSetViewHint(app->view, PUGL_USE_DEBUG_CONTEXT, app->opts.errorChecking);
- puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MAJOR, 3);
- puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MINOR, 3);
+ puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MAJOR, app->glMajorVersion);
+ puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MINOR, app->glMinorVersion);
puglSetViewHint(app->view, PUGL_RESIZABLE, app->opts.resizable);
puglSetViewHint(app->view, PUGL_SAMPLES, app->opts.samples);
puglSetViewHint(app->view, PUGL_DOUBLE_BUFFER, app->opts.doubleBuffer);
@@ -298,7 +279,12 @@ setupGl(PuglTestApp* app)
return PUGL_FAILURE;
}
+ const char* const headerFile = (app->glMajorVersion == 3
+ ? "shaders/header_330.glsl"
+ : "shaders/header_420.glsl");
+
// Load shader sources
+ char* const headerSource = loadShader(headerFile);
char* const vertexSource = loadShader("shaders/rect.vert");
char* const fragmentSource = loadShader("shaders/rect.frag");
if (!vertexSource || !fragmentSource) {
@@ -307,16 +293,23 @@ setupGl(PuglTestApp* app)
}
// Compile rectangle shaders and program
- app->drawRect = compileProgram(vertexSource, fragmentSource);
+ app->drawRect = compileProgram(headerSource, vertexSource, fragmentSource);
free(fragmentSource);
free(vertexSource);
+ free(headerSource);
if (!app->drawRect.program) {
return PUGL_FAILURE;
}
- // Get location of rectangle shader uniforms
- app->u_projection =
- glGetUniformLocation(app->drawRect.program, "u_projection");
+ // Get location of rectangle shader uniform block
+ const GLuint globalsIndex = glGetUniformBlockIndex(app->drawRect.program,
+ "UniformBufferObject");
+
+ // Generate/bind a uniform buffer for setting rectangle properties
+ GLuint uboHandle = 0;
+ glGenBuffers(1, &uboHandle);
+ glBindBuffer(GL_UNIFORM_BUFFER, uboHandle);
+ glBindBufferBase(GL_UNIFORM_BUFFER, globalsIndex, uboHandle);
// Generate/bind a VAO to track state
glGenVertexArrays(1, &app->vao);
@@ -391,19 +384,19 @@ teardownGl(PuglTestApp* app)
int
main(int argc, char** argv)
{
- PuglTestApp app;
- memset(&app, 0, sizeof(app));
+ PuglTestApp app = {0};
- const PuglRect frame = {0, 0, defaultWidth, defaultHeight};
+ app.glMajorVersion = 3;
+ app.glMinorVersion = 3;
// Parse command line options
if (parseOptions(&app, argc, argv)) {
- puglPrintTestUsage("pugl_gl3_demo", "[NUM_RECTS]");
+ puglPrintTestUsage("pugl_shader_demo", "[NUM_RECTS] [GL_MAJOR]");
return 1;
}
// Create and configure world and view
- setupPugl(&app, frame);
+ setupPugl(&app);
// Create window (which will send a PUGL_CREATE event)
const PuglStatus st = puglRealize(app.view);
diff --git a/pugl/examples/pugl_window_demo.c b/pugl/examples/pugl_window_demo.c
index 183119c6..f326f214 100644
--- a/pugl/examples/pugl_window_demo.c
+++ b/pugl/examples/pugl_window_demo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl_window_demo.c A demonstration of multiple Pugl windows.
+ @file pugl_window_demo.c
+ @brief A demonstration of multiple Pugl windows.
*/
#include "cube_view.h"
@@ -33,21 +34,23 @@ typedef struct {
PuglView* view;
double xAngle;
double yAngle;
- float dist;
double lastMouseX;
double lastMouseY;
double lastDrawTime;
+ float dist;
bool entered;
} CubeView;
typedef struct {
PuglWorld* world;
CubeView cubes[2];
- bool continuous;
int quit;
+ bool continuous;
bool verbose;
} PuglTestApp;
+static const double pad = 64.0;
+
static void
onDisplay(PuglView* view)
{
@@ -199,19 +202,19 @@ main(int argc, char** argv)
puglSetClassName(app.world, "Pugl Test");
PuglStatus st = PUGL_SUCCESS;
- for (size_t i = 0; i < 2; ++i) {
+ for (unsigned i = 0; i < 2; ++i) {
CubeView* cube = &app.cubes[i];
PuglView* view = cube->view;
- static const double pad = 64.0;
- const PuglRect frame = {pad + (256.0 + pad) * i,
- pad + (256.0 + pad) * i,
- 256.0,
- 256.0};
+ const PuglRect frame = {pad + (128.0 + pad) * i,
+ pad + (128.0 + pad) * i,
+ 512.0,
+ 512.0};
cube->dist = 10;
puglSetWindowTitle(view, "Pugl Window Demo");
puglSetFrame(view, frame);
+ puglSetDefaultSize(view, 512, 512);
puglSetMinSize(view, 128, 128);
puglSetBackend(view, puglGlBackend());
@@ -224,6 +227,11 @@ main(int argc, char** argv)
puglSetHandle(view, cube);
puglSetEventFunc(view, onEvent);
+ if (i == 1) {
+ puglSetTransientFor(app.cubes[1].view,
+ puglGetNativeWindow(app.cubes[0].view));
+ }
+
if ((st = puglRealize(view))) {
return logError("Failed to create window (%s)\n", puglStrerror(st));
}
diff --git a/pugl/examples/rects.h b/pugl/examples/rects.h
new file mode 100644
index 00000000..f760226e
--- /dev/null
+++ b/pugl/examples/rects.h
@@ -0,0 +1,82 @@
+/*
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file rects.h
+ @brief Utilities for rectangle animation demos.
+
+ This file contains common definitions for demos that show an animation of
+ many 2D rectangles.
+*/
+
+#include <math.h>
+#include <stddef.h>
+
+typedef float vec2[2];
+
+typedef struct {
+ float pos[2];
+ float size[2];
+ float fillColor[4];
+} Rect;
+
+static const vec2 rectVertices[] = {
+ {0.0f, 0.0f}, // TL
+ {1.0f, 0.0f}, // TR
+ {0.0f, 1.0f}, // BL
+ {1.0f, 1.0f} // BR
+};
+
+static const unsigned rectIndices[4] = {0, 1, 2, 3};
+
+/// Make a new rectangle with the given index (each is slightly different)
+static inline Rect
+makeRect(const size_t index, const float frameWidth)
+{
+ static const float alpha = 0.3f;
+ const float minSize = frameWidth / 64.0f;
+ const float maxSize = frameWidth / 6.0f;
+ const float s = (sinf((float)index) / 2.0f + 0.5f);
+ const float c = (cosf((float)index) / 2.0f + 0.5f);
+
+ const Rect rect = {
+ {0.0f, 0.0f}, // Position is set later during expose
+ {minSize + s * maxSize, minSize + c * maxSize},
+ {0.0f, s / 2.0f + 0.25f, c / 2.0f + 0.25f, alpha},
+ };
+
+ return rect;
+}
+
+/// Move `rect` with the given index around in an arbitrary way that looks cool
+static inline void
+moveRect(Rect* const rect,
+ const size_t index,
+ const size_t numRects,
+ const float frameWidth,
+ const float frameHeight,
+ const double time)
+{
+ const float normal = (float)index / (float)numRects;
+ const float offset[2] = {normal * 128.0f, normal * 128.0f};
+
+ rect->pos[0] = (frameWidth - rect->size[0] + offset[0]) *
+ (sinf((float)time * rect->size[0] / 64.0f + normal) + 1.0f) /
+ 2.0f;
+ rect->pos[1] = (frameHeight - rect->size[1] + offset[1]) *
+ (cosf((float)time * rect->size[1] / 64.0f + normal) + 1.0f) /
+ 2.0f;
+}
diff --git a/pugl/examples/shader_utils.h b/pugl/examples/shader_utils.h
index 834d8fcf..10a7ace7 100644
--- a/pugl/examples/shader_utils.h
+++ b/pugl/examples/shader_utils.h
@@ -1,5 +1,5 @@
/*
- Copyright 2019 David Robillard <http://drobilla.net>
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -28,17 +28,18 @@ typedef struct
} Program;
static GLuint
-compileShader(const char* source, const GLenum type)
+compileShader(const char* header, const char* source, const GLenum type)
{
- GLuint shader = glCreateShader(type);
- const int sourceLength = (int)strlen(source);
- glShaderSource(shader, 1, &source, &sourceLength);
+ const GLchar* sources[] = {header, source};
+ const GLint lengths[] = {(GLint)strlen(header), (GLint)strlen(source)};
+ GLuint shader = glCreateShader(type);
+ glShaderSource(shader, 2, sources, lengths);
glCompileShader(shader);
- int status;
+ int status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
- GLint length;
+ GLint length = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
char* log = (char*)calloc(1, (size_t)length);
@@ -61,13 +62,17 @@ deleteProgram(Program program)
}
static Program
-compileProgram(const char* vertexSource, const char* fragmentSource)
+compileProgram(const char* headerSource,
+ const char* vertexSource,
+ const char* fragmentSource)
{
static const Program nullProgram = {0, 0, 0};
- Program program = {compileShader(vertexSource, GL_VERTEX_SHADER),
- compileShader(fragmentSource, GL_FRAGMENT_SHADER),
- glCreateProgram()};
+ Program program = {
+ compileShader(headerSource, vertexSource, GL_VERTEX_SHADER),
+ compileShader(headerSource, fragmentSource, GL_FRAGMENT_SHADER),
+ glCreateProgram(),
+ };
if (!program.vertexShader || !program.fragmentShader || !program.program) {
deleteProgram(program);
@@ -78,10 +83,10 @@ compileProgram(const char* vertexSource, const char* fragmentSource)
glAttachShader(program.program, program.fragmentShader);
glLinkProgram(program.program);
- GLint status;
+ GLint status = 0;
glGetProgramiv(program.program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
- GLint length;
+ GLint length = 0;
glGetProgramiv(program.program, GL_INFO_LOG_LENGTH, &length);
char* log = (char*)calloc(1, (size_t)length);
diff --git a/pugl/pugl/detail/implementation.c b/pugl/pugl/detail/implementation.c
index ee9b242c..6cc44901 100644
--- a/pugl/pugl/detail/implementation.c
+++ b/pugl/pugl/detail/implementation.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file implementation.c Platform-independent implementation.
+ @file implementation.c
+ @brief Platform-independent implementation.
*/
#include "pugl/detail/implementation.h"
@@ -60,6 +61,7 @@ puglStrerror(const PuglStatus status)
case PUGL_FAILURE: return "Non-fatal failure";
case PUGL_UNKNOWN_ERROR: return "Unknown system error";
case PUGL_BAD_BACKEND: return "Invalid or missing backend";
+ case PUGL_BAD_CONFIGURATION: return "Invalid view configuration";
case PUGL_BAD_PARAMETER: return "Invalid parameter";
case PUGL_BACKEND_FAILED: return "Backend initialisation failed";
case PUGL_REGISTRATION_FAILED: return "Class registration failed";
@@ -188,9 +190,9 @@ puglNewView(PuglWorld* const world)
return NULL;
}
- view->world = world;
- view->frame.width = 640;
- view->frame.height = 480;
+ view->world = world;
+ view->minWidth = 1;
+ view->minHeight = 1;
puglSetDefaultHints(view->hints);
@@ -226,6 +228,7 @@ puglFreeView(PuglView* view)
free(view->title);
free(view->clipboard.data);
+ free(view->clipboardType.data);
puglFreeViewInternals(view);
free(view);
}
@@ -309,7 +312,7 @@ PuglStatus
puglEnterContext(PuglView* view, bool drawing)
{
const PuglEventExpose expose = {
- PUGL_EXPOSE, 0, 0, 0, view->frame.width, view->frame.height, 0};
+ PUGL_EXPOSE, 0, 0.0, 0.0, view->frame.width, view->frame.height};
view->backend->enter(view, drawing ? &expose : NULL);
@@ -320,7 +323,7 @@ PuglStatus
puglLeaveContext(PuglView* view, bool drawing)
{
const PuglEventExpose expose = {
- PUGL_EXPOSE, 0, 0, 0, view->frame.width, view->frame.height, 0};
+ PUGL_EXPOSE, 0, 0.0, 0.0, view->frame.width, view->frame.height};
view->backend->leave(view, drawing ? &expose : NULL);
@@ -336,7 +339,7 @@ puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc)
return PUGL_SUCCESS;
}
-/** Return the code point for buf, or the replacement character on error. */
+/// Return the code point for buf, or the replacement character on error
uint32_t
puglDecodeUTF8(const uint8_t* buf)
{
@@ -445,7 +448,7 @@ puglGetInternalClipboard(const PuglView* const view,
}
if (type) {
- *type = "text/plain";
+ *type = view->clipboardType.data;
}
return view->clipboard.data;
@@ -457,10 +460,11 @@ puglSetInternalClipboard(PuglView* const view,
const void* const data,
const size_t len)
{
- if (type && strcmp(type, "text/plain")) {
+ if (!type) {
return PUGL_UNSUPPORTED_TYPE;
}
+ puglSetBlob(&view->clipboardType, type, strlen(type) + 1);
puglSetBlob(&view->clipboard, data, len);
return PUGL_SUCCESS;
}
diff --git a/pugl/pugl/detail/implementation.h b/pugl/pugl/detail/implementation.h
index bcecd858..ff97fef7 100644
--- a/pugl/pugl/detail/implementation.h
+++ b/pugl/pugl/detail/implementation.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file implementation.h Shared declarations for implementation.
+ @file implementation.h
+ @brief Shared declarations for implementation.
*/
#ifndef PUGL_DETAIL_IMPLEMENTATION_H
@@ -29,42 +30,42 @@
PUGL_BEGIN_DECLS
-/** Set `blob` to `data` with length `len`, reallocating if necessary. */
+/// Set `blob` to `data` with length `len`, reallocating if necessary
void puglSetBlob(PuglBlob* dest, const void* data, size_t len);
-/** Reallocate and set `*dest` to `string`. */
+/// Reallocate and set `*dest` to `string`
void puglSetString(char** dest, const char* string);
-/** Allocate and initialise world internals (implemented once per platform) */
+/// Allocate and initialise world internals (implemented once per platform)
PuglWorldInternals*
puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags);
-/** Destroy and free world internals (implemented once per platform) */
+/// Destroy and free world internals (implemented once per platform)
void puglFreeWorldInternals(PuglWorld* world);
-/** Allocate and initialise view internals (implemented once per platform) */
+/// Allocate and initialise view internals (implemented once per platform)
PuglInternals* puglInitViewInternals(void);
-/** Destroy and free view internals (implemented once per platform) */
+/// Destroy and free view internals (implemented once per platform)
void puglFreeViewInternals(PuglView* view);
-/** Return the Unicode code point for `buf` or the replacement character. */
+/// Return the Unicode code point for `buf` or the replacement character
uint32_t puglDecodeUTF8(const uint8_t* buf);
-/** Dispatch an event with a simple `type` to `view`. */
+/// Dispatch an event with a simple `type` to `view`
void puglDispatchSimpleEvent(PuglView* view, PuglEventType type);
-/** Dispatch `event` to `view` while already in the graphics context. */
+/// Dispatch `event` to `view` while already in the graphics context
void puglDispatchEventInContext(PuglView* view, const PuglEvent* event);
-/** Dispatch `event` to `view`, entering graphics context if necessary. */
+/// Dispatch `event` to `view`, entering graphics context if necessary
void puglDispatchEvent(PuglView* view, const PuglEvent* event);
-/** Set internal (stored in view) clipboard contents. */
+/// Set internal (stored in view) clipboard contents
const void*
puglGetInternalClipboard(const PuglView* view, const char** type, size_t* len);
-/** Set internal (stored in view) clipboard contents. */
+/// Set internal (stored in view) clipboard contents
PuglStatus
puglSetInternalClipboard(PuglView* view,
const char* type,
diff --git a/pugl/pugl/detail/mac.h b/pugl/pugl/detail/mac.h
index 2243337a..7b64cfe1 100644
--- a/pugl/pugl/detail/mac.h
+++ b/pugl/pugl/detail/mac.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
Permission to use, copy, modify, and/or distribute this software for any
@@ -16,7 +16,8 @@
*/
/**
- @file mac.h Shared definitions for MacOS implementation.
+ @file mac.h
+ @brief Shared definitions for MacOS implementation.
*/
#include "pugl/pugl.h"
@@ -26,15 +27,6 @@
#include <stdint.h>
@interface PuglWrapperView : NSView<NSTextInputClient>
-{
-@public
- PuglView* puglview;
- NSTrackingArea* trackingArea;
- NSMutableAttributedString* markedText;
- NSTimer* timer;
- NSMutableDictionary* userTimers;
- bool reshaped;
-}
- (void) dispatchExpose:(NSRect)rect;
- (void) setReshaped;
@@ -42,10 +34,6 @@
@end
@interface PuglWindow : NSWindow
-{
-@public
- PuglView* puglview;
-}
- (void) setPuglview:(PuglView*)view;
@@ -60,6 +48,8 @@ struct PuglInternalsImpl {
NSApplication* app;
PuglWrapperView* wrapperView;
NSView* drawView;
+ NSCursor* cursor;
PuglWindow* window;
uint32_t mods;
+ bool mouseTracked;
};
diff --git a/pugl/pugl/detail/mac.m b/pugl/pugl/detail/mac.m
index 501be02d..5f3b89fc 100644
--- a/pugl/pugl/detail/mac.m
+++ b/pugl/pugl/detail/mac.m
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
Permission to use, copy, modify, and/or distribute this software for any
@@ -16,7 +16,8 @@
*/
/**
- @file mac.m MacOS implementation.
+ @file mac.m
+ @brief MacOS implementation.
*/
#define GL_SILENCE_DEPRECATION 1
@@ -40,31 +41,88 @@ typedef NSUInteger NSWindowStyleMask;
#endif
static NSRect
-rectToScreen(NSRect rect)
+rectToScreen(NSScreen* screen, NSRect rect)
{
- const double screenHeight = [[NSScreen mainScreen] frame].size.height;
+ const double screenHeight = [screen frame].size.height;
rect.origin.y = screenHeight - rect.origin.y - rect.size.height;
return rect;
}
+static NSScreen*
+viewScreen(PuglView* view)
+{
+ return view->impl->window ? [view->impl->window screen] : [NSScreen mainScreen];
+}
+
+static NSRect
+nsRectToPoints(PuglView* view, const NSRect rect)
+{
+ const double scaleFactor = [viewScreen(view) backingScaleFactor];
+
+ return NSMakeRect(rect.origin.x / scaleFactor,
+ rect.origin.y / scaleFactor,
+ rect.size.width / scaleFactor,
+ rect.size.height / scaleFactor);
+}
+
+static NSRect
+nsRectFromPoints(PuglView* view, const NSRect rect)
+{
+ const double scaleFactor = [viewScreen(view) backingScaleFactor];
+
+ return NSMakeRect(rect.origin.x * scaleFactor,
+ rect.origin.y * scaleFactor,
+ rect.size.width * scaleFactor,
+ rect.size.height * scaleFactor);
+}
+
+static NSPoint
+nsPointFromPoints(PuglView* view, const NSPoint point)
+{
+ const double scaleFactor = [viewScreen(view) backingScaleFactor];
+
+ return NSMakePoint(point.x * scaleFactor, point.y * scaleFactor);
+}
+
+static NSRect
+rectToNsRect(const PuglRect rect)
+{
+ return NSMakeRect(rect.x, rect.y, rect.width, rect.height);
+}
+
+static NSSize
+sizePoints(PuglView* view, const double width, const double height)
+{
+ const double scaleFactor = [viewScreen(view) backingScaleFactor];
+
+ return NSMakeSize(width / scaleFactor, height / scaleFactor);
+}
+
static void
updateViewRect(PuglView* view)
{
NSWindow* const window = view->impl->window;
if (window) {
- const double screenHeight = [[NSScreen mainScreen] frame].size.height;
- const NSRect frame = [window frame];
- const NSRect content = [window contentRectForFrameRect:frame];
-
- view->frame.x = content.origin.x;
- view->frame.y = screenHeight - content.origin.y - content.size.height;
- view->frame.width = content.size.width;
- view->frame.height = content.size.height;
+ const NSRect screenFramePt = [[NSScreen mainScreen] frame];
+ const NSRect screenFramePx = nsRectFromPoints(view, screenFramePt);
+ const NSRect framePt = [window frame];
+ const NSRect contentPt = [window contentRectForFrameRect:framePt];
+ const NSRect contentPx = nsRectFromPoints(view, contentPt);
+ const double screenHeight = screenFramePx.size.height;
+
+ view->frame.x = contentPx.origin.x;
+ view->frame.y = screenHeight - contentPx.origin.y - contentPx.size.height;
+ view->frame.width = contentPx.size.width;
+ view->frame.height = contentPx.size.height;
}
}
@implementation PuglWindow
+{
+@public
+ PuglView* puglview;
+}
- (id) initWithContentRect:(NSRect)contentRect
styleMask:(NSWindowStyleMask)aStyle
@@ -74,9 +132,9 @@ updateViewRect(PuglView* view)
(void)flag;
NSWindow* result = [super initWithContentRect:contentRect
- styleMask:aStyle
- backing:bufferingType
- defer:NO];
+ styleMask:aStyle
+ backing:bufferingType
+ defer:NO];
[result setAcceptsMouseMovedEvents:YES];
return (PuglWindow*)result;
@@ -85,7 +143,9 @@ updateViewRect(PuglView* view)
- (void)setPuglview:(PuglView*)view
{
puglview = view;
- [self setContentSize:NSMakeSize(view->frame.width, view->frame.height)];
+
+ [self
+ setContentSize:sizePoints(view, view->frame.width, view->frame.height)];
}
- (BOOL) canBecomeKeyWindow
@@ -124,13 +184,24 @@ updateViewRect(PuglView* view)
@end
@implementation PuglWrapperView
+{
+@public
+ PuglView* puglview;
+ NSTrackingArea* trackingArea;
+ NSMutableAttributedString* markedText;
+ NSTimer* timer;
+ NSMutableDictionary* userTimers;
+ bool reshaped;
+}
- (void) dispatchExpose:(NSRect)rect
{
+ const double scaleFactor = [[NSScreen mainScreen] backingScaleFactor];
+
if (reshaped) {
updateViewRect(puglview);
- const PuglEventConfigure ev = {
+ const PuglEventConfigure ev = {
PUGL_CONFIGURE,
0,
puglview->frame.x,
@@ -150,16 +221,26 @@ updateViewRect(PuglView* view)
const PuglEventExpose ev = {
PUGL_EXPOSE,
0,
- rect.origin.x,
- rect.origin.y,
- rect.size.width,
- rect.size.height,
- 0
+ rect.origin.x * scaleFactor,
+ rect.origin.y * scaleFactor,
+ rect.size.width * scaleFactor,
+ rect.size.height * scaleFactor,
};
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
+- (NSSize) intrinsicContentSize
+{
+ if (puglview->defaultWidth || puglview->defaultHeight) {
+ return sizePoints(puglview,
+ puglview->defaultWidth,
+ puglview->defaultHeight);
+ }
+
+ return NSMakeSize(NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric);
+}
+
- (BOOL) isFlipped
{
return YES;
@@ -246,7 +327,9 @@ keySymToSpecial(const NSEvent* const ev)
- (NSPoint) eventLocation:(NSEvent*)event
{
- return [self convertPoint:[event locationInWindow] fromView:nil];
+ return nsPointFromPoints(puglview,
+ [self convertPoint:[event locationInWindow]
+ fromView:nil]);
}
static void
@@ -272,11 +355,15 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
- (void) mouseEntered:(NSEvent*)event
{
handleCrossing(self, event, PUGL_POINTER_IN);
+ [puglview->impl->cursor set];
+ puglview->impl->mouseTracked = true;
}
- (void) mouseExited:(NSEvent*)event
{
+ [[NSCursor arrowCursor] set];
handleCrossing(self, event, PUGL_POINTER_OUT);
+ puglview->impl->mouseTracked = false;
}
- (void) mouseMoved:(NSEvent*)event
@@ -292,8 +379,6 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
rloc.x,
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(event),
- 0,
- 1,
};
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
@@ -374,19 +459,32 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
- (void) scrollWheel:(NSEvent*)event
{
- const NSPoint wloc = [self eventLocation:event];
- const NSPoint rloc = [NSEvent mouseLocation];
- const PuglEventScroll ev = {
- PUGL_SCROLL,
- 0,
- [event timestamp],
- wloc.x,
- wloc.y,
- rloc.x,
- [[NSScreen mainScreen] frame].size.height - rloc.y,
- getModifiers(event),
- [event scrollingDeltaX],
- [event scrollingDeltaY],
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const double dx = [event scrollingDeltaX];
+ const double dy = [event scrollingDeltaY];
+ const PuglScrollDirection dir =
+ ((dx == 0.0 && dy > 0.0)
+ ? PUGL_SCROLL_UP
+ : ((dx == 0.0 && dy < 0.0)
+ ? PUGL_SCROLL_DOWN
+ : ((dy == 0.0 && dx > 0.0)
+ ? PUGL_SCROLL_RIGHT
+ : ((dy == 0.0 && dx < 0.0) ? PUGL_SCROLL_LEFT
+ : PUGL_SCROLL_SMOOTH))));
+
+ const PuglEventScroll ev = {
+ PUGL_SCROLL,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ [event hasPreciseScrollingDeltas] ? PUGL_SCROLL_SMOOTH : dir,
+ dx,
+ dy,
};
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
@@ -653,15 +751,15 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
@end
@interface PuglWindowDelegate : NSObject<NSWindowDelegate>
-{
- PuglWindow* window;
-}
- (instancetype) initWithPuglWindow:(PuglWindow*)window;
@end
@implementation PuglWindowDelegate
+{
+ PuglWindow* window;
+}
- (instancetype) initWithPuglWindow:(PuglWindow*)puglWindow
{
@@ -692,7 +790,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
(void)notification;
PuglEvent ev = {{PUGL_FOCUS_IN, 0}};
- ev.focus.grab = false;
+ ev.focus.mode = PUGL_CROSSING_NORMAL;
puglDispatchEvent(window->puglview, &ev);
}
@@ -701,21 +799,23 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
(void)notification;
PuglEvent ev = {{PUGL_FOCUS_OUT, 0}};
- ev.focus.grab = false;
+ ev.focus.mode = PUGL_CROSSING_NORMAL;
puglDispatchEvent(window->puglview, &ev);
}
@end
PuglWorldInternals*
-puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type),
- PuglWorldFlags PUGL_UNUSED(flags))
+puglInitWorldInternals(PuglWorldType type, PuglWorldFlags PUGL_UNUSED(flags))
{
PuglWorldInternals* impl = (PuglWorldInternals*)calloc(
1, sizeof(PuglWorldInternals));
- impl->app = [NSApplication sharedApplication];
- impl->autoreleasePool = [NSAutoreleasePool new];
+ impl->app = [NSApplication sharedApplication];
+
+ if (type == PUGL_PROGRAM) {
+ impl->autoreleasePool = [NSAutoreleasePool new];
+ }
return impl;
}
@@ -723,7 +823,10 @@ puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type),
void
puglFreeWorldInternals(PuglWorld* world)
{
- [world->impl->autoreleasePool drain];
+ if (world->impl->autoreleasePool) {
+ [world->impl->autoreleasePool drain];
+ }
+
free(world->impl);
}
@@ -736,7 +839,11 @@ puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world))
PuglInternals*
puglInitViewInternals(void)
{
- return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+ PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
+
+ impl->cursor = [NSCursor arrowCursor];
+
+ return impl;
}
static NSLayoutConstraint*
@@ -749,13 +856,35 @@ puglConstraint(id item, NSLayoutAttribute attribute, float constant)
toItem: nil
attribute: NSLayoutAttributeNotAnAttribute
multiplier: 1.0
- constant: constant];
+ constant: (CGFloat)constant];
}
PuglStatus
puglRealize(PuglView* view)
{
- PuglInternals* impl = view->impl;
+ PuglInternals* impl = view->impl;
+ const NSScreen* const screen = [NSScreen mainScreen];
+ const double scaleFactor = [screen backingScaleFactor];
+
+ if (view->frame.width == 0.0 && view->frame.height == 0.0) {
+ if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) {
+ return PUGL_BAD_CONFIGURATION;
+ }
+
+ const double screenWidthPx = [screen frame].size.width * scaleFactor;
+ const double screenHeightPx = [screen frame].size.height * scaleFactor;
+
+ view->frame.width = view->defaultWidth;
+ view->frame.height = view->defaultHeight;
+ view->frame.x = screenWidthPx / 2.0 - view->frame.width / 2.0;
+ view->frame.y = screenHeightPx / 2.0 - view->frame.height / 2.0;
+ }
+
+ const NSRect framePx = rectToNsRect(view->frame);
+ const NSRect framePt = NSMakeRect(framePx.origin.x / scaleFactor,
+ framePx.origin.y / scaleFactor,
+ framePx.size.width / scaleFactor,
+ framePx.size.height / scaleFactor);
// Create wrapper view to handle input
impl->wrapperView = [PuglWrapperView alloc];
@@ -763,8 +892,7 @@ puglRealize(PuglView* view)
impl->wrapperView->userTimers = [[NSMutableDictionary alloc] init];
impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init];
[impl->wrapperView setAutoresizesSubviews:YES];
- [impl->wrapperView initWithFrame:
- NSMakeRect(0, 0, view->frame.width, view->frame.height)];
+ [impl->wrapperView initWithFrame:framePt];
[impl->wrapperView addConstraint:
puglConstraint(impl->wrapperView, NSLayoutAttributeWidth, view->minWidth)];
[impl->wrapperView addConstraint:
@@ -788,10 +916,6 @@ puglRealize(PuglView* view)
[impl->drawView setHidden:NO];
[[impl->drawView window] makeFirstResponder:impl->wrapperView];
} else {
- const NSRect frame = rectToScreen(
- NSMakeRect(view->frame.x, view->frame.y,
- view->minWidth, view->minHeight));
-
unsigned style = (NSClosableWindowMask |
NSTitledWindowMask |
NSMiniaturizableWindowMask );
@@ -800,7 +924,7 @@ puglRealize(PuglView* view)
}
PuglWindow* window = [[[PuglWindow alloc]
- initWithContentRect:frame
+ initWithContentRect:rectToScreen([NSScreen mainScreen], framePt)
styleMask:style
backing:NSBackingStoreBuffered
defer:NO
@@ -817,7 +941,8 @@ puglRealize(PuglView* view)
}
if (view->minWidth || view->minHeight) {
- [window setContentMinSize:NSMakeSize(view->minWidth,
+ [window setContentMinSize:sizePoints(view,
+ view->minWidth,
view->minHeight)];
}
impl->window = window;
@@ -826,10 +951,13 @@ puglRealize(PuglView* view)
initWithPuglWindow:window];
if (view->minAspectX && view->minAspectY) {
- [window setContentAspectRatio:NSMakeSize(view->minAspectX,
+ [window setContentAspectRatio:sizePoints(view,
+ view->minAspectX,
view->minAspectY)];
}
+ puglSetFrame(view, view->frame);
+
[window setContentView:impl->wrapperView];
[view->world->impl->app activateIgnoringOtherApps:YES];
[window makeFirstResponder:impl->wrapperView];
@@ -1066,8 +1194,9 @@ puglPostRedisplay(PuglView* view)
PuglStatus
puglPostRedisplayRect(PuglView* view, const PuglRect rect)
{
- [view->impl->drawView setNeedsDisplayInRect:
- NSMakeRect(rect.x, rect.y, rect.width, rect.height)];
+ const NSRect rectPx = rectToNsRect(rect);
+
+ [view->impl->drawView setNeedsDisplayInRect:nsRectToPoints(view, rectPx)];
return PUGL_SUCCESS;
}
@@ -1103,32 +1232,59 @@ puglSetFrame(PuglView* view, const PuglRect frame)
// Update view frame to exactly the requested frame in Pugl coordinates
view->frame = frame;
- const NSRect rect = NSMakeRect(frame.x, frame.y, frame.width, frame.height);
+ const NSRect framePx = rectToNsRect(frame);
+ const NSRect framePt = nsRectToPoints(view, framePx);
if (impl->window) {
// Resize window to fit new content rect
- const NSRect windowFrame = [
- impl->window frameRectForContentRect:rectToScreen(rect)];
+ const NSRect screenPt = rectToScreen(viewScreen(view), framePt);
+ const NSRect winFrame = [impl->window frameRectForContentRect:screenPt];
- [impl->window setFrame:windowFrame display:NO];
+ [impl->window setFrame:winFrame display:NO];
}
// Resize views
- const NSRect drawRect = NSMakeRect(0, 0, frame.width, frame.height);
- [impl->wrapperView setFrame:(impl->window ? drawRect : rect)];
- [impl->drawView setFrame:drawRect];
+ const NSRect sizePx = NSMakeRect(0, 0, frame.width, frame.height);
+ const NSRect sizePt = [impl->drawView convertRectFromBacking:sizePx];
+
+ [impl->wrapperView setFrame:(impl->window ? sizePt : framePt)];
+ [impl->drawView setFrame:sizePt];
return PUGL_SUCCESS;
}
PuglStatus
+puglSetDefaultSize(PuglView* const view, const int width, const int height)
+{
+ view->defaultWidth = width;
+ view->defaultHeight = height;
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
puglSetMinSize(PuglView* const view, const int width, const int height)
{
view->minWidth = width;
view->minHeight = height;
if (view->impl->window && (view->minWidth || view->minHeight)) {
- [view->impl->window
- setContentMinSize:NSMakeSize(view->minWidth, view->minHeight)];
+ [view->impl->window setContentMinSize:sizePoints(view,
+ view->minWidth,
+ view->minHeight)];
+ }
+
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglSetMaxSize(PuglView* const view, const int width, const int height)
+{
+ view->maxWidth = width;
+ view->maxHeight = height;
+
+ if (view->impl->window && (view->maxWidth || view->maxHeight)) {
+ [view->impl->window setContentMaxSize:sizePoints(view,
+ view->maxWidth,
+ view->maxHeight)];
}
return PUGL_SUCCESS;
@@ -1147,13 +1303,31 @@ puglSetAspectRatio(PuglView* const view,
view->maxAspectY = maxY;
if (view->impl->window && view->minAspectX && view->minAspectY) {
- [view->impl->window setContentAspectRatio:NSMakeSize(view->minAspectX,
+ [view->impl->window setContentAspectRatio:sizePoints(view,
+ view->minAspectX,
view->minAspectY)];
}
return PUGL_SUCCESS;
}
+PuglStatus
+puglSetTransientFor(PuglView* view, PuglNativeView parent)
+{
+ view->transientParent = parent;
+
+ if (view->impl->window) {
+ NSWindow* parentWindow = [(NSView*)parent window];
+ if (parentWindow) {
+ [parentWindow addChildWindow:view->impl->window
+ ordered:NSWindowAbove];
+ return PUGL_SUCCESS;
+ }
+ }
+
+ return PUGL_FAILURE;
+}
+
const void*
puglGetClipboard(PuglView* const view,
const char** const type,
@@ -1171,6 +1345,47 @@ puglGetClipboard(PuglView* const view,
return puglGetInternalClipboard(view, type, len);
}
+static NSCursor*
+puglGetNsCursor(const PuglCursor cursor)
+{
+ switch (cursor) {
+ case PUGL_CURSOR_ARROW:
+ return [NSCursor arrowCursor];
+ case PUGL_CURSOR_CARET:
+ return [NSCursor IBeamCursor];
+ case PUGL_CURSOR_CROSSHAIR:
+ return [NSCursor crosshairCursor];
+ case PUGL_CURSOR_HAND:
+ return [NSCursor pointingHandCursor];
+ case PUGL_CURSOR_NO:
+ return [NSCursor operationNotAllowedCursor];
+ case PUGL_CURSOR_LEFT_RIGHT:
+ return [NSCursor resizeLeftRightCursor];
+ case PUGL_CURSOR_UP_DOWN:
+ return [NSCursor resizeUpDownCursor];
+ }
+
+ return NULL;
+}
+
+PuglStatus
+puglSetCursor(PuglView* view, PuglCursor cursor)
+{
+ PuglInternals* const impl = view->impl;
+ NSCursor* const cur = puglGetNsCursor(cursor);
+ if (!cur) {
+ return PUGL_FAILURE;
+ }
+
+ impl->cursor = cur;
+
+ if (impl->mouseTracked) {
+ [cur set];
+ }
+
+ return PUGL_SUCCESS;
+}
+
PuglStatus
puglSetClipboard(PuglView* const view,
const char* const type,
@@ -1180,6 +1395,10 @@ puglSetClipboard(PuglView* const view,
NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard];
const char* const str = (const char*)data;
+ if (type && strcmp(type, "text/plain")) {
+ return PUGL_UNSUPPORTED_TYPE;
+ }
+
PuglStatus st = puglSetInternalClipboard(view, type, data, len);
if (st) {
return st;
diff --git a/pugl/pugl/detail/mac_cairo.m b/pugl/pugl/detail/mac_cairo.m
index 51c1c13b..18209d91 100644
--- a/pugl/pugl/detail/mac_cairo.m
+++ b/pugl/pugl/detail/mac_cairo.m
@@ -1,5 +1,5 @@
/*
- Copyright 2019 David Robillard <http://drobilla.net>
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,13 +15,14 @@
*/
/**
- @file mac_cairo.m Cairo graphics backend for MacOS.
+ @file mac_cairo.m
+ @brief Cairo graphics backend for MacOS.
*/
#include "pugl/detail/implementation.h"
#include "pugl/detail/mac.h"
+#include "pugl/detail/stub.h"
#include "pugl/pugl_cairo.h"
-#include "pugl/pugl_stub.h"
#include <cairo-quartz.h>
@@ -30,6 +31,9 @@
#include <assert.h>
@interface PuglCairoView : NSView
+@end
+
+@implementation PuglCairoView
{
@public
PuglView* puglview;
@@ -37,13 +41,11 @@
cairo_t* cr;
}
-@end
-
-@implementation PuglCairoView
-
- (id) initWithFrame:(NSRect)frame
{
- return (self = [super initWithFrame:frame]);
+ self = [super initWithFrame:frame];
+
+ return self;
}
- (void) resizeWithOldSuperviewSize:(NSSize)oldSize
@@ -69,7 +71,7 @@ puglMacCairoCreate(PuglView* view)
PuglCairoView* drawView = [PuglCairoView alloc];
drawView->puglview = view;
- [drawView initWithFrame:NSMakeRect(0, 0, view->frame.width, view->frame.height)];
+ [drawView initWithFrame:[impl->wrapperView bounds]];
if (view->hints[PUGL_RESIZABLE]) {
[drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
} else {
@@ -103,10 +105,17 @@ puglMacCairoEnter(PuglView* view, const PuglEventExpose* expose)
assert(!drawView->surface);
assert(!drawView->cr);
+ const double scale = 1.0 / [[NSScreen mainScreen] backingScaleFactor];
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
+ const CGSize sizePx = {view->frame.width, view->frame.height};
+ const CGSize sizePt = CGContextConvertSizeToUserSpace(context, sizePx);
+
+ // Convert coordinates to standard Cairo space
+ CGContextTranslateCTM(context, 0.0, -sizePt.height);
+ CGContextScaleCTM(context, scale, -scale);
drawView->surface = cairo_quartz_surface_create_for_cg_context(
- context, view->frame.width, view->frame.height);
+ context, (unsigned)sizePx.width, (unsigned)sizePx.height);
drawView->cr = cairo_create(drawView->surface);
diff --git a/pugl/pugl/detail/mac_gl.m b/pugl/pugl/detail/mac_gl.m
index eda43711..4bf6fc11 100644
--- a/pugl/pugl/detail/mac_gl.m
+++ b/pugl/pugl/detail/mac_gl.m
@@ -1,5 +1,5 @@
/*
- Copyright 2019-2020 David Robillard <http://drobilla.net>
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,28 +15,28 @@
*/
/**
- @file mac_gl.m OpenGL graphics backend for MacOS.
+ @file mac_gl.m
+ @brief OpenGL graphics backend for MacOS.
*/
#include "pugl/detail/implementation.h"
#include "pugl/detail/mac.h"
+#include "pugl/detail/stub.h"
#include "pugl/pugl_gl.h"
-#include "pugl/pugl_stub.h"
#ifndef __MAC_10_10
# define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core
#endif
@interface PuglOpenGLView : NSOpenGLView
+@end
+
+@implementation PuglOpenGLView
{
@public
PuglView* puglview;
}
-@end
-
-@implementation PuglOpenGLView
-
- (id) initWithFrame:(NSRect)frame
{
const bool compat = puglview->hints[PUGL_USE_COMPAT_PROFILE];
@@ -69,6 +69,8 @@
self = [super initWithFrame:frame];
}
+ [self setWantsBestResolutionOpenGLSurface:YES];
+
if (self) {
[[self openGLContext] makeCurrentContext];
[self reshape];
@@ -97,11 +99,9 @@ puglMacGlCreate(PuglView* view)
{
PuglInternals* impl = view->impl;
PuglOpenGLView* drawView = [PuglOpenGLView alloc];
- const NSRect rect = NSMakeRect(
- 0, 0, view->frame.width, view->frame.height);
drawView->puglview = view;
- [drawView initWithFrame:rect];
+ [drawView initWithFrame:[impl->wrapperView bounds]];
if (view->hints[PUGL_RESIZABLE]) {
[drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
} else {
diff --git a/pugl/pugl/detail/mac_stub.m b/pugl/pugl/detail/mac_stub.m
index 71a54b88..8271735c 100644
--- a/pugl/pugl/detail/mac_stub.m
+++ b/pugl/pugl/detail/mac_stub.m
@@ -1,5 +1,5 @@
/*
- Copyright 2019-2020 David Robillard <http://drobilla.net>
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,25 +15,26 @@
*/
/**
- @file mac_stub.m Stub graphics backend for MacOS.
+ @file mac_stub.m
+ @brief Stub graphics backend for MacOS.
*/
#include "pugl/detail/implementation.h"
#include "pugl/detail/mac.h"
+#include "pugl/detail/stub.h"
#include "pugl/pugl_stub.h"
#import <Cocoa/Cocoa.h>
@interface PuglStubView : NSView
+@end
+
+@implementation PuglStubView
{
@public
PuglView* puglview;
}
-@end
-
-@implementation PuglStubView
-
- (void) resizeWithOldSuperviewSize:(NSSize)oldSize
{
PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
diff --git a/pugl/pugl/detail/stub.h b/pugl/pugl/detail/stub.h
new file mode 100644
index 00000000..acd31813
--- /dev/null
+++ b/pugl/pugl/detail/stub.h
@@ -0,0 +1,75 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file stub.h
+ @brief Definition of generic stub backend functions.
+*/
+
+#ifndef PUGL_DETAIL_STUB_H
+#define PUGL_DETAIL_STUB_H
+
+#include "pugl/pugl.h"
+
+PUGL_BEGIN_DECLS
+
+static inline PuglStatus
+puglStubConfigure(PuglView* view)
+{
+ (void)view;
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglStubCreate(PuglView* view)
+{
+ (void)view;
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglStubDestroy(PuglView* view)
+{
+ (void)view;
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglStubEnter(PuglView* view, const PuglEventExpose* expose)
+{
+ (void)view;
+ (void)expose;
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglStubLeave(PuglView* view, const PuglEventExpose* expose)
+{
+ (void)view;
+ (void)expose;
+ return PUGL_SUCCESS;
+}
+
+static inline void*
+puglStubGetContext(PuglView* view)
+{
+ (void)view;
+ return NULL;
+}
+
+PUGL_END_DECLS
+
+#endif // PUGL_DETAIL_STUB_H
diff --git a/pugl/pugl/detail/types.h b/pugl/pugl/detail/types.h
index eb450e14..6f676fdb 100644
--- a/pugl/pugl/detail/types.h
+++ b/pugl/pugl/detail/types.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file types.h Shared internal type definitions.
+ @file types.h
+ @brief Shared internal type definitions.
*/
#ifndef PUGL_DETAIL_TYPES_H
@@ -36,22 +37,22 @@
# define PUGL_UNUSED(name) name
#endif
-/** Platform-specific world internals. */
+/// Platform-specific world internals
typedef struct PuglWorldInternalsImpl PuglWorldInternals;
-/** Platform-specific view internals. */
+/// Platform-specific view internals
typedef struct PuglInternalsImpl PuglInternals;
-/** View hints. */
+/// View hints
typedef int PuglHints[PUGL_NUM_VIEW_HINTS];
-/** Blob of arbitrary data. */
+/// Blob of arbitrary data
typedef struct {
- void* data; //!< Dynamically allocated data
- size_t len; //!< Length of data in bytes
+ void* data; ///< Dynamically allocated data
+ size_t len; ///< Length of data in bytes
} PuglBlob;
-/** Cross-platform view definition. */
+/// Cross-platform view definition
struct PuglViewImpl {
PuglWorld* world;
const PuglBackend* backend;
@@ -60,13 +61,18 @@ struct PuglViewImpl {
PuglEventFunc eventFunc;
char* title;
PuglBlob clipboard;
+ PuglBlob clipboardType;
PuglNativeView parent;
uintptr_t transientParent;
- PuglHints hints;
PuglRect frame;
PuglEventConfigure lastConfigure;
+ PuglHints hints;
+ int defaultWidth;
+ int defaultHeight;
int minWidth;
int minHeight;
+ int maxWidth;
+ int maxHeight;
int minAspectX;
int minAspectY;
int maxAspectX;
@@ -74,7 +80,7 @@ struct PuglViewImpl {
bool visible;
};
-/** Cross-platform world definition. */
+/// Cross-platform world definition
struct PuglWorldImpl {
PuglWorldInternals* impl;
PuglWorldHandle handle;
@@ -86,27 +92,27 @@ struct PuglWorldImpl {
PuglLogLevel logLevel;
};
-/** Opaque surface used by graphics backend. */
+/// Opaque surface used by graphics backend
typedef void PuglSurface;
-/** Graphics backend interface. */
+/// Graphics backend interface
struct PuglBackendImpl {
- /** Get visual information from display and setup view as necessary. */
+ /// Get visual information from display and setup view as necessary
PuglStatus (*configure)(PuglView*);
- /** Create surface and drawing context. */
+ /// Create surface and drawing context
PuglStatus (*create)(PuglView*);
- /** Destroy surface and drawing context. */
+ /// Destroy surface and drawing context
PuglStatus (*destroy)(PuglView*);
- /** Enter drawing context, for drawing if expose is non-null. */
+ /// Enter drawing context, for drawing if expose is non-null
PuglStatus (*enter)(PuglView*, const PuglEventExpose*);
- /** Leave drawing context, after drawing if expose is non-null. */
+ /// Leave drawing context, after drawing if expose is non-null
PuglStatus (*leave)(PuglView*, const PuglEventExpose*);
- /** Return the puglGetContext() handle for the application, if any. */
+ /// Return the puglGetContext() handle for the application, if any
void* (*getContext)(PuglView*);
};
diff --git a/pugl/pugl/detail/win.c b/pugl/pugl/detail/win.c
index 44ba6cd9..ce81edeb 100644
--- a/pugl/pugl/detail/win.c
+++ b/pugl/pugl/detail/win.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,12 +15,14 @@
*/
/**
- @file win.c Windows implementation.
+ @file win.c
+ @brief Windows implementation.
*/
#include "pugl/detail/win.h"
#include "pugl/detail/implementation.h"
+#include "pugl/detail/stub.h"
#include "pugl/pugl.h"
#include "pugl/pugl_stub.h"
@@ -189,6 +191,8 @@ puglRealize(PuglView* view)
puglSetWindowTitle(view, view->title);
}
+ view->impl->cursor = LoadCursor(NULL, IDC_ARROW);
+
puglSetFrame(view, view->frame);
SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);
@@ -342,7 +346,7 @@ initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam)
event->scroll.dy = 0;
}
-/** Return the code point for buf, or the replacement character on error. */
+/// Return the code point for buf, or the replacement character on error
static uint32_t
puglDecodeUTF16(const wchar_t* buf, const int len)
{
@@ -539,6 +543,11 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
}
switch (message) {
+ case WM_SETCURSOR:
+ if (LOWORD(lParam) == HTCLIENT) {
+ SetCursor(view->impl->cursor);
+ }
+ break;
case WM_SHOWWINDOW:
if (wParam) {
handleConfigure(view, &event);
@@ -577,8 +586,9 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
RedrawWindow(view->impl->hwnd, NULL, NULL,
RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT);
} else if (wParam >= PUGL_USER_TIMER_MIN) {
- const PuglEventTimer ev = {PUGL_TIMER, 0, wParam - PUGL_USER_TIMER_MIN};
- puglDispatchEvent(view, (const PuglEvent*)&ev);
+ PuglEvent ev = {{PUGL_TIMER, 0}};
+ ev.timer.id = wParam - PUGL_USER_TIMER_MIN;
+ puglDispatchEvent(view, &ev);
}
break;
case WM_EXITSIZEMOVE:
@@ -591,6 +601,10 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
mmi = (MINMAXINFO*)lParam;
mmi->ptMinTrackSize.x = view->minWidth;
mmi->ptMinTrackSize.y = view->minHeight;
+ if (view->maxWidth > 0 && view->maxHeight > 0) {
+ mmi->ptMaxTrackSize.x = view->maxWidth;
+ mmi->ptMaxTrackSize.y = view->maxHeight;
+ }
break;
case WM_PAINT:
GetUpdateRect(view->impl->hwnd, &rect, false);
@@ -599,7 +613,6 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
event.expose.y = rect.top;
event.expose.width = rect.right - rect.left;
event.expose.height = rect.bottom - rect.top;
- event.expose.count = 0;
break;
case WM_ERASEBKGND:
return true;
@@ -627,7 +640,6 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
event.motion.xRoot = pt.x;
event.motion.yRoot = pt.y;
event.motion.state = getModifiers();
- event.motion.isHint = false;
break;
case WM_MOUSELEAVE:
GetCursorPos(&pt);
@@ -656,10 +668,16 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
case WM_MOUSEWHEEL:
initScrollEvent(&event, view, lParam);
event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ event.scroll.direction = (event.scroll.dy > 0
+ ? PUGL_SCROLL_UP
+ : PUGL_SCROLL_DOWN);
break;
case WM_MOUSEHWHEEL:
initScrollEvent(&event, view, lParam);
event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ event.scroll.direction = (event.scroll.dx > 0
+ ? PUGL_SCROLL_RIGHT
+ : PUGL_SCROLL_LEFT);
break;
case WM_KEYDOWN:
if (!ignoreKeyEvent(view, lParam)) {
@@ -958,6 +976,14 @@ puglSetFrame(PuglView* view, const PuglRect frame)
}
PuglStatus
+puglSetDefaultSize(PuglView* const view, const int width, const int height)
+{
+ view->defaultWidth = width;
+ view->defaultHeight = height;
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
puglSetMinSize(PuglView* const view, const int width, const int height)
{
view->minWidth = width;
@@ -966,6 +992,14 @@ puglSetMinSize(PuglView* const view, const int width, const int height)
}
PuglStatus
+puglSetMaxSize(PuglView* const view, const int width, const int height)
+{
+ view->maxWidth = width;
+ view->maxHeight = height;
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
puglSetAspectRatio(PuglView* const view,
const int minX,
const int minY,
@@ -979,6 +1013,23 @@ puglSetAspectRatio(PuglView* const view,
return PUGL_SUCCESS;
}
+PuglStatus
+puglSetTransientFor(PuglView* view, PuglNativeView parent)
+{
+ if (view->parent) {
+ return PUGL_FAILURE;
+ }
+
+ view->transientParent = parent;
+
+ if (view->impl->hwnd) {
+ SetWindowLongPtr(view->impl->hwnd, GWLP_HWNDPARENT, (LONG_PTR)parent);
+ return GetLastError() == NO_ERROR ? PUGL_SUCCESS : PUGL_FAILURE;
+ }
+
+ return PUGL_SUCCESS;
+}
+
const void*
puglGetClipboard(PuglView* const view,
const char** const type,
@@ -1014,6 +1065,10 @@ puglSetClipboard(PuglView* const view,
{
PuglInternals* const impl = view->impl;
+ if (type && strcmp(type, "text/plain")) {
+ return PUGL_UNSUPPORTED_TYPE;
+ }
+
PuglStatus st = puglSetInternalClipboard(view, type, data, len);
if (st) {
return st;
@@ -1064,7 +1119,40 @@ puglWinStubLeave(PuglView* view, const PuglEventExpose* expose)
if (expose) {
PAINTSTRUCT ps;
EndPaint(view->impl->hwnd, &ps);
- SwapBuffers(view->impl->hdc);
+ }
+
+ return PUGL_SUCCESS;
+}
+
+static const char* const cursor_ids[] = {
+ IDC_ARROW, // ARROW
+ IDC_IBEAM, // CARET
+ IDC_CROSS, // CROSSHAIR
+ IDC_HAND, // HAND
+ IDC_NO, // NO
+ IDC_SIZEWE, // LEFT_RIGHT
+ IDC_SIZENS, // UP_DOWN
+};
+
+PuglStatus
+puglSetCursor(PuglView* view, PuglCursor cursor)
+{
+ PuglInternals* const impl = view->impl;
+ const unsigned index = (unsigned)cursor;
+ const unsigned count = sizeof(cursor_ids) / sizeof(cursor_ids[0]);
+
+ if (index >= count) {
+ return PUGL_BAD_PARAMETER;
+ }
+
+ const HCURSOR cur = LoadCursor(NULL, cursor_ids[index]);
+ if (!cur) {
+ return PUGL_FAILURE;
+ }
+
+ impl->cursor = cur;
+ if (impl->mouseTracked) {
+ SetCursor(cur);
}
return PUGL_SUCCESS;
diff --git a/pugl/pugl/detail/win.h b/pugl/pugl/detail/win.h
index 949fa901..1b9b0c4f 100644
--- a/pugl/pugl/detail/win.h
+++ b/pugl/pugl/detail/win.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file win.h Shared definitions for Windows implementation.
+ @file win.h
+ @brief Shared definitions for Windows implementation.
*/
#include "pugl/detail/implementation.h"
@@ -34,6 +35,7 @@ struct PuglInternalsImpl {
PuglWinPFD pfd;
int pfId;
HWND hwnd;
+ HCURSOR cursor;
HDC hdc;
PuglSurface* surface;
DWORD refreshRate;
@@ -87,15 +89,35 @@ puglWinGetWindowExFlags(const PuglView* const view)
}
static inline PuglStatus
-puglWinCreateWindow(const PuglView* const view,
- const char* const title,
- HWND* const hwnd,
- HDC* const hdc)
+puglWinCreateWindow(PuglView* const view,
+ const char* const title,
+ HWND* const hwnd,
+ HDC* const hdc)
{
const char* className = (const char*)view->world->className;
const unsigned winFlags = puglWinGetWindowFlags(view);
const unsigned winExFlags = puglWinGetWindowExFlags(view);
+ if (view->frame.width == 0.0 && view->frame.height == 0.0) {
+ if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) {
+ return PUGL_BAD_CONFIGURATION;
+ }
+
+ RECT desktopRect;
+ GetClientRect(GetDesktopWindow(), &desktopRect);
+
+ const int screenWidth = desktopRect.right - desktopRect.left;
+ const int screenHeight = desktopRect.bottom - desktopRect.top;
+
+ view->frame.width = view->defaultWidth;
+ view->frame.height = view->defaultHeight;
+ view->frame.x = screenWidth / 2.0 - view->frame.width / 2.0;
+ view->frame.y = screenHeight / 2.0 - view->frame.height / 2.0;
+ }
+
+ // The meaning of "parent" depends on the window type (WS_CHILD)
+ PuglNativeView parent = view->parent ? view->parent : view->transientParent;
+
// Calculate total window size to accommodate requested view size
RECT wr = { (long)view->frame.x, (long)view->frame.y,
(long)view->frame.width, (long)view->frame.height };
@@ -105,7 +127,7 @@ puglWinCreateWindow(const PuglView* const view,
if (!(*hwnd = CreateWindowEx(winExFlags, className, title, winFlags,
CW_USEDEFAULT, CW_USEDEFAULT,
wr.right-wr.left, wr.bottom-wr.top,
- (HWND)view->parent, NULL, NULL, NULL))) {
+ (HWND)parent, NULL, NULL, NULL))) {
return PUGL_REALIZE_FAILED;
} else if (!(*hdc = GetDC(*hwnd))) {
DestroyWindow(*hwnd);
diff --git a/pugl/pugl/detail/win_cairo.c b/pugl/pugl/detail/win_cairo.c
index a8b371f1..1b9afb93 100644
--- a/pugl/pugl/detail/win_cairo.c
+++ b/pugl/pugl/detail/win_cairo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,13 +15,14 @@
*/
/**
- @file win_cairo.c Cairo graphics backend for Windows.
+ @file win_cairo.c
+ @brief Cairo graphics backend for Windows.
*/
+#include "pugl/detail/stub.h"
#include "pugl/detail/types.h"
#include "pugl/detail/win.h"
#include "pugl/pugl_cairo.h"
-#include "pugl/pugl_stub.h"
#include <cairo-win32.h>
#include <cairo.h>
@@ -152,7 +153,6 @@ puglWinCairoLeave(PuglView* view, const PuglEventExpose* expose)
PAINTSTRUCT ps;
EndPaint(view->impl->hwnd, &ps);
- SwapBuffers(view->impl->hdc);
}
return PUGL_SUCCESS;
diff --git a/pugl/pugl/detail/win_gl.c b/pugl/pugl/detail/win_gl.c
index f5acfd62..8cdad760 100644
--- a/pugl/pugl/detail/win_gl.c
+++ b/pugl/pugl/detail/win_gl.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,13 +15,14 @@
*/
/**
- @file win_gl.c OpenGL graphics backend for Windows.
+ @file win_gl.c
+ @brief OpenGL graphics backend for Windows.
*/
+#include "pugl/detail/stub.h"
#include "pugl/detail/types.h"
#include "pugl/detail/win.h"
#include "pugl/pugl_gl.h"
-#include "pugl/pugl_stub.h"
#include <windows.h>
@@ -35,7 +36,6 @@
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DOUBLE_BUFFER_ARB 0x2011
#define WGL_PIXEL_TYPE_ARB 0x2013
-#define WGL_COLOR_BITS_ARB 0x2014
#define WGL_RED_BITS_ARB 0x2015
#define WGL_GREEN_BITS_ARB 0x2017
#define WGL_BLUE_BITS_ARB 0x2019
@@ -49,7 +49,6 @@
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
-#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
#define WGL_CONTEXT_FLAGS_ARB 0x2094
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
@@ -295,7 +294,7 @@ puglGetProcAddress(const char* name)
}
const PuglBackend*
-puglGlBackend()
+puglGlBackend(void)
{
static const PuglBackend backend = {puglWinGlConfigure,
puglWinGlCreate,
diff --git a/pugl/pugl/detail/x11.c b/pugl/pugl/detail/x11.c
index e3fb2649..01058a30 100644
--- a/pugl/pugl/detail/x11.c
+++ b/pugl/pugl/detail/x11.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Copyright 2013 Robin Gareus <robin@gareus.org>
Copyright 2011-2012 Ben Loftis, Harrison Consoles
@@ -17,7 +17,8 @@
*/
/**
- @file x11.c X11 implementation.
+ @file x11.c
+ @brief X11 implementation.
*/
#define _POSIX_C_SOURCE 199309L
@@ -25,6 +26,7 @@
#include "pugl/detail/x11.h"
#include "pugl/detail/implementation.h"
+#include "pugl/detail/stub.h"
#include "pugl/detail/types.h"
#include "pugl/pugl.h"
#include "pugl/pugl_stub.h"
@@ -40,6 +42,11 @@
# include <X11/extensions/syncconst.h>
#endif
+#ifdef HAVE_XCURSOR
+# include <X11/Xcursor/Xcursor.h>
+# include <X11/cursorfont.h>
+#endif
+
#include <sys/select.h>
#include <sys/time.h>
@@ -74,11 +81,11 @@ static bool
puglInitXSync(PuglWorldInternals* impl)
{
#ifdef HAVE_XSYNC
- int syncMajor;
- int syncMinor;
- int errorBase;
- XSyncSystemCounter* counters;
- int numCounters;
+ int syncMajor = 0;
+ int syncMinor = 0;
+ int errorBase = 0;
+ XSyncSystemCounter* counters = NULL;
+ int numCounters = 0;
if (XSyncQueryExtension(impl->display, &impl->syncEventBase, &errorBase) &&
XSyncInitialize(impl->display, &syncMajor, &syncMinor) &&
@@ -121,6 +128,7 @@ puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags)
// Intern the various atoms we will need
impl->atoms.CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0);
impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0);
+ impl->atoms.TARGETS = XInternAtom(display, "TARGETS", 0);
impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0);
impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0);
impl->atoms.PUGL_CLIENT_MSG = XInternAtom(display, "_PUGL_CLIENT_MSG", 0);
@@ -151,7 +159,13 @@ puglGetNativeWorld(PuglWorld* world)
PuglInternals*
puglInitViewInternals(void)
{
- return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+ PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
+
+#ifdef HAVE_XCURSOR
+ impl->cursorShape = XC_arrow;
+#endif
+
+ return impl;
}
static PuglStatus
@@ -192,23 +206,40 @@ puglFindView(PuglWorld* world, const Window window)
return NULL;
}
-static XSizeHints
-getSizeHints(const PuglView* view)
+static PuglStatus
+updateSizeHints(const PuglView* view)
{
+ if (!view->impl->win) {
+ return PUGL_SUCCESS;
+ }
+
+ Display* display = view->world->impl->display;
XSizeHints sizeHints = {0};
if (!view->hints[PUGL_RESIZABLE]) {
- sizeHints.flags = PMinSize|PMaxSize;
- sizeHints.min_width = (int)view->frame.width;
- sizeHints.min_height = (int)view->frame.height;
- sizeHints.max_width = (int)view->frame.width;
- sizeHints.max_height = (int)view->frame.height;
+ sizeHints.flags = PBaseSize | PMinSize | PMaxSize;
+ sizeHints.base_width = (int)view->frame.width;
+ sizeHints.base_height = (int)view->frame.height;
+ sizeHints.min_width = (int)view->frame.width;
+ sizeHints.min_height = (int)view->frame.height;
+ sizeHints.max_width = (int)view->frame.width;
+ sizeHints.max_height = (int)view->frame.height;
} else {
+ if (view->defaultWidth || view->defaultHeight) {
+ sizeHints.flags = PBaseSize;
+ sizeHints.base_width = view->defaultWidth;
+ sizeHints.base_height = view->defaultHeight;
+ }
if (view->minWidth || view->minHeight) {
sizeHints.flags = PMinSize;
sizeHints.min_width = view->minWidth;
sizeHints.min_height = view->minHeight;
}
+ if (view->maxWidth || view->maxHeight) {
+ sizeHints.flags = PMaxSize;
+ sizeHints.max_width = view->maxWidth;
+ sizeHints.max_height = view->maxHeight;
+ }
if (view->minAspectX) {
sizeHints.flags |= PAspect;
sizeHints.min_aspect.x = view->minAspectX;
@@ -218,9 +249,29 @@ getSizeHints(const PuglView* view)
}
}
- return sizeHints;
+ XSetNormalHints(display, view->impl->win, &sizeHints);
+ return PUGL_SUCCESS;
}
+#ifdef HAVE_XCURSOR
+static PuglStatus
+puglDefineCursorShape(PuglView* view, unsigned shape)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWorld* const world = view->world;
+ Display* const display = world->impl->display;
+ const Cursor cur = XcursorShapeLoadCursor(display, shape);
+
+ if (cur) {
+ XDefineCursor(display, impl->win, cur);
+ XFreeCursor(display, cur);
+ return PUGL_SUCCESS;
+ }
+
+ return PUGL_FAILURE;
+}
+#endif
+
PuglStatus
puglRealize(PuglView* view)
{
@@ -234,6 +285,18 @@ puglRealize(PuglView* view)
if (!view->backend || !view->backend->configure) {
return PUGL_BAD_BACKEND;
+ } else if (view->frame.width == 0.0 && view->frame.height == 0.0) {
+ if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) {
+ return PUGL_BAD_CONFIGURATION;
+ }
+
+ const int screenWidth = DisplayWidth(display, impl->screen);
+ const int screenHeight = DisplayHeight(display, impl->screen);
+
+ view->frame.width = view->defaultWidth;
+ view->frame.height = view->defaultHeight;
+ view->frame.x = screenWidth / 2.0 - view->frame.width / 2.0;
+ view->frame.y = screenHeight / 2.0 - view->frame.height / 2.0;
}
PuglStatus st = view->backend->configure(view);
@@ -263,8 +326,7 @@ puglRealize(PuglView* view)
return st;
}
- XSizeHints sizeHints = getSizeHints(view);
- XSetNormalHints(display, win, &sizeHints);
+ updateSizeHints(view);
XClassHint classHint = { world->className, world->className };
XSetClassHint(display, win, &classHint);
@@ -293,6 +355,10 @@ puglRealize(PuglView* view)
"XCreateID failed\n");
}
+#ifdef HAVE_XCURSOR
+ puglDefineCursorShape(view, impl->cursorShape);
+#endif
+
puglDispatchSimpleEvent(view, PUGL_CREATE);
return PUGL_SUCCESS;
@@ -450,10 +516,10 @@ translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
static uint32_t
translateModifiers(const unsigned xstate)
{
- return (((xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0) |
- ((xstate & ControlMask) ? PUGL_MOD_CTRL : 0) |
- ((xstate & Mod1Mask) ? PUGL_MOD_ALT : 0) |
- ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0));
+ return (((xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0u) |
+ ((xstate & ControlMask) ? PUGL_MOD_CTRL : 0u) |
+ ((xstate & Mod1Mask) ? PUGL_MOD_ALT : 0u) |
+ ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0u));
}
static PuglEvent
@@ -500,22 +566,23 @@ translateEvent(PuglView* view, XEvent xevent)
event.expose.y = xevent.xexpose.y;
event.expose.width = xevent.xexpose.width;
event.expose.height = xevent.xexpose.height;
- event.expose.count = xevent.xexpose.count;
break;
case MotionNotify:
event.type = PUGL_MOTION;
- event.motion.time = xevent.xmotion.time / 1e3;
+ event.motion.time = (double)xevent.xmotion.time / 1e3;
event.motion.x = xevent.xmotion.x;
event.motion.y = xevent.xmotion.y;
event.motion.xRoot = xevent.xmotion.x_root;
event.motion.yRoot = xevent.xmotion.y_root;
event.motion.state = translateModifiers(xevent.xmotion.state);
- event.motion.isHint = (xevent.xmotion.is_hint == NotifyHint);
+ if (xevent.xmotion.is_hint == NotifyHint) {
+ event.motion.flags |= PUGL_IS_HINT;
+ }
break;
case ButtonPress:
if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
event.type = PUGL_SCROLL;
- event.scroll.time = xevent.xbutton.time / 1e3;
+ event.scroll.time = (double)xevent.xbutton.time / 1e3;
event.scroll.x = xevent.xbutton.x;
event.scroll.y = xevent.xbutton.y;
event.scroll.xRoot = xevent.xbutton.x_root;
@@ -524,10 +591,22 @@ translateEvent(PuglView* view, XEvent xevent)
event.scroll.dx = 0.0;
event.scroll.dy = 0.0;
switch (xevent.xbutton.button) {
- case 4: event.scroll.dy = 1.0; break;
- case 5: event.scroll.dy = -1.0; break;
- case 6: event.scroll.dx = -1.0; break;
- case 7: event.scroll.dx = 1.0; break;
+ case 4:
+ event.scroll.dy = 1.0;
+ event.scroll.direction = PUGL_SCROLL_UP;
+ break;
+ case 5:
+ event.scroll.dy = -1.0;
+ event.scroll.direction = PUGL_SCROLL_DOWN;
+ break;
+ case 6:
+ event.scroll.dx = -1.0;
+ event.scroll.direction = PUGL_SCROLL_LEFT;
+ break;
+ case 7:
+ event.scroll.dx = 1.0;
+ event.scroll.direction = PUGL_SCROLL_RIGHT;
+ break;
}
// fallthru
}
@@ -537,7 +616,7 @@ translateEvent(PuglView* view, XEvent xevent)
event.button.type = ((xevent.type == ButtonPress)
? PUGL_BUTTON_PRESS
: PUGL_BUTTON_RELEASE);
- event.button.time = xevent.xbutton.time / 1e3;
+ event.button.time = (double)xevent.xbutton.time / 1e3;
event.button.x = xevent.xbutton.x;
event.button.y = xevent.xbutton.y;
event.button.xRoot = xevent.xbutton.x_root;
@@ -551,7 +630,7 @@ translateEvent(PuglView* view, XEvent xevent)
event.type = ((xevent.type == KeyPress)
? PUGL_KEY_PRESS
: PUGL_KEY_RELEASE);
- event.key.time = xevent.xkey.time / 1e3;
+ event.key.time = (double)xevent.xkey.time / 1e3;
event.key.x = xevent.xkey.x;
event.key.y = xevent.xkey.y;
event.key.xRoot = xevent.xkey.x_root;
@@ -564,7 +643,7 @@ translateEvent(PuglView* view, XEvent xevent)
event.type = ((xevent.type == EnterNotify)
? PUGL_POINTER_IN
: PUGL_POINTER_OUT);
- event.crossing.time = xevent.xcrossing.time / 1e3;
+ event.crossing.time = (double)xevent.xcrossing.time / 1e3;
event.crossing.x = xevent.xcrossing.x;
event.crossing.y = xevent.xcrossing.y;
event.crossing.xRoot = xevent.xcrossing.x_root;
@@ -581,7 +660,12 @@ translateEvent(PuglView* view, XEvent xevent)
case FocusIn:
case FocusOut:
event.type = (xevent.type == FocusIn) ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT;
- event.focus.grab = (xevent.xfocus.mode != NotifyNormal);
+ event.focus.mode = PUGL_CROSSING_NORMAL;
+ if (xevent.xfocus.mode == NotifyGrab) {
+ event.focus.mode = PUGL_CROSSING_GRAB;
+ } else if (xevent.xfocus.mode == NotifyUngrab) {
+ event.focus.mode = PUGL_CROSSING_UNGRAB;
+ }
break;
default:
@@ -646,7 +730,8 @@ puglStartTimer(PuglView* view, uintptr_t id, double timeout)
PuglWorldInternals* w = view->world->impl;
Display* const display = w->display;
const XSyncCounter counter = w->serverTimeCounter;
- const XSyncTrigger trigger = {counter, XSyncRelative, value, 0};
+ const XSyncTestType type = XSyncPositiveTransition;
+ const XSyncTrigger trigger = {counter, XSyncRelative, value, type};
XSyncAlarmAttributes attr = {trigger, value, True, XSyncAlarmActive};
const XSyncAlarm alarm = XSyncCreateAlarm(display, 0x17, &attr);
const PuglTimer timer = {alarm, view, id};
@@ -791,7 +876,6 @@ mergeExposeEvents(PuglEvent* dst, const PuglEvent* src)
dst->expose.y = MIN(dst->expose.y, src->expose.y);
dst->expose.width = max_x - dst->expose.x;
dst->expose.height = max_y - dst->expose.y;
- dst->expose.count = MIN(dst->expose.count, src->expose.count);
}
}
@@ -817,8 +901,14 @@ handleSelectionNotify(const PuglWorld* world, PuglView* view)
&left,
&str);
- if (str && fmt == 8 && type == world->impl->atoms.UTF8_STRING &&
- left == 0) {
+ if (str && fmt == 8 && left == 0) {
+ char *type_name = XGetAtomName(world->impl->display, type);
+ if (type_name) {
+ puglSetBlob(&view->clipboardType, type_name, strlen(type_name) + 1);
+ XFree(type_name);
+ } else {
+ puglSetBlob(&view->clipboardType, NULL, 0);
+ }
puglSetBlob(&view->clipboard, str, len);
}
@@ -843,17 +933,35 @@ handleSelectionRequest(const PuglWorld* world,
const char* type = NULL;
size_t len = 0;
const void* data = puglGetInternalClipboard(view, &type, &len);
- if (data && request->selection == world->impl->atoms.CLIPBOARD &&
- request->target == world->impl->atoms.UTF8_STRING) {
- note.property = request->property;
- XChangeProperty(world->impl->display,
- note.requestor,
- note.property,
- note.target,
- 8,
- PropModeReplace,
- (const uint8_t*)data,
- (int)len);
+ if (data && request->selection == world->impl->atoms.CLIPBOARD) {
+ const Atom types [2] = {
+ world->impl->atoms.TARGETS,
+ XInternAtom(world->impl->display, type, 0)
+ };
+
+ if (request->target == world->impl->atoms.TARGETS) {
+ note.property = request->property;
+ XChangeProperty(world->impl->display,
+ note.requestor,
+ note.property,
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ (const uint8_t*)types,
+ (int)(sizeof(types) / sizeof(Atom)));
+ } else if (request->target == types[1]) {
+ note.property = request->property;
+ XChangeProperty(world->impl->display,
+ note.requestor,
+ note.property,
+ note.target,
+ 8,
+ PropModeReplace,
+ (const uint8_t*)data,
+ (int)len);
+ } else {
+ note.property = None;
+ }
} else {
note.property = None;
}
@@ -896,8 +1004,10 @@ handleTimerEvent(PuglWorld* world, XEvent xevent)
for (size_t i = 0; i < world->impl->numTimers; ++i) {
if (world->impl->timers[i].alarm == notify->alarm) {
- const PuglEventTimer ev = {PUGL_TIMER, 0, world->impl->timers[i].id};
- puglDispatchEvent(world->impl->timers[i].view, (const PuglEvent*)&ev);
+ PuglEvent event = {{PUGL_TIMER, 0}};
+ event.timer.id = world->impl->timers[i].id;
+ puglDispatchEvent(world->impl->timers[i].view,
+ (const PuglEvent*)&event);
}
}
@@ -949,10 +1059,10 @@ puglDispatchX11Events(PuglWorld* world)
} else if (xevent.type == FocusOut) {
XUnsetICFocus(impl->xic);
} else if (xevent.type == SelectionClear) {
+ puglSetBlob(&view->clipboardType, NULL, 0);
puglSetBlob(&view->clipboard, NULL, 0);
} else if (xevent.type == SelectionNotify &&
xevent.xselection.selection == atoms->CLIPBOARD &&
- xevent.xselection.target == atoms->UTF8_STRING &&
xevent.xselection.property == XA_PRIMARY) {
handleSelectionNotify(world, view);
} else if (xevent.type == SelectionRequest) {
@@ -1033,7 +1143,8 @@ puglGetTime(const PuglWorld* world)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
- return ((double)ts.tv_sec + ts.tv_nsec / 1000000000.0) - world->startTime;
+ return ((double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0) -
+ world->startTime;
}
PuglStatus
@@ -1048,7 +1159,7 @@ PuglStatus
puglPostRedisplayRect(PuglView* view, PuglRect rect)
{
const PuglEventExpose event = {
- PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height, 0
+ PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height
};
if (view->world->impl->dispatchingEvents) {
@@ -1102,19 +1213,27 @@ puglSetFrame(PuglView* view, const PuglRect frame)
}
PuglStatus
-puglSetMinSize(PuglView* const view, const int width, const int height)
+puglSetDefaultSize(PuglView* const view, const int width, const int height)
{
- Display* display = view->world->impl->display;
+ view->defaultWidth = width;
+ view->defaultHeight = height;
+ return updateSizeHints(view);
+}
+PuglStatus
+puglSetMinSize(PuglView* const view, const int width, const int height)
+{
view->minWidth = width;
view->minHeight = height;
+ return updateSizeHints(view);
+}
- if (view->impl->win) {
- XSizeHints sizeHints = getSizeHints(view);
- XSetNormalHints(display, view->impl->win, &sizeHints);
- }
-
- return PUGL_SUCCESS;
+PuglStatus
+puglSetMaxSize(PuglView* const view, const int width, const int height)
+{
+ view->minWidth = width;
+ view->minHeight = height;
+ return updateSizeHints(view);
}
PuglStatus
@@ -1124,19 +1243,12 @@ puglSetAspectRatio(PuglView* const view,
const int maxX,
const int maxY)
{
- Display* display = view->world->impl->display;
-
view->minAspectX = minX;
view->minAspectY = minY;
view->maxAspectX = maxX;
view->maxAspectY = maxY;
- if (view->impl->win) {
- XSizeHints sizeHints = getSizeHints(view);
- XSetNormalHints(display, view->impl->win, &sizeHints);
- }
-
- return PUGL_SUCCESS;
+ return updateSizeHints(view);
}
PuglStatus
@@ -1165,12 +1277,17 @@ puglGetClipboard(PuglView* const view,
const Window owner = XGetSelectionOwner(impl->display, atoms->CLIPBOARD);
if (owner != None && owner != impl->win) {
// Clear internal selection
+ puglSetBlob(&view->clipboardType, NULL, 0);
puglSetBlob(&view->clipboard, NULL, 0);
+ const Atom type_atom = type && *type
+ ? XInternAtom(impl->display, *type, 0)
+ : atoms->UTF8_STRING;
+
// Request selection from the owner
XConvertSelection(impl->display,
atoms->CLIPBOARD,
- atoms->UTF8_STRING,
+ type_atom,
XA_PRIMARY,
impl->win,
CurrentTime);
@@ -1202,6 +1319,44 @@ puglSetClipboard(PuglView* const view,
return PUGL_SUCCESS;
}
+#ifdef HAVE_XCURSOR
+static const unsigned cursor_nums[] = {
+ XC_arrow, // ARROW
+ XC_xterm, // CARET
+ XC_crosshair, // CROSSHAIR
+ XC_hand2, // HAND
+ XC_pirate, // NO
+ XC_sb_h_double_arrow, // LEFT_RIGHT
+ XC_sb_v_double_arrow, // UP_DOWN
+};
+#endif
+
+PuglStatus
+puglSetCursor(PuglView* view, PuglCursor cursor)
+{
+#ifdef HAVE_XCURSOR
+ PuglInternals* const impl = view->impl;
+ const unsigned index = (unsigned)cursor;
+ const unsigned count = sizeof(cursor_nums) / sizeof(cursor_nums[0]);
+ if (index >= count) {
+ return PUGL_BAD_PARAMETER;
+ }
+
+ const unsigned shape = cursor_nums[index];
+ if (!impl->win || impl->cursorShape == shape) {
+ return PUGL_SUCCESS;
+ }
+
+ impl->cursorShape = cursor_nums[index];
+
+ return puglDefineCursorShape(view, impl->cursorShape);
+#else
+ (void)view;
+ (void)cursor;
+ return PUGL_FAILURE;
+#endif
+}
+
const PuglBackend*
puglStubBackend(void)
{
diff --git a/pugl/pugl/detail/x11.h b/pugl/pugl/detail/x11.h
index 6b7a1508..cedba560 100644
--- a/pugl/pugl/detail/x11.h
+++ b/pugl/pugl/detail/x11.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file x11.h Shared definitions for X11 implementation.
+ @file x11.h
+ @brief Shared definitions for X11 implementation.
*/
#include "pugl/detail/types.h"
@@ -32,6 +33,7 @@
typedef struct {
Atom CLIPBOARD;
Atom UTF8_STRING;
+ Atom TARGETS;
Atom WM_PROTOCOLS;
Atom WM_DELETE_WINDOW;
Atom PUGL_CLIENT_MSG;
@@ -43,7 +45,7 @@ typedef struct {
typedef struct {
XID alarm;
PuglView* view;
- uint64_t id;
+ uintptr_t id;
} PuglTimer;
struct PuglWorldInternalsImpl {
@@ -60,13 +62,16 @@ struct PuglWorldInternalsImpl {
struct PuglInternalsImpl {
Display* display;
- int screen;
XVisualInfo* vi;
Window win;
XIC xic;
PuglSurface* surface;
PuglEvent pendingConfigure;
PuglEvent pendingExpose;
+ int screen;
+#ifdef HAVE_XCURSOR
+ unsigned cursorShape;
+#endif
};
static inline PuglStatus
diff --git a/pugl/pugl/detail/x11_cairo.c b/pugl/pugl/detail/x11_cairo.c
index 0229d978..0112c4ee 100644
--- a/pugl/pugl/detail/x11_cairo.c
+++ b/pugl/pugl/detail/x11_cairo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file x11_cairo.c Cairo graphics backend for X11.
+ @file x11_cairo.c
+ @brief Cairo graphics backend for X11.
*/
#include "pugl/detail/types.h"
diff --git a/pugl/pugl/detail/x11_gl.c b/pugl/pugl/detail/x11_gl.c
index 33a05df3..f5e6b8d4 100644
--- a/pugl/pugl/detail/x11_gl.c
+++ b/pugl/pugl/detail/x11_gl.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,14 +15,15 @@
*/
/**
- @file x11_gl.c OpenGL graphics backend for X11.
+ @file x11_gl.c
+ @brief OpenGL graphics backend for X11.
*/
+#include "pugl/detail/stub.h"
#include "pugl/detail/types.h"
#include "pugl/detail/x11.h"
#include "pugl/pugl.h"
#include "pugl/pugl_gl.h"
-#include "pugl/pugl_stub.h"
#include <GL/glx.h>
#include <X11/X.h>
@@ -114,6 +115,28 @@ puglX11GlConfigure(PuglView* view)
}
static PuglStatus
+puglX11GlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose))
+{
+ PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
+ glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx);
+ return PUGL_SUCCESS;
+}
+
+static PuglStatus
+puglX11GlLeave(PuglView* view, const PuglEventExpose* expose)
+{
+ PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
+
+ if (expose && surface->double_buffered) {
+ glXSwapBuffers(view->impl->display, view->impl->win);
+ }
+
+ glXMakeCurrent(view->impl->display, None, NULL);
+
+ return PUGL_SUCCESS;
+}
+
+static PuglStatus
puglX11GlCreate(PuglView* view)
{
PuglInternals* const impl = view->impl;
@@ -152,7 +175,9 @@ puglX11GlCreate(PuglView* view)
const int swapInterval = view->hints[PUGL_SWAP_INTERVAL];
if (glXSwapIntervalEXT && swapInterval != PUGL_DONT_CARE) {
+ puglX11GlEnter(view, NULL);
glXSwapIntervalEXT(display, impl->win, swapInterval);
+ puglX11GlLeave(view, NULL);
}
glXGetConfig(impl->display,
@@ -175,28 +200,6 @@ puglX11GlDestroy(PuglView* view)
return PUGL_SUCCESS;
}
-static PuglStatus
-puglX11GlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose))
-{
- PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
- glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx);
- return PUGL_SUCCESS;
-}
-
-static PuglStatus
-puglX11GlLeave(PuglView* view, const PuglEventExpose* expose)
-{
- PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
-
- if (expose && surface->double_buffered) {
- glXSwapBuffers(view->impl->display, view->impl->win);
- }
-
- glXMakeCurrent(view->impl->display, None, NULL);
-
- return PUGL_SUCCESS;
-}
-
PuglGlFunc
puglGetProcAddress(const char* name)
{
diff --git a/pugl/pugl/gl.h b/pugl/pugl/gl.h
index 55a55c4a..dbb2e60c 100644
--- a/pugl/pugl/gl.h
+++ b/pugl/pugl/gl.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2014 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file gl.h Portable header wrapper for gl.h.
+ @file gl.h
+ @brief Portable header wrapper for gl.h.
Unfortunately, GL includes vary across platforms so this header allows for
pure portable programs.
diff --git a/pugl/pugl/glu.h b/pugl/pugl/glu.h
index 0ade70c9..94da8fc5 100644
--- a/pugl/pugl/glu.h
+++ b/pugl/pugl/glu.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2015 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file glu.h Portable header wrapper for glu.h.
+ @file glu.h
+ @brief Portable header wrapper for glu.h.
Unfortunately, GL includes vary across platforms so this header allows for
pure portable programs.
diff --git a/pugl/pugl/pugl.h b/pugl/pugl/pugl.h
index 57e23fa7..c32a17d8 100644
--- a/pugl/pugl/pugl.h
+++ b/pugl/pugl/pugl.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl.h Pugl API.
+ @file pugl.h
+ @brief Pugl API.
*/
#ifndef PUGL_PUGL_H
@@ -52,6 +53,12 @@
# endif
#endif
+#if defined(__GNUC__)
+# define PUGL_CONST_FUNC __attribute__((const))
+#else
+# define PUGL_CONST_FUNC
+#endif
+
#ifdef __cplusplus
# define PUGL_BEGIN_DECLS extern "C" {
# define PUGL_END_DECLS }
@@ -63,9 +70,13 @@
PUGL_BEGIN_DECLS
/**
- @defgroup pugl_api Pugl
+ @defgroup pugl Pugl
A minimal portable API for embeddable GUIs.
@{
+
+ @defgroup pugl_c C API
+ Public C API.
+ @{
*/
/**
@@ -87,7 +98,7 @@ typedef struct {
Event definitions.
All updates to the view happen via events, which are dispatched to the
- view's #PuglEventFunc by Pugl. Most events map directly to one from the
+ view's event function by Pugl. Most events map directly to one from the
underlying window system, but some are constructed by Pugl itself so there
is not necessarily a direct correspondence.
@@ -210,7 +221,8 @@ typedef enum {
Common flags for all event types.
*/
typedef enum {
- PUGL_IS_SEND_EVENT = 1 ///< Event is synthetic
+ PUGL_IS_SEND_EVENT = 1, ///< Event is synthetic
+ PUGL_IS_HINT = 2 ///< Event is a hint (not direct user input)
} PuglEventFlag;
/**
@@ -228,6 +240,22 @@ typedef enum {
} PuglCrossingMode;
/**
+ Scroll direction.
+
+ Describes the direction of a #PuglEventScroll along with whether the scroll
+ is a "smooth" scroll. The discrete directions are for devices like mouse
+ wheels with constrained axes, while a smooth scroll is for those with
+ arbitrary scroll direction freedom, like some touchpads.
+*/
+typedef enum {
+ PUGL_SCROLL_UP, ///< Scroll up
+ PUGL_SCROLL_DOWN, ///< Scroll down
+ PUGL_SCROLL_LEFT, ///< Scroll left
+ PUGL_SCROLL_RIGHT, ///< Scroll right
+ PUGL_SCROLL_SMOOTH ///< Smooth scroll in any direction
+} PuglScrollDirection;
+
+/**
Common header for all event structs.
*/
typedef struct {
@@ -322,7 +350,6 @@ typedef struct {
double y; ///< View-relative Y coordinate
double width; ///< Width of exposed region
double height; ///< Height of exposed region
- int count; ///< Number of expose events to follow
} PuglEventExpose;
/**
@@ -342,9 +369,9 @@ typedef PuglEventAny PuglEventClose;
view with the keyboard focus will receive any key press or release events.
*/
typedef struct {
- PuglEventType type; ///< #PUGL_FOCUS_IN or #PUGL_FOCUS_OUT
- PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
- bool grab; ///< True iff this is a grab/ungrab event
+ PuglEventType type; ///< #PUGL_FOCUS_IN or #PUGL_FOCUS_OUT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ PuglCrossingMode mode; ///< Reason for focus change
} PuglEventFocus;
/**
@@ -355,12 +382,12 @@ typedef struct {
as text input.
Keys are represented portably as Unicode code points, using the "natural"
- code point for the key where possible (see #PuglKey for details). The #key
+ code point for the key where possible (see #PuglKey for details). The `key`
field is the code for the pressed key, without any modifiers applied. For
- example, a press or release of the 'A' key will have #key 97 ('a')
+ example, a press or release of the 'A' key will have `key` 97 ('a')
regardless of whether shift or control are being held.
- Alternatively, the raw #keycode can be used to work directly with physical
+ Alternatively, the raw `keycode` can be used to work directly with physical
keys, but note that this value is not portable and differs between platforms
and hardware.
*/
@@ -407,7 +434,7 @@ typedef struct {
This event is sent when the pointer enters or leaves the view. This can
happen for several reasons (not just the user dragging the pointer over the
- window edge), as described by the #mode field.
+ window edge), as described by the `mode` field.
*/
typedef struct {
PuglEventType type; ///< #PUGL_POINTER_IN or #PUGL_POINTER_OUT
@@ -448,30 +475,29 @@ typedef struct {
double xRoot; ///< Root-relative X coordinate
double yRoot; ///< Root-relative Y coordinate
PuglMods state; ///< Bitwise OR of #PuglMod flags
- bool isHint; ///< True iff this event is a motion hint
- bool focus; ///< True iff this is the focused view
} PuglEventMotion;
/**
Scroll event.
The scroll distance is expressed in "lines", an arbitrary unit that
- corresponds to a single tick of a detented mouse wheel. For example, #dy =
+ corresponds to a single tick of a detented mouse wheel. For example, `dy` =
1.0 scrolls 1 line up. Some systems and devices support finer resolution
and/or higher values for fast scrolls, so programs should handle any value
gracefully.
*/
typedef struct {
- PuglEventType type; ///< #PUGL_SCROLL
- PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
- double time; ///< Time in seconds
- double x; ///< View-relative X coordinate
- double y; ///< View-relative Y coordinate
- double xRoot; ///< Root-relative X coordinate
- double yRoot; ///< Root-relative Y coordinate
- PuglMods state; ///< Bitwise OR of #PuglMod flags
- double dx; ///< Scroll X distance in lines
- double dy; ///< Scroll Y distance in lines
+ PuglEventType type; ///< #PUGL_SCROLL
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ PuglScrollDirection direction; ///< Scroll direction
+ double dx; ///< Scroll X distance in lines
+ double dy; ///< Scroll Y distance in lines
} PuglEventScroll;
/**
@@ -494,7 +520,7 @@ typedef struct {
This event is sent at the regular interval specified in the call to
puglStartTimer() that activated it.
- The #id is the application-specific ID given to puglStartTimer() which
+ The `id` is the application-specific ID given to puglStartTimer() which
distinguishes this timer from others. It should always be checked in the
event handler, even in applications that register only one timer.
*/
@@ -507,7 +533,7 @@ typedef struct {
/**
View event.
- This is a union of all event types. The #type must be checked to determine
+ This is a union of all event types. The type must be checked to determine
which fields are safe to access. A pointer to PuglEvent can either be cast
to the appropriate type, or the union members used.
@@ -548,6 +574,7 @@ typedef enum {
PUGL_FAILURE, ///< Non-fatal failure
PUGL_UNKNOWN_ERROR, ///< Unknown system error
PUGL_BAD_BACKEND, ///< Invalid or missing backend
+ PUGL_BAD_CONFIGURATION, ///< Invalid view configuration
PUGL_BAD_PARAMETER, ///< Invalid parameter
PUGL_BACKEND_FAILED, ///< Backend initialisation failed
PUGL_REGISTRATION_FAILED, ///< Class registration failed
@@ -594,7 +621,7 @@ typedef struct PuglWorldImpl PuglWorld;
typedef void* PuglWorldHandle;
/**
- The type of a PuglWorld.
+ The type of a World.
*/
typedef enum {
PUGL_PROGRAM, ///< Top-level application
@@ -728,19 +755,18 @@ puglGetTime(const PuglWorld* world);
This function is a single iteration of the main loop, and should be called
repeatedly to update all views.
- If a positive timeout is given, then events will be processed for that
- amount of time, starting from when this function was called. For purely
- event-driven programs, a timeout of -1.0 can be used to block indefinitely
- until something happens. For continuously animating programs, a timeout
- that is a reasonable fraction of the ideal frame period should be used, to
- minimise input latency by ensuring that as many input events are consumed as
- possible before drawing. Plugins should always use a timeout of 0.0 to
- avoid blocking the host.
+ If `timeout` is zero, then this function will not block. Plugins should
+ always use a timeout of zero to avoid blocking the host.
- @param world The world to update.
+ If a positive `timeout` is given, then events will be processed for that
+ amount of time, starting from when this function was called.
- @param timeout Maximum time to wait, in seconds. If zero, the call returns
- immediately, if negative, the call blocks indefinitely.
+ If a negative `timeout` is given, this function will block indefinitely
+ until an event occurs.
+
+ For continuously animating programs, a timeout that is a reasonable fraction
+ of the ideal frame period should be used, to minimise input latency by
+ ensuring that as many input events are consumed as possible before drawing.
@return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if not, or an error.
*/
@@ -937,6 +963,16 @@ PUGL_API PuglStatus
puglSetFrame(PuglView* view, PuglRect frame);
/**
+ Set the default size of the view.
+
+ This should be called before puglResize() to set the default size of the
+ view, which will be the initial size of the window if this is a top level
+ view.
+*/
+PUGL_API PuglStatus
+puglSetDefaultSize(PuglView* view, int width, int height);
+
+/**
Set the minimum size of the view.
If an initial minimum size is known, this should be called before
@@ -946,6 +982,15 @@ PUGL_API PuglStatus
puglSetMinSize(PuglView* view, int width, int height);
/**
+ Set the maximum size of the view.
+
+ If an initial maximum size is known, this should be called before
+ puglRealize() to avoid stutter, though it can be called afterwards as well.
+*/
+PUGL_API PuglStatus
+puglSetMaxSize(PuglView* view, int width, int height);
+
+/**
Set the view aspect ratio range.
The x and y values here represent a ratio of width to height. To set a
@@ -992,6 +1037,9 @@ puglSetParentWindow(PuglView* view, PuglNativeView parent);
Set this for transient children like dialogs, to have them properly
associated with their parent window. This should be called before
puglRealize().
+
+ A view can either have a parent (for embedding) or a transient parent (for
+ top-level windows like dialogs), but not both.
*/
PUGL_API PuglStatus
puglSetTransientFor(PuglView* view, PuglNativeView parent);
@@ -1091,6 +1139,22 @@ puglPostRedisplayRect(PuglView* view, PuglRect rect);
*/
/**
+ A mouse cursor type.
+
+ This is a portable subset of mouse cursors that exist on X11, MacOS, and
+ Windows.
+*/
+typedef enum {
+ PUGL_CURSOR_ARROW, ///< Default pointing arrow
+ PUGL_CURSOR_CARET, ///< Caret (I-Beam) for text entry
+ PUGL_CURSOR_CROSSHAIR, ///< Cross-hair
+ PUGL_CURSOR_HAND, ///< Hand with a pointing finger
+ PUGL_CURSOR_NO, ///< Operation not allowed
+ PUGL_CURSOR_LEFT_RIGHT, ///< Left/right arrow for horizontal resize
+ PUGL_CURSOR_UP_DOWN, ///< Up/down arrow for vertical resize
+} PuglCursor;
+
+/**
Grab the keyboard input focus.
*/
PUGL_API PuglStatus
@@ -1134,6 +1198,16 @@ PUGL_API const void*
puglGetClipboard(PuglView* view, const char** type, size_t* len);
/**
+ Set the mouse cursor.
+
+ This changes the system cursor that is displayed when the pointer is inside
+ the view. May fail if setting the cursor is not supported on this system,
+ for example if compiled on X11 without Xcursor support.
+ */
+PUGL_API PuglStatus
+puglSetCursor(PuglView* view, PuglCursor cursor);
+
+/**
Request user attention.
This hints to the system that the window or application requires attention
@@ -1496,6 +1570,7 @@ puglLeaveContext(PuglView* view, bool drawing);
/**
@}
@}
+ @}
*/
PUGL_END_DECLS
diff --git a/pugl/pugl/pugl.hpp b/pugl/pugl/pugl.hpp
index 73cfe2a6..30725603 100644
--- a/pugl/pugl/pugl.hpp
+++ b/pugl/pugl/pugl.hpp
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl.hpp Pugl C++ API wrapper.
+ @file pugl.hpp
+ @brief Pugl C++ API wrapper.
*/
#ifndef PUGL_PUGL_HPP
@@ -23,12 +24,18 @@
#include "pugl/pugl.h"
-/**
- @defgroup puglxx C++
+#include <cassert>
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+/**
+ @defgroup pugl_cxx C++ API
C++ API wrapper.
- @ingroup pugl_api
+ @ingroup pugl
@{
*/
@@ -37,84 +44,657 @@
*/
namespace pugl {
+namespace detail {
+
+/// Free function for a C object
+template<typename T>
+using FreeFunc = void (*)(T*);
+
+/// Simple overhead-free deleter for a C object
+template<typename T, FreeFunc<T> Free>
+struct Deleter {
+ void operator()(T* ptr) { Free(ptr); }
+};
+
+/// Generic C++ wrapper for a C object
+template<class T, FreeFunc<T> Free>
+class Wrapper
+{
+public:
+ T* cobj() { return _ptr.get(); }
+ const T* cobj() const { return _ptr.get(); }
+
+protected:
+ explicit Wrapper(T* ptr)
+ : _ptr(ptr, Deleter<T, Free>{})
+ {}
+
+private:
+ std::unique_ptr<T, Deleter<T, Free>> _ptr;
+};
+
+} // namespace detail
+
+using Rect = PuglRect; ///< @copydoc PuglRect
+
/**
- A drawable region that receives events.
+ @defgroup eventsxx Events
+ @ingroup pugl_cxx
+ @copydoc events
+ @{
+*/
- This is a thin wrapper for a PuglView that contains only a pointer.
+/**
+ A strongly-typed analogue of PuglEvent.
- @ingroup puglxx
+ This is bit-for-bit identical to the corresponding PuglEvent, so events are
+ simply cast to this type to avoid any copying overhead.
+
+ @tparam t The `type` field of the corresponding PuglEvent.
+
+ @tparam Base The specific struct type of the corresponding PuglEvent.
*/
-class View {
+template<PuglEventType t, class Base>
+struct Event final : Base {
+ using BaseEvent = Base;
+
+ static constexpr const PuglEventType type = t;
+};
+
+using Mod = PuglMod; ///< @copydoc PuglMod
+using Mods = PuglMods; ///< @copydoc PuglMods
+using Key = PuglKey; ///< @copydoc PuglKey
+using EventType = PuglEventType; ///< @copydoc PuglEventType
+using EventFlag = PuglEventFlag; ///< @copydoc PuglEventFlag
+using EventFlags = PuglEventFlags; ///< @copydoc PuglEventFlags
+using CrossingMode = PuglCrossingMode; ///< @copydoc PuglCrossingMode
+
+/// @copydoc PuglEventCreate
+using CreateEvent = Event<PUGL_CREATE, PuglEventCreate>;
+
+/// @copydoc PuglEventDestroy
+using DestroyEvent = Event<PUGL_DESTROY, PuglEventDestroy>;
+
+/// @copydoc PuglEventConfigure
+using ConfigureEvent = Event<PUGL_CONFIGURE, PuglEventConfigure>;
+
+/// @copydoc PuglEventMap
+using MapEvent = Event<PUGL_MAP, PuglEventMap>;
+
+/// @copydoc PuglEventUnmap
+using UnmapEvent = Event<PUGL_UNMAP, PuglEventUnmap>;
+
+/// @copydoc PuglEventUpdate
+using UpdateEvent = Event<PUGL_UPDATE, PuglEventUpdate>;
+
+/// @copydoc PuglEventExpose
+using ExposeEvent = Event<PUGL_EXPOSE, PuglEventExpose>;
+
+/// @copydoc PuglEventClose
+using CloseEvent = Event<PUGL_CLOSE, PuglEventClose>;
+
+/// @copydoc PuglEventFocus
+using FocusInEvent = Event<PUGL_FOCUS_IN, PuglEventFocus>;
+
+/// @copydoc PuglEventFocus
+using FocusOutEvent = Event<PUGL_FOCUS_OUT, PuglEventFocus>;
+
+/// @copydoc PuglEventKey
+using KeyPressEvent = Event<PUGL_KEY_PRESS, PuglEventKey>;
+
+/// @copydoc PuglEventKey
+using KeyReleaseEvent = Event<PUGL_KEY_RELEASE, PuglEventKey>;
+
+/// @copydoc PuglEventText
+using TextEvent = Event<PUGL_TEXT, PuglEventText>;
+
+/// @copydoc PuglEventCrossing
+using PointerInEvent = Event<PUGL_POINTER_IN, PuglEventCrossing>;
+
+/// @copydoc PuglEventCrossing
+using PointerOutEvent = Event<PUGL_POINTER_OUT, PuglEventCrossing>;
+
+/// @copydoc PuglEventButton
+using ButtonPressEvent = Event<PUGL_BUTTON_PRESS, PuglEventButton>;
+
+/// @copydoc PuglEventButton
+using ButtonReleaseEvent = Event<PUGL_BUTTON_RELEASE, PuglEventButton>;
+
+/// @copydoc PuglEventMotion
+using MotionEvent = Event<PUGL_MOTION, PuglEventMotion>;
+
+/// @copydoc PuglEventScroll
+using ScrollEvent = Event<PUGL_SCROLL, PuglEventScroll>;
+
+/// @copydoc PuglEventClient
+using ClientEvent = Event<PUGL_CLIENT, PuglEventClient>;
+
+/// @copydoc PuglEventTimer
+using TimerEvent = Event<PUGL_TIMER, PuglEventTimer>;
+
+/**
+ @}
+ @defgroup statusxx Status
+ @ingroup pugl_cxx
+ @copydoc status
+ @{
+*/
+
+/// @copydoc PuglStatus
+enum class Status {
+ success, ///< @copydoc PUGL_SUCCESS
+ failure, ///< @copydoc PUGL_FAILURE
+ unknownError, ///< @copydoc PUGL_UNKNOWN_ERROR
+ badBackend, ///< @copydoc PUGL_BAD_BACKEND
+ badConfiguration, ///< @copydoc PUGL_BAD_CONFIGURATION
+ badParameter, ///< @copydoc PUGL_BAD_PARAMETER
+ backendFailed, ///< @copydoc PUGL_BACKEND_FAILED
+ registrationFailed, ///< @copydoc PUGL_REGISTRATION_FAILED
+ realizeFailed, ///< @copydoc PUGL_REALIZE_FAILED
+ setFormatFailed, ///< @copydoc PUGL_SET_FORMAT_FAILED
+ createContextFailed, ///< @copydoc PUGL_CREATE_CONTEXT_FAILED
+ unsupportedType, ///< @copydoc PUGL_UNSUPPORTED_TYPE
+};
+
+static_assert(Status(PUGL_UNSUPPORTED_TYPE) == Status::unsupportedType, "");
+
+/// @copydoc puglStrerror
+static inline const char*
+strerror(const pugl::Status status)
+{
+ return puglStrerror(static_cast<PuglStatus>(status));
+}
+
+/**
+ @}
+ @defgroup worldxx World
+ @ingroup pugl_cxx
+ @copydoc world
+ @{
+*/
+
+class World;
+
+/// @copydoc PuglWorldType
+enum class WorldType {
+ program, ///< @copydoc PUGL_PROGRAM
+ module, ///< @copydoc PUGL_MODULE
+};
+
+static_assert(WorldType(PUGL_MODULE) == WorldType::module, "");
+
+/// @copydoc PuglWorldFlag
+enum class WorldFlag {
+ threads = PUGL_WORLD_THREADS, ///< @copydoc PUGL_WORLD_THREADS
+};
+
+static_assert(WorldFlag(PUGL_WORLD_THREADS) == WorldFlag::threads, "");
+
+using WorldFlags = PuglWorldFlags; ///< @copydoc PuglWorldFlags
+
+/// @copydoc PuglLogLevel
+enum class LogLevel {
+ err = PUGL_LOG_LEVEL_ERR, ///< @copydoc PUGL_LOG_LEVEL_ERR
+ warning = PUGL_LOG_LEVEL_WARNING, ///< @copydoc PUGL_LOG_LEVEL_WARNING
+ info = PUGL_LOG_LEVEL_INFO, ///< @copydoc PUGL_LOG_LEVEL_INFO
+ debug = PUGL_LOG_LEVEL_DEBUG, ///< @copydoc PUGL_LOG_LEVEL_DEBUG
+};
+
+static_assert(LogLevel(PUGL_LOG_LEVEL_DEBUG) == LogLevel::debug, "");
+
+/// @copydoc PuglLogFunc
+using LogFunc =
+ std::function<void(World& world, LogLevel level, const char* msg)>;
+
+/**
+ A `std::chrono` compatible clock that uses Pugl time.
+*/
+class Clock
+{
public:
- View(int* pargc, char** argv)
- : _view(puglInit(pargc, argv))
+ using rep = double; ///< Time representation
+ using duration = std::chrono::duration<double>; ///< Duration in seconds
+ using time_point = std::chrono::time_point<Clock>; ///< A Pugl time point
+
+ static constexpr bool is_steady = true; ///< Steady clock flag, always true
+
+ /// Construct a clock that uses time from puglGetTime()
+ explicit Clock(World& world)
+ : _world{world}
+ {}
+
+ /// Return the current time
+ time_point now() const;
+
+private:
+ const pugl::World& _world;
+};
+
+/// @copydoc PuglWorld
+class World : public detail::Wrapper<PuglWorld, puglFreeWorld>
+{
+public:
+ explicit World(WorldType type, WorldFlags flags)
+ : Wrapper{puglNewWorld(static_cast<PuglWorldType>(type), flags)}
+ , _clock(*this)
+ {
+ if (!cobj()) {
+ throw std::runtime_error("Failed to create pugl::World");
+ }
+ }
+
+ explicit World(WorldType type)
+ : World{type, {}}
{
- puglSetHandle(_view, this);
- puglSetEventFunc(_view, _onEvent);
+ if (!cobj()) {
+ throw std::runtime_error("Failed to create pugl::World");
+ }
}
- virtual ~View() { puglDestroy(_view); }
+ /// @copydoc puglGetNativeWorld
+ void* nativeWorld() { return puglGetNativeWorld(cobj()); }
+
+ // TODO: setLogFunc
- virtual void initWindowParent(PuglNativeWindow parent) {
- puglInitWindowParent(_view, parent);
+ Status setLogLevel(const LogLevel level)
+ {
+ return static_cast<Status>(
+ puglSetLogLevel(cobj(), static_cast<PuglLogLevel>(level)));
}
- virtual void initWindowSize(int width, int height) {
- puglInitWindowSize(_view, width, height);
+ /// @copydoc puglSetClassName
+ Status setClassName(const char* const name)
+ {
+ return static_cast<Status>(puglSetClassName(cobj(), name));
}
- virtual void initWindowMinSize(int width, int height) {
- puglInitWindowMinSize(_view, width, height);
+ /// @copydoc puglGetTime
+ double time() const { return puglGetTime(cobj()); }
+
+ /// @copydoc puglUpdate
+ Status update(const double timeout)
+ {
+ return static_cast<Status>(puglUpdate(cobj(), timeout));
+ }
+
+ /// Return a clock that uses Pugl time
+ const Clock& clock() { return _clock; }
+
+private:
+ Clock _clock;
+};
+
+inline Clock::time_point
+Clock::now() const
+{
+ return time_point{duration{_world.time()}};
+}
+
+/**
+ @}
+ @defgroup viewxx View
+ @ingroup pugl_cxx
+ @copydoc view
+ @{
+*/
+
+using Backend = PuglBackend; ///< @copydoc PuglBackend
+using NativeView = PuglNativeView; ///< @copydoc PuglNativeView
+
+/// @copydoc PuglViewHint
+enum class ViewHint {
+ useCompatProfile, ///< @copydoc PUGL_USE_COMPAT_PROFILE
+ useDebugContext, ///< @copydoc PUGL_USE_DEBUG_CONTEXT
+ contextVersionMajor, ///< @copydoc PUGL_CONTEXT_VERSION_MAJOR
+ contextVersionMinor, ///< @copydoc PUGL_CONTEXT_VERSION_MINOR
+ redBits, ///< @copydoc PUGL_RED_BITS
+ greenBits, ///< @copydoc PUGL_GREEN_BITS
+ blueBits, ///< @copydoc PUGL_BLUE_BITS
+ alphaBits, ///< @copydoc PUGL_ALPHA_BITS
+ depthBits, ///< @copydoc PUGL_DEPTH_BITS
+ stencilBits, ///< @copydoc PUGL_STENCIL_BITS
+ samples, ///< @copydoc PUGL_SAMPLES
+ doubleBuffer, ///< @copydoc PUGL_DOUBLE_BUFFER
+ swapInterval, ///< @copydoc PUGL_SWAP_INTERVAL
+ resizable, ///< @copydoc PUGL_RESIZABLE
+ ignoreKeyRepeat, ///< @copydoc PUGL_IGNORE_KEY_REPEAT
+};
+
+static_assert(ViewHint(PUGL_IGNORE_KEY_REPEAT) == ViewHint::ignoreKeyRepeat,
+ "");
+
+using ViewHintValue = PuglViewHintValue; ///< @copydoc PuglViewHintValue
+
+/// @copydoc PuglCursor
+enum class Cursor {
+ arrow, ///< @copydoc PUGL_CURSOR_ARROW
+ caret, ///< @copydoc PUGL_CURSOR_CARET
+ crosshair, ///< @copydoc PUGL_CURSOR_CROSSHAIR
+ hand, ///< @copydoc PUGL_CURSOR_HAND
+ no, ///< @copydoc PUGL_CURSOR_NO
+ leftRight, ///< @copydoc PUGL_CURSOR_LEFT_RIGHT
+ upDown, ///< @copydoc PUGL_CURSOR_UP_DOWN
+};
+
+static_assert(Cursor(PUGL_CURSOR_UP_DOWN) == Cursor::upDown, "");
+
+/// @copydoc PuglView
+class View : protected detail::Wrapper<PuglView, puglFreeView>
+{
+public:
+ /**
+ @name Setup
+ Methods for creating and destroying a view.
+ @{
+ */
+
+ explicit View(World& world)
+ : Wrapper{puglNewView(world.cobj())}
+ , _world(world)
+ {
+ if (!cobj()) {
+ throw std::runtime_error("Failed to create pugl::View");
+ }
+
+ puglSetHandle(cobj(), this);
+ puglSetEventFunc(cobj(), dispatchEvent);
+ }
+
+ virtual ~View() = default;
+
+ View(const View&) = delete;
+ View& operator=(const View&) = delete;
+
+ View(View&&) = delete;
+ View&& operator=(View&&) = delete;
+
+ const pugl::World& world() const { return _world; }
+ pugl::World& world() { return _world; }
+
+ /// @copydoc puglSetViewHint
+ Status setHint(ViewHint hint, int value)
+ {
+ return static_cast<Status>(
+ puglSetViewHint(cobj(), static_cast<PuglViewHint>(hint), value));
+ }
+
+ /**
+ @}
+ @name Frame
+ Methods for working with the position and size of a view.
+ @{
+ */
+
+ /// @copydoc puglGetFrame
+ Rect frame() const { return puglGetFrame(cobj()); }
+
+ /// @copydoc puglSetFrame
+ Status setFrame(Rect frame)
+ {
+ return static_cast<Status>(puglSetFrame(cobj(), frame));
+ }
+
+ /// @copydoc puglSetDefaultSize
+ Status setDefaultSize(int width, int height)
+ {
+ return static_cast<Status>(puglSetDefaultSize(cobj(), width, height));
+ }
+
+ /// @copydoc puglSetMinSize
+ Status setMinSize(int width, int height)
+ {
+ return static_cast<Status>(puglSetMinSize(cobj(), width, height));
}
- virtual void initWindowAspectRatio(int min_x, int min_y, int max_x, int max_y) {
- puglInitWindowAspectRatio(_view, min_x, min_y, max_x, max_y);
+ /// @copydoc puglSetMaxSize
+ Status setMaxSize(int width, int height)
+ {
+ return static_cast<Status>(puglSetMaxSize(cobj(), width, height));
}
- virtual void initResizable(bool resizable) {
- puglInitResizable(_view, resizable);
+ /// @copydoc puglSetAspectRatio
+ Status setAspectRatio(int minX, int minY, int maxX, int maxY)
+ {
+ return static_cast<Status>(
+ puglSetAspectRatio(cobj(), minX, minY, maxX, maxY));
}
- virtual void initTransientFor(uintptr_t parent) {
- puglInitTransientFor(_view, parent);
+ /**
+ @}
+ @name Windows
+ Methods for working with top-level windows.
+ @{
+ */
+
+ /// @copydoc puglSetWindowTitle
+ Status setWindowTitle(const char* title)
+ {
+ return static_cast<Status>(puglSetWindowTitle(cobj(), title));
}
- virtual void initBackend(const PuglBackend* backend) {
- puglInitBackend(_view, backend);
+ /// @copydoc puglSetParentWindow
+ Status setParentWindow(NativeView parent)
+ {
+ return static_cast<Status>(puglSetParentWindow(cobj(), parent));
+ }
+
+ /// @copydoc puglSetTransientFor
+ Status setTransientFor(NativeView parent)
+ {
+ return static_cast<Status>(puglSetTransientFor(cobj(), parent));
+ }
+
+ /// @copydoc puglRealize
+ Status realize() { return static_cast<Status>(puglRealize(cobj())); }
+
+ /// @copydoc puglShowWindow
+ Status showWindow() { return static_cast<Status>(puglShowWindow(cobj())); }
+
+ /// @copydoc puglHideWindow
+ Status hideWindow() { return static_cast<Status>(puglHideWindow(cobj())); }
+
+ /// @copydoc puglGetVisible
+ bool visible() const { return puglGetVisible(cobj()); }
+
+ /// @copydoc puglGetNativeWindow
+ NativeView nativeWindow() { return puglGetNativeWindow(cobj()); }
+
+ /**
+ @}
+ @name Graphics
+ Methods for working with the graphics context and scheduling
+ redisplays.
+ @{
+ */
+
+ /// @copydoc puglGetContext
+ void* context() { return puglGetContext(cobj()); }
+
+ /// @copydoc puglPostRedisplay
+ Status postRedisplay()
+ {
+ return static_cast<Status>(puglPostRedisplay(cobj()));
}
- virtual void createWindow(const char* title) {
- puglCreateWindow(_view, title);
+ /// @copydoc puglPostRedisplayRect
+ Status postRedisplayRect(const Rect rect)
+ {
+ return static_cast<Status>(puglPostRedisplayRect(cobj(), rect));
}
- virtual void showWindow() { puglShowWindow(_view); }
- virtual void hideWindow() { puglHideWindow(_view); }
- virtual PuglNativeWindow getNativeWindow() { return puglGetNativeWindow(_view); }
+ /**
+ @}
+ @name Interaction
+ Methods for interacting with the user and window system.
+ @{
+ */
- virtual void onEvent(const PuglEvent* event) = 0;
+ /// @copydoc puglGrabFocus
+ Status grabFocus() { return static_cast<Status>(puglGrabFocus(cobj())); }
- virtual void* getContext() { return puglGetContext(_view); }
- virtual void ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); }
- virtual void grabFocus() { puglGrabFocus(_view); }
- virtual void requestAttention() { puglRequestAttention(_view); }
- virtual PuglStatus waitForEvent() { return puglWaitForEvent(_view); }
- virtual PuglStatus processEvents() { return puglProcessEvents(_view); }
- virtual void postRedisplay() { puglPostRedisplay(_view); }
+ /// @copydoc puglHasFocus
+ bool hasFocus() const { return puglHasFocus(cobj()); }
- PuglView* cobj() { return _view; }
+ /// @copydoc puglSetBackend
+ Status setBackend(const PuglBackend* backend)
+ {
+ return static_cast<Status>(puglSetBackend(cobj(), backend));
+ }
+
+ /// @copydoc puglSetCursor
+ Status setCursor(const Cursor cursor)
+ {
+ return static_cast<Status>(
+ puglSetCursor(cobj(), static_cast<PuglCursor>(cursor)));
+ }
+
+ /// @copydoc puglRequestAttention
+ Status requestAttention()
+ {
+ return static_cast<Status>(puglRequestAttention(cobj()));
+ }
+
+ /**
+ @}
+ @name Event Handlers
+ Methods called when events are dispatched to the view.
+ @{
+ */
+
+ virtual Status onCreate(const CreateEvent&) PUGL_CONST_FUNC;
+ virtual Status onDestroy(const DestroyEvent&) PUGL_CONST_FUNC;
+ virtual Status onConfigure(const ConfigureEvent&) PUGL_CONST_FUNC;
+ virtual Status onMap(const MapEvent&) PUGL_CONST_FUNC;
+ virtual Status onUnmap(const UnmapEvent&) PUGL_CONST_FUNC;
+ virtual Status onUpdate(const UpdateEvent&) PUGL_CONST_FUNC;
+ virtual Status onExpose(const ExposeEvent&) PUGL_CONST_FUNC;
+ virtual Status onClose(const CloseEvent&) PUGL_CONST_FUNC;
+ virtual Status onFocusIn(const FocusInEvent&) PUGL_CONST_FUNC;
+ virtual Status onFocusOut(const FocusOutEvent&) PUGL_CONST_FUNC;
+ virtual Status onKeyPress(const KeyPressEvent&) PUGL_CONST_FUNC;
+ virtual Status onKeyRelease(const KeyReleaseEvent&) PUGL_CONST_FUNC;
+ virtual Status onText(const TextEvent&) PUGL_CONST_FUNC;
+ virtual Status onPointerIn(const PointerInEvent&) PUGL_CONST_FUNC;
+ virtual Status onPointerOut(const PointerOutEvent&) PUGL_CONST_FUNC;
+ virtual Status onButtonPress(const ButtonPressEvent&) PUGL_CONST_FUNC;
+ virtual Status onButtonRelease(const ButtonReleaseEvent&) PUGL_CONST_FUNC;
+ virtual Status onMotion(const MotionEvent&) PUGL_CONST_FUNC;
+ virtual Status onScroll(const ScrollEvent&) PUGL_CONST_FUNC;
+ virtual Status onClient(const ClientEvent&) PUGL_CONST_FUNC;
+ virtual Status onTimer(const TimerEvent&) PUGL_CONST_FUNC;
+
+ /**
+ @}
+ */
+
+ PuglView* cobj() { return Wrapper::cobj(); }
+ const PuglView* cobj() const { return Wrapper::cobj(); }
private:
- static void _onEvent(PuglView* view, const PuglEvent* event) {
- ((View*)puglGetHandle(view))->onEvent(event);
+ template<class Typed, class Base>
+ static const Typed& typedEventRef(const Base& base)
+ {
+ const auto& event = static_cast<const Typed&>(base);
+ static_assert(sizeof(event) == sizeof(typename Typed::BaseEvent), "");
+ static_assert(std::is_standard_layout<Typed>::value, "");
+ assert(event.type == Typed::type);
+ return event;
}
- PuglView* _view;
+ static PuglStatus dispatchEvent(PuglView* view, const PuglEvent* event) noexcept {
+ try {
+ View* self = static_cast<View*>(puglGetHandle(view));
+
+ return self->dispatch(event);
+ } catch (...) {
+ return PUGL_UNKNOWN_ERROR;
+ }
+ }
+
+ PuglStatus dispatch(const PuglEvent* event)
+ {
+ switch (event->type) {
+ case PUGL_NOTHING:
+ return PUGL_SUCCESS;
+ case PUGL_CREATE:
+ return static_cast<PuglStatus>(
+ onCreate(typedEventRef<CreateEvent>(event->any)));
+ case PUGL_DESTROY:
+ return static_cast<PuglStatus>(
+ onDestroy(typedEventRef<DestroyEvent>(event->any)));
+ case PUGL_CONFIGURE:
+ return static_cast<PuglStatus>(onConfigure(
+ typedEventRef<ConfigureEvent>(event->configure)));
+ case PUGL_MAP:
+ return static_cast<PuglStatus>(
+ onMap(typedEventRef<MapEvent>(event->any)));
+ case PUGL_UNMAP:
+ return static_cast<PuglStatus>(
+ onUnmap(typedEventRef<UnmapEvent>(event->any)));
+ case PUGL_UPDATE:
+ return static_cast<PuglStatus>(
+ onUpdate(typedEventRef<UpdateEvent>(event->any)));
+ case PUGL_EXPOSE:
+ return static_cast<PuglStatus>(
+ onExpose(typedEventRef<ExposeEvent>(event->expose)));
+ case PUGL_CLOSE:
+ return static_cast<PuglStatus>(
+ onClose(typedEventRef<CloseEvent>(event->any)));
+ case PUGL_FOCUS_IN:
+ return static_cast<PuglStatus>(
+ onFocusIn(typedEventRef<FocusInEvent>(event->focus)));
+ case PUGL_FOCUS_OUT:
+ return static_cast<PuglStatus>(
+ onFocusOut(typedEventRef<FocusOutEvent>(event->focus)));
+ case PUGL_KEY_PRESS:
+ return static_cast<PuglStatus>(
+ onKeyPress(typedEventRef<KeyPressEvent>(event->key)));
+ case PUGL_KEY_RELEASE:
+ return static_cast<PuglStatus>(
+ onKeyRelease(typedEventRef<KeyReleaseEvent>(event->key)));
+ case PUGL_TEXT:
+ return static_cast<PuglStatus>(
+ onText(typedEventRef<TextEvent>(event->text)));
+ case PUGL_POINTER_IN:
+ return static_cast<PuglStatus>(onPointerIn(
+ typedEventRef<PointerInEvent>(event->crossing)));
+ case PUGL_POINTER_OUT:
+ return static_cast<PuglStatus>(onPointerOut(
+ typedEventRef<PointerOutEvent>(event->crossing)));
+ case PUGL_BUTTON_PRESS:
+ return static_cast<PuglStatus>(onButtonPress(
+ typedEventRef<ButtonPressEvent>(event->button)));
+ case PUGL_BUTTON_RELEASE:
+ return static_cast<PuglStatus>(onButtonRelease(
+ typedEventRef<ButtonReleaseEvent>(event->button)));
+ case PUGL_MOTION:
+ return static_cast<PuglStatus>(
+ onMotion(typedEventRef<MotionEvent>(event->motion)));
+ case PUGL_SCROLL:
+ return static_cast<PuglStatus>(
+ onScroll(typedEventRef<ScrollEvent>(event->scroll)));
+ case PUGL_CLIENT:
+ return static_cast<PuglStatus>(
+ onClient(typedEventRef<ClientEvent>(event->client)));
+ case PUGL_TIMER:
+ return static_cast<PuglStatus>(
+ onTimer(typedEventRef<TimerEvent>(event->timer)));
+ }
+
+ return PUGL_FAILURE;
+ }
+
+ World& _world;
};
-} // namespace pugl
+/**
+ @}
+*/
+
+} // namespace pugl
/**
@}
*/
-#endif /* PUGL_PUGL_HPP */
+#endif /* PUGL_PUGL_HPP */
diff --git a/pugl/pugl/pugl.ipp b/pugl/pugl/pugl.ipp
new file mode 100644
index 00000000..7c39a63d
--- /dev/null
+++ b/pugl/pugl/pugl.ipp
@@ -0,0 +1,154 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl.ipp
+ @brief Pugl C++ API wrapper implementation.
+
+ This file must be included exactly once in the application.
+*/
+
+#include "pugl/pugl.hpp"
+
+namespace pugl {
+
+Status
+View::onCreate(const CreateEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onDestroy(const DestroyEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onConfigure(const ConfigureEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onMap(const MapEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onUnmap(const UnmapEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onUpdate(const UpdateEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onExpose(const ExposeEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onClose(const CloseEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onFocusIn(const FocusInEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onFocusOut(const FocusOutEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onKeyPress(const KeyPressEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onKeyRelease(const KeyReleaseEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onText(const TextEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onPointerIn(const PointerInEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onPointerOut(const PointerOutEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onButtonPress(const ButtonPressEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onButtonRelease(const ButtonReleaseEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onMotion(const MotionEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onScroll(const ScrollEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onClient(const ClientEvent&)
+{
+ return pugl::Status::success;
+}
+
+Status
+View::onTimer(const TimerEvent&)
+{
+ return pugl::Status::success;
+}
+
+}
diff --git a/pugl/pugl/pugl_cairo.h b/pugl/pugl/pugl_cairo.h
index e71072e8..c68f6bb0 100644
--- a/pugl/pugl/pugl_cairo.h
+++ b/pugl/pugl/pugl_cairo.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl_cairo.h Declaration of Cairo backend accessor.
+ @file pugl_cairo.h
+ @brief Declaration of Cairo backend accessor.
*/
#ifndef PUGL_PUGL_CAIRO_H
@@ -28,7 +29,7 @@ PUGL_BEGIN_DECLS
/**
@defgroup cairo Cairo
Cairo graphics support.
- @ingroup pugl_api
+ @ingroup pugl_c
@{
*/
diff --git a/pugl/pugl/pugl_cairo.hpp b/pugl/pugl/pugl_cairo.hpp
new file mode 100644
index 00000000..5b17ab71
--- /dev/null
+++ b/pugl/pugl/pugl_cairo.hpp
@@ -0,0 +1,50 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_cairo.hpp
+ @brief Declaration of Cairo backend accessor for C++.
+*/
+
+#ifndef PUGL_PUGL_CAIRO_HPP
+#define PUGL_PUGL_CAIRO_HPP
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_cairo.h"
+
+namespace pugl {
+
+/**
+ @defgroup cairoxx Cairo
+ Cairo graphics support.
+ @ingroup pugl_cxx
+ @{
+*/
+
+/// @copydoc puglCairoBackend
+static inline const PuglBackend*
+cairoBackend()
+{
+ return puglCairoBackend();
+}
+
+/**
+ @}
+*/
+
+} // namespace pugl
+
+#endif // PUGL_PUGL_CAIRO_HPP
diff --git a/pugl/pugl/pugl_gl.h b/pugl/pugl/pugl_gl.h
index 9c5fa945..d501b3c1 100644
--- a/pugl/pugl/pugl_gl.h
+++ b/pugl/pugl/pugl_gl.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl_gl.h OpenGL-specific API.
+ @file pugl_gl.h
+ @brief OpenGL-specific API.
*/
#ifndef PUGL_PUGL_GL_H
@@ -28,7 +29,7 @@ PUGL_BEGIN_DECLS
/**
@defgroup gl OpenGL
OpenGL graphics support.
- @ingroup pugl_api
+ @ingroup pugl_c
@{
*/
diff --git a/pugl/pugl/pugl_gl.hpp b/pugl/pugl/pugl_gl.hpp
new file mode 100644
index 00000000..4bc5bbd5
--- /dev/null
+++ b/pugl/pugl/pugl_gl.hpp
@@ -0,0 +1,60 @@
+/*
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_gl.hpp
+ @brief OpenGL-specific C++ API.
+*/
+
+#ifndef PUGL_PUGL_GL_HPP
+#define PUGL_PUGL_GL_HPP
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_gl.h"
+
+namespace pugl {
+
+/**
+ @defgroup glxx OpenGL
+ OpenGL graphics support.
+ @ingroup pugl_cxx
+ @{
+*/
+
+/// @copydoc PuglGlFunc
+using GlFunc = PuglGlFunc;
+
+/// @copydoc puglGetProcAddress
+static inline GlFunc
+getProcAddress(const char* name)
+{
+ return puglGetProcAddress(name);
+}
+
+/// @copydoc puglGlBackend
+static inline const PuglBackend*
+glBackend()
+{
+ return puglGlBackend();
+}
+
+/**
+ @}
+*/
+
+} // namespace pugl
+
+#endif // PUGL_PUGL_GL_HPP
diff --git a/pugl/pugl/pugl_stub.h b/pugl/pugl/pugl_stub.h
index da918aac..ef480005 100644
--- a/pugl/pugl/pugl_stub.h
+++ b/pugl/pugl/pugl_stub.h
@@ -1,5 +1,5 @@
/*
- Copyright 2019 David Robillard <http://drobilla.net>
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,8 @@
*/
/**
- @file pugl_stub.h Stub backend functions and accessor declaration.
+ @file pugl_stub.h
+ @brief Stub backend functions and accessor declaration.
*/
#ifndef PUGL_PUGL_STUB_H
@@ -36,7 +37,7 @@ PUGL_BEGIN_DECLS
for other backends to reuse since not all need non-trivial implementations
of every backend function.
- @ingroup pugl_api
+ @ingroup pugl_c
@{
*/
@@ -50,50 +51,6 @@ PUGL_API
const PuglBackend*
puglStubBackend(void);
-static inline PuglStatus
-puglStubConfigure(PuglView* view)
-{
- (void)view;
- return PUGL_SUCCESS;
-}
-
-static inline PuglStatus
-puglStubCreate(PuglView* view)
-{
- (void)view;
- return PUGL_SUCCESS;
-}
-
-static inline PuglStatus
-puglStubDestroy(PuglView* view)
-{
- (void)view;
- return PUGL_SUCCESS;
-}
-
-static inline PuglStatus
-puglStubEnter(PuglView* view, const PuglEventExpose* expose)
-{
- (void)view;
- (void)expose;
- return PUGL_SUCCESS;
-}
-
-static inline PuglStatus
-puglStubLeave(PuglView* view, const PuglEventExpose* expose)
-{
- (void)view;
- (void)expose;
- return PUGL_SUCCESS;
-}
-
-static inline void*
-puglStubGetContext(PuglView* view)
-{
- (void)view;
- return NULL;
-}
-
/**
@}
*/
diff --git a/pugl/pugl/pugl_stub_backend.h b/pugl/pugl/pugl_stub.hpp
index e5aa5136..c5f3901b 100644
--- a/pugl/pugl/pugl_stub_backend.h
+++ b/pugl/pugl/pugl_stub.hpp
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -14,10 +14,37 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef PUGL_PUGL_STUB_BACKEND_H
-#define PUGL_PUGL_STUB_BACKEND_H
+/**
+ @file pugl_stub.hpp
+ @brief Declaration of Stub backend accessor for C++.
+*/
+
+#ifndef PUGL_PUGL_STUB_HPP
+#define PUGL_PUGL_STUB_HPP
-#warning "This header is deprecated, use pugl/pugl_stub.h instead."
+#include "pugl/pugl.h"
#include "pugl/pugl_stub.h"
-#endif // PUGL_PUGL_STUB_BACKEND_H
+namespace pugl {
+
+/**
+ @defgroup stubxx Stub
+ Stub graphics support.
+ @ingroup pugl_cxx
+ @{
+*/
+
+/// @copydoc puglStubBackend
+static inline const PuglBackend*
+stubBackend()
+{
+ return puglStubBackend();
+}
+
+/**
+ @}
+*/
+
+} // namespace pugl
+
+#endif // PUGL_PUGL_STUB_HPP
diff --git a/pugl/shaders/header_330.glsl b/pugl/shaders/header_330.glsl
new file mode 100644
index 00000000..bfe7a00f
--- /dev/null
+++ b/pugl/shaders/header_330.glsl
@@ -0,0 +1,5 @@
+#version 330 core
+
+#define INTER(qualifiers)
+#define UBO(qualifiers) layout(std140)
+
diff --git a/pugl/shaders/header_420.glsl b/pugl/shaders/header_420.glsl
new file mode 100644
index 00000000..55fbe8ac
--- /dev/null
+++ b/pugl/shaders/header_420.glsl
@@ -0,0 +1,5 @@
+#version 420 core
+
+#define INTER(qualifiers) layout(qualifiers)
+#define UBO(qualifiers) layout(std140, qualifiers)
+
diff --git a/pugl/shaders/rect.frag b/pugl/shaders/rect.frag
index 5e3af9da..ecec50d5 100644
--- a/pugl/shaders/rect.frag
+++ b/pugl/shaders/rect.frag
@@ -1,5 +1,3 @@
-#version 330 core
-
/* The fragment shader uses the UV coordinates to calculate whether it is in
the T, R, B, or L border. These are then mixed with the border color, and
their inverse is mixed with the fill color, to calculate the fragment color.
@@ -10,9 +8,9 @@
specified precisely in pixels to draw sharp lines. The border width is just
hardcoded, but could be made a uniform or vertex attribute easily enough. */
-noperspective in vec2 f_uv;
-noperspective in vec2 f_size;
-noperspective in vec4 f_fillColor;
+INTER(location = 0) noperspective in vec2 f_uv;
+INTER(location = 1) noperspective in vec2 f_size;
+INTER(location = 2) noperspective in vec4 f_fillColor;
layout(location = 0) out vec4 FragColor;
diff --git a/pugl/shaders/rect.vert b/pugl/shaders/rect.vert
index bf2e951e..09f1917f 100644
--- a/pugl/shaders/rect.vert
+++ b/pugl/shaders/rect.vert
@@ -1,18 +1,19 @@
-#version 330 core
-
/* The vertex shader is trivial, but forwards scaled UV coordinates (in pixels)
to the fragment shader for drawing the border. */
-uniform mat4 u_projection;
+UBO(binding = 0) uniform UniformBufferObject
+{
+ mat4 projection;
+} ubo;
layout(location = 0) in vec2 v_position;
layout(location = 1) in vec2 v_origin;
layout(location = 2) in vec2 v_size;
layout(location = 3) in vec4 v_fillColor;
-noperspective out vec2 f_uv;
-noperspective out vec2 f_size;
-noperspective out vec4 f_fillColor;
+INTER(location = 0) noperspective out vec2 f_uv;
+INTER(location = 1) noperspective out vec2 f_size;
+INTER(location = 2) noperspective out vec4 f_fillColor;
void
main()
@@ -24,7 +25,7 @@ main()
v_origin[0], v_origin[1], 0.0, 1.0);
// clang-format on
- mat4 MVP = u_projection * m;
+ mat4 MVP = ubo.projection * m;
f_uv = v_position * v_size;
f_size = v_size;
diff --git a/pugl/pugl/pugl_cairo_backend.h b/pugl/test/test_build.c
index 3f8cec3d..de2ed28a 100644
--- a/pugl/pugl/pugl_cairo_backend.h
+++ b/pugl/test/test_build.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -14,10 +14,21 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef PUGL_PUGL_CAIRO_BACKEND_H
-#define PUGL_PUGL_CAIRO_BACKEND_H
+/*
+ Tests that C headers compile without any warnings.
+*/
+
+#define PUGL_DISABLE_DEPRECATED
-#warning "This header is deprecated, use pugl/pugl_cairo.h instead."
+#include "pugl/gl.h"
+#include "pugl/glu.h"
+#include "pugl/pugl.h"
#include "pugl/pugl_cairo.h"
+#include "pugl/pugl_gl.h"
+#include "pugl/pugl_stub.h"
-#endif // PUGL_PUGL_CAIRO_BACKEND_H
+int
+main(void)
+{
+ return 0;
+}
diff --git a/pugl/pugl/pugl_gl_backend.h b/pugl/test/test_build.cpp
index e1b9a152..79f1dfce 100644
--- a/pugl/pugl/pugl_gl_backend.h
+++ b/pugl/test/test_build.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -14,10 +14,23 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef PUGL_PUGL_GL_BACKEND_H
-#define PUGL_PUGL_GL_BACKEND_H
+/*
+ Tests that C++ headers compile without any warnings.
+*/
+
+#define PUGL_DISABLE_DEPRECATED
-#warning "This header is deprecated, use pugl/pugl_gl.h instead."
-#include "pugl/pugl_gl.h"
+#include "pugl/gl.h"
+#include "pugl/glu.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl.hpp"
+#include "pugl/pugl.ipp"
+#include "pugl/pugl_cairo.hpp"
+#include "pugl/pugl_gl.hpp"
+#include "pugl/pugl_stub.hpp"
-#endif // PUGL_PUGL_GL_BACKEND_H
+int
+main()
+{
+ return 0;
+}
diff --git a/pugl/test/test_redisplay.c b/pugl/test/test_redisplay.c
index 75006cb5..91b606fd 100644
--- a/pugl/test/test_redisplay.c
+++ b/pugl/test/test_redisplay.c
@@ -1,5 +1,5 @@
/*
- Copyright 2020 David Robillard <http://drobilla.net>
+ Copyright 2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -47,13 +47,13 @@ typedef enum {
typedef struct
{
- PuglTestOptions opts;
PuglWorld* world;
PuglView* view;
+ PuglTestOptions opts;
State state;
} PuglTest;
-static const PuglRect redisplayRect = {1, 2, 3, 4};
+static const PuglRect redisplayRect = {2, 4, 8, 16};
static const uintptr_t postRedisplayId = 42;
static PuglStatus
@@ -66,6 +66,13 @@ onEvent(PuglView* view, const PuglEvent* event)
}
switch (event->type) {
+ case PUGL_UPDATE:
+ if (test->state == SHOULD_REDISPLAY) {
+ puglPostRedisplayRect(view, redisplayRect);
+ test->state = POSTED_REDISPLAY;
+ }
+ break;
+
case PUGL_EXPOSE:
if (test->state == START) {
test->state = EXPOSED;
@@ -80,8 +87,7 @@ onEvent(PuglView* view, const PuglEvent* event)
case PUGL_CLIENT:
if (event->client.data1 == postRedisplayId) {
- puglPostRedisplayRect(view, redisplayRect);
- test->state = POSTED_REDISPLAY;
+ test->state = SHOULD_REDISPLAY;
}
break;
@@ -94,9 +100,9 @@ onEvent(PuglView* view, const PuglEvent* event)
int
main(int argc, char** argv)
{
- PuglTest app = {puglParseTestOptions(&argc, &argv),
- puglNewWorld(PUGL_PROGRAM, 0),
+ PuglTest app = {puglNewWorld(PUGL_PROGRAM, 0),
NULL,
+ puglParseTestOptions(&argc, &argv),
START};
// Set up view
@@ -105,6 +111,7 @@ main(int argc, char** argv)
puglSetBackend(app.view, puglStubBackend());
puglSetHandle(app.view, &app);
puglSetEventFunc(app.view, onEvent);
+ puglSetDefaultSize(app.view, 512, 512);
// Create and show window
assert(!puglRealize(app.view));
@@ -114,8 +121,10 @@ main(int argc, char** argv)
}
// Send a custom event to trigger a redisplay in the event loop
- const PuglEventClient client = { PUGL_CLIENT, 0, postRedisplayId, 0 };
- assert(!puglSendEvent(app.view, (const PuglEvent*)&client));
+ PuglEvent client_event = {{PUGL_CLIENT, 0}};
+ client_event.client.data1 = postRedisplayId;
+ client_event.client.data2 = 0;
+ assert(!puglSendEvent(app.view, &client_event));
// Loop until an expose happens in the same iteration as the redisplay
app.state = SHOULD_REDISPLAY;
diff --git a/pugl/test/test_show_hide.c b/pugl/test/test_show_hide.c
index cc2c9722..ebbbee98 100644
--- a/pugl/test/test_show_hide.c
+++ b/pugl/test/test_show_hide.c
@@ -1,5 +1,5 @@
/*
- Copyright 2020 David Robillard <http://drobilla.net>
+ Copyright 2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -41,9 +41,9 @@ typedef enum {
} State;
typedef struct {
- PuglTestOptions opts;
PuglWorld* world;
PuglView* view;
+ PuglTestOptions opts;
State state;
} PuglTest;
@@ -71,7 +71,7 @@ onEvent(PuglView* view, const PuglEvent* event)
test->state = MAPPED;
break;
case PUGL_EXPOSE:
- assert(test->state == MAPPED);
+ assert(test->state == MAPPED || test->state == EXPOSED);
test->state = EXPOSED;
break;
case PUGL_UNMAP:
@@ -104,9 +104,9 @@ tick(PuglWorld* world)
int
main(int argc, char** argv)
{
- PuglTest test = {puglParseTestOptions(&argc, &argv),
- puglNewWorld(PUGL_PROGRAM, 0),
+ PuglTest test = {puglNewWorld(PUGL_PROGRAM, 0),
NULL,
+ puglParseTestOptions(&argc, &argv),
START};
// Set up view
@@ -115,6 +115,7 @@ main(int argc, char** argv)
puglSetBackend(test.view, puglStubBackend());
puglSetHandle(test.view, &test);
puglSetEventFunc(test.view, onEvent);
+ puglSetDefaultSize(test.view, 512, 512);
// Create initially invisible window
assert(!puglRealize(test.view));
diff --git a/pugl/test/test_timer.c b/pugl/test/test_timer.c
index 58d06528..d567da0e 100644
--- a/pugl/test/test_timer.c
+++ b/pugl/test/test_timer.c
@@ -1,5 +1,5 @@
/*
- Copyright 2020 David Robillard <http://drobilla.net>
+ Copyright 2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -55,9 +55,9 @@ typedef enum {
} State;
typedef struct {
- PuglTestOptions opts;
PuglWorld* world;
PuglView* view;
+ PuglTestOptions opts;
size_t numAlarms;
State state;
} PuglTest;
@@ -97,9 +97,9 @@ roundPeriod(const double period)
int
main(int argc, char** argv)
{
- PuglTest app = {puglParseTestOptions(&argc, &argv),
- puglNewWorld(PUGL_PROGRAM, 0),
+ PuglTest app = {puglNewWorld(PUGL_PROGRAM, 0),
NULL,
+ puglParseTestOptions(&argc, &argv),
0,
START};
@@ -109,6 +109,7 @@ main(int argc, char** argv)
puglSetBackend(app.view, puglStubBackend());
puglSetHandle(app.view, &app);
puglSetEventFunc(app.view, onEvent);
+ puglSetDefaultSize(app.view, 512, 512);
// Create and show window
assert(!puglRealize(app.view));
diff --git a/pugl/test/test_update.c b/pugl/test/test_update.c
index 081fb9b3..bdcb28be 100644
--- a/pugl/test/test_update.c
+++ b/pugl/test/test_update.c
@@ -1,5 +1,5 @@
/*
- Copyright 2020 David Robillard <http://drobilla.net>
+ Copyright 2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -44,9 +44,9 @@ typedef enum {
} State;
typedef struct {
- PuglTestOptions opts;
PuglWorld* world;
PuglView* view;
+ PuglTestOptions opts;
State state;
} PuglTest;
@@ -90,9 +90,9 @@ onEvent(PuglView* view, const PuglEvent* event)
int
main(int argc, char** argv)
{
- PuglTest app = {puglParseTestOptions(&argc, &argv),
- puglNewWorld(PUGL_PROGRAM, 0),
+ PuglTest app = {puglNewWorld(PUGL_PROGRAM, 0),
NULL,
+ puglParseTestOptions(&argc, &argv),
START};
// Set up view
@@ -101,6 +101,7 @@ main(int argc, char** argv)
puglSetBackend(app.view, puglStubBackend());
puglSetHandle(app.view, &app);
puglSetEventFunc(app.view, onEvent);
+ puglSetDefaultSize(app.view, 512, 512);
// Create and show window
assert(!puglRealize(app.view));
diff --git a/pugl/test/test_utils.h b/pugl/test/test_utils.h
index 7d33601e..977fba5f 100644
--- a/pugl/test/test_utils.h
+++ b/pugl/test/test_utils.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2020 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -67,6 +67,40 @@ printModifiers(const uint32_t mods)
(mods & PUGL_MOD_SUPER) ? " Super" : "");
}
+static inline const char*
+crossingModeString(const PuglCrossingMode mode)
+{
+ switch (mode) {
+ case PUGL_CROSSING_NORMAL:
+ return "normal";
+ case PUGL_CROSSING_GRAB:
+ return "grab";
+ case PUGL_CROSSING_UNGRAB:
+ return "ungrab";
+ }
+
+ return "unknown";
+}
+
+static inline const char*
+scrollDirectionString(const PuglScrollDirection direction)
+{
+ switch (direction) {
+ case PUGL_SCROLL_UP:
+ return "up";
+ case PUGL_SCROLL_DOWN:
+ return "down";
+ case PUGL_SCROLL_LEFT:
+ return "left";
+ case PUGL_SCROLL_RIGHT:
+ return "right";
+ case PUGL_SCROLL_SMOOTH:
+ return "smooth";
+ }
+
+ return "unknown";
+}
+
static inline int
printEvent(const PuglEvent* event, const char* prefix, const bool verbose)
{
@@ -103,31 +137,34 @@ printEvent(const PuglEvent* event, const char* prefix, const bool verbose)
event->button.y) +
printModifiers(event->scroll.state));
case PUGL_SCROLL:
- return (PRINT("%sScroll %5.1f %5.1f at " PFMT " ",
+ return (PRINT("%sScroll %5.1f %5.1f (%s) at " PFMT " ",
prefix,
event->scroll.dx,
event->scroll.dy,
+ scrollDirectionString(event->scroll.direction),
event->scroll.x,
event->scroll.y) +
printModifiers(event->scroll.state));
case PUGL_POINTER_IN:
- return PRINT("%sMouse enter at " PFMT "\n",
+ return PRINT("%sMouse enter at " PFMT " (%s)\n",
prefix,
event->crossing.x,
- event->crossing.y);
+ event->crossing.y,
+ crossingModeString(event->crossing.mode));
case PUGL_POINTER_OUT:
- return PRINT("%sMouse leave at " PFMT "\n",
+ return PRINT("%sMouse leave at " PFMT " (%s)\n",
prefix,
event->crossing.x,
- event->crossing.y);
+ event->crossing.y,
+ crossingModeString(event->crossing.mode));
case PUGL_FOCUS_IN:
- return PRINT("%sFocus in%s\n",
+ return PRINT("%sFocus in (%s)\n",
prefix,
- event->focus.grab ? " (grab)" : "");
+ crossingModeString(event->crossing.mode));
case PUGL_FOCUS_OUT:
- return PRINT("%sFocus out%s\n",
+ return PRINT("%sFocus out (%s)\n",
prefix,
- event->focus.grab ? " (ungrab)" : "");
+ crossingModeString(event->crossing.mode));
case PUGL_CLIENT:
return PRINT("%sClient %" PRIXPTR " %" PRIXPTR "\n",
prefix,
@@ -173,8 +210,7 @@ printEvent(const PuglEvent* event, const char* prefix, const bool verbose)
event->motion.x,
event->motion.y);
default:
- fprintf(stderr, "%sUnknown event type %u\n", prefix, event->type);
- break;
+ return PRINT("%sUnknown event type %d\n", prefix, (int)event->type);
}
}
diff --git a/pugl/wscript b/pugl/wscript
index 2fc48dea..f799ddd4 100644
--- a/pugl/wscript
+++ b/pugl/wscript
@@ -3,7 +3,7 @@
import os
import sys
-from waflib import Logs, Options, TaskGen
+from waflib import Build, Logs, Options, TaskGen
from waflib.extras import autowaf
# Library and package version (UNIX style major, minor, micro)
@@ -22,6 +22,7 @@ out = 'build' # Build directory
def options(ctx):
ctx.load('compiler_c')
+ ctx.load('compiler_cxx')
opts = ctx.configuration_options()
opts.add_option('--target', default=None, dest='target',
@@ -41,46 +42,102 @@ def options(ctx):
def configure(conf):
conf.load('compiler_c', cache=True)
+ try:
+ conf.load('compiler_cxx', cache=True)
+ except Exception:
+ pass
+
conf.load('autowaf', cache=True)
autowaf.set_c_lang(conf, 'c99')
+ if 'COMPILER_CXX' in conf.env:
+ autowaf.set_cxx_lang(conf, 'c++11')
conf.env.ALL_HEADERS = Options.options.all_headers
conf.env.TARGET_PLATFORM = Options.options.target or sys.platform
platform = conf.env.TARGET_PLATFORM
- if platform == 'darwin':
- conf.env.append_unique('CFLAGS', ['-Wno-deprecated-declarations'])
+ if Options.options.strict:
+ # Check for programs used by lint target
+ conf.find_program("flake8", var="FLAKE8", mandatory=False)
+ conf.find_program("clang-tidy", var="CLANG_TIDY", mandatory=False)
+ conf.find_program("iwyu_tool", var="IWYU_TOOL", mandatory=False)
- if conf.env.MSVC_COMPILER:
- conf.env.append_unique('CFLAGS', ['/wd4191'])
- else:
- conf.env.append_value('LINKFLAGS', ['-fvisibility=hidden'])
- conf.env.append_value('CFLAGS', ['-fvisibility=hidden'])
- if Options.options.strict:
- conf.env.append_value('CFLAGS', ['-Wunused-parameter',
- '-Wno-pedantic'])
-
- if conf.env.TARGET_PLATFORM == 'darwin':
- conf.env.append_unique('CFLAGS', ['-DGL_SILENCE_DEPRECATION'])
- conf.env.append_unique('CXXFLAGS', ['-DGL_SILENCE_DEPRECATION'])
-
- if Options.options.ultra_strict and 'clang' in conf.env.CC:
- for var in ['CFLAGS', 'CXXFLAGS']:
- flags = conf.env[var]
- conf.env[var] = [f for f in flags if not f.startswith('-W')]
- conf.env.append_value(var, [
- '-Weverything',
- '-Wno-bad-function-cast',
- '-Wno-float-equal',
- '-Wno-format-nonliteral',
+ if Options.options.ultra_strict:
+ # All warnings enabled by autowaf, disable some we trigger
+
+ autowaf.add_compiler_flags(conf.env, '*', {
+ 'clang': [
'-Wno-padded',
'-Wno-reserved-id-macro',
'-Wno-switch-enum',
- ])
-
- conf.env.append_value('CXXFLAGS', ['-Wno-c++98-compat',
- '-Wno-c++98-compat-pedantic'])
-
+ ],
+ 'gcc': [
+ '-Wno-padded',
+ '-Wno-switch-enum',
+ ],
+ 'msvc': [
+ '/wd4061', # enumerator in switch is not explicitly handled
+ '/wd4514', # unreferenced inline function has been removed
+ '/wd4820', # padding added after construct
+ '/wd5045', # will insert Spectre mitigation for memory load
+ ],
+ })
+
+ autowaf.add_compiler_flags(conf.env, 'c', {
+ 'clang': [
+ '-Wno-bad-function-cast',
+ '-Wno-float-equal',
+ '-Wno-implicit-fallthrough',
+ ],
+ 'gcc': [
+ '-Wno-bad-function-cast',
+ '-Wno-float-equal',
+ ],
+ 'msvc': [
+ '/wd4191', # unsafe conversion from type to type
+ '/wd4706', # assignment within conditional expression
+ '/wd4710', # function not inlined
+ '/wd5045', # will insert Spectre mitigation for memory load
+ ],
+ })
+
+ autowaf.add_compiler_flags(conf.env, 'cxx', {
+ 'clang': [
+ '-Wno-documentation-unknown-command',
+ '-Wno-old-style-cast',
+ ],
+ 'gcc': [
+ '-Wno-old-style-cast',
+ ],
+ 'msvc': [
+ '/wd4355', # 'this' used in base member initializer list
+ '/wd4571', # structured exceptions (SEH) are no longer caught
+ '/wd4625', # copy constructor implicitly deleted
+ '/wd4626', # assignment operator implicitly deleted
+ '/wd5026', # move constructor implicitly deleted
+ '/wd5027', # move assignment operator implicitly deleted
+ ],
+ })
+
+ # Add some platform-specific warning suppressions
+ if conf.env.TARGET_PLATFORM == "win32":
+ autowaf.add_compiler_flags(conf.env, '*', {
+ 'gcc': ['-Wno-cast-function-type',
+ '-Wno-conversion',
+ '-Wno-format',
+ '-Wno-suggest-attribute=format'],
+ })
+ elif conf.env.TARGET_PLATFORM == 'darwin':
+ autowaf.add_compiler_flags(conf.env, '*', {
+ 'clang': ['-DGL_SILENCE_DEPRECATION',
+ '-Wno-deprecated-declarations',
+ '-Wno-direct-ivar-access'],
+ 'gcc': ['-DGL_SILENCE_DEPRECATION',
+ '-Wno-deprecated-declarations',
+ '-Wno-direct-ivar-access'],
+ })
+
+ # Check for base system libraries needed on some systems
conf.check_cc(lib='m', uselib_store='M', mandatory=False)
conf.check_cc(lib='dl', uselib_store='DL', mandatory=False)
@@ -114,6 +171,11 @@ def configure(conf):
msg='Checking for function XSyncQueryExtension'):
conf.define('HAVE_XSYNC', 1)
+ if conf.check_cc(lib='Xcursor',
+ uselib_store='XCURSOR',
+ mandatory=False):
+ conf.define('HAVE_XCURSOR', 1)
+
if not Options.options.no_gl:
glx_fragment = """#include <GL/glx.h>
int main(void) { glXSwapBuffers(0, 0); return 0; }"""
@@ -138,7 +200,18 @@ def configure(conf):
'BUILD_SHARED': not Options.options.no_shared,
'BUILD_STATIC': conf.env.BUILD_TESTS or not Options.options.no_static})
- autowaf.set_lib_env(conf, 'pugl', PUGL_VERSION)
+ if conf.env.TARGET_PLATFORM == 'win32':
+ conf.env.PUGL_PLATFORM = 'win'
+ elif conf.env.TARGET_PLATFORM == 'darwin':
+ conf.env.PUGL_PLATFORM = 'mac'
+ else:
+ conf.env.PUGL_PLATFORM = 'x11'
+
+ autowaf.set_lib_env(conf, 'pugl', PUGL_VERSION,
+ lib='pugl_' + conf.env.PUGL_PLATFORM)
+
+ autowaf.set_lib_env(conf, 'pugl_gl', PUGL_VERSION,
+ lib='pugl_%s_gl' % conf.env.PUGL_PLATFORM)
autowaf.display_summary(
conf,
@@ -189,6 +262,7 @@ def build(bld):
includedir = '${INCLUDEDIR}/pugl-%s/pugl' % PUGL_MAJOR_VERSION
bld.install_files(includedir, bld.path.ant_glob('pugl/*.h'))
bld.install_files(includedir, bld.path.ant_glob('pugl/*.hpp'))
+ bld.install_files(includedir, bld.path.ant_glob('pugl/*.ipp'))
if bld.env.ALL_HEADERS:
detaildir = os.path.join(includedir, 'detail')
bld.install_files(detaildir, bld.path.ant_glob('pugl/detail/*.h'))
@@ -208,18 +282,26 @@ def build(bld):
'install_path': '${LIBDIR}',
'vnum': PUGL_VERSION})
+ flags = []
+ if not bld.env.MSVC_COMPILER:
+ flags = ['-fPIC', '-fvisibility=hidden']
+
if bld.env.BUILD_SHARED:
- bld(features = 'c cshlib',
- name = name,
- target = 'pugl_' + name,
- defines = ['PUGL_INTERNAL', 'PUGL_SHARED'],
+ bld(features = 'c cshlib',
+ name = name,
+ target = 'pugl_%s-%s' % (name, PUGL_MAJOR_VERSION),
+ defines = ['PUGL_INTERNAL', 'PUGL_SHARED'],
+ cflags = flags,
+ linkflags = flags,
**args)
if bld.env.BUILD_STATIC:
- bld(features = 'c cstlib',
- name = 'pugl_%s_static' % name,
- target = 'pugl_' + name,
- defines = ['PUGL_INTERNAL', 'PUGL_DISABLE_DEPRECATED'],
+ bld(features = 'c cstlib',
+ name = 'pugl_%s_static' % name,
+ target = 'pugl_%s-%s' % (name, PUGL_MAJOR_VERSION),
+ defines = ['PUGL_INTERNAL', 'PUGL_DISABLE_DEPRECATED'],
+ cflags = flags,
+ linkflags = flags,
**args)
def build_platform(platform, **kwargs):
@@ -278,7 +360,7 @@ def build(bld):
else:
platform = 'x11'
build_platform('x11',
- uselib=['M', 'X11', 'XSYNC'],
+ uselib=['M', 'X11', 'XSYNC', 'XCURSOR'],
source=lib_source + ['pugl/detail/x11.c'])
if bld.env.HAVE_GL:
@@ -293,6 +375,8 @@ def build(bld):
source=['pugl/detail/x11_cairo.c'])
def build_example(prog, source, platform, backend, **kwargs):
+ lang = 'cxx' if source[0].endswith('.cpp') else 'c'
+
use = ['pugl_%s_static' % platform,
'pugl_%s_%s_static' % (platform, backend)]
@@ -312,7 +396,7 @@ def build(bld):
deps.get(platform, {}).get(k, []) +
deps.get(backend_lib, {}).get(k, []))})
- bld(features = 'c cprogram',
+ bld(features = '%s %sprogram' % (lang, lang),
source = source,
target = target,
use = use,
@@ -328,16 +412,22 @@ def build(bld):
target = 'shaders/%s' % s)
if bld.env.HAVE_GL:
+ glad_cflags = [] if bld.env.MSVC_COMPILER else ['-Wno-pedantic']
build_example('pugl_embed_demo', ['examples/pugl_embed_demo.c'],
platform, 'gl', uselib=['GL', 'M'])
build_example('pugl_window_demo', ['examples/pugl_window_demo.c'],
platform, 'gl', uselib=['GL', 'M'])
+ build_example('pugl_cursor_demo', ['examples/pugl_cursor_demo.c'],
+ platform, 'gl', uselib=['GL', 'M'])
build_example('pugl_print_events',
['examples/pugl_print_events.c'],
platform, 'stub')
- build_example('pugl_gl3_demo',
- ['examples/pugl_gl3_demo.c', 'examples/glad/glad.c'],
- platform, 'gl', uselib=['DL', 'GL', 'M'])
+ build_example('pugl_shader_demo',
+ ['examples/pugl_shader_demo.c',
+ 'examples/glad/glad.c'],
+ platform, 'gl',
+ cflags=glad_cflags,
+ uselib=['DL', 'GL', 'M'])
if bld.env.HAVE_CAIRO:
build_example('pugl_cairo_demo', ['examples/pugl_cairo_demo.c'],
@@ -353,6 +443,43 @@ def build(bld):
'pugl_%s_stub_static' % platform],
uselib = deps[platform]['uselib'] + ['CAIRO'])
+ # Make a hyper strict warning environment for checking API headers
+ strict_env = bld.env.derive()
+ autowaf.remove_all_warning_flags(strict_env)
+ autowaf.enable_all_warnings(strict_env)
+ autowaf.set_warnings_as_errors(strict_env)
+ autowaf.add_compiler_flags(strict_env, '*', {
+ 'clang': ['-Wno-padded'],
+ 'gcc': ['-Wno-padded'],
+ })
+ autowaf.add_compiler_flags(strict_env, 'cxx', {
+ 'clang': ['-Wno-documentation-unknown-command'],
+ })
+
+ # Check that C headers build with (almost) no warnings
+ bld(features = 'c cprogram',
+ source = 'test/test_build.c',
+ target = 'test/test_build_c',
+ install_path = '',
+ env = strict_env,
+ use = ['pugl_%s_static' % platform],
+ uselib = deps[platform]['uselib'] + ['CAIRO'])
+
+ # Check that C++ headers build with (almost) no warnings
+ bld(features = 'cxx cxxprogram',
+ source = 'test/test_build.cpp',
+ target = 'test/test_build_cpp',
+ install_path = '',
+ env = strict_env,
+ use = ['pugl_%s_static' % platform],
+ uselib = deps[platform]['uselib'] + ['CAIRO'])
+
+ if bld.env.CXX and bld.env.HAVE_GL:
+ build_example('pugl_cxx_demo', ['examples/pugl_cxx_demo.cpp'],
+ platform, 'gl',
+ defines=['PUGL_DISABLE_DEPRECATED'],
+ uselib=['GL', 'M'])
+
if bld.env.DOCS:
autowaf.build_dox(bld, 'PUGL', PUGL_VERSION, top, out)
@@ -364,25 +491,73 @@ def test(tst):
check(['test/test_%s' % test])
+class LintContext(Build.BuildContext):
+ fun = cmd = 'lint'
+
+
def lint(ctx):
"checks code for style issues"
import json
import subprocess
- subprocess.call("flake8 wscript --ignore E221,W504,E251,E241",
- shell=True)
+ st = 0
- with open('build/compile_commands.json', 'r') as db:
- commands = json.load(db)
- files = [c['file'] for c in commands
- if os.path.basename(c['file']) != 'glad.c']
+ if "FLAKE8" in ctx.env:
+ Logs.info("Running flake8")
+ st = subprocess.call([ctx.env.FLAKE8[0],
+ "wscript",
+ "--ignore",
+ "E221,W504,E251,E241,E741"])
+ else:
+ Logs.warn("Not running flake8")
+
+ if "IWYU_TOOL" in ctx.env:
+ Logs.info("Running include-what-you-use")
+ cmd = [ctx.env.IWYU_TOOL[0], "-o", "clang", "-p", "build"]
+ output = subprocess.check_output(cmd).decode('utf-8')
+ if 'error: ' in output:
+ sys.stdout.write(output)
+ st += 1
+ else:
+ Logs.warn("Not running include-what-you-use")
+
+ if "CLANG_TIDY" in ctx.env and "clang" in ctx.env.CC[0]:
+ Logs.info("Running clang-tidy")
+ with open('build/compile_commands.json', 'r') as db:
+ commands = json.load(db)
+ files = [c['file'] for c in commands
+ if os.path.basename(c['file']) != 'glad.c']
+
+ c_files = [os.path.join('build', f)
+ for f in files if f.endswith('.c')]
+
+ cpp_files = [os.path.join('build', f)
+ for f in files if f.endswith('.cpp')]
+
+ c_files = list(map(os.path.abspath, c_files))
+ cpp_files = list(map(os.path.abspath, cpp_files))
+
+ procs = []
+ for c_file in c_files:
+ cmd = [ctx.env.CLANG_TIDY[0], "--quiet", "-p=.", c_file]
+ procs += [subprocess.Popen(cmd, cwd="build")]
+
+ for cpp_file in cpp_files:
+ cmd = [ctx.env.CLANG_TIDY[0],
+ '--header-filter=".*\\.hpp"',
+ "--quiet",
+ "-p=.", cpp_file]
+ procs += [subprocess.Popen(cmd, cwd="build")]
+
+ for proc in procs:
+ stdout, stderr = proc.communicate()
+ st += proc.returncode
+ else:
+ Logs.warn("Not running clang-tidy")
- subprocess.call(['clang-tidy'] + files, cwd='build')
+ if st != 0:
+ sys.exit(st)
- try:
- subprocess.call(['iwyu_tool.py', '-o', 'clang', '-p', 'build'])
- except Exception:
- Logs.warn('Failed to call iwyu_tool.py')
# Alias .m files to be compiled like .c files, gcc will do the right thing.
@TaskGen.extension('.m')
diff --git a/src/backend_cairo.c b/src/backend_cairo.c
index af002ba8..0fe1cbd7 100644
--- a/src/backend_cairo.c
+++ b/src/backend_cairo.c
@@ -180,7 +180,7 @@ d2tk_cairo_post(void *data, d2tk_core_t *core __attribute__((unused)),
return true; // do enter 2nd pass
}
-#ifdef D2TK_DEBUG //FIXME needs multiple buffers to work
+#if D2TK_DEBUG //FIXME needs multiple buffers to work
{
d2tk_rect_t rect;
uint32_t *pixels = d2tk_core_get_pixels(core, &rect);
@@ -369,6 +369,23 @@ _d2tk_cairo_surf_draw(cairo_t *ctx, cairo_surface_t *surf, d2tk_coord_t xo,
cairo_paint(ctx);
}
+static inline char *
+_absolute_path(d2tk_backend_cairo_t *backend, const char *rel)
+{
+ char *abs = NULL;
+
+ if(rel[0] == '/')
+ {
+ assert(asprintf(&abs, "%s", rel) != -1);
+ }
+ else
+ {
+ assert(asprintf(&abs, "%s%s", backend->bundle_path, rel) != -1);
+ }
+
+ return abs;
+}
+
static inline void
d2tk_cairo_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
d2tk_coord_t xo, d2tk_coord_t yo, const d2tk_clip_t *clip, unsigned pass)
@@ -531,7 +548,7 @@ d2tk_cairo_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
if(!*sprite)
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
//fprintf(stderr, "\tcreating sprite\n");
#endif
const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32,
@@ -560,7 +577,7 @@ d2tk_cairo_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
}
else
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
//fprintf(stderr, "\texisting sprite\n");
#endif
}
@@ -716,40 +733,48 @@ d2tk_cairo_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
if(!*sprite)
{
- char *img_path = NULL;
- assert(asprintf(&img_path, "%s%s", backend->bundle_path, body->path) != -1);
+ char *img_path = _absolute_path(backend, body->path);
assert(img_path);
- int W, H, N;
- stbi_set_unpremultiply_on_load(1);
- stbi_convert_iphone_png_to_rgb(1);
- uint8_t *pixels = stbi_load(img_path, &W, &H, &N, 4);
- free(img_path);
- assert(pixels );
-
- // bitswap and premultiply pixel data
- for(unsigned i = 0; i < W*H*sizeof(uint32_t); i += sizeof(uint32_t))
+ struct stat st;
+ if(stat(img_path, &st) == 0)
{
- // get alpha channel
- const uint8_t a = pixels[i+3];
+ int W, H, N;
+ uint8_t *pixels = NULL;
- // premultiply with alpha channel
- const uint8_t r = ( (uint16_t)pixels[i+0] * a ) >> 8;
- const uint8_t g = ( (uint16_t)pixels[i+1] * a ) >> 8;
- const uint8_t b = ( (uint16_t)pixels[i+2] * a ) >> 8;
+ stbi_set_unpremultiply_on_load(1);
+ stbi_convert_iphone_png_to_rgb(1);
+ pixels = stbi_load(img_path, &W, &H, &N, 4);
- // merge and byteswap to correct endianness
- uint32_t *pix = (uint32_t *)&pixels[i];
- *pix = (a << 24) | (r << 16) | (g << 8) | b;
- }
+ if(pixels)
+ {
+ // bitswap and premultiply pixel data
+ for(unsigned i = 0; i < W*H*sizeof(uint32_t); i += sizeof(uint32_t))
+ {
+ // get alpha channel
+ const uint8_t a = pixels[i+3];
- cairo_surface_t *surf = cairo_image_surface_create_for_data(pixels,
- CAIRO_FORMAT_ARGB32, W, H, W*sizeof(uint32_t));
+ // premultiply with alpha channel
+ const uint8_t r = ( (uint16_t)pixels[i+0] * a ) >> 8;
+ const uint8_t g = ( (uint16_t)pixels[i+1] * a ) >> 8;
+ const uint8_t b = ( (uint16_t)pixels[i+2] * a ) >> 8;
- const cairo_user_data_key_t key = { 0 };
- cairo_surface_set_user_data(surf, &key, pixels, _d2tk_cairo_img_free);
+ // merge and byteswap to correct endianness
+ uint32_t *pix = (uint32_t *)&pixels[i];
+ *pix = (a << 24) | (r << 16) | (g << 8) | b;
+ }
- *sprite = (uintptr_t)surf;
+ cairo_surface_t *surf = cairo_image_surface_create_for_data(pixels,
+ CAIRO_FORMAT_ARGB32, W, H, W*sizeof(uint32_t));
+
+ const cairo_user_data_key_t key = { 0 };
+ cairo_surface_set_user_data(surf, &key, pixels, _d2tk_cairo_img_free);
+
+ *sprite = (uintptr_t)surf;
+ }
+ }
+
+ free(img_path);
}
cairo_surface_t *surf = (cairo_surface_t *)*sprite;
@@ -785,38 +810,10 @@ d2tk_cairo_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
{
const d2tk_body_custom_t *body = &com->body->custom;
- const uint64_t hash = d2tk_hash(body->data, body->size);
- uintptr_t *sprite = d2tk_core_get_sprite(core, hash, SPRITE_TYPE_SURF);
- assert(sprite);
-
- if(!*sprite)
- {
- const size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32,
- body->w);
- const size_t bufsz = stride * body->h;
- void *buf = calloc(1, bufsz);
- cairo_surface_t *surf = cairo_image_surface_create_for_data(buf,
- CAIRO_FORMAT_ARGB32, body->w, body->h, stride);
- assert(surf);
-
- const cairo_user_data_key_t key = { 0 };
- cairo_surface_set_user_data(surf, &key, buf, _d2tk_cairo_buf_free);
-
- cairo_t *ctx2 = cairo_create(surf);
-
- body->custom(ctx2, body->size, body->data);
-
- cairo_surface_flush(surf);
- cairo_destroy(ctx2);
-
- *sprite = (uintptr_t)surf;
- }
-
- cairo_surface_t *surf = (cairo_surface_t *)*sprite;
- assert(surf);
-
- _d2tk_cairo_surf_draw(ctx, surf, xo, yo, D2TK_ALIGN_LEFT | D2TK_ALIGN_TOP,
- &D2TK_RECT(body->x, body->y, body->w, body->h));
+ cairo_save(ctx);
+ body->custom(ctx, &D2TK_RECT(body->x + xo, body->y + yo, body->w, body->h),
+ body->data);
+ cairo_restore(ctx);
} break;
case D2TK_INSTR_STROKE_WIDTH:
{
diff --git a/src/backend_nanovg.c b/src/backend_nanovg.c
index c804b5c7..27ccaf85 100644
--- a/src/backend_nanovg.c
+++ b/src/backend_nanovg.c
@@ -19,6 +19,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
+#include <sys/stat.h>
#include <nanovg.h>
@@ -289,7 +290,7 @@ d2tk_nanovg_end(void *data __attribute__((unused)),
nvgFillPaint(ctx, fg);
nvgFill(ctx);
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
{
d2tk_rect_t rect;
uint32_t *pixels = d2tk_core_get_pixels(core, &rect);
@@ -429,6 +430,23 @@ _d2tk_nanovg_surf_draw(NVGcontext *ctx, int img, d2tk_coord_t xo,
nvgFill(ctx);
}
+static inline char *
+_absolute_path(d2tk_backend_nanovg_t *backend, const char *rel)
+{
+ char *abs = NULL;
+
+ if(rel[0] == '/')
+ {
+ assert(asprintf(&abs, "%s", rel) != -1);
+ }
+ else
+ {
+ assert(asprintf(&abs, "%s%s", backend->bundle_path, rel) != -1);
+ }
+
+ return abs;
+}
+
static inline void
d2tk_nanovg_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
d2tk_coord_t xo, d2tk_coord_t yo, const d2tk_clip_t *clip, unsigned pass)
@@ -565,7 +583,7 @@ d2tk_nanovg_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
if(!*sprite)
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
//fprintf(stderr, "\tcreating sprite\n");
#endif
NVGLUframebuffer *fbo = nvgluCreateFramebuffer(ctx, body->clip.w, body->clip.h, NVG_IMAGE_NEAREST);
@@ -594,7 +612,7 @@ d2tk_nanovg_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
}
else
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
//fprintf(stderr, "\texisting sprite\n");
#endif
}
@@ -683,7 +701,7 @@ d2tk_nanovg_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
*sprite = (uintptr_t)face;
}
- nvgFontFace(ctx, body->face);
+ nvgFontFaceId(ctx, *sprite);
} break;
case D2TK_INSTR_FONT_SIZE:
{
@@ -744,19 +762,25 @@ d2tk_nanovg_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
if(!*sprite)
{
- char *img_path = NULL;
- assert(asprintf(&img_path, "%s%s", backend->bundle_path, body->path) != -1);
+ char *img_path = _absolute_path(backend, body->path);
assert(img_path);
- *sprite = nvgCreateImage(ctx, img_path, NVG_IMAGE_GENERATE_MIPMAPS);
+ struct stat st;
+ if(stat(img_path, &st) == 0)
+ {
+ *sprite = nvgCreateImage(ctx, img_path, NVG_IMAGE_GENERATE_MIPMAPS);
+ }
+
free(img_path);
}
const int img = *sprite;
- assert(img);
- _d2tk_nanovg_surf_draw(ctx, img, xo, yo, body->align,
- &D2TK_RECT(body->x, body->y, body->w, body->h));
+ if(img)
+ {
+ _d2tk_nanovg_surf_draw(ctx, img, xo, yo, body->align,
+ &D2TK_RECT(body->x, body->y, body->w, body->h));
+ }
} break;
case D2TK_INSTR_BITMAP:
{
@@ -783,77 +807,11 @@ d2tk_nanovg_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
case D2TK_INSTR_CUSTOM:
{
const d2tk_body_custom_t *body = &com->body->custom;
- const uint64_t hash = d2tk_hash(body->data, body->size);
-
- if(pass == 0)
- {
- if(true) // cached
- {
- uintptr_t *sprite = d2tk_core_get_sprite(core, hash, SPRITE_TYPE_FBO);
- assert(sprite);
-
- if(!*sprite)
- {
-#ifdef D2TK_DEBUG
- //fprintf(stderr, "\tcreating sprite\n");
-#endif
- NVGLUframebuffer *fbo = nvgluCreateFramebuffer(ctx, body->w, body->h, NVG_IMAGE_NEAREST);
- assert(fbo);
- nvgluBindFramebuffer(fbo);
-
- glViewport(0, 0, body->w, body->h);
- glClearColor(0.f, 0.f, 0.f, 0.f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-
- nvgBeginFrame(ctx, body->w, body->h, 1.f);
- nvgSave(ctx);
-
- body->custom(ctx, body->size, body->data);
-
- nvgRestore(ctx);
- nvgEndFrame(ctx);
-
- nvgluBindFramebuffer(NULL);
-
- *sprite = (uintptr_t )fbo;
- }
- else
- {
-#ifdef D2TK_DEBUG
- //fprintf(stderr, "\texisting sprite\n");
-#endif
- }
- }
- }
- else if(pass == 1)
- {
- nvgSave(ctx);
- if(clip)
- {
- nvgScissor(ctx, clip->x0, clip->y0, clip->w, clip->h);
- }
-
- if(true) // cached
- {
- uintptr_t *sprite = d2tk_core_get_sprite(core, hash, SPRITE_TYPE_FBO);
- assert(sprite && *sprite);
-
- NVGLUframebuffer *fbo = (NVGLUframebuffer *)*sprite;
- assert(fbo);
-
- // paint pre-rendered sprite
- const NVGpaint pat = nvgImagePattern(ctx, body->x, body->y,
- body->w, body->h, 0, fbo->image, 1.0f);
- nvgBeginPath(ctx);
- nvgRect(ctx, body->x, body->y, body->w, body->h);
- nvgStrokeWidth(ctx, 0);
- nvgFillPaint(ctx, pat);
- nvgFill(ctx);
- }
-
- nvgRestore(ctx);
- }
+ nvgSave(ctx);
+ body->custom(ctx, &D2TK_RECT(body->x + xo, body->y + yo, body->w, body->h),
+ body->data);
+ nvgRestore(ctx);
} break;
case D2TK_INSTR_STROKE_WIDTH:
{
diff --git a/src/base.c b/src/base.c
index 3ee2586a..bca11132 100644
--- a/src/base.c
+++ b/src/base.c
@@ -19,6 +19,9 @@
#include <math.h>
#include <string.h>
#include <inttypes.h>
+#if !defined(_WIN32)
+# include <poll.h>
+#endif
#include "base_internal.h"
@@ -609,7 +612,7 @@ d2tk_base_is_active_hot(d2tk_base_t *base, d2tk_id_t id,
{
state |= D2TK_STATE_FOCUS_OUT;
_d2tk_flip_clear_old(&base->focusitem); // clear previous focus
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
fprintf(stderr, "\tfocus out 0x%016"PRIx64"\n", id);
#endif
}
@@ -622,7 +625,7 @@ d2tk_base_is_active_hot(d2tk_base_t *base, d2tk_id_t id,
else
{
state |= D2TK_STATE_FOCUS_IN;
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
fprintf(stderr, "\tfocus in 0x%016"PRIx64"\n", id);
#endif
_d2tk_base_change_focus(base);
@@ -821,6 +824,42 @@ d2tk_base_post(d2tk_base_t *base)
d2tk_core_post(base->core);
}
+static int
+_d2tk_base_probe(int fd)
+{
+ if(fd <= 0)
+ {
+ return 0;
+ }
+
+#if !defined(_WIN32)
+ struct pollfd fds = {
+ .fd = fd,
+ .events = POLLIN,
+ .revents = 0
+ };
+
+ switch(poll(&fds, 1, 0))
+ {
+ case -1:
+ {
+ //printf("[%s] error: %s\n", __func__, strerror(errno));
+ } return 0;
+ case 0:
+ {
+ //printf("[%s] timeout\n", __func__);
+ } return 0;
+
+ default:
+ {
+ //printf("[%s] ready\n", __func__);
+ } return 1;
+ }
+#else
+ return 0;
+#endif
+}
+
D2TK_API void
d2tk_base_probe(d2tk_base_t *base)
{
@@ -830,7 +869,9 @@ d2tk_base_probe(d2tk_base_t *base)
if(atom->id && atom->type && atom->event)
{
- if(atom->event(D2TK_ATOM_EVENT_PROBE, atom->body))
+ const int fd = atom->event(D2TK_ATOM_EVENT_FD, atom->body);
+
+ if(_d2tk_base_probe(fd))
{
d2tk_base_set_again(base);
break;
@@ -839,6 +880,29 @@ d2tk_base_probe(d2tk_base_t *base)
}
}
+D2TK_API int
+d2tk_base_get_file_descriptors(d2tk_base_t *base, int *fds, int numfds)
+{
+ int idx = 0;
+
+ for(unsigned i = 0; i < _D2TK_MAX_ATOM; i++)
+ {
+ d2tk_atom_t *atom = &base->atoms[i];
+
+ if(atom->id && atom->type && atom->event)
+ {
+ const int fd = atom->event(D2TK_ATOM_EVENT_FD, atom->body);
+
+ if( (fd > 0) && (idx < numfds) )
+ {
+ fds[idx++] = fd;
+ }
+ }
+ }
+
+ return idx;
+}
+
D2TK_API void
d2tk_base_clear_focus(d2tk_base_t *base)
{
diff --git a/src/base_custom.c b/src/base_custom.c
index c001e5ab..2396530b 100644
--- a/src/base_custom.c
+++ b/src/base_custom.c
@@ -18,12 +18,12 @@
#include "base_internal.h"
D2TK_API void
-d2tk_base_custom(d2tk_base_t *base, uint32_t size, const void *data,
+d2tk_base_custom(d2tk_base_t *base, uint64_t dhash, const void *data,
const d2tk_rect_t *rect, d2tk_core_custom_t custom)
{
const d2tk_hash_dict_t dict [] = {
{ rect, sizeof(d2tk_rect_t) } ,
- { data, size }, //FIXME
+ { &dhash, sizeof(uint64_t)},
{ NULL, 0 }
};
const uint64_t hash = d2tk_hash_dict(dict);
@@ -34,7 +34,7 @@ d2tk_base_custom(d2tk_base_t *base, uint32_t size, const void *data,
{
const size_t ref = d2tk_core_bbox_push(core, true, rect);
- d2tk_core_custom(core, rect, size, data, custom);
+ d2tk_core_custom(core, rect, dhash, data, custom);
d2tk_core_bbox_pop(core, ref);
}
diff --git a/src/base_internal.h b/src/base_internal.h
index e17b5a41..35c06919 100644
--- a/src/base_internal.h
+++ b/src/base_internal.h
@@ -41,7 +41,7 @@ typedef enum _d2tk_atom_type_t {
typedef enum _d2tk_atom_event_type_t {
D2TK_ATOM_EVENT_NONE,
- D2TK_ATOM_EVENT_PROBE,
+ D2TK_ATOM_EVENT_FD,
D2TK_ATOM_EVENT_DEINIT
} d2tk_atom_event_type_t;
diff --git a/src/base_pty.c b/src/base_pty.c
index 246b2068..82902acc 100644
--- a/src/base_pty.c
+++ b/src/base_pty.c
@@ -24,9 +24,9 @@
#include <pty.h>
#include <utmp.h>
#include <sched.h>
+#include <limits.h>
#include <sys/wait.h>
#include <sys/mman.h>
-#include <poll.h>
#include "base_internal.h"
@@ -279,10 +279,21 @@ _clone(void *data)
signal(SIGSTOP, SIG_DFL);
signal(SIGCONT, SIG_DFL);
- putenv("TERM=xterm-256color");
- //putenv("COLORTERM=truecolor");
+ char envh [PATH_MAX];
+ char envu [PATH_MAX];
+ char *home = getenv("HOME");
+ char *user = getenv("USER");
+ snprintf(envh, sizeof(envh), "HOME=%s", home ? home : "");
+ snprintf(envu, sizeof(envu), "USER=%s", user ? user : "");
+
+ char *envp [] = {
+ "TERM=xterm-256color",
+ envh,
+ envu,
+ NULL
+ };
- execvp(clone_data->argv[0], clone_data->argv);
+ execvpe(clone_data->argv[0], clone_data->argv, envp);
fprintf(stderr_save, "cannot exec(%s) - %s\n", clone_data->argv[0], strerror(errno));
_exit(EXIT_FAILURE);
@@ -445,33 +456,9 @@ _term_init(d2tk_atom_body_pty_t *vpty, char **argv,
}
static int
-_term_probe(d2tk_atom_body_pty_t *vpty)
+_term_fd(d2tk_atom_body_pty_t *vpty)
{
- int again = 0;
-
- struct pollfd fds = {
- .fd = vpty->fd,
- .events = POLLIN
- };
-
- switch(poll(&fds, 1, 0))
- {
- case -1:
- {
- //printf("[%s] error: %s\n", __func__, strerror(errno));
- } break;
- case 0:
- {
- //printf("[%s] timeout\n", __func__);
- } break;
- default:
- {
- //printf("[%s] ready\n", __func__);
- again = 1;
- } break;
- }
-
- return again;
+ return vpty->fd;
}
static int
@@ -525,9 +512,9 @@ _term_event(d2tk_atom_event_type_t event, void *data)
switch(event)
{
- case D2TK_ATOM_EVENT_PROBE:
+ case D2TK_ATOM_EVENT_FD:
{
- return _term_probe(vpty);
+ return _term_fd(vpty);
} break;
case D2TK_ATOM_EVENT_DEINIT:
{
diff --git a/src/base_vkb.c b/src/base_vkb.c
index 6d6ebe18..bbe65c6f 100644
--- a/src/base_vkb.c
+++ b/src/base_vkb.c
@@ -566,7 +566,7 @@ _vkb_event(d2tk_atom_event_type_t event, void *data)
return _vkb_deinit(vkb);
} break;
- case D2TK_ATOM_EVENT_PROBE:
+ case D2TK_ATOM_EVENT_FD:
// fall-through
case D2TK_ATOM_EVENT_NONE:
// fall-through
diff --git a/src/core.c b/src/core.c
index 6f111e70..e0cf8e26 100644
--- a/src/core.c
+++ b/src/core.c
@@ -228,7 +228,7 @@ _d2tk_sprites_gc(d2tk_core_t *core)
if(sprite->body)
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
fprintf(stderr, "\tgc sprites (%08"PRIx64")\n", sprite->hash);
#endif
core->driver->sprite_free(core->data, sprite->type, sprite->body);
@@ -333,7 +333,7 @@ _d2tk_memcaches_gc(d2tk_core_t *core)
if(memcache->body)
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
fprintf(stderr, "\tgc memcaches (%08"PRIx64")\n", memcache->hash);
#endif
d2tk_widget_body_t *body = (d2tk_widget_body_t *)memcache->body;
@@ -1196,7 +1196,7 @@ d2tk_core_bitmap(d2tk_core_t *core, const d2tk_rect_t *rect, uint32_t w,
}
D2TK_API void
-d2tk_core_custom(d2tk_core_t *core, const d2tk_rect_t *rect, uint32_t size,
+d2tk_core_custom(d2tk_core_t *core, const d2tk_rect_t *rect, uint64_t dhash,
const void *data, d2tk_core_custom_t custom)
{
const size_t len = sizeof(d2tk_body_custom_t);
@@ -1208,7 +1208,7 @@ d2tk_core_custom(d2tk_core_t *core, const d2tk_rect_t *rect, uint32_t size,
body->custom.y = rect->y;
body->custom.w = rect->w;
body->custom.h = rect->h;
- body->custom.size = size;
+ body->custom.dhash = dhash;
body->custom.data = data;
body->custom.custom = custom;
@@ -1322,7 +1322,7 @@ _d2tk_diff(d2tk_core_t *core, d2tk_com_t *curcom_ref, d2tk_com_t *oldcom_ref)
continue;
}
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
d2tk_body_bbox_t *curbbox2 = &curcom2->body->bbox;
fprintf(stderr,
@@ -1338,7 +1338,7 @@ _d2tk_diff(d2tk_core_t *core, d2tk_com_t *curcom_ref, d2tk_com_t *oldcom_ref)
if(curcom->body->bbox.container && oldcom->body->bbox.container)
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
fprintf(stderr, "\t comparing nested containers\n");
#endif
_d2tk_diff(core, curcom, oldcom);
@@ -1353,7 +1353,7 @@ _d2tk_diff(d2tk_core_t *core, d2tk_com_t *curcom_ref, d2tk_com_t *oldcom_ref)
if(!match)
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
d2tk_body_bbox_t *oldbbox = &oldcom->body->bbox;
fprintf(stderr,
@@ -1375,7 +1375,7 @@ _d2tk_diff(d2tk_core_t *core, d2tk_com_t *curcom_ref, d2tk_com_t *oldcom_ref)
continue;
}
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
d2tk_body_bbox_t *curbbox2 = &curcom2->body->bbox;
fprintf(stderr,
@@ -1409,7 +1409,7 @@ d2tk_core_post(d2tk_core_t *core)
if(core->full_refresh)
{
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
fprintf(stderr,
"\tfull_refresh (%"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32")\n",
0, 0, core->w, core->h);
@@ -1454,7 +1454,7 @@ d2tk_core_post(d2tk_core_t *core)
aoi = &tmp;
}
-#ifdef D2TK_DEBUG
+#if D2TK_DEBUG
fprintf(stderr, "\tnfills: %zu\n", bitmap->nfills);
#endif
for(unsigned pass = 0; pass < 2; pass++)
diff --git a/src/core_internal.h b/src/core_internal.h
index 37fbf35f..3dc9a438 100644
--- a/src/core_internal.h
+++ b/src/core_internal.h
@@ -195,7 +195,7 @@ struct _d2tk_body_custom_t {
d2tk_coord_t y;
d2tk_coord_t w;
d2tk_coord_t h;
- uint32_t size;
+ uint64_t dhash;
const void *data;
d2tk_core_custom_t custom;
};
diff --git a/src/frontend_fbdev.c b/src/frontend_fbdev.c
index e237ee56..3ae87ed9 100644
--- a/src/frontend_fbdev.c
+++ b/src/frontend_fbdev.c
@@ -633,6 +633,12 @@ d2tk_frontend_poll(d2tk_frontend_t *fbdev __attribute__((unused)),
return 0;
}
+D2TK_API int
+d2tk_frontend_get_file_descriptors(d2tk_frontend_t *fbdev, int *fds, int numfds)
+{
+ return d2tk_base_get_file_descriptors(fbdev->base, fds, numfds);
+}
+
D2TK_API void
d2tk_frontend_run(d2tk_frontend_t *fbdev, const sig_atomic_t *done)
{
@@ -774,3 +780,24 @@ d2tk_frontend_get_scale()
{
return 1.f;
}
+
+D2TK_API int
+d2tk_frontend_set_clipboard(d2tk_frontend_t *dpugl, const char *type,
+ const void *buf, size_t buf_len)
+{
+ (void)dpugl;
+ (void)type;
+ (void)buf;
+ (void)buf_len;
+ return 1; //FIXME
+}
+
+D2TK_API const void *
+d2tk_frontend_get_clipboard(d2tk_frontend_t *dpugl, const char **type,
+ size_t *buf_len)
+{
+ (void)dpugl;
+ (void)type;
+ (void)buf_len;
+ return NULL; //FIXME
+}
diff --git a/src/frontend_glfw.c b/src/frontend_glfw.c
new file mode 100644
index 00000000..6950c000
--- /dev/null
+++ b/src/frontend_glfw.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "core_internal.h"
+#include <d2tk/frontend_glfw.h>
+
+#include <d2tk/backend.h>
+
+#define GLFW_INCLUDE_ES3
+#define GLFW_INCLUDE_GLEXT
+#include <GLFW/glfw3.h>
+
+struct _d2tk_frontend_t {
+ const d2tk_glfw_config_t *config;
+ int w;
+ int h;
+ GLFWwindow * window;
+ d2tk_base_t *base;
+ void *ctx;
+};
+
+static inline void
+_d2tk_frontend_expose(d2tk_frontend_t *dglfw)
+{
+ d2tk_base_t *base = dglfw->base;
+
+ d2tk_coord_t w;
+ d2tk_coord_t h;
+ d2tk_base_get_dimensions(base, &w, &h);
+
+ if(d2tk_base_pre(base, NULL) == 0)
+ {
+ dglfw->config->expose(dglfw->config->data, w, h);
+
+ d2tk_base_post(base);
+ }
+}
+
+D2TK_API int
+d2tk_frontend_poll(d2tk_frontend_t *dglfw, double timeout)
+{
+ d2tk_base_t *base = dglfw->base;
+ int width;
+ int height;
+
+ if(timeout < 0.0)
+ {
+ glfwWaitEvents();
+ }
+ else if(timeout == 0.0)
+ {
+ glfwPollEvents();
+ }
+ else
+ {
+ glfwWaitEventsTimeout(timeout);
+ }
+
+ glfwGetFramebufferSize(dglfw->window, &width, &height);
+ if( (dglfw->w != width) || (dglfw->h != height) )
+ {
+ dglfw->w = width;
+ dglfw->h = height;
+
+ d2tk_base_set_dimensions(base, dglfw->w, dglfw->h);
+ }
+
+ _d2tk_frontend_expose(dglfw);
+ glfwSwapBuffers(dglfw->window);
+
+ return 0;
+}
+
+D2TK_API int
+d2tk_frontend_get_file_descriptors(d2tk_frontend_t *dglfw, int *fds, int numfds)
+{
+ return d2tk_base_get_file_descriptors(dglfw->base, fds, numfds);
+}
+
+D2TK_API int
+d2tk_frontend_step(d2tk_frontend_t *dglfw)
+{
+ return d2tk_frontend_poll(dglfw, 0.0);
+}
+
+D2TK_API void
+d2tk_frontend_run(d2tk_frontend_t *dglfw, const sig_atomic_t *done)
+{
+ while(!*done)
+ {
+ if(d2tk_frontend_poll(dglfw, -1.0))
+ {
+ break;
+ }
+ }
+}
+
+D2TK_API void
+d2tk_frontend_free(d2tk_frontend_t *dglfw)
+{
+ glfwDestroyWindow(dglfw->window);
+ free(dglfw);
+
+ glfwTerminate();
+}
+
+D2TK_API float
+d2tk_frontend_get_scale()
+{
+#if 0
+ float xscale;
+ float yscale;
+
+ glfwGetWindowContentScale(dglfw->window, &xscale, &yscale);
+
+ return xscale;
+#else
+ return 1.f;
+#endif
+}
+
+static void
+_d2tk_frontend_modifiers(d2tk_base_t *base, int mods)
+{
+ d2tk_base_set_modmask(base, D2TK_MODMASK_SHIFT,
+ (mods & GLFW_MOD_SHIFT) ? true : false);
+ d2tk_base_set_modmask(base, D2TK_MODMASK_CTRL,
+ (mods & GLFW_MOD_CONTROL) ? true : false);
+ d2tk_base_set_modmask(base, D2TK_MODMASK_ALT,
+ (mods & GLFW_MOD_ALT) ? true : false);
+}
+
+static void
+_d2tk_key(GLFWwindow *window, int key, int scancode, int action, int mods)
+{
+ d2tk_frontend_t *dglfw = glfwGetWindowUserPointer(window);
+ d2tk_base_t *base = dglfw->base;
+ const bool pressed = action == GLFW_PRESS;
+
+ _d2tk_frontend_modifiers(base, mods);
+
+ fprintf(stderr, "[%s] %i %i %i %i\n", __func__, key, scancode, action, mods);
+
+ d2tk_keymask_t mask = D2TK_KEYMASK_NONE;
+ unsigned int codepoint = 0;
+
+ switch(key)
+ {
+ case GLFW_KEY_ENTER:
+ {
+ mask = D2TK_KEYMASK_ENTER;
+ codepoint = '\n';
+ } break;
+ case GLFW_KEY_TAB:
+ {
+ mask = D2TK_KEYMASK_TAB;
+ codepoint = '\t';
+ } break;
+ case GLFW_KEY_BACKSPACE:
+ {
+ mask = D2TK_KEYMASK_BACKSPACE;
+ codepoint = '\b';
+ } break;
+ case GLFW_KEY_ESCAPE:
+ {
+ mask = D2TK_KEYMASK_ESCAPE;
+ codepoint = 0x1B;
+ } break;
+
+ case GLFW_KEY_UP:
+ {
+ mask = D2TK_KEYMASK_UP;
+ } break;
+ case GLFW_KEY_DOWN:
+ {
+ mask = D2TK_KEYMASK_DOWN;
+ } break;
+ case GLFW_KEY_LEFT:
+ {
+ mask = D2TK_KEYMASK_LEFT;
+ } break;
+ case GLFW_KEY_RIGHT:
+ {
+ mask = D2TK_KEYMASK_RIGHT;
+ } break;
+
+ case GLFW_KEY_INSERT:
+ {
+ mask = D2TK_KEYMASK_INS;
+ } break;
+ case GLFW_KEY_DELETE:
+ {
+ mask = D2TK_KEYMASK_DEL;
+ } break;
+ case GLFW_KEY_HOME:
+ {
+ mask = D2TK_KEYMASK_HOME;
+ } break;
+ case GLFW_KEY_END:
+ {
+ mask = D2TK_KEYMASK_END;
+ } break;
+ case GLFW_KEY_PAGE_UP:
+ {
+ mask = D2TK_KEYMASK_PAGEUP;
+ } break;
+ case GLFW_KEY_PAGE_DOWN:
+ {
+ mask = D2TK_KEYMASK_PAGEDOWN;
+ } break;
+ }
+
+ if(mask != D2TK_KEYMASK_NONE)
+ {
+ d2tk_base_set_keymask(base, mask, pressed);
+ }
+
+ if(codepoint && pressed)
+ {
+ d2tk_base_append_utf8(base, codepoint);
+ }
+}
+
+static void
+_d2tk_char(GLFWwindow *window, unsigned int codepoint)
+{
+ d2tk_frontend_t *dglfw = glfwGetWindowUserPointer(window);
+ d2tk_base_t *base = dglfw->base;
+
+ fprintf(stderr, "[%s] %u\n", __func__, codepoint);
+
+ d2tk_base_append_utf8(base, codepoint);
+}
+
+static void
+_d2tk_cursor_pos(GLFWwindow *window, double xpos, double ypos)
+{
+ d2tk_frontend_t *dglfw = glfwGetWindowUserPointer(window);
+ d2tk_base_t *base = dglfw->base;
+
+ d2tk_base_set_mouse_pos(base, xpos, ypos);
+}
+
+static void
+_d2tk_cursor_enter(GLFWwindow *window, int entered)
+{
+ d2tk_frontend_t *dglfw = glfwGetWindowUserPointer(window);
+ d2tk_base_t *base = dglfw->base;
+
+ if(entered)
+ {
+ d2tk_base_set_full_refresh(base);
+ }
+}
+
+static void
+_d2tk_mouse_button(GLFWwindow *window, int button, int action, int mods)
+{
+ d2tk_frontend_t *dglfw = glfwGetWindowUserPointer(window);
+ d2tk_base_t *base = dglfw->base;
+ const bool pressed = action == GLFW_PRESS;
+
+ _d2tk_frontend_modifiers(base, mods);
+
+ switch(button)
+ {
+ case 3:
+ {
+ d2tk_base_set_butmask(base, D2TK_BUTMASK_RIGHT, pressed);
+ } break;
+ case 2:
+ {
+ d2tk_base_set_butmask(base, D2TK_BUTMASK_MIDDLE, pressed);
+ } break;
+ case 1:
+ // fall-through
+ default:
+ {
+ d2tk_base_set_butmask(base, D2TK_BUTMASK_LEFT, pressed);
+ } break;
+ }
+}
+
+static void
+_d2tk_scroll(GLFWwindow *window, double xoffset, double yoffset)
+{
+ d2tk_frontend_t *dglfw = glfwGetWindowUserPointer(window);
+ d2tk_base_t *base = dglfw->base;
+
+ d2tk_base_add_mouse_scroll(base, xoffset, yoffset);
+}
+
+D2TK_API d2tk_frontend_t *
+d2tk_glfw_new(const d2tk_glfw_config_t *config)
+{
+ if(glfwInit() != GLFW_TRUE)
+ {
+ return NULL;
+ }
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+
+ d2tk_frontend_t *dglfw = calloc(1, sizeof(d2tk_frontend_t));
+ if(!dglfw)
+ {
+ goto fail;
+ }
+
+ dglfw->config = config;
+
+ dglfw->window = glfwCreateWindow(dglfw->config->w, dglfw->config->h, "d2tk",
+ NULL, NULL);
+
+ if(!dglfw->window)
+ {
+ goto fail;
+ }
+
+ glfwSetWindowUserPointer(dglfw->window, dglfw);
+
+ glfwSetKeyCallback(dglfw->window, _d2tk_key);
+ glfwSetCharCallback(dglfw->window, _d2tk_char);
+ glfwSetCursorPosCallback(dglfw->window, _d2tk_cursor_pos);
+ glfwSetCursorEnterCallback(dglfw->window, _d2tk_cursor_enter);
+ glfwSetMouseButtonCallback(dglfw->window, _d2tk_mouse_button);
+ glfwSetScrollCallback(dglfw->window, _d2tk_scroll);
+ glfwMakeContextCurrent(dglfw->window);
+ glfwSwapInterval(1);
+
+ dglfw->ctx = d2tk_core_driver.new(dglfw->config->bundle_path);
+
+ if(!dglfw->ctx)
+ {
+ goto fail;
+ }
+
+ dglfw->base = d2tk_base_new(&d2tk_core_driver, dglfw->ctx);
+ if(!dglfw->base)
+ {
+ goto fail;
+ }
+
+ return dglfw;
+
+fail:
+ if(dglfw)
+ {
+ if(dglfw->window)
+ {
+ glfwDestroyWindow(dglfw->window);
+ }
+
+ free(dglfw);
+
+ glfwTerminate();
+ }
+
+ return NULL;
+}
+
+D2TK_API void
+d2tk_frontend_redisplay(d2tk_frontend_t *dglfw)
+{
+ (void)dglfw;
+ //FIXME
+}
+
+D2TK_API int
+d2tk_frontend_set_size(d2tk_frontend_t *dglfw, d2tk_coord_t w, d2tk_coord_t h)
+{
+ d2tk_base_set_dimensions(dglfw->base, w, h);
+ d2tk_frontend_redisplay(dglfw);
+
+ return 0;
+}
+
+D2TK_API int
+d2tk_frontend_get_size(d2tk_frontend_t *dglfw, d2tk_coord_t *w, d2tk_coord_t *h)
+{
+ int width;
+ int height;
+
+ glfwGetWindowSize(dglfw->window, &width, &height);
+
+ *w = width;
+ *h = height;
+
+ return 0;
+}
+
+D2TK_API d2tk_base_t *
+d2tk_frontend_get_base(d2tk_frontend_t *dglfw)
+{
+ return dglfw->base;
+}
+
+D2TK_API int
+d2tk_frontend_set_clipboard(d2tk_frontend_t *dpugl, const char *type,
+ const void *buf, size_t buf_len)
+{
+ (void)dpugl;
+ (void)type;
+ (void)buf;
+ (void)buf_len;
+ return 1; //FIXME
+}
+
+D2TK_API const void *
+d2tk_frontend_get_clipboard(d2tk_frontend_t *dpugl, const char **type,
+ size_t *buf_len)
+{
+ (void)dpugl;
+ (void)type;
+ (void)buf_len;
+ return NULL; //FIXME
+}
diff --git a/src/frontend_pugl.c b/src/frontend_pugl.c
index b607a6e4..3473984b 100644
--- a/src/frontend_pugl.c
+++ b/src/frontend_pugl.c
@@ -486,6 +486,32 @@ d2tk_frontend_poll(d2tk_frontend_t *dpugl, double timeout)
}
D2TK_API int
+d2tk_frontend_get_file_descriptors(d2tk_frontend_t *dpugl, int *fds, int numfds)
+{
+ int idx = 0;
+
+#if defined(__APPLE__) || de
+ //FIXME
+ (void)dpugl;
+ return -1;
+#elif defined(_WIN32)
+ //FIXME
+ (void)dpugl;
+ return -1;
+#else
+ Display *disp = puglGetNativeWorld(dpugl->world);
+ const int fd = disp ? ConnectionNumber(disp) : 0;
+
+ if( (fd > 0) && (idx < numfds) )
+ {
+ fds[idx++] = fd;
+ }
+#endif
+
+ return idx + d2tk_base_get_file_descriptors(dpugl->base, &fds[idx], numfds-idx);
+}
+
+D2TK_API int
d2tk_frontend_step(d2tk_frontend_t *dpugl)
{
return d2tk_frontend_poll(dpugl, 0.0);
@@ -707,3 +733,17 @@ d2tk_frontend_get_base(d2tk_frontend_t *dpugl)
{
return dpugl->base;
}
+
+D2TK_API int
+d2tk_frontend_set_clipboard(d2tk_frontend_t *dpugl, const char *type,
+ const void *buf, size_t buf_len)
+{
+ return puglSetClipboard(dpugl->view, type, buf, buf_len);
+}
+
+D2TK_API const void *
+d2tk_frontend_get_clipboard(d2tk_frontend_t *dpugl, const char **type,
+ size_t *buf_len)
+{
+ return puglGetClipboard(dpugl->view, type, buf_len);
+}
diff --git a/src/util_spawn.c b/src/util_spawn.c
new file mode 100644
index 00000000..09c740d8
--- /dev/null
+++ b/src/util_spawn.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+
+#include <d2tk/util.h>
+
+typedef struct _clone_data_t clone_data_t;
+
+struct _clone_data_t {
+ char **argv;
+};
+
+static int
+_clone(void *data)
+{
+ clone_data_t *clone_data = data;
+
+ execvp(clone_data->argv[0], clone_data->argv);
+ _exit(EXIT_FAILURE);
+
+ return 0;
+}
+
+D2TK_API int
+d2tk_util_spawn(char **argv)
+{
+ clone_data_t clone_data = {
+ .argv = argv
+ };
+
+#if D2TK_CLONE == 1
+# define STACK_SIZE (1024 * 1024)
+ uint8_t *stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+ if(stack == MAP_FAILED)
+ {
+ return -1;
+ }
+
+ uint8_t *stack_top = stack + STACK_SIZE;
+ const int flags = CLONE_FS | CLONE_IO | CLONE_VFORK | CLONE_VM;
+ const int pid = clone(_clone, stack_top, flags, &clone_data);
+# undef STACK_SIZE
+#elif D2TK_VFORK == 1
+ const int pid = vfork();
+#else
+ const int pid = fork();
+#endif
+
+ switch(pid)
+ {
+ case -1:
+ {
+ } return -1;
+
+#if D2TK_CLONE == 0
+ case 0: //child
+ {
+ // everything is done in _clone
+ } return _clone(&clone_data);
+#endif
+
+ default: // parent
+ {
+ } return pid;
+ }
+}
+
+D2TK_API int
+d2tk_util_kill(int *kid)
+{
+ if(*kid <= 0)
+ {
+ return 0;
+ }
+
+ kill(*kid, SIGKILL);
+
+ if(waitpid(*kid, NULL, 0) == *kid)
+ {
+ *kid = -1;
+ return 0;
+ }
+
+ return 1;
+}
+
+D2TK_API int
+d2tk_util_wait(int *kid)
+{
+ if(*kid <= 0)
+ {
+ return 0;
+ }
+
+ if(waitpid(*kid, NULL, WNOHANG) == *kid)
+ {
+ *kid = -1;
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/test/base.c b/test/base.c
index 48262166..5cd145ed 100644
--- a/test/base.c
+++ b/test/base.c
@@ -1836,10 +1836,14 @@ _test_bitmap()
static const uint32_t custom_data;
static void
-_custom(void *ctx, uint32_t size, const void *data)
+_custom(void *ctx, const d2tk_rect_t *rect, const void *data)
{
assert(ctx == NULL);
- assert(size == sizeof(custom_data));
+ assert(rect != NULL);
+ assert(rect->x == 0);
+ assert(rect->y == 0);
+ assert(rect->w == DIM_W);
+ assert(rect->h == DIM_H);
assert(data == &custom_data);
}
@@ -1854,7 +1858,9 @@ _test_custom()
const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
assert(base);
- d2tk_base_custom(base, sizeof(custom_data), &custom_data, &rect, _custom);
+ const uint64_t dhash = d2tk_hash(&custom_data, sizeof(custom_data));
+
+ d2tk_base_custom(base, dhash, &custom_data, &rect, _custom);
d2tk_base_free(base);
}
diff --git a/test/core.c b/test/core.c
index a7dc40bd..5389ed36 100644
--- a/test/core.c
+++ b/test/core.c
@@ -1242,14 +1242,18 @@ _test_bitmap()
#define CUSTOM_Y 20
#define CUSTOM_W 30
#define CUSTOM_H 40
-#define CUSTOM_SIZE 0
-#define CUSTOM_DATA NULL
+static const uint32_t _data = 12;
+#define CUSTOM_DATA (&_data)
static void
-_custom(void *ctx, uint32_t size, const void *data)
+_custom(void *ctx, const d2tk_rect_t *rect, const void *data)
{
assert(ctx == NULL);
- assert(size == CUSTOM_SIZE);
+ assert(rect != NULL);
+ assert(rect->x == CUSTOM_X);
+ assert(rect->y == CUSTOM_Y);
+ assert(rect->w == CUSTOM_W);
+ assert(rect->h == CUSTOM_H);
assert(data == CUSTOM_DATA);
}
@@ -1263,13 +1267,15 @@ _check_custom(const d2tk_com_t *com, const d2tk_clip_t *clip)
assert(clip->w == CLIP_W);
assert(clip->h == CLIP_H);
+ const uint64_t dhash = d2tk_hash(CUSTOM_DATA, sizeof(uint32_t));
+
assert(com->size == sizeof(d2tk_body_custom_t));
assert(com->instr == D2TK_INSTR_CUSTOM);
assert(com->body->custom.x == CUSTOM_X - CLIP_X);
assert(com->body->custom.y == CUSTOM_Y - CLIP_Y);
assert(com->body->custom.w == CUSTOM_W);
assert(com->body->custom.h == CUSTOM_H);
- assert(com->body->custom.size == CUSTOM_SIZE);
+ assert(com->body->custom.dhash == dhash);
assert(com->body->custom.data == CUSTOM_DATA);
assert(com->body->custom.custom == _custom);
}
@@ -1291,8 +1297,10 @@ _test_custom()
&D2TK_RECT(CLIP_X, CLIP_Y, CLIP_W, CLIP_H));
assert(ref >= 0);
+ const uint64_t dhash = d2tk_hash(CUSTOM_DATA, sizeof(uint32_t));
+
d2tk_core_custom(core, &D2TK_RECT(CUSTOM_X, CUSTOM_Y, CUSTOM_W, CUSTOM_H),
- CUSTOM_SIZE, CUSTOM_DATA, _custom);
+ dhash, CUSTOM_DATA, _custom);
d2tk_core_bbox_pop(core, ref);
d2tk_core_post(core);