From 2f90b716c29a623b4ec090bb455d2c49b4062f98 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 1 Jun 2020 13:03:16 +0200 Subject: Add puglSetCursor() --- AUTHORS | 3 +- examples/pugl_cursor_demo.c | 171 ++++++++++++++++++++++++++++++++++++++++++++ pugl/detail/mac.h | 2 + pugl/detail/mac.m | 51 ++++++++++++- pugl/detail/win.c | 41 +++++++++++ pugl/detail/win.h | 1 + pugl/detail/x11.c | 74 ++++++++++++++++++- pugl/detail/x11.h | 3 + pugl/pugl.h | 26 +++++++ pugl/pugl.hpp | 20 ++++++ wscript | 9 ++- 11 files changed, 397 insertions(+), 4 deletions(-) create mode 100644 examples/pugl_cursor_demo.c diff --git a/AUTHORS b/AUTHORS index 1470491..18aadaf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,4 +8,5 @@ Hanspeter Portner Stefan Westerfeld Jordan Halase Oliver Schmidt -Zoë Sparks \ No newline at end of file +Zoë Sparks +Jean Pierre Cimalando diff --git a/examples/pugl_cursor_demo.c b/examples/pugl_cursor_demo.c new file mode 100644 index 0000000..244d22f --- /dev/null +++ b/examples/pugl_cursor_demo.c @@ -0,0 +1,171 @@ +/* + Copyright 2012-2020 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 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 + +static const int N_CURSORS = 7; +static const int N_ROWS = 2; +static const int N_COLS = 4; + +typedef struct { + PuglTestOptions opts; + PuglWorld* world; + 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/detail/mac.h b/pugl/detail/mac.h index b38dcd3..296faeb 100644 --- a/pugl/detail/mac.h +++ b/pugl/detail/mac.h @@ -61,6 +61,8 @@ struct PuglInternalsImpl { NSApplication* app; PuglWrapperView* wrapperView; NSView* drawView; + NSCursor* cursor; PuglWindow* window; uint32_t mods; + bool mouseTracked; }; diff --git a/pugl/detail/mac.m b/pugl/detail/mac.m index da9de20..3bf7cdf 100644 --- a/pugl/detail/mac.m +++ b/pugl/detail/mac.m @@ -343,11 +343,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 @@ -807,7 +811,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* @@ -1309,6 +1317,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, diff --git a/pugl/detail/win.c b/pugl/detail/win.c index 4b73c99..3f9f8f9 100644 --- a/pugl/detail/win.c +++ b/pugl/detail/win.c @@ -190,6 +190,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); @@ -540,6 +542,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); @@ -1107,6 +1114,40 @@ puglWinStubLeave(PuglView* view, const PuglEventExpose* expose) 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; +} + const PuglBackend* puglStubBackend(void) { diff --git a/pugl/detail/win.h b/pugl/detail/win.h index 087bbce..1b9b0c4 100644 --- a/pugl/detail/win.h +++ b/pugl/detail/win.h @@ -35,6 +35,7 @@ struct PuglInternalsImpl { PuglWinPFD pfd; int pfId; HWND hwnd; + HCURSOR cursor; HDC hdc; PuglSurface* surface; DWORD refreshRate; diff --git a/pugl/detail/x11.c b/pugl/detail/x11.c index da7b23d..2d74b68 100644 --- a/pugl/detail/x11.c +++ b/pugl/detail/x11.c @@ -41,6 +41,11 @@ # include #endif +#ifdef HAVE_XCURSOR +# include +# include +#endif + #include #include @@ -152,7 +157,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 @@ -240,6 +251,25 @@ updateSizeHints(const PuglView* view) 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) { @@ -323,6 +353,10 @@ puglRealize(PuglView* view) "XCreateID failed\n"); } +#ifdef HAVE_XCURSOR + puglDefineCursorShape(view, impl->cursorShape); +#endif + puglDispatchSimpleEvent(view, PUGL_CREATE); return PUGL_SUCCESS; @@ -1234,6 +1268,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/detail/x11.h b/pugl/detail/x11.h index aac8177..164a57e 100644 --- a/pugl/detail/x11.h +++ b/pugl/detail/x11.h @@ -64,6 +64,9 @@ struct PuglInternalsImpl { int screen; XVisualInfo* vi; Window win; +#ifdef HAVE_XCURSOR + unsigned cursorShape; +#endif XIC xic; PuglSurface* surface; PuglEvent pendingConfigure; diff --git a/pugl/pugl.h b/pugl/pugl.h index b489407..3d80d6e 100644 --- a/pugl/pugl.h +++ b/pugl/pugl.h @@ -1123,6 +1123,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. */ @@ -1166,6 +1182,16 @@ puglSetClipboard(PuglView* view, 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. diff --git a/pugl/pugl.hpp b/pugl/pugl.hpp index 2e18306..f1aef2f 100644 --- a/pugl/pugl.hpp +++ b/pugl/pugl.hpp @@ -357,6 +357,19 @@ 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 : public detail::Wrapper { @@ -514,6 +527,13 @@ public: return static_cast(puglSetBackend(cobj(), backend)); } + /// @copydoc puglSetCursor + Status setCursor(const Cursor cursor) + { + return static_cast( + puglSetCursor(cobj(), static_cast(cursor))); + } + /// @copydoc puglRequestAttention Status requestAttention() { diff --git a/wscript b/wscript index da520e0..ce10d58 100644 --- a/wscript +++ b/wscript @@ -125,6 +125,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 int main(void) { glXSwapBuffers(0, 0); return 0; }""" @@ -309,7 +314,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: @@ -366,6 +371,8 @@ def build(bld): 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') -- cgit v1.2.1