diff options
author | David Robillard <d@drobilla.net> | 2019-07-27 21:24:36 +0200 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2019-07-29 01:59:19 +0200 |
commit | 37fe29ab9c4a5ea22bc5996b020fa39c854965fa (patch) | |
tree | 5ac433830c4113d07c51b662ca6d4707d4be12e8 /pugl/detail/mac.m | |
parent | 41dea932f866c10eb9c303298545d6b7151cfcd0 (diff) | |
download | pugl-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.m | 1069 |
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 |