pmanna Personal Site
cocos2d 0.99.4
Sometimes, in a menu, it's necessary to input a value from a continuous range of possibilities: typically, in regular UI this is the work for a slider. Unfortunately, cocos2d doesn't have an actual slider widget, but it's relatively easy to add one.
The first step has been to tell CCMenu to track a touch: subclass ccTouchMoved:withEvent: as follows
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event { NSAssert(state == kMenuStateTrackingTouch, @"[Menu ccTouchMoved] -- invalid state"); CCMenuItem *currentItem = [self itemForTouch:touch]; if (currentItem != selectedItem) { [selectedItem unselected]; selectedItem = currentItem; [selectedItem selected]; } else { if ([selectedItem respondsToSelector: @selector(dragToPoint:)]) { CGPoint touchLocation = [selectedItem convertTouchToNodeSpace: touch]; [selectedItem dragToPoint: touchLocation]; } } }
Next, define a new CCMenuItem subclass:
@interface CCMenuItemSlider : CCMenuItem <CCRGBAProtocol> { float minValue_; float maxValue_; float value_; BOOL isVertical; CCNode<CCRGBAProtocol> *trackImage_, *knobImage_; } /** returns the minimum */ @property (nonatomic,readwrite) float minValue; /** returns the maximum */ @property (nonatomic,readwrite) float maxValue; /** returns the value */ @property (nonatomic,readwrite) float value; /** the image for the sliding track */ @property (nonatomic,readwrite,retain) CCNode<CCRGBAProtocol> *trackImage; /** the image for the knob */ @property (nonatomic,readwrite,retain) CCNode<CCRGBAProtocol> *knobImage; /** creates a menu item with a track and knob image*/ +(id) itemFromTrackImage: (NSString*)value knobImage:(NSString*) value2; /** creates a menu item with a track and knob image with target/selector */ +(id) itemFromTrackImage: (NSString*)value knobImage:(NSString*) value2 target:(id) t selector:(SEL) s; /** initializes a slider menu item from two images with a target selector */ -(id) initFromTrackImage: (NSString *)trkImage knobImage: (NSString *)knbImage target: (id)target selector: (SEL)selector; /** Drag the knob around */ -(void) dragToPoint: (CGPoint)aPoint; @end
The actual implementation looks like this:
// // MenuItemSlider // @implementation CCMenuItemSlider @synthesize minValue=minValue_, maxValue=maxValue_, value=value_; @synthesize trackImage=trackImage_, knobImage=knobImage_; +(id) itemFromTrackImage: (NSString*)value knobImage:(NSString*) value2 { return [[[self alloc] initFromTrackImage:value knobImage:value2 target:nil selector:nil] autorelease]; } +(id) itemFromTrackImage: (NSString*)value knobImage:(NSString*) value2 target:(id) t selector:(SEL) s { return [[[self alloc] initFromTrackImage:value knobImage:value2 target: t selector: s] autorelease]; } -(id) initFromTrackImage: (NSString *)trkImage knobImage: (NSString *)knbImage target: (id)target selector: (SEL)selector { if( (self=[super initWithTarget:target selector:selector]) ) { self.trackImage = [CCSprite spriteWithFile: trkImage]; self.knobImage = [CCSprite spriteWithFile: knbImage]; // Content size of the track is our reference // Knob must lie within [self setContentSize: trackImage_.contentSize]; [self addChild: knobImage_ z:2]; isVertical = (self.contentSize.height > self.contentSize.width); self.minValue = 0.0f; self.maxValue = 100.0f; self.value = 50.0f; } return self; } - (void)setValue: (float)aValue { float valueRatio; if (isVertical) valueRatio = (self.contentSize.height - knobImage_.contentSize.height) / (maxValue_ - minValue_); else valueRatio = (self.contentSize.width - knobImage_.contentSize.width) / (maxValue_ - minValue_); if (aValue < minValue_) value_ = minValue_; else if (aValue > maxValue_) value_ = maxValue_; else value_ = aValue; if (isVertical) knobImage_.position = CGPointMake(self.contentSize.width / 2, (value_ - minValue_) * valueRatio + knobImage_.contentSize.height / 2); else knobImage_.position = CGPointMake((value_ - minValue_) * valueRatio + knobImage_.contentSize.width / 2, self.contentSize.height / 2); } - (void)draw { [trackImage_ draw]; } -(void) dragToPoint: (CGPoint)aPoint { float valueRatio; float absValue; if (isVertical) { valueRatio = (maxValue_ - minValue_) / (self.contentSize.height - knobImage_.contentSize.height); absValue = aPoint.y - knobImage_.contentSize.height / 2; } else { valueRatio = (maxValue_ - minValue_) / (self.contentSize.width - knobImage_.contentSize.width); absValue = aPoint.x - knobImage_.contentSize.width / 2; } self.value = minValue_ + absValue * valueRatio; [self activate]; } #pragma mark CCMenuItemSlider - CCRGBAProtocol protocol - (void) setOpacity: (GLubyte)opacity { [trackImage_ setOpacity:opacity]; [knobImage_ setOpacity:opacity]; } -(void) setColor:(ccColor3B)color { [trackImage_ setColor:color]; [knobImage_ setColor:color]; } -(GLubyte) opacity { return [trackImage_ opacity]; } -(ccColor3B) color { return [trackImage_ color]; } @end
Finally, the use of it is quite simple:
CCMenuItemSlider *slider = [CCMenuItemSlider itemFromTrackImage: @"SliderTrack.jpg" knobImage: @"SliderKnob.png" target: self selector: @selector(changeMaxNumber:)]; slider.minValue = 10; slider.maxValue = 100; slider.value = maxNum;
Sliders of different styles aren't hard to do, the code handles automatically if the slider is horizontal or vertical: the only care the designer should take is that the track (i.e. the fixed part) is used to calculate the actual content size, so it should include the whole area intended to be used as slider. If knob is meant to be bigger than the track, just make the exceeding content transparent.