Creating Aqua effects in code
By Thom McGrath on
Using nothing but some gradient code and logic, you can creating Aqua effects using REALbasic code. We'll explain both.
Gradients are actually quite simple. All we need to do is blend two colors into each other. So we'll create a method called CreateGradient, which takes parameters for size and colors, and returns a picture. We step through each line and calculate the percent of each color for each line, as so:
Protected Function CreateGradient(w As Integer, h As Integer, s As Color, e As Color) As Picture // This method would be just as useful moved to a module Dim p As picture p = newpicture(w,h,32) Dim i As Integer Dim samt,eamt As Double For i = 0 To h samt = 1 - (i / h) eamt = i / h p.graphics.forecolor = rgb(_ (s.red * samt) + (e.red * eamt),_ (s.green *samt) + (e.green * eamt),_ (s.blue * samt) + (e.blue * eamt)_ ) p.graphics.drawline -1,i,w+1,i Next Return p End Function
We can put this method in a module, or directly in the class we're going to create later. You may also notice we draw each line a tad off each side. This is to compensate for Quartz anti-aliasing, which would make the edges look a bit strange.
Setting up a button class
In this tutorial, we're going to create a button class called AquaButton. It is designed to look like the Small Square Button in Cocoa, which are used for the +/- buttons we see all over the OS. The code is also designed to be cross-platform and double buffered using canvas subclass called DoubleBufferedCanvas. It is included in the project, or you can read up on it from Aaron Ballman's blog.
So start off by creating a canvas subclass and call it something useful, maybe AquaButton? First, you'll need properties to store data, and computed properties will be used to retreive and set them. We do this so we can force the button to redraw when we change something. So create three properties: pBGColor As Color = &cE6E6E6, pCaption As String, and pState As Integer. Next, create four computed properties: BackgroundColor As Color, Caption As String, IsEnabled As Boolean, and State As Integer.
The "Set" portions of the computed properties do two closely related tasks. First, they determine of the new value is different from the previous value, and second, inform the canvas to redraw only if the value is different. This is useful, especially in state management, to prevent unnecessary redraws. The double buffered canvas adds a method Redraw. So update your computed properties BackgroundColor, Caption, and State to simply return their property-based counterparts, and then to check for a value change, and redraw in the set method, similar to so:
If pstate <> value Then pstate = value me.redraw End
Of course, don't use pState for all of them. That would be pointless. We also want to fill in IsEnabled's Get with
Return (me.state <> kstatedisabled)
And it's Set with
If value Then me.state = kstatenormal Else me.state = kstatedisabled End
Those constants are simple. kStateNormal As Number = 0, kStatePressed As Number = 1, kStateDisabled As Number = 2. While on the topic of constants, you may want to add a few colors: kColorClear = &cE6E6E6, kColorAqua = &7BB5EE, and kColorGraphite = &cACB5BF.
Now add an event called Action, and insert the following code into the correct events to enable proper mouse tracking:
Function MouseDown(X As Integer, Y As Integer) As Boolean If x > 0 And x < me.width And y > 0 And y < me.height And me.state <> kstatedisabled Then me.state = kstatepressed Return True End End Function Sub MouseDrag(X As Integer, Y As Integer) If x > 0 And x < me.width And y > 0 And y < me.height And me.state <> kstatedisabled Then me.state = kstatepressed Else me.state = kstatenormal End End Sub Sub MouseUp(X As Integer, Y As Integer) If x > 0 And x < me.width And y > 0 And y < me.height And me.state <> kstatedisabled Then me.state = kstatenormal Action End End Sub
Excellent! We now have an invisible, but functional button. Really, you could use the entire tutorial up until this point just to create a button of any kind! So now we get into the Aqua effects. All the remaining work is done in the Paint event of our class.
First thing is first, we draw the background color.
g.forecolor = me.backgroundcolor g.fillrect 0,0,me.width,me.height
Next, we create a gradient that is half the height of the button. This will become the mask for our highlight. It starts with &c505050 and ends with &c323232. To get the translucency we need, we create a picture of the same size, filled with white. We then draw the gradient into the mask. This could be done without dimming a new picture, but we did it for demonstration.
Dim gradient As picture = me.creategradient(me.width,me.height / 2,&c505050,&c323232) Dim overlay As picture overlay = newpicture(me.width,me.height / 2,32) overlay.graphics.forecolor = &cFFFFFF overlay.graphics.fillrect 0,0,me.width,me.height overlay.mask.graphics.drawpicture gradient,0,0 g.drawpicture overlay,0,0
Next, we want to lay the text on top. There are two basic problems with this code. First, the color calculations are "best guess" and to get the accurate would likely take some more tricky math. What I have done is very close, in my opinion. The second, I didn't position the text 100% correctly, it looks a little too close to the bottom. Being a tutorial, I figured I don't need to give all the correct answers away, so fixing these will be left as an exercise for the reader.
When the button is disabled, we only need to make the text darker than the background color, making it appear translucent. The button also never calls for a shadow, making our life simple.
If me.isenabled Then g.forecolor = &c000000 Else g.forecolor = rgb(me.backgroundcolor.red - 100,me.backgroundcolor.green - 100,me.backgroundcolor.blue - 100) End g.textfont = "System" g.textsize = 0 Dim strwidth As Integer = g.stringwidth(me.caption)
We also need to draw the border. As with the text, the color is close but not 100%
g.forecolor = rgb(me.backgroundcolor.red - 100,me.backgroundcolor.green - 100,me.backgroundcolor.blue - 100) g.drawrect 0,0,me.width,me.height
Finally, if the button is pressed, we tint it with a 50% black.
If me.state = kstatepressed Then Dim tint As picture tint = newpicture(me.width,me.height,32) tint.graphics.forecolor = &c000000 tint.graphics.fillrect 0,0,me.width,me.height tint.mask.graphics.forecolor = &c808080 tint.mask.graphics.fillrect 0,0,me.width,me.height g.drawpicture tint,0,0 End
Used as a demonstration, this button actually looks very good. The technique of adding a highlight to things is really the basis of Aqua, and can be added to any graphic. In the real world, colors would likely need to be calculated more accurately, the text would be positioned better, and the class would support graphics. Because really, who wants to use this kind of button for anything but +/- buttons?
I've attached the project for download and experimentation
( 8.7 KB )