diff options
| -rw-r--r-- | AUTHORS | 3 | ||||
| -rw-r--r-- | examples/pugl_cursor_demo.c | 171 | ||||
| -rw-r--r-- | pugl/detail/mac.h | 2 | ||||
| -rw-r--r-- | pugl/detail/mac.m | 51 | ||||
| -rw-r--r-- | pugl/detail/win.c | 41 | ||||
| -rw-r--r-- | pugl/detail/win.h | 1 | ||||
| -rw-r--r-- | pugl/detail/x11.c | 74 | ||||
| -rw-r--r-- | pugl/detail/x11.h | 3 | ||||
| -rw-r--r-- | pugl/pugl.h | 26 | ||||
| -rw-r--r-- | pugl/pugl.hpp | 20 | ||||
| -rw-r--r-- | wscript | 9 | 
11 files changed, 397 insertions, 4 deletions
| @@ -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()  	{ @@ -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') | 
