6. Now let's play!
6.1 Make it red
In one sense, we finished “rubifying” our application when we converted its Objective-C code to Ruby. But in another, we still have one more thing to do. Let’s replace those multicolored .tiff images we’ve been drawing with a red circle using NSBezierPath.bezierPathWithOvalInRect. Change your CustomView drawRect method to the following:
def drawRect(rect) OSX::NSColor.clearColor.set OSX::NSRectFill(frame) OSX::NSColor.redColor.set OSX::NSBezierPath.bezierPathWithOvalInRect(frame).fill window.invalidateShadow end
Now when you run it, your application’s window should look like this:

That’s nice, but we can do better. Let’s now illustrate one of the very cool characteristics of transparent windows: they can be nonconvex, and even noncontiguous! Instead of drawing one big circle in our window, we will draw four small ones, one at each corner. Try making the change yourself, or paste in the following new version of drawRect:
def drawRect(rect) OSX::NSColor.clearColor.set OSX::NSRectFill(frame) OSX::NSColor.redColor.set h,w = frame.size.height, frame.size.width s = 0.25 * ((h < w) ? h : w) [[0,0], [w-s, 0], [0, h-s], [w-s, h-s]].each {|x,y| OSX::NSBezierPath.bezierPathWithOvalInRect([x, y, s, s]).fill } window.invalidateShadow end
Here’s what your application’s window should look like now:

Mouse down on one of the red circles and drag the window around. Then click in any of the transparent areas and see that your clicks go straight through to whatever is beneath the window: as far as the windowing system is concerned, in its transparent regions, your window just isn’t there!
6.2 Get it moving
And now for something I hope you’ll really like: let’s animate our window and have it dynamically change shape as its contents move around. How about having the dots move along the edges of the window? OK, replace your CustomView implementation with the following code:
MAXCOUNT = 100 class CustomView < OSX::NSView def awakeFromNib @count = 0 @timer = OSX::NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(1.0/30.0, self, :tick, nil, true) setNeedsDisplay(true) end def tick(sender) @count += 1 @count = 0 if @count == MAXCOUNT setNeedsDisplay(true) window.oc_display end ns_overrides :acceptsFirstMouse_ def acceptsFirstMouse(event) true end ns_overrides :drawRect_ def drawRect(rect) OSX::NSColor.clearColor.set OSX::NSRectFill(frame) OSX::NSColor.redColor.set h,w = frame.size.height, frame.size.width s = 0.25 * ((h < w) ? h : w) dh = (h-s)*@count/MAXCOUNT dw = (w-s)*@count/MAXCOUNT [[dw,0], [w-s, dh], [0, h-s-dh], [w-s-dw, h-s]].each {|x,y| OSX::NSBezierPath.bezierPathWithOvalInRect([x, y, s, s]).fill } window.invalidateShadow end end
If you’ve tried it, you’re probably saying “whoa, that’s really, uh, weird.”
OK, so we can’t all win Apple Design Awards, but with RubyCocoa we can definitely push our applications in interesting new directions—and get there faster and with less code than using Objective-C only.
Be sure to drag the window around a bit by clicking and dragging those moving circles. Notice that once you’ve started dragging the window your mouse doesn’t need to be on a red circle. I don’t know how to change that, but this example has gotten weird enough already, wouldn’t you say?
A few comments on the code, for the curious:
- We use an NSTimer to run our animation. You’ve seen this before if you’ve read my Ruby Rocks article. It is configured to call tick thirty times a second.
- The tick function increments a counter and uses setNeedsDisplay to tell the windowing system to redraw the window. We also have to call window.oc_display to regenerate the window’s shape.
- drawRect uses the counter to move the origins of our circles’ enclosing rectangles parametrically along the edges of the window.
- We override acceptsFirstMouse so that the window will respond to the first mouse click inside its boundaries. Normally that first mouse click selects the window and then is discarded. But that would mean that our window’s mouseDown function wouldn’t get called until our second click, and those moving circles are hard enough to click on once!
6.3 Don't stop now
There’s one more thing I want to do with this example. Let’s make the window full-screen. Add the following code to your CustomWindow initWithContentRect_styleMask_backing_defer method:
screenFrame = OSX::NSScreen.mainScreen.frame setFrame_display(screenFrame, true)
Run it. Oops, it’s full-screen, but our view is stuck in the lower left corner. What happened? Open the nib file, MainMenu.nib, and use the size inspector (command-3) to look at the view. Reconfigure it so that the view resizes with the window. To do that, click on the springs and lines until the size inspector window looks like this:

Now save and close the nib file, rebuild, and run your application. Yeah, whoa.
Did you find an error? Is something missing? Post your comment or suggestion below!
Comments (0) post