NSWindow flip animation (easy and universal)

后端 未结 1 1262
半阙折子戏
半阙折子戏 2021-01-24 00:20

How to make flip animation for OS X application windows without complex coding?

相关标签:
1条回答
  • 2021-01-24 00:52

    Finally, I did it. I have created object that work with NSWindowController objects instead of NSWidows.

    ALWindowFlipAnimator.h

    #import <Foundation/Foundation.h>
    
    //............................................................................................................
    //      Shorten macroes:
    #define FLIPANIMATOR  [ALWindowFlipAnimator sharedWindowFlipAnimator]
    
    //............................................................................................................
    //      Window flip direction:
    typedef NS_ENUM(NSUInteger, ALFlipDirection)
    {
      ALFlipDirectionLeft,
      ALFlipDirectionRight,
      ALFlipDirectionUp,
      ALFlipDirectionDown
    };
    
    @interface ALWindowFlipAnimator : NSObject
    
    +(ALWindowFlipAnimator *)sharedWindowFlipAnimator;
    -(void)flipToWindowNibName:(NSString *)nibName direction:(ALFlipDirection)direction;
    
    @end
    

    ALWindowFlipAnimator.m

    #import "ALWindowFlipAnimator.h"
    #import <QuartzCore/QuartzCore.h>
    
    @implementation ALWindowFlipAnimator
    {
      NSWindowController *_currentWindowController; // Current window controller
      NSWindowController *_nextWindowController;    // Next window controller to flip to
      NSWindow *_animationWindow;                   // Window where flip animation plays
    }
    
    //============================================================================================================
    //      Initialize flip window controller
    //============================================================================================================
    -(id)init
    {
      self = [super init];
      if (self)
      {
        _currentWindowController = nil;
        _nextWindowController = nil;
        _animationWindow = nil;
      }
      return self;
    }
    
    //============================================================================================================
    //      Create shared flip window manager
    //============================================================================================================
    +(ALWindowFlipAnimator *)sharedWindowFlipAnimator
    {
      static ALWindowFlipAnimator *wfa = nil;
      if (!wfa) wfa = [[ALWindowFlipAnimator alloc] init];
      return wfa;
    }
    
    //============================================================================================================
    //      Flip to window with selected NIB file name from the current one
    //============================================================================================================
    -(void)flipToWindowNibName:(NSString *)nibName direction:(ALFlipDirection)direction
    {
      if (!_currentWindowController || ![[_currentWindowController window] isVisible])
      {
        // No current window controller or window is closed
        _currentWindowController = [[NSClassFromString(nibName) alloc] initWithWindowNibName:nibName];
        [_currentWindowController showWindow:self];
      }
      else
      {
        if ([[_currentWindowController className] isEqualToString:nibName])
          // Bring current window to front
          [[_currentWindowController window] makeKeyAndOrderFront:self];
        else
        {
          // Flip to new window
          _nextWindowController = [[NSClassFromString(nibName) alloc] initWithWindowNibName:nibName];
          [self flipToNextWindowControllerDirection:direction];
        }
      }
    }
    
    #pragma mark - Flip animation
    
    #define DEF_DURATION  2.0   // Animation duration
    #define DEF_SCALE     1.2   // Scaling factor for animation window (_animationWindow)
    
    //============================================================================================================
    //      Start window flipping animation
    //============================================================================================================
    -(void)flipToNextWindowControllerDirection:(ALFlipDirection)direction
    {
      NSWindow *currentWindow = [_currentWindowController window];
      NSWindow *nextWindow = [_nextWindowController window];
      NSView *currentWindowView = [currentWindow.contentView superview];
      NSView *nextWindowView = [nextWindow.contentView superview];
    
      // Create window for animation
      CGFloat maxWidth  = MAX(currentWindow.frame.size.width, nextWindow.frame.size.width);
      CGFloat maxHeight = MAX(currentWindow.frame.size.height, nextWindow.frame.size.height);
      CGFloat xscale = DEF_SCALE * 2.0;
      maxWidth += maxWidth * xscale;
      maxHeight += maxHeight * xscale;
    
      CGRect animationFrame = CGRectMake(NSMidX(currentWindow.frame) - (maxWidth / 2),
                                         NSMidY(currentWindow.frame) - (maxHeight / 2),
                                         maxWidth, maxHeight);
      _animationWindow =  [[NSWindow alloc] initWithContentRect:NSRectFromCGRect(animationFrame)
                                                      styleMask:NSBorderlessWindowMask
                                                        backing:NSBackingStoreBuffered
                                                          defer:NO];
      [_animationWindow setOpaque:NO];
      [_animationWindow setHasShadow:NO];
      [_animationWindow setBackgroundColor:[NSColor clearColor]];
      [_animationWindow.contentView setWantsLayer:YES];
      [_animationWindow setLevel:NSScreenSaverWindowLevel];
    
      // Move next window closer to the current one
      CGRect nextFrame = CGRectMake(NSMidX(currentWindow.frame) - (NSWidth(nextWindow.frame) / 2 ),
                                    NSMaxY(currentWindow.frame) - NSHeight(nextWindow.frame),
                                    NSWidth(nextWindow.frame), NSHeight(nextWindow.frame));
      [nextWindow setFrame:NSRectFromCGRect(nextFrame) display:NO];
    
      // Make snapshots of current and next windows
      [CATransaction begin];
      CALayer *currentWindowSnapshot = [self snapshotToImageLayerFromView:currentWindowView];
      CALayer *nextWindowSnapshot = [self snapshotToImageLayerFromView:nextWindowView];
      [CATransaction commit];
    
      currentWindowSnapshot.frame = [self rect:currentWindowView.frame
                                      fromView:currentWindowView
                                        toView:[_animationWindow contentView]];
      nextWindowSnapshot.frame = [self rect:nextWindowView.frame
                                   fromView:nextWindowView
                                     toView:[_animationWindow contentView]];
    
      // Create 3D transform matrix to snapshots
      CATransform3D transform = CATransform3DIdentity;
      transform.m34 = -(1.0 / 1500.0);
      currentWindowSnapshot.transform = transform;
      nextWindowSnapshot.transform = transform;
    
      // Add snapshots to animation window
      [CATransaction begin];
      [[_animationWindow.contentView layer] addSublayer:currentWindowSnapshot];
      [[_animationWindow.contentView layer] addSublayer:nextWindowSnapshot];
      [CATransaction commit];
      [_animationWindow makeKeyAndOrderFront:nil];
    
      // Animation for snapshots
      [CATransaction begin];
      CAAnimation *currentSnapshotAnimation = [self animationWithDuration:(DEF_DURATION * 0.5) flip:YES direction:direction];
      CAAnimation *nextSnapshotAnimation = [self animationWithDuration:(DEF_DURATION * 0.5) flip:NO direction:direction];
      [CATransaction commit];
    
      // Start animation
      nextSnapshotAnimation.delegate = self;
      [currentWindow orderOut:nil];
      [CATransaction begin];
      [currentWindowSnapshot addAnimation:currentSnapshotAnimation forKey:@"flipAnimation"];
      [nextWindowSnapshot addAnimation:nextSnapshotAnimation forKey:@"flipAnimation"];
      [CATransaction commit];
    }
    
    //============================================================================================================
    //      Convert rectangle from one view coordinates to another
    //============================================================================================================
    -(CGRect)rect:(NSRect)rect fromView:(NSView *)fromView toView:(NSView *)toView
    {
      rect = [fromView convertRect:rect toView:nil];
      rect = [fromView.window convertRectToScreen:rect];
      rect = [toView.window convertRectFromScreen:rect];
      rect = [toView convertRect:rect fromView:nil];
      return NSRectToCGRect(rect);
    }
    
    //============================================================================================================
    //      Get snapshot of selected view as layer with bitmap image
    //============================================================================================================
    -(CALayer *)snapshotToImageLayerFromView:(NSView*)view
    {
      // Make view snapshot
      NSBitmapImageRep *snapshot = [view bitmapImageRepForCachingDisplayInRect:view.bounds];
      [view cacheDisplayInRect:view.bounds toBitmapImageRep:snapshot];
      // Convert snapshot to layer
      CALayer *layer = [CALayer layer];
      layer.contents = (id)snapshot.CGImage;
      layer.doubleSided = NO;
      // Add shadow of window to snapshot
      [layer setShadowOpacity:0.5];
      [layer setShadowOffset:CGSizeMake(0.0, -10.0)];
      [layer setShadowRadius:15.0];
      return layer;
    }
    
    //============================================================================================================
    //      Create animation
    //============================================================================================================
    -(CAAnimation *)animationWithDuration:(CGFloat)time flip:(BOOL)flip direction:(ALFlipDirection)direction
    {
      // Set flip direction
      NSString *keyPath = @"transform.rotation.y";
      if (direction == ALFlipDirectionUp || direction == ALFlipDirectionDown) keyPath = @"transform.rotation.x";
      CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:keyPath];
      CGFloat startValue = flip ? 0.0 : -M_PI;
      CGFloat endValue = flip ? M_PI : 0.0;
      if (direction == ALFlipDirectionLeft || direction == ALFlipDirectionUp)
      {
        startValue = flip ? 0.0 : M_PI;
        endValue = flip ? -M_PI : 0.0;
      }
    
      flipAnimation.fromValue = [NSNumber numberWithDouble:startValue];
      flipAnimation.toValue = [NSNumber numberWithDouble:endValue];
    
      CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
      scaleAnimation.toValue = [NSNumber numberWithFloat:DEF_SCALE];
      scaleAnimation.duration = time * 0.5;
      scaleAnimation.autoreverses = YES;
    
      CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
      animationGroup.animations = [NSArray arrayWithObjects:flipAnimation, scaleAnimation, nil];
      animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
      animationGroup.duration = time;
      animationGroup.fillMode = kCAFillModeForwards;
      animationGroup.removedOnCompletion = NO;
    
      return animationGroup;
    }
    
    //============================================================================================================
    //      Flip animation did finish
    //============================================================================================================
    -(void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
    {
      [[_nextWindowController window] makeKeyAndOrderFront:nil];
      [_animationWindow orderOut:nil];
      _animationWindow = nil;
      _currentWindowController = _nextWindowController;
    }
    
    @end
    

    How to use:

    1. Create some new NSWindowController objects with NIBs in your project
    2. Call [FLIPANIMATOR flipToWindowNibName:@"SecondWindowController" direction:ALFlipDirectionRight]; to flip to second window, or [FLIPANIMATOR flipToWindowNibName:@"FirstWindowController" direction:ALFlipDirectionLeft]; to return back
    3. Link QuarzCore.framework to your project
    4. That's all!
    0 讨论(0)
提交回复
热议问题