I have a selectable color palette made up of a bunch of NSButtons. I overload the drawRect function in order to draw the buttons as a central colored circle along with a border that indicates if the button is selected (using NSBezierPath fill and stroke). This all works great, until I place the palette on a NSPopover, which causes the buttons to be drawn partially transparent. Now my colors in the buttons change depending on what's beneath the popover. It's very noticeable for the black button (lower left corner).
Is there a way to disable the transparency for just the center of the button on the popover, without disabling it completely for all controls?
I figured this out - overriding the allowsVibrancy property of the button to return NO disables the transparency effect.
Objective-C:
- (BOOL)allowsVibrancy {
return NO;
}
Swift:
override var allowsVibrancy: Bool { return false }
Related
I have a NSVisualEffectView with vibrancy containing text fields (NSTextField or NSComboBox) and borderless buttons. The buttons are positioned over the text fields and I want to disable the vibrancy effect on the borderless buttons since they're supposed to appear on the white background of the text fields.
What I tried doing, as per recommendation in the NSVisualEffectView class reference, is to wrap my NSButton inside another NSVisualEffectView with its state set to Inactive. What this does is it replaces the "vibrant" background by a light grey background.
The picture below illustrates this. The first field is my attempted solution, the second shows the default behavior of a borderless button as child of a NSVisualEffectView.
I also tried subclassing the NSButton and set its cell background color to white or clear but I always get the grey background.
How can I change the light grey background to a white or clear background?
Thanks
I managed to solve this myself after a few hours of headache. The solution doesn't need the button to be wrapped in a NSVisualEffectView. Simply subclassing NSButton and overriding the allowsVibrancy property and setting it to false was enough.
In Swift:
override var allowsVibrancy: Bool { return false }
UPDATE:
I've added a sample project for testing, see at the bottom of the post.
ORIGINAL QUESTION:
I've got an NSWindow and I change its background when some other parameters change.
The window background is a gradient I'm drawing by overriding drawRect in a subclass of the window's view.
class MainWindowView: NSView {
override func drawRect(dirtyRect: NSRect) {
var rect = dirtyRect
let gradient = NSGradient(startingColor: backgroundColor, endingColor: darkerBackgroundColor)
gradient.drawInRect(rect, relativeCenterPosition: NSPoint(x: 0, y: 0))
super.drawRect(rect)
}
}
And I've got two NSTextFields on this window.
The NSTextFields are both set to drawsBackground = false in awakeFromNib and set to borderless in IB.
I'm not using NSAttributedStrings here, only changing the stringValue of the NSTextFields, and of course their textColor.
Everything is working... except that sometimes, the text fields have an unexpected slightly dark semi-transparent background.
(It's hard to see on some screens but it's there.)
Question: why does this darker background appear?
And of course: what could I do to fix it?
I'm pretty sure it's the gradient that breaks something but I can't find what...
Note: the project is in Swift but I can read an Objective-C answer.
EDIT:
So indeed it seems to be coming from the gradient that's behind, see this other screenshot from a test window. This time the gradient is drawn in a Custom View under an NSTextView, and the same undesired effect happens: parts of the text field background are visible but shouldn't.
UPDATE:
I have made a very simple example in a project for testing, with a gradient that shows the phenomenon more visibly. There's only a window, my gradient class and a text field. You can get it (30ko only) in this ZIP file.
You always draw the gradient in the dirty rect. When the text changes, that rect is only the size of the textfield, not of the whole view. Your drawRect function then draws the full gradient in the textfield's background rect, rather than just the portion of the background-view-wide gradient you'd see through the textfield.
If you redraw using your view's frame, and ignore the dirty rect argument, you should get the desired appearance.
I'm guessing your text field isn't layer-backed. If not, turn on layers (in IB or via -wantsLayer for the view in code) for at least the text field. If that alone doesn't work, try turning on layers for the gradient-hosting view as well.
I have a window with a custom view in it. This view get's an image drawn as a background in it. Let's assume for now that it's a structured grey background image.
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
[self.titlebarBGImage drawInRect:dirtyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1];
}
In this custom view I added a square textured button and configured it in IB to have no border. In my AppDelegate I then give this button an image in the applicationDidFinishLaunching method:
// ...
// [[self.minimizeButton cell] setBackgroundColor:[NSColor clearColor]];
[self.minimizeButton setImage:minimizeImage];
// ...
Somehow the same background (titlebarBGImage) is also drawn as the buttons background, so it has a structured grey colored image, but scaled to fit the button size. This looks silly and is not what I want obviously. If I then uncomment the line with setBackgroundColor: the background is cleared, but the custom views background also doesn't shine through where the buttons image is transparent (the button image is a circle) so I get a rectangle where the windows background shines through (i.e. green in the example image), not the background of the view the button is in.
Here's an image to make it clear what happens:
From top to bottom:
What I want to achieve, i.e. views grey structured bg shines through buttons transparent area
What I get with upper code, i.e. without the uncommented line: The custom views background is drawn squeezed into the buttons background
what I get with the clearColor line, i.e. window color (green) shines through, not the bg of the view.
How can I get the top most result?
Edit: I pushed an example project to github so that you can see what I mean.
One way to solve this is using Core Animation Layers. I found out watching this tutorial. For that you got to your MainMenu.xib file, select the Button object and click on the layers tab in the Utilities (see screenshot (1)). Then activate Core Animation Layer for the top most View. With that all subelements also have Core Animation Layer activated (There might be performance issues with that if the project is bigger, so take care).
Next I subclassed the NSButton (calling it BPButtonView in my Github project which I updated now). Select the Button in the xib-file and change the Custom Class to BPButtonView, or whatever you called it.
The code for the new NSButton subclass looks like this:
#implementation BPButtonView
- (BOOL)wantsUpdateLayer {
return YES;
}
- (void)updateLayer {
if ( [self.cell isHighlighted] ) {
self.layer.contents = [NSImage imageNamed:#"buttonbg.png"];
} else {
self.layer.contents = [NSImage imageNamed:#"buttonbg.png"];
}
}
#end
I used the if-statement in the updateLayer method just to show, that depending of the buttons state (pressed vs. unpressed) you can provide a different picture. In the Video tutorial there's also a nice trick how to achieve a clean resize of a buttons picture.
And here's a screenshot of the resulting app. The red circle in the middle is the button and the green textured thing is the views background.
Can I just want to replace the appearance by setting a property of NSButton?
I am able to change the appearance by using the "image" property of the button but I got an ugly gray rectangle whenever the custom button is clicked (the image has a transparency). Is there a way to hide that rectangle?
Thanks.
The following call changes the behavior by making image be darkened when clicked:
[[button cell] setHighlightsBy:NSContentsCellMask];
I am developing a desktop application in which I want to change the color of the title bar of an NSWindow. How exactly can I do this?
NSWindow's content view has a superview, which is an instance of NSThemeFrame. That class is responsible for drawing the title text, the window/toolbar background texture, and it contains subviews for everything else (close button, full screen button, NSDocument icon, etc).
You can use the Objective-C runtime to replace NSThemeFrame's drawRect: method with your own method, which will call the parent implementation and then perform custom drawing on top of it.
There is also a private method to find the rect the title is drawn in, and public methods on NSFont to find it's font and font size.
What I did is set the window background colour to be a solid colour (black) instead of a gradient/texture, then set it to be a "textured" window (which causes the background colour to actually be rendered, otherwise it would not happen), then I draw a black square over the title bar in the area where I know the title has already been drawn, then draw my own title in it's place, with light grey instead of dark grey.
Source code is here: https://github.com/abhibeckert/Dux/blob/master/Dux/DuxProjectWindow.m (note: it only does a custom title text colour if DUX_DARK_MODE == 1)
Doing this will probably get your app blocked from the Mac App Store, but it is fairly reliable. Just make sure you test it with every new major version of OS X.
To change the color of the window's toolbar:
Set window style Textured in Attribute inspector.
In code: [window setBackgroundColor: MyCustomColor];
This uses private methods, but works:
NSEnumerator *viewEnum = [[[[[[window contentView] superview] titlebarViewController] view] subviews] objectEnumerator];
NSView *viewObject;
while(viewObject = (NSView *)[viewEnum nextObject]) {
if([viewObject className] == #"NSTextField") [viewObject setTextColor: .. your color .. ];
}