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)
360 self.title =
property.title ? [NSString stringWithUTF8String:property.title] :
@"";
361 else if (property.type == VuoWindowProperty_FullScreen)
366 bool wantsFullScreen =
property.fullScreen;
370 if (wantsFullScreen && !requestedScreen)
373 NSInteger requestedDeviceId = [[[requestedScreen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
374 NSInteger currentDeviceId = [[[
self.screen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
375 bool changingScreen = requestedDeviceId != currentDeviceId;
381 [
self setFullScreen:NO onScreen:nil];
384 if (NSScreen.screensHaveSeparateSpaces)
386 self.shouldGoFullscreen = requestedScreen;
389 [
self setFullScreen:YES onScreen:requestedScreen];
392 [
self setFullScreen:property.fullScreen onScreen:requestedScreen];
395 [
self setFrameOrigin:(NSPoint){ NSMidX(requestedScreen.visibleFrame) - self.frame.size.width / 2,
396 NSMidY(requestedScreen.visibleFrame) - self.frame.size.height /2 }];
398 else if (property.type == VuoWindowProperty_Position)
400 NSRect propertyInPoints = NSMakeRect(property.left, property.top, 0, 0);
401 if (property.unit == VuoCoordinateUnit_Pixels)
402 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
404 NSRect mainScreenRect = [[[NSScreen screens] objectAtIndex:0] frame];
406 _contentRectWhenWindowed.origin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - _contentRectWhenWindowed.size.height - propertyInPoints.origin.y);
409 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
410 self.frameOrigin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - contentRect.size.height - propertyInPoints.origin.y);
413 else if (property.type == VuoWindowProperty_Size)
415 NSRect propertyInPoints = NSMakeRect(0, 0, property.width, property.height);
416 if (property.unit == VuoCoordinateUnit_Pixels)
417 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
418 _maintainsPixelSizeWhenBackingChanges = (
property.unit == VuoCoordinateUnit_Pixels);
421 _contentRectWhenWindowed.size = propertyInPoints.size;
424 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
428 contentRect.origin.y += contentRect.size.height - propertyInPoints.size.height;
430 contentRect.size = NSMakeSize(propertyInPoints.size.width, propertyInPoints.size.height);
433 _constrainToScreen = NO;
434 [
self setFrame:[
self frameRectForContentRect:contentRect] display:YES animate:NO];
435 _constrainToScreen = YES;
437 @catch (NSException *e)
440 VUserLog(
"Error: Couldn't change window size to %lldx%lld: %s", property.width, property.height, description);
445 else if (property.type == VuoWindowProperty_AspectRatio)
447 if (property.aspectRatio < 1./10000
448 || property.aspectRatio > 10000)
450 VUserLog(
"Error: Couldn't change window aspect ratio to %g since it's unrealistically narrow.", property.aspectRatio);
454 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
457 else if (property.type == VuoWindowProperty_AspectRatioReset)
459 else if (property.type == VuoWindowProperty_Resizable)
461 [[
self standardWindowButton:NSWindowZoomButton] setEnabled:property.resizable];
463 _styleMaskWhenWindowed =
property.resizable ? (_styleMaskWhenWindowed | NSResizableWindowMask) : (_styleMaskWhenWindowed & ~NSResizableWindowMask);
465 self.styleMask =
property.resizable ? ([
self styleMask] | NSResizableWindowMask) : ([
self styleMask] & ~NSResizableWindowMask);
467 else if (property.type == VuoWindowProperty_Cursor)
469 _cursor =
property.cursor;
470 [
self invalidateCursorRectsForView:self.contentView];
484 extern OSStatus GetWindowBounds(WindowRef window, WindowRegionCode regionCode, Rect *globalBounds);
486 GetWindowBounds([
self windowRef], kWindowStructureRgn, &qdRect);
488 return NSMakeRect(qdRect.left,
489 (
float)CGDisplayPixelsHigh(kCGDirectMainDisplay) - qdRect.bottom,
490 qdRect.right - qdRect.left,
491 qdRect.bottom - qdRect.top);
504 - (void)setAspectRatioToWidth:(
unsigned int)pixelsWide height:(
unsigned int)pixelsHigh
509 NSSize newAspect = NSMakeSize(pixelsWide, pixelsHigh);
510 if (NSEqualSizes([
self contentAspectRatio], newAspect))
514 self.contentAspectRatio = newAspect;
529 CGFloat desiredWidth = pixelsWide;
530 CGFloat desiredHeight = pixelsHigh;
531 CGRect windowFrame = [
self liveFrame];
532 CGFloat aspectRatio = (CGFloat)pixelsWide / (CGFloat)pixelsHigh;
535 if (_userResizedWindow)
538 desiredWidth = CGRectGetWidth(windowFrame);
539 desiredHeight = CGRectGetWidth(windowFrame) / aspectRatio;
543 NSRect screenFrame = [[
self screen] visibleFrame];
544 NSRect maxContentRect = [
self contentRectForFrameRect:screenFrame];
545 if (desiredWidth > maxContentRect.size.width || desiredHeight > maxContentRect.size.height)
547 CGFloat maxContentAspectRatio = maxContentRect.size.width / maxContentRect.size.height;
548 if (aspectRatio >= maxContentAspectRatio)
551 desiredWidth = maxContentRect.size.width;
552 desiredHeight = maxContentRect.size.width / aspectRatio;
557 desiredWidth = maxContentRect.size.height * aspectRatio;
558 desiredHeight = maxContentRect.size.height;
562 NSSize newContentSize = NSMakeSize(desiredWidth, desiredHeight);
563 NSSize newWindowSize = [
self frameRectForContentRect:NSMakeRect(0, 0, newContentSize.width, newContentSize.height)].size;
566 CGFloat topY = CGRectGetMinY(windowFrame) + CGRectGetHeight(windowFrame);
567 NSRect newWindowFrame = NSMakeRect(CGRectGetMinX(windowFrame), topY - newWindowSize.height, newWindowSize.width, newWindowSize.height);
569 _programmaticallyResizingWindow = YES;
570 [
self setFrame:newWindowFrame display:YES];
571 _programmaticallyResizingWindow = NO;
590 self.resizeIncrements = NSMakeSize(1,1);
597 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
599 if (_constrainToScreen)
600 return [
super constrainFrameRect:frameRect toScreen:screen];
608 - (void)setFullScreenPresentation:(
bool)enabled
616 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
621 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationDefault;
661 - (void)setFullScreen:(BOOL)wantsFullScreen onScreen:(NSScreen *)screen
663 _programmaticallyResizingWindow =
true;
671 _contentRectWhenWindowed = [
self contentRectForFrameRect:self.frame];
672 _styleMaskWhenWindowed = [
self styleMask];
676 if (screen && ![[
self screen] isEqualTo:screen])
678 NSRect windowFrame =
self.frame;
679 NSRect screenFrame = screen.frame;
680 self.frameOrigin = NSMakePoint(screenFrame.origin.x + screenFrame.size.width/2 - windowFrame.size.width/2,
681 screenFrame.origin.y + screenFrame.size.height/2 - windowFrame.size.height/2);
688 bool useMacFullScreenMode =
true;
692 if (!NSScreen.screensHaveSeparateSpaces)
693 useMacFullScreenMode =
false;
696 if (useMacFullScreenMode)
698 _isInMacFullScreenMode = YES;
703 self.styleMask |= NSResizableWindowMask;
705 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
707 dispatch_async(dispatch_get_main_queue(), ^{
708 _programmaticallyTransitioningFullScreen =
true;
709 [
self toggleFullScreen:nil];
710 [
self updateFullScreenMenu];
716 [
self setFullScreenPresentation:YES];
718 NSSize car =
self.contentAspectRatio;
720 if (!NSEqualSizes(car, NSMakeSize(0,0)))
721 self.contentAspectRatio = car;
724 [
self setFrame:self.screen.frame display:YES];
726 [
self finishFullScreenTransition];
727 [
self updateFullScreenMenu];
734 if (_isInMacFullScreenMode)
736 _isInMacFullScreenMode = NO;
738 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
740 dispatch_async(dispatch_get_main_queue(), ^{
741 _programmaticallyTransitioningFullScreen =
true;
742 [
self toggleFullScreen:nil];
743 self.styleMask = _styleMaskWhenWindowed;
744 [
self updateFullScreenMenu];
750 [
self setFullScreenPresentation:NO];
752 NSSize car =
self.contentAspectRatio;
753 self.styleMask = _styleMaskWhenWindowed;
754 self.title = _titleBackup;
755 if (!NSEqualSizes(car, NSMakeSize(0,0)))
756 self.contentAspectRatio = car;
758 [
self setFrame:[
self frameRectForContentRect:_contentRectWhenWindowed] display:YES];
760 [
self finishFullScreenTransition];
761 [
self updateFullScreenMenu];
773 _programmaticallyResizingWindow =
false;
775 [
self.delegate windowDidResize:nil];
777 [
self makeFirstResponder:self];
779 if (
self.shouldGoFullscreen)
780 dispatch_async(dispatch_get_main_queue(), ^{
781 NSScreen *s = [
self.shouldGoFullscreen retain];
782 self.shouldGoFullscreen = nil;
783 [
self setFullScreen:true onScreen:s];
793 - (void)toggleFullScreen
795 [
self setFullScreen:![
self isFullScreen] onScreen:nil];
803 - (void)toggleRecording
808 self.temporaryMovieURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
809 _recorder = [[
VuoWindowRecorder alloc] initWithWindow:self url:_temporaryMovieURL];
814 [
self stopRecording];
820 - (void)promptToSaveMovie
822 NSSavePanel *sp = [NSSavePanel savePanel];
823 [sp setTitle:@"Save Movie"];
824 [sp setNameFieldLabel:@"Save Movie To:"];
825 [sp setPrompt:@"Save"];
826 [sp setAllowedFileTypes:@[@"mov"]];
829 sp.nameFieldStringValue = [NSString stringWithUTF8String:title];
832 if ([sp runModal] == NSFileHandlingPanelCancelButton)
836 if (![[NSFileManager defaultManager] moveItemAtURL:_temporaryMovieURL toURL:[sp URL] error:&error])
838 if ([error code] == NSFileWriteFileExistsError)
841 if (![[NSFileManager defaultManager] replaceItemAtURL:[sp URL]
842 withItemAtURL:_temporaryMovieURL
849 NSAlert *alert = [NSAlert alertWithError:error];
853 [
self promptToSaveMovie];
858 NSAlert *alert = [NSAlert alertWithError:error];
863 self.temporaryMovieURL = nil;
873 - (void)stopRecording
884 [
self setFullScreen:NO onScreen:nil];
886 [
self promptToSaveMovie];
894 - (void)cancelOperation:(
id)sender
896 [
self setFullScreen:NO onScreen:nil];
904 NSView *v =
self.contentView;
918 [
self makeFirstResponder:nil];