aboutsummaryrefslogtreecommitdiffstats
path: root/pugl/detail/mac.m
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2019-07-27 21:24:36 +0200
committerDavid Robillard <d@drobilla.net>2019-07-29 01:59:19 +0200
commit37fe29ab9c4a5ea22bc5996b020fa39c854965fa (patch)
tree5ac433830c4113d07c51b662ca6d4707d4be12e8 /pugl/detail/mac.m
parent41dea932f866c10eb9c303298545d6b7151cfcd0 (diff)
downloadpugl-37fe29ab9c4a5ea22bc5996b020fa39c854965fa.tar.gz
pugl-37fe29ab9c4a5ea22bc5996b020fa39c854965fa.tar.bz2
pugl-37fe29ab9c4a5ea22bc5996b020fa39c854965fa.zip
Reorganize source to separate private implementation details
Taking a page from C++ convention, where "detail" is for things that should not be included in user code.
Diffstat (limited to 'pugl/detail/mac.m')
-rw-r--r--pugl/detail/mac.m1069
1 files changed, 1069 insertions, 0 deletions
diff --git a/pugl/detail/mac.m b/pugl/detail/mac.m
new file mode 100644
index 0000000..83adcd1
--- /dev/null
+++ b/pugl/detail/mac.m
@@ -0,0 +1,1069 @@
+/*
+ Copyright 2012-2019 David Robillard <http://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 MacOS implementation.
+*/
+
+#define GL_SILENCE_DEPRECATION 1
+
+#include "pugl/detail/implementation.h"
+#include "pugl/gl.h"
+#include "pugl/pugl_gl_backend.h"
+
+#ifdef PUGL_HAVE_CAIRO
+#include "pugl/detail/cairo_gl.h"
+#include "pugl/pugl_cairo_backend.h"
+#endif
+
+#import <Cocoa/Cocoa.h>
+
+#include <mach/mach_time.h>
+
+#include <stdlib.h>
+
+#ifndef __MAC_10_10
+#define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core
+typedef NSUInteger NSEventModifierFlags;
+typedef NSUInteger NSWindowStyleMask;
+#endif
+
+@class PuglOpenGLView;
+
+struct PuglInternalsImpl {
+ NSApplication* app;
+ PuglOpenGLView* glview;
+ id window;
+ NSEvent* nextEvent;
+ uint32_t mods;
+#ifdef PUGL_HAVE_CAIRO
+ cairo_surface_t* surface;
+ cairo_t* cr;
+ PuglCairoGL cairo_gl;
+#endif
+};
+
+@interface PuglWindow : NSWindow
+{
+@public
+ PuglView* puglview;
+}
+
+- (void) setPuglview:(PuglView*)view;
+
+@end
+
+@implementation PuglWindow
+
+- (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:NSMakeSize(view->width, view->height)];
+}
+
+- (BOOL) canBecomeKeyWindow
+{
+ return YES;
+}
+
+- (BOOL) canBecomeMainWindow
+{
+ return YES;
+}
+
+@end
+
+@interface PuglOpenGLView : NSOpenGLView<NSTextInputClient>
+{
+@public
+ PuglView* puglview;
+ NSTrackingArea* trackingArea;
+ NSMutableAttributedString* markedText;
+ NSTimer* timer;
+ NSTimer* urgentTimer;
+}
+
+@end
+
+@implementation PuglOpenGLView
+
+- (id) initWithFrame:(NSRect)frame
+{
+ const int major = puglview->hints.context_version_major;
+ const int profile = ((puglview->hints.use_compat_profile || major < 3)
+ ? NSOpenGLProfileVersionLegacy
+ : puglview->hints.context_version_major >= 4
+ ? NSOpenGLProfileVersion4_1Core
+ : NSOpenGLProfileVersion3_2Core);
+
+ NSOpenGLPixelFormatAttribute pixelAttribs[16] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAOpenGLProfile, profile,
+ NSOpenGLPFAColorSize, 32,
+ NSOpenGLPFADepthSize, 32,
+ NSOpenGLPFAMultisample, puglview->hints.samples ? 1 : 0,
+ NSOpenGLPFASampleBuffers, puglview->hints.samples ? 1 : 0,
+ NSOpenGLPFASamples, puglview->hints.samples,
+ 0};
+
+ NSOpenGLPixelFormat *pixelFormat = [
+ [NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs];
+
+ if (pixelFormat) {
+ self = [super initWithFrame:frame pixelFormat:pixelFormat];
+ [pixelFormat release];
+ } else {
+ self = [super initWithFrame:frame];
+ }
+
+ if (self) {
+ [[self openGLContext] makeCurrentContext];
+ [self reshape];
+ }
+ return self;
+}
+
+- (void) reshape
+{
+ [super reshape];
+ [[self openGLContext] update];
+
+ if (!puglview) {
+ return;
+ }
+
+ const NSRect bounds = [self bounds];
+ const PuglEventConfigure ev = {
+ PUGL_CONFIGURE,
+ 0,
+ bounds.origin.x,
+ bounds.origin.y,
+ bounds.size.width,
+ bounds.size.height,
+ };
+
+ puglview->backend->resize(puglview, ev.width, ev.height);
+
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (void) drawRect:(NSRect)rect
+{
+ const PuglEventExpose ev = {
+ PUGL_EXPOSE,
+ 0,
+ rect.origin.x,
+ rect.origin.y,
+ rect.size.width,
+ rect.size.height,
+ 0
+ };
+
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (BOOL) isFlipped
+{
+ return YES;
+}
+
+- (BOOL) acceptsFirstResponder
+{
+ return YES;
+}
+
+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 [self convertPoint:[event locationInWindow] fromView:nil];
+}
+
+static void
+handleCrossing(PuglOpenGLView* 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_ENTER_NOTIFY);
+}
+
+- (void) mouseExited:(NSEvent*)event
+{
+ handleCrossing(self, event, PUGL_LEAVE_NOTIFY);
+}
+
+- (void) mouseMoved:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventMotion ev = {
+ PUGL_MOTION_NOTIFY,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ 0,
+ 1
+ };
+ 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 PuglEventScroll ev = {
+ PUGL_SCROLL,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ [event deltaX],
+ [event deltaY]
+ };
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (void) keyDown:(NSEvent*)event
+{
+ if (puglview->ignoreKeyRepeat && [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 = (
+ [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 = [(id)puglview bounds];
+ return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
+}
+
+- (void) insertText:(id)string
+ replacementRange:(NSRange)replacement
+{
+ (void)replacement;
+
+ NSEvent* const event = [NSApp currentEvent];
+ NSString* const characters =
+ ([string isKindOfClass:[NSAttributedString class]]
+ ? [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 = 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) resizeTick
+{
+ puglPostRedisplay(puglview);
+}
+
+- (void) urgentTick
+{
+ [NSApp requestUserAttention:NSInformationalRequest];
+}
+
+- (void) viewDidEndLiveResize
+{
+ [super viewDidEndLiveResize];
+ [timer invalidate];
+ timer = NULL;
+}
+
+@end
+
+@interface PuglWindowDelegate : NSObject<NSWindowDelegate>
+{
+ PuglWindow* window;
+}
+
+- (instancetype) initWithPuglWindow:(PuglWindow*)window;
+
+@end
+
+@implementation PuglWindowDelegate
+
+- (instancetype) initWithPuglWindow:(PuglWindow*)puglWindow
+{
+ if ((self = [super init])) {
+ window = puglWindow;
+ }
+
+ return self;
+}
+
+- (BOOL) windowShouldClose:(id)sender
+{
+ (void)sender;
+
+ PuglEvent ev = { 0 };
+ ev.type = PUGL_CLOSE;
+ puglDispatchEvent(window->puglview, &ev);
+ return YES;
+}
+
+- (void) windowDidBecomeKey:(NSNotification*)notification
+{
+ (void)notification;
+
+ PuglOpenGLView* glview = window->puglview->impl->glview;
+ if (window->puglview->impl->glview->urgentTimer) {
+ [glview->urgentTimer invalidate];
+ glview->urgentTimer = NULL;
+ }
+
+ PuglEvent ev = { 0 };
+ ev.type = PUGL_FOCUS_IN;
+ ev.focus.grab = false;
+ puglDispatchEvent(window->puglview, &ev);
+}
+
+- (void) windowDidResignKey:(NSNotification*)notification
+{
+ (void)notification;
+
+ PuglEvent ev = { 0 };
+ ev.type = PUGL_FOCUS_OUT;
+ ev.focus.grab = false;
+ puglDispatchEvent(window->puglview, &ev);
+}
+
+@end
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+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: constant];
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ PuglInternals* impl = view->impl;
+
+ [NSAutoreleasePool new];
+ impl->app = [NSApplication sharedApplication];
+
+ impl->glview = [PuglOpenGLView alloc];
+ impl->glview->trackingArea = nil;
+ impl->glview->markedText = [[NSMutableAttributedString alloc] init];
+ impl->glview->puglview = view;
+
+ [impl->glview initWithFrame:NSMakeRect(0, 0, view->width, view->height)];
+ [impl->glview addConstraint:
+ puglConstraint(impl->glview, NSLayoutAttributeWidth, view->min_width)];
+ [impl->glview addConstraint:
+ puglConstraint(impl->glview, NSLayoutAttributeHeight, view->min_height)];
+ if (!view->hints.resizable) {
+ [impl->glview setAutoresizingMask:NSViewNotSizable];
+ }
+
+ if (view->parent) {
+ NSView* pview = (NSView*)view->parent;
+ [pview addSubview:impl->glview];
+ [impl->glview setHidden:NO];
+ [[impl->glview window] makeFirstResponder:impl->glview];
+ } else {
+ NSString* titleString = [[NSString alloc]
+ initWithBytes:title
+ length:strlen(title)
+ encoding:NSUTF8StringEncoding];
+ NSRect frame = NSMakeRect(0, 0, view->min_width, view->min_height);
+ unsigned style = (NSClosableWindowMask |
+ NSTitledWindowMask |
+ NSMiniaturizableWindowMask );
+ if (view->hints.resizable) {
+ style |= NSResizableWindowMask;
+ }
+
+ id window = [[[PuglWindow alloc]
+ initWithContentRect:frame
+ styleMask:style
+ backing:NSBackingStoreBuffered
+ defer:NO
+ ] retain];
+ [window setPuglview:view];
+ [window setTitle:titleString];
+ if (view->min_width || view->min_height) {
+ [window setContentMinSize:NSMakeSize(view->min_width,
+ view->min_height)];
+ }
+ impl->window = window;
+
+ ((NSWindow*)window).delegate = [[PuglWindowDelegate alloc]
+ initWithPuglWindow:window];
+
+ if (view->min_aspect_x && view->min_aspect_y) {
+ [window setContentAspectRatio:NSMakeSize(view->min_aspect_x,
+ view->min_aspect_y)];
+ }
+
+ [window setContentView:impl->glview];
+ [impl->app activateIgnoringOtherApps:YES];
+ [window makeFirstResponder:impl->glview];
+ [window makeKeyAndOrderFront:window];
+ }
+
+ [impl->glview updateTrackingAreas];
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ [view->impl->window setIsVisible:YES];
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ [view->impl->window setIsVisible:NO];
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+ view->backend->destroy(view);
+ view->impl->glview->puglview = NULL;
+ [view->impl->glview removeFromSuperview];
+ if (view->impl->window) {
+ [view->impl->window close];
+ }
+ [view->impl->glview release];
+ if (view->impl->window) {
+ [view->impl->window release];
+ }
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ [view->impl->window makeKeyWindow];
+}
+
+void
+puglRequestAttention(PuglView* view)
+{
+ if (![view->impl->window isKeyWindow]) {
+ [NSApp requestUserAttention:NSInformationalRequest];
+ view->impl->glview->urgentTimer =
+ [NSTimer scheduledTimerWithTimeInterval:2.0
+ target:view->impl->glview
+ selector:@selector(urgentTick)
+ userInfo:nil
+ repeats:YES];
+ }
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ /* Note that dequeue:NO is broken (it blocks forever even when events are
+ pending), so we work around this by dequeueing the event here and
+ storing it in view->impl->nextEvent for later processing. */
+ if (!view->impl->nextEvent) {
+ view->impl->nextEvent =
+ [view->impl->window nextEventMatchingMask:NSAnyEventMask];
+ }
+
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ if (view->impl->nextEvent) {
+ // Process event that was dequeued earier by puglWaitForEvent
+ [view->impl->app sendEvent: view->impl->nextEvent];
+ view->impl->nextEvent = NULL;
+ }
+
+ // Process all pending events
+ for (NSEvent* ev = NULL;
+ (ev = [view->impl->window nextEventMatchingMask:NSAnyEventMask
+ untilDate:nil
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES]);) {
+ [view->impl->app sendEvent: ev];
+ }
+
+ 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;
+}
+
+double
+puglGetTime(PuglView* view)
+{
+ return (mach_absolute_time() / 1e9) - view->start_time;
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ [view->impl->glview setNeedsDisplay: YES];
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return (PuglNativeWindow)view->impl->glview;
+}
+
+// Backend
+
+static int
+puglMacConfigure(PuglView* PUGL_UNUSED(view))
+{
+ return 0;
+}
+
+static int
+puglMacCreate(PuglView* PUGL_UNUSED(view))
+{
+ return 0;
+}
+
+static int
+puglMacGlDestroy(PuglView* PUGL_UNUSED(view))
+{
+ return 0;
+}
+
+static int
+puglMacGlEnter(PuglView* view, bool PUGL_UNUSED(drawing))
+{
+ [[view->impl->glview openGLContext] makeCurrentContext];
+ return 0;
+}
+
+static int
+puglMacGlLeave(PuglView* view, bool drawing)
+{
+ if (drawing) {
+ [[view->impl->glview openGLContext] flushBuffer];
+ }
+
+ [NSOpenGLContext clearCurrentContext];
+
+ return 0;
+}
+
+static int
+puglMacGlResize(PuglView* PUGL_UNUSED(view),
+ int PUGL_UNUSED(width),
+ int PUGL_UNUSED(height))
+{
+ return 0;
+}
+
+static void*
+puglMacGlGetContext(PuglView* PUGL_UNUSED(view))
+{
+ return NULL;
+}
+
+const PuglBackend* puglGlBackend(void)
+{
+ static const PuglBackend backend = {
+ puglMacConfigure,
+ puglMacCreate,
+ puglMacGlDestroy,
+ puglMacGlEnter,
+ puglMacGlLeave,
+ puglMacGlResize,
+ puglMacGlGetContext
+ };
+
+ return &backend;
+}
+
+#ifdef PUGL_HAVE_CAIRO
+
+static int
+puglMacCairoDestroy(PuglView* view)
+{
+ pugl_cairo_gl_free(&view->impl->cairo_gl);
+ return 0;
+}
+
+static int
+puglMacCairoEnter(PuglView* view, bool PUGL_UNUSED(drawing))
+{
+ [[view->impl->glview openGLContext] makeCurrentContext];
+
+ return 0;
+}
+
+static int
+puglMacCairoLeave(PuglView* view, bool drawing)
+{
+ if (drawing) {
+ pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height);
+ [[view->impl->glview openGLContext] flushBuffer];
+ }
+
+ [NSOpenGLContext clearCurrentContext];
+
+ return 0;
+}
+
+
+static int
+puglMacCairoResize(PuglView* view, int width, int height)
+{
+ PuglInternals* impl = view->impl;
+
+ cairo_surface_destroy(impl->surface);
+ cairo_destroy(impl->cr);
+ impl->surface = pugl_cairo_gl_create(&impl->cairo_gl, width, height, 4);
+ impl->cr = cairo_create(impl->surface);
+ pugl_cairo_gl_configure(&impl->cairo_gl, width, height);
+
+ return 0;
+}
+
+static void*
+puglMacCairoGetContext(PuglView* view)
+{
+ return view->impl->cr;
+}
+
+const PuglBackend* puglCairoBackend(void)
+{
+ static const PuglBackend backend = {
+ puglMacConfigure,
+ puglMacCreate,
+ puglMacCairoDestroy,
+ puglMacCairoEnter,
+ puglMacCairoLeave,
+ puglMacCairoResize,
+ puglMacCairoGetContext
+ };
+
+ return &backend;
+}
+
+#endif