// // GHViewAnimation.m // // From ViewAnimationTest project. (TODO: Find source) // Not sure where this is from originally, and I refactored it a bunch. // If anyone recognizes this code, let me know. // #import "GHViewAnimation.h" @implementation GHViewAnimation - (id)initWithContainer:(NSView *)container view:(NSView *)view1 view:(NSView *)view2 { self = [super init]; if (self) { container_ = container; view1_ = [self wrapView:view1 container:container_ hide:NO]; view2_ = [self wrapView:view2 container:container_ hide:YES]; toView_ = view2_; fromView_ = view1_; } return self; } - (NSView *)wrapView:(NSView *)view container:(NSView *)container hide:(BOOL)hide { NSRect rect = [container bounds]; NSView *tempView = [[NSView alloc] initWithFrame:rect]; [tempView addSubview:view]; [tempView setAutoresizingMask:[container autoresizingMask]]; [container addSubview:tempView]; rect = [tempView bounds]; [tempView setHidden:hide]; [view setFrame:rect]; return [tempView autorelease]; } - (void)prepareSubviewOfView:(NSView *)view { NSView *subview = [[view subviews] objectAtIndex:0]; [subview setFrameOrigin:NSZeroPoint]; // Reset the mask and let each animation turn on whatever resizing options it needs [subview setAutoresizingMask:NSViewNotSizable]; } - (void)resetSubviewOfView:(NSView *)view { NSView *subview = [[view subviews] objectAtIndex:0]; [subview setFrameOrigin:NSZeroPoint]; // Allow the views to resize properly now that the animation is done. [subview setAutoresizingMask:[container_ autoresizingMask]]; } - (NSViewAnimation *)dissolve { [toView_ setFrame:[container_ bounds]]; // Note: view does not need to be unhidden manually as in the other cases. This is because the fade in // effect will do it for you. NSViewAnimation *animation = [[[NSViewAnimation alloc] initWithViewAnimations: [NSArray arrayWithObjects: // Old view is set to fade out [NSDictionary dictionaryWithObjectsAndKeys:fromView_, NSViewAnimationTargetKey, NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, nil], // New view is set to fade in [NSDictionary dictionaryWithObjectsAndKeys:toView_, NSViewAnimationTargetKey, NSViewAnimationFadeInEffect, NSViewAnimationEffectKey, nil], nil] ] autorelease]; return animation; } - (NSViewAnimation *)moveIn { NSRect rect = [container_ bounds]; // Set the old view to be resizable on the right side. It will appear as if it's sitting still [[[fromView_ subviews] objectAtIndex:0] setAutoresizingMask:NSViewMaxXMargin]; // Set the new view to be out of bounds on the right side, ready to be animated in [toView_ setFrame:NSMakeRect(NSMaxX(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect))]; // If a previous animation resulted in a zero frame, it set it to hidden. We have to unhide it manually. [toView_ setHidden:NO]; NSViewAnimation *animation = [[[NSViewAnimation alloc] initWithViewAnimations: [NSArray arrayWithObjects: // The left "viewport" shrinks to zero-width on the left side [NSDictionary dictionaryWithObjectsAndKeys:fromView_, NSViewAnimationTargetKey, [NSValue valueWithRect:NSMakeRect(NSMinX(rect), NSMinY(rect), 0.0, NSHeight(rect))], NSViewAnimationEndFrameKey, nil], // New view frame is just the viewable area. Since it is currently out of bounds, it will appear to slide // in bounds. [NSDictionary dictionaryWithObjectsAndKeys:toView_, NSViewAnimationTargetKey, [NSValue valueWithRect:rect], NSViewAnimationEndFrameKey, nil], nil] ] autorelease]; return animation; } - (NSViewAnimation *)push { NSRect rect = [container_ bounds]; // Set old view to be resizable on the left side. It will appear to move to the left since the right margin is // fixed. [[[fromView_ subviews] objectAtIndex:0] setAutoresizingMask:NSViewMinXMargin]; // Set the new view to be out of bounds on the right side, ready to be animated in [toView_ setFrame:NSMakeRect(NSMaxX(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect))]; // If a previous animation resulted in a zero frame, it set it to hidden. We have to unhide it manually. [toView_ setHidden:NO]; NSViewAnimation *animation = [[[NSViewAnimation alloc] initWithViewAnimations: [NSArray arrayWithObjects: // Old view frame gets moved to the left, out of bounds. [NSDictionary dictionaryWithObjectsAndKeys:fromView_, NSViewAnimationTargetKey, [NSValue valueWithRect:NSMakeRect(NSMinX(rect) - NSWidth(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect))], NSViewAnimationEndFrameKey, nil], // New view frame is just the viewable area. Since it is currently out of bounds, it will appear to slide // in bounds. Note, this is the same as in the "Move In" case. [NSDictionary dictionaryWithObjectsAndKeys:toView_, NSViewAnimationTargetKey, [NSValue valueWithRect:rect], NSViewAnimationEndFrameKey, nil], nil] ] autorelease]; return animation; } - (NSViewAnimation *)reveal { NSRect rect = [container_ bounds]; // Set old view to be resizable on the left side. It will appear to move to the left since the right margin is // fixed. [[[fromView_ subviews] objectAtIndex:0] setAutoresizingMask:NSViewMinXMargin]; // Set new view to be resizable on the right side. It will appear to sit still. [[[toView_ subviews] objectAtIndex:0] setAutoresizingMask:NSViewMinXMargin]; // Make the right viewport zero-width view on the right side. It will reveal the view as it expands to the left. [toView_ setFrame:NSMakeRect(NSMaxX(rect), NSMinY(rect), 0.0, NSHeight(rect))]; [toView_ setHidden:NO]; // Make the subview such that it's right edge is flush with its superview/viewport. [[[toView_ subviews] objectAtIndex:0] setFrame:NSMakeRect(NSMinX(rect) - NSWidth(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect))]; NSViewAnimation *animation = [[[NSViewAnimation alloc] initWithViewAnimations: [NSArray arrayWithObjects: // The left "viewport" shrinks to zero-width [NSDictionary dictionaryWithObjectsAndKeys:fromView_, NSViewAnimationTargetKey, [NSValue valueWithRect:NSMakeRect(NSMinX(rect), NSMinY(rect), 0.0, NSHeight(rect))], NSViewAnimationEndFrameKey, nil], // The right view port expands to encompass the full bounds. [NSDictionary dictionaryWithObjectsAndKeys:toView_, NSViewAnimationTargetKey, [NSValue valueWithRect:rect], NSViewAnimationEndFrameKey, nil], nil] ] autorelease]; return animation; } - (NSViewAnimation *)wipe { NSRect rect = [container_ bounds]; // Set the old view to be resizable on the right side. It will appear as if it's sitting still [[[fromView_ subviews] objectAtIndex:0] setAutoresizingMask:NSViewMaxXMargin]; // Set new view to be resizable on the right side. It will appear to sit still. [[[toView_ subviews] objectAtIndex:0] setAutoresizingMask:NSViewMinXMargin]; // Make the right viewport zero-wdith [toView_ setFrame:NSMakeRect(NSMaxX(rect), NSMinY(rect), 0.0, NSHeight(rect))]; [toView_ setHidden:NO]; // Make the subview such that it's right edge is flush with its superview/viewport. [[[toView_ subviews] objectAtIndex:0] setFrame:NSMakeRect(NSMinX(rect) - NSWidth(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect))]; NSViewAnimation *animation = [[[NSViewAnimation alloc] initWithViewAnimations: [NSArray arrayWithObjects: // The left "viewport" shrinks to zero-width [NSDictionary dictionaryWithObjectsAndKeys:fromView_, NSViewAnimationTargetKey, [NSValue valueWithRect:NSMakeRect(NSMinX(rect), NSMinY(rect), 0.0, NSHeight(rect))], NSViewAnimationEndFrameKey, nil], // The right view port expands to encompass the full bounds. [NSDictionary dictionaryWithObjectsAndKeys:toView_, NSViewAnimationTargetKey, [NSValue valueWithRect:rect], NSViewAnimationEndFrameKey, nil], nil] ] autorelease]; return animation; } - (void)animate:(GHViewAnimationType)animationType { if ([view1_ isHidden]) { toView_ = view1_; fromView_ = view2_; } else { toView_ = view2_; fromView_ = view1_; } // Unset the first responder. Can cause drawing artifacts since the blue glow extends beyond the view's bounds. //[[container_ window] makeFirstResponder:nil]; // Turn off all autoresizing for now. Will be twiddling these for different effects. [self prepareSubviewOfView:toView_]; [self prepareSubviewOfView:fromView_]; NSViewAnimation *animation = nil; switch (animationType) { case GHViewAnimationDissolve: animation = [self dissolve]; break; case GHViewAnimationMoveIn: animation = [self moveIn]; break; case GHViewAnimationPush: animation = [self push]; break; case GHViewAnimationReveal: animation = [self reveal]; break; case GHViewAnimationWipe: animation = [self wipe]; break; default: NSAssert(false, @"Invalid animation type"); } [animation setAnimationBlockingMode:NSAnimationBlocking]; [animation startAnimation]; // Not all the animations above result in the old view being hidden so do it here [fromView_ setHidden:YES]; [self resetSubviewOfView:fromView_]; [self resetSubviewOfView:toView_]; } @end