diff options
Diffstat (limited to 'pugl/pugl')
28 files changed, 1822 insertions, 425 deletions
diff --git a/pugl/pugl/detail/implementation.c b/pugl/pugl/detail/implementation.c index ee9b242..055e917 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); @@ -309,7 +311,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 +322,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 +338,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) { 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..23671ae 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, 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..edd2bd0 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; @@ -62,11 +63,15 @@ struct PuglViewImpl { PuglBlob clipboard; 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 +79,7 @@ struct PuglViewImpl { bool visible; }; -/** Cross-platform world definition. */ +/// Cross-platform world definition struct PuglWorldImpl { PuglWorldInternals* impl; PuglWorldHandle handle; @@ -86,27 +91,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..4f7afee 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, @@ -1064,7 +1115,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..7b8daf2 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) && @@ -151,7 +158,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 +205,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,8 +248,28 @@ 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 +284,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 +325,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 +354,10 @@ puglRealize(PuglView* view) "XCreateID failed\n"); } +#ifdef HAVE_XCURSOR + puglDefineCursorShape(view, impl->cursorShape); +#endif + puglDispatchSimpleEvent(view, PUGL_CREATE); return PUGL_SUCCESS; @@ -450,10 +515,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 +565,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 +590,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 +615,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 +629,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 +642,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 +659,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 +729,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 +875,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); } } @@ -896,8 +979,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); } } @@ -1033,7 +1118,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 +1134,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 +1188,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 +1218,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 @@ -1202,6 +1289,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..cf647ed 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" @@ -43,7 +44,7 @@ typedef struct { typedef struct { XID alarm; PuglView* view; - uint64_t id; + uintptr_t id; } PuglTimer; struct PuglWorldInternalsImpl { @@ -60,13 +61,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_backend.h b/pugl/pugl/pugl_cairo.hpp index 3f8cec3..5b17ab7 100644 --- a/pugl/pugl/pugl_cairo_backend.h +++ b/pugl/pugl/pugl_cairo.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_CAIRO_BACKEND_H -#define PUGL_PUGL_CAIRO_BACKEND_H +/** + @file pugl_cairo.hpp + @brief Declaration of Cairo backend accessor for C++. +*/ + +#ifndef PUGL_PUGL_CAIRO_HPP +#define PUGL_PUGL_CAIRO_HPP -#warning "This header is deprecated, use pugl/pugl_cairo.h instead." +#include "pugl/pugl.h" #include "pugl/pugl_cairo.h" -#endif // PUGL_PUGL_CAIRO_BACKEND_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_backend.h b/pugl/pugl/pugl_gl.hpp index e1b9a15..4bc5bbd 100644 --- a/pugl/pugl/pugl_gl_backend.h +++ b/pugl/pugl/pugl_gl.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,47 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef PUGL_PUGL_GL_BACKEND_H -#define PUGL_PUGL_GL_BACKEND_H +/** + @file pugl_gl.hpp + @brief OpenGL-specific C++ API. +*/ + +#ifndef PUGL_PUGL_GL_HPP +#define PUGL_PUGL_GL_HPP -#warning "This header is deprecated, use pugl/pugl_gl.h instead." +#include "pugl/pugl.h" #include "pugl/pugl_gl.h" -#endif // PUGL_PUGL_GL_BACKEND_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 |