/* * CCNotifications * * Copyright (c) 2010 ForzeField Studios S.L. * http://forzefield.com * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #import "CCNotifications.h" #import "CCArray.h" #import "notificationDesign.h" @implementation ccNotificationData @synthesize title = title_; @synthesize message = message_; @synthesize media = media_; @synthesize mediaType = mediaType_; @synthesize tag = tag_; @synthesize animated = animated_; - (void) dealloc { [self setTitle:nil]; [self setMessage:nil]; [self setMedia:nil]; [super dealloc]; } @end @interface CCNotifications (Private) - (void) _updateAnimationIn; - (void) _updateAnimationOut; - (CCActionInterval*) _animation:(char)type time:(ccTime)time; - (void) _showNotification; - (void) _addNotificationToArray:(ccNotificationData*)data cached:(BOOL)isCached; - (void) _startScheduler; - (void) _hideNotification; - (void) _hideNotificationScheduler; - (void) registerWithTouchDispatcher; - (void) _setState:(char)states; @end @implementation CCNotifications @synthesize position = position_; @synthesize notificationDesign = template_; @synthesize animationIn = animationIn_; @synthesize animationOut = animationOut_; @synthesize delegate = delegate_; @synthesize showingTime = showingTime_; @synthesize currentNotification = currentNotification_; static CCNotifications *sharedManager; + (CCNotifications *)sharedManager { if (!sharedManager) sharedManager = [[CCNotifications alloc] init]; return sharedManager; } + (id) alloc { NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton."); return [super alloc]; } + (id) systemWithTemplate:(CCNode *)notifications { NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton. You should use setTemplate"); sharedManager = [[CCNotifications alloc] initWithTemplate:notifications]; return sharedManager; } + (void) purgeSharedManager { [sharedManager release]; } - (id) init { CCNode *templates = [[[CCNotificationDefaultDesign alloc] init] autorelease]; self = [self initWithTemplate:templates]; return self; } - (id) initWithTemplate:(CCNode *)templates { if( (self = [super init]) ) { self.notificationDesign = templates; delegate_ = nil; state_ = kCCNotificationStateHide; typeAnimationIn_ = kCCNotificationAnimationMovement; typeAnimationOut_ = kCCNotificationAnimationMovement; timeAnimationIn_ = 0.0f; timeAnimationOut_ = 0.0f; cachedNotifications_ = [[CCArray alloc] initWithCapacity:4]; //Default settings showingTime_ = 4.0f; position_ = kCCNotificationPositionTop; [self setAnimation:kCCNotificationAnimationMovement time:0.5f]; //[self setAnimationIn:kCCNotificationAnimationMovement time:0.5f]; //[self setAnimationOut:kCCNotificationAnimationScale time:0.5f]; } return self; } - (void) _setState:(char)states { if(state_==states) return; state_ = states; if([delegate_ respondsToSelector:@selector(notification:newState:)]) [delegate_ notification:currentNotification_ newState:state_]; if([delegate_ respondsToSelector:@selector(notificationChangeState:tag:)]) [delegate_ notificationChangeState:state_ tag:[currentNotification_ tag]]; } - (void) setPosition:(char)positions { position_ = positions; [self updateAnimations]; } - (void) setNotificationDesign:(CCNode *) templates { if(state_!=kCCNotificationStateHide) [template_ stopAllActions]; if(state_==kCCNotificationStateShowing) { #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED [[CCTouchDispatcher sharedDispatcher] removeDelegate:self]; #endif [[CCScheduler sharedScheduler] unscheduleSelector:@selector(_hideNotificationScheduler) forTarget:self]; } [templates retain]; [template_ release]; template_ = templates; [template_ setVisible:NO]; [template_ setIsRelativeAnchorPoint:YES]; [self _setState:kCCNotificationStateHide]; } #pragma mark Notification Actions - (CCActionInterval*) _animation:(char)type time:(ccTime)time { CCActionInterval *action = nil; switch (type){ case kCCNotificationAnimationMovement: if(position_==kCCNotificationPositionBottom) action = [CCMoveBy actionWithDuration:time position:ccp(0, template_.contentSize.height)]; else if(position_ == kCCNotificationPositionTop) action = [CCMoveBy actionWithDuration:time position:ccp(0, -template_.contentSize.height)]; break; case kCCNotificationAnimationScale: action = [CCScaleBy actionWithDuration:time scale:(1.0f-KNOTIFICATIONMIN_SCALE)/KNOTIFICATIONMIN_SCALE]; break; default: break; } return action; } - (void) _updateAnimationIn { self.animationIn = [CCSequence actionOne:[self _animation:typeAnimationIn_ time:timeAnimationIn_] two:[CCCallFunc actionWithTarget:self selector:@selector(_startScheduler)]]; } - (void) _updateAnimationOut { CCActionInterval *tempAction = [self _animation:typeAnimationOut_ time:timeAnimationOut_]; self.animationOut = [CCSequence actionOne:[tempAction reverse] two:[CCCallFunc actionWithTarget:self selector:@selector(_hideNotification)]]; } - (void) updateAnimations { [self _updateAnimationIn]; [self _updateAnimationOut]; } - (void) setAnimationIn:(char)type time:(ccTime)time { typeAnimationIn_ = type; timeAnimationIn_ = time; [self _updateAnimationIn]; } - (void) setAnimationOut:(char)type time:(ccTime)time { typeAnimationOut_ = type; timeAnimationOut_ = time; [self _updateAnimationOut]; } - (void) setAnimation:(char)type time:(ccTime)time { typeAnimationIn_ = typeAnimationOut_ = type; timeAnimationIn_ = timeAnimationOut_ = time; [self updateAnimations]; } #pragma mark Notification steps - (void) _startScheduler { #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED [self registerWithTouchDispatcher]; #endif [self _setState:kCCNotificationStateShowing]; [template_ stopAllActions]; [[CCScheduler sharedScheduler] scheduleSelector:@selector(_hideNotificationScheduler) forTarget:self interval:showingTime_ paused:NO]; } - (void) _hideNotification { [self _setState:kCCNotificationStateHide]; [template_ setVisible:NO]; [template_ stopAllActions]; [template_ onExit]; //Release current notification [cachedNotifications_ removeObject:currentNotification_]; self.currentNotification = nil; //Check next notification [self _showNotification]; } - (void) _hideNotificationScheduler { #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED [[CCTouchDispatcher sharedDispatcher] removeDelegate:self]; #endif [[CCScheduler sharedScheduler] unscheduleSelector:@selector(_hideNotificationScheduler) forTarget:self]; if([currentNotification_ animated]) { [self _setState:kCCNotificationStateAnimationOut]; [template_ runAction:animationOut_]; }else [self _hideNotification]; } #pragma mark Manager Notifications - (void) _addNotificationToArray:(ccNotificationData*)data cached:(BOOL)isCached { if(isCached) { [cachedNotifications_ addObject:data]; if([cachedNotifications_ count]==1) [self _showNotification]; }else{ if(currentNotification_) { [cachedNotifications_ removeObject:currentNotification_]; } [cachedNotifications_ insertObject:data atIndex:0]; [self _showNotification]; } } - (ccNotificationData*) addWithTitle:(NSString*)title message:(NSString*)message image:(NSString*)image tag:(int)tag animate:(BOOL)animate waitUntilDone:(BOOL)isCached { ccNotificationData *data = [[ccNotificationData alloc] init]; data.title = title; data.message = message; data.media = image; data.mediaType = kCCNotificationMediaPath; data.tag = tag; data.animated = animate; [self _addNotificationToArray:data cached:isCached]; [data release]; return data; } - (ccNotificationData*) addWithTitle:(NSString*)title message:(NSString*)message texture:(CCTexture2D*)texture tag:(int)tag animate:(BOOL)animate waitUntilDone:(BOOL)isCached { ccNotificationData *data = [[ccNotificationData alloc] init]; data.title = title; data.message = message; data.media = texture; data.mediaType = kCCNotificationMediaTexture; data.tag = tag; data.animated = animate; [self _addNotificationToArray:data cached:isCached]; [data release]; return data; } - (ccNotificationData*) addWithTitle:(NSString*)title message:(NSString*)message image:(NSString*)image tag:(int)tag animate:(BOOL)animate { return [self addWithTitle:title message:message image:image tag:tag animate:animate waitUntilDone:YES]; } - (ccNotificationData*) addWithTitle:(NSString*)title message:(NSString*)message texture:(CCTexture2D*)texture tag:(int)tag animate:(BOOL)animate { return [self addWithTitle:title message:message texture:texture tag:tag animate:animate waitUntilDone:YES]; } /* Fast methods */ - (ccNotificationData*) addWithTitle:(NSString*)title message:(NSString*)message image:(NSString*)image { return [self addWithTitle:title message:message image:image tag:-1 animate:YES waitUntilDone:YES]; } - (ccNotificationData*) addWithTitle:(NSString*)title message:(NSString*)message texture:(CCTexture2D*)texture { return [self addWithTitle:title message:message texture:texture tag:-1 animate:YES waitUntilDone:YES]; } /* Deprecated */ - (void) addSafelyWithTitle:(NSString*)title message:(NSString*)message image:(NSString*)image tag:(int)tag animate:(BOOL)animate { [self addWithTitle:title message:message image:image tag:tag animate:animate waitUntilDone:YES]; } - (void) _showNotification { if([cachedNotifications_ count]==0) return; //Get notification data self.currentNotification = [cachedNotifications_ objectAtIndex:0]; //Stop system if(state_==kCCNotificationStateShowing) { #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED [[CCTouchDispatcher sharedDispatcher] removeDelegate:self]; #else [[CCEventDispatcher sharedDispatcher] removeMouseDelegate:self]; #endif } if(state_!=kCCNotificationStateHide) { [self _setState:kCCNotificationStateHide]; [template_ setVisible:NO]; [template_ stopAllActions]; [template_ onExit]; [[CCScheduler sharedScheduler] unscheduleSelector:@selector(_hideNotificationScheduler) forTarget:self]; } //Get variables CCTexture2D *texture = (currentNotification_.media) ? ((currentNotification_.mediaType==kCCNotificationMediaTexture) ? (CCTexture2D*)currentNotification_.media : [[CCTextureCache sharedTextureCache] addImage:(NSString*)currentNotification_.media]) : nil; //Prepare template [template_ setVisible:NO]; [template_ stopAllActions]; [template_ onExit]; //Prepare animation CGSize winSize = CGSizeMake(480,320); if(currentNotification_.animated) { if(position_==kCCNotificationPositionBottom){ [template_ setAnchorPoint:ccp(0.5f, 0)]; switch (typeAnimationIn_) { case kCCNotificationAnimationMovement: [template_ setScale:1.0f]; [template_ setPosition:ccp(winSize.width/2.0f, -template_.contentSize.height)]; break; case kCCNotificationAnimationScale: [template_ setScale:KNOTIFICATIONMIN_SCALE]; [template_ setPosition:ccp(winSize.width/2.0f, 0)]; break; default: return; } }else if(position_==kCCNotificationPositionTop) { [template_ setAnchorPoint:ccp(0.5f, 1)]; switch (typeAnimationIn_){ case kCCNotificationAnimationMovement: [template_ setScale:1.0f]; [template_ setPosition:ccp(winSize.width/2.0f, winSize.height+template_.contentSize.height)]; break; case kCCNotificationAnimationScale: [template_ setScale:KNOTIFICATIONMIN_SCALE]; [template_ setPosition:ccp(winSize.width/2.0f, winSize.height)]; break; default: return; } } [self _setState:kCCNotificationStateAnimationIn]; [template_ onEnter]; [template_ runAction:animationIn_]; }else{ if(position_==kCCNotificationPositionBottom) { [template_ setAnchorPoint:ccp(0.5f, 0)]; [template_ setPosition:ccp(winSize.width/2.0f, 0)]; }else if(position_==kCCNotificationPositionTop) { [template_ setAnchorPoint:ccp(0.5f, 1)]; [template_ setPosition:ccp(winSize.width/2.0f, winSize.height)]; } [self _startScheduler]; } //Update template [template_ setTitle:[currentNotification_ title] message:[currentNotification_ message] texture:texture]; [template_ setVisible:YES]; } #pragma mark Touch Events #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED - (void) registerWithTouchDispatcher { [[CCTouchDispatcher sharedDispatcher] addStandardDelegate:self priority:INT_MIN]; } - (void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]]; CGRect rect = [template_ boundingBox]; if(CGRectContainsPoint(rect, point)) if([delegate_ respondsToSelector:@selector(touched:)] && [delegate_ touched:[currentNotification_ tag]]) [self _hideNotificationScheduler]; } #endif #pragma mark Other methods - (void) visit { [template_ visit]; } - (NSString*) description { return [NSString stringWithFormat:@"<%@ = %08X>", [self class], self]; } -(void) dealloc { CCLOG(@"cocos2d: deallocing %@", self); sharedManager = nil; [cachedNotifications_ release]; [self setCurrentNotification:nil]; [self setNotificationDesign:nil]; [self setDelegate:nil]; [self setAnimationIn:nil]; [self setAnimationOut:nil]; [super dealloc]; } @end