How to create a transparent window with non-rectangular buttons?
I am looking to make a custom window which would look something like this (never mind the quick photoshop):
The main problem I have is not to make the window transparent (although I guess any info is welcome!), but that I want to be able to click only on the visible part of the buttons. For example, if I click just outside the top left of the "5" button, I want it to register a click on the "1" button and not the "5" button, even though I clicked in button 5's bounding box. And if I click just ouside of the "1" button's top left "corner", I want it to click on whatever is beneath my window and not on the button. I also need to be able to resize this window and the pictures in the buttons should resize with them.
I was thinking of using a transparent NSWindow with transparent NSButtons containing PNGs with alpha for each button but I think the overlapping buttons will be a problem.
I have also heard about applications like Bowtie, SweetFM and others using WebKit to display their interfaces. If that is a possibility on at least 10.3 that would be interesting, but I don't know how that would work either.
I looked around for click masks, areas or something like that but I haven't found anything yet. I will update this post as I find more info (if I do!).
Any hints on how to proceed?
(Note that I am working on 10.4 and ideally I need to support some older versions of Mac OS X too.)
(Also, I found the "rounded window" example on Apple's dev. site but it doesn't run on 10.4 anymore so I'm not sure this is the way to go.)
Thanks in advance!
UPDATE:
(see update 2, this problem is fixed now)
OK now I'm having a slightly different (but not unrelated) issue. What I did so far is this:
I opted for making a custom control that I'll be able to use for each of my buttons:
1- Subclass NSControl and override drawRect, mouseUp, mouseDragged and mouseDown. This way I can get the behavior I want when dragging on a button (move the window).
2- Load images for each button states (same shape but one is darkened) into NSImages and draw them into the view using compositeToPoint:fromRect:operation:
when necessary. The images are 185 x 185 pixels PNG files with alpha exported from Photoshop.
3- Get the NSImage's bitmap representation:
NSBitmapImageRep *bitRep = [[NSBitmapImageRep imageRepWithData:[unpressedImage TIFFRepresentation]] retain];
OR
NSBitmapImageRep *bitRep = [[unpressedImage representations] objectAtIndex:0];
Both seem to give the same result. I tried compositing this bitmap rep onto my window to see if the image has same size and look as the original and all seems good (you'll see why I did that after this enumeration...).
4- In the mouse event methods, I use the bitmap representation to get the alpha component for a particular point:
NSPoint mouse = [self convertPoint:[theEvent locationInWindow] fromView:ni开发者_如何学运维l]; // Returns the correct x and y positions on the control (i.e: between 0 and 185 for each axis).
NSColor *colorUnderMouse = [[[unpressedImage representations] objectAtIndex:0] colorAtX:mouse.x y:mouse.y];
float alpha = [colorUnderMouse alphaComponent];
Then I print the alpha value to the console but I am getting very weird results. Getting the alpha component and the color under the mouse seems to work at first glance (the colors and alpha returned are in my bitmap) but it looks like the color is not taken at the right point in the bitRep
, or the image contained in the bitRep
is distorted (it isn't distorted when I composite the bitRep
on the window though, which is very strange). Again I double-checked the x and y mouse coordinates were in the (0,185) range and that the size of the NSBitmapImageRep
is 185 x 185 too.
First it looks like the NSBitmapImageRep
is flipped horizontally compared to my NSImage
. If I flip my coordinates the values returned seem mostly OK (most of the shape seems to fit with the values returned) but there are still parts of the image which return wrong values.
I'm clueless at this point and having never worked with NSBitmapImageRep
before maybe I just forgot to specify some format information about my image which would be why the values are off. Could someone clarify this please?
Thanks again!
UPDATE 2:
Ok never mind, I found the issue. I used the setColor:atX:y: method to draw a colored dot on the bitRep before printing it on my window and the issue was clear as day: the Y coordinate is reversed in the bitRep, but the image is printed with the "good" orientation. This means that the bitRep has "Windows-style" top-left origin coordinate system while the window has the cartesian coordinate system (bottom-left) origin. Simply reversing my Y coordinate fixed the issues I had getting the alpha component and color.
This must have been written somewhere in the docs, sorry to have bothered you because of my lack of research...!
What about overriding the NSResponder
event methods (mouseUp:
, mouseDown:
, etc) in your custom buttons (presumably you'd be subclassing NSControl
or NSButton
to make these)? In those methods you could properly calculate your bounding rectangle (or circle, in this case) and do a simple hit test with the coordinates of the click to see if the click should be handled.
You might also be able to find some help in Apple's event handling docs, specifically about mouse events and custom views.
What about making it all a single control? It might make drawing a little bit complex, but it looks like you're doing custom drawing anyway.
If you did it as a single control, you could first check and see if the click point is within the center circle. If it's not, then all you have to do is identify which quadrant it's in to know which "button" it belongs to (while also verifying that the click fits within the outer circle).
Alternatively, you could create a transparent view over your buttons that captures the click events and forwards them on to the appropriate control based on the logic I just specified.
Similar to Marc W's response, in your own event method you can check the alpha value of the clicked on the bitmap and ignore if the alpha is lower than a certain value.
To get the bitmap, read this.
Now you should have a bitmap pointer like this (pseudocode - you'll have to fill in the pieces):
pixels = CGBitmapContextGetData( ctx ); // there's actually two ways to get pixels in the above Apple tech note
Then, you can do this to get the pixel you are interested in, and test it:
// I'm assuming each pixel is 24 bits of color and one byte of alpha
#define getRed(p) ((p) & 0x000000FF)
#define getGreen(p) ((p) & 0x0000FF00) >> 8
#define getBlue(p) ((p) & 0x00FF0000) >> 16
#define getAlpha(p) ((p) & 0xFF000000) >> 24
CGPoint pixPt;
long pixel;
pixPt = getMouseClick();
pixel = pixels[pixPt.x + pixPt.y * bytesPerRow];
if (getAlpha(pixel) < 25)
// you've clicked on transparent pixels, alpha of 10% or less (alpha ranges from 0-255)
This opens up some possibilities for you, such as having a ring around your inside circle that is inert and won't belong to any of the quadrants or the middle circle. Of course, there's always other ways to do these things without resorting to pixel-wrangling :-)
If you generate the shapes in code using NSBezierPath, testing whether a click is inside one of the shapes is a one-liner: send the shape a containsPoint:
message.
I'd make this a single view that owns the five paths. When drawing, draw the center one last; when responding to a mouse event, hit-test it first. Give this view one action property per segment, and optionally one target per segment as well, depending on what you need.
You'd need to do two things:
- override NSView's hitTest: method to return NIL whenever the click is outside the image, so the click in that view is ignored and passedto the view overlapping it.
- Override mouseDown: and implement your own mouse tracking loop in there (Using NSEventTrackingRunLoopMode) that also checks whether the point is in a non-transparent area. NSBitmapImageRep or NSImage has a colorAtX:y: method or something like hat which you can use for the latter.
Mu solution works only on non-intersect buttons. I have non-rectangular button, that must change images from gray to green.
I change regular gradient button like thisthats all. Hope this help someone.
精彩评论