18 #import <Carbon/Carbon.h>
22 "title" :
"VuoGraphicsWindow",
26 "VuoGraphicsWindowDelegate",
32 "VuoList_VuoWindowProperty"
60 @property(retain) NSMenuItem *recordMenuItem;
61 @property(retain) id<NSWindowDelegate> privateDelegate;
74 userData:(
void *)userData
76 NSRect mainScreenFrame = [[NSScreen mainScreen] frame];
78 _styleMaskWhenWindowed = NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask;
79 if (
self = [super initWithContentRect:_contentRectWhenWindowed
80 styleMask:_styleMaskWhenWindowed
81 backing:NSBackingStoreBuffered
84 self.colorSpace = NSColorSpace.sRGBColorSpace;
87 self.delegate =
self.privateDelegate;
88 self.releasedWhenClosed = NO;
90 _cursor = VuoCursor_Pointer;
94 self.acceptsMouseMovedEvents = YES;
96 self.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
99 [
self registerForDraggedTypes:@[NSFilenamesPboardType]];
101 _userResizedWindow = NO;
102 _programmaticallyResizingWindow = NO;
103 _constrainToScreen = YES;
106 self.title = [NSString stringWithUTF8String:title];
109 _backingScaleFactorCached =
self.backingScaleFactor;
112 initCallback:initCallback
113 updateBackingCallback:updateBackingCallback
114 backingScaleFactor:_backingScaleFactorCached
115 resizeCallback:resizeCallback
116 drawCallback:drawCallback
118 l.contentsScale = _backingScaleFactorCached;
124 self.contentView = v;
127 _contentRectCached = l.frame;
141 NSView *v =
self.contentView;
152 - (BOOL)canBecomeMainWindow
160 - (BOOL)canBecomeKeyWindow
165 - (void)updateFullScreenMenu
167 NSMenuItem *fullScreenMenuItem = [[[[[NSApplication sharedApplication] mainMenu] itemWithTag:VuoViewMenuItemTag] submenu] itemWithTag:VuoFullScreenMenuItemTag];
168 fullScreenMenuItem.title =
self.isFullScreen ?
@"Exit Full Screen" :
@"Enter Full Screen";
176 - (void)becomeMainWindow
178 [
super becomeMainWindow];
180 if (!_isInMacFullScreenMode)
181 [
self setFullScreenPresentation:self.isFullScreen];
183 NSMenu *fileMenu = [[[NSMenu alloc] initWithTitle:@"File"] autorelease];
184 _recordMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:@selector(toggleRecording) keyEquivalent:@"e"];
185 [_recordMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask|NSAlternateKeyMask];
186 [fileMenu addItem:_recordMenuItem];
187 NSMenuItem *fileMenuItem = [[NSMenuItem new] autorelease];
188 [fileMenuItem setSubmenu:fileMenu];
190 NSMenu *viewMenu = [[[NSMenu alloc] initWithTitle:@"View"] autorelease];
191 NSMenuItem *fullScreenMenuItem = [[[NSMenuItem alloc] initWithTitle:@"" action:@selector(toggleFullScreen) keyEquivalent:@"f"] autorelease];
193 [viewMenu addItem:fullScreenMenuItem];
194 NSMenuItem *viewMenuItem = [[NSMenuItem new] autorelease];
196 [viewMenuItem setSubmenu:viewMenu];
198 NSMenu *windowMenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
199 NSMenuItem *minimizeMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease];
200 NSMenuItem *zoomMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""] autorelease];
201 NSMenuItem *cycleMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Cycle Through Windows" action:@selector(_cycleWindows:) keyEquivalent:@"`"] autorelease];
202 [windowMenu addItem:minimizeMenuItem];
203 [windowMenu addItem:zoomMenuItem];
204 [windowMenu addItem:cycleMenuItem];
205 NSMenuItem *windowMenuItem = [[NSMenuItem new] autorelease];
206 [windowMenuItem setSubmenu:windowMenu];
208 NSMutableArray *windowMenuItems = [NSMutableArray arrayWithCapacity:3];
209 [windowMenuItems addObject:fileMenuItem];
210 [windowMenuItems addObject:viewMenuItem];
211 [windowMenuItems addObject:windowMenuItem];
214 [
self updateFullScreenMenu];
222 - (void)resignMainWindow
224 [
super resignMainWindow];
243 - (void)keyDown:(NSEvent *)event
245 if ([event keyCode] == kVK_Escape && [
self isFullScreen])
246 [
super keyDown:event];
255 _recordMenuItem.title =
@"Stop Recording…";
257 _recordMenuItem.title =
@"Start Recording";
273 NSView *v =
self.contentView;
294 NSView *v =
self.contentView;
309 NSView *v =
self.contentView;
316 _updatedWindow = NULL;
317 _showedWindow = NULL;
318 _requestedFrame = NULL;
328 __block NSUInteger styleMask;
330 styleMask =
self.styleMask;
332 return _isInMacFullScreenMode || styleMask == 0;
339 - (void)setTitle:(NSString *)title
341 self.titleBackup = title;
353 for (
unsigned int i = 1; i <= propertyCount; ++i)
358 if (property.type == VuoWindowProperty_Title)
359 self.title =
property.title ? [NSString stringWithUTF8String:property.title] :
@"";
360 else if (property.type == VuoWindowProperty_FullScreen)
368 NSInteger requestedDeviceId = [[[requestedScreen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
369 NSInteger currentDeviceId = [[[
self.screen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
370 if (requestedDeviceId != currentDeviceId)
371 [
self setFullScreen:NO onScreen:nil];
375 [
self setFullScreen:property.fullScreen onScreen:requestedScreen];
377 else if (property.type == VuoWindowProperty_Position)
379 NSRect propertyInPoints = NSMakeRect(property.left, property.top, 0, 0);
380 if (property.unit == VuoCoordinateUnit_Pixels)
381 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
383 NSRect mainScreenRect = [[[NSScreen screens] objectAtIndex:0] frame];
385 _contentRectWhenWindowed.origin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - _contentRectWhenWindowed.size.height - propertyInPoints.origin.y);
388 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
389 self.frameOrigin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - contentRect.size.height - propertyInPoints.origin.y);
392 else if (property.type == VuoWindowProperty_Size)
394 NSRect propertyInPoints = NSMakeRect(0, 0, property.width, property.height);
395 if (property.unit == VuoCoordinateUnit_Pixels)
396 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
397 _maintainsPixelSizeWhenBackingChanges = (
property.unit == VuoCoordinateUnit_Pixels);
400 _contentRectWhenWindowed.size = propertyInPoints.size;
403 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
407 contentRect.origin.y += contentRect.size.height - propertyInPoints.size.height;
409 contentRect.size = NSMakeSize(propertyInPoints.size.width, propertyInPoints.size.height);
412 _constrainToScreen = NO;
413 [
self setFrame:[
self frameRectForContentRect:contentRect] display:YES animate:NO];
414 _constrainToScreen = YES;
416 @catch (NSException *e)
419 VUserLog(
"Error: Couldn't change window size to %lldx%lld: %s", property.width, property.height, description);
424 else if (property.type == VuoWindowProperty_AspectRatio)
426 if (property.aspectRatio < 1./10000
427 || property.aspectRatio > 10000)
429 VUserLog(
"Error: Couldn't change window aspect ratio to %g since it's unrealistically narrow.", property.aspectRatio);
433 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
436 else if (property.type == VuoWindowProperty_AspectRatioReset)
438 else if (property.type == VuoWindowProperty_Resizable)
440 [[
self standardWindowButton:NSWindowZoomButton] setEnabled:property.resizable];
442 _styleMaskWhenWindowed =
property.resizable ? (_styleMaskWhenWindowed | NSResizableWindowMask) : (_styleMaskWhenWindowed & ~NSResizableWindowMask);
444 self.styleMask =
property.resizable ? ([
self styleMask] | NSResizableWindowMask) : ([
self styleMask] & ~NSResizableWindowMask);
446 else if (property.type == VuoWindowProperty_Cursor)
448 _cursor =
property.cursor;
449 [
self invalidateCursorRectsForView:self.contentView];
463 extern OSStatus GetWindowBounds(WindowRef window, WindowRegionCode regionCode, Rect *globalBounds);
465 GetWindowBounds([
self windowRef], kWindowStructureRgn, &qdRect);
467 return NSMakeRect(qdRect.left,
468 (
float)CGDisplayPixelsHigh(kCGDirectMainDisplay) - qdRect.bottom,
469 qdRect.right - qdRect.left,
470 qdRect.bottom - qdRect.top);
483 - (void)setAspectRatioToWidth:(
unsigned int)pixelsWide height:(
unsigned int)pixelsHigh
488 NSSize newAspect = NSMakeSize(pixelsWide, pixelsHigh);
489 if (NSEqualSizes([
self contentAspectRatio], newAspect))
493 self.contentAspectRatio = newAspect;
508 CGFloat desiredWidth = pixelsWide;
509 CGFloat desiredHeight = pixelsHigh;
510 CGRect windowFrame = [
self liveFrame];
511 CGFloat aspectRatio = (CGFloat)pixelsWide / (CGFloat)pixelsHigh;
514 if (_userResizedWindow)
517 desiredWidth = CGRectGetWidth(windowFrame);
518 desiredHeight = CGRectGetWidth(windowFrame) / aspectRatio;
522 NSRect screenFrame = [[
self screen] visibleFrame];
523 NSRect maxContentRect = [
self contentRectForFrameRect:screenFrame];
524 if (desiredWidth > maxContentRect.size.width || desiredHeight > maxContentRect.size.height)
526 CGFloat maxContentAspectRatio = maxContentRect.size.width / maxContentRect.size.height;
527 if (aspectRatio >= maxContentAspectRatio)
530 desiredWidth = maxContentRect.size.width;
531 desiredHeight = maxContentRect.size.width / aspectRatio;
536 desiredWidth = maxContentRect.size.height * aspectRatio;
537 desiredHeight = maxContentRect.size.height;
541 NSSize newContentSize = NSMakeSize(desiredWidth, desiredHeight);
542 NSSize newWindowSize = [
self frameRectForContentRect:NSMakeRect(0, 0, newContentSize.width, newContentSize.height)].size;
545 CGFloat topY = CGRectGetMinY(windowFrame) + CGRectGetHeight(windowFrame);
546 NSRect newWindowFrame = NSMakeRect(CGRectGetMinX(windowFrame), topY - newWindowSize.height, newWindowSize.width, newWindowSize.height);
548 _programmaticallyResizingWindow = YES;
549 [
self setFrame:newWindowFrame display:YES];
550 _programmaticallyResizingWindow = NO;
569 self.resizeIncrements = NSMakeSize(1,1);
576 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
578 if (_constrainToScreen)
579 return [
super constrainFrameRect:frameRect toScreen:screen];
587 - (void)setFullScreenPresentation:(
bool)enabled
595 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
600 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationDefault;
640 - (void)setFullScreen:(BOOL)wantsFullScreen onScreen:(NSScreen *)screen
642 _programmaticallyResizingWindow =
true;
650 _contentRectWhenWindowed = [
self contentRectForFrameRect:self.frame];
651 _styleMaskWhenWindowed = [
self styleMask];
655 if (screen && ![[
self screen] isEqualTo:screen])
657 NSRect windowFrame =
self.frame;
658 NSRect screenFrame = screen.frame;
659 self.frameOrigin = NSMakePoint(screenFrame.origin.x + screenFrame.size.width/2 - windowFrame.size.width/2,
660 screenFrame.origin.y + screenFrame.size.height/2 - windowFrame.size.height/2);
667 bool useMacFullScreenMode =
true;
671 if (!NSScreen.screensHaveSeparateSpaces)
672 useMacFullScreenMode =
false;
675 if (useMacFullScreenMode)
677 _isInMacFullScreenMode = YES;
682 self.styleMask |= NSResizableWindowMask;
684 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
686 dispatch_async(dispatch_get_main_queue(), ^{
687 _programmaticallyTransitioningFullScreen =
true;
688 [
self toggleFullScreen:nil];
689 [
self updateFullScreenMenu];
695 [
self setFullScreenPresentation:YES];
697 NSSize car =
self.contentAspectRatio;
699 if (!NSEqualSizes(car, NSMakeSize(0,0)))
700 self.contentAspectRatio = car;
703 [
self setFrame:self.screen.frame display:YES];
705 [
self finishFullScreenTransition];
706 [
self updateFullScreenMenu];
713 if (_isInMacFullScreenMode)
715 _isInMacFullScreenMode = NO;
717 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
719 dispatch_async(dispatch_get_main_queue(), ^{
720 _programmaticallyTransitioningFullScreen =
true;
721 [
self toggleFullScreen:nil];
722 self.styleMask = _styleMaskWhenWindowed;
723 [
self updateFullScreenMenu];
729 [
self setFullScreenPresentation:NO];
731 NSSize car =
self.contentAspectRatio;
732 self.styleMask = _styleMaskWhenWindowed;
733 self.title = _titleBackup;
734 if (!NSEqualSizes(car, NSMakeSize(0,0)))
735 self.contentAspectRatio = car;
737 [
self setFrame:[
self frameRectForContentRect:_contentRectWhenWindowed] display:YES];
739 [
self finishFullScreenTransition];
740 [
self updateFullScreenMenu];
752 _programmaticallyResizingWindow =
false;
754 [
self.delegate windowDidResize:nil];
756 [
self makeFirstResponder:self];
764 - (void)toggleFullScreen
766 [
self setFullScreen:![
self isFullScreen] onScreen:nil];
774 - (void)toggleRecording
779 self.temporaryMovieURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
780 _recorder = [[
VuoWindowRecorder alloc] initWithWindow:self url:_temporaryMovieURL];
785 [
self stopRecording];
791 - (void)promptToSaveMovie
793 NSSavePanel *sp = [NSSavePanel savePanel];
794 [sp setTitle:@"Save Movie"];
795 [sp setNameFieldLabel:@"Save Movie To:"];
796 [sp setPrompt:@"Save"];
797 [sp setAllowedFileTypes:@[@"mov"]];
800 sp.nameFieldStringValue = [NSString stringWithUTF8String:title];
803 if ([sp runModal] == NSFileHandlingPanelCancelButton)
807 if (![[NSFileManager defaultManager] moveItemAtURL:_temporaryMovieURL toURL:[sp URL] error:&error])
809 if ([error code] == NSFileWriteFileExistsError)
812 if (![[NSFileManager defaultManager] replaceItemAtURL:[sp URL]
813 withItemAtURL:_temporaryMovieURL
820 NSAlert *alert = [NSAlert alertWithError:error];
824 [
self promptToSaveMovie];
829 NSAlert *alert = [NSAlert alertWithError:error];
834 self.temporaryMovieURL = nil;
844 - (void)stopRecording
855 [
self setFullScreen:NO onScreen:nil];
857 [
self promptToSaveMovie];
865 - (void)cancelOperation:(
id)sender
867 [
self setFullScreen:NO onScreen:nil];
875 NSView *v =
self.contentView;
889 [
self makeFirstResponder:nil];