aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS3
-rw-r--r--examples/pugl_cursor_demo.c171
-rw-r--r--pugl/detail/mac.h2
-rw-r--r--pugl/detail/mac.m51
-rw-r--r--pugl/detail/win.c41
-rw-r--r--pugl/detail/win.h1
-rw-r--r--pugl/detail/x11.c74
-rw-r--r--pugl/detail/x11.h3
-rw-r--r--pugl/pugl.h26
-rw-r--r--pugl/pugl.hpp20
-rw-r--r--wscript9
11 files changed, 397 insertions, 4 deletions
diff --git a/AUTHORS b/AUTHORS
index 1470491..18aadaf 100644
--- a/AUTHORS
+++ b/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/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 <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 {
+ 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 <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>
@@ -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
@@ -1124,6 +1124,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
@@ -1167,6 +1183,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
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<PuglView, puglFreeView>
{
@@ -514,6 +527,13 @@ public:
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()
{
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 <GL/glx.h>
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')