18 #import <Carbon/Carbon.h>
22 "title" :
"VuoGraphicsWindow",
26 "VuoGraphicsWindowDelegate",
32 "VuoList_VuoWindowProperty"
60 @property(retain) NSMenuItem *recordMenuItem;
61 @property(retain) id<NSWindowDelegate> privateDelegate;
63 @property(retain) NSScreen *shouldGoFullscreen;
75 userData:(
void *)userData
77 NSRect mainScreenFrame = [[NSScreen mainScreen] frame];
79 _styleMaskWhenWindowed = NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask;
80 if (
self = [super initWithContentRect:_contentRectWhenWindowed
81 styleMask:_styleMaskWhenWindowed
82 backing:NSBackingStoreBuffered
85 self.colorSpace = NSColorSpace.sRGBColorSpace;
88 self.delegate =
self.privateDelegate;
89 self.releasedWhenClosed = NO;
91 _cursor = VuoCursor_Pointer;
95 self.acceptsMouseMovedEvents = YES;
97 self.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
100 [
self registerForDraggedTypes:@[NSFilenamesPboardType]];
102 _userResizedWindow = NO;
103 _programmaticallyResizingWindow = NO;
104 _constrainToScreen = YES;
107 self.title = [NSString stringWithUTF8String:title];
110 _backingScaleFactorCached =
self.backingScaleFactor;
113 initCallback:initCallback
114 updateBackingCallback:updateBackingCallback
115 backingScaleFactor:_backingScaleFactorCached
116 resizeCallback:resizeCallback
117 drawCallback:drawCallback
119 l.contentsScale = _backingScaleFactorCached;
125 self.contentView = v;
128 _contentRectCached = l.frame;
144 NSView *v =
self.contentView;
153 - (BOOL)canBecomeMainWindow
161 - (BOOL)canBecomeKeyWindow
166 - (void)updateFullScreenMenu
168 NSMenuItem *fullScreenMenuItem = [[[[[NSApplication sharedApplication] mainMenu] itemWithTag:VuoViewMenuItemTag] submenu] itemWithTag:VuoFullScreenMenuItemTag];
169 fullScreenMenuItem.title =
self.isFullScreen ?
@"Exit Full Screen" :
@"Enter Full Screen";
177 - (void)becomeMainWindow
179 [
super becomeMainWindow];
181 if (!_isInMacFullScreenMode)
182 [
self setFullScreenPresentation:self.isFullScreen];
184 NSMenu *fileMenu = [[[NSMenu alloc] initWithTitle:@"File"] autorelease];
185 _recordMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:@selector(toggleRecording) keyEquivalent:@"e"];
186 [_recordMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask|NSAlternateKeyMask];
187 [fileMenu addItem:_recordMenuItem];
188 NSMenuItem *fileMenuItem = [[NSMenuItem new] autorelease];
189 [fileMenuItem setSubmenu:fileMenu];
191 NSMenu *viewMenu = [[[NSMenu alloc] initWithTitle:@"View"] autorelease];
192 NSMenuItem *fullScreenMenuItem = [[[NSMenuItem alloc] initWithTitle:@"" action:@selector(toggleFullScreen) keyEquivalent:@"f"] autorelease];
194 [viewMenu addItem:fullScreenMenuItem];
195 NSMenuItem *viewMenuItem = [[NSMenuItem new] autorelease];
197 [viewMenuItem setSubmenu:viewMenu];
199 NSMenu *windowMenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
200 NSMenuItem *minimizeMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease];
201 NSMenuItem *zoomMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""] autorelease];
202 NSMenuItem *cycleMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Cycle Through Windows" action:@selector(_cycleWindows:) keyEquivalent:@"`"] autorelease];
203 [windowMenu addItem:minimizeMenuItem];
204 [windowMenu addItem:zoomMenuItem];
205 [windowMenu addItem:cycleMenuItem];
206 NSMenuItem *windowMenuItem = [[NSMenuItem new] autorelease];
207 [windowMenuItem setSubmenu:windowMenu];
209 NSMutableArray *windowMenuItems = [NSMutableArray arrayWithCapacity:3];
210 [windowMenuItems addObject:fileMenuItem];
211 [windowMenuItems addObject:viewMenuItem];
212 [windowMenuItems addObject:windowMenuItem];
215 [
self updateFullScreenMenu];
223 - (void)resignMainWindow
225 [
super resignMainWindow];
244 - (void)keyDown:(NSEvent *)event
246 if ([event keyCode] == kVK_Escape && [
self isFullScreen])
247 [
super keyDown:event];
256 _recordMenuItem.title =
@"Stop Recording…";
258 _recordMenuItem.title =
@"Start Recording";
276 NSView *v =
self.contentView;
297 NSView *v =
self.contentView;
312 NSView *v =
self.contentView;
317 _updatedWindow = NULL;
318 _showedWindow = NULL;
319 _requestedFrame = NULL;
329 __block NSUInteger styleMask;
331 styleMask =
self.styleMask;
333 return _isInMacFullScreenMode || styleMask == 0;
340 - (void)setTitle:(NSString *)title
342 self.titleBackup = title;
354 for (
unsigned int i = 1; i <= propertyCount; ++i)
359 if (property.type == VuoWindowProperty_Title)
361 self.title =
property.title ? [NSString stringWithUTF8String:property.title] :
@"";
369 else if (property.type == VuoWindowProperty_FullScreen)
374 bool wantsFullScreen =
property.fullScreen;
378 if (wantsFullScreen && !requestedScreen)
381 NSInteger requestedDeviceId = [[[requestedScreen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
382 NSInteger currentDeviceId = [[[
self.screen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
383 bool changingScreen = requestedDeviceId != currentDeviceId;
389 [
self setFullScreen:NO onScreen:nil];
392 if (NSScreen.screensHaveSeparateSpaces)
394 self.shouldGoFullscreen = requestedScreen;
397 [
self setFullScreen:YES onScreen:requestedScreen];
400 [
self setFullScreen:property.fullScreen onScreen:requestedScreen];
403 [
self setFrameOrigin:(NSPoint){ NSMidX(requestedScreen.visibleFrame) - self.frame.size.width / 2,
404 NSMidY(requestedScreen.visibleFrame) - self.frame.size.height /2 }];
406 else if (property.type == VuoWindowProperty_Position)
408 NSRect propertyInPoints = NSMakeRect(property.left, property.top, 0, 0);
409 if (property.unit == VuoCoordinateUnit_Pixels)
410 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
412 NSRect mainScreenRect = [[[NSScreen screens] objectAtIndex:0] frame];
414 _contentRectWhenWindowed.origin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - _contentRectWhenWindowed.size.height - propertyInPoints.origin.y);
417 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
418 self.frameOrigin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - contentRect.size.height - propertyInPoints.origin.y);
421 else if (property.type == VuoWindowProperty_Size)
423 NSRect propertyInPoints = NSMakeRect(0, 0, property.width, property.height);
424 if (property.unit == VuoCoordinateUnit_Pixels)
425 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
426 _maintainsPixelSizeWhenBackingChanges = (
property.unit == VuoCoordinateUnit_Pixels);
429 _contentRectWhenWindowed.size = propertyInPoints.size;
432 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
436 contentRect.origin.y += contentRect.size.height - propertyInPoints.size.height;
438 contentRect.size = NSMakeSize(propertyInPoints.size.width, propertyInPoints.size.height);
441 _constrainToScreen = NO;
442 [
self setFrame:[
self frameRectForContentRect:contentRect] display:YES animate:NO];
443 _constrainToScreen = YES;
445 @catch (NSException *e)
449 VUserLog(
"Error: Couldn't change window size to %lldx%lld: %s", property.width, property.height, description);
453 else if (property.type == VuoWindowProperty_AspectRatio)
455 if (property.aspectRatio < 1./10000
456 || property.aspectRatio > 10000)
458 VUserLog(
"Error: Couldn't change window aspect ratio to %g since it's unrealistically narrow.", property.aspectRatio);
462 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
465 else if (property.type == VuoWindowProperty_AspectRatioReset)
467 else if (property.type == VuoWindowProperty_Resizable)
469 [[
self standardWindowButton:NSWindowZoomButton] setEnabled:property.resizable];
471 _styleMaskWhenWindowed =
property.resizable ? (_styleMaskWhenWindowed | NSResizableWindowMask) : (_styleMaskWhenWindowed & ~NSResizableWindowMask);
473 self.styleMask =
property.resizable ? ([
self styleMask] | NSResizableWindowMask) : ([
self styleMask] & ~NSResizableWindowMask);
475 else if (property.type == VuoWindowProperty_Cursor)
477 _cursor =
property.cursor;
478 [
self invalidateCursorRectsForView:self.contentView];
492 extern OSStatus GetWindowBounds(WindowRef window, WindowRegionCode regionCode, Rect *globalBounds);
494 GetWindowBounds([
self windowRef], kWindowStructureRgn, &qdRect);
496 return NSMakeRect(qdRect.left,
497 (
float)CGDisplayPixelsHigh(kCGDirectMainDisplay) - qdRect.bottom,
498 qdRect.right - qdRect.left,
499 qdRect.bottom - qdRect.top);
512 - (void)setAspectRatioToWidth:(
unsigned int)pixelsWide height:(
unsigned int)pixelsHigh
517 NSSize newAspect = NSMakeSize(pixelsWide, pixelsHigh);
518 if (NSEqualSizes([
self contentAspectRatio], newAspect))
522 self.contentAspectRatio = newAspect;
537 CGFloat desiredWidth = pixelsWide;
538 CGFloat desiredHeight = pixelsHigh;
539 CGRect windowFrame = [
self liveFrame];
540 CGFloat aspectRatio = (CGFloat)pixelsWide / (CGFloat)pixelsHigh;
543 if (_userResizedWindow)
546 desiredWidth = CGRectGetWidth(windowFrame);
547 desiredHeight = CGRectGetWidth(windowFrame) / aspectRatio;
551 NSRect screenFrame = [[
self screen] visibleFrame];
552 NSRect maxContentRect = [
self contentRectForFrameRect:screenFrame];
553 if (desiredWidth > maxContentRect.size.width || desiredHeight > maxContentRect.size.height)
555 CGFloat maxContentAspectRatio = maxContentRect.size.width / maxContentRect.size.height;
556 if (aspectRatio >= maxContentAspectRatio)
559 desiredWidth = maxContentRect.size.width;
560 desiredHeight = maxContentRect.size.width / aspectRatio;
565 desiredWidth = maxContentRect.size.height * aspectRatio;
566 desiredHeight = maxContentRect.size.height;
570 NSSize newContentSize = NSMakeSize(desiredWidth, desiredHeight);
571 NSSize newWindowSize = [
self frameRectForContentRect:NSMakeRect(0, 0, newContentSize.width, newContentSize.height)].size;
574 CGFloat topY = CGRectGetMinY(windowFrame) + CGRectGetHeight(windowFrame);
575 NSRect newWindowFrame = NSMakeRect(CGRectGetMinX(windowFrame), topY - newWindowSize.height, newWindowSize.width, newWindowSize.height);
577 _programmaticallyResizingWindow = YES;
578 [
self setFrame:newWindowFrame display:YES];
579 _programmaticallyResizingWindow = NO;
598 self.resizeIncrements = NSMakeSize(1,1);
605 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
607 if (_constrainToScreen)
608 return [
super constrainFrameRect:frameRect toScreen:screen];
616 - (void)setFullScreenPresentation:(
bool)enabled
624 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
629 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationDefault;
669 - (void)setFullScreen:(BOOL)wantsFullScreen onScreen:(NSScreen *)screen
671 _programmaticallyResizingWindow =
true;
679 _contentRectWhenWindowed = [
self contentRectForFrameRect:self.frame];
680 _styleMaskWhenWindowed = [
self styleMask];
684 if (screen && ![[
self screen] isEqualTo:screen])
686 NSRect windowFrame =
self.frame;
687 NSRect screenFrame = screen.frame;
688 self.frameOrigin = NSMakePoint(screenFrame.origin.x + screenFrame.size.width/2 - windowFrame.size.width/2,
689 screenFrame.origin.y + screenFrame.size.height/2 - windowFrame.size.height/2);
696 bool useMacFullScreenMode =
true;
700 if (!NSScreen.screensHaveSeparateSpaces)
701 useMacFullScreenMode =
false;
704 if (useMacFullScreenMode)
706 _isInMacFullScreenMode = YES;
711 self.styleMask |= NSResizableWindowMask;
713 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
715 dispatch_async(dispatch_get_main_queue(), ^{
716 _programmaticallyTransitioningFullScreen =
true;
717 [
self toggleFullScreen:nil];
718 [
self updateFullScreenMenu];
724 [
self setFullScreenPresentation:YES];
726 NSSize car =
self.contentAspectRatio;
728 if (!NSEqualSizes(car, NSMakeSize(0,0)))
729 self.contentAspectRatio = car;
732 [
self setFrame:self.screen.frame display:YES];
734 [
self finishFullScreenTransition];
735 [
self updateFullScreenMenu];
742 if (_isInMacFullScreenMode)
744 _isInMacFullScreenMode = NO;
746 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
748 dispatch_async(dispatch_get_main_queue(), ^{
749 _programmaticallyTransitioningFullScreen =
true;
750 [
self toggleFullScreen:nil];
751 self.styleMask = _styleMaskWhenWindowed;
752 [
self updateFullScreenMenu];
758 [
self setFullScreenPresentation:NO];
760 NSSize car =
self.contentAspectRatio;
761 self.styleMask = _styleMaskWhenWindowed;
762 self.title = _titleBackup;
763 if (!NSEqualSizes(car, NSMakeSize(0,0)))
764 self.contentAspectRatio = car;
766 [
self setFrame:[
self frameRectForContentRect:_contentRectWhenWindowed] display:YES];
768 [
self finishFullScreenTransition];
769 [
self updateFullScreenMenu];
781 _programmaticallyResizingWindow =
false;
783 [
self.delegate windowDidResize:nil];
785 [
self makeFirstResponder:self];
787 if (
self.shouldGoFullscreen)
788 dispatch_async(dispatch_get_main_queue(), ^{
789 NSScreen *s = [
self.shouldGoFullscreen retain];
790 self.shouldGoFullscreen = nil;
791 [
self setFullScreen:true onScreen:s];
801 - (void)toggleFullScreen
803 [
self setFullScreen:![
self isFullScreen] onScreen:nil];
811 - (void)toggleRecording
816 self.temporaryMovieURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
817 _recorder = [[
VuoWindowRecorder alloc] initWithWindow:self url:_temporaryMovieURL];
822 [
self stopRecording];
828 - (void)promptToSaveMovie
830 NSSavePanel *sp = [NSSavePanel savePanel];
831 [sp setTitle:@"Save Movie"];
832 [sp setNameFieldLabel:@"Save Movie To:"];
833 [sp setPrompt:@"Save"];
834 [sp setAllowedFileTypes:@[@"mov"]];
837 sp.nameFieldStringValue = [NSString stringWithUTF8String:title];
840 if ([sp runModal] == NSFileHandlingPanelCancelButton)
844 if (![[NSFileManager defaultManager] moveItemAtURL:_temporaryMovieURL toURL:[sp URL] error:&error])
846 if ([error code] == NSFileWriteFileExistsError)
849 if (![[NSFileManager defaultManager] replaceItemAtURL:[sp URL]
850 withItemAtURL:_temporaryMovieURL
857 NSAlert *alert = [NSAlert alertWithError:error];
861 [
self promptToSaveMovie];
866 NSAlert *alert = [NSAlert alertWithError:error];
871 self.temporaryMovieURL = nil;
881 - (void)stopRecording
892 [
self setFullScreen:NO onScreen:nil];
894 [
self promptToSaveMovie];
902 - (void)cancelOperation:(
id)sender
904 [
self setFullScreen:NO onScreen:nil];
912 NSView *v =
self.contentView;
926 [
self makeFirstResponder:nil];