├── NSWindow+Additions.h ├── NSWindow+Additions.m ├── NSWindowFlipper.h ├── NSWindowFlipper.m └── README /NSWindow+Additions.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSWindow (Extensions) 5 | -(NSPoint)midpoint; //get the midpoint of the window 6 | -(void)setMidpoint:(NSPoint)midpoint; //set the midpoint of the window 7 | @end 8 | -------------------------------------------------------------------------------- /NSWindow+Additions.m: -------------------------------------------------------------------------------- 1 | #import "NSWindow+Additions.h" 2 | 3 | 4 | @implementation NSWindow (Extensions) 5 | 6 | -(NSPoint)midpoint 7 | { 8 | NSRect frame = [self frame]; 9 | NSPoint midpoint = NSMakePoint(frame.origin.x + (frame.size.width/2), 10 | frame.origin.y + (frame.size.height/2)); 11 | return midpoint; 12 | } 13 | -(void)setMidpoint:(NSPoint)midpoint 14 | { 15 | NSRect frame = [self frame]; 16 | frame.origin = NSMakePoint(midpoint.x - (frame.size.width/2), 17 | midpoint.y - (frame.size.height/2)); 18 | [self setFrame:frame display:YES]; 19 | } 20 | 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /NSWindowFlipper.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code was originally written by Tyler from Mizage (http://mizage.com) 3 | This code is licensed under the "Feel free to use this however you want. A shout out would be cool." license. 4 | Enjoy! 5 | */ 6 | 7 | #import 8 | 9 | //This little class is used to accomodate performSelector:withObject:afterDelay. 10 | @interface FlipArguments : NSObject 11 | { 12 | NSWindow* toWindow; //the window to which we are flipping 13 | CFTimeInterval duration; //the duration of the flip 14 | BOOL shadowed; //draw a shadow under the window while flipping 15 | } 16 | -(id)initWithToWindow:(NSWindow*)ToWindow flipDuration:(CFTimeInterval)Duration shadowed:(BOOL)Shadowed; 17 | @property(readonly,nonatomic)NSWindow* toWindow; 18 | @property(readonly,nonatomic)CFTimeInterval duration; 19 | @property(readonly,nonatomic)BOOL shadowed; 20 | @end 21 | 22 | @interface NSWindow (Flipper) 23 | 24 | -(void)flipToWindow:(NSWindow*)to withDuration:(CFTimeInterval)duration shadowed:(BOOL)shadowed; 25 | -(void)flipWithArguments:(FlipArguments*)flipArguments; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /NSWindowFlipper.m: -------------------------------------------------------------------------------- 1 | /* 2 | This code was originally written by Tyler from Mizage (http://mizage.com) 3 | This code is licensed under the "Feel free to use this however you want. A shout out would be cool." license. 4 | Enjoy! 5 | */ 6 | 7 | 8 | #import "NSWindowFlipper.h" 9 | #import 10 | 11 | #import "NSWindow+Additions.h" 12 | 13 | @implementation FlipArguments 14 | 15 | @synthesize toWindow,duration,shadowed; 16 | 17 | -(id)initWithToWindow:(NSWindow*)ToWindow flipDuration:(CFTimeInterval)Duration shadowed:(BOOL)Shadowed; 18 | { 19 | if(self = [super init]) 20 | { 21 | toWindow = ToWindow; 22 | duration = Duration; 23 | shadowed = Shadowed; 24 | } 25 | return self; 26 | } 27 | 28 | @end 29 | 30 | @interface NSWindowFlipperDelegate : NSObject 31 | { 32 | NSWindow* fromWindow; 33 | NSWindow* toWindow; 34 | NSWindow* fromFlipWindow; 35 | NSWindow* toFlipWindow; 36 | } 37 | -(id)initWithFromWindow:(NSWindow*)FromWindow toWindow:(NSWindow*)ToWindow fromFlipWindow:(NSWindow*)FromFlipWindow toFlipWindow:(NSWindow*)ToFlipWindow; 38 | @end 39 | 40 | @interface NSWindowFlipperDelegate (Private) 41 | -(id)autoreleasePrivate; 42 | @end 43 | 44 | //This class is used to do the final few steps after the animation has completed 45 | @implementation NSWindowFlipperDelegate 46 | 47 | -(id)initWithFromWindow:(NSWindow*)FromWindow toWindow:(NSWindow*)ToWindow fromFlipWindow:(NSWindow*)FromFlipWindow toFlipWindow:(NSWindow*)ToFlipWindow 48 | { 49 | if(self = [super init]) 50 | { 51 | fromWindow = FromWindow; 52 | toWindow = ToWindow; 53 | fromFlipWindow = FromFlipWindow; 54 | toFlipWindow = ToFlipWindow; 55 | } 56 | return self; 57 | } 58 | 59 | //Called when the flip finishes. Tears down the window images we made and brings the window we flipped to into focus 60 | -(void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag 61 | { 62 | NSDisableScreenUpdates(); 63 | [fromFlipWindow close]; 64 | [toFlipWindow close]; 65 | 66 | [toWindow setAlphaValue:1.0]; 67 | [toWindow makeKeyWindow]; 68 | 69 | NSEnableScreenUpdates(); 70 | [self autoreleasePrivate]; 71 | } 72 | 73 | -(id)autoreleasePrivate 74 | { 75 | return [super autorelease]; 76 | } 77 | -(id)autorelease 78 | { 79 | //no-op to shut up the static analyzer 80 | return self; 81 | } 82 | @end 83 | 84 | @implementation NSWindow (Flipper) 85 | 86 | -(void)flipToWindow:(NSWindow*)to withDuration:(CFTimeInterval)duration shadowed:(BOOL)shadowed 87 | { 88 | FlipArguments* args = [[FlipArguments alloc] initWithToWindow:to flipDuration:duration shadowed:shadowed]; 89 | [self flipWithArguments:args]; 90 | [args release]; 91 | } 92 | 93 | -(void)flipWithArguments:(FlipArguments*)flipArguments 94 | { 95 | NSWindow* toWindow = [flipArguments toWindow]; 96 | CFTimeInterval duration = [flipArguments duration]; 97 | BOOL shadowed = [flipArguments shadowed]; 98 | 99 | //Center the toWindow under the fromWindow 100 | [toWindow setMidpoint:[self midpoint]]; 101 | 102 | //force redisplay of hidden window so we get an up to date image 103 | [toWindow display]; 104 | 105 | NSString* animationKey = @"transform"; 106 | //Create two windows to contain images of the windows 107 | NSWindow* flipFromWindow = [[NSWindow alloc] initWithContentRect:NSInsetRect([self frame],-100,-100) 108 | styleMask:NSBorderlessWindowMask 109 | backing:NSBackingStoreBuffered 110 | defer:NO]; 111 | [flipFromWindow setOpaque:NO]; 112 | [flipFromWindow setHasShadow:NO]; 113 | [flipFromWindow setBackgroundColor:[NSColor clearColor]]; 114 | 115 | NSWindow* flipToWindow = [[NSWindow alloc] initWithContentRect:NSInsetRect([toWindow frame],-100,-100) 116 | styleMask:NSBorderlessWindowMask 117 | backing:NSBackingStoreBuffered 118 | defer:NO]; 119 | [flipToWindow setOpaque:NO]; 120 | [flipToWindow setHasShadow:NO]; 121 | [flipToWindow setBackgroundColor:[NSColor clearColor]]; 122 | 123 | //Two temp views to get some data 124 | NSView* tempFrom = [[self contentView] superview]; 125 | NSView* tempTo = [[toWindow contentView] superview]; 126 | 127 | NSRect tempFromBounds = [tempFrom bounds]; 128 | NSRect tempToBounds = [tempTo bounds]; 129 | 130 | //Grab the bitmap of the windows 131 | NSBitmapImageRep* fromBitmap = [tempFrom bitmapImageRepForCachingDisplayInRect:tempFromBounds]; 132 | [tempFrom cacheDisplayInRect:tempFromBounds toBitmapImageRep:fromBitmap]; 133 | 134 | NSBitmapImageRep* toBitmap = [tempTo bitmapImageRepForCachingDisplayInRect:tempToBounds]; 135 | [tempTo cacheDisplayInRect:tempToBounds toBitmapImageRep:toBitmap]; 136 | 137 | 138 | //Create two views sized to their respective windows 139 | NSView* fromView = [[[NSView alloc] initWithFrame:tempFromBounds] autorelease]; 140 | NSView* toView = [[[NSView alloc] initWithFrame:tempToBounds] autorelease]; 141 | 142 | [fromView setWantsLayer:YES]; 143 | [fromView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)]; 144 | [toView setWantsLayer:YES]; 145 | [toView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)]; 146 | 147 | //Add the views to the windows 148 | [flipFromWindow setContentView:fromView]; 149 | [flipToWindow setContentView:toView]; 150 | 151 | //Create two layers sized to their respective windows 152 | CGRect fromLayerBounds = NSRectToCGRect(tempFromBounds); 153 | CGRect toLayerBounds = NSRectToCGRect(tempToBounds); 154 | 155 | CALayer* fromLayer = [CALayer layer]; 156 | [fromLayer setFrame:fromLayerBounds]; 157 | 158 | CALayer* toLayer = [CALayer layer]; 159 | [toLayer setFrame:toLayerBounds]; 160 | 161 | //Fill the layers with the bitmaps 162 | [fromLayer setContents:(id)[fromBitmap CGImage]]; 163 | [toLayer setContents:(id)[toBitmap CGImage]]; 164 | 165 | //Turn off double sided so layer will cull when not facing us 166 | [fromLayer setDoubleSided:NO]; 167 | [toLayer setDoubleSided:NO]; 168 | 169 | //Set up gravity 170 | [fromLayer setContentsGravity:kCAGravityCenter]; 171 | [toLayer setContentsGravity:kCAGravityCenter]; 172 | 173 | //Make the layer we are flipping have a rotation of M_PI so it is facing away and culled 174 | [toLayer setValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f)] forKeyPath:animationKey]; 175 | 176 | if(shadowed) 177 | { 178 | //Create shadows on the layers - the shadow varies between versions of OSX. 179 | //I should really write some code to accomodate it, but it hasn't been critical as we don't use it. 180 | //Keep in mind, the shadow drawn is a filled box. If your view is transparent, it will look weird. 181 | //Basically this shadow is really poorly implemented and needs to be done properly. 182 | int shadowRadius = 14; 183 | CGSize offset = CGSizeMake(0,-22.5); 184 | float opacity = 0.4; 185 | 186 | [fromLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)]; 187 | [fromLayer setShadowRadius:shadowRadius]; 188 | [fromLayer setShadowOffset:offset]; 189 | [fromLayer setShadowOpacity:opacity]; 190 | [toLayer setShadowColor:CGColorGetConstantColor(kCGColorBlack)]; 191 | [toLayer setShadowRadius:shadowRadius]; 192 | [toLayer setShadowOffset:offset]; 193 | [toLayer setShadowOpacity:opacity]; 194 | } 195 | 196 | //Add the layers to their respective views 197 | [fromView setLayer:fromLayer]; 198 | [toView setLayer:toLayer]; 199 | 200 | //We need to disable screen updates so all this setup doesn't cause weird visual flickering, etc 201 | NSDisableScreenUpdates(); 202 | 203 | //Bring up the new bitmapped windows 204 | [flipToWindow orderFront:nil]; 205 | [flipFromWindow orderFront:nil]; 206 | [flipToWindow display]; 207 | [flipFromWindow display]; 208 | 209 | //Remove the original window 210 | [self orderOut:nil]; 211 | 212 | //Bring up the destination window 213 | [toWindow setAlphaValue:0.0]; 214 | [toWindow orderFront:nil]; 215 | 216 | //Our flippers are in place and ready to go, enable updates to draw them. They should look identical at this point 217 | NSEnableScreenUpdates(); 218 | 219 | //Set up the animation 220 | CABasicAnimation* fromAnimation = [CABasicAnimation animationWithKeyPath:animationKey]; 221 | CABasicAnimation* toAnimation = [CABasicAnimation animationWithKeyPath:animationKey]; 222 | 223 | [fromAnimation setRemovedOnCompletion:NO]; 224 | [toAnimation setRemovedOnCompletion:NO]; 225 | 226 | //The zDistance is what makes it look like the window is rotating around a center. Playing with this value is fun. Try it! 227 | int zDistance = 850; 228 | 229 | CATransform3D fromTransform = CATransform3DIdentity; 230 | fromTransform.m34 = 1.0 / -zDistance; 231 | fromTransform = CATransform3DRotate(fromTransform,M_PI,0.0f, 1.0f, 0.0f); 232 | 233 | CATransform3D toTransform = CATransform3DIdentity; 234 | toTransform.m34 = 1.0 / -zDistance; 235 | toTransform = CATransform3DRotate(toTransform,2*M_PI,0.0f, 1.0f, 0.0f); 236 | 237 | //Apply all our options to our animations 238 | [fromAnimation setFromValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]]; 239 | [fromAnimation setToValue:[NSValue valueWithCATransform3D:fromTransform]]; 240 | [fromAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 241 | [toAnimation setFromValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f)]]; 242 | [toAnimation setToValue:[NSValue valueWithCATransform3D:toTransform]]; 243 | [toAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 244 | 245 | CGEventRef event = CGEventCreate(NULL); 246 | CGEventFlags modifiers = CGEventGetFlags(event); 247 | CFRelease(event); 248 | 249 | //For fun. Hold shift to double duration of flip. Hold shift and control to quadruple it. 250 | if(modifiers & kCGEventFlagMaskShift) 251 | duration *= 2; 252 | if((modifiers & kCGEventFlagMaskShift) && (modifiers & kCGEventFlagMaskControl)) 253 | duration *= 4; 254 | 255 | [fromAnimation setDuration:duration]; 256 | [toAnimation setDuration:duration]; 257 | 258 | //Create our delegate to do final teardown and such. We only apply it to one animation. 259 | NSWindowFlipperDelegate* delegate = [[NSWindowFlipperDelegate alloc] initWithFromWindow:self 260 | toWindow:toWindow 261 | fromFlipWindow:flipFromWindow 262 | toFlipWindow:flipToWindow]; 263 | [toAnimation setDelegate:[delegate autorelease]]; 264 | 265 | 266 | //Fire animations 267 | [fromLayer setValue:[NSValue valueWithCATransform3D:fromTransform] forKeyPath:animationKey]; 268 | [fromLayer addAnimation:fromAnimation forKey:animationKey]; 269 | [toLayer setValue:[NSValue valueWithCATransform3D:toTransform] forKeyPath:animationKey]; 270 | [toLayer addAnimation:toAnimation forKey:animationKey]; 271 | } 272 | 273 | @end 274 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is a NSWindow extension that allows you to flip from one window to another along the vertical axis, much like what is standard on iOS. 2 | 3 | Usage: 4 | 5 | NSWindow* from = ... 6 | NSWindow* to = ... 7 | 8 | 9 | //To support performSelector:withObject:afterDelay:, use: 10 | 11 | FlipArguments* flipArgs = [[FlipArguments alloc] initWithToWindow:to flipDuration:2.5 shadowed:NO]; 12 | 13 | //0.0 is used to fire this on the next runloop iteration. This makes it so a button will be in its "pressed" state if you use a button to do the flip. 14 | [from performSelector:@selector(flipWithArguments) withObject:flipArgs afterDelay:0.0]; 15 | 16 | //To execute the flip immediately, use: 17 | 18 | [from flipToWindow:to withDuration:2.5 shadowed:NO]; 19 | --------------------------------------------------------------------------------