diff options
Diffstat (limited to 'include/pugl')
30 files changed, 9413 insertions, 0 deletions
diff --git a/include/pugl/detail/implementation.c b/include/pugl/detail/implementation.c new file mode 100644 index 0000000..6a5f932 --- /dev/null +++ b/include/pugl/detail/implementation.c @@ -0,0 +1,493 @@ +/* + 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 implementation.c + @brief Platform-independent implementation. +*/ + +#include "pugl/detail/implementation.h" +#include "pugl/pugl.h" + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static const char* +puglLogLevelPrefix(const PuglLogLevel level) +{ + switch (level) { + case PUGL_LOG_LEVEL_ERR: + return "error: "; + case PUGL_LOG_LEVEL_WARNING: + return "warning: "; + case PUGL_LOG_LEVEL_INFO: + case PUGL_LOG_LEVEL_DEBUG: + return ""; + } + + return ""; +} + +static void +puglDefaultLogFunc(PuglWorld* PUGL_UNUSED(world), + PuglLogLevel level, + const char* msg) +{ + fprintf(stderr, "%s%s", puglLogLevelPrefix(level), msg); +} + +const char* +puglStrerror(const PuglStatus status) +{ + // clang-format off + switch (status) { + case PUGL_SUCCESS: return "Success"; + 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"; + case PUGL_REALIZE_FAILED: return "View creation failed"; + case PUGL_SET_FORMAT_FAILED: return "Failed to set pixel format"; + case PUGL_CREATE_CONTEXT_FAILED: return "Failed to create drawing context"; + case PUGL_UNSUPPORTED_TYPE: return "Unsupported data type"; + } + // clang-format on + + return "Unknown error"; +} + +void +puglSetString(char** dest, const char* string) +{ + if (*dest != string) { + const size_t len = strlen(string); + + *dest = (char*)realloc(*dest, len + 1); + strncpy(*dest, string, len + 1); + } +} + +void +puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len) +{ + if (data) { + dest->len = len; + dest->data = realloc(dest->data, len + 1); + memcpy(dest->data, data, len); + ((char*)dest->data)[len] = 0; + } else { + dest->len = 0; + dest->data = NULL; + } +} + +static void +puglSetDefaultHints(PuglHints hints) +{ + hints[PUGL_USE_COMPAT_PROFILE] = PUGL_TRUE; + hints[PUGL_CONTEXT_VERSION_MAJOR] = 2; + hints[PUGL_CONTEXT_VERSION_MINOR] = 0; + hints[PUGL_RED_BITS] = 8; + hints[PUGL_GREEN_BITS] = 8; + hints[PUGL_BLUE_BITS] = 8; + hints[PUGL_ALPHA_BITS] = 8; + hints[PUGL_DEPTH_BITS] = 0; + hints[PUGL_STENCIL_BITS] = 0; + hints[PUGL_SAMPLES] = 0; + hints[PUGL_DOUBLE_BUFFER] = PUGL_TRUE; + hints[PUGL_SWAP_INTERVAL] = PUGL_DONT_CARE; + hints[PUGL_RESIZABLE] = PUGL_FALSE; + hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE; + hints[PUGL_REFRESH_RATE] = PUGL_DONT_CARE; +} + +PuglWorld* +puglNewWorld(PuglWorldType type, PuglWorldFlags flags) +{ + PuglWorld* world = (PuglWorld*)calloc(1, sizeof(PuglWorld)); + if (!world || !(world->impl = puglInitWorldInternals(type, flags))) { + free(world); + return NULL; + } + + world->startTime = puglGetTime(world); + world->logFunc = puglDefaultLogFunc; + world->logLevel = PUGL_LOG_LEVEL_INFO; + + puglSetString(&world->className, "Pugl"); + + return world; +} + +void +puglFreeWorld(PuglWorld* const world) +{ + puglFreeWorldInternals(world); + free(world->className); + free(world->views); + free(world); +} + +void +puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle) +{ + world->handle = handle; +} + +PuglWorldHandle +puglGetWorldHandle(PuglWorld* world) +{ + return world->handle; +} + +PuglStatus +puglSetLogFunc(PuglWorld* world, PuglLogFunc logFunc) +{ + world->logFunc = logFunc; + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetLogLevel(PuglWorld* world, PuglLogLevel level) +{ + world->logLevel = level; + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetClassName(PuglWorld* const world, const char* const name) +{ + puglSetString(&world->className, name); + return PUGL_SUCCESS; +} + +PuglView* +puglNewView(PuglWorld* const world) +{ + PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); + if (!view || !(view->impl = puglInitViewInternals())) { + free(view); + return NULL; + } + + view->world = world; + view->minWidth = 1; + view->minHeight = 1; + + puglSetDefaultHints(view->hints); + + // Add to world view list + ++world->numViews; + world->views = (PuglView**)realloc(world->views, + world->numViews * sizeof(PuglView*)); + + world->views[world->numViews - 1] = view; + + return view; +} + +void +puglFreeView(PuglView* view) +{ + puglDispatchSimpleEvent(view, PUGL_DESTROY); + + // Remove from world view list + PuglWorld* world = view->world; + for (size_t i = 0; i < world->numViews; ++i) { + if (world->views[i] == view) { + if (i == world->numViews - 1) { + world->views[i] = NULL; + } else { + memmove(world->views + i, world->views + i + 1, + sizeof(PuglView*) * (world->numViews - i - 1)); + world->views[world->numViews - 1] = NULL; + } + --world->numViews; + } + } + + free(view->title); + free(view->clipboard.data); + puglFreeViewInternals(view); + free(view); +} + +PuglWorld* +puglGetWorld(PuglView* view) +{ + return view->world; +} + +PuglStatus +puglSetViewHint(PuglView* view, PuglViewHint hint, int value) +{ + if (value == PUGL_DONT_CARE) { + switch (hint) { + case PUGL_USE_COMPAT_PROFILE: + case PUGL_USE_DEBUG_CONTEXT: + case PUGL_CONTEXT_VERSION_MAJOR: + case PUGL_CONTEXT_VERSION_MINOR: + case PUGL_SWAP_INTERVAL: + return PUGL_BAD_PARAMETER; + default: + break; + } + } + + if (hint < PUGL_NUM_VIEW_HINTS) { + view->hints[hint] = value; + return PUGL_SUCCESS; + } + + return PUGL_BAD_PARAMETER; +} + +int +puglGetViewHint(const PuglView* view, PuglViewHint hint) +{ + if (hint < PUGL_NUM_VIEW_HINTS) { + return view->hints[hint]; + } + + return PUGL_DONT_CARE; +} + +PuglStatus +puglSetParentWindow(PuglView* view, PuglNativeView parent) +{ + view->parent = parent; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetBackend(PuglView* view, const PuglBackend* backend) +{ + view->backend = backend; + return PUGL_SUCCESS; +} + +void +puglSetHandle(PuglView* view, PuglHandle handle) +{ + view->handle = handle; +} + +PuglHandle +puglGetHandle(PuglView* view) +{ + return view->handle; +} + +bool +puglGetVisible(const PuglView* view) +{ + return view->visible; +} + +PuglRect +puglGetFrame(const PuglView* view) +{ + return view->frame; +} + +void* +puglGetContext(PuglView* view) +{ + return view->backend->getContext(view); +} + +#ifndef PUGL_DISABLE_DEPRECATED + +PuglStatus +puglPollEvents(PuglWorld* world, double timeout) +{ + return puglUpdate(world, timeout); +} + +PuglStatus +puglDispatchEvents(PuglWorld* world) +{ + return puglUpdate(world, 0.0); +} + +PuglStatus +puglEnterContext(PuglView* view, bool drawing) +{ + const PuglEventExpose expose = { + PUGL_EXPOSE, 0, 0.0, 0.0, view->frame.width, view->frame.height}; + + view->backend->enter(view, drawing ? &expose : NULL); + + return PUGL_SUCCESS; +} + +PuglStatus +puglLeaveContext(PuglView* view, bool drawing) +{ + const PuglEventExpose expose = { + PUGL_EXPOSE, 0, 0.0, 0.0, view->frame.width, view->frame.height}; + + view->backend->leave(view, drawing ? &expose : NULL); + + return PUGL_SUCCESS; +} + +#endif + +PuglStatus +puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc) +{ + view->eventFunc = eventFunc; + return PUGL_SUCCESS; +} + +/// Return the code point for buf, or the replacement character on error +uint32_t +puglDecodeUTF8(const uint8_t* buf) +{ +#define FAIL_IF(cond) do { if (cond) return 0xFFFD; } while (0) + + // http://en.wikipedia.org/wiki/UTF-8 + + if (buf[0] < 0x80) { + return buf[0]; + } else if (buf[0] < 0xC2) { + return 0xFFFD; + } else if (buf[0] < 0xE0) { + FAIL_IF((buf[1] & 0xC0u) != 0x80); + return ((uint32_t)buf[0] << 6u) + buf[1] - 0x3080u; + } else if (buf[0] < 0xF0) { + FAIL_IF((buf[1] & 0xC0u) != 0x80); + FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0); + FAIL_IF((buf[2] & 0xC0u) != 0x80); + return ((uint32_t)buf[0] << 12u) + // + ((uint32_t)buf[1] << 6u) + // + ((uint32_t)buf[2] - 0xE2080u); + } else if (buf[0] < 0xF5) { + FAIL_IF((buf[1] & 0xC0u) != 0x80); + FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90); + FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90); + FAIL_IF((buf[2] & 0xC0u) != 0x80u); + FAIL_IF((buf[3] & 0xC0u) != 0x80u); + return (((uint32_t)buf[0] << 18u) + // + ((uint32_t)buf[1] << 12u) + // + ((uint32_t)buf[2] << 6u) + // + ((uint32_t)buf[3] - 0x3C82080u)); + } + return 0xFFFD; +} + +static inline bool +puglMustConfigure(PuglView* view, const PuglEventConfigure* configure) +{ + return memcmp(configure, &view->lastConfigure, sizeof(PuglEventConfigure)); +} + +void +puglDispatchSimpleEvent(PuglView* view, const PuglEventType type) +{ + assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP || + type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE); + + const PuglEvent event = {{type, 0}}; + puglDispatchEvent(view, &event); +} + +void +puglDispatchEventInContext(PuglView* view, const PuglEvent* event) +{ + if (event->type == PUGL_CONFIGURE) { + view->frame.x = event->configure.x; + view->frame.y = event->configure.y; + view->frame.width = event->configure.width; + view->frame.height = event->configure.height; + + if (puglMustConfigure(view, &event->configure)) { + view->eventFunc(view, event); + view->lastConfigure = event->configure; + } + } else { + view->eventFunc(view, event); + } +} + +void +puglDispatchEvent(PuglView* view, const PuglEvent* event) +{ + switch (event->type) { + case PUGL_NOTHING: + break; + case PUGL_CREATE: + case PUGL_DESTROY: + view->backend->enter(view, NULL); + view->eventFunc(view, event); + view->backend->leave(view, NULL); + break; + case PUGL_CONFIGURE: + if (puglMustConfigure(view, &event->configure)) { + view->backend->enter(view, NULL); + puglDispatchEventInContext(view, event); + view->backend->leave(view, NULL); + } + break; + case PUGL_EXPOSE: + view->backend->enter(view, &event->expose); + view->eventFunc(view, event); + view->backend->leave(view, &event->expose); + break; + default: + view->eventFunc(view, event); + } +} + +const void* +puglGetInternalClipboard(const PuglView* const view, + const char** const type, + size_t* const len) +{ + if (len) { + *len = view->clipboard.len; + } + + if (type) { + *type = "text/plain"; + } + + return view->clipboard.data; +} + +PuglStatus +puglSetInternalClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + if (type && strcmp(type, "text/plain")) { + return PUGL_UNSUPPORTED_TYPE; + } + + puglSetBlob(&view->clipboard, data, len); + return PUGL_SUCCESS; +} + diff --git a/include/pugl/detail/implementation.h b/include/pugl/detail/implementation.h new file mode 100644 index 0000000..ff97fef --- /dev/null +++ b/include/pugl/detail/implementation.h @@ -0,0 +1,77 @@ +/* + 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 implementation.h + @brief Shared declarations for implementation. +*/ + +#ifndef PUGL_DETAIL_IMPLEMENTATION_H +#define PUGL_DETAIL_IMPLEMENTATION_H + +#include "pugl/detail/types.h" +#include "pugl/pugl.h" + +#include <stddef.h> +#include <stdint.h> + +PUGL_BEGIN_DECLS + +/// 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` +void puglSetString(char** dest, const char* string); + +/// Allocate and initialise world internals (implemented once per platform) +PuglWorldInternals* +puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags); + +/// Destroy and free world internals (implemented once per platform) +void puglFreeWorldInternals(PuglWorld* world); + +/// Allocate and initialise view internals (implemented once per platform) +PuglInternals* puglInitViewInternals(void); + +/// Destroy and free view internals (implemented once per platform) +void puglFreeViewInternals(PuglView* view); + +/// 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` +void puglDispatchSimpleEvent(PuglView* view, PuglEventType type); + +/// 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 +void puglDispatchEvent(PuglView* view, const PuglEvent* event); + +/// 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 +PuglStatus +puglSetInternalClipboard(PuglView* view, + const char* type, + const void* data, + size_t len); + +PUGL_END_DECLS + +#endif // PUGL_DETAIL_IMPLEMENTATION_H diff --git a/include/pugl/detail/mac.h b/include/pugl/detail/mac.h new file mode 100644 index 0000000..7b64cfe --- /dev/null +++ b/include/pugl/detail/mac.h @@ -0,0 +1,55 @@ +/* + 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 + 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 mac.h + @brief Shared definitions for MacOS implementation. +*/ + +#include "pugl/pugl.h" + +#import <Cocoa/Cocoa.h> + +#include <stdint.h> + +@interface PuglWrapperView : NSView<NSTextInputClient> + +- (void) dispatchExpose:(NSRect)rect; +- (void) setReshaped; + +@end + +@interface PuglWindow : NSWindow + +- (void) setPuglview:(PuglView*)view; + +@end + +struct PuglWorldInternalsImpl { + NSApplication* app; + NSAutoreleasePool* autoreleasePool; +}; + +struct PuglInternalsImpl { + NSApplication* app; + PuglWrapperView* wrapperView; + NSView* drawView; + NSCursor* cursor; + PuglWindow* window; + uint32_t mods; + bool mouseTracked; +}; diff --git a/include/pugl/detail/mac.m b/include/pugl/detail/mac.m new file mode 100644 index 0000000..354546a --- /dev/null +++ b/include/pugl/detail/mac.m @@ -0,0 +1,1459 @@ +/* + 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 + 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 mac.m + @brief MacOS implementation. +*/ + +#define GL_SILENCE_DEPRECATION 1 + +#include "pugl/detail/implementation.h" +#include "pugl/detail/mac.h" +#include "pugl/pugl.h" + +#import <Cocoa/Cocoa.h> + +#include <mach/mach_time.h> + +#include <stdlib.h> + +#ifndef __MAC_10_10 +typedef NSUInteger NSEventModifierFlags; +#endif + +#ifndef __MAC_10_12 +typedef NSUInteger NSWindowStyleMask; +#endif + +static NSRect +rectToScreen(NSScreen* screen, NSRect rect) +{ + 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 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 + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag +{ + (void)flag; + + NSWindow* result = [super initWithContentRect:contentRect + styleMask:aStyle + backing:bufferingType + defer:NO]; + + [result setAcceptsMouseMovedEvents:YES]; + return (PuglWindow*)result; +} + +- (void)setPuglview:(PuglView*)view +{ + puglview = view; + + [self + setContentSize:sizePoints(view, view->frame.width, view->frame.height)]; +} + +- (BOOL) canBecomeKeyWindow +{ + return YES; +} + +- (BOOL) canBecomeMainWindow +{ + return YES; +} + +- (void) setIsVisible:(BOOL)flag +{ + if (flag && !puglview->visible) { + const PuglEventConfigure ev = { + PUGL_CONFIGURE, + 0, + puglview->frame.x, + puglview->frame.y, + puglview->frame.width, + puglview->frame.height, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + puglDispatchSimpleEvent(puglview, PUGL_MAP); + } else if (!flag && puglview->visible) { + puglDispatchSimpleEvent(puglview, PUGL_UNMAP); + } + + puglview->visible = flag; + + [super setIsVisible:flag]; +} + +@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 = { + PUGL_CONFIGURE, + 0, + puglview->frame.x, + puglview->frame.y, + puglview->frame.width, + puglview->frame.height, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + reshaped = false; + } + + if (![[puglview->impl->drawView window] isVisible]) { + return; + } + + const PuglEventExpose ev = { + PUGL_EXPOSE, + 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; +} + +- (BOOL) acceptsFirstResponder +{ + return YES; +} + +- (void) setReshaped +{ + reshaped = true; +} + +static uint32_t +getModifiers(const NSEvent* const ev) +{ + const NSEventModifierFlags modifierFlags = [ev modifierFlags]; + + return (((modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0) | + ((modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0) | + ((modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0) | + ((modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0)); +} + +static PuglKey +keySymToSpecial(const NSEvent* const ev) +{ + NSString* chars = [ev charactersIgnoringModifiers]; + if ([chars length] == 1) { + switch ([chars characterAtIndex:0]) { + case NSF1FunctionKey: return PUGL_KEY_F1; + case NSF2FunctionKey: return PUGL_KEY_F2; + case NSF3FunctionKey: return PUGL_KEY_F3; + case NSF4FunctionKey: return PUGL_KEY_F4; + case NSF5FunctionKey: return PUGL_KEY_F5; + case NSF6FunctionKey: return PUGL_KEY_F6; + case NSF7FunctionKey: return PUGL_KEY_F7; + case NSF8FunctionKey: return PUGL_KEY_F8; + case NSF9FunctionKey: return PUGL_KEY_F9; + case NSF10FunctionKey: return PUGL_KEY_F10; + case NSF11FunctionKey: return PUGL_KEY_F11; + case NSF12FunctionKey: return PUGL_KEY_F12; + case NSDeleteCharacter: return PUGL_KEY_BACKSPACE; + case NSDeleteFunctionKey: return PUGL_KEY_DELETE; + case NSLeftArrowFunctionKey: return PUGL_KEY_LEFT; + case NSUpArrowFunctionKey: return PUGL_KEY_UP; + case NSRightArrowFunctionKey: return PUGL_KEY_RIGHT; + case NSDownArrowFunctionKey: return PUGL_KEY_DOWN; + case NSPageUpFunctionKey: return PUGL_KEY_PAGE_UP; + case NSPageDownFunctionKey: return PUGL_KEY_PAGE_DOWN; + case NSHomeFunctionKey: return PUGL_KEY_HOME; + case NSEndFunctionKey: return PUGL_KEY_END; + case NSInsertFunctionKey: return PUGL_KEY_INSERT; + case NSMenuFunctionKey: return PUGL_KEY_MENU; + case NSScrollLockFunctionKey: return PUGL_KEY_SCROLL_LOCK; + case NSClearLineFunctionKey: return PUGL_KEY_NUM_LOCK; + case NSPrintScreenFunctionKey: return PUGL_KEY_PRINT_SCREEN; + case NSPauseFunctionKey: return PUGL_KEY_PAUSE; + } + // SHIFT, CTRL, ALT, and SUPER are handled in [flagsChanged] + } + return (PuglKey)0; +} + +- (void) updateTrackingAreas +{ + if (trackingArea != nil) { + [self removeTrackingArea:trackingArea]; + [trackingArea release]; + } + + const int opts = (NSTrackingMouseEnteredAndExited | + NSTrackingMouseMoved | + NSTrackingActiveAlways); + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:opts + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; +} + +- (NSPoint) eventLocation:(NSEvent*)event +{ + return nsPointFromPoints(puglview, + [self convertPoint:[event locationInWindow] + fromView:nil]); +} + +static void +handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type) +{ + const NSPoint wloc = [view eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventCrossing ev = { + type, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + PUGL_CROSSING_NORMAL, + }; + + puglDispatchEvent(view->puglview, (const PuglEvent*)&ev); +} + +- (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) cursorUpdate:(NSEvent*)event +{ + (void)event; + [puglview->impl->cursor set]; +} + +- (void) mouseMoved:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventMotion ev = { + PUGL_MOTION, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) mouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void) rightMouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void) otherMouseDragged:(NSEvent*)event +{ + [self mouseMoved: event]; +} + +- (void) mouseDown:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventButton ev = { + PUGL_BUTTON_PRESS, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + (uint32_t)[event buttonNumber] + 1, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) mouseUp:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglEventButton ev = { + PUGL_BUTTON_RELEASE, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + (uint32_t)[event buttonNumber] + 1, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) rightMouseDown:(NSEvent*)event +{ + [self mouseDown: event]; +} + +- (void) rightMouseUp:(NSEvent*)event +{ + [self mouseUp: event]; +} + +- (void) otherMouseDown:(NSEvent*)event +{ + [self mouseDown: event]; +} + +- (void) otherMouseUp:(NSEvent*)event +{ + [self mouseUp: event]; +} + +- (void) scrollWheel:(NSEvent*)event +{ + 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); +} + +- (void) keyDown:(NSEvent*)event +{ + if (puglview->hints[PUGL_IGNORE_KEY_REPEAT] && [event isARepeat]) { + return; + } + + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglKey spec = keySymToSpecial(event); + const NSString* chars = [event charactersIgnoringModifiers]; + const char* str = [[chars lowercaseString] UTF8String]; + const uint32_t code = (spec ? spec : puglDecodeUTF8((const uint8_t*)str)); + + const PuglEventKey ev = { + PUGL_KEY_PRESS, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + (code != 0xFFFD) ? code : 0, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + + if (!spec) { + [self interpretKeyEvents:@[event]]; + } +} + +- (void)keyUp:(NSEvent*)event +{ + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + const PuglKey spec = keySymToSpecial(event); + const NSString* chars = [event charactersIgnoringModifiers]; + const char* str = [[chars lowercaseString] UTF8String]; + const uint32_t code = (spec ? spec : puglDecodeUTF8((const uint8_t*)str)); + + const PuglEventKey ev = { + PUGL_KEY_RELEASE, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + (code != 0xFFFD) ? code : 0, + }; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (BOOL) hasMarkedText +{ + return [markedText length] > 0; +} + +- (NSRange) markedRange +{ + return (([markedText length] > 0) + ? NSMakeRange(0, [markedText length] - 1) + : NSMakeRange(NSNotFound, 0)); +} + +- (NSRange) selectedRange +{ + return NSMakeRange(NSNotFound, 0); +} + +- (void)setMarkedText:(id)string + selectedRange:(NSRange)selected + replacementRange:(NSRange)replacement +{ + (void)selected; + (void)replacement; + [markedText release]; + markedText = ( + [(NSObject*)string isKindOfClass:[NSAttributedString class]] + ? [[NSMutableAttributedString alloc] initWithAttributedString:string] + : [[NSMutableAttributedString alloc] initWithString:string]); +} + +- (void) unmarkText +{ + [[markedText mutableString] setString:@""]; +} + +- (NSArray*) validAttributesForMarkedText +{ + return @[]; +} + +- (NSAttributedString*) + attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actual +{ + (void)range; + (void)actual; + return nil; +} + +- (NSUInteger) characterIndexForPoint:(NSPoint)point +{ + (void)point; + return 0; +} + +- (NSRect) firstRectForCharacterRange:(NSRange)range + actualRange:(NSRangePointer)actual +{ + (void)range; + (void)actual; + + const NSRect frame = [self bounds]; + return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); +} + +- (void) doCommandBySelector:(SEL)selector +{ + (void)selector; +} + +- (void) insertText:(id)string + replacementRange:(NSRange)replacement +{ + (void)replacement; + + NSEvent* const event = [NSApp currentEvent]; + NSString* const characters = + ([(NSObject*)string isKindOfClass:[NSAttributedString class]] + ? [(NSAttributedString*)string string] + : (NSString*)string); + + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + for (size_t i = 0; i < [characters length]; ++i) { + const uint32_t code = [characters characterAtIndex:i]; + char utf8[8] = {0}; + NSUInteger len = 0; + + [characters getBytes:utf8 + maxLength:sizeof(utf8) + usedLength:&len + encoding:NSUTF8StringEncoding + options:0 + range:NSMakeRange(i, i + 1) + remainingRange:nil]; + + PuglEventText ev = { + PUGL_TEXT, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + getModifiers(event), + [event keyCode], + code, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + }; + + memcpy(ev.string, utf8, len); + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + } +} + +- (void) flagsChanged:(NSEvent*)event +{ + const uint32_t mods = getModifiers(event); + PuglEventType type = PUGL_NOTHING; + PuglKey special = (PuglKey)0; + + if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) { + type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_SHIFT; + } else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) { + type = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_CTRL; + } else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) { + type = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_ALT; + } else if ((mods & PUGL_MOD_SUPER) != (puglview->impl->mods & PUGL_MOD_SUPER)) { + type = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + special = PUGL_KEY_SUPER; + } + + if (special != 0) { + const NSPoint wloc = [self eventLocation:event]; + const NSPoint rloc = [NSEvent mouseLocation]; + PuglEventKey ev = { + type, + 0, + [event timestamp], + wloc.x, + wloc.y, + rloc.x, + [[NSScreen mainScreen] frame].size.height - rloc.y, + mods, + [event keyCode], + special + }; + puglDispatchEvent(puglview, (const PuglEvent*)&ev); + } + + puglview->impl->mods = mods; +} + +- (BOOL) preservesContentInLiveResize +{ + return NO; +} + +- (void) viewWillStartLiveResize +{ + timer = [NSTimer timerWithTimeInterval:(1 / 60.0) + target:self + selector:@selector(resizeTick) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:timer + forMode:NSRunLoopCommonModes]; + + [super viewWillStartLiveResize]; +} + +- (void) viewWillDraw +{ + puglDispatchSimpleEvent(puglview, PUGL_UPDATE); + [super viewWillDraw]; +} + +- (void) resizeTick +{ + puglPostRedisplay(puglview); +} + +- (void) timerTick:(NSTimer*)userTimer +{ + const NSNumber* userInfo = userTimer.userInfo; + const PuglEventTimer ev = {PUGL_TIMER, 0, userInfo.unsignedLongValue}; + + puglDispatchEvent(puglview, (const PuglEvent*)&ev); +} + +- (void) viewDidEndLiveResize +{ + [super viewDidEndLiveResize]; + [timer invalidate]; + timer = NULL; +} + +@end + +@interface PuglWindowDelegate : NSObject<NSWindowDelegate> + +- (instancetype) initWithPuglWindow:(PuglWindow*)window; + +@end + +@implementation PuglWindowDelegate +{ + PuglWindow* window; +} + +- (instancetype) initWithPuglWindow:(PuglWindow*)puglWindow +{ + if ((self = [super init])) { + window = puglWindow; + } + + return self; +} + +- (BOOL) windowShouldClose:(id)sender +{ + (void)sender; + + puglDispatchSimpleEvent(window->puglview, PUGL_CLOSE); + return YES; +} + +- (void) windowDidMove:(NSNotification*)notification +{ + (void)notification; + + updateViewRect(window->puglview); +} + +- (void) windowDidBecomeKey:(NSNotification*)notification +{ + (void)notification; + + PuglEvent ev = {{PUGL_FOCUS_IN, 0}}; + ev.focus.mode = PUGL_CROSSING_NORMAL; + puglDispatchEvent(window->puglview, &ev); +} + +- (void) windowDidResignKey:(NSNotification*)notification +{ + (void)notification; + + PuglEvent ev = {{PUGL_FOCUS_OUT, 0}}; + ev.focus.mode = PUGL_CROSSING_NORMAL; + puglDispatchEvent(window->puglview, &ev); +} + +@end + +PuglWorldInternals* +puglInitWorldInternals(PuglWorldType type, PuglWorldFlags PUGL_UNUSED(flags)) +{ + PuglWorldInternals* impl = (PuglWorldInternals*)calloc( + 1, sizeof(PuglWorldInternals)); + + impl->app = [NSApplication sharedApplication]; + + if (type == PUGL_PROGRAM) { + impl->autoreleasePool = [NSAutoreleasePool new]; + } + + return impl; +} + +void +puglFreeWorldInternals(PuglWorld* world) +{ + if (world->impl->autoreleasePool) { + [world->impl->autoreleasePool drain]; + } + + free(world->impl); +} + +void* +puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world)) +{ + return NULL; +} + +PuglInternals* +puglInitViewInternals(void) +{ + PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); + + impl->cursor = [NSCursor arrowCursor]; + + return impl; +} + +static NSLayoutConstraint* +puglConstraint(id item, NSLayoutAttribute attribute, float constant) +{ + return [NSLayoutConstraint + constraintWithItem: item + attribute: attribute + relatedBy: NSLayoutRelationGreaterThanOrEqual + toItem: nil + attribute: NSLayoutAttributeNotAnAttribute + multiplier: 1.0 + constant: (CGFloat)constant]; +} + +PuglStatus +puglRealize(PuglView* view) +{ + PuglInternals* impl = view->impl; + if (impl->wrapperView) { + return PUGL_FAILURE; + } + + const NSScreen* const screen = [NSScreen mainScreen]; + const double scaleFactor = [screen backingScaleFactor]; + + // Getting depth from the display mode seems tedious, just set usual values + if (view->hints[PUGL_RED_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_RED_BITS] = 8; + } + if (view->hints[PUGL_BLUE_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_BLUE_BITS] = 8; + } + if (view->hints[PUGL_GREEN_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_GREEN_BITS] = 8; + } + if (view->hints[PUGL_ALPHA_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_ALPHA_BITS] = 8; + } + + CGDirectDisplayID displayId = CGMainDisplayID(); + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId); + + // Try to get refresh rate from mode (usually fails) + view->hints[PUGL_REFRESH_RATE] = (int)CGDisplayModeGetRefreshRate(mode); + + CGDisplayModeRelease(mode); + if (view->hints[PUGL_REFRESH_RATE] == 0) { + // Get refresh rate from a display link + // TODO: Keep and actually use the display link for something? + CVDisplayLinkRef link; + CVDisplayLinkCreateWithCGDisplay(displayId, &link); + + const CVTime p = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); + const double r = p.timeScale / (double)p.timeValue; + view->hints[PUGL_REFRESH_RATE] = (int)lrint(r); + + CVDisplayLinkRelease(link); + } + + 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]; + impl->wrapperView->puglview = view; + impl->wrapperView->userTimers = [[NSMutableDictionary alloc] init]; + impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init]; + [impl->wrapperView setAutoresizesSubviews:YES]; + [impl->wrapperView initWithFrame:framePt]; + [impl->wrapperView addConstraint: + puglConstraint(impl->wrapperView, NSLayoutAttributeWidth, view->minWidth)]; + [impl->wrapperView addConstraint: + puglConstraint(impl->wrapperView, NSLayoutAttributeHeight, view->minHeight)]; + + // Create draw view to be rendered to + PuglStatus st = PUGL_SUCCESS; + if ((st = view->backend->configure(view)) || + (st = view->backend->create(view))) { + return st; + } + + // Add draw view to wrapper view + [impl->wrapperView addSubview:impl->drawView]; + [impl->wrapperView setHidden:NO]; + [impl->drawView setHidden:NO]; + + if (view->parent) { + NSView* pview = (NSView*)view->parent; + [pview addSubview:impl->wrapperView]; + [impl->drawView setHidden:NO]; + [[impl->drawView window] makeFirstResponder:impl->wrapperView]; + } else { + unsigned style = (NSClosableWindowMask | + NSTitledWindowMask | + NSMiniaturizableWindowMask ); + if (view->hints[PUGL_RESIZABLE]) { + style |= NSResizableWindowMask; + } + + PuglWindow* window = [[[PuglWindow alloc] + initWithContentRect:rectToScreen([NSScreen mainScreen], framePt) + styleMask:style + backing:NSBackingStoreBuffered + defer:NO + ] retain]; + [window setPuglview:view]; + + if (view->title) { + NSString* titleString = [[NSString alloc] + initWithBytes:view->title + length:strlen(view->title) + encoding:NSUTF8StringEncoding]; + + [window setTitle:titleString]; + } + + if (view->minWidth || view->minHeight) { + [window setContentMinSize:sizePoints(view, + view->minWidth, + view->minHeight)]; + } + impl->window = window; + + ((NSWindow*)window).delegate = [[PuglWindowDelegate alloc] + initWithPuglWindow:window]; + + if (view->minAspectX && view->minAspectY) { + [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]; + [window makeKeyAndOrderFront:window]; + [impl->window setIsVisible:NO]; + } + + [impl->wrapperView updateTrackingAreas]; + + puglDispatchSimpleEvent(view, PUGL_CREATE); + + return PUGL_SUCCESS; +} + +PuglStatus +puglShowWindow(PuglView* view) +{ + if (![view->impl->window isVisible]) { + [view->impl->window setIsVisible:YES]; + [view->impl->drawView setNeedsDisplay: YES]; + updateViewRect(view); + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglHideWindow(PuglView* view) +{ + [view->impl->window setIsVisible:NO]; + return PUGL_SUCCESS; +} + +void +puglFreeViewInternals(PuglView* view) +{ + if (view) { + if (view->backend) { + view->backend->destroy(view); + } + + if (view->impl) { + [view->impl->wrapperView removeFromSuperview]; + view->impl->wrapperView->puglview = NULL; + if (view->impl->window) { + [view->impl->window close]; + } + [view->impl->wrapperView release]; + if (view->impl->window) { + [view->impl->window release]; + } + free(view->impl); + } + } +} + +PuglStatus +puglGrabFocus(PuglView* view) +{ + NSWindow* window = [view->impl->wrapperView window]; + + [window makeKeyWindow]; + [window makeFirstResponder:view->impl->wrapperView]; + return PUGL_SUCCESS; +} + +bool +puglHasFocus(const PuglView* view) +{ + PuglInternals* const impl = view->impl; + + return ([[impl->wrapperView window] isKeyWindow] && + [[impl->wrapperView window] firstResponder] == impl->wrapperView); +} + +PuglStatus +puglRequestAttention(PuglView* view) +{ + if (![view->impl->window isKeyWindow]) { + [view->world->impl->app requestUserAttention:NSInformationalRequest]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ + puglStopTimer(view, id); + + NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id]; + + NSTimer* timer = [NSTimer timerWithTimeInterval:timeout + target:view->impl->wrapperView + selector:@selector(timerTick:) + userInfo:idNumber + repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; + + view->impl->wrapperView->userTimers[idNumber] = timer; + + return PUGL_SUCCESS; +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ + NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id]; + NSTimer* timer = view->impl->wrapperView->userTimers[idNumber]; + + if (timer) { + [view->impl->wrapperView->userTimers removeObjectForKey:timer]; + [timer invalidate]; + return PUGL_SUCCESS; + } + + return PUGL_UNKNOWN_ERROR; +} + +PuglStatus puglSendEvent(PuglView* view, const PuglEvent* event) +{ + if (event->type == PUGL_CLIENT) { + PuglWrapperView* wrapper = view->impl->wrapperView; + const NSWindow* window = [wrapper window]; + const NSRect rect = [wrapper frame]; + const NSPoint center = {NSMidX(rect), NSMidY(rect)}; + + NSEvent* nsevent = [NSEvent + otherEventWithType:NSApplicationDefined + location:center + modifierFlags:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] + windowNumber:window.windowNumber + context:nil + subtype:PUGL_CLIENT + data1:(NSInteger)event->client.data1 + data2:(NSInteger)event->client.data2]; + + [view->world->impl->app postEvent:nsevent atStart:false]; + return PUGL_SUCCESS; + } + + return PUGL_UNSUPPORTED_TYPE; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglWaitForEvent(PuglView* view) +{ + return puglPollEvents(view->world, -1.0); +} +#endif + +static void +dispatchClientEvent(PuglWorld* world, NSEvent* ev) +{ + NSWindow* win = [ev window]; + NSPoint loc = [ev locationInWindow]; + for (size_t i = 0; i < world->numViews; ++i) { + PuglView* view = world->views[i]; + PuglWrapperView* wrapper = view->impl->wrapperView; + if ([wrapper window] == win && NSPointInRect(loc, [wrapper frame])) { + const PuglEventClient event = {PUGL_CLIENT, + 0, + (uintptr_t)[ev data1], + (uintptr_t)[ev data2]}; + + puglDispatchEvent(view, (const PuglEvent*)&event); + } + } +} + +PuglStatus +puglUpdate(PuglWorld* world, const double timeout) +{ + NSDate* date = ((timeout < 0) + ? [NSDate distantFuture] + : [NSDate dateWithTimeIntervalSinceNow:timeout]); + + for (NSEvent* ev = NULL; + (ev = [world->impl->app nextEventMatchingMask:NSAnyEventMask + untilDate:date + inMode:NSDefaultRunLoopMode + dequeue:YES]);) { + + if ([ev type] == NSApplicationDefined && [ev subtype] == PUGL_CLIENT) { + dispatchClientEvent(world, ev); + } + + [world->impl->app sendEvent: ev]; + + if (timeout < 0) { + // Now that we've waited and got an event, set the date to now to + // avoid looping forever + date = [NSDate date]; + } + } + + for (size_t i = 0; i < world->numViews; ++i) { + PuglView* const view = world->views[i]; + + if ([[view->impl->drawView window] isVisible]) { + puglDispatchSimpleEvent(view, PUGL_UPDATE); + } + + [view->impl->drawView displayIfNeeded]; + } + + return PUGL_SUCCESS; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglProcessEvents(PuglView* view) +{ + return puglDispatchEvents(view->world); +} +#endif + +double +puglGetTime(const PuglWorld* world) +{ + return (mach_absolute_time() / 1e9) - world->startTime; +} + +PuglStatus +puglPostRedisplay(PuglView* view) +{ + [view->impl->drawView setNeedsDisplay: YES]; + return PUGL_SUCCESS; +} + +PuglStatus +puglPostRedisplayRect(PuglView* view, const PuglRect rect) +{ + const NSRect rectPx = rectToNsRect(rect); + + [view->impl->drawView setNeedsDisplayInRect:nsRectToPoints(view, rectPx)]; + + return PUGL_SUCCESS; +} + +PuglNativeView +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeView)view->impl->wrapperView; +} + +PuglStatus +puglSetWindowTitle(PuglView* view, const char* title) +{ + puglSetString(&view->title, title); + + if (view->impl->window) { + NSString* titleString = [[NSString alloc] + initWithBytes:title + length:strlen(title) + encoding:NSUTF8StringEncoding]; + + [view->impl->window setTitle:titleString]; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetFrame(PuglView* view, const PuglRect frame) +{ + PuglInternals* const impl = view->impl; + + // Update view frame to exactly the requested frame in Pugl coordinates + view->frame = frame; + + const NSRect framePx = rectToNsRect(frame); + const NSRect framePt = nsRectToPoints(view, framePx); + if (impl->window) { + // Resize window to fit new content rect + const NSRect screenPt = rectToScreen(viewScreen(view), framePt); + const NSRect winFrame = [impl->window frameRectForContentRect:screenPt]; + + [impl->window setFrame:winFrame display:NO]; + } + + // Resize views + 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: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; +} + +PuglStatus +puglSetAspectRatio(PuglView* const view, + const int minX, + const int minY, + const int maxX, + const int maxY) +{ + view->minAspectX = minX; + view->minAspectY = minY; + view->maxAspectX = maxX; + view->maxAspectY = maxY; + + if (view->impl->window && view->minAspectX && view->minAspectY) { + [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, + size_t* const len) +{ + NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard]; + + if ([[pasteboard types] containsObject:NSStringPboardType]) { + const NSString* str = [pasteboard stringForType:NSStringPboardType]; + const char* utf8 = [str UTF8String]; + + puglSetBlob(&view->clipboard, utf8, strlen(utf8) + 1); + } + + 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, + const void* const data, + const size_t len) +{ + NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard]; + const char* const str = (const char*)data; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } + + NSString* nsString = [NSString stringWithUTF8String:str]; + if (nsString) { + [pasteboard + declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] + owner:nil]; + + [pasteboard setString:nsString forType:NSStringPboardType]; + + return PUGL_SUCCESS; + } + + return PUGL_UNKNOWN_ERROR; +} diff --git a/include/pugl/detail/mac_cairo.m b/include/pugl/detail/mac_cairo.m new file mode 100644 index 0000000..18209d9 --- /dev/null +++ b/include/pugl/detail/mac_cairo.m @@ -0,0 +1,164 @@ +/* + 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 + 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 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 <cairo-quartz.h> + +#import <Cocoa/Cocoa.h> + +#include <assert.h> + +@interface PuglCairoView : NSView +@end + +@implementation PuglCairoView +{ +@public + PuglView* puglview; + cairo_surface_t* surface; + cairo_t* cr; +} + +- (id) initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + + return self; +} + +- (void) resizeWithOldSuperviewSize:(NSSize)oldSize +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [super resizeWithOldSuperviewSize:oldSize]; + [wrapper setReshaped]; +} + +- (void) drawRect:(NSRect)rect +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + [wrapper dispatchExpose:rect]; +} + +@end + +static PuglStatus +puglMacCairoCreate(PuglView* view) +{ + PuglInternals* impl = view->impl; + PuglCairoView* drawView = [PuglCairoView alloc]; + + drawView->puglview = view; + [drawView initWithFrame:[impl->wrapperView bounds]]; + if (view->hints[PUGL_RESIZABLE]) { + [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + } else { + [drawView setAutoresizingMask:NSViewNotSizable]; + } + + impl->drawView = drawView; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacCairoDestroy(PuglView* view) +{ + PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; + + [drawView removeFromSuperview]; + [drawView release]; + + view->impl->drawView = nil; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacCairoEnter(PuglView* view, const PuglEventExpose* expose) +{ + PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; + if (!expose) { + return PUGL_SUCCESS; + } + + 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, (unsigned)sizePx.width, (unsigned)sizePx.height); + + drawView->cr = cairo_create(drawView->surface); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacCairoLeave(PuglView* view, const PuglEventExpose* expose) +{ + PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView; + if (!expose) { + return PUGL_SUCCESS; + } + + assert(drawView->surface); + assert(drawView->cr); + + CGContextRef context = cairo_quartz_surface_get_cg_context(drawView->surface); + + cairo_destroy(drawView->cr); + cairo_surface_destroy(drawView->surface); + drawView->cr = NULL; + drawView->surface = NULL; + + CGContextFlush(context); + + return PUGL_SUCCESS; +} + +static void* +puglMacCairoGetContext(PuglView* view) +{ + return ((PuglCairoView*)view->impl->drawView)->cr; +} + +const PuglBackend* puglCairoBackend(void) +{ + static const PuglBackend backend = {puglStubConfigure, + puglMacCairoCreate, + puglMacCairoDestroy, + puglMacCairoEnter, + puglMacCairoLeave, + puglMacCairoGetContext}; + + return &backend; +} diff --git a/include/pugl/detail/mac_gl.m b/include/pugl/detail/mac_gl.m new file mode 100644 index 0000000..4d4f324 --- /dev/null +++ b/include/pugl/detail/mac_gl.m @@ -0,0 +1,201 @@ +/* + 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 + 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 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" + +#ifndef __MAC_10_10 +# define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core +#endif + +@interface PuglOpenGLView : NSOpenGLView +@end + +@implementation PuglOpenGLView +{ +@public + PuglView* puglview; +} + +- (id) initWithFrame:(NSRect)frame +{ + const bool compat = puglview->hints[PUGL_USE_COMPAT_PROFILE]; + const unsigned samples = (unsigned)puglview->hints[PUGL_SAMPLES]; + const int major = puglview->hints[PUGL_CONTEXT_VERSION_MAJOR]; + const unsigned profile = ((compat || major < 3) + ? NSOpenGLProfileVersionLegacy + : (major >= 4 + ? NSOpenGLProfileVersion4_1Core + : NSOpenGLProfileVersion3_2Core)); + + // Set attributes to default if they are unset + // (There is no GLX_DONT_CARE equivalent on MacOS) + if (puglview->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) { + puglview->hints[PUGL_DEPTH_BITS] = 0; + } + if (puglview->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) { + puglview->hints[PUGL_STENCIL_BITS] = 0; + } + if (puglview->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) { + puglview->hints[PUGL_SAMPLES] = 1; + } + if (puglview->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) { + puglview->hints[PUGL_DOUBLE_BUFFER] = 1; + } + if (puglview->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) { + puglview->hints[PUGL_SWAP_INTERVAL] = 1; + } + + const unsigned colorSize = (unsigned)(puglview->hints[PUGL_RED_BITS] + + puglview->hints[PUGL_BLUE_BITS] + + puglview->hints[PUGL_GREEN_BITS] + + puglview->hints[PUGL_ALPHA_BITS]); + + NSOpenGLPixelFormatAttribute pixelAttribs[17] = { + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAAccelerated, + NSOpenGLPFAOpenGLProfile, profile, + NSOpenGLPFAColorSize, colorSize, + NSOpenGLPFADepthSize, (unsigned)puglview->hints[PUGL_DEPTH_BITS], + NSOpenGLPFAStencilSize, (unsigned)puglview->hints[PUGL_STENCIL_BITS], + NSOpenGLPFAMultisample, samples ? 1 : 0, + NSOpenGLPFASampleBuffers, samples ? 1 : 0, + NSOpenGLPFASamples, samples, + 0}; + + NSOpenGLPixelFormat* pixelFormat = [ + [NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs]; + + if (pixelFormat) { + self = [super initWithFrame:frame pixelFormat:pixelFormat]; + [pixelFormat release]; + } else { + self = [super initWithFrame:frame]; + } + + [self setWantsBestResolutionOpenGLSurface:YES]; + + if (self) { + [[self openGLContext] makeCurrentContext]; + [self reshape]; + } + return self; +} + +- (void) reshape +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [super reshape]; + [wrapper setReshaped]; +} + +- (void) drawRect:(NSRect)rect +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + [wrapper dispatchExpose:rect]; +} + +@end + +static PuglStatus +puglMacGlCreate(PuglView* view) +{ + PuglInternals* impl = view->impl; + PuglOpenGLView* drawView = [PuglOpenGLView alloc]; + + drawView->puglview = view; + [drawView initWithFrame:[impl->wrapperView bounds]]; + if (view->hints[PUGL_RESIZABLE]) { + [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + } else { + [drawView setAutoresizingMask:NSViewNotSizable]; + } + + impl->drawView = drawView; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacGlDestroy(PuglView* view) +{ + PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; + + [drawView removeFromSuperview]; + [drawView release]; + + view->impl->drawView = nil; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacGlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose)) +{ + PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; + + [[drawView openGLContext] makeCurrentContext]; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacGlLeave(PuglView* view, const PuglEventExpose* expose) +{ + PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView; + + if (expose) { + [[drawView openGLContext] flushBuffer]; + } + + [NSOpenGLContext clearCurrentContext]; + + return PUGL_SUCCESS; +} + +PuglGlFunc +puglGetProcAddress(const char *name) +{ + CFBundleRef framework = + CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + + CFStringRef symbol = CFStringCreateWithCString( + kCFAllocatorDefault, name, kCFStringEncodingASCII); + + PuglGlFunc func = (PuglGlFunc)CFBundleGetFunctionPointerForName( + framework, symbol); + + CFRelease(symbol); + + return func; +} + +const PuglBackend* puglGlBackend(void) +{ + static const PuglBackend backend = {puglStubConfigure, + puglMacGlCreate, + puglMacGlDestroy, + puglMacGlEnter, + puglMacGlLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/include/pugl/detail/mac_stub.m b/include/pugl/detail/mac_stub.m new file mode 100644 index 0000000..8271735 --- /dev/null +++ b/include/pugl/detail/mac_stub.m @@ -0,0 +1,96 @@ +/* + 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 + 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 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; +} + +- (void) resizeWithOldSuperviewSize:(NSSize)oldSize +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [super resizeWithOldSuperviewSize:oldSize]; + [wrapper setReshaped]; +} + +- (void) drawRect:(NSRect)rect +{ + PuglWrapperView* wrapper = (PuglWrapperView*)[self superview]; + + [wrapper dispatchExpose:rect]; +} + +@end + +static PuglStatus +puglMacStubCreate(PuglView* view) +{ + PuglInternals* impl = view->impl; + PuglStubView* drawView = [PuglStubView alloc]; + + drawView->puglview = view; + [drawView initWithFrame:NSMakeRect(0, 0, view->frame.width, view->frame.height)]; + if (view->hints[PUGL_RESIZABLE]) { + [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + } else { + [drawView setAutoresizingMask:NSViewNotSizable]; + } + + impl->drawView = drawView; + return PUGL_SUCCESS; +} + +static PuglStatus +puglMacStubDestroy(PuglView* view) +{ + PuglStubView* const drawView = (PuglStubView*)view->impl->drawView; + + [drawView removeFromSuperview]; + [drawView release]; + + view->impl->drawView = nil; + return PUGL_SUCCESS; +} + +const PuglBackend* +puglStubBackend(void) +{ + static const PuglBackend backend = {puglStubConfigure, + puglMacStubCreate, + puglMacStubDestroy, + puglStubEnter, + puglStubLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/include/pugl/detail/stub.h b/include/pugl/detail/stub.h new file mode 100644 index 0000000..acd3181 --- /dev/null +++ b/include/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/include/pugl/detail/types.h b/include/pugl/detail/types.h new file mode 100644 index 0000000..edd2bd0 --- /dev/null +++ b/include/pugl/detail/types.h @@ -0,0 +1,118 @@ +/* + 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 types.h + @brief Shared internal type definitions. +*/ + +#ifndef PUGL_DETAIL_TYPES_H +#define PUGL_DETAIL_TYPES_H + +#include "pugl/pugl.h" + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +// Unused parameter macro to suppresses warnings and make it impossible to use +#if defined(__cplusplus) +# define PUGL_UNUSED(name) +#elif defined(__GNUC__) +# define PUGL_UNUSED(name) name##_unused __attribute__((__unused__)) +#else +# define PUGL_UNUSED(name) name +#endif + +/// Platform-specific world internals +typedef struct PuglWorldInternalsImpl PuglWorldInternals; + +/// Platform-specific view internals +typedef struct PuglInternalsImpl PuglInternals; + +/// View hints +typedef int PuglHints[PUGL_NUM_VIEW_HINTS]; + +/// Blob of arbitrary data +typedef struct { + void* data; ///< Dynamically allocated data + size_t len; ///< Length of data in bytes +} PuglBlob; + +/// Cross-platform view definition +struct PuglViewImpl { + PuglWorld* world; + const PuglBackend* backend; + PuglInternals* impl; + PuglHandle handle; + PuglEventFunc eventFunc; + char* title; + PuglBlob clipboard; + PuglNativeView parent; + uintptr_t transientParent; + PuglRect frame; + PuglEventConfigure lastConfigure; + PuglHints hints; + int defaultWidth; + int defaultHeight; + int minWidth; + int minHeight; + int maxWidth; + int maxHeight; + int minAspectX; + int minAspectY; + int maxAspectX; + int maxAspectY; + bool visible; +}; + +/// Cross-platform world definition +struct PuglWorldImpl { + PuglWorldInternals* impl; + PuglWorldHandle handle; + PuglLogFunc logFunc; + char* className; + double startTime; + size_t numViews; + PuglView** views; + PuglLogLevel logLevel; +}; + +/// Opaque surface used by graphics backend +typedef void PuglSurface; + +/// Graphics backend interface +struct PuglBackendImpl { + /// Get visual information from display and setup view as necessary + PuglStatus (*configure)(PuglView*); + + /// Create surface and drawing context + PuglStatus (*create)(PuglView*); + + /// Destroy surface and drawing context + PuglStatus (*destroy)(PuglView*); + + /// 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 + PuglStatus (*leave)(PuglView*, const PuglEventExpose*); + + /// Return the puglGetContext() handle for the application, if any + void* (*getContext)(PuglView*); +}; + +#endif // PUGL_DETAIL_TYPES_H diff --git a/include/pugl/detail/win.c b/include/pugl/detail/win.c new file mode 100644 index 0000000..078b0a0 --- /dev/null +++ b/include/pugl/detail/win.c @@ -0,0 +1,1152 @@ +/* + 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 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" + +#include <windows.h> +#include <windowsx.h> + +#include <math.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <wctype.h> + +#ifndef WM_MOUSEWHEEL +# define WM_MOUSEWHEEL 0x020A +#endif +#ifndef WM_MOUSEHWHEEL +# define WM_MOUSEHWHEEL 0x020E +#endif +#ifndef WHEEL_DELTA +# define WHEEL_DELTA 120 +#endif +#ifndef GWLP_USERDATA +# define GWLP_USERDATA (-21) +#endif + +#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) +#define PUGL_LOCAL_MARK_MSG (WM_USER + 51) +#define PUGL_LOCAL_CLIENT_MSG (WM_USER + 52) +#define PUGL_RESIZE_TIMER_ID 9461 +#define PUGL_USER_TIMER_MIN 9470 + +typedef BOOL (WINAPI *PFN_SetProcessDPIAware)(void); + +LRESULT CALLBACK +wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + +static wchar_t* +puglUtf8ToWideChar(const char* const utf8) +{ + const int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); + if (len > 0) { + wchar_t* result = (wchar_t*)calloc((size_t)len, sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, utf8, -1, result, len); + return result; + } + + return NULL; +} + +static char* +puglWideCharToUtf8(const wchar_t* const wstr, size_t* len) +{ + int n = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + if (n > 0) { + char* result = (char*)calloc((size_t)n, sizeof(char)); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, result, n, NULL, NULL); + *len = (size_t)n; + return result; + } + + return NULL; +} + +static bool +puglRegisterWindowClass(const char* name) +{ + WNDCLASSEX wc = { 0 }; + if (GetClassInfoEx(GetModuleHandle(NULL), name, &wc)) { + return true; // Already registered + } + + wc.cbSize = sizeof(wc); + wc.style = CS_OWNDC; + wc.lpfnWndProc = wndProc; + wc.hInstance = GetModuleHandle(NULL); + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wc.lpszClassName = name; + + return RegisterClassEx(&wc); +} + +PuglWorldInternals* +puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type), + PuglWorldFlags PUGL_UNUSED(flags)) +{ + PuglWorldInternals* impl = (PuglWorldInternals*)calloc( + 1, sizeof(PuglWorldInternals)); + if (!impl) { + return NULL; + } + + HMODULE user32 = LoadLibrary("user32.dll"); + if (user32) { + PFN_SetProcessDPIAware SetProcessDPIAware = + (PFN_SetProcessDPIAware)GetProcAddress( + user32, "SetProcessDPIAware"); + if (SetProcessDPIAware) { + SetProcessDPIAware(); + } + + FreeLibrary(user32); + } + + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + impl->timerFrequency = (double)frequency.QuadPart; + + return impl; +} + +void* +puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world)) +{ + return GetModuleHandle(NULL); +} + +PuglInternals* +puglInitViewInternals(void) +{ + return (PuglInternals*)calloc(1, sizeof(PuglInternals)); +} + +static PuglStatus +puglPollWinEvents(PuglWorld* world, const double timeout) +{ + (void)world; + + if (timeout < 0) { + WaitMessage(); + } else { + MsgWaitForMultipleObjects( + 0, NULL, FALSE, (DWORD)(timeout * 1e3), QS_ALLEVENTS); + } + return PUGL_SUCCESS; +} + +PuglStatus +puglRealize(PuglView* view) +{ + PuglInternals* impl = view->impl; + if (impl->hwnd) { + return PUGL_FAILURE; + } + + // Getting depth from the display mode seems tedious, just set usual values + if (view->hints[PUGL_RED_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_RED_BITS] = 8; + } + if (view->hints[PUGL_BLUE_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_BLUE_BITS] = 8; + } + if (view->hints[PUGL_GREEN_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_GREEN_BITS] = 8; + } + if (view->hints[PUGL_ALPHA_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_ALPHA_BITS] = 8; + } + + // Get refresh rate for resize draw timer + DEVMODEA devMode = {0}; + EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &devMode); + view->hints[PUGL_REFRESH_RATE] = (int)devMode.dmDisplayFrequency; + + // Register window class if necessary + if (!puglRegisterWindowClass(view->world->className)) { + return PUGL_REGISTRATION_FAILED; + } + + if (!view->backend || !view->backend->configure) { + return PUGL_BAD_BACKEND; + } + + PuglStatus st; + if ((st = view->backend->configure(view))) { + return st; + } else if ((st = view->backend->create(view))) { + return st; + } + + if (view->title) { + puglSetWindowTitle(view, view->title); + } + + view->impl->cursor = LoadCursor(NULL, IDC_ARROW); + + puglSetFrame(view, view->frame); + SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view); + + puglDispatchSimpleEvent(view, PUGL_CREATE); + + return PUGL_SUCCESS; +} + +PuglStatus +puglShowWindow(PuglView* view) +{ + PuglInternals* impl = view->impl; + + ShowWindow(impl->hwnd, SW_SHOWNORMAL); + SetFocus(impl->hwnd); + return PUGL_SUCCESS; +} + +PuglStatus +puglHideWindow(PuglView* view) +{ + PuglInternals* impl = view->impl; + + ShowWindow(impl->hwnd, SW_HIDE); + return PUGL_SUCCESS; +} + +void +puglFreeViewInternals(PuglView* view) +{ + if (view) { + if (view->backend) { + view->backend->destroy(view); + } + + ReleaseDC(view->impl->hwnd, view->impl->hdc); + DestroyWindow(view->impl->hwnd); + free(view->impl); + } +} + +void +puglFreeWorldInternals(PuglWorld* world) +{ + UnregisterClass(world->className, NULL); + free(world->impl); +} + +static PuglKey +keySymToSpecial(WPARAM sym) +{ + // clang-format off + switch (sym) { + case VK_F1: return PUGL_KEY_F1; + case VK_F2: return PUGL_KEY_F2; + case VK_F3: return PUGL_KEY_F3; + case VK_F4: return PUGL_KEY_F4; + case VK_F5: return PUGL_KEY_F5; + case VK_F6: return PUGL_KEY_F6; + case VK_F7: return PUGL_KEY_F7; + case VK_F8: return PUGL_KEY_F8; + case VK_F9: return PUGL_KEY_F9; + case VK_F10: return PUGL_KEY_F10; + case VK_F11: return PUGL_KEY_F11; + case VK_F12: return PUGL_KEY_F12; + case VK_BACK: return PUGL_KEY_BACKSPACE; + case VK_DELETE: return PUGL_KEY_DELETE; + case VK_LEFT: return PUGL_KEY_LEFT; + case VK_UP: return PUGL_KEY_UP; + case VK_RIGHT: return PUGL_KEY_RIGHT; + case VK_DOWN: return PUGL_KEY_DOWN; + case VK_PRIOR: return PUGL_KEY_PAGE_UP; + case VK_NEXT: return PUGL_KEY_PAGE_DOWN; + case VK_HOME: return PUGL_KEY_HOME; + case VK_END: return PUGL_KEY_END; + case VK_INSERT: return PUGL_KEY_INSERT; + case VK_SHIFT: + case VK_LSHIFT: return PUGL_KEY_SHIFT_L; + case VK_RSHIFT: return PUGL_KEY_SHIFT_R; + case VK_CONTROL: + case VK_LCONTROL: return PUGL_KEY_CTRL_L; + case VK_RCONTROL: return PUGL_KEY_CTRL_R; + case VK_MENU: + case VK_LMENU: return PUGL_KEY_ALT_L; + case VK_RMENU: return PUGL_KEY_ALT_R; + case VK_LWIN: return PUGL_KEY_SUPER_L; + case VK_RWIN: return PUGL_KEY_SUPER_R; + case VK_CAPITAL: return PUGL_KEY_CAPS_LOCK; + case VK_SCROLL: return PUGL_KEY_SCROLL_LOCK; + case VK_NUMLOCK: return PUGL_KEY_NUM_LOCK; + case VK_SNAPSHOT: return PUGL_KEY_PRINT_SCREEN; + case VK_PAUSE: return PUGL_KEY_PAUSE; + } + // clang-format on + + return (PuglKey)0; +} + +static uint32_t +getModifiers(void) +{ + // clang-format off + return (((GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0u) | + ((GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0u) | + ((GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0u) | + ((GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0u) | + ((GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0u)); + // clang-format on +} + +static void +initMouseEvent(PuglEvent* event, + PuglView* view, + int button, + bool press, + LPARAM lParam) +{ + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ClientToScreen(view->impl->hwnd, &pt); + + if (press) { + SetCapture(view->impl->hwnd); + } else { + ReleaseCapture(); + } + + event->button.time = GetMessageTime() / 1e3; + event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE; + event->button.x = GET_X_LPARAM(lParam); + event->button.y = GET_Y_LPARAM(lParam); + event->button.xRoot = pt.x; + event->button.yRoot = pt.y; + event->button.state = getModifiers(); + event->button.button = (uint32_t)button; +} + +static void +initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam) +{ + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(view->impl->hwnd, &pt); + + event->scroll.time = GetMessageTime() / 1e3; + event->scroll.type = PUGL_SCROLL; + event->scroll.x = pt.x; + event->scroll.y = pt.y; + event->scroll.xRoot = GET_X_LPARAM(lParam); + event->scroll.yRoot = GET_Y_LPARAM(lParam); + event->scroll.state = getModifiers(); + event->scroll.dx = 0; + event->scroll.dy = 0; +} + +/// Return the code point for buf, or the replacement character on error +static uint32_t +puglDecodeUTF16(const wchar_t* buf, const int len) +{ + const uint32_t c0 = buf[0]; + const uint32_t c1 = buf[0]; + if (c0 >= 0xD800 && c0 < 0xDC00) { + if (len < 2) { + return 0xFFFD; // Surrogate, but length is only 1 + } else if (c1 >= 0xDC00 && c1 <= 0xDFFF) { + return ((c0 & 0x03FF) << 10) + (c1 & 0x03FF) + 0x10000; + } + + return 0xFFFD; // Unpaired surrogates + } + + return c0; +} + +static void +initKeyEvent(PuglEventKey* event, + PuglView* view, + bool press, + WPARAM wParam, + LPARAM lParam) +{ + POINT rpos = { 0, 0 }; + GetCursorPos(&rpos); + + POINT cpos = { rpos.x, rpos.y }; + ScreenToClient(view->impl->hwnd, &rpos); + + const unsigned scode = (uint32_t)((lParam & 0xFF0000) >> 16); + const unsigned vkey = ((wParam == VK_SHIFT) + ? MapVirtualKey(scode, MAPVK_VSC_TO_VK_EX) + : (unsigned)wParam); + + const unsigned vcode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC); + const unsigned kchar = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR); + const bool dead = kchar >> (sizeof(UINT) * 8 - 1) & 1; + const bool ext = lParam & 0x01000000; + + event->type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; + event->time = GetMessageTime() / 1e3; + event->state = getModifiers(); + event->xRoot = rpos.x; + event->yRoot = rpos.y; + event->x = cpos.x; + event->y = cpos.y; + event->keycode = (uint32_t)((lParam & 0xFF0000) >> 16); + event->key = 0; + + const PuglKey special = keySymToSpecial(vkey); + if (special) { + if (ext && (special == PUGL_KEY_CTRL || special == PUGL_KEY_ALT)) { + event->key = special + 1u; // Right hand key + } else { + event->key = special; + } + } else if (!dead) { + // Translate unshifted key + BYTE keyboardState[256] = {0}; + wchar_t buf[5] = {0}; + + event->key = puglDecodeUTF16( + buf, ToUnicode(vkey, vcode, keyboardState, buf, 4, 1 << 2)); + } +} + +static void +initCharEvent(PuglEvent* event, PuglView* view, WPARAM wParam, LPARAM lParam) +{ + const wchar_t utf16[2] = { wParam & 0xFFFF, (wParam >> 16) & 0xFFFF }; + + initKeyEvent(&event->key, view, true, wParam, lParam); + event->type = PUGL_TEXT; + event->text.character = puglDecodeUTF16(utf16, 2); + + if (!WideCharToMultiByte( + CP_UTF8, 0, utf16, 2, event->text.string, 8, NULL, NULL)) { + memset(event->text.string, 0, 8); + } +} + +static bool +ignoreKeyEvent(PuglView* view, LPARAM lParam) +{ + return view->hints[PUGL_IGNORE_KEY_REPEAT] && (lParam & (1 << 30)); +} + +static RECT +handleConfigure(PuglView* view, PuglEvent* event) +{ + RECT rect; + GetClientRect(view->impl->hwnd, &rect); + MapWindowPoints(view->impl->hwnd, + view->parent ? (HWND)view->parent : HWND_DESKTOP, + (LPPOINT)&rect, + 2); + + const LONG width = rect.right - rect.left; + const LONG height = rect.bottom - rect.top; + + view->frame.x = rect.left; + view->frame.y = rect.top; + + event->configure.type = PUGL_CONFIGURE; + event->configure.x = view->frame.x; + event->configure.y = view->frame.y; + event->configure.width = width; + event->configure.height = height; + + if (view->frame.width != width || view->frame.height != height) { + view->frame.width = width; + view->frame.height = height; + } + + return rect; +} + +static void +handleCrossing(PuglView* view, const PuglEventType type, POINT pos) +{ + POINT root_pos = pos; + ClientToScreen(view->impl->hwnd, &root_pos); + + const PuglEventCrossing ev = { + type, + 0, + GetMessageTime() / 1e3, + (double)pos.x, + (double)pos.y, + (double)root_pos.x, + (double)root_pos.y, + getModifiers(), + PUGL_CROSSING_NORMAL, + }; + + puglDispatchEvent(view, (const PuglEvent*)&ev); +} + +static void +constrainAspect(const PuglView* const view, + RECT* const size, + const WPARAM wParam) +{ + const float minA = (float)view->minAspectX / (float)view->minAspectY; + const float maxA = (float)view->maxAspectX / (float)view->maxAspectY; + const int w = size->right - size->left; + const int h = size->bottom - size->top; + const float a = (float)w / (float)h; + + switch (wParam) { + case WMSZ_TOP: + size->top = (a < minA ? (LONG)(size->bottom - w * minA) : + a > maxA ? (LONG)(size->bottom - w * maxA) : + size->top); + break; + case WMSZ_TOPRIGHT: + case WMSZ_RIGHT: + case WMSZ_BOTTOMRIGHT: + size->right = (a < minA ? (LONG)(size->left + h * minA) : + a > maxA ? (LONG)(size->left + h * maxA) : + size->right); + break; + case WMSZ_BOTTOM: + size->bottom = (a < minA ? (LONG)(size->top + w * minA) : + a > maxA ? (LONG)(size->top + w * maxA) : + size->bottom); + break; + case WMSZ_BOTTOMLEFT: + case WMSZ_LEFT: + case WMSZ_TOPLEFT: + size->left = (a < minA ? (LONG)(size->right - h * minA) : + a > maxA ? (LONG)(size->right - h * maxA) : + size->left); + break; + } +} + +static LRESULT +handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) +{ + PuglEvent event; + void* dummy_ptr = NULL; + RECT rect; + MINMAXINFO* mmi; + POINT pt; + + memset(&event, 0, sizeof(event)); + + event.any.type = PUGL_NOTHING; + if (InSendMessageEx(dummy_ptr)) { + event.any.flags |= PUGL_IS_SEND_EVENT; + } + + switch (message) { + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT) { + SetCursor(view->impl->cursor); + } else { + return DefWindowProc(view->impl->hwnd, message, wParam, lParam); + } + break; + case WM_SHOWWINDOW: + if (wParam) { + handleConfigure(view, &event); + puglDispatchEvent(view, &event); + event.type = PUGL_NOTHING; + + RedrawWindow(view->impl->hwnd, NULL, NULL, + RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); + } + + if ((bool)wParam != view->visible) { + view->visible = wParam; + event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP; + } + break; + case WM_SIZE: + handleConfigure(view, &event); + InvalidateRect(view->impl->hwnd, NULL, false); + break; + case WM_SIZING: + if (view->minAspectX) { + constrainAspect(view, (RECT*)lParam, wParam); + return TRUE; + } + break; + case WM_ENTERSIZEMOVE: + case WM_ENTERMENULOOP: + view->impl->resizing = true; + SetTimer(view->impl->hwnd, + PUGL_RESIZE_TIMER_ID, + 1000 / (UINT)view->hints[PUGL_REFRESH_RATE], + NULL); + break; + case WM_TIMER: + if (wParam == PUGL_RESIZE_TIMER_ID) { + RedrawWindow(view->impl->hwnd, NULL, NULL, + RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT); + } else if (wParam >= PUGL_USER_TIMER_MIN) { + PuglEvent ev = {{PUGL_TIMER, 0}}; + ev.timer.id = wParam - PUGL_USER_TIMER_MIN; + puglDispatchEvent(view, &ev); + } + break; + case WM_EXITSIZEMOVE: + case WM_EXITMENULOOP: + KillTimer(view->impl->hwnd, PUGL_RESIZE_TIMER_ID); + view->impl->resizing = false; + puglPostRedisplay(view); + break; + case WM_GETMINMAXINFO: + 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); + event.expose.type = PUGL_EXPOSE; + event.expose.x = rect.left; + event.expose.y = rect.top; + event.expose.width = rect.right - rect.left; + event.expose.height = rect.bottom - rect.top; + break; + case WM_ERASEBKGND: + return true; + case WM_MOUSEMOVE: + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + + if (!view->impl->mouseTracked) { + TRACKMOUSEEVENT tme = {0}; + + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = view->impl->hwnd; + TrackMouseEvent(&tme); + + handleCrossing(view, PUGL_POINTER_IN, pt); + view->impl->mouseTracked = true; + } + + ClientToScreen(view->impl->hwnd, &pt); + event.motion.type = PUGL_MOTION; + event.motion.time = GetMessageTime() / 1e3; + event.motion.x = GET_X_LPARAM(lParam); + event.motion.y = GET_Y_LPARAM(lParam); + event.motion.xRoot = pt.x; + event.motion.yRoot = pt.y; + event.motion.state = getModifiers(); + break; + case WM_MOUSELEAVE: + GetCursorPos(&pt); + ScreenToClient(view->impl->hwnd, &pt); + handleCrossing(view, PUGL_POINTER_OUT, pt); + view->impl->mouseTracked = false; + break; + case WM_LBUTTONDOWN: + initMouseEvent(&event, view, 1, true, lParam); + break; + case WM_MBUTTONDOWN: + initMouseEvent(&event, view, 2, true, lParam); + break; + case WM_RBUTTONDOWN: + initMouseEvent(&event, view, 3, true, lParam); + break; + case WM_LBUTTONUP: + initMouseEvent(&event, view, 1, false, lParam); + break; + case WM_MBUTTONUP: + initMouseEvent(&event, view, 2, false, lParam); + break; + case WM_RBUTTONUP: + initMouseEvent(&event, view, 3, false, lParam); + break; + 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)) { + initKeyEvent(&event.key, view, true, wParam, lParam); + } + break; + case WM_KEYUP: + initKeyEvent(&event.key, view, false, wParam, lParam); + break; + case WM_CHAR: + initCharEvent(&event, view, wParam, lParam); + break; + case WM_SETFOCUS: + event.type = PUGL_FOCUS_IN; + break; + case WM_KILLFOCUS: + event.type = PUGL_FOCUS_OUT; + break; + case WM_SYSKEYDOWN: + initKeyEvent(&event.key, view, true, wParam, lParam); + break; + case WM_SYSKEYUP: + initKeyEvent(&event.key, view, false, wParam, lParam); + break; + case WM_SYSCHAR: + return TRUE; + case PUGL_LOCAL_CLIENT_MSG: + event.client.type = PUGL_CLIENT; + event.client.data1 = (uintptr_t)wParam; + event.client.data2 = (uintptr_t)lParam; + break; + case WM_QUIT: + case PUGL_LOCAL_CLOSE_MSG: + event.any.type = PUGL_CLOSE; + break; + default: + return DefWindowProc(view->impl->hwnd, message, wParam, lParam); + } + + puglDispatchEvent(view, &event); + + return 0; +} + +PuglStatus +puglGrabFocus(PuglView* view) +{ + SetFocus(view->impl->hwnd); + return PUGL_SUCCESS; +} + +bool +puglHasFocus(const PuglView* view) +{ + return GetFocus() == view->impl->hwnd; +} + +PuglStatus +puglRequestAttention(PuglView* view) +{ + FLASHWINFO info = {sizeof(FLASHWINFO), + view->impl->hwnd, + FLASHW_ALL|FLASHW_TIMERNOFG, + 1, + 0}; + + FlashWindowEx(&info); + + return PUGL_SUCCESS; +} + +PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ + const UINT msec = (UINT)floor(timeout * 1000.0); + + return (SetTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id, msec, NULL) + ? PUGL_SUCCESS + : PUGL_UNKNOWN_ERROR); +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ + return (KillTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id) + ? PUGL_SUCCESS + : PUGL_UNKNOWN_ERROR); +} + +PuglStatus +puglSendEvent(PuglView* view, const PuglEvent* event) +{ + if (event->type == PUGL_CLIENT) { + PostMessage(view->impl->hwnd, + PUGL_LOCAL_CLIENT_MSG, + (WPARAM)event->client.data1, + (LPARAM)event->client.data2); + + return PUGL_SUCCESS; + } + + return PUGL_UNSUPPORTED_TYPE; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglWaitForEvent(PuglView* PUGL_UNUSED(view)) +{ + WaitMessage(); + return PUGL_SUCCESS; +} +#endif + +static PuglStatus +puglDispatchViewEvents(PuglView* view) +{ + /* Windows has no facility to process only currently queued messages, which + causes the event loop to run forever in cases like mouse movement where + the queue is constantly being filled with new messages. To work around + this, we post a message to ourselves before starting, record its time + when it is received, then break the loop on the first message that was + created afterwards. */ + + long markTime = 0; + MSG msg; + while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { + if (msg.message == PUGL_LOCAL_MARK_MSG) { + markTime = GetMessageTime(); + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + if (markTime != 0 && GetMessageTime() > markTime) { + break; + } + } + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglDispatchWinEvents(PuglWorld* world) +{ + for (size_t i = 0; i < world->numViews; ++i) { + PostMessage(world->views[i]->impl->hwnd, PUGL_LOCAL_MARK_MSG, 0, 0); + } + + for (size_t i = 0; i < world->numViews; ++i) { + puglDispatchViewEvents(world->views[i]); + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglUpdate(PuglWorld* world, double timeout) +{ + const double startTime = puglGetTime(world); + PuglStatus st = PUGL_SUCCESS; + + if (timeout < 0.0) { + st = puglPollWinEvents(world, timeout); + st = st ? st : puglDispatchWinEvents(world); + } else if (timeout == 0.0) { + st = puglDispatchWinEvents(world); + } else { + const double endTime = startTime + timeout - 0.001; + for (double t = startTime; t < endTime; t = puglGetTime(world)) { + if ((st = puglPollWinEvents(world, endTime - t)) || + (st = puglDispatchWinEvents(world))) { + break; + } + } + } + + for (size_t i = 0; i < world->numViews; ++i) { + if (world->views[i]->visible) { + puglDispatchSimpleEvent(world->views[i], PUGL_UPDATE); + } + + UpdateWindow(world->views[i]->impl->hwnd); + } + + return st; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglProcessEvents(PuglView* view) +{ + return puglUpdate(view->world, 0.0); +} +#endif + +LRESULT CALLBACK +wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (message) { + case WM_CREATE: + PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); + return 0; + case WM_CLOSE: + PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam); + return 0; + case WM_DESTROY: + return 0; + default: + if (view && hwnd == view->impl->hwnd) { + return handleMessage(view, message, wParam, lParam); + } else { + return DefWindowProc(hwnd, message, wParam, lParam); + } + } +} + +double +puglGetTime(const PuglWorld* world) +{ + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + return ((double)count.QuadPart / world->impl->timerFrequency - + world->startTime); +} + +PuglStatus +puglPostRedisplay(PuglView* view) +{ + InvalidateRect(view->impl->hwnd, NULL, false); + return PUGL_SUCCESS; +} + +PuglStatus +puglPostRedisplayRect(PuglView* view, const PuglRect rect) +{ + const RECT r = {(long)floor(rect.x), + (long)floor(rect.y), + (long)ceil(rect.x + rect.width), + (long)ceil(rect.y + rect.height)}; + + InvalidateRect(view->impl->hwnd, &r, false); + + return PUGL_SUCCESS; +} + +PuglNativeView +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeView)view->impl->hwnd; +} + +PuglStatus +puglSetWindowTitle(PuglView* view, const char* title) +{ + puglSetString(&view->title, title); + + if (view->impl->hwnd) { + wchar_t* wtitle = puglUtf8ToWideChar(title); + if (wtitle) { + SetWindowTextW(view->impl->hwnd, wtitle); + free(wtitle); + } + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetFrame(PuglView* view, const PuglRect frame) +{ + view->frame = frame; + + if (view->impl->hwnd) { + RECT rect = { (long)frame.x, + (long)frame.y, + (long)frame.x + (long)frame.width, + (long)frame.y + (long)frame.height }; + + AdjustWindowRectEx(&rect, puglWinGetWindowFlags(view), + FALSE, + puglWinGetWindowExFlags(view)); + + if (!SetWindowPos(view->impl->hwnd, + HWND_TOP, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER)) { + return PUGL_UNKNOWN_ERROR; + } + } + + 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; + return PUGL_SUCCESS; +} + +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, + const int maxX, + const int maxY) +{ + view->minAspectX = minX; + view->minAspectY = minY; + view->maxAspectX = maxX; + view->maxAspectY = maxY; + 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, + size_t* const len) +{ + PuglInternals* const impl = view->impl; + + if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || + !OpenClipboard(impl->hwnd)) { + return NULL; + } + + HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); + wchar_t* wstr = mem ? (wchar_t*)GlobalLock(mem) : NULL; + if (!wstr) { + CloseClipboard(); + return NULL; + } + + free(view->clipboard.data); + view->clipboard.data = puglWideCharToUtf8(wstr, &view->clipboard.len); + GlobalUnlock(mem); + CloseClipboard(); + + return puglGetInternalClipboard(view, type, len); +} + +PuglStatus +puglSetClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + PuglInternals* const impl = view->impl; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } else if (!OpenClipboard(impl->hwnd)) { + return PUGL_UNKNOWN_ERROR; + } + + // Measure string and allocate global memory for clipboard + const char* str = (const char*)data; + const int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t)); + if (!mem) { + CloseClipboard(); + return PUGL_UNKNOWN_ERROR; + } + + // Lock global memory + wchar_t* wstr = (wchar_t*)GlobalLock(mem); + if (!wstr) { + GlobalFree(mem); + CloseClipboard(); + return PUGL_UNKNOWN_ERROR; + } + + // Convert string into global memory and set it as clipboard data + MultiByteToWideChar(CP_UTF8, 0, str, (int)len, wstr, wlen); + wstr[wlen] = 0; + GlobalUnlock(mem); + SetClipboardData(CF_UNICODETEXT, mem); + CloseClipboard(); + 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/include/pugl/detail/win.h b/include/pugl/detail/win.h new file mode 100644 index 0000000..b0d92e0 --- /dev/null +++ b/include/pugl/detail/win.h @@ -0,0 +1,147 @@ +/* + 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 win.h + @brief Shared definitions for Windows implementation. +*/ + +#include "pugl/detail/implementation.h" + +#include <windows.h> + +#include <stdbool.h> + +typedef PIXELFORMATDESCRIPTOR PuglWinPFD; + +struct PuglWorldInternalsImpl { + double timerFrequency; +}; + +struct PuglInternalsImpl { + PuglWinPFD pfd; + int pfId; + HWND hwnd; + HCURSOR cursor; + HDC hdc; + PuglSurface* surface; + bool flashing; + bool resizing; + bool mouseTracked; +}; + +static inline PuglWinPFD +puglWinGetPixelFormatDescriptor(const PuglHints hints) +{ + const int rgbBits = (hints[PUGL_RED_BITS] + // + hints[PUGL_GREEN_BITS] + // + hints[PUGL_BLUE_BITS]); + + const DWORD dwFlags = hints[PUGL_DOUBLE_BUFFER] ? PFD_DOUBLEBUFFER : 0u; + + PuglWinPFD pfd; + ZeroMemory(&pfd, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | dwFlags; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = (BYTE)rgbBits; + pfd.cRedBits = (BYTE)hints[PUGL_RED_BITS]; + pfd.cGreenBits = (BYTE)hints[PUGL_GREEN_BITS]; + pfd.cBlueBits = (BYTE)hints[PUGL_BLUE_BITS]; + pfd.cAlphaBits = (BYTE)hints[PUGL_ALPHA_BITS]; + pfd.cDepthBits = (BYTE)hints[PUGL_DEPTH_BITS]; + pfd.cStencilBits = (BYTE)hints[PUGL_STENCIL_BITS]; + pfd.iLayerType = PFD_MAIN_PLANE; + return pfd; +} + +static inline unsigned +puglWinGetWindowFlags(const PuglView* const view) +{ + const bool resizable = view->hints[PUGL_RESIZABLE]; + const unsigned sizeFlags = resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0u; + + return (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | + (view->parent + ? WS_CHILD + : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | sizeFlags))); +} + +static inline unsigned +puglWinGetWindowExFlags(const PuglView* const view) +{ + return WS_EX_NOINHERITLAYOUT | (view->parent ? 0u : WS_EX_APPWINDOW); +} + +static inline PuglStatus +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 }; + AdjustWindowRectEx(&wr, winFlags, FALSE, winExFlags); + + // Create window and get drawing context + if (!(*hwnd = CreateWindowEx(winExFlags, className, title, winFlags, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right-wr.left, wr.bottom-wr.top, + (HWND)parent, NULL, NULL, NULL))) { + return PUGL_REALIZE_FAILED; + } else if (!(*hdc = GetDC(*hwnd))) { + DestroyWindow(*hwnd); + *hwnd = NULL; + return PUGL_REALIZE_FAILED; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglWinStubConfigure(PuglView* view); + +PuglStatus +puglWinStubEnter(PuglView* view, const PuglEventExpose* expose); + +PuglStatus +puglWinStubLeave(PuglView* view, const PuglEventExpose* expose); diff --git a/include/pugl/detail/win_cairo.c b/include/pugl/detail/win_cairo.c new file mode 100644 index 0000000..1b9afb9 --- /dev/null +++ b/include/pugl/detail/win_cairo.c @@ -0,0 +1,178 @@ +/* + 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 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 <cairo-win32.h> +#include <cairo.h> + +#include <stdlib.h> + +typedef struct { + cairo_surface_t* surface; + cairo_t* cr; + HDC drawDc; + HBITMAP drawBitmap; +} PuglWinCairoSurface; + +static PuglStatus +puglWinCairoCreateDrawContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + surface->drawDc = CreateCompatibleDC(impl->hdc); + surface->drawBitmap = CreateCompatibleBitmap( + impl->hdc, (int)view->frame.width, (int)view->frame.height); + + DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap)); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinCairoDestroyDrawContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + DeleteDC(surface->drawDc); + DeleteObject(surface->drawBitmap); + + surface->drawDc = NULL; + surface->drawBitmap = NULL; + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinCairoConfigure(PuglView* view) +{ + const PuglStatus st = puglWinStubConfigure(view); + + if (!st) { + view->impl->surface = (PuglWinCairoSurface*)calloc( + 1, sizeof(PuglWinCairoSurface)); + } + + return st; +} + +static void +puglWinCairoClose(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + cairo_surface_destroy(surface->surface); + + surface->surface = NULL; +} + +static PuglStatus +puglWinCairoOpen(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + cairo_status_t st = CAIRO_STATUS_SUCCESS; + if (!(surface->surface = cairo_win32_surface_create(surface->drawDc)) || + (st = cairo_surface_status(surface->surface)) || + !(surface->cr = cairo_create(surface->surface)) || + (st = cairo_status(surface->cr))) { + return PUGL_CREATE_CONTEXT_FAILED; + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinCairoDestroy(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + puglWinCairoClose(view); + puglWinCairoDestroyDrawContext(view); + free(surface); + impl->surface = NULL; + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinCairoEnter(PuglView* view, const PuglEventExpose* expose) +{ + PuglStatus st = PUGL_SUCCESS; + + if (expose && + !(st = puglWinCairoCreateDrawContext(view)) && + !(st = puglWinCairoOpen(view))) { + PAINTSTRUCT ps; + BeginPaint(view->impl->hwnd, &ps); + } + + return st; +} + +static PuglStatus +puglWinCairoLeave(PuglView* view, const PuglEventExpose* expose) +{ + PuglInternals* const impl = view->impl; + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; + + if (expose) { + cairo_surface_flush(surface->surface); + BitBlt(impl->hdc, + 0, 0, (int)view->frame.width, (int)view->frame.height, + surface->drawDc, 0, 0, SRCCOPY); + + puglWinCairoClose(view); + puglWinCairoDestroyDrawContext(view); + + PAINTSTRUCT ps; + EndPaint(view->impl->hwnd, &ps); + } + + return PUGL_SUCCESS; +} + +static void* +puglWinCairoGetContext(PuglView* view) +{ + return ((PuglWinCairoSurface*)view->impl->surface)->cr; +} + +const PuglBackend* +puglCairoBackend() +{ + static const PuglBackend backend = {puglWinCairoConfigure, + puglStubCreate, + puglWinCairoDestroy, + puglWinCairoEnter, + puglWinCairoLeave, + puglWinCairoGetContext}; + + return &backend; +} diff --git a/include/pugl/detail/win_gl.c b/include/pugl/detail/win_gl.c new file mode 100644 index 0000000..096c715 --- /dev/null +++ b/include/pugl/detail/win_gl.c @@ -0,0 +1,325 @@ +/* + 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 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 <windows.h> + +#include <GL/gl.h> + +#include <stdbool.h> +#include <stdlib.h> + +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 + +typedef HGLRC (*WglCreateContextAttribs)(HDC, HGLRC, const int*); +typedef BOOL (*WglSwapInterval)(int); +typedef BOOL (*WglChoosePixelFormat)( + HDC, const int*, const FLOAT*, UINT, int*, UINT*); + +typedef struct { + WglChoosePixelFormat wglChoosePixelFormat; + WglCreateContextAttribs wglCreateContextAttribs; + WglSwapInterval wglSwapInterval; +} PuglWinGlProcs; + +typedef struct { + PuglWinGlProcs procs; + HGLRC hglrc; +} PuglWinGlSurface; + +// Struct to manage the fake window used during configuration +typedef struct { + HWND hwnd; + HDC hdc; +} PuglFakeWindow; + +static PuglStatus +puglWinError(PuglFakeWindow* fakeWin, const PuglStatus status) +{ + if (fakeWin->hwnd) { + ReleaseDC(fakeWin->hwnd, fakeWin->hdc); + DestroyWindow(fakeWin->hwnd); + } + + return status; +} + +static PuglWinGlProcs puglWinGlGetProcs(void) +{ + const PuglWinGlProcs procs = { + (WglChoosePixelFormat)( + wglGetProcAddress("wglChoosePixelFormatARB")), + (WglCreateContextAttribs)( + wglGetProcAddress("wglCreateContextAttribsARB")), + (WglSwapInterval)( + wglGetProcAddress("wglSwapIntervalEXT")) + }; + + return procs; +} + +static PuglStatus +puglWinGlConfigure(PuglView* view) +{ + PuglInternals* impl = view->impl; + + // Set attributes to default if they are unset + // (There is no GLX_DONT_CARE equivalent on Windows) + if (view->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_DEPTH_BITS] = 0; + } + if (view->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) { + view->hints[PUGL_STENCIL_BITS] = 0; + } + if (view->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) { + view->hints[PUGL_SAMPLES] = 1; + } + if (view->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) { + view->hints[PUGL_DOUBLE_BUFFER] = 1; + } + if (view->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) { + view->hints[PUGL_SWAP_INTERVAL] = 1; + } + + // clang-format off + const int pixelAttrs[] = { + WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_SUPPORT_OPENGL_ARB, GL_TRUE, + WGL_DOUBLE_BUFFER_ARB, view->hints[PUGL_DOUBLE_BUFFER], + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_SAMPLE_BUFFERS_ARB, view->hints[PUGL_SAMPLES] ? 1 : 0, + WGL_SAMPLES_ARB, view->hints[PUGL_SAMPLES], + WGL_RED_BITS_ARB, view->hints[PUGL_RED_BITS], + WGL_GREEN_BITS_ARB, view->hints[PUGL_GREEN_BITS], + WGL_BLUE_BITS_ARB, view->hints[PUGL_BLUE_BITS], + WGL_ALPHA_BITS_ARB, view->hints[PUGL_ALPHA_BITS], + WGL_DEPTH_BITS_ARB, view->hints[PUGL_DEPTH_BITS], + WGL_STENCIL_BITS_ARB, view->hints[PUGL_STENCIL_BITS], + 0, + }; + // clang-format on + + PuglWinGlSurface* const surface = + (PuglWinGlSurface*)calloc(1, sizeof(PuglWinGlSurface)); + impl->surface = surface; + + // Create fake window for getting at GL context + PuglStatus st = PUGL_SUCCESS; + PuglFakeWindow fakeWin = {0, 0}; + static const char* title = "Pugl Configuration"; + if ((st = puglWinCreateWindow(view, title, &fakeWin.hwnd, &fakeWin.hdc))) { + return puglWinError(&fakeWin, st); + } + + // Set pixel format for fake window + const PuglWinPFD fakePfd = puglWinGetPixelFormatDescriptor(view->hints); + const int fakePfId = ChoosePixelFormat(fakeWin.hdc, &fakePfd); + if (!fakePfId) { + return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED); + } else if (!SetPixelFormat(fakeWin.hdc, fakePfId, &fakePfd)) { + return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED); + } + + // Create fake GL context to get at the functions we need + HGLRC fakeRc = wglCreateContext(fakeWin.hdc); + if (!fakeRc) { + return puglWinError(&fakeWin, PUGL_CREATE_CONTEXT_FAILED); + } + + // Enter fake context and get extension functions + wglMakeCurrent(fakeWin.hdc, fakeRc); + surface->procs = puglWinGlGetProcs(); + + if (surface->procs.wglChoosePixelFormat) { + // Choose pixel format based on attributes + UINT numFormats = 0; + if (!surface->procs.wglChoosePixelFormat( + fakeWin.hdc, pixelAttrs, NULL, 1u, &impl->pfId, &numFormats)) { + return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED); + } + + DescribePixelFormat( + impl->hdc, impl->pfId, sizeof(impl->pfd), &impl->pfd); + } else { + // Modern extensions not available, use basic pixel format + impl->pfd = fakePfd; + impl->pfId = fakePfId; + } + + // Dispose of fake window and context + wglMakeCurrent(NULL, NULL); + wglDeleteContext(fakeRc); + ReleaseDC(fakeWin.hwnd, fakeWin.hdc); + DestroyWindow(fakeWin.hwnd); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinGlCreate(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglWinGlSurface* const surface = (PuglWinGlSurface*)impl->surface; + PuglStatus st = PUGL_SUCCESS; + + const int contextAttribs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, + view->hints[PUGL_CONTEXT_VERSION_MAJOR], + + WGL_CONTEXT_MINOR_VERSION_ARB, + view->hints[PUGL_CONTEXT_VERSION_MINOR], + + WGL_CONTEXT_FLAGS_ARB, + (view->hints[PUGL_USE_DEBUG_CONTEXT] ? WGL_CONTEXT_DEBUG_BIT_ARB : 0), + + WGL_CONTEXT_PROFILE_MASK_ARB, + (view->hints[PUGL_USE_COMPAT_PROFILE] + ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB + : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB), + + 0}; + + // Create real window with desired pixel format + if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { + return st; + } else if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { + ReleaseDC(impl->hwnd, impl->hdc); + DestroyWindow(impl->hwnd); + impl->hwnd = NULL; + impl->hdc = NULL; + return PUGL_SET_FORMAT_FAILED; + } + + // Create GL context + if (surface->procs.wglCreateContextAttribs && + !(surface->hglrc = surface->procs.wglCreateContextAttribs( + impl->hdc, 0, contextAttribs))) { + return PUGL_CREATE_CONTEXT_FAILED; + } else if (!(surface->hglrc = wglCreateContext(impl->hdc))) { + return PUGL_CREATE_CONTEXT_FAILED; + } + + // Enter context and set swap interval + wglMakeCurrent(impl->hdc, surface->hglrc); + const int swapInterval = view->hints[PUGL_SWAP_INTERVAL]; + if (surface->procs.wglSwapInterval && swapInterval != PUGL_DONT_CARE) { + surface->procs.wglSwapInterval(swapInterval); + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinGlDestroy(PuglView* view) +{ + PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; + if (surface) { + wglMakeCurrent(NULL, NULL); + wglDeleteContext(surface->hglrc); + free(surface); + view->impl->surface = NULL; + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinGlEnter(PuglView* view, const PuglEventExpose* expose) +{ + PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface; + + wglMakeCurrent(view->impl->hdc, surface->hglrc); + + if (expose) { + PAINTSTRUCT ps; + BeginPaint(view->impl->hwnd, &ps); + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglWinGlLeave(PuglView* view, const PuglEventExpose* expose) +{ + if (expose) { + PAINTSTRUCT ps; + EndPaint(view->impl->hwnd, &ps); + SwapBuffers(view->impl->hdc); + } + + wglMakeCurrent(NULL, NULL); + return PUGL_SUCCESS; +} + +PuglGlFunc +puglGetProcAddress(const char* name) +{ + const PuglGlFunc func = (PuglGlFunc)wglGetProcAddress(name); + + /* Windows has the annoying property that wglGetProcAddress returns NULL + for functions from OpenGL 1.1, so we fall back to pulling them directly + from opengl32.dll */ + + return func + ? func + : (PuglGlFunc)GetProcAddress(GetModuleHandle("opengl32.dll"), name); +} + +const PuglBackend* +puglGlBackend(void) +{ + static const PuglBackend backend = {puglWinGlConfigure, + puglWinGlCreate, + puglWinGlDestroy, + puglWinGlEnter, + puglWinGlLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/include/pugl/detail/win_stub.c b/include/pugl/detail/win_stub.c new file mode 100644 index 0000000..ab9e6aa --- /dev/null +++ b/include/pugl/detail/win_stub.c @@ -0,0 +1,80 @@ +/* + 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. +*/ + +#include "pugl/pugl_stub.h" + +#include "pugl/detail/stub.h" +#include "pugl/detail/types.h" +#include "pugl/detail/win.h" + +PuglStatus +puglWinStubConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglStatus st = PUGL_SUCCESS; + + if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) { + return st; + } + + impl->pfd = puglWinGetPixelFormatDescriptor(view->hints); + impl->pfId = ChoosePixelFormat(impl->hdc, &impl->pfd); + + if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) { + ReleaseDC(impl->hwnd, impl->hdc); + DestroyWindow(impl->hwnd); + impl->hwnd = NULL; + impl->hdc = NULL; + return PUGL_SET_FORMAT_FAILED; + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglWinStubEnter(PuglView* view, const PuglEventExpose* expose) +{ + if (expose) { + PAINTSTRUCT ps; + BeginPaint(view->impl->hwnd, &ps); + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglWinStubLeave(PuglView* view, const PuglEventExpose* expose) +{ + if (expose) { + PAINTSTRUCT ps; + EndPaint(view->impl->hwnd, &ps); + } + + return PUGL_SUCCESS; +} + +const PuglBackend* +puglStubBackend(void) +{ + static const PuglBackend backend = {puglWinStubConfigure, + puglStubCreate, + puglStubDestroy, + puglWinStubEnter, + puglWinStubLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/include/pugl/detail/x11.c b/include/pugl/detail/x11.c new file mode 100644 index 0000000..fd76606 --- /dev/null +++ b/include/pugl/detail/x11.c @@ -0,0 +1,1348 @@ +/* + Copyright 2012-2020 David Robillard <d@drobilla.net> + Copyright 2013 Robin Gareus <robin@gareus.org> + Copyright 2011-2012 Ben Loftis, Harrison Consoles + + 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 x11.c + @brief X11 implementation. +*/ + +#define _POSIX_C_SOURCE 199309L + +#include "pugl/detail/x11.h" + +#include "pugl/detail/implementation.h" +#include "pugl/detail/types.h" +#include "pugl/pugl.h" + +#include <X11/X.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> + +#ifdef HAVE_XRANDR +# include <X11/extensions/Xrandr.h> +#endif + +#ifdef HAVE_XSYNC +# include <X11/extensions/sync.h> +# 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> + +#include <math.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +enum WmClientStateMessageAction { + WM_STATE_REMOVE, + WM_STATE_ADD, + WM_STATE_TOGGLE +}; + +static const long eventMask = + (ExposureMask | StructureNotifyMask | + VisibilityChangeMask | FocusChangeMask | + EnterWindowMask | LeaveWindowMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask); + +static bool +puglInitXSync(PuglWorldInternals* impl) +{ +#ifdef HAVE_XSYNC + 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) && + (counters = XSyncListSystemCounters(impl->display, &numCounters))) { + + for (int n = 0; n < numCounters; ++n) { + if (!strcmp(counters[n].name, "SERVERTIME")) { + impl->serverTimeCounter = counters[n].counter; + impl->syncSupported = true; + break; + } + } + + XSyncFreeSystemCounterList(counters); + } +#else + (void)impl; +#endif + + return false; +} + +PuglWorldInternals* +puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags) +{ + if (type == PUGL_PROGRAM && (flags & PUGL_WORLD_THREADS)) { + XInitThreads(); + } + + Display* display = XOpenDisplay(NULL); + if (!display) { + return NULL; + } + + PuglWorldInternals* impl = (PuglWorldInternals*)calloc( + 1, sizeof(PuglWorldInternals)); + + impl->display = display; + + // Intern the various atoms we will need + impl->atoms.CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0); + impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0); + impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); + impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0); + impl->atoms.PUGL_CLIENT_MSG = XInternAtom(display, "_PUGL_CLIENT_MSG", 0); + impl->atoms.NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", 0); + impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0); + impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION = + XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0); + + // Open input method + XSetLocaleModifiers(""); + if (!(impl->xim = XOpenIM(display, NULL, NULL, NULL))) { + XSetLocaleModifiers("@im="); + impl->xim = XOpenIM(display, NULL, NULL, NULL); + } + + puglInitXSync(impl); + XFlush(display); + + return impl; +} + +void* +puglGetNativeWorld(PuglWorld* world) +{ + return world->impl->display; +} + +PuglInternals* +puglInitViewInternals(void) +{ + PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); + +#ifdef HAVE_XCURSOR + impl->cursorShape = XC_arrow; +#endif + + return impl; +} + +static PuglStatus +puglPollX11Socket(PuglWorld* world, const double timeout) +{ + if (XPending(world->impl->display) > 0) { + return PUGL_SUCCESS; + } + + const int fd = ConnectionNumber(world->impl->display); + const int nfds = fd + 1; + int ret = 0; + fd_set fds; + FD_ZERO(&fds); // NOLINT + FD_SET(fd, &fds); + + if (timeout < 0.0) { + ret = select(nfds, &fds, NULL, NULL, NULL); + } else { + const long sec = (long)timeout; + const long usec = (long)((timeout - (double)sec) * 1e6); + struct timeval tv = {sec, usec}; + ret = select(nfds, &fds, NULL, NULL, &tv); + } + + return ret < 0 ? PUGL_UNKNOWN_ERROR : PUGL_SUCCESS; +} + +static PuglView* +puglFindView(PuglWorld* world, const Window window) +{ + for (size_t i = 0; i < world->numViews; ++i) { + if (world->views[i]->impl->win == window) { + return world->views[i]; + } + } + + return NULL; +} + +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 = 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; + sizeHints.min_aspect.y = view->minAspectY; + sizeHints.max_aspect.x = view->maxAspectX; + sizeHints.max_aspect.y = view->maxAspectY; + } + } + + 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) +{ + PuglInternals* const impl = view->impl; + if (impl->win) { + return PUGL_FAILURE; + } + + PuglWorld* const world = view->world; + PuglX11Atoms* const atoms = &view->world->impl->atoms; + Display* const display = world->impl->display; + + impl->display = display; + impl->screen = DefaultScreen(display); + + 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); + if (st || !impl->vi) { + view->backend->destroy(view); + return st ? st : PUGL_BACKEND_FAILED; + } + + Window xParent = view->parent ? (Window)view->parent + : RootWindow(display, impl->screen); + + Colormap cmap = XCreateColormap( + display, xParent, impl->vi->visual, AllocNone); + + XSetWindowAttributes attr = {0}; + attr.colormap = cmap; + attr.event_mask = eventMask; + + const Window win = impl->win = XCreateWindow( + display, xParent, + (int)view->frame.x, (int)view->frame.y, + (unsigned)view->frame.width, (unsigned)view->frame.height, + 0, impl->vi->depth, InputOutput, + impl->vi->visual, CWColormap | CWEventMask, &attr); + + if ((st = view->backend->create(view))) { + return st; + } + +#ifdef HAVE_XRANDR + // Set refresh rate hint to the real refresh rate + XRRScreenConfiguration* conf = XRRGetScreenInfo(display, xParent); + short current_rate = XRRConfigCurrentRate(conf); + + view->hints[PUGL_REFRESH_RATE] = current_rate; + XRRFreeScreenConfigInfo(conf); +#endif + + updateSizeHints(view); + + XClassHint classHint = { world->className, world->className }; + XSetClassHint(display, win, &classHint); + + if (view->title) { + puglSetWindowTitle(view, view->title); + } + + if (!view->parent) { + XSetWMProtocols(display, win, &atoms->WM_DELETE_WINDOW, 1); + } + + if (view->transientParent) { + XSetTransientForHint(display, win, (Window)(view->transientParent)); + } + + // Create input context + const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing; + if (!(impl->xic = XCreateIC(world->impl->xim, + XNInputStyle, im_style, + XNClientWindow, win, + XNFocusWindow, win, + NULL))) { + view->world->logFunc(view->world, + PUGL_LOG_LEVEL_WARNING, + "XCreateID failed\n"); + } + +#ifdef HAVE_XCURSOR + puglDefineCursorShape(view, impl->cursorShape); +#endif + + puglDispatchSimpleEvent(view, PUGL_CREATE); + + return PUGL_SUCCESS; +} + +PuglStatus +puglShowWindow(PuglView* view) +{ + PuglStatus st = PUGL_SUCCESS; + + if (!view->impl->win) { + if ((st = puglRealize(view))) { + return st; + } + } + + XMapRaised(view->impl->display, view->impl->win); + puglPostRedisplay(view); + + return st; +} + +PuglStatus +puglHideWindow(PuglView* view) +{ + XUnmapWindow(view->impl->display, view->impl->win); + return PUGL_SUCCESS; +} + +void +puglFreeViewInternals(PuglView* view) +{ + if (view && view->impl) { + if (view->impl->xic) { + XDestroyIC(view->impl->xic); + } + if (view->backend) { + view->backend->destroy(view); + } + if (view->impl->display) { + XDestroyWindow(view->impl->display, view->impl->win); + } + XFree(view->impl->vi); + free(view->impl); + } +} + +void +puglFreeWorldInternals(PuglWorld* world) +{ + if (world->impl->xim) { + XCloseIM(world->impl->xim); + } + XCloseDisplay(world->impl->display); + free(world->impl->timers); + free(world->impl); +} + +static PuglKey +keySymToSpecial(KeySym sym) +{ + switch (sym) { + case XK_F1: return PUGL_KEY_F1; + case XK_F2: return PUGL_KEY_F2; + case XK_F3: return PUGL_KEY_F3; + case XK_F4: return PUGL_KEY_F4; + case XK_F5: return PUGL_KEY_F5; + case XK_F6: return PUGL_KEY_F6; + case XK_F7: return PUGL_KEY_F7; + case XK_F8: return PUGL_KEY_F8; + case XK_F9: return PUGL_KEY_F9; + case XK_F10: return PUGL_KEY_F10; + case XK_F11: return PUGL_KEY_F11; + case XK_F12: return PUGL_KEY_F12; + case XK_Left: return PUGL_KEY_LEFT; + case XK_Up: return PUGL_KEY_UP; + case XK_Right: return PUGL_KEY_RIGHT; + case XK_Down: return PUGL_KEY_DOWN; + case XK_Page_Up: return PUGL_KEY_PAGE_UP; + case XK_Page_Down: return PUGL_KEY_PAGE_DOWN; + case XK_Home: return PUGL_KEY_HOME; + case XK_End: return PUGL_KEY_END; + case XK_Insert: return PUGL_KEY_INSERT; + case XK_Shift_L: return PUGL_KEY_SHIFT_L; + case XK_Shift_R: return PUGL_KEY_SHIFT_R; + case XK_Control_L: return PUGL_KEY_CTRL_L; + case XK_Control_R: return PUGL_KEY_CTRL_R; + case XK_Alt_L: return PUGL_KEY_ALT_L; + case XK_ISO_Level3_Shift: + case XK_Alt_R: return PUGL_KEY_ALT_R; + case XK_Super_L: return PUGL_KEY_SUPER_L; + case XK_Super_R: return PUGL_KEY_SUPER_R; + case XK_Menu: return PUGL_KEY_MENU; + case XK_Caps_Lock: return PUGL_KEY_CAPS_LOCK; + case XK_Scroll_Lock: return PUGL_KEY_SCROLL_LOCK; + case XK_Num_Lock: return PUGL_KEY_NUM_LOCK; + case XK_Print: return PUGL_KEY_PRINT_SCREEN; + case XK_Pause: return PUGL_KEY_PAUSE; + default: break; + } + return (PuglKey)0; +} + +static int +lookupString(XIC xic, XEvent* xevent, char* str, KeySym* sym) +{ + Status status = 0; + +#ifdef X_HAVE_UTF8_STRING + const int n = Xutf8LookupString(xic, &xevent->xkey, str, 7, sym, &status); +#else + const int n = XmbLookupString(xic, &xevent->xkey, str, 7, sym, &status); +#endif + + return status == XBufferOverflow ? 0 : n; +} + +static void +translateKey(PuglView* view, XEvent* xevent, PuglEvent* event) +{ + const unsigned state = xevent->xkey.state; + const bool filter = XFilterEvent(xevent, None); + + event->key.keycode = xevent->xkey.keycode; + xevent->xkey.state = 0; + + // Lookup unshifted key + char ustr[8] = {0}; + KeySym sym = 0; + const int ufound = XLookupString(&xevent->xkey, ustr, 8, &sym, NULL); + const PuglKey special = keySymToSpecial(sym); + + event->key.key = ((special || ufound <= 0) + ? special + : puglDecodeUTF8((const uint8_t*)ustr)); + + if (xevent->type == KeyPress && !filter && !special) { + // Lookup shifted key for possible text event + xevent->xkey.state = state; + + char sstr[8] = {0}; + const int sfound = lookupString(view->impl->xic, xevent, sstr, &sym); + if (sfound > 0) { + // Dispatch key event now + puglDispatchEvent(view, event); + + // "Return" a text event in its place + event->text.type = PUGL_TEXT; + event->text.character = puglDecodeUTF8((const uint8_t*)sstr); + memcpy(event->text.string, sstr, sizeof(sstr)); + } + } +} + +static uint32_t +translateModifiers(const unsigned xstate) +{ + 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 +translateEvent(PuglView* view, XEvent xevent) +{ + const PuglX11Atoms* atoms = &view->world->impl->atoms; + + PuglEvent event = {{PUGL_NOTHING, 0}}; + event.any.flags = xevent.xany.send_event ? PUGL_IS_SEND_EVENT : 0; + + switch (xevent.type) { + case ClientMessage: + if (xevent.xclient.message_type == atoms->WM_PROTOCOLS) { + const Atom protocol = (Atom)xevent.xclient.data.l[0]; + if (protocol == atoms->WM_DELETE_WINDOW) { + event.type = PUGL_CLOSE; + } + } else if (xevent.xclient.message_type == atoms->PUGL_CLIENT_MSG) { + event.type = PUGL_CLIENT; + event.client.data1 = (uintptr_t)xevent.xclient.data.l[0]; + event.client.data2 = (uintptr_t)xevent.xclient.data.l[1]; + } + break; + case VisibilityNotify: + view->visible = xevent.xvisibility.state != VisibilityFullyObscured; + break; + case MapNotify: + event.type = PUGL_MAP; + break; + case UnmapNotify: + event.type = PUGL_UNMAP; + view->visible = false; + break; + case ConfigureNotify: + event.type = PUGL_CONFIGURE; + event.configure.x = xevent.xconfigure.x; + event.configure.y = xevent.xconfigure.y; + event.configure.width = xevent.xconfigure.width; + event.configure.height = xevent.xconfigure.height; + break; + case Expose: + event.type = PUGL_EXPOSE; + event.expose.x = xevent.xexpose.x; + event.expose.y = xevent.xexpose.y; + event.expose.width = xevent.xexpose.width; + event.expose.height = xevent.xexpose.height; + break; + case MotionNotify: + event.type = PUGL_MOTION; + 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); + 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 = (double)xevent.xbutton.time / 1e3; + event.scroll.x = xevent.xbutton.x; + event.scroll.y = xevent.xbutton.y; + event.scroll.xRoot = xevent.xbutton.x_root; + event.scroll.yRoot = xevent.xbutton.y_root; + event.scroll.state = translateModifiers(xevent.xbutton.state); + event.scroll.dx = 0.0; + event.scroll.dy = 0.0; + switch (xevent.xbutton.button) { + case 4: + event.scroll.dy = 1.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 + } + // fallthru + case ButtonRelease: + if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) { + event.button.type = ((xevent.type == ButtonPress) + ? PUGL_BUTTON_PRESS + : PUGL_BUTTON_RELEASE); + event.button.time = (double)xevent.xbutton.time / 1e3; + event.button.x = xevent.xbutton.x; + event.button.y = xevent.xbutton.y; + event.button.xRoot = xevent.xbutton.x_root; + event.button.yRoot = xevent.xbutton.y_root; + event.button.state = translateModifiers(xevent.xbutton.state); + event.button.button = xevent.xbutton.button; + } + break; + case KeyPress: + case KeyRelease: + event.type = ((xevent.type == KeyPress) + ? PUGL_KEY_PRESS + : PUGL_KEY_RELEASE); + event.key.time = (double)xevent.xkey.time / 1e3; + event.key.x = xevent.xkey.x; + event.key.y = xevent.xkey.y; + event.key.xRoot = xevent.xkey.x_root; + event.key.yRoot = xevent.xkey.y_root; + event.key.state = translateModifiers(xevent.xkey.state); + translateKey(view, &xevent, &event); + break; + case EnterNotify: + case LeaveNotify: + event.type = ((xevent.type == EnterNotify) + ? PUGL_POINTER_IN + : PUGL_POINTER_OUT); + 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; + event.crossing.yRoot = xevent.xcrossing.y_root; + event.crossing.state = translateModifiers(xevent.xcrossing.state); + event.crossing.mode = PUGL_CROSSING_NORMAL; + if (xevent.xcrossing.mode == NotifyGrab) { + event.crossing.mode = PUGL_CROSSING_GRAB; + } else if (xevent.xcrossing.mode == NotifyUngrab) { + event.crossing.mode = PUGL_CROSSING_UNGRAB; + } + break; + + case FocusIn: + case FocusOut: + event.type = (xevent.type == FocusIn) ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT; + 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: + break; + } + + return event; +} + +PuglStatus +puglGrabFocus(PuglView* view) +{ + XSetInputFocus( + view->impl->display, view->impl->win, RevertToNone, CurrentTime); + return PUGL_SUCCESS; +} + +bool +puglHasFocus(const PuglView* view) +{ + int revertTo = 0; + Window focusedWindow = 0; + XGetInputFocus(view->impl->display, &focusedWindow, &revertTo); + return focusedWindow == view->impl->win; +} + +PuglStatus +puglRequestAttention(PuglView* view) +{ + PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + XEvent event = {0}; + + event.type = ClientMessage; + event.xclient.window = impl->win; + event.xclient.format = 32; + event.xclient.message_type = atoms->NET_WM_STATE; + event.xclient.data.l[0] = WM_STATE_ADD; + event.xclient.data.l[1] = (long)atoms->NET_WM_STATE_DEMANDS_ATTENTION; + event.xclient.data.l[2] = 0; + event.xclient.data.l[3] = 1; + event.xclient.data.l[4] = 0; + + const Window root = RootWindow(impl->display, impl->screen); + XSendEvent(impl->display, + root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &event); + + return PUGL_SUCCESS; +} + +PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout) +{ +#ifdef HAVE_XSYNC + if (view->world->impl->syncSupported) { + XSyncValue value; + XSyncIntToValue(&value, (int)floor(timeout * 1000.0)); + + PuglWorldInternals* w = view->world->impl; + Display* const display = w->display; + const XSyncCounter counter = w->serverTimeCounter; + 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}; + + if (alarm != None) { + for (size_t i = 0; i < w->numTimers; ++i) { + if (w->timers[i].view == view && w->timers[i].id == id) { + // Replace existing timer + XSyncDestroyAlarm(w->display, w->timers[i].alarm); + w->timers[i] = timer; + return PUGL_SUCCESS; + } + } + + // Add new timer + const size_t size = ++w->numTimers * sizeof(timer); + w->timers = (PuglTimer*)realloc(w->timers, size); + w->timers[w->numTimers - 1] = timer; + return PUGL_SUCCESS; + } + } +#else + (void)view; + (void)id; + (void)timeout; +#endif + + return PUGL_FAILURE; +} + +PuglStatus +puglStopTimer(PuglView* view, uintptr_t id) +{ +#ifdef HAVE_XSYNC + PuglWorldInternals* w = view->world->impl; + + for (size_t i = 0; i < w->numTimers; ++i) { + if (w->timers[i].view == view && w->timers[i].id == id) { + XSyncDestroyAlarm(w->display, w->timers[i].alarm); + + if (i == w->numTimers - 1) { + memset(&w->timers[i], 0, sizeof(PuglTimer)); + } else { + memmove(w->timers + i, + w->timers + i + 1, + sizeof(PuglTimer) * (w->numTimers - i - 1)); + + memset(&w->timers[i], 0, sizeof(PuglTimer)); + } + + --w->numTimers; + return PUGL_SUCCESS; + } + } +#else + (void)view; + (void)id; +#endif + + return PUGL_FAILURE; +} + +static XEvent +puglEventToX(PuglView* view, const PuglEvent* event) +{ + XEvent xev = {0}; + xev.xany.send_event = True; + + switch (event->type) { + case PUGL_EXPOSE: { + const double x = floor(event->expose.x); + const double y = floor(event->expose.y); + const double w = ceil(event->expose.x + event->expose.width) - x; + const double h = ceil(event->expose.y + event->expose.height) - y; + + xev.xexpose.type = Expose; + xev.xexpose.serial = 0; + xev.xexpose.display = view->impl->display; + xev.xexpose.window = view->impl->win; + xev.xexpose.x = (int)x; + xev.xexpose.y = (int)y; + xev.xexpose.width = (int)w; + xev.xexpose.height = (int)h; + break; + } + + case PUGL_CLIENT: + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.display = view->impl->display; + xev.xclient.window = view->impl->win; + xev.xclient.message_type = view->world->impl->atoms.PUGL_CLIENT_MSG; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (long)event->client.data1; + xev.xclient.data.l[1] = (long)event->client.data2; + break; + + default: + break; + } + + return xev; +} + +PuglStatus +puglSendEvent(PuglView* view, const PuglEvent* event) +{ + XEvent xev = puglEventToX(view, event); + + if (xev.type) { + if (XSendEvent(view->impl->display, view->impl->win, False, 0, &xev)) { + return PUGL_SUCCESS; + } else { + return PUGL_UNKNOWN_ERROR; + } + } + + return PUGL_UNSUPPORTED_TYPE; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglWaitForEvent(PuglView* view) +{ + XEvent xevent; + XPeekEvent(view->impl->display, &xevent); + return PUGL_SUCCESS; +} +#endif + +static void +mergeExposeEvents(PuglEvent* dst, const PuglEvent* src) +{ + if (!dst->type) { + *dst = *src; + } else { + const double max_x = MAX(dst->expose.x + dst->expose.width, + src->expose.x + src->expose.width); + const double max_y = MAX(dst->expose.y + dst->expose.height, + src->expose.y + src->expose.height); + + dst->expose.x = MIN(dst->expose.x, src->expose.x); + 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; + } +} + +static void +handleSelectionNotify(const PuglWorld* world, PuglView* view) +{ + uint8_t* str = NULL; + Atom type = 0; + int fmt = 0; + unsigned long len = 0; + unsigned long left = 0; + + XGetWindowProperty(world->impl->display, + view->impl->win, + XA_PRIMARY, + 0, + 0x1FFFFFFF, + False, + AnyPropertyType, + &type, + &fmt, + &len, + &left, + &str); + + if (str && fmt == 8 && type == world->impl->atoms.UTF8_STRING && + left == 0) { + puglSetBlob(&view->clipboard, str, len); + } + + XFree(str); +} + +static void +handleSelectionRequest(const PuglWorld* world, + PuglView* view, + const XSelectionRequestEvent* request) +{ + XSelectionEvent note = {SelectionNotify, + request->serial, + False, + world->impl->display, + request->requestor, + request->selection, + request->target, + None, + request->time}; + + const char* type = NULL; + size_t len = 0; + const void* data = puglGetInternalClipboard(view, &type, &len); + if (data && request->selection == world->impl->atoms.CLIPBOARD && + request->target == world->impl->atoms.UTF8_STRING) { + note.property = request->property; + XChangeProperty(world->impl->display, + note.requestor, + note.property, + note.target, + 8, + PropModeReplace, + (const uint8_t*)data, + (int)len); + } else { + note.property = None; + } + + XSendEvent(world->impl->display, note.requestor, True, 0, (XEvent*)¬e); +} + +/// Flush pending configure and expose events for all views +static void +flushExposures(PuglWorld* world) +{ + for (size_t i = 0; i < world->numViews; ++i) { + PuglView* const view = world->views[i]; + + if (view->visible) { + puglDispatchSimpleEvent(view, PUGL_UPDATE); + } + + const PuglEvent configure = view->impl->pendingConfigure; + const PuglEvent expose = view->impl->pendingExpose; + + view->impl->pendingConfigure.type = PUGL_NOTHING; + view->impl->pendingExpose.type = PUGL_NOTHING; + + if (configure.type || expose.type) { + view->backend->enter(view, expose.type ? &expose.expose : NULL); + puglDispatchEventInContext(view, &configure); + puglDispatchEventInContext(view, &expose); + view->backend->leave(view, expose.type ? &expose.expose : NULL); + } + } +} + +static bool +handleTimerEvent(PuglWorld* world, XEvent xevent) +{ +#ifdef HAVE_XSYNC + if (xevent.type == world->impl->syncEventBase + XSyncAlarmNotify) { + XSyncAlarmNotifyEvent* notify = ((XSyncAlarmNotifyEvent*)&xevent); + + for (size_t i = 0; i < world->impl->numTimers; ++i) { + if (world->impl->timers[i].alarm == notify->alarm) { + PuglEvent event = {{PUGL_TIMER, 0}}; + event.timer.id = world->impl->timers[i].id; + puglDispatchEvent(world->impl->timers[i].view, + (const PuglEvent*)&event); + } + } + + return true; + } +#else + (void)world; + (void)xevent; +#endif + + return false; +} + +static PuglStatus +puglDispatchX11Events(PuglWorld* world) +{ + const PuglX11Atoms* const atoms = &world->impl->atoms; + + // Flush output to the server once at the start + Display* display = world->impl->display; + XFlush(display); + + // Process all queued events (without further flushing) + while (XEventsQueued(display, QueuedAfterReading) > 0) { + XEvent xevent; + XNextEvent(display, &xevent); + + if (handleTimerEvent(world, xevent)) { + continue; + } + + PuglView* view = puglFindView(world, xevent.xany.window); + if (!view) { + continue; + } + + // Handle special events + PuglInternals* const impl = view->impl; + if (xevent.type == KeyRelease && view->hints[PUGL_IGNORE_KEY_REPEAT]) { + XEvent next; + if (XCheckTypedWindowEvent(display, impl->win, KeyPress, &next) && + next.type == KeyPress && + next.xkey.time == xevent.xkey.time && + next.xkey.keycode == xevent.xkey.keycode) { + continue; + } + } else if (xevent.type == FocusIn) { + XSetICFocus(impl->xic); + } else if (xevent.type == FocusOut) { + XUnsetICFocus(impl->xic); + } else if (xevent.type == SelectionClear) { + puglSetBlob(&view->clipboard, NULL, 0); + } else if (xevent.type == SelectionNotify && + xevent.xselection.selection == atoms->CLIPBOARD && + xevent.xselection.target == atoms->UTF8_STRING && + xevent.xselection.property == XA_PRIMARY) { + handleSelectionNotify(world, view); + } else if (xevent.type == SelectionRequest) { + handleSelectionRequest(world, view, &xevent.xselectionrequest); + } + + // Translate X11 event to Pugl event + const PuglEvent event = translateEvent(view, xevent); + + if (event.type == PUGL_EXPOSE) { + // Expand expose event to be dispatched after loop + mergeExposeEvents(&view->impl->pendingExpose, &event); + } else if (event.type == PUGL_CONFIGURE) { + // Expand configure event to be dispatched after loop + view->impl->pendingConfigure = event; + view->frame.x = event.configure.x; + view->frame.y = event.configure.y; + view->frame.width = event.configure.width; + view->frame.height = event.configure.height; + } else if (event.type == PUGL_MAP && view->parent) { + XWindowAttributes attrs; + XGetWindowAttributes(view->impl->display, view->impl->win, &attrs); + + const PuglEventConfigure configure = { + PUGL_CONFIGURE, 0, attrs.x, attrs.y, attrs.width, attrs.height}; + + puglDispatchEvent(view, (const PuglEvent*)&configure); + puglDispatchEvent(view, &event); + } else { + // Dispatch event to application immediately + puglDispatchEvent(view, &event); + } + } + + return PUGL_SUCCESS; +} + +#ifndef PUGL_DISABLE_DEPRECATED +PuglStatus +puglProcessEvents(PuglView* view) +{ + return puglUpdate(view->world, 0.0); +} +#endif + +PuglStatus +puglUpdate(PuglWorld* world, double timeout) +{ + const double startTime = puglGetTime(world); + PuglStatus st = PUGL_SUCCESS; + + world->impl->dispatchingEvents = true; + + if (timeout < 0.0) { + st = puglPollX11Socket(world, timeout); + st = st ? st : puglDispatchX11Events(world); + } else if (timeout <= 0.001) { + st = puglDispatchX11Events(world); + } else { + const double endTime = startTime + timeout - 0.001; + for (double t = startTime; t < endTime; t = puglGetTime(world)) { + if ((st = puglPollX11Socket(world, endTime - t)) || + (st = puglDispatchX11Events(world))) { + break; + } + } + } + + flushExposures(world); + + world->impl->dispatchingEvents = false; + + return st; +} + +double +puglGetTime(const PuglWorld* world) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0) - + world->startTime; +} + +PuglStatus +puglPostRedisplay(PuglView* view) +{ + const PuglRect rect = { 0, 0, view->frame.width, view->frame.height }; + + return puglPostRedisplayRect(view, rect); +} + +PuglStatus +puglPostRedisplayRect(PuglView* view, PuglRect rect) +{ + const PuglEventExpose event = { + PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height + }; + + if (view->world->impl->dispatchingEvents) { + // Currently dispatching events, add/expand expose for the loop end + mergeExposeEvents(&view->impl->pendingExpose, (const PuglEvent*)&event); + } else if (view->visible) { + // Not dispatching events, send an X expose so we wake up next time + return puglSendEvent(view, (const PuglEvent*)&event); + } + + return PUGL_SUCCESS; +} + +PuglNativeView +puglGetNativeWindow(PuglView* view) +{ + return (PuglNativeView)view->impl->win; +} + +PuglStatus +puglSetWindowTitle(PuglView* view, const char* title) +{ + Display* display = view->world->impl->display; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + puglSetString(&view->title, title); + + if (view->impl->win) { + XStoreName(display, view->impl->win, title); + XChangeProperty(display, view->impl->win, atoms->NET_WM_NAME, + atoms->UTF8_STRING, 8, PropModeReplace, + (const uint8_t*)title, (int)strlen(title)); + } + + return PUGL_SUCCESS; +} + +PuglStatus +puglSetFrame(PuglView* view, const PuglRect frame) +{ + if (view->impl->win) { + if (!XMoveResizeWindow(view->world->impl->display, + view->impl->win, + (int)frame.x, + (int)frame.y, + (unsigned)frame.width, + (unsigned)frame.height)) { + return PUGL_UNKNOWN_ERROR; + } + } + + view->frame = frame; + return PUGL_SUCCESS; +} + +PuglStatus +puglSetDefaultSize(PuglView* const view, const int width, const int height) +{ + 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); +} + +PuglStatus +puglSetMaxSize(PuglView* const view, const int width, const int height) +{ + view->minWidth = width; + view->minHeight = height; + return updateSizeHints(view); +} + +PuglStatus +puglSetAspectRatio(PuglView* const view, + const int minX, + const int minY, + const int maxX, + const int maxY) +{ + view->minAspectX = minX; + view->minAspectY = minY; + view->maxAspectX = maxX; + view->maxAspectY = maxY; + + return updateSizeHints(view); +} + +PuglStatus +puglSetTransientFor(PuglView* view, PuglNativeView parent) +{ + Display* display = view->world->impl->display; + + view->transientParent = parent; + + if (view->impl->win) { + XSetTransientForHint(display, view->impl->win, + (Window)view->transientParent); + } + + return PUGL_SUCCESS; +} + +const void* +puglGetClipboard(PuglView* const view, + const char** const type, + size_t* const len) +{ + PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + const Window owner = XGetSelectionOwner(impl->display, atoms->CLIPBOARD); + if (owner != None && owner != impl->win) { + // Clear internal selection + puglSetBlob(&view->clipboard, NULL, 0); + + // Request selection from the owner + XConvertSelection(impl->display, + atoms->CLIPBOARD, + atoms->UTF8_STRING, + XA_PRIMARY, + impl->win, + CurrentTime); + + // Run event loop until data is received + while (!view->clipboard.data) { + puglUpdate(view->world, -1.0); + } + } + + return puglGetInternalClipboard(view, type, len); +} + +PuglStatus +puglSetClipboard(PuglView* const view, + const char* const type, + const void* const data, + const size_t len) +{ + PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + PuglStatus st = puglSetInternalClipboard(view, type, data, len); + if (st) { + return st; + } + + XSetSelectionOwner(impl->display, atoms->CLIPBOARD, impl->win, CurrentTime); + 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 +} diff --git a/include/pugl/detail/x11.h b/include/pugl/detail/x11.h new file mode 100644 index 0000000..b5ca75c --- /dev/null +++ b/include/pugl/detail/x11.h @@ -0,0 +1,76 @@ +/* + 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 x11.h + @brief Shared definitions for X11 implementation. +*/ + +#include "pugl/detail/types.h" +#include "pugl/pugl.h" + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +typedef struct { + Atom CLIPBOARD; + Atom UTF8_STRING; + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + Atom PUGL_CLIENT_MSG; + Atom NET_WM_NAME; + Atom NET_WM_STATE; + Atom NET_WM_STATE_DEMANDS_ATTENTION; +} PuglX11Atoms; + +typedef struct { + XID alarm; + PuglView* view; + uintptr_t id; +} PuglTimer; + +struct PuglWorldInternalsImpl { + Display* display; + PuglX11Atoms atoms; + XIM xim; + PuglTimer* timers; + size_t numTimers; + XID serverTimeCounter; + int syncEventBase; + bool syncSupported; + bool dispatchingEvents; +}; + +struct PuglInternalsImpl { + Display* display; + XVisualInfo* vi; + Window win; + XIC xic; + PuglSurface* surface; + PuglEvent pendingConfigure; + PuglEvent pendingExpose; + int screen; +#ifdef HAVE_XCURSOR + unsigned cursorShape; +#endif +}; + +PuglStatus puglX11StubConfigure(PuglView* view); diff --git a/include/pugl/detail/x11_cairo.c b/include/pugl/detail/x11_cairo.c new file mode 100644 index 0000000..0112c4e --- /dev/null +++ b/include/pugl/detail/x11_cairo.c @@ -0,0 +1,170 @@ +/* + 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 x11_cairo.c + @brief Cairo graphics backend for X11. +*/ + +#include "pugl/detail/types.h" +#include "pugl/detail/x11.h" +#include "pugl/pugl.h" +#include "pugl/pugl_cairo.h" + +#include <X11/Xutil.h> +#include <cairo-xlib.h> +#include <cairo.h> + +#include <stdlib.h> + +typedef struct { + cairo_surface_t* back; + cairo_surface_t* front; + cairo_t* cr; +} PuglX11CairoSurface; + +static void +puglX11CairoClose(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + cairo_surface_destroy(surface->front); + cairo_surface_destroy(surface->back); + surface->front = surface->back = NULL; +} + +static PuglStatus +puglX11CairoOpen(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + surface->back = cairo_xlib_surface_create(impl->display, + impl->win, + impl->vi->visual, + (int)view->frame.width, + (int)view->frame.height); + + surface->front = cairo_surface_create_similar( + surface->back, + cairo_surface_get_content(surface->back), + (int)view->frame.width, + (int)view->frame.height); + + if (cairo_surface_status(surface->back) || + cairo_surface_status(surface->front)) { + puglX11CairoClose(view); + return PUGL_CREATE_CONTEXT_FAILED; + } + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11CairoCreate(PuglView* view) +{ + PuglInternals* const impl = view->impl; + + impl->surface = (cairo_surface_t*)calloc(1, sizeof(PuglX11CairoSurface)); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11CairoDestroy(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + puglX11CairoClose(view); + free(surface); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11CairoEnter(PuglView* view, const PuglEventExpose* expose) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + PuglStatus st = PUGL_SUCCESS; + + if (expose && !(st = puglX11CairoOpen(view))) { + surface->cr = cairo_create(surface->front); + + if (cairo_status(surface->cr)) { + st = PUGL_CREATE_CONTEXT_FAILED; + } + } + + return st; +} + +static PuglStatus +puglX11CairoLeave(PuglView* view, const PuglEventExpose* expose) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + if (expose) { + // Destroy front context and create a new one for drawing to the back + cairo_destroy(surface->cr); + surface->cr = cairo_create(surface->back); + + // Clip to expose region + cairo_rectangle(surface->cr, + expose->x, + expose->y, + expose->width, + expose->height); + cairo_clip(surface->cr); + + // Paint front onto back + cairo_set_source_surface(surface->cr, surface->front, 0, 0); + cairo_paint(surface->cr); + + // Flush to X and close everything + cairo_destroy(surface->cr); + cairo_surface_flush(surface->back); + puglX11CairoClose(view); + surface->cr = NULL; + } + + return PUGL_SUCCESS; +} + +static void* +puglX11CairoGetContext(PuglView* view) +{ + PuglInternals* const impl = view->impl; + PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; + + return surface->cr; +} + +const PuglBackend* +puglCairoBackend(void) +{ + static const PuglBackend backend = {puglX11StubConfigure, + puglX11CairoCreate, + puglX11CairoDestroy, + puglX11CairoEnter, + puglX11CairoLeave, + puglX11CairoGetContext}; + + return &backend; +} diff --git a/include/pugl/detail/x11_gl.c b/include/pugl/detail/x11_gl.c new file mode 100644 index 0000000..b756355 --- /dev/null +++ b/include/pugl/detail/x11_gl.c @@ -0,0 +1,238 @@ +/* + 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 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 <GL/glx.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +typedef struct { + GLXFBConfig fb_config; + GLXContext ctx; +} PuglX11GlSurface; + +static int +puglX11GlHintValue(const int value) +{ + return value == PUGL_DONT_CARE ? (int)GLX_DONT_CARE : value; +} + +static int +puglX11GlGetAttrib(Display* const display, + GLXFBConfig fb_config, + const int attrib) +{ + int value = 0; + glXGetFBConfigAttrib(display, fb_config, attrib, &value); + return value; +} + +static PuglStatus +puglX11GlConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + const int screen = impl->screen; + Display* const display = impl->display; + + PuglX11GlSurface* const surface = + (PuglX11GlSurface*)calloc(1, sizeof(PuglX11GlSurface)); + impl->surface = surface; + + const int attrs[] = { + GLX_X_RENDERABLE, True, + GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_SAMPLES, puglX11GlHintValue(view->hints[PUGL_SAMPLES]), + GLX_RED_SIZE, puglX11GlHintValue(view->hints[PUGL_RED_BITS]), + GLX_GREEN_SIZE, puglX11GlHintValue(view->hints[PUGL_GREEN_BITS]), + GLX_BLUE_SIZE, puglX11GlHintValue(view->hints[PUGL_BLUE_BITS]), + GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints[PUGL_ALPHA_BITS]), + GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints[PUGL_DEPTH_BITS]), + GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints[PUGL_STENCIL_BITS]), + GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]), + None + }; + + int n_fbc = 0; + GLXFBConfig* fbc = glXChooseFBConfig(display, screen, attrs, &n_fbc); + if (n_fbc <= 0) { + return PUGL_CREATE_CONTEXT_FAILED; + } + + surface->fb_config = fbc[0]; + impl->vi = glXGetVisualFromFBConfig(impl->display, fbc[0]); + + view->hints[PUGL_RED_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_RED_SIZE); + view->hints[PUGL_GREEN_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_GREEN_SIZE); + view->hints[PUGL_BLUE_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_BLUE_SIZE); + view->hints[PUGL_ALPHA_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_ALPHA_SIZE); + view->hints[PUGL_DEPTH_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_DEPTH_SIZE); + view->hints[PUGL_STENCIL_BITS] = puglX11GlGetAttrib( + display, fbc[0], GLX_STENCIL_SIZE); + view->hints[PUGL_SAMPLES] = puglX11GlGetAttrib( + display, fbc[0], GLX_SAMPLES); + view->hints[PUGL_DOUBLE_BUFFER] = puglX11GlGetAttrib( + display, fbc[0], GLX_DOUBLEBUFFER); + + char msg[256]; + + snprintf( + msg, + sizeof(msg), + "Using visual 0x%lX: R=%d G=%d B=%d A=%d D=%d DOUBLE=%d SAMPLES=%d\n", + impl->vi->visualid, + puglX11GlGetAttrib(display, fbc[0], GLX_RED_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_GREEN_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_BLUE_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_ALPHA_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_DEPTH_SIZE), + puglX11GlGetAttrib(display, fbc[0], GLX_DOUBLEBUFFER), + puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLES)); + + view->world->logFunc(view->world, PUGL_LOG_LEVEL_INFO, msg); + + XFree(fbc); + + 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) +{ + if (expose && view->hints[PUGL_DOUBLE_BUFFER]) { + 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; + PuglX11GlSurface* const surface = (PuglX11GlSurface*)impl->surface; + Display* const display = impl->display; + GLXFBConfig fb_config = surface->fb_config; + + const int ctx_attrs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MAJOR], + GLX_CONTEXT_MINOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MINOR], + GLX_CONTEXT_FLAGS_ARB, (view->hints[PUGL_USE_DEBUG_CONTEXT] + ? GLX_CONTEXT_DEBUG_BIT_ARB + : 0), + GLX_CONTEXT_PROFILE_MASK_ARB, (view->hints[PUGL_USE_COMPAT_PROFILE] + ? GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB + : GLX_CONTEXT_CORE_PROFILE_BIT_ARB), + 0}; + + PFNGLXCREATECONTEXTATTRIBSARBPROC create_context = + (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress( + (const uint8_t*)"glXCreateContextAttribsARB"); + + PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = + (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress( + (const uint8_t*)"glXSwapIntervalEXT"); + + surface->ctx = create_context(display, fb_config, 0, True, ctx_attrs); + if (!surface->ctx) { + surface->ctx = + glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True); + } + + if (!surface->ctx) { + return PUGL_CREATE_CONTEXT_FAILED; + } + + 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, + impl->vi, + GLX_DOUBLEBUFFER, + &view->hints[PUGL_DOUBLE_BUFFER]); + + glXQueryDrawable(display, + impl->win, + GLX_SWAP_INTERVAL_EXT, + (unsigned int*)&view->hints[PUGL_SWAP_INTERVAL]); + + return PUGL_SUCCESS; +} + +static PuglStatus +puglX11GlDestroy(PuglView* view) +{ + PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface; + if (surface) { + glXDestroyContext(view->impl->display, surface->ctx); + free(surface); + view->impl->surface = NULL; + } + return PUGL_SUCCESS; +} + +PuglGlFunc +puglGetProcAddress(const char* name) +{ + return glXGetProcAddress((const uint8_t*)name); +} + +const PuglBackend* puglGlBackend(void) +{ + static const PuglBackend backend = {puglX11GlConfigure, + puglX11GlCreate, + puglX11GlDestroy, + puglX11GlEnter, + puglX11GlLeave, + puglStubGetContext}; + + return &backend; +} diff --git a/include/pugl/detail/x11_stub.c b/include/pugl/detail/x11_stub.c new file mode 100644 index 0000000..8efd68f --- /dev/null +++ b/include/pugl/detail/x11_stub.c @@ -0,0 +1,54 @@ +/* + 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. +*/ + +#include "pugl/pugl_stub.h" + +#include "pugl/detail/stub.h" +#include "pugl/detail/types.h" +#include "pugl/detail/x11.h" + +PuglStatus +puglX11StubConfigure(PuglView* view) +{ + PuglInternals* const impl = view->impl; + XVisualInfo pat = {0}; + int n = 0; + + pat.screen = impl->screen; + impl->vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n); + + view->hints[PUGL_RED_BITS] = impl->vi->bits_per_rgb; + view->hints[PUGL_GREEN_BITS] = impl->vi->bits_per_rgb; + view->hints[PUGL_BLUE_BITS] = impl->vi->bits_per_rgb; + view->hints[PUGL_ALPHA_BITS] = 0; + + return PUGL_SUCCESS; +} + +const PuglBackend* +puglStubBackend(void) +{ + static const PuglBackend backend = { + puglX11StubConfigure, + puglStubCreate, + puglStubDestroy, + puglStubEnter, + puglStubLeave, + puglStubGetContext, + }; + + return &backend; +} diff --git a/include/pugl/gl.h b/include/pugl/gl.h new file mode 100644 index 0000000..9f7a741 --- /dev/null +++ b/include/pugl/gl.h @@ -0,0 +1,36 @@ +/* + 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 gl.h + @brief Portable header wrapper for gl.h. + + Unfortunately, GL includes vary across platforms so this header allows for + pure portable programs. +*/ + +// IWYU pragma: begin_exports + +#ifdef __APPLE__ +# include "OpenGL/gl.h" +#else +# ifdef _WIN32 +# include <windows.h> /* Broken Windows GL headers require this */ +# endif +# include "GL/gl.h" +#endif + +// IWYU pragma: end_exports diff --git a/include/pugl/glu.h b/include/pugl/glu.h new file mode 100644 index 0000000..423a917 --- /dev/null +++ b/include/pugl/glu.h @@ -0,0 +1,36 @@ +/* + 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 glu.h + @brief Portable header wrapper for glu.h. + + Unfortunately, GL includes vary across platforms so this header allows for + pure portable programs. +*/ + +// IWYU pragma: begin_exports + +#ifdef __APPLE__ +# include "OpenGL/glu.h" +#else +# ifdef _WIN32 +# include <windows.h> /* Broken Windows GL headers require this */ +# endif +# include "GL/glu.h" +#endif + +// IWYU pragma: end_exports diff --git a/include/pugl/pugl.h b/include/pugl/pugl.h new file mode 100644 index 0000000..ad24c0d --- /dev/null +++ b/include/pugl/pugl.h @@ -0,0 +1,1624 @@ +/* + 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.h + @brief Pugl API. +*/ + +#ifndef PUGL_PUGL_H +#define PUGL_PUGL_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#ifdef PUGL_SHARED +# ifdef _WIN32 +# define PUGL_LIB_IMPORT __declspec(dllimport) +# define PUGL_LIB_EXPORT __declspec(dllexport) +# else +# define PUGL_LIB_IMPORT __attribute__((visibility("default"))) +# define PUGL_LIB_EXPORT __attribute__((visibility("default"))) +# endif +# ifdef PUGL_INTERNAL +# define PUGL_API PUGL_LIB_EXPORT +# else +# define PUGL_API PUGL_LIB_IMPORT +# endif +#else +# define PUGL_API +#endif + +#ifndef PUGL_DISABLE_DEPRECATED +# if defined(__clang__) +# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("", rep))) +# elif defined(__GNUC__) +# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("Use " rep))) +# else +# define PUGL_DEPRECATED_BY(rep) +# 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 } +#else +# define PUGL_BEGIN_DECLS +# define PUGL_END_DECLS +#endif + +PUGL_BEGIN_DECLS + +/** + @defgroup pugl Pugl + A minimal portable API for embeddable GUIs. + @{ + + @defgroup pugl_c C API + Public C API. + @{ +*/ + +/** + A rectangle. + + This is used to describe things like view position and size. Pugl generally + uses coordinates where the top left corner is 0,0. +*/ +typedef struct { + double x; + double y; + double width; + double height; +} PuglRect; + +/** + @defgroup events Events + + Event definitions. + + All updates to the view happen via events, which are dispatched to 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. + + @{ +*/ + +/** + Keyboard modifier flags. +*/ +typedef enum { + PUGL_MOD_SHIFT = 1u << 0u, ///< Shift key + PUGL_MOD_CTRL = 1u << 1u, ///< Control key + PUGL_MOD_ALT = 1u << 2u, ///< Alt/Option key + PUGL_MOD_SUPER = 1u << 3u ///< Mod4/Command/Windows key +} PuglMod; + +/** + Bitwise OR of #PuglMod values. +*/ +typedef uint32_t PuglMods; + +/** + Keyboard key codepoints. + + All keys are identified by a Unicode code point in PuglEventKey::key. This + enumeration defines constants for special keys that do not have a standard + code point, and some convenience constants for control characters. Note + that all keys are handled in the same way, this enumeration is just for + convenience when writing hard-coded key bindings. + + Keys that do not have a standard code point use values in the Private Use + Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`). Applications + must take care to not interpret these values beyond key detection, the + mapping used here is arbitrary and specific to Pugl. +*/ +typedef enum { + // ASCII control codes + PUGL_KEY_BACKSPACE = 0x08, + PUGL_KEY_ESCAPE = 0x1B, + PUGL_KEY_DELETE = 0x7F, + + // Unicode Private Use Area + PUGL_KEY_F1 = 0xE000, + PUGL_KEY_F2, + PUGL_KEY_F3, + PUGL_KEY_F4, + PUGL_KEY_F5, + PUGL_KEY_F6, + PUGL_KEY_F7, + PUGL_KEY_F8, + PUGL_KEY_F9, + PUGL_KEY_F10, + PUGL_KEY_F11, + PUGL_KEY_F12, + PUGL_KEY_LEFT, + PUGL_KEY_UP, + PUGL_KEY_RIGHT, + PUGL_KEY_DOWN, + PUGL_KEY_PAGE_UP, + PUGL_KEY_PAGE_DOWN, + PUGL_KEY_HOME, + PUGL_KEY_END, + PUGL_KEY_INSERT, + PUGL_KEY_SHIFT, + PUGL_KEY_SHIFT_L = PUGL_KEY_SHIFT, + PUGL_KEY_SHIFT_R, + PUGL_KEY_CTRL, + PUGL_KEY_CTRL_L = PUGL_KEY_CTRL, + PUGL_KEY_CTRL_R, + PUGL_KEY_ALT, + PUGL_KEY_ALT_L = PUGL_KEY_ALT, + PUGL_KEY_ALT_R, + PUGL_KEY_SUPER, + PUGL_KEY_SUPER_L = PUGL_KEY_SUPER, + PUGL_KEY_SUPER_R, + PUGL_KEY_MENU, + PUGL_KEY_CAPS_LOCK, + PUGL_KEY_SCROLL_LOCK, + PUGL_KEY_NUM_LOCK, + PUGL_KEY_PRINT_SCREEN, + PUGL_KEY_PAUSE +} PuglKey; + +/** + The type of a PuglEvent. +*/ +typedef enum { + PUGL_NOTHING, ///< No event + PUGL_CREATE, ///< View created, a #PuglEventCreate + PUGL_DESTROY, ///< View destroyed, a #PuglEventDestroy + PUGL_CONFIGURE, ///< View moved/resized, a #PuglEventConfigure + PUGL_MAP, ///< View made visible, a #PuglEventMap + PUGL_UNMAP, ///< View made invisible, a #PuglEventUnmap + PUGL_UPDATE, ///< View ready to draw, a #PuglEventUpdate + PUGL_EXPOSE, ///< View must be drawn, a #PuglEventExpose + PUGL_CLOSE, ///< View will be closed, a #PuglEventClose + PUGL_FOCUS_IN, ///< Keyboard focus entered view, a #PuglEventFocus + PUGL_FOCUS_OUT, ///< Keyboard focus left view, a #PuglEventFocus + PUGL_KEY_PRESS, ///< Key pressed, a #PuglEventKey + PUGL_KEY_RELEASE, ///< Key released, a #PuglEventKey + PUGL_TEXT, ///< Character entered, a #PuglEventText + PUGL_POINTER_IN, ///< Pointer entered view, a #PuglEventCrossing + PUGL_POINTER_OUT, ///< Pointer left view, a #PuglEventCrossing + PUGL_BUTTON_PRESS, ///< Mouse button pressed, a #PuglEventButton + PUGL_BUTTON_RELEASE, ///< Mouse button released, a #PuglEventButton + PUGL_MOTION, ///< Pointer moved, a #PuglEventMotion + PUGL_SCROLL, ///< Scrolled, a #PuglEventScroll + PUGL_CLIENT, ///< Custom client message, a #PuglEventClient + PUGL_TIMER, ///< Timer triggered, a #PuglEventTimer + +#ifndef PUGL_DISABLE_DEPRECATED + PUGL_ENTER_NOTIFY PUGL_DEPRECATED_BY("PUGL_POINTER_IN") = PUGL_POINTER_IN, + PUGL_LEAVE_NOTIFY PUGL_DEPRECATED_BY("PUGL_POINTER_OUT") = PUGL_POINTER_OUT, + PUGL_MOTION_NOTIFY PUGL_DEPRECATED_BY("PUGL_MOTION") = PUGL_MOTION, +#endif + +} PuglEventType; + +/** + Common flags for all event types. +*/ +typedef enum { + PUGL_IS_SEND_EVENT = 1, ///< Event is synthetic + PUGL_IS_HINT = 2 ///< Event is a hint (not direct user input) +} PuglEventFlag; + +/** + Bitwise OR of #PuglEventFlag values. +*/ +typedef uint32_t PuglEventFlags; + +/** + Reason for a PuglEventCrossing. +*/ +typedef enum { + PUGL_CROSSING_NORMAL, ///< Crossing due to pointer motion + PUGL_CROSSING_GRAB, ///< Crossing due to a grab + PUGL_CROSSING_UNGRAB ///< Crossing due to a grab release +} PuglCrossingMode; + +/** + 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 { + PuglEventType type; ///< Event type + PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values +} PuglEventAny; + +/** + View create event. + + This event is sent when a view is realized before it is first displayed, + with the graphics context entered. This is typically used for setting up + the graphics system, for example by loading OpenGL extensions. + + This event type has no extra fields. +*/ +typedef PuglEventAny PuglEventCreate; + +/** + View destroy event. + + This event is the counterpart to #PuglEventCreate, and it is sent when the + view is being destroyed. This is typically used for tearing down the + graphics system, or otherwise freeing any resources allocated when the + create event was handled. + + This is the last event sent to any view, and immediately after it is + processed, the view is destroyed and may no longer be used. + + This event type has no extra fields. +*/ +typedef PuglEventAny PuglEventDestroy; + +/** + View resize or move event. + + A configure event is sent whenever the view is resized or moved. When a + configure event is received, the graphics context is active but not set up + for drawing. For example, it is valid to adjust the OpenGL viewport or + otherwise configure the context, but not to draw anything. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_CONFIGURE + PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values + double x; ///< New parent-relative X coordinate + double y; ///< New parent-relative Y coordinate + double width; ///< New width + double height; ///< New height +} PuglEventConfigure; + +/** + View show event. + + This event is sent when a view is mapped to the screen and made visible. + + This event type has no extra fields. +*/ +typedef PuglEventAny PuglEventMap; + +/** + View hide event. + + This event is sent when a view is unmapped from the screen and made + invisible. + + This event type has no extra fields. +*/ +typedef PuglEventAny PuglEventUnmap; + +/** + View update event. + + This event is sent to every view near the end of a main loop iteration when + any pending exposures are about to be redrawn. It is typically used to mark + regions to expose with puglPostRedisplay() or puglPostRedisplayRect(). For + example, to continuously animate, a view calls puglPostRedisplay() when an + update event is received, and it will then shortly receive an expose event. +*/ +typedef PuglEventAny PuglEventUpdate; + +/** + Expose event for when a region must be redrawn. + + When an expose event is received, the graphics context is active, and the + view must draw the entire specified region. The contents of the region are + undefined, there is no preservation of anything drawn previously. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_EXPOSE + PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values + double x; ///< View-relative X coordinate + double y; ///< View-relative Y coordinate + double width; ///< Width of exposed region + double height; ///< Height of exposed region +} PuglEventExpose; + +/** + View close event. + + This event is sent when the view is to be closed, for example when the user + clicks the close button. + + This event type has no extra fields. +*/ +typedef PuglEventAny PuglEventClose; + +/** + Keyboard focus event. + + This event is sent whenever the view gains or loses the keyboard focus. The + 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 + PuglCrossingMode mode; ///< Reason for focus change +} PuglEventFocus; + +/** + Key press or release event. + + This event represents low-level key presses and releases. This can be used + for "direct" keyboard handing like key bindings, but must not be interpreted + 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` + 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') + regardless of whether shift or control are being held. + + 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. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_KEY_PRESS or #PUGL_KEY_RELEASE + 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 + uint32_t keycode; ///< Raw key code + uint32_t key; ///< Unshifted Unicode character code, or 0 +} PuglEventKey; + +/** + Character input event. + + This event represents text input, usually as the result of a key press. The + text is given both as a Unicode character code and a UTF-8 string. + + Note that this event is generated by the platform's input system, so there + is not necessarily a direct correspondence between text events and physical + key presses. For example, with some input methods a sequence of several key + presses will generate a single character. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_TEXT + 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 + uint32_t keycode; ///< Raw key code + uint32_t character; ///< Unicode character code + char string[8]; ///< UTF-8 string +} PuglEventText; + +/** + Pointer enter or leave event. + + 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. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_POINTER_IN or #PUGL_POINTER_OUT + 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 + PuglCrossingMode mode; ///< Reason for crossing +} PuglEventCrossing; + +/** + Button press or release event. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_BUTTON_PRESS or #PUGL_BUTTON_RELEASE + 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 + uint32_t button; ///< Button number starting from 1 +} PuglEventButton; + +/** + Pointer motion event. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_MOTION + 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 +} PuglEventMotion; + +/** + Scroll event. + + The scroll distance is expressed in "lines", an arbitrary unit that + corresponds to a single tick of a detented mouse wheel. For example, `dy` = + 1.0 scrolls 1 line up. Some systems and devices support finer resolution + and/or higher values for fast scrolls, so programs should handle any value + gracefully. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_SCROLL + 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; + +/** + Custom client message event. + + This can be used to send a custom message to a view, which is delivered via + the window system and processed in the event loop as usual. Among other + things, this makes it possible to wake up the event loop for any reason. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_CLIENT + PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values + uintptr_t data1; ///< Client-specific data + uintptr_t data2; ///< Client-specific data +} PuglEventClient; + +/** + Timer event. + + 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 + distinguishes this timer from others. It should always be checked in the + event handler, even in applications that register only one timer. +*/ +typedef struct { + PuglEventType type; ///< #PUGL_TIMER + PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values + uintptr_t id; ///< Timer ID +} PuglEventTimer; + +/** + View event. + + 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. + + The graphics system may only be accessed when handling certain events. The + graphics context is active for #PUGL_CREATE, #PUGL_DESTROY, #PUGL_CONFIGURE, + and #PUGL_EXPOSE, but only enabled for drawing for #PUGL_EXPOSE. +*/ +typedef union { + PuglEventAny any; ///< Valid for all event types + PuglEventType type; ///< Event type + PuglEventButton button; ///< #PUGL_BUTTON_PRESS, #PUGL_BUTTON_RELEASE + PuglEventConfigure configure; ///< #PUGL_CONFIGURE + PuglEventExpose expose; ///< #PUGL_EXPOSE + PuglEventKey key; ///< #PUGL_KEY_PRESS, #PUGL_KEY_RELEASE + PuglEventText text; ///< #PUGL_TEXT + PuglEventCrossing crossing; ///< #PUGL_POINTER_IN, #PUGL_POINTER_OUT + PuglEventMotion motion; ///< #PUGL_MOTION + PuglEventScroll scroll; ///< #PUGL_SCROLL + PuglEventFocus focus; ///< #PUGL_FOCUS_IN, #PUGL_FOCUS_OUT + PuglEventClient client; ///< #PUGL_CLIENT + PuglEventTimer timer; ///< #PUGL_TIMER +} PuglEvent; + +/** + @} + @defgroup status Status + + Status codes and error handling. + + @{ +*/ + +/** + Return status code. +*/ +typedef enum { + PUGL_SUCCESS, ///< Success + 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 + PUGL_REALIZE_FAILED, ///< System view realization failed + PUGL_SET_FORMAT_FAILED, ///< Failed to set pixel format + PUGL_CREATE_CONTEXT_FAILED, ///< Failed to create drawing context + PUGL_UNSUPPORTED_TYPE, ///< Unsupported data type +} PuglStatus; + +/** + Return a string describing a status code. +*/ +PUGL_API PUGL_CONST_FUNC +const char* +puglStrerror(PuglStatus status); + +/** + @} + @defgroup world World + + The top-level context of a Pugl application or plugin. + + The world contains all library-wide state. There is no static data in Pugl, + so it is safe to use multiple worlds in a single process. This is to + facilitate plugins or other situations where it is not possible to share a + world, but a single world should be shared for all views where possible. + + @{ +*/ + +/** + The "world" of application state. + + The world represents everything that is not associated with a particular + view. Several worlds can be created in a single process, but code using + different worlds must be isolated so they are never mixed. Views are + strongly associated with the world they were created in. +*/ +typedef struct PuglWorldImpl PuglWorld; + +/** + Handle for the world's opaque user data. +*/ +typedef void* PuglWorldHandle; + +/** + The type of a World. +*/ +typedef enum { + PUGL_PROGRAM, ///< Top-level application + PUGL_MODULE ///< Plugin or module within a larger application +} PuglWorldType; + +/** + World flags. +*/ +typedef enum { + /** + Set up support for threads if necessary. + + - X11: Calls XInitThreads() which is required for some drivers. + */ + PUGL_WORLD_THREADS = 1u << 0u +} PuglWorldFlag; + +/** + Bitwise OR of #PuglWorldFlag values. +*/ +typedef uint32_t PuglWorldFlags; + +/** + A log message level, compatible with syslog. +*/ +typedef enum { + PUGL_LOG_LEVEL_ERR = 3, ///< Error + PUGL_LOG_LEVEL_WARNING = 4, ///< Warning + PUGL_LOG_LEVEL_INFO = 6, ///< Informational message + PUGL_LOG_LEVEL_DEBUG = 7 ///< Debug message +} PuglLogLevel; + +/** + A function called to report log messages. + + @param world The world that produced this log message. + @param level Log level. + @param msg Message string. +*/ +typedef void (*PuglLogFunc)(PuglWorld* world, + PuglLogLevel level, + const char* msg); + +/** + Create a new world. + + @param type The type, which dictates what this world is responsible for. + @param flags Flags to control world features. + @return A new world, which must be later freed with puglFreeWorld(). +*/ +PUGL_API PuglWorld* +puglNewWorld(PuglWorldType type, PuglWorldFlags flags); + +/** + Free a world allocated with puglNewWorld(). +*/ +PUGL_API void +puglFreeWorld(PuglWorld* world); + +/** + Set the user data for the world. + + This is usually a pointer to a struct that contains all the state which must + be accessed by several views. + + The handle is opaque to Pugl and is not interpreted in any way. +*/ +PUGL_API void +puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle); + +/** + Get the user data for the world. +*/ +PUGL_API PuglWorldHandle +puglGetWorldHandle(PuglWorld* world); + +/** + Return a pointer to the native handle of the world. + + @return + - X11: A pointer to the `Display`. + - MacOS: `NULL`. + - Windows: The `HMODULE` of the calling process. +*/ +PUGL_API void* +puglGetNativeWorld(PuglWorld* world); + +/** + Set the function to call to log a message. + + This will be called to report any log messages generated internally by Pugl + which are enabled according to the log level. +*/ +PUGL_API PuglStatus +puglSetLogFunc(PuglWorld* world, PuglLogFunc logFunc); + +/** + Set the level of log messages to emit. + + Any log messages with a level less than or equal to `level` will be emitted. +*/ +PUGL_API PuglStatus +puglSetLogLevel(PuglWorld* world, PuglLogLevel level); + +/** + Set the class name of the application. + + This is a stable identifier for the application, used as the window + class/instance name on X11 and Windows. It is not displayed to the user, + but can be used in scripts and by window managers, so it should be the same + for every instance of the application, but different from other + applications. +*/ +PUGL_API PuglStatus +puglSetClassName(PuglWorld* world, const char* name); + +/** + Return the time in seconds. + + This is a monotonically increasing clock with high resolution. The returned + time is only useful to compare against other times returned by this + function, its absolute value has no meaning. +*/ +PUGL_API double +puglGetTime(const PuglWorld* world); + +/** + Update by processing events from the window system. + + This function is a single iteration of the main loop, and should be called + repeatedly to update all views. + + If `timeout` is zero, then this function will not block. Plugins should + always use a timeout of zero to avoid blocking the host. + + If a positive `timeout` is given, then events will be processed for that + amount of time, starting from when this function was called. + + 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. +*/ +PUGL_API PuglStatus +puglUpdate(PuglWorld* world, double timeout); + +/** + @} + + @defgroup view View + + A drawable region that receives events. + + A view can be thought of as a window, but does not necessarily correspond to + a top-level window in a desktop environment. For example, a view can be + embedded in some other window, or represent an embedded system where there + is no concept of multiple windows at all. + + @{ +*/ + +/** + A drawable region that receives events. +*/ +typedef struct PuglViewImpl PuglView; + +/** + A graphics backend. + + The backend dictates how graphics are set up for a view, and how drawing is + performed. A backend must be set by calling puglSetBackend() before + realising a view. + + If you are using a local copy of Pugl, it is possible to implement a custom + backend. See the definition of `PuglBackendImpl` in the source code for + details. +*/ +typedef struct PuglBackendImpl PuglBackend; + +/** + A native view handle. + + X11: This is a `Window`. + + MacOS: This is a pointer to an `NSView*`. + + Windows: This is a `HWND`. +*/ +typedef uintptr_t PuglNativeView; + +/** + Handle for a view's opaque user data. +*/ +typedef void* PuglHandle; + +/** + A hint for configuring a view. +*/ +typedef enum { + PUGL_USE_COMPAT_PROFILE, ///< Use compatible (not core) OpenGL profile + PUGL_USE_DEBUG_CONTEXT, ///< True to use a debug OpenGL context + PUGL_CONTEXT_VERSION_MAJOR, ///< OpenGL context major version + PUGL_CONTEXT_VERSION_MINOR, ///< OpenGL context minor version + PUGL_RED_BITS, ///< Number of bits for red channel + PUGL_GREEN_BITS, ///< Number of bits for green channel + PUGL_BLUE_BITS, ///< Number of bits for blue channel + PUGL_ALPHA_BITS, ///< Number of bits for alpha channel + PUGL_DEPTH_BITS, ///< Number of bits for depth buffer + PUGL_STENCIL_BITS, ///< Number of bits for stencil buffer + PUGL_SAMPLES, ///< Number of samples per pixel (AA) + PUGL_DOUBLE_BUFFER, ///< True if double buffering should be used + PUGL_SWAP_INTERVAL, ///< Number of frames between buffer swaps + PUGL_RESIZABLE, ///< True if view should be resizable + PUGL_IGNORE_KEY_REPEAT, ///< True if key repeat events are ignored + PUGL_REFRESH_RATE, ///< Refresh rate in Hz + + PUGL_NUM_VIEW_HINTS +} PuglViewHint; + +/** + A special view hint value. +*/ +typedef enum { + PUGL_DONT_CARE = -1, ///< Use best available value + PUGL_FALSE = 0, ///< Explicitly false + PUGL_TRUE = 1 ///< Explicitly true +} PuglViewHintValue; + +/** + A function called when an event occurs. +*/ +typedef PuglStatus (*PuglEventFunc)(PuglView* view, const PuglEvent* event); + +/** + @name Setup + Functions for creating and destroying a view. + @{ +*/ + +/** + Create a new view. + + A newly created view does not correspond to a real system view or window. + It must first be configured, then the system view can be created with + puglRealize(). +*/ +PUGL_API PuglView* +puglNewView(PuglWorld* world); + +/** + Free a view created with puglNewView(). +*/ +PUGL_API void +puglFreeView(PuglView* view); + +/** + Return the world that `view` is a part of. +*/ +PUGL_API PuglWorld* +puglGetWorld(PuglView* view); + +/** + Set the user data for a view. + + This is usually a pointer to a struct that contains all the state which must + be accessed by a view. Everything needed to process events should be stored + here, not in static variables. + + The handle is opaque to Pugl and is not interpreted in any way. +*/ +PUGL_API void +puglSetHandle(PuglView* view, PuglHandle handle); + +/** + Get the user data for a view. +*/ +PUGL_API PuglHandle +puglGetHandle(PuglView* view); + +/** + Set the graphics backend to use for a view. + + This must be called once to set the graphics backend before calling + puglRealize(). + + Pugl includes the following backends: + + - puglGlBackend(), declared in pugl_gl.h + - puglCairoBackend(), declared in pugl_cairo.h + + Note that backends are modular and not compiled into the main Pugl library + to avoid unnecessary dependencies. To use a particular backend, + applications must link against the appropriate backend library, or be sure + to compile in the appropriate code if using a local copy of Pugl. +*/ +PUGL_API PuglStatus +puglSetBackend(PuglView* view, const PuglBackend* backend); + +/** + Set the function to call when an event occurs. +*/ +PUGL_API PuglStatus +puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc); + +/** + Set a hint to configure view properties. + + This only has an effect when called before puglRealize(). +*/ +PUGL_API PuglStatus +puglSetViewHint(PuglView* view, PuglViewHint hint, int value); + +/** + Get the value for a view hint. + + If the view has been realized, this can be used to get the actual value of a + hint which was initially set to PUGL_DONT_CARE, or has been adjusted from + the suggested value. +*/ +PUGL_API int +puglGetViewHint(const PuglView* view, PuglViewHint hint); + +/** + @} + @anchor frame + @name Frame + Functions for working with the position and size of a view. + @{ +*/ + +/** + Get the current position and size of the view. + + The position is in screen coordinates with an upper left origin. +*/ +PUGL_API PuglRect +puglGetFrame(const PuglView* view); + +/** + Set the current position and size of the view. + + The position is in screen coordinates with an upper left origin. + + @return #PUGL_UNKNOWN_ERROR on failure, in which case the view frame is + unchanged. +*/ +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. + + @return #PUGL_UNKNOWN_ERROR on failure, but always succeeds if the view is + not yet realized. +*/ +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 + puglRealize() to avoid stutter, though it can be called afterwards as well. + + @return #PUGL_UNKNOWN_ERROR on failure, but always succeeds if the view is + not yet realized. +*/ +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. + + @return #PUGL_UNKNOWN_ERROR on failure, but always succeeds if the view is + not yet realized. +*/ +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 + fixed aspect ratio, set the minimum and maximum values to the same ratio. + + Note that setting different minimum and maximum constraints does not + currenty work on MacOS (the minimum is used), so only setting a fixed aspect + ratio works properly across all platforms. + + If an initial aspect ratio is known, this should be called before + puglRealize() to avoid stutter, though it can be called afterwards as well. + + @return #PUGL_UNKNOWN_ERROR on failure, but always succeeds if the view is + not yet realized. +*/ +PUGL_API PuglStatus +puglSetAspectRatio(PuglView* view, int minX, int minY, int maxX, int maxY); + +/** + @} + @name Windows + Functions for working with system views and the window hierarchy. + @{ +*/ + +/** + Set the title of the window. + + This only makes sense for non-embedded views that will have a corresponding + top-level window, and sets the title, typically displayed in the title bar + or in window switchers. +*/ +PUGL_API PuglStatus +puglSetWindowTitle(PuglView* view, const char* title); + +/** + Set the parent window for embedding a view in an existing window. + + This must be called before puglRealize(), reparenting is not supported. +*/ +PUGL_API PuglStatus +puglSetParentWindow(PuglView* view, PuglNativeView parent); + +/** + Set the transient parent of the window. + + 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); + +/** + Realise a view by creating a corresponding system view or window. + + After this call, the (initially invisible) underlying system view exists and + can be accessed with puglGetNativeWindow(). There is currently no + corresponding unrealize function, the system view will be destroyed along + with the view when puglFreeView() is called. + + The view should be fully configured using the above functions before this is + called. This function may only be called once per view. +*/ +PUGL_API PuglStatus +puglRealize(PuglView* view); + +/** + Show the view. + + If the view has not yet been realized, the first call to this function will + do so automatically. + + If the view is currently hidden, it will be shown and possibly raised to the + top depending on the platform. +*/ +PUGL_API PuglStatus +puglShowWindow(PuglView* view); + +/** + Hide the current window. +*/ +PUGL_API PuglStatus +puglHideWindow(PuglView* view); + +/** + Return true iff the view is currently visible. +*/ +PUGL_API bool +puglGetVisible(const PuglView* view); + +/** + Return the native window handle. +*/ +PUGL_API PuglNativeView +puglGetNativeWindow(PuglView* view); + +/** + @} + @name Graphics + Functions for working with the graphics context and scheduling redisplays. + @{ +*/ + +/** + Get the graphics context. + + This is a backend-specific context used for drawing if the backend graphics + API requires one. It is only available during an expose. + + @return + - Cairo: A pointer to a + [`cairo_t`](http://www.cairographics.org/manual/cairo-cairo-t.html). + - OpenGL: `NULL`. + - Stub: `NULL`. +*/ +PUGL_API void* +puglGetContext(PuglView* view); + +/** + Request a redisplay for the entire view. + + This will cause an expose event to be dispatched later. If called from + within the event handler, the expose should arrive at the end of the current + event loop iteration, though this is not strictly guaranteed on all + platforms. If called elsewhere, an expose will be enqueued to be processed + in the next event loop iteration. +*/ +PUGL_API PuglStatus +puglPostRedisplay(PuglView* view); + +/** + Request a redisplay of the given rectangle within the view. + + This has the same semantics as puglPostRedisplay(), but allows giving a + precise region for redrawing only a portion of the view. +*/ +PUGL_API PuglStatus +puglPostRedisplayRect(PuglView* view, PuglRect rect); + +/** + @} + @anchor interaction + @name Interaction + Functions for interacting with the user and window system. + @{ +*/ + +/** + 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 +puglGrabFocus(PuglView* view); + +/** + Return whether `view` has the keyboard input focus. +*/ +PUGL_API bool +puglHasFocus(const PuglView* view); + +/** + Set the clipboard contents. + + This sets the system clipboard contents, which can be retrieved with + puglGetClipboard() or pasted into other applications. + + @param view The view. + @param type The MIME type of the data, "text/plain" is assumed if `NULL`. + @param data The data to copy to the clipboard. + @param len The length of data in bytes (including terminator if necessary). +*/ +PUGL_API PuglStatus +puglSetClipboard(PuglView* view, + const char* type, + const void* data, + size_t len); + +/** + Get the clipboard contents. + + This gets the system clipboard contents, which may have been set with + puglSetClipboard() or copied from another application. + + @param view The view. + @param[out] type Set to the MIME type of the data. + @param[out] len Set to the length of the data in bytes. + @return The clipboard contents, or `NULL`. +*/ +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. + + @return + - #PUGL_BAD_PARAMETER if the given cursor is invalid. + - #PUGL_FAILURE if the cursor isknown but loading it from the system fails. +*/ +PUGL_API PuglStatus +puglSetCursor(PuglView* view, PuglCursor cursor); + +/** + Request user attention. + + This hints to the system that the window or application requires attention + from the user. The exact effect depends on the platform, but is usually + something like a flashing task bar entry or bouncing application icon. +*/ +PUGL_API PuglStatus +puglRequestAttention(PuglView* view); + +/** + Activate a repeating timer event. + + This starts a timer which will send a #PuglEventTimer to `view` every + `timeout` seconds. This can be used to perform some action in a view at a + regular interval with relatively low frequency. Note that the frequency of + timer events may be limited by how often puglUpdate() is called. + + If the given timer already exists, it is replaced. + + @param view The view to begin seding #PUGL_TIMER events to. + + @param id The identifier for this timer. This is an application-specific ID + that should be a low number, typically the value of a constant or `enum` + that starts from 0. There is a platform-specific limit to the number of + supported timers, and overhead associated with each, so applications should + create only a few timers and perform several tasks in one if necessary. + + @param timeout The period, in seconds, of this timer. This is not + guaranteed to have a resolution better than 10ms (the maximum timer + resolution on Windows) and may be rounded up if it is too short. On X11 and + MacOS, a resolution of about 1ms can usually be relied on. + + @return + - #PUGL_FAILURE if timers are not supported by this system or build. + - #PUGL_UNKNOWN_ERROR if setting the timer failed. +*/ +PUGL_API PuglStatus +puglStartTimer(PuglView* view, uintptr_t id, double timeout); + +/** + Stop an active timer. + + @param view The view that the timer is set for. + @param id The ID previously passed to puglStartTimer(). + + @return + - #PUGL_FAILURE if timers are not supported by this system or build. + - #PUGL_UNKNOWN_ERROR if stopping the timer failed. +*/ +PUGL_API PuglStatus +puglStopTimer(PuglView* view, uintptr_t id); + +/** + Send an event to a view via the window system. + + If supported, the event will be delivered to the view via the event loop + like other events. Note that this function only works for certain event + types. + + Currently, only #PUGL_CLIENT events are supported on all platforms. + + X11: A #PUGL_EXPOSE event can be sent, which is similar to calling + puglPostRedisplayRect(), but will always send a message to the X server, + even when called in an event handler. + + @return + - #PUGL_UNSUPPORTED_TYPE if sending events of this type is not supported. + - #PUGL_UNKNOWN_ERROR if sending the event failed. +*/ +PUGL_API PuglStatus +puglSendEvent(PuglView* view, const PuglEvent* event); + +/** + @} +*/ + +#ifndef PUGL_DISABLE_DEPRECATED + +/** + @} + @name Deprecated API + @{ +*/ + +/** + A native window handle. + + X11: This is a `Window`. + + MacOS: This is a pointer to an `NSView*`. + + Windows: This is a `HWND`. +*/ +typedef uintptr_t PuglNativeWindow; + +/** + Create a Pugl application and view. + + To create a window, call the various puglInit* functions as necessary, then + call puglRealize(). + + @deprecated Use puglNewApp() and puglNewView(). + + @param pargc Pointer to argument count (currently unused). + @param argv Arguments (currently unused). + @return A newly created view. +*/ +static inline PUGL_DEPRECATED_BY("puglNewView") PuglView* +puglInit(const int* pargc, char** argv) +{ + (void)pargc; + (void)argv; + + return puglNewView(puglNewWorld(PUGL_MODULE, 0)); +} + +/** + Destroy an app and view created with `puglInit()`. + + @deprecated Use puglFreeApp() and puglFreeView(). +*/ +static inline PUGL_DEPRECATED_BY("puglFreeView") void +puglDestroy(PuglView* view) +{ + PuglWorld* const world = puglGetWorld(view); + + puglFreeView(view); + puglFreeWorld(world); +} + +/** + Set the window class name before creating a window. +*/ +static inline PUGL_DEPRECATED_BY("puglSetClassName") void +puglInitWindowClass(PuglView* view, const char* name) +{ + puglSetClassName(puglGetWorld(view), name); +} + +/** + Set the window size before creating a window. + + @deprecated Use puglSetFrame(). +*/ +static inline PUGL_DEPRECATED_BY("puglSetFrame") void +puglInitWindowSize(PuglView* view, int width, int height) +{ + PuglRect frame = puglGetFrame(view); + + frame.width = width; + frame.height = height; + + puglSetFrame(view, frame); +} + +/** + Set the minimum window size before creating a window. +*/ +static inline PUGL_DEPRECATED_BY("puglSetMinSize") void +puglInitWindowMinSize(PuglView* view, int width, int height) +{ + puglSetMinSize(view, width, height); +} + +/** + Set the window aspect ratio range before creating a window. + + The x and y values here represent a ratio of width to height. To set a + fixed aspect ratio, set the minimum and maximum values to the same ratio. + + Note that setting different minimum and maximum constraints does not + currenty work on MacOS (the minimum is used), so only setting a fixed aspect + ratio works properly across all platforms. +*/ +static inline PUGL_DEPRECATED_BY("puglSetAspectRatio") void +puglInitWindowAspectRatio(PuglView* view, + int minX, + int minY, + int maxX, + int maxY) +{ + puglSetAspectRatio(view, minX, minY, maxX, maxY); +} + +/** + Set transient parent before creating a window. + + On X11, parent must be a Window. + On OSX, parent must be an NSView*. +*/ +static inline PUGL_DEPRECATED_BY("puglSetTransientFor") void +puglInitTransientFor(PuglView* view, uintptr_t parent) +{ + puglSetTransientFor(view, (PuglNativeWindow)parent); +} + +/** + Enable or disable resizing before creating a window. + + @deprecated Use puglSetViewHint() with #PUGL_RESIZABLE. +*/ +static inline PUGL_DEPRECATED_BY("puglSetViewHint") void +puglInitResizable(PuglView* view, bool resizable) +{ + puglSetViewHint(view, PUGL_RESIZABLE, resizable); +} + +/** + Get the current size of the view. + + @deprecated Use puglGetFrame(). + +*/ +static inline PUGL_DEPRECATED_BY("puglGetFrame") void +puglGetSize(PuglView* view, int* width, int* height) +{ + const PuglRect frame = puglGetFrame(view); + + *width = (int)frame.width; + *height = (int)frame.height; +} + +/** + Ignore synthetic repeated key events. + + @deprecated Use puglSetViewHint() with #PUGL_IGNORE_KEY_REPEAT. +*/ +static inline PUGL_DEPRECATED_BY("puglSetViewHint") void +puglIgnoreKeyRepeat(PuglView* view, bool ignore) +{ + puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, ignore); +} + +/** + Set a hint before creating a window. + + @deprecated Use puglSetWindowHint(). +*/ +static inline PUGL_DEPRECATED_BY("puglSetViewHint") void +puglInitWindowHint(PuglView* view, PuglViewHint hint, int value) +{ + puglSetViewHint(view, hint, value); +} + +/** + Set the parent window before creating a window (for embedding). + + @deprecated Use puglSetWindowParent(). +*/ +static inline PUGL_DEPRECATED_BY("puglSetParentWindow") void +puglInitWindowParent(PuglView* view, PuglNativeWindow parent) +{ + puglSetParentWindow(view, parent); +} + +/** + Set the graphics backend to use. + + @deprecated Use puglSetBackend(). +*/ +static inline PUGL_DEPRECATED_BY("puglSetBackend") int +puglInitBackend(PuglView* view, const PuglBackend* backend) +{ + return (int)puglSetBackend(view, backend); +} + +/** + Realise a view by creating a corresponding system view or window. + + The view should be fully configured using the above functions before this is + called. This function may only be called once per view. + + @deprecated Use puglRealize(), or just show the view. +*/ +static inline PUGL_DEPRECATED_BY("puglRealize") PuglStatus +puglCreateWindow(PuglView* view, const char* title) +{ + puglSetWindowTitle(view, title); + return puglRealize(view); +} + +/** + Block and wait for an event to be ready. + + This can be used in a loop to only process events via puglProcessEvents when + necessary. This function will block indefinitely if no events are + available, so is not appropriate for use in programs that need to perform + regular updates (e.g. animation). + + @deprecated Use puglPollEvents(). +*/ +PUGL_API PUGL_DEPRECATED_BY("puglPollEvents") PuglStatus +puglWaitForEvent(PuglView* view); + +/** + Process all pending window events. + + This handles input events as well as rendering, so it should be called + regularly and rapidly enough to keep the UI responsive. This function does + not block if no events are pending. + + @deprecated Use puglDispatchEvents(). +*/ +PUGL_API PUGL_DEPRECATED_BY("puglDispatchEvents") PuglStatus +puglProcessEvents(PuglView* view); + +/** + Poll for events that are ready to be processed. + + This polls for events that are ready for any view in the world, potentially + blocking depending on `timeout`. + + @param world The world to poll for events. + + @param timeout Maximum time to wait, in seconds. If zero, the call returns + immediately, if negative, the call blocks indefinitely. + + @return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if not, or an error. + + @deprecated Use puglUpdate(). +*/ +PUGL_API PUGL_DEPRECATED_BY("puglUpdate") PuglStatus +puglPollEvents(PuglWorld* world, double timeout); + +/** + Dispatch any pending events to views. + + This processes all pending events, dispatching them to the appropriate + views. View event handlers will be called in the scope of this call. This + function does not block, if no events are pending then it will return + immediately. + + @deprecated Use puglUpdate(). +*/ +PUGL_API PUGL_DEPRECATED_BY("puglUpdate") PuglStatus +puglDispatchEvents(PuglWorld* world); + +/** + Enter the graphics context. + + Note that, unlike some similar libraries, Pugl automatically enters and + leaves the graphics context when required and application should not + normally do this. Drawing in Pugl is only allowed during the processing of + an expose event. + + However, this can be used to enter the graphics context elsewhere, for + example to call any GL functions during setup. + + @param view The view being entered. + @param drawing If true, prepare for drawing. + + @deprecated Set up graphics when a #PUGL_CREATE event is received. +*/ +PUGL_API PUGL_DEPRECATED_BY("PUGL_CREATE") PuglStatus +puglEnterContext(PuglView* view, bool drawing); + +/** + Leave the graphics context. + + This must be called after puglEnterContext() with a matching `drawing` + parameter. + + @param view The view being left. + @param drawing If true, finish drawing, for example by swapping buffers. + + @deprecated Shut down graphics when a #PUGL_DESTROY event is received. +*/ +PUGL_API PUGL_DEPRECATED_BY("PUGL_DESTROY") PuglStatus +puglLeaveContext(PuglView* view, bool drawing); + +#endif /* PUGL_DISABLE_DEPRECATED */ + +/** + @} + @} + @} +*/ + +PUGL_END_DECLS + +#endif /* PUGL_PUGL_H */ diff --git a/include/pugl/pugl.hpp b/include/pugl/pugl.hpp new file mode 100644 index 0000000..9709e51 --- /dev/null +++ b/include/pugl/pugl.hpp @@ -0,0 +1,726 @@ +/* + 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.hpp + @brief Pugl C++ API wrapper. +*/ + +#ifndef PUGL_PUGL_HPP +#define PUGL_PUGL_HPP + +#include "pugl/pugl.h" + +#include <cassert> +#include <chrono> +#include <functional> +#include <memory> +#include <stdexcept> +#include <type_traits> + +/** + @defgroup pugl_cxx C++ API + C++ API wrapper. + + @ingroup pugl + @{ +*/ + +/** + Pugl C++ API namespace. +*/ +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: + Wrapper(const Wrapper&) = delete; + Wrapper& operator=(const Wrapper&) = delete; + + Wrapper(Wrapper&&) = default; + Wrapper& operator=(Wrapper&&) = default; + + 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 + +/** + @defgroup eventsxx Events + @ingroup pugl_cxx + @copydoc events + @{ +*/ + +/** + A strongly-typed analogue of PuglEvent. + + 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. +*/ +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: + 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} + {} + + Clock(const Clock&) = delete; + Clock& operator=(const Clock&) = delete; + + Clock(Clock&&) = delete; + Clock& operator=(Clock&&) = delete; + + /// Return the current time + time_point now() const; + +private: + const pugl::World& _world; +}; + +/// @copydoc PuglWorld +class World : public detail::Wrapper<PuglWorld, puglFreeWorld> +{ +public: + World(const World&) = delete; + World& operator=(const World&) = delete; + + World(World&&) = delete; + World& operator=(World&&) = delete; + + 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, {}} + { + if (!cobj()) { + throw std::runtime_error("Failed to create pugl::World"); + } + } + + /// @copydoc puglGetNativeWorld + void* nativeWorld() { return puglGetNativeWorld(cobj()); } + + // TODO: setLogFunc + + Status setLogLevel(const LogLevel level) + { + return static_cast<Status>( + puglSetLogLevel(cobj(), static_cast<PuglLogLevel>(level))); + } + + /// @copydoc puglSetClassName + Status setClassName(const char* const name) + { + return static_cast<Status>(puglSetClassName(cobj(), name)); + } + + /// @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 + refreshRate, ///< @copydoc PUGL_REFRESH_RATE +}; + +static_assert(ViewHint(PUGL_REFRESH_RATE) == ViewHint::refreshRate, ""); + +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)); + } + + /// @copydoc puglGetViewHint + int getHint(ViewHint hint) + { + return puglGetViewHint(cobj(), static_cast<PuglViewHint>(hint)); + } + + /** + @} + @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)); + } + + /// @copydoc puglSetMaxSize + Status setMaxSize(int width, int height) + { + return static_cast<Status>(puglSetMaxSize(cobj(), width, height)); + } + + /// @copydoc puglSetAspectRatio + Status setAspectRatio(int minX, int minY, int maxX, int maxY) + { + return static_cast<Status>( + puglSetAspectRatio(cobj(), minX, minY, maxX, maxY)); + } + + /** + @} + @name Windows + Methods for working with top-level windows. + @{ + */ + + /// @copydoc puglSetWindowTitle + Status setWindowTitle(const char* title) + { + return static_cast<Status>(puglSetWindowTitle(cobj(), title)); + } + + /// @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())); + } + + /// @copydoc puglPostRedisplayRect + Status postRedisplayRect(const Rect rect) + { + return static_cast<Status>(puglPostRedisplayRect(cobj(), rect)); + } + + /** + @} + @name Interaction + Methods for interacting with the user and window system. + @{ + */ + + /// @copydoc puglGrabFocus + Status grabFocus() { return static_cast<Status>(puglGrabFocus(cobj())); } + + /// @copydoc puglHasFocus + bool hasFocus() const { return puglHasFocus(cobj()); } + + /// @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: + 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; + } + + 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 + +/** + @} +*/ + +#endif /* PUGL_PUGL_HPP */ diff --git a/include/pugl/pugl.ipp b/include/pugl/pugl.ipp new file mode 100644 index 0000000..0ed8c4d --- /dev/null +++ b/include/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; +} + +} // namespace pugl diff --git a/include/pugl/pugl_cairo.h b/include/pugl/pugl_cairo.h new file mode 100644 index 0000000..4b3b621 --- /dev/null +++ b/include/pugl/pugl_cairo.h @@ -0,0 +1,50 @@ +/* + 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_cairo.h + @brief Declaration of Cairo backend accessor. +*/ + +#ifndef PUGL_PUGL_CAIRO_H +#define PUGL_PUGL_CAIRO_H + +#include "pugl/pugl.h" + +PUGL_BEGIN_DECLS + +/** + @defgroup cairo Cairo + Cairo graphics support. + @ingroup pugl_c + @{ +*/ + +/** + Cairo graphics backend accessor. + + Pass the return value to puglInitBackend() to draw to a view with Cairo. +*/ +PUGL_API PUGL_CONST_FUNC const PuglBackend* +puglCairoBackend(void); + +/** + @} +*/ + +PUGL_END_DECLS + +#endif // PUGL_PUGL_CAIRO_H diff --git a/include/pugl/pugl_cairo.hpp b/include/pugl/pugl_cairo.hpp new file mode 100644 index 0000000..5b17ab7 --- /dev/null +++ b/include/pugl/pugl_cairo.hpp @@ -0,0 +1,50 @@ +/* + 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_cairo.hpp + @brief Declaration of Cairo backend accessor for C++. +*/ + +#ifndef PUGL_PUGL_CAIRO_HPP +#define PUGL_PUGL_CAIRO_HPP + +#include "pugl/pugl.h" +#include "pugl/pugl_cairo.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/include/pugl/pugl_gl.h b/include/pugl/pugl_gl.h new file mode 100644 index 0000000..471c5ac --- /dev/null +++ b/include/pugl/pugl_gl.h @@ -0,0 +1,61 @@ +/* + 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_gl.h + @brief OpenGL-specific API. +*/ + +#ifndef PUGL_PUGL_GL_H +#define PUGL_PUGL_GL_H + +#include "pugl/pugl.h" + +PUGL_BEGIN_DECLS + +/** + @defgroup gl OpenGL + OpenGL graphics support. + @ingroup pugl_c + @{ +*/ + +/** + OpenGL extension function. +*/ +typedef void (*PuglGlFunc)(void); + +/** + Return the address of an OpenGL extension function. +*/ +PUGL_API PuglGlFunc +puglGetProcAddress(const char* name); + +/** + OpenGL graphics backend. + + Pass the return value to puglSetBackend() to draw to a view with OpenGL. +*/ +PUGL_API PUGL_CONST_FUNC const PuglBackend* +puglGlBackend(void); + +PUGL_END_DECLS + +/** + @} +*/ + +#endif // PUGL_PUGL_GL_H diff --git a/include/pugl/pugl_gl.hpp b/include/pugl/pugl_gl.hpp new file mode 100644 index 0000000..4bc5bbd --- /dev/null +++ b/include/pugl/pugl_gl.hpp @@ -0,0 +1,60 @@ +/* + 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_gl.hpp + @brief OpenGL-specific C++ API. +*/ + +#ifndef PUGL_PUGL_GL_HPP +#define PUGL_PUGL_GL_HPP + +#include "pugl/pugl.h" +#include "pugl/pugl_gl.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/include/pugl/pugl_stub.h b/include/pugl/pugl_stub.h new file mode 100644 index 0000000..f50418e --- /dev/null +++ b/include/pugl/pugl_stub.h @@ -0,0 +1,60 @@ +/* + 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 + 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_stub.h + @brief Stub backend functions and accessor declaration. +*/ + +#ifndef PUGL_PUGL_STUB_H +#define PUGL_PUGL_STUB_H + +#include "pugl/pugl.h" + +PUGL_BEGIN_DECLS + +/** + @defgroup stub Stub + + Stub graphics backend. + + The stub backend functions do nothing and always + return success. These do not make for a usable backend on their own since + the platform implementation would fail to create a window, but are useful + for other backends to reuse since not all need non-trivial implementations + of every backend function. + + @ingroup pugl_c + @{ +*/ + +/** + Stub graphics backend. + + This backend just creates a simple native window without setting up any + portable graphics API. +*/ +PUGL_API PUGL_CONST_FUNC +const PuglBackend* +puglStubBackend(void); + +/** + @} +*/ + +PUGL_END_DECLS + +#endif // PUGL_PUGL_STUB_H diff --git a/include/pugl/pugl_stub.hpp b/include/pugl/pugl_stub.hpp new file mode 100644 index 0000000..c5f3901 --- /dev/null +++ b/include/pugl/pugl_stub.hpp @@ -0,0 +1,50 @@ +/* + 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_stub.hpp + @brief Declaration of Stub backend accessor for C++. +*/ + +#ifndef PUGL_PUGL_STUB_HPP +#define PUGL_PUGL_STUB_HPP + +#include "pugl/pugl.h" +#include "pugl/pugl_stub.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 |