Created
October 25, 2013 21:21
-
-
Save BenBarahona/7162028 to your computer and use it in GitHub Desktop.
Modified FGScrollLayer class for creating horizontal or vertical scrolls in cocos2d
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// FGScrollLayer.h | |
// Fall G | |
// | |
// Created by Dai Xuefeng on 23/9/12. | |
// Copyright 2012 Nofootbird. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
#import "cocos2d.h" | |
@class FGScrollLayer; | |
@protocol FGScrollLayerDelegate | |
@optional | |
/** Called when scroll layer begins scrolling. | |
* Usefull to cancel CCTouchDispatcher standardDelegates. | |
*/ | |
- (void) scrollLayerScrollingStarted:(FGScrollLayer *) sender; | |
/** Called at the end of moveToPage: | |
* Doesn't get called in selectPage: | |
*/ | |
- (void) scrollLayer: (FGScrollLayer *) sender scrolledToPageNumber: (int) page; | |
@end | |
/** Scrolling layer for Menus, like iOS Springboard Screen. | |
* | |
* It is a very clean and elegant subclass of CCLayer that lets you pass-in an array | |
* of layers and it will then create a smooth scroller. | |
* Complete with the "snapping" effect. You can create screens with anything that can be added to a CCLayer. | |
* | |
* @version 0.2.1 | |
*/ | |
@interface FGScrollLayer : CCLayer | |
{ | |
NSObject <FGScrollLayerDelegate> *delegate_; | |
// The screen coord of initial point the user starts their swipe. | |
CGFloat startSwipe_; | |
// The coord of initial position the user starts theri swipe. | |
CGFloat startSwipeLayerPos_; | |
// For what distance user must slide finger to start scrolling menu. | |
CGFloat minimumTouchLengthToSlide_; | |
// For what distance user must slide finger to change the page. | |
CGFloat minimumTouchLengthToChangePage_; | |
// Internal state of scrollLayer (scrolling or idle). | |
int state_; | |
BOOL stealTouches_; | |
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED | |
// Holds the touch that started the scroll | |
UITouch *scrollTouch_; | |
#endif | |
// parent node for all layers | |
CCNode* layerParant; // use this node to simulate layer sliding effect | |
// Holds pages. | |
NSArray *layers_; | |
// Holds current pages width offset. | |
CGFloat pagesOffset_; | |
// Holds the maximum upper position | |
CGFloat maxVerticalPos_; | |
// Holds the maximum side position | |
CGFloat maxHorizontalPos_; | |
// holds the visible height / width for this layer | |
CGFloat visibleSize; | |
/*sliding effect controls*/ | |
CGFloat slidingSpeed; | |
CGFloat friction; | |
BOOL horizontalView; | |
} | |
@property (readwrite, assign) NSObject <FGScrollLayerDelegate> *delegate; | |
#pragma mark Scroll Config Properties | |
/** Calibration property. Minimum moving touch length that is enough | |
* to cancel menu items and start scrolling a layer. | |
*/ | |
@property(readwrite, assign) CGFloat minimumTouchLengthToSlide; | |
/** Calibration property. Minimum moving touch length that is enough to change | |
* the page, without snapping back to the previous selected page. | |
*/ | |
@property(readwrite, assign) CGFloat minimumTouchLengthToChangePage; | |
/** If YES - when starting scrolling FGScrollLayer will claim touches, that are | |
* already claimed by others targetedTouchDelegates by calling CCTouchDispatcher#touchesCancelled | |
* Usefull to have ability to scroll with touch above menus in pages. | |
* If NO - scrolling will start, but no touches will be cancelled. | |
* Default is YES. | |
*/ | |
@property(readwrite) BOOL stealTouches; | |
#pragma mark Pages Control Properties | |
/** Offset, that can be used to let user see next/previous page. */ | |
@property(readwrite) CGFloat pagesOffset; | |
#pragma mark Init/Creation | |
/** | |
* create a scroll layer with an array of nodes | |
* @param nodes is the array containing all the nodes | |
* @param height is the visible height of this layer, used to calculate maxVerticalPos_ | |
* return a scroll layer | |
*/ | |
-(id) initWithNode:(NSArray*)nodes visibleHeight:(CGFloat)height horizontalScroller:(BOOL)horizontal; | |
/** | |
* Create a scroll layer with an array of nodes | |
* @param nodes is the array containing all the nodes | |
* @param height is the visible height of this layer, used to calculate maxVerticalPos_ | |
* return an autoreleased scroll layer | |
*/ | |
+(id) scrollLayerWithNodes:(NSArray*)nodes visibleHeight:(CGFloat)height horizontalScroller:(BOOL)horizontal; | |
#pragma mark Misc | |
/** | |
* Return the number of pages | |
*/ | |
-(int) totalPagesCount; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// FGScrollLayer.m | |
// Fall G | |
// | |
// Created by Dai Xuefeng on 23/9/12. | |
// Copyright 2012 Nofootbird. All rights reserved. | |
// | |
#import "FGScrollLayer.h" | |
#import "ReportSize.h" | |
enum | |
{ | |
kFGScrollLayerStateIdle, | |
kFGScrollLayerStateSliding, | |
kFGScrollLayerStateInertia, | |
}; | |
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED | |
@interface CCTouchDispatcher (targetedHandlersGetter) | |
- (id<NSFastEnumeration>) targetedHandlers; | |
@end | |
@implementation CCTouchDispatcher (targetedHandlersGetter) | |
- (id<NSFastEnumeration>) targetedHandlers | |
{ | |
return targetedHandlers; | |
} | |
@end | |
#endif | |
@implementation FGScrollLayer | |
@synthesize delegate = delegate_; | |
@synthesize minimumTouchLengthToSlide = minimumTouchLengthToSlide_; | |
@synthesize minimumTouchLengthToChangePage = minimumTouchLengthToChangePage_; | |
@synthesize pagesOffset = pagesOffset_; | |
@synthesize stealTouches = stealTouches_; | |
- (int) totalPagesCount | |
{ | |
return [layers_ count]; | |
} | |
#define DEFAULT_FRICTION 2400 | |
-(id) initWithNode:(NSArray*)nodes visibleHeight:(CGFloat)height horizontalScroller:(BOOL)horizontal{ | |
if (self = [super init]) { | |
NSAssert([nodes count], @"at least one node is necessary in array nodes!"); | |
// Enable Touches/Mouse. | |
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED | |
self.isTouchEnabled = YES; | |
#endif | |
self.stealTouches = YES; | |
// Set default minimum touch length to scroll. | |
self.minimumTouchLengthToSlide = 30.0f; | |
self.minimumTouchLengthToChangePage = 50.0f; | |
slidingSpeed = 0; | |
friction = DEFAULT_FRICTION; | |
horizontalView = horizontal; | |
// Save array of layers. | |
layers_ = [[NSArray arrayWithArray:nodes] retain]; | |
[self updatePages:height]; | |
[self schedule:@selector(tick:)]; | |
} | |
return self; | |
} | |
+(id) scrollLayerWithNodes:(NSArray*)nodes visibleHeight:(CGFloat)height horizontalScroller:(BOOL)horizontal{ | |
return [[[self alloc] initWithNode:nodes visibleHeight:height horizontalScroller:horizontal] autorelease]; | |
} | |
- (void) dealloc | |
{ | |
self.delegate = nil; | |
[layers_ release]; | |
layers_ = nil; | |
[super dealloc]; | |
} | |
- (void) updatePages:(CGFloat)height | |
{ | |
if(horizontalView) | |
{ | |
// Loop through the array and add the screens if needed. | |
int i = 0; | |
CGFloat previousPosX = 0; | |
layerParant = [CCNode node]; | |
[layerParant setPosition:CGPointZero]; | |
[self addChild:layerParant]; | |
for (id<ReportSize> l in layers_) | |
{ | |
[(CCNode*)l setPosition:ccp(previousPosX, 0)]; | |
[(CCNode*)l setAnchorPoint:ccp(0.5, 0)]; | |
previousPosX += [l getWidth]; | |
if (!((CCNode*)l).parent) | |
[layerParant addChild:l]; | |
i++; | |
} | |
visibleSize = height; | |
maxHorizontalPos_ = MAX(0, previousPosX - visibleSize) * -1; | |
NSLog(@"MAX H. POS: %f", maxHorizontalPos_); | |
} | |
else | |
{ | |
// Loop through the array and add the screens if needed. | |
int i = 0; | |
CGFloat previousPosY = 0; | |
layerParant = [CCNode node]; | |
[layerParant setPosition:CGPointZero]; | |
[self addChild:layerParant]; | |
for (id<ReportSize> l in layers_) | |
{ | |
CGFloat height = [l getHeight]; | |
[(CCNode*)l setPosition:ccp(0, -height - previousPosY)]; | |
[(CCNode*)l setAnchorPoint:ccp(0.5, 0)]; | |
previousPosY += [l getHeight]; | |
if (!((CCNode*)l).parent) | |
[layerParant addChild:l]; | |
i++; | |
} | |
visibleSize = height; | |
maxVerticalPos_ = MAX(0, previousPosY - visibleSize); | |
} | |
} | |
#pragma mark Touches | |
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED | |
/** Register with more priority than CCMenu's but don't swallow touches. */ | |
-(void) registerWithTouchDispatcher | |
{ | |
#if COCOS2D_VERSION >= 0x00020000 | |
CCTouchDispatcher *dispatcher = [[CCDirector sharedDirector] touchDispatcher]; | |
int priority = kCCMenuHandlerPriority - 1; | |
#else | |
CCTouchDispatcher *dispatcher = [CCTouchDispatcher sharedDispatcher]; | |
int priority = kCCMenuTouchPriority - 1; | |
#endif | |
[dispatcher addTargetedDelegate:self priority: priority swallowsTouches:NO]; | |
} | |
/** Hackish stuff - stole touches from other CCTouchDispatcher targeted delegates. | |
Used to claim touch without receiving ccTouchBegan. */ | |
- (void) claimTouch: (UITouch *) aTouch | |
{ | |
#if COCOS2D_VERSION >= 0x00020000 | |
CCTouchDispatcher *dispatcher = [[CCDirector sharedDirector] touchDispatcher]; | |
#else | |
CCTouchDispatcher *dispatcher = [CCTouchDispatcher sharedDispatcher]; | |
#endif | |
// Enumerate through all targeted handlers. | |
for ( CCTargetedTouchHandler *handler in [dispatcher targetedHandlers] ) | |
{ | |
// Only our handler should claim the touch. | |
if (handler.delegate == self) | |
{ | |
if (![handler.claimedTouches containsObject: aTouch]) | |
{ | |
[handler.claimedTouches addObject: aTouch]; | |
} | |
} | |
else | |
{ | |
// Steal touch from other targeted delegates, if they claimed it. | |
if ([handler.claimedTouches containsObject: aTouch]) | |
{ | |
if ([handler.delegate respondsToSelector:@selector(ccTouchCancelled:withEvent:)]) | |
{ | |
[handler.delegate ccTouchCancelled: aTouch withEvent: nil]; | |
} | |
[handler.claimedTouches removeObject: aTouch]; | |
} | |
} | |
} | |
} | |
-(void)ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event | |
{ | |
if( scrollTouch_ == touch ) { | |
scrollTouch_ = nil; | |
} | |
} | |
// these two variables are to make a sliding effect on scroll view | |
static CGFloat previousTouchPoint = -1; | |
static CGFloat previousTouchTime = -1; | |
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event | |
{ | |
if( scrollTouch_ == nil ) { | |
scrollTouch_ = touch; | |
} else { | |
return NO; | |
} | |
CGPoint touchPoint = [touch locationInView:[touch view]]; | |
touchPoint = [[CCDirector sharedDirector] convertToGL:touchPoint]; | |
startSwipe_ = (horizontalView) ? touchPoint.x : touchPoint.y; | |
startSwipeLayerPos_ = (horizontalView) ? [layerParant position].x : [layerParant position].y; | |
state_ = kFGScrollLayerStateIdle; | |
slidingSpeed = 0; | |
friction = DEFAULT_FRICTION; | |
return YES; | |
} | |
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event | |
{ | |
if( scrollTouch_ != touch ) { | |
return; | |
} | |
CGPoint touchPoint = [touch locationInView:[touch view]]; | |
touchPoint = [[CCDirector sharedDirector] convertToGL:touchPoint]; | |
// If finger is dragged for more distance then minimum - start sliding and cancel pressed buttons. | |
// Of course only if we not already in sliding mode | |
if(horizontalView) | |
{ | |
if ( (state_ != kFGScrollLayerStateSliding) | |
&& (fabsf(touchPoint.x-startSwipe_) >= self.minimumTouchLengthToSlide) ) | |
{ | |
state_ = kFGScrollLayerStateSliding; | |
// Avoid jerk after state change. | |
startSwipe_ = touchPoint.x; | |
startSwipeLayerPos_ = [layerParant position].x; | |
previousTouchPoint = touchPoint.x; | |
previousTouchTime = [touch timestamp]; | |
if (self.stealTouches) | |
{ | |
[self claimTouch: touch]; | |
} | |
if ([self.delegate respondsToSelector:@selector(scrollLayerScrollingStarted:)]) | |
{ | |
[self.delegate scrollLayerScrollingStarted: self]; | |
} | |
} | |
} | |
else | |
{ | |
if ( (state_ != kFGScrollLayerStateSliding) | |
&& (fabsf(touchPoint.y-startSwipe_) >= self.minimumTouchLengthToSlide) ) | |
{ | |
state_ = kFGScrollLayerStateSliding; | |
// Avoid jerk after state change. | |
startSwipe_ = touchPoint.y; | |
startSwipeLayerPos_ = [layerParant position].y; | |
previousTouchPoint = touchPoint.y; | |
previousTouchTime = [touch timestamp]; | |
if (self.stealTouches) | |
{ | |
[self claimTouch: touch]; | |
} | |
if ([self.delegate respondsToSelector:@selector(scrollLayerScrollingStarted:)]) | |
{ | |
[self.delegate scrollLayerScrollingStarted: self]; | |
} | |
} | |
} | |
if (state_ == kFGScrollLayerStateSliding) | |
{ | |
if(horizontalView) | |
{ | |
slidingSpeed = (touchPoint.x - previousTouchPoint)/([touch timestamp] - previousTouchTime) / 0.001; | |
CGFloat desiredX = [layerParant position].x + (touchPoint.x - previousTouchPoint); | |
// if we already out of the boundary, we need to slow down the drag | |
//if (desiredX > maxHorizontalPos_ || desiredX < 0) { | |
if (desiredX < maxHorizontalPos_ || desiredX > 0) { | |
desiredX = [layerParant position].x + (desiredX - [layerParant position].x) * 0.6; | |
} | |
[layerParant setPosition:ccp(desiredX, 0)]; | |
// update scrolling effect variables | |
previousTouchPoint = touchPoint.x; | |
previousTouchTime = [touch timestamp]; | |
} | |
else | |
{ | |
slidingSpeed = (touchPoint.y - previousTouchPoint)/([touch timestamp] - previousTouchTime) / 0.001; | |
CGFloat desiredY = [layerParant position].y + (touchPoint.y - previousTouchPoint); | |
// if we already out of the boundary, we need to slow down the drag | |
if (desiredY > maxVerticalPos_ || desiredY < 0) { | |
desiredY = [layerParant position].y + (desiredY - [layerParant position].y) * 0.6; | |
} | |
[layerParant setPosition:ccp(0, desiredY)]; | |
// update scrolling effect variables | |
previousTouchPoint = touchPoint.y; | |
previousTouchTime = [touch timestamp]; | |
} | |
} | |
} | |
- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event | |
{ | |
if( scrollTouch_ != touch ) | |
return; | |
scrollTouch_ = nil; | |
if ([touch timestamp] - previousTouchTime > 500) { | |
slidingSpeed = 0; | |
}else{ | |
slidingSpeed /= 1000; | |
// set a cap for sliding speed | |
slidingSpeed = MIN(1400, MAX(-1400, slidingSpeed)); | |
} | |
// CCLOG(@"sliding speed is %.02f", slidingSpeed); | |
state_ = kFGScrollLayerStateIdle; | |
// test whether we can directly into inertia state | |
[self switchToInertiaState]; | |
previousTouchPoint = -1; | |
previousTouchTime = -1; | |
} | |
// when layer is out of the boundary, we need to use an inernia effect to drag it back | |
-(void)switchToInertiaState{ | |
CGFloat layerPosition = (horizontalView) ? [layerParant position].x : [layerParant position].y; | |
CGFloat currentMaxPos = (horizontalView) ? maxHorizontalPos_ : maxVerticalPos_; | |
if(horizontalView) | |
{ | |
if (layerPosition < currentMaxPos) { | |
friction = (layerPosition - currentMaxPos) * 8; | |
slidingSpeed = -0.5 * friction; | |
state_ = kFGScrollLayerStateInertia; | |
} | |
if (layerPosition > 0) { | |
friction = -layerPosition * 8; | |
slidingSpeed = 0.5 * friction; | |
state_ = kFGScrollLayerStateInertia; | |
} | |
} | |
else | |
{ | |
if (layerPosition > currentMaxPos) { | |
friction = (layerPosition - currentMaxPos) * 8; | |
slidingSpeed = -0.5 * friction; | |
state_ = kFGScrollLayerStateInertia; | |
} | |
if (layerPosition < 0) { | |
friction = -layerPosition * 8; | |
slidingSpeed = 0.5 * friction; | |
state_ = kFGScrollLayerStateInertia; | |
} | |
} | |
} | |
// handle sliding effect | |
-(void)tick:(ccTime)dt{ | |
if (state_ == kFGScrollLayerStateIdle) | |
{ | |
layerParant.position = (horizontalView) ? | |
ccp([layerParant position].x + slidingSpeed * dt, [layerParant position].y) : | |
ccp([layerParant position].x, [layerParant position].y + slidingSpeed * dt); | |
if (slidingSpeed > 0) { | |
slidingSpeed = MAX(0, slidingSpeed - friction * dt); | |
}else{ | |
slidingSpeed = MIN(0, slidingSpeed + friction * dt); | |
} | |
// test whether we need to switch to inertia state | |
[self switchToInertiaState]; | |
} | |
else if (state_ == kFGScrollLayerStateInertia) | |
{ | |
if(horizontalView) | |
{ | |
if ([layerParant position].x == maxHorizontalPos_ || [layerParant position].x == 0) | |
{ | |
slidingSpeed = 0; | |
} | |
[layerParant setPosition:ccp([layerParant position].x + slidingSpeed * dt, [layerParant position].y)]; | |
if (slidingSpeed > 0) { | |
slidingSpeed = MAX(0, slidingSpeed - friction * dt); | |
[layerParant setPosition:ccp(MIN(maxHorizontalPos_, [layerParant position].x), [layerParant position].y)]; | |
}else if(slidingSpeed < 0){ | |
slidingSpeed = MIN(0, slidingSpeed + friction * dt); | |
[layerParant setPosition:ccp(MAX(0, [layerParant position].x), [layerParant position].y)]; | |
} | |
} | |
else | |
{ | |
if ([layerParant position].y == maxVerticalPos_ || [layerParant position].y == 0) | |
{ | |
slidingSpeed = 0; | |
} | |
[layerParant setPosition:ccp([layerParant position].x, [layerParant position].y + slidingSpeed * dt)]; | |
if (slidingSpeed > 0) { | |
slidingSpeed = MAX(0, slidingSpeed - friction * dt); | |
[layerParant setPosition:ccp([layerParant position].x, MIN(0, [layerParant position].y))]; | |
}else if(slidingSpeed < 0){ | |
slidingSpeed = MIN(0, slidingSpeed + friction * dt); | |
[layerParant setPosition:ccp([layerParant position].x, MAX(maxVerticalPos_, [layerParant position].y))]; | |
} | |
} | |
} | |
} | |
#endif | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ReportSize.h | |
// FallG | |
// | |
// Created by Dai Xuefeng on 19/5/13. | |
// Copyright (c) 2013 Nofootbird. All rights reserved. | |
// | |
// This protocol is used for rectangle node to report its size. | |
#import <Foundation/Foundation.h> | |
@protocol ReportSize <NSObject> | |
-(CGFloat)getWidth; | |
-(CGFloat)getHeight; | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment