From d0c878fe0e496083a1e892795c23a1ac939714a9 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 27 Aug 2014 23:32:26 +0000 Subject: Event-based dispatch. --- pugl/common.h | 124 +++++++++++++++ pugl/event.h | 223 ++++++++++++++++++++++++++ pugl/pugl.h | 98 ++---------- pugl/pugl_internal.h | 115 ++++++++++---- pugl/pugl_osx.m | 6 + pugl/pugl_win.cpp | 6 + pugl/pugl_x11.c | 438 ++++++++++++++++++++++++++++----------------------- pugl_cairo_test.c | 49 +++--- 8 files changed, 731 insertions(+), 328 deletions(-) create mode 100644 pugl/common.h create mode 100644 pugl/event.h diff --git a/pugl/common.h b/pugl/common.h new file mode 100644 index 0000000..4a92403 --- /dev/null +++ b/pugl/common.h @@ -0,0 +1,124 @@ +/* + Copyright 2014 David Robillard + + 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 common.h Common definitions. + + @ingroup pugl + @{ +*/ + +#ifndef PUGL_COMMON_H_INCLUDED +#define PUGL_COMMON_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + A Pugl view. +*/ +typedef struct PuglViewImpl PuglView; + +/** + A native window handle. + + On X11, this is a Window. + On OSX, this is an NSView*. + On Windows, this is a HWND. +*/ +typedef intptr_t PuglNativeWindow; + +/** + Handle for opaque user data. +*/ +typedef void* PuglHandle; + +/** + Return status code. +*/ +typedef enum { + PUGL_SUCCESS = 0 +} PuglStatus; + +/** + Drawing context type. +*/ +typedef enum { + PUGL_GL, + PUGL_CAIRO +} PuglContextType; + +/** + Convenience symbols for ASCII control characters. +*/ +typedef enum { + PUGL_CHAR_BACKSPACE = 0x08, + PUGL_CHAR_ESCAPE = 0x1B, + PUGL_CHAR_DELETE = 0x7F +} PuglChar; + +/** + Keyboard modifier flags. +*/ +typedef enum { + PUGL_MOD_SHIFT = 1, /**< Shift key */ + PUGL_MOD_CTRL = 1 << 1, /**< Control key */ + PUGL_MOD_ALT = 1 << 2, /**< Alt/Option key */ + PUGL_MOD_SUPER = 1 << 3 /**< Mod4/Command/Windows key */ +} PuglMod; + +/** + Special (non-Unicode) keyboard keys. +*/ +typedef enum { + PUGL_KEY_F1 = 1, + PUGL_KEY_F2, + PUGL_KEY_F3, + PUGL_KEY_F4, + PUGL_KEY_F5, + PUGL_KEY_F6, + PUGL_KEY_F7, + PUGL_KEY_F8, + PUGL_KEY_F9, + PUGL_KEY_F10, + PUGL_KEY_F11, + PUGL_KEY_F12, + PUGL_KEY_LEFT, + PUGL_KEY_UP, + PUGL_KEY_RIGHT, + PUGL_KEY_DOWN, + PUGL_KEY_PAGE_UP, + PUGL_KEY_PAGE_DOWN, + PUGL_KEY_HOME, + PUGL_KEY_END, + PUGL_KEY_INSERT, + PUGL_KEY_SHIFT, + PUGL_KEY_CTRL, + PUGL_KEY_ALT, + PUGL_KEY_SUPER +} PuglKey; + +/** + @} + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PUGL_COMMON_H_INCLUDED */ diff --git a/pugl/event.h b/pugl/event.h new file mode 100644 index 0000000..211916d --- /dev/null +++ b/pugl/event.h @@ -0,0 +1,223 @@ +/* + Copyright 2014 David Robillard + + 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 event.h PuglEvent struct definition. + + @ingroup pugl + @{ + + @name Event + @{ +*/ + +#ifndef PUGL_EVENT_H_INCLUDED +#define PUGL_EVENT_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#else +# include +#endif + +#include "pugl/common.h" + +/** + The type of a PuglEvent. +*/ +typedef enum { + PUGL_BUTTON_PRESS, + PUGL_BUTTON_RELEASE, + PUGL_CONFIGURE, + PUGL_EXPOSE, + PUGL_KEY_PRESS, + PUGL_KEY_RELEASE, + PUGL_ENTER_NOTIFY, + PUGL_LEAVE_NOTIFY, + PUGL_MOTION_NOTIFY, + PUGL_NOTHING, + PUGL_SCROLL +} PuglEventType; + +/** + Reason for a PuglEventCrossing. +*/ +typedef enum { + PUGL_CROSSING_NORMAL, /**< Crossing due to pointer motion. */ + PUGL_CROSSING_GRAB, /**< Crossing due to a grab. */ + PUGL_CROSSING_UNGRAB /**< Crossing due to a grab release. */ +} PuglCrossingMode; + +/** + Common header for all event structs. +*/ +typedef struct { + PuglEventType type; /**< Event type. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ +} PuglEventAny; + +/** + Button press or release event. + + For event types PUGL_BUTTON_PRESS and PUGL_BUTTON_RELEASE. +*/ +typedef struct { + PuglEventType type; /**< PUGL_BUTTON_PRESS or PUGL_BUTTON_RELEASE. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ + uint32_t time; /**< Time in milliseconds. */ + double x; /**< View-relative X coordinate. */ + double y; /**< View-relative Y coordinate. */ + double x_root; /**< Root-relative X coordinate. */ + double y_root; /**< Root-relative Y coordinate. */ + unsigned state; /**< Bitwise OR of PuglMod flags. */ + unsigned button; /**< 1-relative button number. */ +} PuglEventButton; + +/** + Configure event for when window size or position has changed. +*/ +typedef struct { + PuglEventType type; /**< PUGL_CONFIGURE. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ + double x; /**< New parent-relative X coordinate. */ + double y; /**< New parent-relative Y coordinate. */ + double width; /**< New width. */ + double height; /**< New height. */ +} PuglEventConfigure; + +/** + Expose event for when a region must be redrawn. +*/ +typedef struct { + PuglEventType type; /**< PUGL_EXPOSE. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ + double x; /**< View-relative X coordinate. */ + 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; + +/** + Key press event. + + Keys that correspond to a Unicode character are expressed as a character + code. For other keys, `character` will be 0 and `special` indicates the key + pressed. +*/ +typedef struct { + PuglEventType type; /**< PUGL_KEY_PRESS or PUGL_KEY_RELEASE. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ + uint32_t time; /**< Time in milliseconds. */ + double x; /**< View-relative X coordinate. */ + double y; /**< View-relative Y coordinate. */ + double x_root; /**< Root-relative X coordinate. */ + double y_root; /**< Root-relative Y coordinate. */ + unsigned state; /**< Bitwise OR of PuglMod flags. */ + uint32_t character; /**< Unicode character code, or 0. */ + PuglKey special; /**< Special key, if character is 0. */ +} PuglEventKey; + +/** + Pointer crossing event (enter and leave). +*/ +typedef struct { + PuglEventType type; /**< PUGL_ENTER_NOTIFY or PUGL_LEAVE_NOTIFY. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ + uint32_t time; /**< Time in milliseconds. */ + double x; /**< View-relative X coordinate. */ + double y; /**< View-relative Y coordinate. */ + double x_root; /**< Root-relative X coordinate. */ + double y_root; /**< Root-relative Y coordinate. */ + unsigned state; /**< Bitwise OR of PuglMod flags. */ + PuglCrossingMode mode; /**< Reason for crossing. */ +} PuglEventCrossing; + +/** + Pointer motion event. +*/ +typedef struct { + PuglEventType type; /**< PUGL_MOTION_NOTIFY. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ + uint32_t time; /**< Time in milliseconds. */ + double x; /**< View-relative X coordinate. */ + double y; /**< View-relative Y coordinate. */ + double x_root; /**< Root-relative X coordinate. */ + double y_root; /**< Root-relative Y coordinate. */ + unsigned state; /**< Bitwise OR of PuglMod flags. */ + bool is_hint; /**< True iff this event is a motion hint. */ + bool focus; /**< True iff this is the focused window. */ +} 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` = + 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. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ + uint32_t time; /**< Time in milliseconds. */ + double x; /**< View-relative X coordinate. */ + double y; /**< View-relative Y coordinate. */ + double x_root; /**< Root-relative X coordinate. */ + double y_root; /**< Root-relative Y coordinate. */ + unsigned state; /**< Bitwise OR of PuglMod flags. */ + double dx; /**< Scroll X distance in lines. */ + double dy; /**< Scroll Y distance in lines. */ +} PuglEventScroll; + +/** + Interface event. + + This is a union of all event structs. 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. +*/ +typedef union { + PuglEventType type; /**< Event type. */ + PuglEventAny any; /**< Valid for all event types. */ + PuglEventButton button; /**< PUGL_BUTTON_PRESS, PUGL_BUTTON_RELEASE. */ + PuglEventConfigure configure; /**< PUGL_CONFIGURE. */ + PuglEventCrossing crossing; /**< PUGL_ENTER_NOTIFY, PUGL_LEAVE_NOTIFY. */ + PuglEventExpose expose; /**< PUGL_EXPOSE. */ + PuglEventKey key; /**< PUGL_KEY_PRESS, PUGL_KEY_RELEASE. */ + PuglEventMotion motion; /**< PUGL_MOTION_NOTIFY. */ + PuglEventScroll scroll; /**< PUGL_SCROLL. */ +} PuglEvent; + +/** + @} + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PUGL_EVENT_H_INCLUDED */ diff --git a/pugl/pugl.h b/pugl/pugl.h index 8cd8afb..d13ce12 100644 --- a/pugl/pugl.h +++ b/pugl/pugl.h @@ -23,6 +23,9 @@ #include +#include "pugl/common.h" +#include "pugl/event.h" + #ifdef PUGL_SHARED # ifdef _WIN32 # define PUGL_LIB_IMPORT __declspec(dllimport) @@ -53,88 +56,9 @@ extern "C" { */ /** - An OpenGL view. -*/ -typedef struct PuglViewImpl PuglView; - -/** - A native window handle. - - On X11, this is a Window. - On OSX, this is an NSView*. - On Windows, this is a HWND. -*/ -typedef intptr_t PuglNativeWindow; - -/** - Return status code. + A function called when an event occurs. */ -typedef enum { - PUGL_SUCCESS = 0 -} PuglStatus; - -/** - Drawing context type. -*/ -typedef enum { - PUGL_GL, - PUGL_CAIRO -} PuglContextType; - -/** - Convenience symbols for ASCII control characters. -*/ -typedef enum { - PUGL_CHAR_BACKSPACE = 0x08, - PUGL_CHAR_ESCAPE = 0x1B, - PUGL_CHAR_DELETE = 0x7F -} PuglChar; - -/** - Special (non-Unicode) keyboard keys. -*/ -typedef enum { - PUGL_KEY_F1 = 1, - PUGL_KEY_F2, - PUGL_KEY_F3, - PUGL_KEY_F4, - PUGL_KEY_F5, - PUGL_KEY_F6, - PUGL_KEY_F7, - PUGL_KEY_F8, - PUGL_KEY_F9, - PUGL_KEY_F10, - PUGL_KEY_F11, - PUGL_KEY_F12, - PUGL_KEY_LEFT, - PUGL_KEY_UP, - PUGL_KEY_RIGHT, - PUGL_KEY_DOWN, - PUGL_KEY_PAGE_UP, - PUGL_KEY_PAGE_DOWN, - PUGL_KEY_HOME, - PUGL_KEY_END, - PUGL_KEY_INSERT, - PUGL_KEY_SHIFT, - PUGL_KEY_CTRL, - PUGL_KEY_ALT, - PUGL_KEY_SUPER -} PuglKey; - -/** - Keyboard modifier flags. -*/ -typedef enum { - PUGL_MOD_SHIFT = 1, /**< Shift key */ - PUGL_MOD_CTRL = 1 << 1, /**< Control key */ - PUGL_MOD_ALT = 1 << 2, /**< Alt/Option key */ - PUGL_MOD_SUPER = 1 << 3 /**< Mod4/Command/Windows key */ -} PuglMod; - -/** - Handle for opaque user data. -*/ -typedef void* PuglHandle; +typedef void (*PuglEventFunc)(PuglView* view, const PuglEvent* event); /** A function called when the window is closed. @@ -313,6 +237,12 @@ puglGetModifiers(PuglView* view); PUGL_API void puglIgnoreKeyRepeat(PuglView* view, bool ignore); +/** + Set the function to call when an event occurs. +*/ +PUGL_API void +puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc); + /** Set the function to call when the window is closed. */ @@ -367,6 +297,12 @@ puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc); PUGL_API PuglNativeWindow puglGetNativeWindow(PuglView* view); +/** + Grab the input focus. +*/ +PUGL_API void +puglGrabFocus(PuglView* view); + /** Process all pending window events. diff --git a/pugl/pugl_internal.h b/pugl/pugl_internal.h index 3db08f7..32d31bf 100644 --- a/pugl/pugl_internal.h +++ b/pugl/pugl_internal.h @@ -24,27 +24,18 @@ If you are copying the pugl code into your source tree, the following symbols can be defined to tweak pugl behaviour: - PUGL_GRAB_FOCUS: Work around reparent keyboard issues by grabbing focus. - PUGL_VERBOSE: Print graphics information to console. PUGL_HAVE_CAIRO: Include Cairo support code. PUGL_HAVE_GL: Include OpenGL support code. */ -#include "pugl.h" - -#ifdef PUGL_VERBOSE -# include -# define PUGL_LOG(str) fprintf(stderr, "pugl: " str) -# define PUGL_LOGF(fmt, ...) fprintf(stderr, "pugl: " fmt, __VA_ARGS__) -#else -# define PUGL_LOG(str) -# define PUGL_LOGF(fmt, ...) -#endif +#include "pugl/pugl.h" +#include "pugl/event.h" typedef struct PuglInternalsImpl PuglInternals; struct PuglViewImpl { PuglHandle handle; + PuglEventFunc eventFunc; PuglCloseFunc closeFunc; PuglDisplayFunc displayFunc; PuglKeyboardFunc keyboardFunc; @@ -71,8 +62,6 @@ struct PuglViewImpl { PuglInternals* puglInitInternals(); -void puglDefaultReshape(PuglView* view, int width, int height); - PuglView* puglInit(int* pargc, char** argv) { @@ -143,27 +132,15 @@ puglGetModifiers(PuglView* view) } void -puglDefaultReshape(PuglView* view, int width, int height) +puglIgnoreKeyRepeat(PuglView* view, bool ignore) { -#ifdef PUGL_HAVE_GL - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, width, height, 0, 0, 1); - glViewport(0, 0, width, height); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); -#endif - return; - - // unused - (void)view; + view->ignoreKeyRepeat = ignore; } void -puglIgnoreKeyRepeat(PuglView* view, bool ignore) +puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc) { - view->ignoreKeyRepeat = ignore; + view->eventFunc = eventFunc; } void @@ -213,3 +190,81 @@ puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc) { view->specialFunc = specialFunc; } + +void +puglEnterContext(PuglView* view); + +void +puglLeaveContext(PuglView* view, bool flush); + +static void +puglDispatchEvent(PuglView* view, const PuglEvent* event) +{ + if (view->eventFunc) { + view->eventFunc(view, event); + } + + switch (event->type) { + case PUGL_CONFIGURE: + puglEnterContext(view); + view->width = event->configure.width; + view->height = event->configure.height; + if (view->reshapeFunc) { + view->reshapeFunc(view, view->width, view->height); + } + puglLeaveContext(view, false); + break; + case PUGL_EXPOSE: + if (event->expose.count == 0) { + puglEnterContext(view); + if (view->displayFunc) { + view->displayFunc(view); + } + view->redisplay = false; + puglLeaveContext(view, true); + } + break; + case PUGL_MOTION_NOTIFY: + view->event_timestamp_ms = event->motion.time; + view->mods = event->motion.state; + if (view->motionFunc) { + view->motionFunc(view, event->motion.x, event->motion.y); + } + break; + case PUGL_SCROLL: + if (view->scrollFunc) { + view->scrollFunc(view, + event->scroll.x, event->scroll.y, + event->scroll.dx, event->scroll.dy); + } + break; + case PUGL_BUTTON_PRESS: + case PUGL_BUTTON_RELEASE: + view->event_timestamp_ms = event->button.time; + view->mods = event->button.state; + if (view->mouseFunc) { + view->mouseFunc(view, + event->button.button, + event->type == PUGL_BUTTON_PRESS, + event->button.x, + event->button.y); + } + break; + case PUGL_KEY_PRESS: + case PUGL_KEY_RELEASE: + view->event_timestamp_ms = event->key.time; + view->mods = event->key.state; + if (event->key.special && view->specialFunc) { + view->specialFunc(view, + event->type == PUGL_KEY_PRESS, + event->key.special); + } else if (event->key.character && view->keyboardFunc) { + view->keyboardFunc(view, + event->type == PUGL_KEY_PRESS, + event->key.character); + } + break; + default: + break; + } +} diff --git a/pugl/pugl_osx.m b/pugl/pugl_osx.m index 86c3c34..d502002 100644 --- a/pugl/pugl_osx.m +++ b/pugl/pugl_osx.m @@ -419,6 +419,12 @@ puglDestroy(PuglView* view) free(view); } +void +puglGrabFocus(PuglView* view) +{ + // TODO +} + PuglStatus puglProcessEvents(PuglView* view) { diff --git a/pugl/pugl_win.cpp b/pugl/pugl_win.cpp index a458bee..9dfa70c 100644 --- a/pugl/pugl_win.cpp +++ b/pugl/pugl_win.cpp @@ -358,6 +358,12 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) return 0; } +void +puglGrabFocus(PuglView* view) +{ + // TODO +} + PuglStatus puglProcessEvents(PuglView* view) { diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c index 509e527..65eaab8 100644 --- a/pugl/pugl_x11.c +++ b/pugl/pugl_x11.c @@ -40,6 +40,7 @@ #endif #include "pugl_internal.h" +#include "pugl/event.h" struct PuglInternalsImpl { Display* display; @@ -60,15 +61,11 @@ puglInitInternals() return (PuglInternals*)calloc(1, sizeof(PuglInternals)); } -int -puglCreateWindow(PuglView* view, const char* title) +static XVisualInfo* +getVisual(PuglView* view) { - PuglInternals* impl = view->impl; - - impl->display = XOpenDisplay(0); - impl->screen = DefaultScreen(impl->display); - - XVisualInfo* vi = NULL; + PuglInternals* const impl = view->impl; + XVisualInfo* vi = NULL; #ifdef PUGL_HAVE_GL if (view->ctx_type == PUGL_GL) { @@ -93,14 +90,6 @@ puglCreateWindow(PuglView* view, const char* title) } else { impl->doubleBuffered = True; } - - impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE); - PUGL_LOGF("GLX depth %d, %s-buffered, %s\n", - vi->depth, - impl->doubleBuffered ? "double" : "single", - (glXIsDirect(impl->display, impl->ctx) - ? "direct (set LIBGL_ALWAYS_INDIRECT=1 to disable)" - : "indirect")); } #endif #ifdef PUGL_HAVE_CAIRO @@ -112,6 +101,81 @@ puglCreateWindow(PuglView* view, const char* title) } #endif + return vi; +} + +static void +createContext(PuglView* view, XVisualInfo* vi) +{ + PuglInternals* const impl = view->impl; + +#ifdef PUGL_HAVE_GL + if (view->ctx_type == PUGL_GL) { + impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE); + } +#endif +#ifdef PUGL_HAVE_CAIRO + if (view->ctx_type == PUGL_CAIRO) { + cairo_surface_t* surface = cairo_xlib_surface_create( + impl->display, impl->win, vi->visual, view->width, view->height); + if (!(impl->cr = cairo_create(surface))) { + fprintf(stderr, "failed to create cairo context\n"); + } + } +#endif +} + +static void +destroyContext(PuglView* view) +{ +#ifdef PUGL_HAVE_GL + if (view->ctx_type == PUGL_GL) { + glXDestroyContext(view->impl->display, view->impl->ctx); + } +#endif +#ifdef PUGL_HAVE_CAIRO + if (view->ctx_type == PUGL_CAIRO) { + glXDestroyContext(view->impl->display, view->impl->ctx); + } +#endif +} + +void +puglEnterContext(PuglView* view) +{ +#ifdef PUGL_HAVE_GL + if (view->ctx_type == PUGL_GL) { + glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx); + } +#endif +} + +void +puglLeaveContext(PuglView* view, bool flush) +{ +#ifdef PUGL_HAVE_GL + if (view->ctx_type == PUGL_GL && flush) { + glFlush(); + if (view->impl->doubleBuffered) { + glXSwapBuffers(view->impl->display, view->impl->win); + } + } +#endif +} + +int +puglCreateWindow(PuglView* view, const char* title) +{ + PuglInternals* const impl = view->impl; + + impl->display = XOpenDisplay(0); + impl->screen = DefaultScreen(impl->display); + + XVisualInfo* const vi = getVisual(view); + if (!vi) { + return 1; + } + Window xParent = view->parent ? (Window)view->parent : RootWindow(impl->display, impl->screen); @@ -124,27 +188,18 @@ puglCreateWindow(PuglView* view, const char* title) attr.background_pixel = BlackPixel(impl->display, impl->screen); attr.border_pixel = BlackPixel(impl->display, impl->screen); attr.colormap = cmap; - attr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask - | ButtonPressMask | ButtonReleaseMask -#ifdef PUGL_GRAB_FOCUS - | EnterWindowMask -#endif - | PointerMotionMask | StructureNotifyMask; + attr.event_mask = (ExposureMask | StructureNotifyMask | + EnterWindowMask | LeaveWindowMask | + KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + PointerMotionMask); impl->win = XCreateWindow( impl->display, xParent, 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual, CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &attr); -#ifdef PUGL_HAVE_CAIRO - if (view->ctx_type == PUGL_CAIRO) { - cairo_surface_t* surface = cairo_xlib_surface_create( - impl->display, impl->win, vi->visual, view->width, view->height); - if (!(impl->cr = cairo_create(surface))) { - fprintf(stderr, "failed to create cairo context\n"); - } - } -#endif + createContext(view, vi); XSizeHints sizeHints; memset(&sizeHints, 0, sizeof(sizeHints)); @@ -174,17 +229,13 @@ puglCreateWindow(PuglView* view, const char* title) void puglShowWindow(PuglView* view) { - PuglInternals* impl = view->impl; - - XMapRaised(impl->display, impl->win); + XMapRaised(view->impl->display, view->impl->win); } void puglHideWindow(PuglView* view) { - PuglInternals* impl = view->impl; - - XUnmapWindow(impl->display, impl->win); + XUnmapWindow(view->impl->display, view->impl->win); } void @@ -194,63 +245,13 @@ puglDestroy(PuglView* view) return; } -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - glXDestroyContext(view->impl->display, view->impl->ctx); - } -#endif - + destroyContext(view); XDestroyWindow(view->impl->display, view->impl->win); XCloseDisplay(view->impl->display); free(view->impl); free(view); } -static void -puglReshape(PuglView* view, int width, int height) -{ -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx); - } -#endif - - if (view->reshapeFunc) { - view->reshapeFunc(view, width, height); - } else { - puglDefaultReshape(view, width, height); - } - - view->width = width; - view->height = height; -} - -static void -puglDisplay(PuglView* view) -{ -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glLoadIdentity(); - } -#endif - - if (view->displayFunc) { - view->displayFunc(view); - } - view->redisplay = false; - -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - glFlush(); - if (view->impl->doubleBuffered) { - glXSwapBuffers(view->impl->display, view->impl->win); - } - } -#endif -} - static PuglKey keySymToSpecial(KeySym sym) { @@ -289,139 +290,184 @@ keySymToSpecial(KeySym sym) } static void -setModifiers(PuglView* view, unsigned xstate, unsigned xtime) +translateKey(PuglView* view, XEvent* xevent, PuglEvent* event) { - view->event_timestamp_ms = xtime; + KeySym sym; + char str[5]; + const int n = XLookupString(&xevent->xkey, str, 4, &sym, NULL); + if (n == 1) { + event->key.character = str[0]; // TODO: multi-byte support + } + event->key.special = keySymToSpecial(sym); +} - view->mods = 0; - view->mods |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0; - view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0; - view->mods |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0; - view->mods |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0; +static unsigned +translateModifiers(unsigned xstate) +{ + unsigned state = 0; + state |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0; + state |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0; + state |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0; + state |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0; + return state; } -static void -dispatchKey(PuglView* view, XEvent* event, bool press) +static PuglEvent +translateEvent(PuglView* view, XEvent xevent) { - KeySym sym; - char str[5]; - const int n = XLookupString(&event->xkey, str, 4, &sym, NULL); - if (n == 0) { - return; - } else if (n > 1) { - fprintf(stderr, "warning: Unsupported multi-byte key %X\n", (int)sym); - return; + PuglEvent event; + memset(&event, 0, sizeof(event)); + + event.any.view = view; + event.any.send_event = xevent.xany.send_event; + + switch (xevent.type) { + case ConfigureNotify: + event.type = PUGL_CONFIGURE; + event.configure.x = xevent.xconfigure.x; + event.configure.y = xevent.xconfigure.y; + event.configure.width = xevent.xconfigure.width; + event.configure.height = xevent.xconfigure.height; + break; + case Expose: + event.type = PUGL_EXPOSE; + event.expose.x = xevent.xexpose.x; + 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_NOTIFY; + event.motion.time = xevent.xmotion.time; + event.motion.x = xevent.xmotion.x; + event.motion.y = xevent.xmotion.y; + event.motion.x_root = xevent.xmotion.x_root; + event.motion.y_root = xevent.xmotion.y_root; + event.motion.state = translateModifiers(xevent.xmotion.state); + event.motion.is_hint = (xevent.xmotion.is_hint == NotifyHint); + break; + case ButtonPress: + if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) { + event.type = PUGL_SCROLL; + event.scroll.time = xevent.xbutton.time; + event.scroll.x = xevent.xbutton.x; + event.scroll.y = xevent.xbutton.y; + event.scroll.x_root = xevent.xbutton.x_root; + event.scroll.y_root = xevent.xbutton.y_root; + event.scroll.state = translateModifiers(xevent.xbutton.state); + event.scroll.dx = 0.0; + event.scroll.dy = 0.0; + switch (xevent.xbutton.button) { + case 4: event.scroll.dy = 1.0f; break; + case 5: event.scroll.dy = -1.0f; break; + case 6: event.scroll.dx = -1.0f; break; + case 7: event.scroll.dx = 1.0f; break; + } + } + // nobreak + case ButtonRelease: + if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) { + event.button.type = ((xevent.type == ButtonPress) + ? PUGL_BUTTON_PRESS + : PUGL_BUTTON_RELEASE); + event.button.time = xevent.xbutton.time; + event.button.x = xevent.xbutton.x; + event.button.y = xevent.xbutton.y; + event.button.x_root = xevent.xbutton.x_root; + event.button.y_root = xevent.xbutton.y_root; + event.button.state = translateModifiers(xevent.xbutton.state); + event.button.button = xevent.xbutton.button; + } + break; + case KeyPress: + case KeyRelease: + event.type = ((xevent.type == KeyPress) + ? PUGL_KEY_PRESS + : PUGL_KEY_RELEASE); + event.key.time = xevent.xbutton.time; + event.key.x = xevent.xbutton.x; + event.key.y = xevent.xbutton.y; + event.key.x_root = xevent.xbutton.x_root; + event.key.y_root = xevent.xbutton.y_root; + event.key.state = translateModifiers(xevent.xbutton.state); + translateKey(view, &xevent, &event); + break; + case EnterNotify: + case LeaveNotify: + event.type = ((xevent.type == EnterNotify) + ? PUGL_ENTER_NOTIFY + : PUGL_LEAVE_NOTIFY); + event.crossing.time = xevent.xcrossing.time; + event.crossing.x = xevent.xcrossing.x; + event.crossing.y = xevent.xcrossing.y; + event.crossing.x_root = xevent.xcrossing.x_root; + event.crossing.y_root = xevent.xcrossing.y_root; + event.crossing.state = translateModifiers(xevent.xcrossing.state); + event.crossing.mode = PUGL_CROSSING_NORMAL; + if (xevent.xcrossing.mode == NotifyGrab) { + event.crossing.mode = PUGL_CROSSING_GRAB; + } else if (xevent.xcrossing.mode == NotifyUngrab) { + event.crossing.mode = PUGL_CROSSING_UNGRAB; + } + break; + default: + break; } - const PuglKey special = keySymToSpecial(sym); - if (special && view->specialFunc) { - view->specialFunc(view, press, special); - } else if (!special && view->keyboardFunc) { - view->keyboardFunc(view, press, str[0]); - } + return event; +} + +void +puglGrabFocus(PuglView* view) +{ + XSetInputFocus( + view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime); } PuglStatus puglProcessEvents(PuglView* view) { - XEvent event; + XEvent xevent; while (XPending(view->impl->display) > 0) { - XNextEvent(view->impl->display, &event); - switch (event.type) { - case MapNotify: - puglReshape(view, view->width, view->height); - break; - case ConfigureNotify: - if ((event.xconfigure.width != view->width) || - (event.xconfigure.height != view->height)) { - puglReshape(view, - event.xconfigure.width, - event.xconfigure.height); - } - break; - case Expose: - if (event.xexpose.count != 0) { - break; - } - puglDisplay(view); - break; - case MotionNotify: - setModifiers(view, event.xmotion.state, event.xmotion.time); - if (view->motionFunc) { - view->motionFunc(view, event.xmotion.x, event.xmotion.y); - } - break; - case ButtonPress: - setModifiers(view, event.xbutton.state, event.xbutton.time); - if (event.xbutton.button >= 4 && event.xbutton.button <= 7) { - if (view->scrollFunc) { - float dx = 0, dy = 0; - switch (event.xbutton.button) { - case 4: dy = 1.0f; break; - case 5: dy = -1.0f; break; - case 6: dx = -1.0f; break; - case 7: dx = 1.0f; break; - } - view->scrollFunc(view, - event.xbutton.x, event.xbutton.y, - dx, dy); - } - break; - } - // nobreak - case ButtonRelease: - setModifiers(view, event.xbutton.state, event.xbutton.time); - if (view->mouseFunc && - (event.xbutton.button < 4 || event.xbutton.button > 7)) { - view->mouseFunc(view, - event.xbutton.button, event.type == ButtonPress, - event.xbutton.x, event.xbutton.y); + XNextEvent(view->impl->display, &xevent); + bool ignore = false; + if (xevent.type == ClientMessage) { + // Handle close message + char* type = XGetAtomName(view->impl->display, + xevent.xclient.message_type); + if (!strcmp(type, "WM_PROTOCOLS") && view->closeFunc) { + view->closeFunc(view); } - break; - case KeyPress: - setModifiers(view, event.xkey.state, event.xkey.time); - dispatchKey(view, &event, true); - break; - case KeyRelease: - setModifiers(view, event.xkey.state, event.xkey.time); + XFree(type); + continue; + } else if (xevent.type == KeyRelease) { + // Ignore key repeat if necessary if (view->ignoreKeyRepeat && XEventsQueued(view->impl->display, QueuedAfterReading)) { XEvent next; XPeekEvent(view->impl->display, &next); if (next.type == KeyPress && - next.xkey.time == event.xkey.time && - next.xkey.keycode == event.xkey.keycode) { - XNextEvent(view->impl->display, &event); - break; - } - } - dispatchKey(view, &event, false); - break; - case ClientMessage: { - char* type = XGetAtomName(view->impl->display, - event.xclient.message_type); - if (!strcmp(type, "WM_PROTOCOLS")) { - if (view->closeFunc) { - view->closeFunc(view); + next.xkey.time == xevent.xkey.time && + next.xkey.keycode == xevent.xkey.keycode) { + XNextEvent(view->impl->display, &xevent); + ignore = true; } } - XFree(type); - } break; -#ifdef PUGL_GRAB_FOCUS - case EnterNotify: - XSetInputFocus(view->impl->display, - view->impl->win, - RevertToPointerRoot, - CurrentTime); - break; -#endif - default: - break; + } + + if (!ignore) { + // Translate and dispatch event + const PuglEvent event = translateEvent(view, xevent); + puglDispatchEvent(view, &event); } } if (view->redisplay) { - puglDisplay(view); + const PuglEventExpose expose = { + PUGL_EXPOSE, view, true, 0, 0, view->width, view->height, 0 + }; + puglDispatchEvent(view, (const PuglEvent*)&expose); } return PUGL_SUCCESS; diff --git a/pugl_cairo_test.c b/pugl_cairo_test.c index 6ff5bd5..bd97668 100644 --- a/pugl_cairo_test.c +++ b/pugl_cairo_test.c @@ -63,7 +63,7 @@ roundedBox(cairo_t* cr, double x, double y, double w, double h) } static void -drawButton(cairo_t* cr, const Button* but) +buttonDraw(cairo_t* cr, const Button* but) { // Draw base if (but->pressed) { @@ -90,39 +90,47 @@ drawButton(cairo_t* cr, const Button* but) cairo_show_text(cr, but->label); } +static bool +buttonTouches(const Button* but, double x, double y) +{ + return (x >= toggle_button.x && x <= toggle_button.x + toggle_button.w && + y >= toggle_button.y && y <= toggle_button.y + toggle_button.h); +} + static void onDisplay(PuglView* view) { cairo_t* cr = puglGetContext(view); - drawButton(cr, &toggle_button); + buttonDraw(cr, &toggle_button); } static void -onKeyboard(PuglView* view, bool press, uint32_t key) +onClose(PuglView* view) { - if (key == 'q' || key == 'Q' || key == PUGL_CHAR_ESCAPE) { - quit = 1; - } + quit = 1; } static void -onMouse(PuglView* view, int button, bool press, int x, int y) +onEvent(PuglView* view, const PuglEvent* event) { - if (press && - x >= toggle_button.x && x <= toggle_button.x + toggle_button.w && - y >= toggle_button.y && y <= toggle_button.y + toggle_button.h) { - toggle_button.pressed = !toggle_button.pressed; - puglPostRedisplay(view); + switch (event->type) { + case PUGL_KEY_PRESS: + if (event->key.character == 'q' || + event->key.character == 'Q' || + event->key.character == PUGL_CHAR_ESCAPE) { + quit = 1; + } + break; + case PUGL_BUTTON_PRESS: + if (buttonTouches(&toggle_button, event->button.x, event->button.y)) { + toggle_button.pressed = !toggle_button.pressed; + puglPostRedisplay(view); + } + default: break; } } -static void -onClose(PuglView* view) -{ - quit = 1; -} - int main(int argc, char** argv) { @@ -148,10 +156,9 @@ main(int argc, char** argv) puglInitWindowSize(view, 512, 512); puglInitResizable(view, resizable); puglInitContextType(view, PUGL_CAIRO); - + puglIgnoreKeyRepeat(view, ignoreKeyRepeat); - puglSetKeyboardFunc(view, onKeyboard); - puglSetMouseFunc(view, onMouse); + puglSetEventFunc(view, onEvent); puglSetDisplayFunc(view, onDisplay); puglSetCloseFunc(view, onClose); -- cgit v1.2.3