PSCoverFlow.m #1

  • //
  • guest/
  • alan_petersen/
  • piper/
  • mac/
  • R1.0/
  • Perforce/
  • Classes/
  • Views/
  • PSCoverFlow.m
  • View
  • Commits
  • Open Download .zip Download (8 KB)
//
//  PSCoverFlow.m
//  CoverFlow
//
//  Created by Adam Czubernat on 23.12.2013.
//  Copyright (c) 2013 Perforce Software, Inc. All rights reserved.
//

#import "PSCoverFlow.h"
#import <QuartzCore/QuartzCore.h>

@interface PSCoverFlow () {
	CAScrollLayer *scrollLayer;
	
	CATransform3D perspective;
	CATransform3D leftTransform;
	CATransform3D rightTransform;
	
	CGSize size;
	NSInteger selectedIndex;
}
- (void)selectIndex:(NSInteger)index;
- (void)postSelectionChange;
- (void)layoutLayers;
- (void)layoutShadowLayer:(CALayer *)layer;
@end


@implementation PSCoverFlow
@synthesize delegate;

- (id)initWithFrame:(NSRect)frameRect {
	self = [super initWithFrame:frameRect];
	if (self) {
		
		self.layer = [CALayer layer];
		self.wantsLayer = YES;
						
		scrollLayer = [CAScrollLayer layer];
		scrollLayer.frame = (CGRect) { CGPointZero, frameRect.size };
		scrollLayer.scrollMode = kCAScrollHorizontally;
		scrollLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
		scrollLayer.layoutManager = self;
		
		[self.layer addSublayer:scrollLayer];
		
		perspective = CATransform3DIdentity;
		perspective.m34 = - 1.0f / 800.0f;
		
		// Gradient mask
		CAGradientLayer *gradient = [CAGradientLayer layer];
		gradient.frame = (CGRect) { CGPointZero, frameRect.size };
		gradient.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
		id clearColor = (__bridge id)CGColorGetConstantColor(kCGColorClear);
		id opaqueColor = (__bridge id)CGColorGetConstantColor(kCGColorBlack);
		gradient.colors = @[ clearColor, opaqueColor, opaqueColor, clearColor ];
		gradient.startPoint = CGPointMake(-0.1f, 0);
		gradient.endPoint = CGPointMake(1.1f, 0);
		self.layer.mask = gradient;
	}
	return self;
}

- (void)setFrame:(NSRect)frameRect {
	[CATransaction begin];
	[CATransaction setDisableActions:YES];
	
	[super setFrame:frameRect];
	[self layoutLayers];
	
	[CATransaction commit];
}

- (void)viewDidMoveToWindow {
	if (self.window)
		[self reload];
}

#pragma mark - CALayoutManager

- (void)layoutSublayersOfLayer:(CALayer *)layer {
	
	CGFloat spacing = size.width * 0.8f;
	
	[scrollLayer scrollToPoint:(CGPoint) {
		size.width * selectedIndex - (self.frame.size.width - size.width) / 2.0f, 0.0f
	}];
	
	[layer.sublayers enumerateObjectsUsingBlock:^(CALayer *sublayer, NSUInteger idx, BOOL *stop) {

		CATransform3D transform = CATransform3DIdentity;

		if (idx < selectedIndex) {
			CGFloat x = spacing * (selectedIndex - idx);
			transform = CATransform3DTranslate(transform, x, 0, 0);
			transform = CATransform3DConcat(leftTransform, transform);
		} else if (idx > selectedIndex) {
			CGFloat x = -spacing * (idx - selectedIndex);
			transform = CATransform3DTranslate(transform, x, 0, 0);
			transform = CATransform3DConcat(rightTransform, transform);
		}
		
		sublayer.transform = transform;
	}];
}

#pragma mark - Private 

- (void)postSelectionChange {
	if (!self.superview)
		return;
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(postSelectionChange) object:nil];
	if ([delegate respondsToSelector:@selector(coverFlow:didSelectIndex:)])
		[delegate coverFlow:self didSelectIndex:selectedIndex];
}

- (void)selectIndex:(NSInteger)index {
	
	if (index >= scrollLayer.sublayers.count || index < 0 || index == selectedIndex)
		return;

	selectedIndex = index;
	
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(postSelectionChange) object:nil];
	[self performSelector:@selector(postSelectionChange) withObject:nil afterDelay:0.2f];

	[CATransaction begin];
	[CATransaction setAnimationTimingFunction:
	 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
	[scrollLayer setNeedsLayout];
	[CATransaction commit];
}

- (void)layoutLayers {
	
	CGFloat angle = 60.0f * M_PI/180;
	CGFloat recess = -200.0f;
	CGFloat sizeScale = 0.85f;
	size = self.frame.size;
	size.width = size.height *= sizeScale;
	CGFloat margin = size.width * 0.6f;
	CGFloat frameMargin = self.frame.size.height * (1.0f - sizeScale) / 2.0f;
	
	leftTransform = CATransform3DTranslate(perspective, -margin, 0, recess);
	leftTransform = CATransform3DRotate(leftTransform, angle, 0, 1, 0);
	rightTransform = CATransform3DTranslate(perspective, margin, 0, recess);
	rightTransform = CATransform3DRotate(rightTransform, -angle, 0, 1, 0);
	
	[scrollLayer.sublayers enumerateObjectsUsingBlock:^(CALayer *layer, NSUInteger idx, BOOL *stop) {
		// Resize layers
		layer.frame = (CGRect) { idx * size.width, frameMargin, size };

		[self layoutShadowLayer:layer];
	}];
}

- (void)layoutShadowLayer:(CALayer *)layer {
	
	NSImage *image = layer.contents;
	if (!image) {
		layer.shadowOpacity = 0;
		return;
	}
	
	// Shadow aspect fit
	CGRect rect = { .size = [layer.contents size] };
	CGFloat ratio = fminf(size.width / rect.size.width, size.height / rect.size.height);
	rect.size = (CGSize) { rect.size.width * ratio, rect.size.height * ratio };
	rect.origin = (CGPoint) { (size.width - rect.size.width) / 2.0f, (size.height - rect.size.height) / 2.0f };
	
	// Resize shadow
	CGPathRef shadowPath = CGPathCreateWithRect(rect, NULL);
	layer.shadowRadius = size.height * 0.02f;
	layer.shadowPath = shadowPath;
	layer.shadowOpacity = 1.0f;
	CGPathRelease(shadowPath);
}

#pragma mark - Public

- (void)reload {
	
	[scrollLayer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
	
	NSInteger imagesCount = 0;
	if ([delegate respondsToSelector:@selector(numberOfImagesInCoverFlow:)])
		imagesCount = [delegate numberOfImagesInCoverFlow:self];
	
	for (NSInteger index = 0; index < imagesCount; index++) {

		CALayer *layer = [CALayer layer];
		layer.shouldRasterize = YES;
		layer.contentsGravity = kCAGravityResizeAspect;
		
		CGColorRef color = CGColorCreateGenericGray(0, 0.2f);
		layer.borderColor = color;
		CGColorRelease(color);
		
		layer.shadowOffset = CGSizeZero;
		layer.shadowColor = CGColorGetConstantColor(kCGColorBlack);
		
		[scrollLayer addSublayer:layer];
	}

	[self reloadImagesInRange:(NSRange) { 0, imagesCount }];
	[self selectIndex:0];
	[self layoutLayers];
}

- (void)reloadImagesInRange:(NSRange)range {
	
	NSInteger count = MIN(range.location + range.length, scrollLayer.sublayers.count);
	for (NSInteger index = range.location; index < count; index++) {
		
		NSImage *image = nil;
		if ([delegate respondsToSelector:@selector(coverFlow:imageForIndex:)])
			image = [delegate coverFlow:self imageForIndex:index];
//		image = image ?: [NSImage imageNamed:NSImageNameQuickLookTemplate];
		
		CALayer *layer = [scrollLayer.sublayers objectAtIndex:index];
		layer.contents = image;
		
		CGColorRef color = image ? nil : CGColorCreateGenericGray(0.7, 0.9f);
		layer.backgroundColor = color;
		CGColorRelease(color);
		layer.borderWidth = image ? 0 : 2.0f;

		[self layoutShadowLayer:layer];
	}
}

- (NSInteger)selectedIndex {
	return selectedIndex;
}

- (void)setSelectedIndex:(NSInteger)index {
	if (index >= scrollLayer.sublayers.count || index < 0 || index == selectedIndex)
		return;
	
	selectedIndex = index;
	[CATransaction begin];
	[CATransaction setAnimationTimingFunction:
	 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
	[scrollLayer setNeedsLayout];
	[CATransaction commit];
}

#pragma mark - NSResponder

- (BOOL)acceptsFirstResponder {
	return YES;
}

- (void)mouseDown:(NSEvent *)theEvent {
	CGPoint point = [self convertPoint:theEvent.locationInWindow fromView:nil];
	CALayer *layer = [scrollLayer hitTest:point];
	NSInteger index = [scrollLayer.sublayers indexOfObjectIdenticalTo:layer];
		
	if (index != NSNotFound)
		[self selectIndex:index];
}

- (void)scrollWheel:(NSEvent *)theEvent {
	CGFloat delta = theEvent.deltaX ?: theEvent.deltaY;
	if (fabs(delta) > 1.0f)
		[self selectIndex:selectedIndex + (delta > 0 ? -1 : 1)];
}

- (void)keyDown:(NSEvent *)event {
	unichar keyChar = [[event charactersIgnoringModifiers] characterAtIndex:0];
	if (keyChar == NSLeftArrowFunctionKey)
		[self moveLeft:nil];
	else if (keyChar == NSRightArrowFunctionKey)
		[self moveRight:nil];
	else if (keyChar == NSUpArrowFunctionKey)
		[self moveUp:nil];
	else if (keyChar == NSDownArrowFunctionKey)
		[self moveDown:nil];
	else
		[super keyDown:event];
}

- (void)moveUp:(id)sender {
	[self moveLeft:sender];
}

- (void)moveDown:(id)sender {
	[self moveRight:sender];
}

- (void)moveLeft:(id)sender {
	[self selectIndex:selectedIndex - 1];
}

- (void)moveRight:(id)sender {
	[self selectIndex:selectedIndex + 1];
}

@end
# Change User Description Committed
#1 15071 alan_petersen Populate -o //guest/perforce_software/piper/...
//guest/alan_petersen/piper/....
//guest/perforce_software/piper/mac/R1.0/Perforce/Classes/Views/PSCoverFlow.m
#1 11254 alan_petersen Populate //guest/perforce_software/piper/mac/R1.0/...
from //guest/perforce_software/piper/mac/main/....
//guest/perforce_software/piper/mac/main/Perforce/Classes/Views/PSCoverFlow.m
#1 11252 alan_petersen Rename/move file(s)
//guest/perforce_software/piper/mac/Perforce/Classes/Views/PSCoverFlow.m
#1 10744 alan_petersen Rename/move file(s)
//guest/perforce_software/piper/Perforce/Classes/Views/PSCoverFlow.m
#2 8985 Matt Attaway Update the copyright on a couple files

No functional change.
#1 8919 Matt Attaway Initial add of Piper, a lightweight Perforce client for artists and designers.