Carbon • 1:07:50
This session covers the new composing model of the Control Manager, which provides a fast, flexible view system and even eliminates the need for WDEFs. Learn how you can take advantage of this new model, including writing your own custom views.
Speaker: Ed Voas
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Ladies and gentlemen, please welcome Lead Engineer, HI Toolbox, Ed Voas. Let's be. All right, we're going to talk about HIView. Very exciting. This is the beginning of something incredible for HIToolbox. First, we're going to talk a little bit about the history of the Control Manager and where we come from, and basically just exactly how far we've come. We're going to talk about HIView, what it is, what it does, how to use it.
Control Manager. Who here likes the Control Manager? That's what I thought. Okay. Uh, but it was introduced in 1984, so give them some credit. quite a while ago. There were some improvements made in System 7, but by and large, it was the same old Control Manager up until Mac OS 8, and that's when things started to change. That's where we added things like control embedding, a lot of new controls, and we started to introduce new APIs for doing things like just activating or deactivating controls. But the Control Manager isn't perfect.
We have a 16-bit coordinate space that it lives in. That's not very good if you want to do, like, highly scrollable views, which have a huge canvas. We erased behind. So, we've done this since 1984. This isn't new. It's just the way it's always worked, and we had to maintain that behavior.
So every time we would go to draw a control, we'd actually erase behind ourselves. And this created all sorts of pattern alignment fun in Aqua. So, and at times, there'd be a failure to clip to our parent. And I think this kind of depended on what node you started at and all of these other fun things. So, there were problems with that.
And, though you can embed controls, you couldn't actually detach a control from the view hierarchy and hold onto it, or just embed it in another window. These are all problems. And you couldn't do overlapping view, and largely this is because of the race behind behavior. We would just trash whatever was under us.
We fixed all of this in HIView. You're going to love it. This stuff is great. What is it? It's a new API destined to replace the existing Control Manager. The Control Manager is just a compatibility API which lives in the same basic space as HIView. This is a real view system. I am not kidding.
This is going to rock your world. And by and large, this change won't affect you. If you're still using the existing Control Manager APIs, they will behave as badly as they always have. Enjoy them. But if you want to take advantage of the new stuff, you can do that too.
So what are the advantages for you, the developer? First, consistency, simplicity. Reduce and simplify. We want to be able to have one consistent model which works everywhere. And once you learn basic HIView concepts, I think you're going to agree that this is a big step forward. It has very efficient drawing.
Essentially, it's a one pass model, and we'll talk about that in a little bit. But, Overall, drawing is a lot faster. And we actually have a unification of our implementation. And what this means is that essentially everything is in HIView. And we'll show you exactly what we mean by that. And it's way easier to write custom views now. Way easier.
Now, in Jaguar, we're not there yet. And even by the time we ship, we won't be completely, fully migrated to HIView. What does this mean? This means that we won't have an analog to the old Control Manager API for every call that the Control Manager had in the HIView.h header, which is a new replacement header. Not yet. And that'll come over time. If you don't see something that you're looking for in HIView.h, just go back to controls.h and use that. It's the same stuff.
And that's because controls and views are largely the exact same thing. The difference is how they act. You know, one goes wild and tries to blow away everything underneath it, and one just draws nicely and behaves the way it should. So it's important to realize that they're actually the same thing. A Control Ref is an HIView Ref. An HIView Ref is our new type to represent views. So they're actually typedef to be the same. Okay, features.
Positing. One-pass drawing. We're going to go into all of this. Modern coordinate system. We use floating point coordinates.
[Transcript missing]
Quartz drawing is the native drawing model of HIView, but you can use Quickdraw. We do proper Z-ordering and clipping. Yes, you won't believe you're running a Carbon application.
And we have the ability to attach and detach views at will. You can just take a view, pull it out of the view system, and hold onto it. You don't have to worry about something bad happening, unless you, of course, try to render that view yourself, and then you crash. But anyways. So anyways, composited drawing. All we do is draw. We do not erase. Not in this mode. And we also sport a predictable drawing order. We always draw back to front. We respect the hierarchy. We respect the Z order. Now, you're probably going, "Oh, my God.
is this, is this HIToolbox? Anyway, uh, am I in the right place? Um, yes, we do all of this stuff. And all these pattern alignment issues that you've had in the past are gone. You don't have to deal with them anymore if you're using HIView. So our goal for getting efficient drawing is to draw any opaque pixel of a window once. This means you won't have to draw it four times if you have something overlapping. And we do this by determining a view's visible area. And we do this in multiple ways.
First we use its parent's bounds. We can determine that if it's kind of outside its parent's bounds, then we know that at least it's partially obscured, if not completely obscured. And we also clip out things of siblings above any particular view in the Z order. And each view can actually have an opaque region of itself. And we use that to help determine what's visible beneath it.
I mentioned we have a one-pass draw model. The way we do this is in validation. The Control Manager doesn't draw. I know that sounds shocking. So direct drawing is discouraged. In fact, there is not an HIView API that allows you to draw a control directly. You have to wait for the drawing loop to pass right now. We'll probably add one eventually, but right now there is none. So I consider that to be very discouraged. If you're calling draw one control, draw controls, update controls, don't. You don't need to.
So, if you want to draw a view, you need to invalidate the view. And you do that by calling HIView set needs display or set needs display in region. And you can invalidate an entire view or just a portion of a view. And then the drawing will actually happen later.
Currently there are two, uh, there are two very predictable times when we will draw. The first is right before we flush. So the normal flush time on event loop, we will actually paint any dirty areas. So we'll just paint it and flush it right in one fell swoop.
And then during window painting. And what that means is as the window is resizing, for example, we actually paint the window immediately. And this is to get the best performance out of live resize and the like. So we will actually draw directly then. Uh, ideally what you would do is you would listen to your bounds change events and update your views and then they'd automatically be dirtied and then we would paint them all at once.
Um, and it's actually important that you listen to the, uh, view bounds change and not the window bounds change. And I might go into that a little bit later. So, we're trying to be lazy. We don't like to do any work, okay? So, we don't invalidate for you. There's only a couple of places where we'll invalidate for you, and that's where it's blindingly obvious.
Like, we're about to show a view. Maybe they want to see it. So let's invalidate, okay? And when we move and resize views, we also invalidate automatically But the view is completely responsible for doing its own validation. If you set the value on a view, if you activate it, deactivate it, set its title, it will not draw. You have to write the code to intercept the notification if you want to draw. And this is done completely to be efficient.
We don't want to be drawing when we don't have to. You take a Control Manager control, like a user pane, and set its title. Well, you're not going to see it, but we're going to redraw everything and everything it contains. But that's what we've always is done. So this is our way around that.
Okay, let's talk a little bit about the coordinate systems that we support here. Essentially, there are two rectangles for every view. So first is the frame. And this is the location of the view within its parent's bounds. It's in, basically, these coordinates are actually in the local coordinate system of its parent.
And speaking of local coordinate system, yes, a view has a local coordinate system. Top left is not at the top of the window or whatever. It's the top left of your view. That's where zero zero is. Very nice. This has some advantages, of course. Now, the bounds are how you move and resize your control. That is what affects your position. But you always draw and you always hit test based on your bounds.
Did I say that right? Anyways. Frame, move, bounce, draw. Okay. And that means every time you get a hit test event, the point will automatically be transformed as the coordinate system of your view. So here's a simple example. Here's our button, and it looks gorgeous, and it's inside this rectangle, which is its parent.
And it has a local coordinate system where 00 is at the top of the button and 120 is at the bottom right. Its frame, however, starts at 3030, which is the offset from the top left of its parent. This is starting to sound like a view system, isn't it? Anyways.
Here's the advantages. One, it's consistent. First off, these rectangles will never change on you, meaning that your local coordinate system is always your local coordinate system. If you move the control, your local coordinate system doesn't change. It still starts at 00. What a concept. And the other benefit is actually a performance benefit, because what we do right now is when you call move control on a control, we will actually move the control, and then we will move all the sub-controls. We don't need to do that anymore, because the sub-controls are always in terms of their parent. So once you move the parent, we're done. We don't have to do all that work. So it's a lot more efficient.
It used to be, way back in the day, that when you wanted to move and resize a control, you had to move it, and then you had to size it. And then there was set control bounds, and that kind of... helped a little bit, but, you know, that thing didn't draw, but move and resize did, and well, what draws and what doesn't? Well, you don't have to worry about any of that stuff anymore.
Doesn't matter. Here's a way to do it. You want to move your control, you just adjust its frame. And you call HIView set frame. It will automatically be invalidated accordingly, and it will just redraw when it's time to redraw. You can actually move it just by a certain delta. We actually support some helper routines like this. Or you can place it in your actual parent view at a specific location. Very easy.
Now, since each view has its local coordinate systems, there might be times when you actually need to convert coordinates between them, and we have routines to help you do that. And we have HIView convert point, rect, and region. And given a region, you also give a source and destination view.
And they could be at any point in the hierarchy. And they will automatically convert between the two view coordinate spaces accordingly by finding the common parent and doing all the fun transforms. If you pass null from one of those, it implies that you're going window relative. And window relative is actually a big, big concept in HIView.
And in fact, a lot of the events that come into your application, all the mouse events, actually have window relative points. And that's to help HIView. So you can, given a window relative point, you can easily convert it into your view space by passing null from as, for example, as the from view.
Because if you're converting from a window relative point to a window view local point, just pass null for the first parameter. And by window relative, I mean, zero, zero is at the top left, the very, very top left of the window surface. The very, very top left of the structure of the window.
Okay. We have some new types. We probably used to point, rect, and, well, we didn't have an analog for size, but now we have HIPoint, HIREct, and HISize. If you haven't guessed, we really like this HI prefix. We kind of dig it. And we go happy with it.
That's just me. HIPoint actually saw its debut in the 10.1 release. And the reason we introduced it at that point, was because we were dealing with tablet events, and we introduced the tablet event support and all that fun stuff. Obviously very popular with that one gentleman. The beauty of this is that you can actually have subpixel resolution in your event, and that was the reason we let this out before the rest of HIView. But it was just part of this larger thing, so it might have seemed out of context at the time, like, "H.I.
Point? What the heck is that?" Well, now it all makes sense. And these things are, like I said, H.I. -- they're actually floating point. And they're actually just typed up to the CG rectangles. And there's a reason for that. And there's a reason we renamed them. And one of the more minor reasons is, you know, consistency with our API. But there's a better one.
So we have two graphics models you can use. The preferred mechanism is Quartz. You can also use Quick Draw. So if you want all the fancy schmancy Quartz, you know, graphic stuff, anti-aliasing, transparency, all of that stuff, you choose the Quartz path. If you have code that you want to migrate to be in this new world, you can use Quick Draw.
So Quartz, native drawing model. Again, you can use all the fancy stuff, baziers, scaling, rotating. You can do all that fun stuff in your HIView. Quartz coordinates, however, are at the bottom left of your view. But guess what? Not if you're using HIView. You put it where it belongs.
Now, of course, it's just a context, so you can flip it if you want. It's completely up to you. And we might have APIs so that you can have it either way, 'cause some applications actually really like the lower left positioning. But the reason we've made it top left-- well, there's actually, like, three reasons.
First off, the windowing system starts with 00s at the top left. Your existing code that you've always written has always been 00 at the top left. And when you resize windows, if we had bottom left view frames, whenever you resize a window, the bottom would be moving, and we'd have to actually adjust the frames of everything even though it wasn't actually physically moving in the window. We wanted to avoid all that. So we just stuck with what we knew worked in the past. The only problem here is that you're drawing upside down.
And if you were to call CGContextDrawImage, you would find that out the hard way. So what we've done is we've actually introduced an API here to help you called HIViewDrawCGImage. And this will actually just flip the context momentarily, draw the image, and you're done. So we're trying to help out where we're necessary to deal with this, you know, where we think it's -- that's the way it's always been. It's actually upside down.
So that's that. I mentioned that Quick Draw drawing is supported. And it is. But you have to let us know. And there will be a way for you to advertise, hey, my view needs Quick Draw. But you gotta realize that when we do this, we're actually gonna, you're gonna be paying a performance cost, and that is because we have to sync up Quick Draw as we're descending the hierarchy to set up for your view. Where, if we're just going CG only, we don't need to do that. So just keep that in mind.
If you ever wanted a picture of a control, it was really hard. Really hard. You had to create it just off screen and do all this other stuff. But even then, you couldn't get an image that you could composite on anything because it didn't have alpha in it.
This is easy now. All you have to do is call HIView create offscreen image, and you have a picture that you can actually just blit onto something really easily. And in the near future, you'll be able to take these images and just use them as drag images or whatever. This is actually what the toolbar uses. It's pretty cool. All right. Demo time. Let's start to see some of this stuff work. We'll see how my radio mouse affects the microphones. All right. What's number one? All right. Here's a window. This window has compositing on.
All right. What I want to show you first is the view hierarchy. Now here's a little window that shows the view hierarchy. And I also want to turn on this little edit mode thing. First thing to notice is-- well, let me point something out here. That's the content view.
[Transcript missing]
Window title: It's a view. Well, I can't see it. Now you can. It's a view. The widgets are views. Maybe I don't like where this is. I'll just move it here. Maybe... I like this bigger. And then you know what? Still works.
[Transcript missing]
So I put this up here. You notice two things. One, this is actually being composited right now. The chasing arrows are drawing over the pulsing button and it all performs great. The other thing to notice, We're actually clipping and z-ordering and all this stuff just works. What you would expect to happen actually now happens. This is great. Another little thing you can notice is that if I move this over here, it's clipped to its parent, the way it's supposed to be.
The other thing I just wanted to show is the content root and all that fun stuff. You'll notice that the content root, when compositing is on here-- and I've enabled it, and we'll tell you how to do that in a little bit-- the content root U, in this case, is not the old root control that you're used to. The old root control had this arbitrarily large space from which to draw. The content root, so to speak, in this term is just a real view. It lives in the hierarchy like anything else, and it's sized appropriately. It's not this wild thing that lives in there.
As you can see, we have this cool hierarchy and everything works. That's really neat and all that stuff. The other thing I'd just like to point out is that this whole little thing that you see here was written using Carbon events, and it was done in a couple of days by one of the engineers on my team, David McCloud. This is pretty cool because not only can you just look at the hierarchy for any window, but you could do interesting things like, let's see, I want to look at this round button. Hmm, okay.
You could do interesting things that maybe I don't like where it is, so I'll just do it. You can move things around. This is just written all Carbon events. This just works because now, especially with all this stuff that we're working on, we're doing all this stuff that we've added from HIView, we get a lot more notifications for things. You can change things like the frame. You can change the minimum and maximum and move things around and do all that stuff. You can write an interface editor in a couple of weeks, and I'm not kidding. It's really cool stuff.
I'm working on this character editor. And this character editor, I'm prototyping it right now. And so I've just got some basic things in here. And as you can see, here's my nib, and everything looks fine. But I'm going to run it. So we'll just click the old button here.
And oh, God. Already I got problems. Look at the role. It's like stamping on the first thing. And I had a rad thing over on the left there, and it's not there. And the word awesome there, it's kind of close. I don't know. I mean, let me see what happens if I do it. Oh, God, look at that. Dude, what am I going to do now? Well, I guess I'll have to, I'll deal with that later. But in the meantime, you know, my boss just walked into my office, and he's like, all right, look.
For this character editor, we want a picture of the character on the background of this thing. And I'm like, dude, I don't think you know what you're asking. I don't think I can do that. This is like the toolbox. It doesn't support that stuff. But I'll try it. What the heck. Let's go for it. Take some space here and get the picture item. And just pop it in. And resize it appropriately.
[Transcript missing]
I mean, I, I still have all the visual problems, and now I got all this real, what's that stripey stuff? That's not what I want. Oh, God. Damn Aqua. Anyways. The, the, the best part about this is that not only does it not look right, it doesn't work. I can't actually click anything. And this is because the click order is the same as the visual order. So what's first to draw, which is Guy, is, you know, so Guy is interfering with my mouse clicks.
I'd love to talk to him about that. So what am I going to do? All right. I guess there's multiple things I could do. I mean, let's see. I was reading on Carbon Dev. I guess I could patch the Quick Draw bottlenecks. Maybe I'll fix that thing. Or maybe I have to rewrite this myself or use something else. I don't know. I just don't think it's possible. But the fact is, it is possible. Let's do it. All right.
[Transcript missing]
But guess what? It works. So we can make him Jeff Daniels. And we just move it. Everything And that's just one sample of compositing. So now, not only has everything been drawn like that, but notice the pulsing button is pulsing, and it's redrawing, it has to redraw the picture behind it, and it obviously only redraws that portion of the picture that it needs to. And it's really fast, and it works great. Okay. Next.
In the last session, I briefly showed scroll view. I just want to show off a couple of these things. These are a couple of views that we wrote. These are completely HIView only. So we have an image view and we have a scroll view. And the image view can be embedded within a scroll view. It supports the scrollable protocol. So the image view is interesting.
Well, not really. It's just an image, right? But anyways, you know, it resizes and all that fun stuff. But, you know, I got this big honking grow box sitting on my picture and it really interferes with things. Well, realize that the grow box is a view. Realize that the grow box is actually z-ordered above the content region. Okay. Realize that we do compositing.
With all of that fun stuff, I could just come up here and make the grow box transparent. And that is that easy. Ed Voas We could just throw this stuff together these days. This is just fun stuff. Just to show you a little bit of stuff. You can actually also size to fit the picture. We support that as part of the image protocol.
[Transcript missing]
I could scroll this way. And the other thing that we could do now in Jaguar is if you hold down the shift key, you could scroll that way. Just thought you might want to know that. Anyways.
So the scroll view is kind of cute, and it does all this neat stuff, but, you know, we actually even support a small variant. So we actually have small scroll bars, so you can use it in a palette or whatever. But, my God, that grow box again. Gee, what's going on? And it's, ugh, yeah. It would look a lot better in a floating window, wouldn't it? All right. Let's make it a floating window. Oh, okay, I'm done.
All right. Now... Realize what I have just done. I know, I, well you selected that menu item, I don't know. I just changed the actual window definition on the fly. I want you to try to do that. I dare you to try to do that on today's Mac OS. You can't do it. And this is all because of HIView. The other thing we can get is Metal. I have my mental scroll view, and ain't it cute? But, of course, I have large scroll bars. So I can set that back.
The other thing about the scroll view, just to give you a couple little things, is you can actually reserve space for placard and all that other fun stuff. But what I want to also demonstrate, the scroll view works to the best of its ability, meaning that it will try to scroll as fast as it can.
[Transcript missing]
So it's kind of, you know, if I just blitted the bits, I'd actually do some really bad stuff. But... Because it's all composited, I can actually scroll this right over it. And notice that the performance is awesome. And just to prove that it's really on top of the scroll view, you can go check that out. We're actually drawing on top of the scroll bar. Or I could be doing this, and it still works. All right. One more.
So, one of the things that I didn't actually show you in here was that... Let me just turn on resizability with this example and run it again. One of the features of the scroll bar that's supported is auto-hide scroll bars. So right now, this window is in the scroll view.
But just the controls are, the picture's not. The picture's the background. So I think you know where I'm going with this. Let me resize the window a little smaller. There it is. Pretty cool, huh? But that's just scrolling. Big deal. Check this out. This is compositing. It all just works. Out of the box, nothing fancy. That is the power and the glory of HIView.
I think I turned this sucker on. Looks pretty cool. First off, it's per window. You have to enable it via a window attribute. And the window attribute is called kWindowCompositingAttribute. And you have to use create new window because it has to be specified at construction time of the window. You can't use change window attributes later.
So if you're using new C control, you might want to, it might be a good time to switch to create new window. And once this gets turned on, all this glorious stuff comes to you. you. The standard handler is actually highly recommended, if not almost required, only because of differences in the way that events are processed. Now I'm going to talk a little bit about that and kind of tell you, but in general we always recommend the standard handler. Party line.
So, I showed you that when windows are composited, it operates completely in terms of views. There are views everywhere, from the structure down. All of that window that you saw, in fact, all of the windows that you see on your system actually have views at the very root level, even on the ones that aren't composited. We're that fancy.
And we did that, obviously, so that we could have one implementation instead of two. That means that the WDEF is dead. You don't need to write WDEFs anymore. All you need to write is views. This is part of reduce and simplify. You don't need to deal with this stuff anymore. It's just one model, and it works everywhere.
You have this composited window. And it all uses views, which means that when we paint, we just render the view hierarchy. And this also means that things like paint procs, if you have client paint procs installed or you expect the content to be erased, it's not going to happen. It's just going to render. However, this is pretty cool. You can actually do cool stuff. You know that the content view exists.
You can probably find it. In fact, we have the control ID specified in our header. So you can actually get it and override the draw method on it and put a picture in the background of your window, just like that. It's just really simple. All these things that were previously difficult to do have become really, really easy. In fact, when you saw me switch the WDEF from document to floating window, I wrote that in a half an hour.
And I don't mean I wrote the code to actually switch it. No, I wrote the code in the WDEF to make it switch dynamically in a half an hour. Because that's how easy these things are. We can start bringing new features faster than you've ever imagined with this stuff.
When the windows resize, we actually just resize the hierarchy. We resize the structure. And what happens from there is the content will resize and all the things propagate down the view hierarchy, much as you'd expect. We have a new event that you can listen to. K event control, parent bounds changed.
This gets sent out whenever a view has resized itself so that children who wish to listen and react to parent changes can. That's one way of doing it. And the other way is, obviously, if a parent manages its children, much like the toolbar or something like that, it would actually do the layout and resize.
When a window is hidden, we actually hide the root view. And this is so that you know I'm hidden. You don't actually have to check window visibility ever. Just check view visibility. That's all you need to deal with. And we do this so that you don't actually try to do any crazy drawing or all that fun stuff.
Not that you have an API to do that, but... And likewise, when the window's shown, we actually just show the root view and repaint everything. And this allows us to actually repaint everything before your window comes on screen. And this actually means that you don't need to listen to k-event window draw content. In fact, there's a lot of k-event window events that you don't need to listen to in this mode. And a standard handler is like, you know, partially neutered because of this. It just does view stuff. And we'll talk about how that works in a little bit.
view hierarchy. As I mentioned, the hierarchy actually starts in structure. So the root view is the window frame. The window widgets are actually views. It has a content view, and you can get that and any other standards sub-view via HIView find by ID, which is the analog to get control by ID.
Now, we used to have this API, and it was called CreateRootControl. And when you use it, it didn't really create a root control. It created something that lived in the content of the window, and that's not what you want in this world. So, It behaves a little differently, but it tries to be compatible so that you can migrate to it easily.
So first off, if you try to call createRootControl on a composited window, it will just tell you the root already exists. And if you call getRootControl, you'll actually just be handed the content view. And this is just so that you can have a compatible behavior. So if you used to call getRootControl and bedControl, you can still do that.
If you want the true root of the window, you just call HIView.getRoot. This is the only API, say from one debugging API, that actually takes a window ref. Every other API is done in terms of views. And that right there at the bottom, khiviewwindowcontentid, is the actual control ID of the content, so you can actually find it if you wanted to. Let's talk a little bit about embedding.
We always had Embed Control, Auto Embed Control. But it's similar. If you want to add a new subview, you call HIView@subview. Now here's the interesting part. If you want to remove a subview, you just call HIView removed from superview. That view is now detached and living separate from the Windows control hierarchy. All the new controls that we introduce will not have an owning window parameter. So when you create them, they will automatically be detached. All of the existing APIs that do take an owning window parameter now accept null.
I showed you that we actually properly z-order. Well, a z-order is something that we support fully. You can iterate the hierarchy just by calling getFirst or lastSubView. You can walk forward or backward. And you can actually change the z-order of controls on the fly and have it redraw properly just by calling HIViewSetZOrder. And you can send views to the absolute back, to the absolute front, or in front of or behind some other relative view. It's pretty cool.
House event handling is a little different in HIView, but not different in a bad way. In the past, you had to do a lot of work. Get the event, call findWindow, figure out what the result was. Let's see, the result happened to be in the contents. I'll call findControl and do all this other stuff. And maybe I'm right, maybe I'm wrong. And maybe it was in the window. I'll call trackbox. There's all these different ways of doing things. It's kind of a pain in the neck. And that was what our standard handler was designed to kind of insulate you from.
Now it's even easier. Basically, when you get an event, it already has half the parameters you want. For one, if you get a mouse event, it already has the window that the mouse event is for, right in it. So all you need to do is extract that, and I should also mention it also has the window mouse location.
Once you get that from the window, all you do is you just figure out which view to give it to, and then you give it to the view. Very simple. And the other beauty is you don't have to treat window widgets differently. This works for all widgets in a window. Period. So in the example that I showed where we have all these widgets, and I clicked on the collapse box while I was in the middle of the window, there's no find window going on.
All it was was that I just clicked on that view. I don't know what it does. I just clicked on it. And the view itself actually is the one that sends out the events. It is actually the one that handles all this stuff. When you click on the grow box, that view calls resize window, much like you'd expect.
So I mentioned these two new parameters: keventparam_windowref and keventparam_window_mouse_location. These have actually existed since 10.1, and you can use them back to 10.1, although the window mouse location might be a little wonky in that release, something I just discovered. But you can find out the window easily just by extracting it from the event.
So we have a couple of event APIs that I'd like to discuss. The first is called HIView. Get view from mouse event. Now... In the past what you would do is do something like a find me the deep or shallow sub pane that was hit and then I'll send the thing there. And we have a function like that and I'll show it to you in a second.
But this is the preferred way to find out, for a mouse event, who should get it. And the reason that it's preferred is that as it descends the hierarchy, trying to find the right pane to give the event to, the parents have a chance to override this. So you can actually stop and say, "No, no, no. I'm going to handle all events for my children." The toolbar uses this, for example.
When you normally click into a toolbar, the click goes right to the toolbar item. But if you hold down the command key, the toolbar says, "Oh, no, no. I want that because I'm going to configure." So that's a way for you to actually override what's going to happen with that event. So you should always call get view from mouse event as opposed to like some raw find me the sub pane call. In general, though, you probably won't even need to do that.
Once you have the target view, all you do is just tell it to click. You say, "Here, handle that." So I'm assuming here you have a mouse down, but just pass it the click event, and it will then fork into one of two events. It could either be a contextual menu click or a regular control click, and that will get handled as expected from the control point of view.
Here's the low-level call, HIView.getSubview.hit. It has a parameter to allow you to either search deep or shallow. So you can find out just the shallow one of your children that was hit, or maybe what the deepest child of yourself was hit. The first parameter is actually the root of where you want to start. Again, we always accept views, we never accept windows.
We also have an API called HIView Simulate Click, and this is actually helpful when you're dealing with keyboard navigation or accessibility. For example, if you receive an accessibility event telling you to do a press action, you could just call HIView Simulate Click on yourself. As long as you're just a simple clickable control, it'll just work. And you can handle that in special ways if you want. It's pretty cool.
All right, so here's the basic thing. So we've gotten our event, and we've determined the window. So all we do is we go, all right, get me the root for the window. All right. Get me the view for the mouse event. Okay. Handle it. Three lines of code. So it couldn't get any easier. Well, it could. Just use a standard handler. Simple.
[Transcript missing]
One other thing I want to mention is that if you have an existing control hierarchy that you try to bring into existence, and they're all using the old bounds, you're going to find out that half your stuff isn't going to be visible. And that's because your bounds need to actually change to be parent relative, and they probably aren't.
All right. So, where does your custom content live? I have this nifty view that I've created, and I spent a lot of time on it. How do I make it work with HIView? Well, the answer is you need to actually wrap it into an HIView. But it's actually not that hard.
Most of the, if you actually look at some of our classes for doing our view stuff internally, they actually look like any other view system. And so you probably will find an analog to something you're already calling or you have, or a method you already have. And it's required to use, to wrap your stuff in HIView because we want to maintain consistent behavior. You want to be properly composited. You want to play in this sandbox here that we've just created. So it's kind of important.
So, much like creating any other HIVobject, as described in the last session, the way to do this is you merely just register your class. You pass in your class ID, tell it what view you're going to subclass, or in this case we're just gonna subclass right from HIView, and then you just fill in your events and you're off. And then you just create it later with HIV, HIVobject create.
So, typically, you're going to want to handle the first two almost as a given. You need to handle hit testing and you need to draw, unless you're just some sort of container object. We also want to just touch on region calculation a little bit. It's something we never really talk about, about what the structure region of a control means and drawing outside your bounds and all that fun stuff. And then I'm just going to run through drag and drop. It's just something I pulled out of a hat to talk about, 'cause we never actually talk about drag and drops in the context of controls ever, really.
So, simple hit testing. Now, you'll see what I have here is actually a C++ method. And that is a method on a class that I've installed that actually wraps up all the HI object stuff. So when I actually get a K event control hit test Carbon event, I turn that into one of my methods here. And what I get is an HI point.
So all I do is I assume that, you know, I have no part was hit. I get my bounds, and then I just see if the rectangle contains the point. Now, you'll notice I'm calling CG_REC_CONTAINS_POINT. And that's because the CG rect is an HI rect is a CG rect is an HI rect.
Remember, they're typed to have to be the same. So you can actually use the CG geometry APIs on our rectangles without any casting or any funny business. And then if it is in the rectangle, we just return it's in my control. And yes, I should be tracked or whatever.
From the draw method, we send you kEventControlDraw, much as we do today. It takes a couple of parameters. Now, if you've written a custom control using Carbon events, you'll notice that there's a parameter missing here. And that's the part code. We do not tell you what part to draw when you're in composited. You tell us what part to draw by invalidating some specific portion of your control.
And what happens is, when you get told to draw later, we will actually figure out, "All right, he told us he wants to invalidate this portion, and we will intersect that with his vis region, and we know that this is all that needs to be drawn." And we actually pass that to you, and we say, "This is all that needs to be redrawn." And that gets passed into this draw region parameter.
When you get that, you can actually use it to figure out, or you can ignore it if you want to. You don't need to use it. But if you have a really complex control and some content requires expensive rendering, and it's not actually part of the region that needs to be redrawn, you can detect that and say, oh, I don't need to bother.
I'm just gonna draw this part and, and get out. So that allows your control, your view to be extremely efficient about what it draws. And that is effectively the replacement for the The other thing is that we pass you a CG context. This context has already been transformed appropriately and all that fun stuff. You should not call beginCGContext for port if you are going to do core graphics drawing.
If you're going to do quick draw drawing, just do quick draw drawing. If you're going to do CG drawing, use this context. And this is very important as we move towards having printing actually work again with our stuff. We want to have one single context that we give to you. And, you know, whatever it happens to be, you know, window, printed page, whatever, it'll just work.
Here's my method. Draw. Again, I get the bounds. Remember, my local coordinate system. Get the bounds, and all I do is I fill it, I set the fill color. Oh, that was nice. I set the fill color and then I stroked the record. I set the fill color and then I outline myself in black, apparently. So don't use it. It's an example. Come on. Anyways, use your imagination.
So yeah, so all we do is we just set the color and we, and we outline a rectangle. But the important thing is here, I haven't checked for visibility. I haven't intersected myself with my vis region or done all this, all the clipping or anything. And in fact, I haven't even, like, saved the context, restored the context. You don't have to do anything. When you get told to draw, there's only one thing you should do.
Draw. Right. So you don't need to save the context. You don't need to restore the context. We've done that for you. Call save, CG, CG context save or whatever it is. Save G state before we call you, expecting that you're probably gonna just trash it and stuff and do all types of fun, neat stuff, and then we'll just restore it and then we'll move on to the next pane that we need to draw. So you can just get in there and just do your drawing. Very easy. And it's also important that if you're in other methods in your view and you want to draw, that you don't draw. Don't call your draw method directly. Don't call draw control. Just invalidate yourself.
Regions are kind of an interesting little topic, and I'll tell you why. If you ever notice, we tend to draw outside our bounds a lot. We like to draw outside the lines. And the reason is, kind of history, but also the ability to make sure that a button never actually changes its real metrics, and its real metrics might be how tall it is.
So, for example, the push buttons are 20 pixels tall or whatever they're supposed to be, and they draw-- but they draw the shadow outside those bounds. So the way we do that is we actually determine a structure region, which is really-- we call it structure region, but what it really is is drawable extent. And your view can choose to support this or not.
And we also have this concept of the opaque region, which tells us which areas of your view are opaque. I'm gonna talk about each one of these now. So like I said, Struct region allows you to actually do drawing outside your bounds. So if you are going to do that, you must intercept this, uh...
[Transcript missing]
So, but if you want to do things like focusing, you may want to draw outside your bounds, you may want to support this.
And then later, if you need to actually reshape yourself, let's say you've got focus and now you need to extend your structure region, or maybe you lost focus and now you want to shrink it up a little bit, you can call this new API called HIView Reshape Structure. This will actually allow you to, we will actually re-query you for your new structure, and then we will invalidate and repaint as necessary. So I lied, there's another case where we invalidate.
The opaque region is important for efficiency. If you have truly opaque areas of your view, you should define them to us. Otherwise, we will do a lot of unnecessary drawing. Let's consider that I'm a picture view. And I'm going to draw my picture. And it's just a rectangular thing, and it's always opaque. Let's just assume that it's a rectangular thing. So I'm going to put one of these picture views in the content of my window. And I make it completely obscure, the content view.
Well, if you define-- if you don't actually support this specific selector for the getRegion thing, getRegionEvent, what's going to happen is we're going to think you're transparent. And so we're going to think you need compositing. So when we actually draw the window, we are going to paint the contents.
We'll paint all the stripes or white or whatever the background of the content is, and then we will draw your picture. So that goes against our goal of drawing all these opaque pixels once. So it's really important to define your opaque regions if you're implementing views. And like I said, if you don't actually respond to this, we're going to think you're transparent.
Out of a hat, I picked drag-and-drop, only because it's mildly interesting. But it's fully supported in this view system, and in a very hierarchical manner. And we support the four big Carbon events that you would expect: enter, within, leave, and receive. The other reason I wanted to talk about drag-and-drop is simply because these Carbon events weren't exported until Jaguar. It didn't exist. So, at present, you need to actually turn on drag support for the window by calling setAutomaticControlDragTracking. Enable for Window. Anyways, run along. And hopefully we'll get to a stage where we don't actually need to do that.
Now here's the deal. We try to be efficient. So if we are dragging onto a view and the view doesn't want the drag, you should tell us. Don't say, "Oh, I might want it." No, just tell us yes or no. Do you want it or do you not want it? Make up your mind.
Be decisive. So if you don't want it, though, we won't actually bother you anymore. We won't give you within messages, we won't tell you leave, and you can't be the receptor of the drag. That just allows us to be efficient with who we're telling, you know, who we're talking to about the drag.
And if you might want to drag, maybe, you should return nowhere and say, yeah, all right, I might like something in here. For example, the toolbar, when you drag over it, you drag and you hover. And at first, it's like, no, I don't want to drag, I don't want to drag, I don't want to drag.
Finally, you know, time passes by, you go, oh, okay, and then it takes the drag. That's a case where it might actually want it later. So we actually do say, yes, we want to be -- we want to play this drag game. And then later on, we actually really accept the drag.
The other interesting little bit about this is just, you know, it kind of works as you expect. The innermost focus view always gets the within events. Well, maybe this isn't what you expect. Views along the way do not get within events. Only the target, the current target of the drag will receive within events. And then once it leaves, then its container will get the within events and so on and so on and so on.
And obviously the innermost target is the drop. Location. All right. Where are we? Oh, demo. Good. All right. It's not much of a demo, but hey, I can't compete with my last one. Basically, all I want to take you through is some code, some code that's been wrapped in a view, in a TView wrapper. Unfortunately, I have to go dig. Bear with me.
Basically, I just want to show you how we write views in the toolbox. Now, keep in mind that up until Jaguar, we were not using C++ for our views. We were very lucky if we were using C++ for our views. And before that, we couldn't because of all the problems inherent in pre-Mac OS X.
So now, we actually have our own class. Now, our class is actually called HIView, but it's not a class that we're going to be using. Here we're going to show a class called Tview. And this isn't much of a technology demonstration as it is just an example of how you can actually use this.
So anyways, this class I just want to say is very, very, very, very, very, very, very, very, very, very much like the one we use internally. It's almost identical. just doesn't do as much stuff. But, as you can see, you know, we have a few public methods, which we do here. But here's all the interesting stuff, which I have in my protected section.
And here's examples where we do things like draw, hit test, we get the region, we can get and set data, you know, when get control data is called and stuff like that, we handle that here. There are things called size constraints. And let me actually show you an example of size constraints for a second. And how they can be used.
Okay. Scroll view. I mentioned that when it resizes, it, you know, it hides the scroll bars. Well, that's great. But notice that the window also pins to the maximum size there. And it does that by knowing the constraints of the control. It knows that it has a minimum and a maximum size. So in this case, the maximum size is the maximum size of the scroll view.
So the scroll view, you know, basically when the window gets resized, it's going to be The window, the code that intercepts that says, all right, well, hey, scroll view, what's your maximum size? And it says, oh, I'm this big. But in order for the scroll view to get that, it actually asks the image view, what's your maximum size? And it would theoretically keep asking until finally it goes, all right, I know exactly how big I should be.
And that's how we do this. So in this specific example, what we're doing is we're actually intercepting the window events for, what is it, k event window, get minimum and maximum size. So whenever resize window is called, for example, these Carbon events get sent out. And all I've done in this example is I handle them, and then I ask the main scroll view for its maximum size and return that.
And we automatically know how to do this stuff. I also wanted to point out, because I forgot to mention it earlier, that while we are resizing here, you notice you really don't see any, uh,
[Transcript missing]
So when you get the balance change event, then you would move them, and then you see this lagging behind where the window stretches and then the content finally paints. So it's actually really important to listen to balance change events at the view level and not at the window level. And that's what I've done in this example.
So here we are, back in our little class. So as you can see, we do things like get size, get rates, get optimal size. That's the equivalent to get best control rect. We can find our window ref. We also support being able to subclass, register subclass. We do a little fancy C++ stuff there to do that.
Our drag and drop protocol at the bottom. Basically, it's pretty full featured. We even have command processing and all that fun stuff. And at the very bottom, we have a class. And the class handler is where we intercept our actual Carbon events. And we call it a virtual method here called handleEvent.
And I kind of talked about this a little bit in the last session. Let's just look at some of my ugly code. So here's the actual Carbon events that we listened to. As you can see, the top three are the HIObject protocol. The rest of it's all commands and then control stuff.
Now, let me scoot down to the actual event handler just to show you a couple of things. Okay, so here's our event handler, and this is for the class. And the class handles construct, initialize, and destruct. And... As I described in the last session, you're called in a specific order and there's all this fun stuff that goes on to be able to create this. But what we do here that's kind of interesting is when we get called to construct, whenever we register a subclass with this specific C++ class, we actually pass a static method of whatever your subclass is so that you can be called later to construct.
So we actually pass that in to the register-- HIObjectRegister subclass. So when we get called to construct, we get past that static constructing in the user data. And as you can see, I actually use-- I just cast the user data into my construct proc and just call it.
And then I get my view back and I stuff it back into the Carbon event and we're created. Then I have my virtual method here to initialize, and then to delete, I just delete. Now, all the HI object stuff, all the Carbon event stuff is all wrapped into this class. I don't need to do it anymore, unless it's very special cases or whatever. I can actually now derive a full C++ view hierarchy.
Ed Voas Ed Voas: From this class if I wanted to. So you can actually hide all of the HI object and Carbon event stuff if you wanted to and go C++ from there, while still maintaining all of the benefits of being an HI object and an HIView. Ed Voas Ed Voas: So essentially you can program in whichever language you want. And I mentioned we call handleEvent. And handleEvent is a virtual method here. And here's where we do all the stuff. And basically, this is where we unpack our events and end up calling virtual methods on our class. Pretty straightforward. Not very exciting.
Thrilling, huh? Okay. Let's just look at a simple thing I wrote, which is a subclass of this. It's called TScrollableView. And it's just this simple little view that follows a scrollable protocol and draws a picture. So, as you can see, it's a C++ class, it's T-scrollable view, does all this fun stuff. Here's where we actually register our subclass using our Tview method, static method, actually.
And here's what we draw. It's this fancy, bazier stuff that I stole from somebody because I'm not smart enough to do this stuff. So anyways, here we have hit testing. And here's our static constructor. I mentioned that what we do is we actually have a static constructor for subclasses that we actually just pass in and and when the construct event gets called, we actually just get called right here on our construct, and we create a new T-scrollable view, and we just return that.
And here's our size constraints. In our case, we have this virtual bounds, because I'm trying to play with the scrollable protocol. And here I have the event handler. Now I don't actually have the scrollable protocol implemented in the base class. I'm actually doing Carbon event stuff here. But the interesting thing is that all we're doing, oh, no, I lied.
It wasn't that interesting. But one of the things that I could do is add a protocol. And I just want to go back to TView for a second to show you something. You can use Carbon events in certain ways so you can define interfaces like this. We have drag and drop interfaces or keyboard focus interface. And what you can do is you can activate these interfaces. Let me show you that just briefly.
So here's a case where we have, you know, some derived class wants to activate an interface. And the reason it's not active by default is we don't want to start intercepting all these events if nobody needs to deal with it. So instead, we can choose to actually activate a certain set of virtual methods in our base class just by implementing something like this. But the basic precept here is that you call addEventTypesToHandler. This means that you can, at any time from any subclass, in theory, add events types to your event handler and just intercept them in certain ways.
We're going to make all of this class available to you. We're going to put it on the CD, and you guys can peruse it and see how to play with this stuff. You might even figure out how to map it to any existing framework that you might have so you can actually start to integrate HIView into your stuff.
But my scrollable view, we also handle the scrollable protocol again. It's mentioned in the, I won't go into it here, but Yes, it's supposed to have a constant there, but that's where I figure in my opaque region, and we just make it our bounds, basically. So let me just run this.
or not. You know what? I'm not going to run it. Well, I'm not going to build it. I'm just going to run it. All right.
[Transcript missing]
And there it is. And it scrolls, albeit strangely. But, and there's that complicated math that I don't understand. It looks good. Anyways, my point with this whole demo is just simply that it's really, really easy to end up writing in HIView.
And, You don't have to necessarily use C. You can use C++. You can use anything you want. And you can actually kind of push a lot of the Carbon event stuff down into some common base class and do a lot of neat stuff from there. It also provides you kind of a way to kind of figure out that, oh, you know what? This class is really a lot like my class.
That means I could probably derive my base view in such a way that it just works with the HIObject. And then you can just have, oof, and then, you know, immediately your view system is working as an HIObject or an HIView. That's really cool. So here's the deal. HIView, one huge step forward for the HIToolbox. This is by far one of the coolest things we've ever done.
And it's finally here. And this has been something that we have been working on for years. I mean, maybe not HIView specifically, but we've been trying to put all of the foundation in place. Carbon events, HIObject, now HIView. Jaguar is the beginning of a brand-new toolbox. I mean, a toolbox.
It's grown in ways that are just amazing. And now we're at a stage where we can do all types of cool stuff quicker than we ever have been able to do in the past. Just that simple window frame thing before. People said, it would be nice if we could change the window frame on the fly. And we're like, yeah, well, that's hard. I don't know. But now we can just do it.
All this stuff is easy. And the more that you guys actually start to play with this stuff and use it, you're going to realize the same thing. You're going to be like, wow, this stuff is really cool. So our future is clear. Carbon events, HI object, HI view. Now, the deal... Well, you might notice that one of those doesn't start with "hi," but I was this close one day.
And I really want you to start to look at the WWDC seed. And you might find a lot of the bugs that I fixed for my demo. But anyways, you can at least start looking at the headers and see the types of APIs that we provide. Maybe there's something that's not there that you absolutely need.
It's imperative that you tell us this as quickly as possible, because we are trying to wrap up what we're doing here and get everything qualified and working great. So in terms of APIs, we're trying to lock them down at this point. So please try to get us your feedback as quickly as possible.
Okay, roadmap. New controls and services. This is a session so packed with information, we don't have any demos in it. I'm sorry. You just have to listen to Guy drone on and on about new controls and services. But we have a lot of cool stuff. He's going to go into the toolbar.
He's going to tell you how to use a toolbar, how to add it to your window, how you can add items to it, all of that fun stuff. He's going to talk about drawers, how to use those. He's going to talk about how to hook into the service menu. And there's just a lot of new stuff to talk about in that session. It's going to be great.
And after that, we have improving performance with Carbon events. Maybe you've just gotten onto Carbon events, now you want to know how to tune your application just so. And what's the most efficient way of doing things. This session will tell you how. It will also have things that aren't part of Carbon events, ways of making your application even faster.
And then tomorrow, you can tell us what you think. Hopefully they're good things. And it's at 10:30 in Room J1 tomorrow. I'd like to see you there. For any technical questions or concerns, you can contact Xavier. And we have preliminary documentation for this stuff. I believe it's on the website and all that fun stuff.