diff options
author | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2020-10-01 19:04:36 +0200 |
---|---|---|
committer | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2020-10-01 19:04:36 +0200 |
commit | 972281393f3dcebdc885f9fb8df7cae5a05e6050 (patch) | |
tree | 71ffadad63329096fd47a52aff16225d1197d39c | |
parent | 4c802a0300db7d8b9082c9fa2be1518f49dbb25f (diff) | |
download | d2tk.lv2-972281393f3dcebdc885f9fb8df7cae5a05e6050.tar.xz |
Squashed 'subprojects/d2tk/' changes from ea894bf..9cc8e97
9cc8e97 pty: export HOME and USER env vars.
7950546 pty: do not call putenv, use excvpe instead.
786d4b6 util: fixes for util spawn.
d6cc5a5 prototype util header with spawn method (unix only).
0279eda Merge commit 'e4017ca6e56aacae7121efe7e3a22b9f424c3f9b' into master
e4017ca Squashed 'pugl/' changes from 1b1a1c3a..2452869d
a656868 pugl: implement clipboard.
02beb92 add support for absolute paths in image widgets.
30b62a4 Merge commit '4e78a74d86e49010467e8c3b2ce3b1b0ace540a8' into pugl-next
4e78a74 Squashed 'pugl/' changes from 8f28d8c9..1b1a1c3a
b5fb57e custom: use hash data hash to discover changes.
98c991e nanovg/cairo: simplify custom backends.
fe78d43 core: add rect argument to custom callback.
7272863 example: prototype custom widget.
ad0a6a2 nanovg: use nvgFontFaceId, should be faster.
5496241 pugl: return correct number of file descriptors.
0a3ebe3 base: do not call poll on mingw.
4b4acef base: add hooks to get vpty file descriptors.
f6eec01 glfw: support enter/tab/backspace/escape keys.
21a7d0a glfw: preliminary working version.
b7a951c glfw: add prototype skeleton.
bab3da7 debug: fix wrong usage of D2TK_DEBUG define.
4d4f8b1 frontend: only get file descriptor on X11.
14e3a15 frontend: add d2tk_frontend_get_file_descriptor.
13db56c put debug overlay flag into config header.
f7a9b6d Merge branch 'master' of /media/sdext/omk/d2tk
9b4ad8a meson: add missing dependency on glu.
git-subtree-dir: subprojects/d2tk
git-subtree-split: 9cc8e978137ba4f2ae198c36f52b5f07dd2b073e
93 files changed, 4264 insertions, 920 deletions
@@ -1 +1 @@ -0.1.1089 +0.1.1143 diff --git a/d2tk/base.h b/d2tk/base.h index 0926b6d..b7fb9c8 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 c1d746e..01ea6b4 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 0b8049b..97567f5 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 eaa2b4a..716bad5 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 0000000..b7b934f --- /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 0000000..1c3b27d --- /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 0000000..56904ed --- /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 0000000..7bd2db3 --- /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 f44e47d..47baed8 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 0000000..31ce905 --- /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 70ab967..4132547 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 a029469..4b6a0c4 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 84f430c..bbb013b 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 33e49c5..316edb2 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 6396503..c324714 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 b788676..043fd1f 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 055d63b..c8039e1 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 0000000..5213b6b --- /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 29dc8be..42d14cd 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 1470491..18aadaf 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 4a287b9..63e6829 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 ae9c420..e8794bc 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 1889302..2995c0e 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 aa6f925..c04bf9e 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 1357fe4..4e91ca2 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 28f0519..680fe77 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 9fd2349..8a81f48 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 9a1cb7a..6d3bb66 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 483446f..5fe0661 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 0000000..03ab5da --- /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 0000000..4addee2 --- /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 3a7b051..774ac77 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 52b58c4..08a4a86 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 c49ed3d..50afb37 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 183119c..f326f21 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 0000000..f760226 --- /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 834d8fc..10a7ace 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 ee9b242..6cc4490 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 bcecd85..ff97fef 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 2243337..7b64cfe 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 501be02..5f3b89f 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 51c1c13..18209d9 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 eda4371..4bf6fc1 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 71a54b8..8271735 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 0000000..acd3181 --- /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 eb450e1..6f676fd 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 44ba6cd..ce81ede 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 949fa90..1b9b0c4 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 a8b371f..1b9afb9 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 f5acfd6..8cdad76 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 e3fb264..01058a3 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 6b7a150..cedba56 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 0229d97..0112c4e 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 33a05df..f5e6b8d 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 55a55c4..dbb2e60 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 0ade70c..94da8fc 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 57e23fa..c32a17d 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 73cfe2a..3072560 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 0000000..7c39a63 --- /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 e71072e..c68f6bb 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 0000000..5b17ab7 --- /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 9c5fa94..d501b3c 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 0000000..4bc5bbd --- /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 da918aa..ef48000 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 e5aa513..c5f3901 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 0000000..bfe7a00 --- /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 0000000..55fbe8a --- /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 5e3af9d..ecec50d 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 bf2e951..09f1917 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 3f8cec3..de2ed28 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 e1b9a15..79f1dfc 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 75006cb..91b606f 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 cc2c972..ebbbee9 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 58d0652..d567da0 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 081fb9b..bdcb28b 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 7d33601..977fba5 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 2fc48de..f799ddd 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 af002ba..0fe1cbd 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 c804b5c..27ccaf8 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: { @@ -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 c001e5a..2396530 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 e17b5a4..35c0691 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 246b206..82902ac 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 6d6ebe1..bbe65c6 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 @@ -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 37fbf35..3dc9a43 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 e237ee5..3ae87ed 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 0000000..6950c00 --- /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 b607a6e..3473984 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 0000000..09c740d --- /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 4826216..5cd145e 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 a7dc40b..5389ed3 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); |