Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2003-424
$eventId
ID of event: wwdc2003
$eventContentId
ID of session without event part: 424
$eventShortId
Shortened ID of event: wwdc03
$year
Year of session: 2003
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC03 • Session 424

Cocoa: Tips and Tricks

Application Frameworks • 57:48

Have you ever wondered how applications implement some of their coolest functionality, such as roll-over popups, progress bars during file saves, or Dock icon badging? This session presents useful techniques - using real code and real examples taken from real applications - to make your application even better by taking full advantage of the power of Cocoa.

Speakers: Chuck Pisula, Tina Huang, Troy Stephens, Doug Davidson

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

We're going to talk to you about some cool tips and tricks. Sort of think of this as the roar for sticking around so late on a Friday evening. And what we're going to do is we're going to talk about some things that you could do which would put cool features in your applications and mimic some of the things that you've seen our own applications doing.

And these topics will range from beginner level topics that will be easy for everyone to understand to some more advanced topics. And don't worry, there's going to be lots of code and demos, but all the source code will be available online for download. I don't know if it's there right now, but it should be there sometime today.

Okay, so the way we're going to run things today is you guys get to ask the questions and we're going to answer them. Unfortunately, Jason gave me a list of questions right before I came up on stage a couple minutes ago, and I punched in all the slides and all of the demos so that we can just flow through nice and smoothly without having to go out to the crowd.

So the first question you guys had for us was, well I've seen iChat, Safari, Address Book, all these applications with these cool roll-over effects. So just to explain what the roll-over effect is, we've got the mouse moving back and forth and you see it represents that I have some clickable area. Okay, so the idea for doing a roll-over is really quite simple.

What we're going to do is track the mouse. We're going to need to know when it enters and exits a region. And once it does that, we're going to set up some state that lets us know that either we want to draw a roll-over or we're done drawing the roll-over.

Okay, so how do we track the mouse? Well, you could imagine that we continually monitor where the mouse's position is at every instant, every time it moves. But that seems a little, well, a little unnecessary and very inefficient. So, all we really need to do in most situations is know when the mouse enters an area and when it exits an area.

If we had to, we really could track the mouse at every instant in every location, but we don't need to for this. Let's just be nice and efficient about it. We're going to use NSView's API to add a tracking rectangle. And when we do this, one of the parameters we can specify is the owner. And the owner gets notified with "mouse entered" and "mouse exited" whenever the mouse enters a region and when it exits a region.

Okay, so once it enters a region, we want to set up our drawing states that we know we want to draw a roll-over. And we're going to use NSBezierPath to help us draw the roll-over when we have our state set. And what we're going to do is use this API to append There's supposed to be a little image there that shows you what we're doing, but there we go.

We're going to append two caps at the end, two half-circular arcs, and when we're done, we're going to use its API to close the path, close it up nicely, and when we draw it, we're going to use its API to fill. And we're going to draw a filled region created using this Bezier path. So if we could go to demo one, please.

Okay, so I'm going to start out first by showing you what we get when we run the application. See, we have some other fans out there. When we move over a region, We get a nice little highlight effect with the Bezier path. Okay, so let's see how we did that. We start by bringing up our nib to see how things are configured.

And we have a number of text fields in here. And the important thing about these text fields that I wanted to point out is that it's called a roll-over text field. And if we look at the classes, we see that roll-over text field is a subclass of NSTextField.

Alright, so we look at the code in rollover text field. We have two instance variables. One is a rollover path. So whenever we enter a region, we're going to set up a rollover path. When we have a rollover path, that's our cue that we want to draw a rollover. And we have this rollover tracking rect tag. This is a value that's returned from the add tracking rect API, and it's what we're going to use to reference that tracking rectangle if we need to remove it or change its position.

So bring up the implementation file. And we start out, we have a number of routines which are just utility nicety routines that we're going to use when we manage our tracking rectangle. We've got something to clear the current tracking rectangle, and then we've got one to reset the current tracking rectangle.

And you notice the first thing it does is clear the tracking rectangle, and then put it at a new position. And we only do this when we're not in live resize. There's no point in doing it during live resize because the mouse is over the girl box. It's not over your control. So we're going to do that at the end of live resize and be a little more performance intensive.

Okay, now we've got a number of places where we insert calls to these routines which will either clear the tracking rectangle or reset it at the appropriate times. So, for instance, if the frame origin of our view moves, well, that's a good time to reset the tracking rectangle. When our frame size changes, we'll do it also. If we, for some reason, get moved into a new window, well, clear the tracking rectangle information for the old window. We're about to move into a new one.

And finally, when we move into a new window, well, let's set up a tracking rectangle in that new window. And again, we skipped setting up any tracking rectangle during live resize. So when live resize is over, let's set it up then. Okay, so that should be all the points that we need to catch. And now that we have a tracking rectangle as the owner, we're going to get a callback when the mouse enters.

When the mouse enters, we will create a Bezier path, and we're going to append one half arc on, I guess, the left side first. Not sure. Yep, it's on the left side first. And then we're going to append another half arc on the right side, and finally, close the path. And we get just that diagram that I had showed you.

And since, you know, Bezier path can tell us what its bounds is, well, let's just dirty the bounds of the Bezier path. That's what really needs to be updated. And finally, when the mouse exits, we're going to say that we need to re-display in that bounds again, and we're going to get rid of the Bezier path.

And finally, to draw the Bezier path, if we have a roll-over path, we want to draw it. And we're going to set the color that we're going to fill the Bezier path with, a nice gray color. And let's go ahead and change the text color to white so that it can be more easily seen on top of the roll-over effect. And then we're just going to let NSTextField do its thing. It knows how to draw the text. So we draw the roll-over in the background, and then we let it draw the text on top. And that's it. So if we could go back to the slides, please.

Okay, so we've answered the first question. Now, the second question that I've heard was that you've seen this cool little raised editing effect in address book. When there's a selected area, it looks like it's, you know, in an editor, which has this nice shadow around it. How can I do that? Well, the idea is actually really pretty simple once you have a key bit of insight on how to do it.

And what we're going to do is track the text selection and draw this raised editor around where the selection is. And the key bit of insight is that, well, first of all, we have this new class called NSShadow in Panther that we're going to take advantage of. And the second bit is, what we're going to do is create an image which has a hole punched out of the middle and a shadow around the outsides.

And because there's a hole punched in the middle, we can just have NSTextView do its normal drawing, and then we draw the image over top of the text. And because there's a hole in the middle, you get to see the text through it. So this is actually just going to be a subclass of NSTextView. Okay, so I mentioned NSShadow is a new class in Panther.

It has API which allows you to configure the shadow, configure things about it like the offset and the blur radius. And it has API which lets you set it as something that's used as the current drawing style. Just like you can use an NSColor or an NSFont and tell it to set as part of the current drawing style, you can do the same - you can use it -

[Transcript missing]

All right, let's run this and see what we're going to get.

So this is a mock-up of something that you might see in Address Book, and it behaves much like Address Book. When I click, I get a raised editor. If I type, the raised editor follows the selection. And pretty much everything works sort of like I expect. And just for fun, we know how to do roll-overs now, so let's throw those in.

We'll take a quick look at the nib again. We just have one special class in there, and it's called Raised Editing TextView, and it's a subclass of NSTextView. We'll go to the code. And we have here four classes which are all part of the implementation of this raised text view.

We're going to focus on one of those in particular right now, but again, like I said, you can download the code and look what the rest of it does. But I'll just give a quick overview. We have one file which has a category that defines all the methods dealing with handling the text selection, and making text selection in that example works just like you'd expect it to work if it was address book.

and then we have a small file that handles the roll-over effects, and then a file which deals with text attributes and makes those easier to use in the rest of our code. And if you want to find out more about what text attributes are, A text view and its text storage deal in attributed text and give you the ability to attach attributes to certain ranges of text. And that's sort of what that class deals with. I'd suggest going to Doug Davidson's talk at 5 o'clock today if you want to find out more about that.

And finally, we get to the part that deals with drawing that raised shadow look. And we start out, we have in our Awake From Nib, we do a little UI setup so it fills the text view with some dummy information. And then we have a routine called Shadow Image Around Path. It takes a Bezier path, which specifies the boundary of where we want the shadow to be.

And what we're going to do is, given that Bezier path, figure out how big the image should be. And the image is going to need to be a little bit bigger than the Bezier path's bounds, because it needs some offsets left, right, top, and bottom to accommodate for the shadow.

So what we do is we create an image, and we're going to do something special. We're going to cache it separately because we know we're resizing this image a lot. We don't want it to be cached in the same cache window as all the other ones. I'd suggest reading up on the documentation on the web to find out more about why we might do this and what it means.

We set the image to be flipped because, well, we're dealing with drawing text and typically flipping the coordinates makes that easier. So what this means is the origin will be at the top left instead of the bottom left. And finally, we lock focus on the image. And what that does is tell the drawing system that our current drawing context is that image. All the drawings are going to go into that image.

We do a little translation so that we draw to the right spot. And finally, if we're the first responder, we want to have the raised look. And what we're going to do is save away the graphic state so that shadows only apply to what we're about to do.

We create a shadow and give it some parameters. And finally, we set the shadow and set a color so that when we fill the path, we actually fill something. And it'll turn out that it doesn't matter what color we set that path to, because we're about to erase it in a little bit. And you'll see what I'm talking about there. So we set the shadow, and then we fill with the path. So we fill in a region, in a rectangular region, and outside of that region that we filled in, it's going to get some shadow.

And finally, we're done drawing with shadows, so let's restore the graphics state. And now let's knock a hole out of the middle, because we're going to need to be able to see through that image to the text that's behind it. And what we do is we clip to the path, so we make sure that only drawing goes into that area inside of the path that we originally passed.

We set the color to be clear color, and then we fill the entire bounds. Setting it to clear color basically knocks a hole out of the middle, so it doesn't matter what we set that color to be before that white color, because we've knocked a hole in the middle. And finally, we set sort of a nice light gray color and draw a border around where the shadow is, just to make it stand out a little bit better. All right, and then we return the image.

And finally, we have a routine which draws the raised editor. What it's going to do is find out where the currently edited range is. And if there's a currently edited selection, and TextView deals in glyphs, not characters. Again, I would go to Doug Davidson's talk if you want to find out more about glyphs and characters and things like that. If we have an area that we want to draw the raised editor in, what we're going to do is talk to the layout manager and find out where those glyphs live, so we get a rectangle back.

And then we're going to inset - well, actually, we're going to outset it - outset that rectangle just a little bit so there's some separation between the selection and where the raised editor and the shadows start. And given that, we're going to create a Bezier path with the rectangle that contains the edited glyphs and pass that off to our helper routine to create a shadow image. We get the shadow image back and we composite it to the screen.

And finally, all we did was override DrawRect in NSTextView. We let TextView do its thing because it knows what to do. It knows how to draw text best. And afterwards, well, we draw a roll-over if we have one. And...roll-overs are cool. And then we draw the raised editor. And that's it, because, you know, we draw that image, and you're able to see through the image and to the text behind it. Okay, if we go back to the slides, please.

So that ends my portion of the talk. I'm going to invite up on stage our next speaker who's going to bring back an old friend that some of you might have been familiar with if you've been to WWDC the last couple of years. She's going to enhance Dot View and try to make it the best app ever. Tina? Thank you very much, Chuck. Once again, my name is Tina Huang, and I work with Chuck on the Cocoa frameworks.

So, many of you are probably familiar with two of our apps on the system, Mail and iChat. And you may be thinking, "Wow, how do they do so many great things with their Dock icons?" For instance, you'll notice here that Mail puts the number of unread messages you have on the upper right side of the icon, and when you minimize one of your iChat buddy windows, chat windows, you have a big picture of your buddy's icon in there rather than a mini version of the entire window. Well, there's two things going on here. The first is Mail uses something called Dock icon badging. And for iChat, they actually do something called your - they set the Docs mini window.

And so, We want to make Dot View the best app ever. So, notice here that Dot View traditionally has just an icon that's this big red dot, and for the mini window, it just miniaturizes the window of the entire Dot View. So notice here that we're going to have a colored badge on the dot view icon to represent what color your current dot is. And we're also going to change the menu window to just be a version of the colored dot that's in your window.

So first, let's talk about Dock icon badging. It's pretty simple. The first thing you want to do is you want to grab the original icon of your Dock, of your application. So to do this, you're going to ask NSBundle to give you the application's main bundle and then get the path for the application from that bundle.

Once you have the application, you can go to NSWorkspace and get the application's icon for the Dock, for the application icon. Then you want to apply that badge to the image. Then you want to set that new image to be your new Dock icon. And there's one last thing. You have to reset the icon back to this original icon before app termination.

And the reason being is, you know, you can imagine a case where an app might want to leave that changed, modified icon as the application. But in this case, we don't. So we're going to register for the NSApplicationWillTerminate notification and set that icon back before the application. So you can see that the application is now being set. And then you can see that the application is now being set. So you can see that the application is now being set. And then you can see that the application is now being set.

And then you can see that the application is now being set. And then you can see that the application is now being set. And then you can see that the application is now being set. And then you can see that the application is now being set. And then you can see that the application is now being set. And then you can see that the application is now being So first, the code here that we're going to change is pretty simple.

In the main dot view, We have this method that updates the application badge. And what you'll see here is, the first thing we do is we grab the unmodified applications image. And we do a bunch of computations to determine where we want to draw that new badge. So, here it is. We draw the original image.

We add the badge, which in this case is just a rectangle in the color of the dot. Then we call this NSApplication method "SetApplicationIconImage" to set the image to be the new image with the badge applied. And that's basically it, except for in order to have the icon reset to its original state when the application terminates, we want to register for the app will terminate notification. And here it is. When we terminate, we grab the original application icon and reset that to be your application's icon. And here we're going to run it for you.

So here's DotView in NOLS Glory. And you can resize it. And notice here, you have just a red dot. I'll make this bigger for you. So here, we just have the main dot, but now we have this colored badge. So if we change the color of the dot, you'll notice here that there's now a badge applied to it.

And that's it. May I go back to slides, please? So now let's talk about that - how we do the Dock mini window. So the first thing is we want to actually compute the image for the mini window lazily. So in order to do this, we're going to first register for the NSWindowDidMiniaturize notification. And when we get that notification, that's when we're going to compute the image for the mini window.

You don't want to be receiving these notifications for all the windows in your app. So in order to make sure that we only have the main window receive these notifications, we're going to look at two NSVUE delegate methods. And so when you receive the NSView, didMoveToWindow delegate method, you know that you are now the main window. And at this point, you will register for the didMiniaturize notification.

When you receive the NSView, View will move to window, delegate method. That means you're about to switch out of your window and that's no longer going to be your main window. And at this point you want to unregister for the NSWindowDidMinitrized notification. So, once you get this notification, you're going to want to create the new mini window image and then set that to be your NSWindow, your Windows mini window. And to do this, use NSWindow's set mini window image method. So, can I switch back to demo, please? So first we'll take a look at the code.

So again, we first take a look at the viewDidMoveToWindowDelegate method. And when we receive this, we're going to register for the NSWindowDidMinitrized notification. When you're about to move out of the window, then you're going to remove yourself from that notification. And here's where the main crux of the mini window occurs. Once we receive that notification, prepare mini window gets called and first we want to compute that new image. So we're going to create the image and draw the dot with the proper color set. And that's going to be a new mini window image.

And then, here it is. You take the main window and you set the mini window image to be the newly computed image. And just for fun, we're going to actually have it tell you the radius for the title. And so we're going to see that run here. So here we go. When you change size and the color of your dot, and then We're going to miniaturize it. Notice here now we just have the dot as a mini window and it tells you that the radius here is 86.6. Back to slides, please.

And so you may now think, wow, we have dot view with all this great stuff, like this great descriptive Doc icons. But what I really want is, rather than have the slider and the color well at the bottom of your dot view window, wouldn't it be great if it had an animating inspector like Finders? Most of you have probably seen this in Finder, and you know, it's pretty neat. You have these disclosure triangles, so you have like the main information.

You click on the disclosure triangle, more detailed information occurs. So how do you do that? Well, it's pretty simple. You have your three main views that are your general views of your inspector. And then each one of these views will have an associated detailed view to go along with it.

The main difficulty here is that you want to temporarily adjust the resizing behavior of each of the views during the window animation, and then animate the window. So, let's take a look at that resizing behavior. Most of you are probably used to doing this in Interface Builder. What you have here is you set a spring that indicates which part of your view is variable. So in this case, if you set the spring of the top part of your view, that means you're going to be pinned to the bottom of your window. And most of you who are familiar with Interface Builder are probably used to doing this.

We're going to show you how you can do the same behavior programmatically. So we have this method on NSView called setAutoResizingMask. And here, rather than have the springs indicate the variable parts of your view, we're going to have the mask indicate the same parts. And so here you'll notice you have the NSMaxYMargin, the MinYMargin, and the heightSizableMask. And so once again, if you want your view to be pinned to the bottom, you would set your auto resizing mask to be NSMaxYMargin.

So, for your inspector, what you're going to want to do is, for each view above your expanding view, you're going to want to set the min-y margin as your auto resizing mask. For everything below that expanding view, you're going to want to set the max-y margin. And for the expanding view itself, you'll want to make the height sizable. And once again, remember, if you want your window to be resizable, make sure that you set them back to their original states, or else funny things will happen when you try to resize your window. So let's go back to demo.

So first we're going to take a look at the code. And so all the main code here occurs during this toggle detail visible. And this is going to occur every time you hit that disclosure triangle. So the first thing that we do is we use this new setHidden API available in NetEaseView that's new to Panther. And basically, if you're now toggling your view to be visible, you want to make sure you make that view visible before you animate so that you're not animating into nothing.

Then you're going to want to order all your disclosure views to the proper Y coordinate. And we have some code that does that. And here's that auto-resizing code we're talking about. So for everything above and below the expanding views, you're going to want to set the auto-resizing mask to the proper mask, as we talked about And for the view that's actually expanding, we're going to set the auto-resizing mass to NSViewHeightSizable.

And finally, you're going to resize the window using the animation API. So we have a set frame to display and set animate to be yes. And once again, if your view is no longer - if you're now hiding your Closing up the disclosure triangle, you're going to want to set the detailed view to be hidden so that it's taken out of the keyboard loop and a bunch of other things. So, let's take a look at that.

So now we have this great inspector for dot view. And so we have a bunch of things you can do. Again, you can change the size of your dot, the color of your dot, and we have a bunch of great new features that, surprisingly, require no code to implement.

And that's it. And notice here that the disclosure triangles animate and everything stays in place. Back to slides, please. And so, that's DotView for you. New and improved. So, to teach you some great tricks that you can do with OpenGL View, I'd now like to introduce Mr. NSView himself, Troy Stephens.

Thank you very much, Tina. Boy, that dot view just gets more and more powerful every year. I mean, I think we're revolutionizing dot management, am I right? So, my topic today, the slide says it all. How can you take an OpenGL view that you're doing some rendering into and go full screen with it? You've seen this in games. Games take over the entire screen. Keynote does this to show your presentation. It's doing it right now. Here it is. Here's our demo. Full screen OpenGL.

How can you do this? Well, there are of course a number of different ways to connect between the platform-independent industry standard OpenGL API and different windowing systems, and even on Mac OS X. I think one of the reasons it's the best is we give you so many ways to connect.

We have GLUT, we have AGL, which existed on Mac OS 9 and is generally used by Carbon apps, we have CGL, which supports AGL, and then we have the NSOpenGL layer in AppKit, of course. So, in this case, we're going to focus on how you would do this if you're using the NSOpenGL classes, NSOpenGL view, NSOpenGL context, NSOpenGL pixel format to handle your OpenGL windowing in your Cocoa app.

So we're going to do things a little differently for this demo. We're going to go to the demo first and take a look, just so we have a mental picture in mind of what it is we're trying to accomplish. Then we'll go to slides and we'll take a look at what some of the concepts are that are involved in doing this, and then I promise we'll go back to the code. We'll take a look at the code. at the code and how you actually do this. So if we could go to demo one, please.

So here we've got an OpenGL view in a window. It does some simple GL rendering. We've got a three-dimensional sphere here with a texture map on it. And you'll notice there's some various... it supports mouse input. We can grab with the mouse, and if we move the mouse vertically, we can rotate the sphere. We can move the mouse horizontally and change the direction the sun is coming from. There's some animation. It's rotating. There's also keyboard support.

If I hit the W key, you'll have to take my word on this. I am hitting the W key. And it's toggling wireframe rendering mode. We can resize this. So if we want to go to full screen with this, say, and take over the entire screen, fill the entire screen area, we just grab this thing down here.

And there you go! Voila! But seriously folks, you want to be able to - what we're really talking about here is - I want to make this clear - not just filling the entire screen with your OpenGL view, you really want to take over the hardware. You want to take control of that display so that you can do various other things that can help to improve your rendering performance. You want to lock to your screen refresh.

So we've got a little method that's wired up to this button, and if we do that, now we're in full screen mode. We've still got a mouse pointer. We could have hidden that if we wanted to. And you'll notice we still have mouse response here and keyboard response.

and we can hit escape. You of course always want to give your users a way out, right? Because we're in full screen mode, there's no quit menu item here or anything, so hit escape and we're back to non-full screen mode. And we can bang on this all we want and go back and forth. It's a pretty smooth transition. I'm not trying to do anything sophisticated like a gamma fade here, but it just works. I tried to keep the code fairly simple.

So, This is, as I said, more than just taking a view and going full screen with it, than filling the screen. What we're actually doing is we're rendering to a context. We have what's called a full screen OpenGL context, and there's actually no view involved when we go to full screen mode. So this is going to take a little refactoring of our code, some re-architecting of our app to really fully support this, right? And if we could go back to slides, please.

What are some of the prerequisites? This is a somewhat advanced technique, so we've got a bunch of stuff to go through. But remember, the sample code, I believe, is available now online, so you can download it after this talk and study it to your heart's content. It'll give you a good baseline to start with if you're trying to implement this technique yourself. You can start with something that works. One of the things that we need to do with NSOpenGL is start by creating a separate NSOpenGL context.

So, if you're using a single-screen context, there's one that's implicitly created for you, or you may be explicitly creating it in your NSOpenGL view. So, we want a separate context to render to when we go to full-screen mode. We want to give it the same, more or less, pixel format attributes that we gave to our non-windowed context.

Let's say we have some common attributes that we're using. We want 24 bits of color buffer, we want a 16-bit depth buffer, we want a double-buffered context so we can page flip, and we want to specify that we'd like to have a hardware-accelerated rendering context, because we know we've got some great hardware we can render with.

So when we create our full-screen OpenGL context, we want to use those attributes, and we want to also specify the full-screen pixel format attribute. That's pretty straightforward. We also want to specify a screen mask. Remember, you could be running on a multi-monitor system, so you need to specify which screen it is that you want to take over and go into full-screen mode with. And the mask itself, for example, if you were on the main display, you could simply pass this simple expression here, cgDisplayId to OpenGL display mask. There's a mask macro for that.

If you look in cgDirectDisplay.h, you will find, I believe, this API. So let's just say we're going to the main display. We specify that for the mask. We also need to do something special in the non-full-screen context, since we know that we want to go full-screen with that.

This one's a little obscure, but by specifying the no-recovery attribute in the non-full-screen context, what we're doing is we're telling NSOpenGL that we want a context that cannot fall back to a software renderer. If we want our non-full-screen context to be compatible with our full-screen context for purposes of text-to-text, then we want to specify that. So we specify that for the mask. We also need to specify that for the non-full-screen context for purposes of texture sharing and sharing display lists and other such OpenGL objects.

We need them to -- we need cgl to consider them compatible. To do that, we have to make our non-full-screen context hardware only, and that we do that using the no-recovery attribute. So another thing that we need to do is we're going to sort of change the model of our application. Normally, you let AppKit do the driving. AppKit runs your run loop. AppKit hands you events.

It tells you when to draw, rec. It tells you when to correct in your view. It tells you when you've got a key down and that sort of thing. But when we're in full-screen mode, we're really taking over the system. We're going to drive everything. We're going to be in our own loop for the whole time that we're in full-screen mode until we exit, driving the event loop and processing our own events.

So in order to facilitate this, what we're going to do is factor code out of our NSOpenGLView subclass and move it into some other common objects so that we can reference it both when we're in full screen mode and when we're in windowed mode. We're going to take all the OpenGL initialization and drawing that we normally do in our drawRect call and move that into another object. And also our input event handling. We want to be able to handle the same keyboard and mouse events, whether we're in full screen or non-full screen.

So to help illustrate exactly what we're doing here, we've got our OpenGL view to start with. Normally it's got a drawRect method, it's got a reshape method that gets called whenever the view is resized and needs to adjust its GL viewport and so on. We also get mouse down and key down events.

So the first thing we're going to do is we're going to take the rendering and updating for changes in size, move those into a scene object - this is just an NS object, this is your sort of data model object - so we're going to move all the rendering out into a separate scene object that we define, so that OpenGL view just calls the scene to do that. And then we're going to have a controller object that handles all of the keyboard events.

And we're just going to have OpenGL view forward all these sort of requests to delegate effectively to these different objects. And then main controller has a go full screen method. We're going to look at that one in detail. That's going to be the focus of what's unique about this demo. And that is the method, as I said, we stay in that method for the entire time that we are in full screen mode.

It takes over control and only when we exit full screen mode does it relinquish control. That invocation is live for the whole time we're in full screen mode. And that's exactly what's wired up to that button that I clicked to go full screen. So if you'll notice here, we have a scene object. This is our model. So we have here, everybody with me, model view controller.

It's Friday. Okay, since I'm not planning on quitting my day job, we'll look at some API. Just a quick overview of some of the API. I know this is a lot of stuff, but these are some of the API that we will find particularly useful in implementing this.

One thing we want to be able to do is capture to the display, talk to Quartz and say, "Hey, we want to own this display so that we can control it." One of the other things that you may want to do is actually change screen resolution. Maybe you want to run in 800x600 or an even higher res than the user is running in. We don't do that for simplicity in this demo, but it's fairly easy to go ahead and do that once you've taken control of the display. You can query Quartz and ask it, "What are the different modes this display supports?" and do that.

Of course, we're going to be creating an NSOpenGL context. The standard initializer for that, init with format, share context. Notice the share context, that's what's going to enable us to share texture objects with our, in fact, the earth texture is uploaded only once to OpenGL. And when we go to full screen mode, we just, since we're specifying the non-full screen context as a share context, we just automatically get it.

We don't have to send it up there again. Set full screen, of course, a method on NSOpenGL context. That's the main method you use to go into full screen mode when you're ready. And then we make, we want to use make current context to make that context current so we can start drawing into it.

Another thing you're going to want to do is potentially sync to the refresh rate so that you can, what this does is it avoids tearing artifacts. It's basically telling GL that you want this context to swap front and back buffers since we're double buffered in sync with the vertical retrace on your monitor. This matters even on an LCD panel. Even LCD panels, people are surprised to discover, have a concept of retrace.

We also are going to need in full screen mode to flush our buffer each time we're done drawing a frame. We'll do that explicitly. So that's where we swap the back buffer to the front buffer. And then when we want to exit full screen mode, we clear the current context.

We send a clear drawable message to the context and that's how we get out of it. And then we can let go of the full screen context and be done with it. So if we could go back to the demo machine, look at the source code that implements this.

And we have a few different classes here. As promised, we have a controller class. Our controller has a connection to the model. This is our full screen context here. It's just an OpenGL context like any other. We've got a connection out to our view. The OpenGL view has a connection to the scene object that knows how to render the scene, so it can delegate out to the scene to draw itself. And it has a connection to the controller, so it can send all the input events to the controller. So let's look at the real interesting part, going full screen. That's in main controller.

It's fairly easy to see? Okay, good. So we'll go right down here to the Go Fullscreen method. This is what's wired up to that button. This is where we go first when we're entering fullscreen mode. We have, first of all, we create our list of pixel format attributes. You notice we have the common attributes here. These are the same, you'll see if you look at the source code, as are specified for the non-fullscreen context. In addition, we've got the display mask. We've got the fullscreen attribute specified here.

I should say they're the same common attributes with the exception of the no recovery attribute. That's in the non-fullscreen context only. So just to - this is a lot of stuff. You can look at this at your leisure, but just to quickly go through it, we create the pixel format with those attributes. And just for debugging, I'm printing out here the renderer ID, which identifies uniquely what card you're running on.

And you can see in the console output when we run the app that we can do that for diagnostics. We see that - we end up seeing that both the fullscreen and non-fullscreen context have the same renderer ID. They're talking to the same hardware. And that's the whole point of the no recovery attribute.

So we create our fullscreen context. We're done with the pixel format. We temporarily stop the animation timer because instead of having a timer call us back every time we want to switch frames, what we do is we drive the animation ourselves. We just check the time interval each time we go through the loop. We're in this loop of checking for input. Updating the simulation, which in this case is just very quickly advancing the globe to the next frame, and then rendering, and we just keep doing that over and over again.

So we capture the display here. Here I've just done capture all displays, but you can be more specific than that. You would want to do that on a multi-monitor system. But capture all displays is fairly simple and universal. Now we tell the context to set fullscreen. We make it current.

Here's where we set the swap interval. This is a case in which we have to drop down to see if we can get it to the fullscreen. We're saving the old swap interval as a courtesy so we can set it back again after we exit fullscreen mode. We tell the scene to set up for a new display size.

And then we enter our loop. And the loop is fairly simple in structure. We're checking for and processing input events. It's the first thing we do each time through the loop. Notice what we're doing with the mouse down event. We're sending it to self. The controller handles these events, and the view likewise forwards mouse down events to the controller. Same thing for mouse up, mouse drag. We don't actually use those, but they're in there. Key down.

So we check for input. We check the time interval for this frame, and we advance our animation by the time delta. And then we render the scene. And as I said, we flush buffer when we're in full screen mode. This is telling GL to do the page flip, bring the back buffer forward, and now we're going to start over and draw the next frame on the new back buffer.

And we're using an auto-release pool here so that each time through the loop any objects that are created, since, again, AppKit isn't driving things anymore. We don't have it to clean up auto-release things. We have to clean up the first objects for us, so this is a common technique. You create an auto-release pool, and you release it at the end of the loop, and everything gets cleaned up for you.

Then there's a little trick in here you'll see to sort of clear the frame buffer, clear a couple of pages before we switch back out of full screen mode. This avoids just kind of an ugly flash of garbage when you go out to back to full screen, back to non-full screen. We're setting the swap interval back to be a good citizen. We're clearing the current context, clearing the drawable.

We're releasing all displays and just telling our windowed OpenGL view we need display, and that's it. So you see we have a key down method in the controller that actually handles the response for any key that might be pressed. Mouse down is implemented in main controller to do the work that is done when the mouse is clicked and then dragged to rotate the globe and so on. So if we look at, very quickly, at the view object, we've got very little left in the view object here. It creates the pixel format, its own pixel format, again with the no recovery attribute, and it's in it with frame method.

But everything else - DrawRec delegates out to the scene, Reshape delegates out to the scene. You get the idea by now that we're just forwarding all those input objects to the scene, all those input events to the controller object, and all of the requests to render to the scene. So we've refactored our code, as it said, to better facilitate going full screen. So I know it's a lot of stuff.

The source code, I believe, is online now if you log into your ADC account. And with that, I'd like to go back to slides. and puts you in the very capable hands of Mr. Doug Davidson, owner of the incredibly powerful, insanely great tech system in Cocoa, who's going to show you some very cool stuff. So, thank you.

Hi, I'm Doug Davidson, and I'm here to answer the next question. So let's see, the question, please. All right, the question is: iChat puts these beautiful bubbles around its text. How could you do something like that? Good question, folks. Thanks. Now, if you want to know more about the theoretical underpinnings for this sort of thing, you can come to my talk later on this afternoon, 5:00, on the Cocoa text system. But this is Tips and Tricks, so we're just going to jump right in and do it.

And what we're going to do is draw one of these bubbles around each paragraph of the text. So there are a few things we have to keep in mind. Now, the Cocoa text system automatically makes sure that the text will be redrawn whenever it changes. But these bubbles are going to be a little bit bigger than the text, so we're going to need to make sure ourselves that they get redrawn, too.

Now, we're going to be drawing these bubbles above the background and below the glyphs in the text itself. So the way we're going to do that is - should be fairly obvious - we're going to subclass NSTextView to add some additional drawing to it. And we're going to want to draw one bubble around each paragraph of the text. So the main thing we need to know in order to do that is where the paragraphs are located - location and size for each paragraph.

And the general answer to any question of that sort in the text system is: ask the Layout Manager. NSLayoutManager knows all. So we're going to ask the Layout Manager to find out the information that will tell us where the paragraphs are located, so we can draw the bubbles around them.

So some APIs we're going to need. All views have a method setNeedsDisplayInRect, but nstextview has an additional, somewhat more specific method, setNeedsDisplayInRectAvoidAdditionalLay out. So we need to know that because we're going to be overriding that. It gets called quite a bit from the text system when text changes and things need to be redisplayed. And nstextview has another method, new for Panther, called drawViewBackgroundInRect. And what this does, this is the method that nstextview uses to actually draw the background for the view. And so we're going to be overriding that so we can do our drawing above the background below the text.

And then we're going to need some NSLayoutManager methods. First of all, generically in the Cocoa system, you always need to use the layout manager to convert between ranges of characters in the text and ranges of glyphs that are displayed on the screen. So we'll need those methods. And we'll need some more layout manager methods to go from a rec that's being drawn to the glyphs in that range.

and to determine where the lines of text are in the text view. And we also have a new method in Panther on NSString called paragraph range for range. There's always been a method line range for range, but in Panther we distinguish between line ranges and paragraph ranges. And what we really want here is the range of a paragraph in the text. So we're going to use that method to determine what the ranges of paragraphs are in the characters of the text. So let's take a look at some code.

If we can come over to demo one. Here's our subclass of NSTextView. And so the first method I want to point out is our override of set needs display and rect avoid additional layout. And so, as I said, we need to make sure that these bubbles, which are a little bit bigger than the paragraphs they enclose, are going to get redrawn when the paragraph changes.

And the quick and dirty way to do that here is to simply expand the rect that is being dirtied by the amount, the width and height that the bubble overlaps from the text. We could probably be a little bit more precise here if we did a little bit more work, but this works fine.

And then the next method I want to point out is draw a view background in Rect, which we are overriding in NSTextView. So the first thing we do is call the super method, which actually draws the background, because we want to draw the background first before the bubbles. And then we're going to figure out where each paragraph is in the rack that we're being asked to draw so we can draw a bubble around it.

So, one thing we always have to do in working with the Layout Manager, the Layout Manager works in text container coordinates, not in view coordinates, so we have to do a translation to get into container coordinates. Then we ask the Layout Manager to determine what the range of glyphs is that we need to deal with to cover the rec that we're being asked to redraw. And we'll convert that to a character range so we can examine the corresponding text.

This is the text that we need to determine the ranges of paragraphs, and we'll go through that text paragraph by paragraph, iterating through each. And so we determine the range of a given paragraph by using the NSString method paragraph range for range. So we'll loop through it paragraph by paragraph, and for each paragraph, we'll have the range of characters in that paragraph.

Then we're going to go back to the Layout Manager to determine where that paragraph is located. So we'll get a glyph range corresponding to that paragraph range from the Layout Manager. And then we're going to go to the Layout Manager to determine where the lines in that paragraph are in the text container and the text view.

So this is a very common idiom when dealing with the Layout Manager, to iterate through by lines. And we call the line fragment used rect for glyph and index method in the Layout Manager. And what that does is it returns what we're asking for, which is the used rect for a particular line. And we want that because that's part of the paragraph.

And it also returns by reference the range of glyphs in that line. So that will help us iterate through by lines. We're going to skip after we're done with this to the beginning of the next glyph after this line. And then we take that used rect and unit it in to the rect that we're determining for the paragraph that tells us where the paragraph lives.

But again, all the coordinates that the Layout Manager uses are container coordinates. So, in order to get it back to view coordinates, we take our paragraph correct and we undo that same translation. And then we call a method that we have that actually draws the bubble. And actually drawing the bubble is pretty simple.

I don't want to go into it in too much detail, but we have some nice images. And we draw an image on one side, an image on the other side, and we fill the middle with a pattern image. So, now we put it all together. Let's run it and see how it works.

There we go. Thank you. Now if we could go back to the slides. I'd like to bring up Chuck again to wrap things up. Thank you, Doug. All right. Thanks, Doug. So, I hope you guys have had some fun with this session. That was sort of the intent of it, have some fun, show you guys how to do some cool features. And I think, you know, I had a lot of fun. In fact, the most fun of the afternoon was seeing Troy sing up here. I hope you all enjoyed that as much as I did.

So let's do the wrap-up and, well, there's not much left. Okay, so you all know by now you're getting DVDs and so on, so take a look at those when you get a chance. What I want to point out is a talk that's at 5 o'clock. I hope some of you can stick around for that. You're going to see Doug talk even more about the tech system and find out how powerful it really is. It's very, very cool stuff. And the other thing I want to point out is who to contact. You probably all know this by now.

John Glunzey is your main contact. We've all seen this before. I'm just going to skip ahead. I want to show you that there's some more information. What I want to point out here is the demo code. The demo code, if it's not available right now, should be up there very soon. Each of the demos that you saw on stage here will be available for you to download and look at at your leisure.