Today we're just going to take a brief look at drawing in Quartz thanks to the new Graphics.Handle feature in REALbasic 2006r1. What we'll do is setup a Quartz CGContextRef for a Canvas and clip it to the bounds and then draw a rectangle with a shadow in it.

A Bunch of Declares
Let's first get the declares out of the way. You really don't need to know how to create these, if you do because you plan to make more, well, this isn't quite the place to learn since it requires a bunch of background knowledge anyway.
Declare Function QDBeginCGContext Lib "Carbon" _
(CGrafPtr as Integer, ByRef CGContextRef as Integer) as Integer
Declare Function QDEndCGContext Lib "Carbon" _
(CGrafPtr as Integer, ByRef CGContextRef as Integer) as Integer
Declare Sub CGContextSaveGState Lib "Carbon" _
(CGContextRef as Integer)
Declare Sub CGContextRestoreGState Lib "Carbon" _
(CGContextRef as Integer)
Declare Sub CGContextClipToRect Lib "Carbon" _
(CGContextRef as Integer, rect as CGRect)
Declare Sub CGContextTranslateCTM Lib "Carbon" _
(CGContextRef as Integer, tx as Single, ty as Single)
Declare Sub CGContextFillRect Lib "Carbon" _
(CGContextRef as Integer, rect as CGRect )
Declare Sub CGContextSetRGBFillColor Lib "Carbon" _
(CGContextRef as Integer, Red as Single, Green as Single, Blue as Single, Alpha as Single)
Declare Sub CGContextSetShadow Lib "Carbon" _
(CGContextRef as Integer, offset as CGPoint, Blur as Single)
The Drawing Code
Ok, now to the code to draw in the canvas. The first thing we do is just draw the white background and declare a few variables. Nothing fancy there. CGContextRef will hold the pointer to the CGContext variable which is synonymous to the "Graphics" class of Quartz.
Sub Paint(g As Graphics)
*<the declares go here>*
// Background
g.ForeColor = &cFFFFFF
g.FillRect 0, 0, g.Width, g.Height
dim err, CGContextRef as Integer
dim rect as CGRect
const noErr = 0
// Open CGContext
err = QDBeginCGContext(g.Handle(Graphics.HandleTypeCGrafPtr), CGContextRef)
if err <> noErr then
break
return
end if
CGContextSaveGState(CGContextRef)
The secret is right above here. What we do is call the function QDBeginCGContext which gets a CGContextRef from an old-style QuickDraw "CGrafPtr". Now the CGContextRef here is for the entire window. The reason it's for the window and not the canvas is that the canvas is (apparently) just an interface to drawing into the window's buffer at differtent times and with different clipping rects.
So what we need to do is first clip the bounds of the context (that is, limit the area we can draw into) and then translate the coordinate system so that it matches that of the canvas. One thing to note is that Quartz uses a bottom-left coordinate system. That means that it's like a normal Cartesian coordinate system. It's like making an "L" with your left hand - your thumb points to positive X and your first finger points to positive Y. This is really useful in some cases but in others its not. Unfortunately, I haven't been able to find a way to flip the coordinate system of a CGContext so for now we're just going to stick to the Quartz system and realize that when we're drawing with Quartz we're using its coordinate system.
// Clip to the Canvas's bounds
rect.origin.x = me.Left
rect.origin.y = self.Height - (me.Top + me.Height)
rect.dimens.width = me.Width
rect.dimens.height = me.Height
CGContextClipToRect(CGContextRef, rect)
CGContextTranslateCTM(CGContextRef, me.Left, self.Height - (me.Top + me.Height))
After that, we're going to setup the offset point for the shadow of and the dimensions for our rectangle and then draw it.
dim offset as CGPoint
offset.x = OffsetX
offset.y = -OffsetY 'negative because quartz and RB use two different coordinate systems
rect.origin.x = g.Width / 2 - 50.0
rect.origin.y = g.Height / 2 - 50.0
rect.dimens.width = 100.0
rect.dimens.height = 100.0
// Draw Rect
CGContextSetShadow(CGContextRef, offset, Blur)
CGContextSetRGBFillColor(CGContextRef, 1.0, 0, 0, 1.0)
CGContextFillRect(CGContextRef, rect)
// Close CGContext
CGContextRestoreGState(CGContextRef)
err = QDEndCGContext(g.Handle(Graphics.HandleTypeCGrafPtr), CGContextRef)
if err <> noErr then
break
return
end if
After restoring the state of our context (since we changed the clipping and origin we need to save the existing state before we do that, and then restore it after we're done) we close it (which then synchronizes the drawing to the window's buffer) and we're all done. After that we can go back to using REALbasic drawing code.
// REALbasic Drawing -- Unfortunately you can't draw using
// RB while the CGContext is open. If you could, you could set
// the shadow and then draw using RB and those objects could
// get shadows, but it doesn't work that way, despite RB using
// Quartz to draw
// Border
g.ForeColor = &c555555
g.DrawRect 0, 0, g.Width, g.Height
// Random Circle
g.ForeColor = &c0000FF
g.FillOval 30, 200, 40, 40
End Sub
Finished
I wanted to have much more complex project, but I ran into some annoyingly serious problems as I was writing the project, so it will have to wait for a future release of REALbasic to have them fixed. In the mean time, you can download this project.