3
votes

I am creating a custom view to display a single colour, it is for a colour picker from an image so the colour may change fairly quickly. To prevent the colour from flashing as the cursor moved I've made it so the colour change is animated over 0.2 seconds.

I'm doing this using a CALayer. As it stands currently this works almost as intended, however, I want an outline around the layer which has a slightly darker colour the the contents, similar to the OS X Yosemite close & resize window buttons.

Circle with dark outline

The method I'm currently using does work, however, sometimes the border colour animation seems to not follow the main colour animation, sometimes it goes through lighter colours. I thought I might be able to do this with filters, but I am absolutely stumped as to how to do this, I was unable to find much documentation online. I have very little experience with CoreAnimation, so I am unsure if this is the best way to do so or if it's blindingly obvious.

- (id)initWithFrame:(NSRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        _pickerColour = [NSColor whiteColor];
        strokeColour = [NSColor whiteColor];

        [self setWantsLayer: true];

        innerLayer = [CALayer layer];
        [innerLayer setCornerRadius: frame.size.width / 2];
        [innerLayer setBorderWidth: 4];
        [innerLayer setFrame: CGRectMake(frame.origin.x - frame.size.width / 2, frame.origin.y - frame.size.width / 2, frame.size.width - 2, frame.size.height - 2)];

        [self.layer addSublayer:innerLayer];
    }

    return self;
}

- (void) setPickerColour:(NSColor *)pickerColour{
    CABasicAnimation *backgroundAnimation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    backgroundAnimation.fillMode = kCAFillModeForwards;
    backgroundAnimation.removedOnCompletion = NO;
    backgroundAnimation.fromValue = (id)self.pickerColour.CGColor;
    backgroundAnimation.toValue = (id)pickerColour.CGColor;
    backgroundAnimation.duration = 0.1f;
    [innerLayer addAnimation:backgroundAnimation forKey:@"backgroundColor"];

    CGFloat h,s,b,a;
    [[self.pickerColour colorUsingColorSpaceName: NSCalibratedRGBColorSpace] getHue:&h saturation:&s brightness:&b alpha:&a];
    NSColor *newStrokeColour = [NSColor colorWithHue:h saturation:MIN(s, 1.0) brightness: MAX(b / 1.2, 0.0) alpha:a];

    CABasicAnimation *borderAnimation = [CABasicAnimation animationWithKeyPath:@"borderColor"];
    borderAnimation.fillMode = kCAFillModeForwards;
    borderAnimation.removedOnCompletion = NO;
    borderAnimation.fromValue = (id)strokeColour.CGColor;
    borderAnimation.toValue = (id)newStrokeColour.CGColor;
    borderAnimation.duration = 0.1f;
    [innerLayer addAnimation:borderAnimation forKey:@"borderColor"];

    strokeColour = newStrokeColour;
    _pickerColour = pickerColour;
}

- (NSColor *) pickerColour{
    return _pickerColour;
}
1
Might this simply be an effect of Core Animation working in RGB rather than HSV/HSB space? Certainly there are no guarantees that a path through the RGB cube won't pass through colors of varying brightness even if its endpoints are equally bright...warrenm
Well that's why I would like to use a filter or something similar so it shows the actual darker version, rather than just a transition.Oliver Cooper
You could use a keyframe animation with values that explicitly interpolate in HSV space rather than RGB space...warrenm
How would you do that? I'm brand new to CoreAnimation, I really wouldn't have a clue.Oliver Cooper
Here's the gist: you use CAKeyframeAnimation instead of CABasicAnimation, providing values and keyTimes arrays that contain a piecewise representation of your path through colorspace. You can use the same NSColor convenience method you're using, but you'll specify a sufficient number of colors to follow the desired arc in HSV space instead of the default linear trajectory through RGB space.warrenm

1 Answers

2
votes

By using a CAKeyframeAnimation instead of CABasicAnimation, you can provide values and keyTimes arrays that contain a piecewise representation of your path through colorspace. You can use the same NSColor convenience method you're using, but you'll specify a sufficient number of colors to follow the desired arc in HSV space instead of the default linear trajectory through RGB space.