Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change NSSlider bar color

I've got an NSWindow and an horizontal NSSlider.

I'd like to change the color of the right part of the slider bar when the window background color changes.

enter image description here

enter image description here

Currently, the right part of the bar isn't visible anymore when the window background is dark.

Note: the window background is actually a gradient I'm drawing by overriding drawRect in a subclass of the window's view.

I thought I could change the slider bar fill color by subclassing NSSliderCell and overriding some method like drawBarInside but I don't understand how it works: should I make a little square image and draw it repeatedly in the rect after the knob? Or maybe just draw a colored line? I've never done this before.

I've looked at this question and it's interesting but it's about drawing the knob and I don't need that for now.

I also had a look at this one which seemed very promising, but when I try to mimic this code my slider bar just disappears...

In this question they use drawWithFrame and it looks interesting but again I'm not sure how it works.

I would like to do this with my own code instead of using a library. Could somebody give me a hint about how to do this please? :)

I'm doing this in Swift but I can read/use Objective-C if necessary.

like image 613
Eric Aya Avatar asked Apr 19 '15 12:04

Eric Aya


2 Answers

This is correct, you have to subclass the NSSliderCell class to redraw the bar or the knob.

NSRect is just a rectangular container, you have to draw inside this container. I made an example based on an custom NSLevelIndicator that I have in one of my program.

First you need to calculate the position of the knob. You must pay attention to the control minimum and maximum value. Next you draw a NSBezierPath for the background and another for the left part.

Custom NSSlider control bar

#import "MyCustomSlider.h"

@implementation MyCustomSlider

- (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped {

//  [super drawBarInside:rect flipped:flipped];

    rect.size.height = 5.0;

    // Bar radius
    CGFloat barRadius = 2.5;

    // Knob position depending on control min/max value and current control value.
    CGFloat value = ([self doubleValue]  - [self minValue]) / ([self maxValue] - [self minValue]);

    // Final Left Part Width
    CGFloat finalWidth = value * ([[self controlView] frame].size.width - 8);

    // Left Part Rect
    NSRect leftRect = rect;
    leftRect.size.width = finalWidth;

    NSLog(@"- Current Rect:%@ \n- Value:%f \n- Final Width:%f", NSStringFromRect(rect), value, finalWidth);

    // Draw Left Part
    NSBezierPath* bg = [NSBezierPath bezierPathWithRoundedRect: rect xRadius: barRadius yRadius: barRadius];
    [NSColor.orangeColor setFill];
    [bg fill];

    // Draw Right Part
    NSBezierPath* active = [NSBezierPath bezierPathWithRoundedRect: leftRect xRadius: barRadius yRadius: barRadius];
    [NSColor.purpleColor setFill];
    [active fill];


}

@end
like image 157
Atika Avatar answered Sep 28 '22 05:09

Atika


First, I created an image of a slider bar and copied it in my project.

Then I used this image in the drawBarInside method to draw in the bar rect before the normal one, so we'll see only the remainder part (I wanted to keep the blue part intact).

This has to be done in a subclass of NSSliderCell:

class CustomSliderCell: NSSliderCell {

    let bar: NSImage

    required init(coder aDecoder: NSCoder) {
        self.bar = NSImage(named: "bar")!
        super.init(coder: aDecoder)
    }

    override func drawBarInside(aRect: NSRect, flipped: Bool) {
        var rect = aRect
        rect.size = NSSize(width: rect.width, height: 3)
        self.bar.drawInRect(rect)
        super.drawBarInside(rect, flipped: flipped)
    }

}

Pro: it works. :)

Con: it removes the rounded edges of the bar and I haven't found a way to redraw this yet.

custom nsslider

UPDATE:

I made a Swift version of the accepted answer, it works very well:

class CustomSliderCell: NSSliderCell {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func drawBarInside(aRect: NSRect, flipped: Bool) {
        var rect = aRect
        rect.size.height = CGFloat(5)
        let barRadius = CGFloat(2.5)
        let value = CGFloat((self.doubleValue - self.minValue) / (self.maxValue - self.minValue))
        let finalWidth = CGFloat(value * (self.controlView!.frame.size.width - 8))
        var leftRect = rect
        leftRect.size.width = finalWidth
        let bg = NSBezierPath(roundedRect: rect, xRadius: barRadius, yRadius: barRadius)
        NSColor.orangeColor().setFill()
        bg.fill()
        let active = NSBezierPath(roundedRect: leftRect, xRadius: barRadius, yRadius: barRadius)
        NSColor.purpleColor().setFill()
        active.fill()
    }

}
like image 24
Eric Aya Avatar answered Sep 28 '22 05:09

Eric Aya