Mac OS • 40:15
Learn about Control and Appearance Manager APIs and how to make your product Aqua compliant. This session covers issues specific to application frameworks, such as PowerPlant, so you can take full advantage of capabilities like moving controls between windows and the new drawing model.
Speaker: Guy Fullerton
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Well, good morning. Looks like a lot of you stayed up kind of late and aren't here yet. So welcome to this session. This session is really just the, if you think of the session that I gave yesterday, which was session 111, talking about how to create a great citizen on Mac OS X, citizen application on Mac OS X. And I said in that session that I wouldn't talk about APIs, talk about conceptual ideas. This session is all about talking through that material, but looking at it from the Carbon API level.
And so I'd like to bring on stage Guy Fullerton from the High Level Toolbox team at Apple, and he's going to talk through the specifics of some of that material as it relates to the APIs. Guy? Guy Fullerton Thanks. First things first, did anybody bring me any donuts? No? Okay.
I guess everybody who got here early saw my presentation last year and knows how fast I talk, how fast I'm going to get through this stuff. That's me. So I want to tell you a little story that you've probably all witnessed firsthand, and it's the story of a Carbon application.
Most Carbon applications start off as an interface lib application on Mac OS 9, and you begin the Carbonization process. And that's fairly straightforward, and it takes you anywhere from a couple weeks to maybe a couple months. But finally you get through the opacity issues and weaning yourself off of some APIs and starting to use other APIs, and you get it so it actually runs well on Carbon lib.
And usually part of the way through that process, you get your application running well enough to try running it on Mac OS X. And the first time you try running it on Mac OS X, it usually crashes, because Mac OS X is really good at exposing bugs that were sort of latent in your Mac OS 9 code base.
Well, you end up fixing those. Those usually don't take too long, because they're just typically writing to null or other strange stuff like that. You fix them, you finally get the app to launch, and it comes up, and it looks kind of good, but what's all that stuff going wrong in my app's interface? So I'm going to tell you how to fix most of those problems today, as well as certain techniques to ease your adoption of Aqua, and how to avoid certain user interface pitfalls. But before I go any further, what I would like to do is show off some of the user interface pitfalls you might fall into. And let's bring this up.
Okay, so what I have here is a small application based on an earlier version of PowerPlant that shows off some of the problems you might run into when you bring your application up under Aqua. Now, the first thing you probably notice when this dialog comes up is that it's got a progress bar animating in the top left-hand corner.
And initially, you might think, oh, okay, that's right, but then you look down in the bottom right-hand corner and you see that, well, your progress bar should really be animating down there. Why is it animating up in the top left-hand corner? You might also see this phenomenon with the pulsing default button as well.
One thing that I hope shows up on the big screen, and I've got the Magnify application running on the right to kind of show it off more, is the white halo problem. If you look closely at the Magnify window, you can see that the pattern shows up on the dialog, but it doesn't show up properly behind the text. And speaking of text, that text doesn't exactly match the proper Aqua look. It just looks too thin, doesn't have the right anti-aliasing, so you probably have that in your application as well.
One other subtle problem that you may not notice here, until you look really, really closely, is a misalignment of patterns. If you take a look at the Magnify window, you can sort of see strange pattern alignment issues going on on the checkbox glyph there. It's almost as though the pattern shifted down by a few pixels.
And speaking of these tabs that the checkboxes are in, the tab pane doesn't look quite right. The tab pane is supposed to have a border around the outside, and it's supposed to have some shadowing around the outside, but it's just not showing up. You might also see this same problem. problem with shadows on push buttons or other controls.
Another problem that doesn't often expose itself until you use the application for a while is over compositing of things like focus rings and edit text frames. If you take a look in the magnify window as I tab back and forth,
[Transcript missing]
So the first thing I want to talk about and show you how to solve is the white halo problem. Now like I mentioned, it's basically when some other color seems to surround your widgets, your controls, your static text items in a dialog that otherwise has the proper line background. Now fundamentally, this is because of two things.
Controls erase their background before they draw. And the reason we have to do this is because a lot of Aqua widgets have shadows. Under Platinum, they didn't often need to erase behind themselves because they were fully opaque, but now we need to erase in a lot of cases. And so when a control is about to draw, you need to make sure that the proper background color has been set up for that control so that the control knows the right thing to erase to.
The best way to get the proper background behind your control is to properly exploit the Control Manager's embedding hierarchy support. Now, Control Manager embedding has been around since Mac OS 8.0, and a lot of people haven't yet been exposed to it, so I'm going to talk a little bit about it now. Essentially what the embedding hierarchy is is a view system in the Control Manager. You can take controls, put them into other controls, move them around as a group. It's just a mini view system. It does hit testing properly, drawing properly, things like that.
Now, when the Control Manager's embedding stuff is working well, and when you're utilizing it properly, it will draw backgrounds properly based on parent controls that contain other controls. For instance, you might have a push button in a tab control. When that push button goes to erase, it traverses up the embedding hierarchy, finds its parent that's a tab control, and says, "Hey, tab control, will you set the background up properly for me so I can erase now?" The tab control does that, and then the button can go ahead and erase and draw its structure and lights gray.
If a control is embedded in such a fashion that it isn't in a parent control that has some sort of opaque background, ultimately, as the control traverses up the embedding hierarchy to find the right background, it might reach the root pane for the window. And if it does reach the root pane for the window, it uses a background that's been associated with the window directly via the setThemeWindowBackground API.
I'm going to talk a little bit more about setThemeWindowBackground a bit later. But basically, what this API lets you do is associate a certain pattern or brush with the window such that a control that is in this situation can erase to that brush and get the right pattern.
So all the system CDEFs support control embedding properly. They do the right thing when they're requested to have background colors set up. And if you have custom CDEFs and you want to start taking advantage of embedding, you can do the right thing here as well. The best way to do this on Mac OS X is to listen to the K-Event Control Apply Background Carbon Event. We talked a lot about Carbon Event in previous sessions, and one of our main points is that in order to adopt current and future Mac OS X technologies, you're going to need to start leveraging Carbon Events a lot more.
What just so happens is that if you have an old school message based CDEF, you don't need this particular Carbon Event right now. We do send out old CDEF messages to your control definition to set up the background. You just have to do a little bit more work.
If your control has a special background, the first thing it needs to do is tell the control manager, "Hey, I've got a special background." And the way you do that is by reporting that you have the K control has special background feature bit. You're going to receive a control message requesting your features. You would set that bit on the way out of the feature request.
Once you've responded that you support a special background, when the control manager is trying to draw your sub-controls, it will send you a K control message setup background message. In this message, you can set up the current port with the proper background so that your sub-controls can draw properly.
Not everyone can take advantage of the control embedding hierarchy fully. A lot of frameworks have their own view system, and we're working on making it such that you can integrate the view system with the control manager more directly. In the meantime, however, you might not be able to fully exploit control embedding, but you still need to set up the right background.
The way you do that is with SetControlColorProc. Simply speaking, SetControlColorProc allows you to associate a proc pointer with a given control, and that proc pointer will be called to set up the background or the text color for a given control. This ControlColorProc can actually be used in conjunction with an embedding hierarchy.
If there's a color proc associated with the control, it will override the control manager's embedding hierarchy support for color, so you can hook in that way. I also mentioned that SetControlColorProc allows you to customize the text. This also goes back to custom control definitions responding to either the Carbon event or the C-DEF message.
If you have a custom background, let's say that it's totally opaque black, and somebody puts a static text control on top of that complete opaque black control, you need to make sure that the text that that static text control draws is visible. The way you would do that is probably by saying, "Hey, draw your text in white." SetControlColorProc will let you set up a white text color as well. Like I mentioned, you can handle the proper Carbon events or the C-DEF message to do the same sort of thing.
Now, sometimes setControlColorProc may not do exactly what you want or it may be a little bit of a hassle to use. So as a last-ditch effort you can always rely--well, not always--you can sometimes rely on the Control Manager's default behavior of erasing to the current port. If the Control Manager does not have a color proc associated with a given control, and if the embedding hierarchy doesn't have a color associated with a given control, and if there's no theme brush associated with the Windows background, we're just going to erase to whatever color happens to be in the current port.
So if you're careful, you can use APIs like setThemeBackground--and I'm going to talk a little bit more about that later--you can use APIs like that to prepare the current port before you call any Control Manager API, which might draw. Now this is going to work in most cases, but it flat out won't work for animating controls. And the reason is that animating controls animate behind your back. You won't have a chance to preflight any color. So you won't be able to use this current port technique for things like push buttons and progress bars and the other animating controls.
Now another obvious problem you'll probably notice as soon as your application comes up is shadow clipping. I showed it in that demo application in the tab pane, but this particular example shows it happening on push buttons. Generally speaking, this is caused by applications clipping to a control's bounds before asking that control to draw.
In Aqua, however, controls have shadows that lie outside their control bounds, and some controls, like pop-up buttons, have traditionally drawn outside their control bounds. So in general, clipping to a control's bounding rectangle isn't going to cut it. You're going to end up cutting off shadows or worse. PowerPlant is probably the most obvious victim of this particular problem. I know they're in the process of fixing that. I think they fixed it already, in fact.
So the way you fix this problem is don't clip to the controls bounds, but you might still have clipping needs. You might still want to make sure that the control doesn't draw willy nilly all over the whole screen. But you still need to give the control the flexibility to draw where it needs to draw. And the way you do that is by asking the control for its drawable area.
You can use the get control region API along with the k control structure meta part tag. And this lets the control tell you, hey, you know what? I draw all over this area of the screen. And this region is going to include not only the sort of opaque structure, but it'll also include the shadow pixels and things like that.
So if you make sure to clip to this structure region, you will let the control draw as much as it needs to draw. Now, get control region is not just useful for finding out where to clip. Various controls support certain part codes depending on the type of control. And you can ask, for instance, a tab control.
You can say, hey, what's the region for your third tab? And we also have another meta part, which is called the k control content meta part that many controls support. And this allows you to query a control for the region that its subcontrol should be embedded into. So you can do proper positioning of your controls within a tab pane, for instance.
So I actually should have changed the title on this slide. This is not just focus ring issues, but this is also Edit Text Frames. As I showed you on the demo, as I was tabbing back and forth between those edit text fields, you could see the Edit Text Frame get darker, and darker, and darker, and darker. And in some cases, you'll see the same phenomenon with the focus ring.
And essentially, what this does is it just wrecks the visual appearance of your API-- sorry, of your interface. It starts looking heavier, and heavier, and heavier, and it just doesn't fit in with Aqua. And the reason this happens is because certain appearance primitives now draw with Alpha, whereas in Platinum, they did not. And the two best examples are focus rings and the Edit Text Frame.
If you're going to draw either of these-- Use either of these primitives to draw your focus rings and your frames. You need to make sure you erase before you draw. Now erase is a little bit of a lie. What you might actually need to do is draw the background or draw whatever happens to lie behind your focus ring or frame because it might not just be a simple background.
So in the demo app I showed you, the progress bar was animating in the total wrong location, but probably a worse instance of this is when your OK button that should be down in the nice bottom right hand corner of your screen according to the proper UI guidelines, all of a sudden jumps up to the top left and starts pulsing. And that's a really quick indication that something's wrong. Fundamentally, this is caused by a non-zero-zero origin in the window that you're dealing with. And this non-zero-zero origin can also cause pattern misalignment, like I showed you in the checkbox.
But you might be thinking to yourself, well, I've been changing the origin for years. Heck, PowerPlant does it, my framework does it, our custom application code does it. It was working fine, right? Well, it was behaving fine, but it really wasn't working fine. If Platinum, if you tried to draw controls on top of patterns or other pictures under Mac OS 9, you would have seen this same problem happen. It just so happened that in most cases you end up drawing with a solid color, so you didn't see the phenomenon. And generally speaking, it's practically impossible to do willy-nilly origin changes to your window on Mac OS X because you're going to run into these two problems.
But you have good reasons for wanting to change the origin. I mean, it's a valuable tool, right? So there are safe ways to do that. The one thing you need to keep in mind is that the control manager really, really, really wants a zero-zero origin associated with the window that it's drawing its controls in.
And in fact, every control's bounding rectangle must be relative to a zero-zero origin in order for the control manager to work properly. For instance, if you have a default button in the bottom right-hand corner of your window and you want it at position 200, 300 relative to the window's top left, you need to make sure the control's bounding rectangle is 200, 300.
If you do need to change the origin, you want to make sure you save and restore it properly. The way to do so is very straightforward. In order to determine what a port's current origin is, unfortunately we don't have an API for that directly, but what you do is you get the port's bounding rectangle, just save off the top left corner of the bounding rectangle. That's its origin. Then you can change the origin to whatever you need it to be, do your setup and other drawing. When you're done drawing, restore the origin to the way it was before.
Let me go into a little bit more background about why the pulsing buttons jump, just to give you a better feel of exactly why this problem happens. If you have your control with a bounding rectangle whose top left is 0, 0, and your framework or your application would make sure to adjust the origin such that it's negative 200, 300 before it draws the control, hoping that the control really will draw down 200 pixels down and 300 pixels down. If you have a bounding rectangle with 300 pixels to the right of the window's origin, that's going to work if and only if you have explicit time to set up the port before you ask that control to draw.
However, because controls draw in timers, you don't have that pre-flight chance. In fact, when a control's timer fires, it can't make any assumptions about the current port. This is also a general rule you'll need to follow yourselves when using Carbon event timers. A timer might be firing at some totally odd time, like when menus are down or when you're tracking in another control.
Therefore, the origin may have been set up to do something totally different, and your timer needs to set the origin and things like the clip to something very predictable. And the control manager, for predictability's sake, sets the origin to 0, 0. Therefore, if your button's top left-hand corner is 0, 0, well, your button's going to draw at the top left-hand corner of the screen-- or of the window, sorry.
Pattern alignment is not the only pattern issue you're going to run into in all likelihood. Sometimes you'll want to draw with a pattern, you've got to color instead. Other times you'll want to draw the opposite direction. You've got to color and you wanted a pattern. Other times you'll just see strange gray backgrounds, which look great in platinum, but why are they coming up in my Aqua interface? And worse yet is a System 7 white background that's probably been in your application for about the last five years.
The reason this happens is because backgrounds under Aqua can be either colors or patterns. And your application needs to be aware of that and needs to be expecting it. Quick Draw's behavior when there is a pattern in the current port is to draw using that pattern, not necessarily the color you've set up.
So if you use an Appearance Manager API to set up a pattern for your window, and then a little while later you change the color to white because you want to draw some area white, then you go to erase and all of a sudden the pattern comes up, but I just set the color to white, why is this pattern coming up? Well, simply speaking, that's because the pattern is still in the port, and that's what Quick Draw is going to use.
So in order to make sure that you don't have unwanted patterns associated with the current port, you can call Normalize Theme Drawing State. Now this API was introduced in, I believe, the Mac OS 8.5 timeframe, and it was designed to just do a quick wipe of the current port settings, such that it's in a very predictable, clean drawing state. It will make sure to eliminate any pattern that's in the port. It will set the foreground color to black, the background color to white, and tweak a few other things to make it more predictable.
Now, these theme drawing state APIs also have a saver and restore, which are very useful if you want to make sure you don't disrupt the system's use of patterns in some callback or possibly in a Carbon event handler. And you use the save theme drawing state and restore theme drawing state to save and restore the theme drawing state.
They will pass back out an opaque reference to a whole bunch of settings. You hang on to these settings. You can do your own port preparation, often with normalized theme drawing state or other preparation. Do your drawing, and finally when you're done, you call restore theme drawing state and pass in the opaque object, and it'll set things back up the way they were before.
Now, if you happen to be drawing gray backgrounds in your windows just because, well, that's what you always did and that's what looked right in Platinum and I didn't know we had to use these Appearance Manager APIs, well, you know what? You've got to use these Appearance Manager APIs to draw the Aqua patterns. And in fact, these same Appearance Manager APIs will let you draw the right thing on CarbonLib on Mac OS 9.
The Appearance Manager supports a whole bunch of different backgrounds and they're tagged at sort of a high level with the notion of theme brushes and theme backgrounds. Theme brushes are, like I said, used in a high level way. You tell the Appearance Manager what sort of background for what case you're drawing in. You might say, "I'm drawing in an active dialog right now," or "I'm drawing a toolbar background right now," or even, "I want to draw a bevel button center fill.
Can you please set that up for me?" We've got several dozen theme brushes and theme backgrounds in Appearance.h. You can go ahead and take a look there and see which ones you want to start using. In general, you want to use the dialog, alert, utility window, toolbar backgrounds, things like that.
That'll get you the proper Aqua lines on Mac OS X and on Platinum they'll draw the proper gray. The way you use these is typically through set theme background and set theme pen. Set theme background takes a theme brush. It talks to the Appearance Manager, figures out what color or pattern needs to be applied to the current port, and then applies it to the current port.
Set theme background applies it to the background, so you can follow that up with an erase rect or any other Quick Draw API that might erase. Set theme pen applies it to the foreground, so you can follow that up with any Quick Draw routine that's going to paint.
As I mentioned before, there's this Set Theme Window Background API. How is this different from Set Theme Background, you might ask? Set Theme Window Background associates a theme brush with a window on a permanent basis. You can come in later and call Set Theme Window Background again and set up a different theme brush to that window, but generally speaking, it's remembered until your next call to Set Theme Window Background. Whereas Set Theme Background transiently just applies this background setting to the port and it can get stomped upon later.
Now the other cool aspect about Set Theme Window Background is it lets the window manager be a lot more efficient. Because there's this brush constant permanently associated with the window, the window manager can use it when it needs to paint a window as it comes on screen for the first time.
This saves a lot of time and a lot of your hassle because in a normal situation, the window might paint white and then later you'll get an update of it and then you'll draw the lines. And you really don't want to be seeing that sort of flicker on Mac OS X under Aqua. It's kind of crummy. So you can start using the Set Theme Window Background API to associate a theme brush with your window. There's one other Theme Background API and that's called Apply Theme Background.
And it's a little bit different. It's essentially similar to Set Theme Background in that it takes a theme background constant and shoves the right color or pattern into the current port. But the difference here is that Apply Theme Background also takes a rectangle to align that background to.
The classic example of this is a background. The background that fills a tab pane doesn't want to align to the window's top left-hand corner. It wants to align to the top left-hand corner of the tab pane itself. So that's why you need to pass in a particular bounding rectangle. And we've got several theme backgrounds that are useful for Apply Theme Background in the right sorts of cases. You can take a look at Appearance.h to find those as well.
So non-Aqua text is sort of a sticky issue. When you bring your app up, you see all these really wacky white halo problems and really ugly visual things. You fix all those and you go, yeah, I think I'm done. I think I got it ready. But you kind of glossed over the fact that your text doesn't quite match up.
And you might be really tempted to just leave your application that way, but please don't. It looks really, really crummy compared to the rest of the Aqua text. And there's another downside in that the ugly text is not only ugly, but it's totally Unicode unsavvy. It's drawn with Mac encoding characters. You can't have mixed run strings.
Your file system might have a file name that has five different languages in it using five different fonts to display it. And if you try to render it with the APIs you're currently using, you're going to just get jumbled garbage and it's going to look just horrible. And the reason it looks bad is because you're using the old Quick Draw text drawing APIs.
There's a number of ways you can make your text look really good under Aqua. The most straightforward way is to adopt the system control definitions, window definitions, and menu definitions as much as possible. The more you adopt those, the less work you have to do in the first place. All of these devprocs have been revved to use AquaText where it makes sense. There's actually a few exceptions to this. The most obvious one is the old school EditText control.
The old school EditText control is going to forever draw with QuickDrawText because it's forever associated with TextEdit. We're not bringing TextEdit forward, so if you really want to do proper Aqua-savvy text editing, you should use the Unicode EditText control. Like the rest of our AquaText story, not only does it look good, but it also fully supports Unicode in multiple languages and stuff like that.
And if you have certain static text areas on your screen that you're using Quick Draw Text APIs to draw directly, one easy way to make those Aqua-savvy is to begin using the Static Text C-Def instead. The Static Text C-Def is really good for simple needs when you just want to draw a little caption or a little label on top of something. And using the Control Manager's embedding support, it'll let you draw on top of most arbitrary backgrounds.
And if you don't like the overhead of using a control or if you need a little bit of a quicker solution, you can always use the Theme Text APIs. I'm going to talk a little bit more about that in a slide or two. And if that doesn't work out for you, you can always use ATSUI or MLTE.
Now these are the lowest level ways for a Carbon app to draw Aqua-savvy text. It's a little more complex to set up and use, but it gives you a heck of a lot more flexibility and a little bit better performance. The downside of using these APIs is there is not currently a way to make your application more flexible.
You can always use the Theme Text API to get the proper theme fonts for given situations to use them with ATSUI and MLTE. This is something that we're going to add in the future, but for right now, if you use ATSUI and MLTE, you might see a few strangenesses when you try to render mixed language text.
So the quick and easy solution to get this Aqua text is via the theme text APIs. We've got APIs to do drawing and measuring and truncation, and essentially they're meant to be very easy replacements for the quick draw text routines you've already been used to. If you're using draw text or draw string or TE text box, you'll want to use draw theme text box.
Likewise, if you're using text width or get theme font info to determine widths and heights, you can use get theme text dimensions. Now, certain theme text draws shadow outside of it. Two best examples are window title bars and push button fonts. And you can determine the amount outside of your text that the shadow will draw via the get theme text shadow outside API.
This returns a rectangle, and each of the fields of the rectangle tells you how far out each of the sides the shadows will draw, so you can take that into account if you need to clip or do some other stuff like that. And if you were using trunk string or trunk text to do your truncation, you can use get theme text box to determine widths and heights.
So if you're using draw text or draw string or get theme text box, you'll want to use draw theme text box to determine widths and heights. If you're using draw text or draw string or get theme text box to determine widths and heights, you can use get theme text box to determine widths and heights.
easily switch over to Truncate Theme Text. All of these Theme Text APIs are Unicode savvy. They take CFStrings, and they're fairly easy to use. I tried to model them after the Quick Draw text routines that they replaced, so they're pretty easy to just swap in and out very easily.
All of these theme text APIs also take the notion of a theme font ID. Theme font IDs have actually been around for a while. The Appearance Manager supports a way of asking, "What font will I draw on if I want to draw on the system font?" We had this support, I believe, in the Mac OS 8 timeframe.
We took these theme font IDs forward, added a whole bunch more, and what they essentially are is and I'll take away for you to say you know what I want to draw a push button font or I want to draw on the system font or I want to draw on the window title font.
And the cool thing about the theme font IDs is not only do they specify one font, they also specify that font size and its shadowing effects. But even cooler, it will resolve to more than one font if the string you are about to render has more than one language in it.
You can give us one CFString that contains Roman text, Japanese text, in the future Korean text and stuff like that. And we will render that one string in multiple fonts as we see it's necessary. And of course, if you call the measuring APIs, we're going to return accurate measurements for all the variety of fonts that we're going to end up using.
Now, the theme font IDs that we offer is only a family of about a dozen of them, and you might have more custom needs. If you have custom needs, you can use the KThemeCurrentPortMetaFont. This meta font lets you set up the current port with whatever text font size, style, and face you want, and the Appearance Manager routines will pull that information out of the current port, map it into the right data structures for ATSUI, and end up rendering Aqua text with it.
Unfortunately, there's a few limitations. It doesn't support all styles. We don't support outline and shadow styles because the underlying engine, ATSUI, doesn't support either of those. And likewise, you don't get the proper Aqua shadowing effects. The only way to get Aqua shadowing effects is to use the theme font ID, which maps to one of those shadowing effects.
And the last downside is that it's got suboptimal Unicode support. If you pass in a Unicode string with multiple languages in it, we're going to do our best to guess the right fonts for that, and we're going to accurately render the glyphs, but the fonts we choose to render other text in, let's say Japanese text or Korean text, if you're in an otherwise Roman application, those fonts may not match visually very well with the one font you specified in the current port. So that's one of the drawbacks to using the current port font. You'll want to stick with the higher level theme font consonants as much as you can.
Now the Theme Text APIs don't actually do anything with the color. Theme Font IDs don't actually imply a color. We let you choose what color you want to draw the text in entirely on your own. And the easiest way to do this is via the Set Theme Text Color API.
Set Theme Text Color, like the Draw Theme Text APIs, has a constant, a family of constants, which lets you tell us what sort of thing you're trying to draw text on. You can say, "I'm trying to draw text on my active dialog," or "I'm trying to draw text on an inactive dialog," and those two theme text colors will resolve into different raw colors to draw the font with.
In the active case, we're going to draw with black. In the inactive case, we're going to draw with the proper gray. We have a whole bunch of these theme text colors pretty much on a one-to-one correspondence with the various theme buttons and theme font IDs that you might be used to from the Appearance Manager.
Now, if you've had a chance to use the Theme Text APIs, most of you probably said, "Yeah, this works. This is pretty good." But some of you might be trying to draw a whole lot of text in a given window. And, well, you know, the Theme Text APIs are going to be a little slow.
There are a couple ways to deal with this and make the Theme Text APIs faster. The best way is to take advantage of the CGContextRef parameter that DrawThemeTextBox allows you to pass in. If you've had a chance to attend any of the Quartz drawing sessions, you'll know that a CG Context Ref is an opaque reference. It's kind of like a Quartz graph port.
It stores all your drawing settings that you want to use. If you don't pass one of these into the Draw Theme Text Box API, we're going to create one for you. And that's okay. We're going to even pull certain settings out of the current port and apply them to the context, and that's also okay.
The problem is that it takes a little bit of time. So if you have 50 calls to Draw Theme Text Box and you pass a null context ref to each of those, we're going to end up creating a context, syncing it with the current port 50 times. And that's just some performance issues you don't want to run into.
If you want to bypass this, the right thing to do is create your context ahead of time, set it up the way you want it with the proper color and the proper clipping and stuff like that, and then pass that one context in to each of your 50 Draw Theme Text Box calls.
That's probably going to solve a majority of your needs. But one thing I found when I was revving certain parts of the system to use these theme text routines is that they use the measuring and truncation routines multiple times before each draw. The static text control, for instance, might measure its bounds twice, once to figure out what rectangle to clip, and then it might measure the text again to know what rectangle to pass in to draw a theme text box. Well, that's silly, and if your application is doing the same thing, it should really stop.
Every call to the measurement routine is actually fairly expensive. We're working on making that a lot faster through things like caching, but in general, if you have redundant calls to the measuring routines or even the drawing routines, I found a few cases in my own code where I had that, you want to eliminate them. And cache. The measurement values, if you can.
For instance, if you're drawing a static text item, you might calculate the bounds for that static text item once, store it off in your object pointer that represents that static text item, and then use that bounding rectangle every time you draw. You don't even need to measure every time before you draw, because you've already done the measurement once.
One other thing we're working on besides the caching, I want to make a way possible such that you can use ATSUI and MLTE in a proper theme font fashion, so it gives you total low-level custom control as well as the proper Unicode font stuff. That's not available now, but it's something I'm looking at for the relatively near future.
I've talked a lot about the Control Manager in the session so far, but a lot of the same principles apply to the Appearance Manager. You can draw virtually any widget that the Control Manager draws with the Appearance Manager primitives. We've got low-level primitives for drawing buttons, for drawing edit text frames, focus rings, tabs, group boxes, all sorts of stuff.
And the important thing to realize is you're going to run into the same sorts of visual issues if you draw with the Appearance Manager primitives. The Appearance Manager draws with Alpha. The Appearance Manager sometimes needs to erase. You need to be aware of both of these things, and some of the Appearance Manager APIs allow you to pass in a ThemeErase proc. This ThemeErase proc gives you a chance to erase properly before the Appearance Manager needs to render. And again, that erase should be in quotes because sometimes you're not literally erasing, you're drawing something behind the new widget you're trying to draw.
And likewise, the Appearance Manager will draw outside of the bounding rectangles you give it. If you give the Appearance Manager a 20 pixel tall bounding rectangle to draw a button, well, the shadow's going to go outside that bounding rectangle. And the way you determine how far outside is to use an API like Get Theme Button Background Bounds. This particular API tells you how far out, at the extreme case, a given widget will draw. And so you can do clipping or whatever else you need to do with that.
And lastly, we offer a new API as part of Carbon in Mac OS X called Get Theme Metrics. Generally speaking widgets under Aqua are about the same size under Platinum, but there are a few exceptions. I think the tabs and tab panes are a few pixels taller. The amount that a focus ring draws outside the input rectangle is a little bit wider.
Edit text frames actually don't draw as far outside the bounding rectangle as they did on Platinum, but they draw inside the rectangle somewhat now, too. And we offer theme metrics for you to ask for this sort of information. There's probably, wow, five or six dozen theme metrics in the headers. You can find out how tall the tabs are, how much an individual tab overlaps the tab pane, stuff like that.
Alright, so I've talked about the Control Manager, I've talked about the Appearance Manager, and they're pretty much equal, but which one should you use? Generally speaking, you want to use the Control Manager. There are some things that the Appearance Manager just can't do for you right now. In some cases, the Control Manager implements certain things directly itself.
The best example of this is the pulsing button. There is no way to draw a properly pulsing button on Mac OS X right now if you use the Appearance Primitives. You must draw with the Control Manager if you want those. But in the rest of the cases, the Appearance Manager works very, very well.
All right, so you heard about the problems. Well, what do you do now? The best thing is go out there and start aquafying. If you got a chance to see John's session yesterday on adopting Aqua, Aqua's totally leveled the playing field. A lot of applications are just getting on board now. And this is your chance to go out there and really polish your user interface, and really step over your competition, and show, hey, you know what, we've got this great interface. And the users will really like that.
A crummy interface really taints the user experience. First time you launch an app, you see all these funky visual problems. You're going to go, whoa, well, they didn't spend much time working on the interface. I wonder how much time they spent working on the code. Is this app going to crash? And it just puts a bad taste in your mouth.
So you want to make sure your application is as Aqua pretty as possible. Take advantage of shadowing as much as you can. Do the theme text stuff as much as you can. And the final word I want to make is that the Carbon high-level toolbox is a perfect way to deliver Carbon applications. Yeah, we've got a few shortcomings, but we're working on that. But I want to assure you that we're bringing the high-level toolbox forward. You saw yesterday's window and menu manager session.
We've added a lot of features there. We've added a bunch of features in the control manager. I'm going to talk about it at the session after this. And we're going to continue enriching the control window menu and all the high-level toolbox managers. So with that, I would like to do some Q&A and go through the roadmap. And so I'll bring up John Galenzi.
Great, thanks Guy. So the roadmap just talks about some other sessions that relate to this material and that relate to the topic of user experience on Mac OS X. The second session, which is just right after this one right here, so you don't even need to move, is 120 Controls & Appearance, where Guy's just going to keep talking about the same sort of stuff, dock, animating dock icons, that sort of stuff, and continue on this track.
And then the feedback forum for the high-level toolbox. It's a great opportunity for you to come and give your feedback as to things that you think are missing in terms of controls or functionality within the Appearance Manager, whatever relates to the high-level toolbox. Then session 112, which is today at 2:00 PM, I believe. No, that was yesterday at 2:00 PM, so watch it on DVD.
Designing and using Aqua Icons. I talked about that one at the end of my session 111 yesterday, which is a great session to come to to learn how to critique the icons that are being designed for your application. Then today, Apple Help. Learning about how to do a good help system and provide user assistance with your product on 10. And then Speech Recognition and Synthesis. Fantastic session to learn how to add speech synthesis or recognition to your application and really extend the user experience on 10. And then yesterday's 114, which if you didn't get there, watch it on DVD.
and then some other sessions that were yesterday or the day before. So let's get into the Q&A. This is my contact information. If you have any comments about this session or if you have feedback in terms of what's supported within High Level Toolbox or within Carbon in terms of user interface, please get in touch with me.