Cocoa HUD Window: remove transparency? - cocoa

I have a standard NSPanel set to HUD style. I want to change the background color, primarily because I want to have a toolbar and don't see any way of making either a standard nstoolbar look good on a HUD nor a way of customizing the background of a toolbar directly.
I am aware of the multitude of ways for creating a completely custom window, and use those in other circumstances. In this case, I want all of the good things that a window provides, but I just don't want transparency. Interestingly, I can change the background color, but not the alpha. Setting alpha values has no effect on the window.
Anyone solved this problem before?

Set the panel's content-view's (just click inside the panel to select it, not the titlebar) subclass to SGPanelView and make that SGPanelView with this implementation of a drawRect method of your class: SGPanelView, a subclass of NSView:
- drawRect:(NSRect)dirtyrect {
[[NSColor blackColor] set];
[NSBezierPath fillRect:[self bounds]];
}
Should work. If not working, try changing bounds to frame.
See http://developer.apple.com/library/mac/#documentation/cocoa/reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html for more information.

Related

NSOutlineView with transparent field editor

I'm working with a NSOutlineView located on a HUD panel. I configured it so that it doesn't draw its background. Everything looks fine until I double click to edit a cell.
The field editor draws its background and focus ring which completely ruin the whole user experience.
This is what I'm doing in the subclass of NSTextFieldCell:
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
{
NSText *text = [super setUpFieldEditorAttributes:textObj];
[text setDrawsBackground:YES];
[text setBackgroundColor:[NSColor darkGrayColor]];
return text;
}
If I use setDrawsBackground:NO it's completely ignored and I get a white background. My solution is far from being good because I can't touch the alpha component of the color (if I do that, again the field editor will use another color as a background), but at least I don't get a white background.
I'm wondering if there's an actual solution to this problem. Do I have to provide my own field editor? Is it worth it?
What I want is simply a field editor with no background and no focus ring, just the cursor blinking.
Thanks!
The problem is that the white background is drawn by NSTableView when it's sent -editColumn:row:withEvent:select:. It fills the cell's rect with +[NSColor textBackgroundColor].
If there's a public API for overriding the current setting for named colors from the developer colorspace, we could set it inside an override of -editColumn:row:withEvent:select: or the like. I do not recall such an API (pointers are appreciated). ALSO: I've only tested this code on Snow Leopard (even the Leopard SDK addendum below). Verify the code against the actual SDKs and runtime environments you intend to support.
NSTableView has a private accessor it uses for the fill color, but it's a read-only property. No setter, so we can't just change the value on a standard NSTableView. We must subclass it. (Since you want the same behavior in an outlineView and NSOutlineView is already a subclass of NSTableView, we're going to subclass NSOutlineView. But, aside from the superclass, the code is identical.)
#interface ASCOutlineView : NSOutlineView {
}
#end
#implementation ASCOutlineView
- _textBackgroundColor
{
return ([NSColor clearColor]);
}
#end
seems to be all one needs to prevent that glaring white block from ruining your HUD when editing table cells in Snow Leopard.
Apps compiled against the Leopard SDK need a little more support though. Leopard's tableViews may have hard-coded some rendering properties so we need to override a choice method.
NSTextFieldCells are actually wrappers for NSTextViews so they can be used inside controls. They normally share the same textView instance, which is managed by the window (or its subclass, panel, in this case). NSTableView alters the settings of the NSTextFieldCell to conform to system UI settings for editing data. Mostly. The NSTextFieldCell then propagates those settings to the NSTextView. At any point along this pipeline we can override a method or two to alter the values of those properties to match our own UI.
I use -[NSTextFieldCell setDrawsBackground:] because it requires little effort to get correct. It's also important to keep the internal state as consistent with the effect we're hoping to achieve in the event some other object might depend on that state.
#interface ASCTextFieldCell : NSTextFieldCell {
}
#end
#implementation ASCTextFieldCell
- (void)setDrawsBackground: (BOOL)flag
{
[super setDrawsBackground: NO];
}
#end
And preventing the focus ring from appearing while the cell's being edited is a simple matter of changing the setting of its focus ring type. Frustratingly, IB doesn't provide access to this property, so it must be done programmatically:
for(eachColumn in [hudOutlineView tableColumns])
{
columnCell = [[ASCTextFieldCell alloc] initTextCell: #""];
[eachColumn setDataCell: columnCell];
if([columnCell respondsToSelector: #selector(setFocusRingType:)] != NO)
[(NSTextFieldCell *)columnCell setFocusRingType: NSFocusRingTypeNone];
}
It looks like there is other background behind field editor, which is drawn as white.
Probably, NSCell, or background of row, whatever else.

NSScrollview and transparent, overlay NSScroller subclasses

I have made a slick NSScroller subclass, but can't figure out how to make it overlay on top of the NSScrollView instead of pushing the documentView aside.
Here you can see the background of a NSCollectionView that I wish to make 100% wide, and have the scroller sit along top. Currently, I have to set a white background to the scroller because drawing with a clearColor is not showing as transparent, but as black.
Am I going about this the wrong way? Am I missing something obvious here? How can I achieve the behavior of a transparent-tracked NSScroller that sits atop a NSScrollView's contents?
I was able to get the positioning by implementing tile in the subclass OF NSSCROLLVIEW
- (void)tile {
[super tile];
[[self contentView] setFrame:[self bounds]];
}
And it turns out that trying to draw clear color was the problem to begin with. In the scroller subclass, I just omitted any drawing that had to do with the slider track completely BY OVERRIDING DRAWRECT: OF THE NSSCROLLER SUBCLASS, LIKE SO:
- (void)drawRect:(NSRect)dirtyRect
{
[self drawKnob];
}
Note that for this to work properly, you MUST enable layer-backing for the scrollView!
That is, call:
[scrollViewInstance setWantsLayer:YES];
or set it in Interface Builder.
If you don't do this, the scrollView's contentView will draw ON TOP OF the scrollers. Also: you should be aware that what you're doing is essentially overlapping two NSViews (NSScroller on top of NSScrollView --- both inherit from NSView.) Unlike UIViews on iOS, overlapping NSViews on OS X is not officially supported by any current version of the OS (10.6 down). Turning on CALayers seems to make it work, but it's still something to bear in mind. Of course, turning on layers can seriously kill drawing performance.
See this SO question for more detail: Is there a proper way to handle overlapping NSView siblings?
What's about this color: [NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:0.0]
I think you have to modify the sizes with the interface builder.

NSTextField over NSOpenGLView

I have made a window with an NSOpenGLView that I am rendering openGL content into.
I want to add some buttons and text fields to the view: I can add NSTextFields and NSButtons using interface builder (or code) but they do not appear.
NSOpenGLView is documented as not being able to have sub views, so I then made my own CustomGLView by deriving directly from NSView and implementing the code to create and use a NSOpenGLContext in it. But the subviews are still not appearing :- the OpenGL context paints over them.
On Windows this problem does not exist:- Windows used to host OpenGL MUST have the WS_CLIPCHILDREN and WS_CHIPSIBLINGS styles set ensuring that any peer, or sub children (views) will not be obscured by the OpenGL surface.
How do I get subviews to display over a NSView thats drawing using OpenGL ?
You have 2 choices:
Create a window just for the text field. Add as a child window of the one hosting the OpenGL view. Major downside is you have to manage positioning it correctly if the Open GL view is moved.
Set up your view hierarchy like so:
Layer-backed view
Layer-hosting view whose layer contains an OpenGL layer
Text field
Simply call -setWantsLayer:YES on the subviews of the NSOpenGLView.
NSOpenGLView cannot have subviews according to the documentation. Even if you subclass the NSOpenGLView, that will change nothing.
What you can do is to create a NSView that will hold both the NSOpenGLView and the NSTextField. You then overlap them in the right order to make one draw atop the other.
I'm not heavily into OpenGL yet, but it's my understanding that you can accomplish the visual effect of subviews with Quartz Extreme using layer-backed views; however, those may be problematic. Since subviews are not supported directly, any solution is liable to be a hack.
Indeed, the solution in that link actually hacks a second window to appear over your OpenGL display, the second window displaying the Cocoa views you desire.
The following code (from the above link) is something I've not tested (again not being an OpenGL guy by nature -- yet), but appears like a fairly clever approach:
// This is the GL Window and view you already have
glWindow = [[GLWindow alloc] initWithContentRect:windowRect];
glView = [[[GLView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[glView translateOriginToPoint:NSMakePoint(glView.bounds.size.width/2, glView.bounds.size.height/2)];
[glWindow setContentView:glView];
// And here's your transparent UI window
uiWindow = [[TransparentWindow alloc] initWithContentRect:windowRect];
uiView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[uiView translateOriginToPoint:NSMakePoint(uiView.bounds.size.width/2, uiView.bounds.size.height/2)];
uiView.wantsLayer = YES;
[uiWindow setContentView:uiView];
[glWindow addChildWindow:uiWindow ordered:NSWindowAbove];
Again, I've not tested this, but it looks like it will get you the visual effect you desire.
The text can be rendered into a texture -- I just used this for a project, did a lot of looking for sample code, and ultimately found Apple's GLString demo code, which was an absolute trove of how-to:
http://developer.apple.com/library/mac/#samplecode/CocoaGL/Listings/GLString_m.html
I haven't tried adding buttons, but you can, of course, draw your own and comparing the positions of click events with those of your buttons...
This was my solution:
1) Create a parent NSView (let's call it parentView).
2) Add an NSOpenGLView Child to parentView.
3) Add an additional NSView Child to parentView (make sure this is after the OpenGLView within the hierarchy). You can add additional TextFields, etc. to this view.
4) In the ViewController for the parent make sure you call [parentView setWantsLayer: TRUE]; I did this within -(void) viewWillAppear
1) The NSOpenGLView can have a subview. It can have plenty even.
2) The reason some views, controls and other elements are being bullied by NSOpenGLView is due to the loading process when the Application launches. I.e If you add a slider or textfield above and into the content view of the window where the NSOpenGLView also resides, upon Application-Launch that textfield will most likely wind up beneath the NSOpenGLView.
This is an Apple Bug. And they know about it.
You can solve it quite easily even without adding a subview to NSOpenGLView...
In Interface Builder drag i.e. a CustomView into the canvas (Not the view). And set it the way you want it with sliders, text and what not. Then create an outlet (Call it i.e topView) in your view controller. Then somewhere in your code... Perhaps (applicationDidFinishLaunching) add this line...
[_window.contentView addSubview:_topView];
(Do your positioning & layout)
This will do the exact same thing as if you had dragged it into the contentView yourself inside IB. Only it will draw the darn thing in the correct Z position.
You loose IB's constraints this way and have to it manually
One could also just subclass and CAOpenGLLayer and use that as a backing layer inside of a regular NSView. There too it is drawn correctly...
Here is Apple's way of wanting to do that. CALayers are a Godsend ;)
Enter following String ** NSOpenGLLayer** in search and hit enter to get to where it is...
NSOpenGLLayer
Hope this helps....

Cocoa: [statusItem setView:myView] makes a white bar menu item no matter what

In my small app for Mac OS X I display some info in system menubar. I use
statusItem = [
[[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength]
retain
];
It works very nice and I can change the text with
[statusItem setTitle:[NSString stringWithString:#"Woo-hoo"]];
But it uses the default menu font which is too big for my relatively unimportant info. So I decided to reimplement it with a custom view. I created a view in Interface Builder.
Unfortunately, however, when I set it as a view for my menu item with
[statusItem setView:myView];
it just displays a white bar in the menu instead of my thing. I tried to
[statusItem
drawStatusBarBackgroundInRect:[myView frame]
withHighlight:NO];
with no success.
In trying to figure out whether a problem is with the view itself or with the way I assign it to the menubar, I created a window and did
[myTestWindow setContentView:myView];
This one worked seamlessly. This makes me think my view is OK :-)
So, what else can I try to make the menu item display my own view?
Thanks!
It happened to be some weird side-effects of window-view autosizing setup in Interface Builder (let’s call them size-effects). In the Inspector you can setup how subviews get resized upon superview sizing. And so it was somehow broken in my case, such that when window gets small enough (menuitem-high), my elements just got drawn outside of the window’s frame.
I re-configured the sizing in IB, eliminating all the automatics I don’t need, and now it works perfectly: the view from IB gets displayed inside a menu item.
What is the height of the frame of the view? Maybe your view is taller than the menubar and you are drawing outside of it. The current menubar is 22 pixels, but you should ask the systemStatusBar for it's thickness, just in case it ever changes.
Try drawing a frame around your view to see if you are getting anything.
[[NSColor blueColor] set];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:self.bounds];
[path setLineWidth:4.0f];
[path stroke];
If you get just an 'L' shape (the bottom left corner) of blue then the view is too large. If you get a rectangle but still no text then you may not be drawing the text inside the view, look at the coordinates you are drawing the text at (and review View Geometry). Putting the view in a window may have worked because it is larger.
For an example of using text in a status menu view take a look at Matt Gemmell's NSStatusItemTest project.
EDIT:
Sorry, somehow I missed where you said you created the view in IB. I did a quick test and I can see the white box you mentioned.
The docs for NSStatusItem's setView: states
The custom view is responsible for
drawing itself and providing its own
behaviors, such as processing mouse
clicks and sending action messages.
And status item views go into a special (apple private) window called NSStatusBarWindow that may have different internal behavior than normal windows and certainly seems to not support views from IB.
So yes, I think you need to create a custom NSView subclass and do your own drawing in drawrect:.

How to make a transparent NSView subclass handle mouse events?

The problem
I have a transparent NSView on a transparent NSWindow. The view's drawRect: method draws some content (NSImages, NSBezierPaths and NSStrings) on the view but leaves parts of it transparent.
Clicking on the regions of the view that have been drawn on invokes the usual mouse event handling methods (mouseDown: and mouseUp:).
Clicking on the transparent areas gives focus to whatever window is behind my transparent window.
I would like to make parts of the transparent region clickable so that accidentally clicking between the elements drawn on my view does not cause the window to lose focus.
Solutions already attempted
Overriding the NSView's hitTest: method. Found that hitTest: was only called when clicking on a non-transparent area of the view.
Overriding the NSView's opaqueAncestor method. Found that this was not called when clicking on any part of the view.
Filling portions of the transparent area with [NSColor clearColor] in the drawRect: method, and with an almost-but-not-quite-transparent colour. This had no effect.
Experimented with the NSTrackingArea class. This appears to only add support for mouseEntered:, mouseExited:, mouseMoved:, and cursorUpdate: methods, not mouseUp: and mouseDown:.
I had the same problem. It looks like [window setIgnoresMouseEvents:NO] will do it.
(On Lion, at least. See http://www.cocoabuilder.com/archive/cocoa/306910-lion-breaks-the-ability-to-click-through-transparent-window-areas-when-the-window-is-resizable.html)
As far as I know, click events to transparent portions of windows aren't delivered to your application at all, so none of the normal event-chain overrides (i.e -hitTest:, -sendEvent:, etc) will work. The only way I can think of off the top of my head is to use Quartz Event Taps to capture all mouse clicks and then figure out if they're over a transparent area of your window manually. That, frankly, sounds like a huge PITA for not much gain.
George : you mentioned that you tried filling portions with an almost but not quite transparent color. In my testing, it only seems to work if the alpha value is above 0.05, so you might have some luck with something like this:
[[NSColor colorWithCalibratedRed:0.01 green:0.01 blue:0.01 alpha:0.05] set];
It's an ugly kludge, but it might work well enough to avoid using an event tap.
Did you try overriding
- (NSView *)hitTest:(NSPoint)aPoint
in your NSView sublcass?
You can use an event monitor to catch events outside a window/view.
https://developer.apple.com/library/mac/documentation/cocoa/conceptual/eventoverview/MonitoringEvents/MonitoringEvents.html
You can override the hitTest method in your NSView so that it always returns itself.
According to the NSView documentation, the hitTest method will either return the NSView that the user has clicked on, or nil if the point is not inside the NSView. In this case, I would invoke [super hitTest:], and then return the current view only if the result would otherwise be nil (just in case your custom view contains subviews).
- (NSView *)hitTest:(NSPoint)aPoint
{
NSView * clickedView = [super hitTest:aPoint];
if (clickedView == nil)
{
clickedView = self;
}
return clickedView;
}
You can do:
NSView* windowContent = [window contentView];
[windowContent setWantsLayer:YES]
Making sure that the background is transparent:
[[windowContent layer] setBackgroundColor:[[NSColor clearColor] CGColor]];
Another option would be to add a transparent background image that fills the contentView.

Resources