The arrow is harder. There's a window with an arrow on Matt Gemmell's site http://mattgemmell.com/ but that uses an NSColor with a pattern image to fill the window background and that seems mess up with core animation, which can't cache the drawn window prior to animation.
Wednesday, March 31, 2010
iCal-like popup window Part I - MAAttachedWindow and CoreAnimation
I have been working on animating the insert frames window in the same way that the iCal window animates. The iCal window does not just appear but instead sort of grows out of the point that you double clicked in.
It is a really nice effect - however rather than the 10 minuets (or so) I thought it would take to deploy it - in actual fact it has taken me a little while to get to the bottom of it.
My first port of call was Core Animation. I have not done any Core Animation before so it was my first look at it. Core Animation is beautiful - doing simple animations is pretty straight forward. An animation like changing a window's bounds or moving it's location is quite simple. However it is not possible to do this kind of animation with Mat Gemmel's MAAttachedWindow.
MAAttachedWindow is quite interesting and clever - what it does is create a pattern that is the window image (a bitmap) and then sets this pattern as the window background colour. So when the window draws it's background in it's background colour this magically draws the window. There is also some code that will keep the window geometry in sync with it's contents this makes it impossible to grow or shrink the window.
I had a solution to this which was to use Core Animation to resize the contents of the window and from this then recalculate the geometry of the window its self. This sort of worked - things animated but in a nasty lumpy fashion. Grubbing around on the web I found this reference to the problem.
There are other ways to draw custom windows. Apple have some excellent Sample code RoundTransparentWindow that draws a custom window. Their method is a little more straight forward than MAAttachedWindow - the window is 100% transparent and within the window is a view that draws the window background. Taking this approach and rolling my selves up for some fun and games with drawing paths gave me a window that animated smoothly.
As you can see - animates nice and smoothly - but is very different than what happens in iCal.
Wednesday, March 17, 2010
Non Modal Dialogs
I am at the point where I need to add a dialog that appears when he user wants to insert frames. the dialog is quite simple - a popup and a field. The normal way of presenting this to the user is with a modal dialog - or its close cousin the sheet. For me as a user modal dialogs are just not that nice. Suddenly "bang" there is a dialog. It is rarely where you want it and it interrupts your flow. I, personally, fild it's cousin "the sheet" even worse. The animation seems at times agonising - and whatever you wre doing you have to focus your atention at the top of the screen. It looks cool but strewth it is irritating.
For me a far nicer way of doing this a sort of cross between a menu and a dialog. Sounds awefull but is actually quite a nice way of working. This is, more or less, what happens in iCal when you click on an appointment in the calendar view.
A window appears with an arrow as a part of its frame. The slight difference that I want is to dismiss it of you click on something else. In this respect I want something that behaves like a menu. As a user you start a sequence of events but you are not trapped into it - in as much as you don't need to find the cancel button - just click somewhere else.
The first part of this is to draw a custom window. Mat Gemmel has a window that does more-or-less what I want. It is very easy to use and deploy.
There is little more to do than to change the default colour for the background and set it to have no border. It now looks something like this.
Sunday, March 7, 2010
Inline editing - NSTextView and the mysterious 5px margin
The PC version of cello has a dialog box to edit labels. You can, using a contextual menu, add a label or edit a label. Deleting a label is a matter of editing the label and removing it's text.
A more OSX way of handling the editing of a label is to be able to edit inline. The idea is that if you double click on a label then you get a text cursor in the label - and you edit the name inline.
To implement this I field the double click and then create an NSTextView. The label is an irregular shape.
I wanted it to grow and shrink as the you type. I wanted the irregular shape to change with as you type. The key bits are to set the NSTextView up as stretchy setHorizontallyResizable:YES and then to stop it from drawing a background setDrawsBackground:NO. When the edited label is drawn it gets it's size from the text field and does not draw text. The result is that the NSTextView is responsible for drawing the text in this unique situation. The final twist is to set the delegate of NSTextView to the timeline view. The textDidChange method just gets the label to redraw - so it now grows and shrinks as you type.
Getting something like this to work is a bit of a fiddle but mainly boils down to getting the right clutch of settings. Mine were these:
[_editLabelView setHorizontallyResizable:YES];
[_editLabelView setMinSize:NSMakeSize(400.0f, kLabelViewHeight)];
[_editLabelView setFieldEditor:YES];
[_editLabelView setTextContainerInset:NSMakeSize(0, 0)];
NSTextContainer *container = [_editLabelView textContainer];
[container setLineFragmentPadding:0];
[_editLabelView setDelegate:self];
[_editLabelView setString:label];
[_editLabelView setRichText:NO];
[_editLabelView setSelectedRange:NSMakeRange(0, [label length])];
[_editLabelView setDrawsBackground:NO];
My biggest problem was that I always had a 5px right shift of the start of the text from where I expected it to be. This margin or inset is the lineFragmentPadding on the NSTextContainer. It took a while to track this one down. The magic fix was
_editLabelView = [[NSTextView alloc] initWithFrame:bounds];
NSTextContainer *container = [_editLabelView textContainer];
[container setLineFragmentPadding:0];
performClickWithFrame, small menus and Señor Calviño
An advantage that MacApp had over Cocoa was that you got the source code. My experience of MacApp was that it was far more buggy but having the code meant that you could see how things work and (of course) fix bugs. Cocoa seems rather bug free - but when you find a bug you have no option but to work around it. I am not pining for the days of MacApp, things move on, Cocoa is very, very good - but I do miss the source.
My current issue is contextual menus. I am using performClickWithFrame to popup the menu - however as the Timeline is quite small I don't want the distraction of a normal size menu - it looks odd and out of place. The solution that I have is to use setAttributedTitle to set the menu items to a small, and comfortable, size. This all works just fine:
The rub come when you have a contextual menu with just one item. It can happen - contextual menus are contextual - they only show items that can be invoked - items that can't (would be disabled in normal menus) are simply omitted. This can leave you with a contextual menu with just one item. Unfortunately it looks like this:
Hello Señor Calviño - yep it is bald as an egg - not even a hint of text. If instead of setAttributedTitle I use setTitle it looks fine (but big).
This seems to be an either/or decision - big or bad. The workaround I had for this good/bad/ugly place was to give the popup a header label. The resulting menu looks like this.
Saturday, March 6, 2010
Workaround - Crash in NSPopUpCell performClickWithFrame:inView:
I have been working on contextual menus for the time line. Contextual menus appear to be quite straight forward. If you don't want your contextual menus customized with whatever can add to contextual menus you create a NSPopUpCell - this is described in Apple's developer documentation. What you need to is nicely covered and well explained in Jesper's blog. Jesper kindly even gives you a category that does the leg work for you.
All looks good - except that there is a missing release in Jesper's category. It is not a big deal and I would probably not have mentioned it except that adding the release caused things to crash. Somewhere there was a stale object - but the code is almost trivial. Hitting Google I got a few mentions of the crash (for example here) but no solution.
I did a few things. I created my own subclass of NSPopUpCell with just a dealloc so that I could put a breakpoint on the dealloc. Was the NSPopUpCell being released by performClickWithFrame:inView? It seemed unlikely and was indeded not happening. It was being called (as I would expect) by the release I added. If I did not release the menu that I set [popUpButtonCell setMenu:menu] then the crash did not happen. The next thing I did was to track the owner counts of my menu. They were as I expected incremented by [popUpButtonCell setMenu:menu] and decremented when I released the cell.
I don't know what the cause of the problem is. I suspect that it is bug in AppKit - but this is of course conjecture. The workaround I found was rather than setting the menu, to use the menu that exists in the NSPopUpCell.
NSMenu *menu = [popupCell menu];
Once you have your hands on this menu you can add and remove items - just you don't get the crash when you dispose of the NSPopUpCell.
Wednesday, March 3, 2010
Poof - Drag Deleting Items in Cocoa
OSX has an idea of drag-deleting items in the UI. You can see this in the dock amoungst other places - drag an item out the dock - and the cursor changes to a pointer adorned witha cloud. Release and "Poof" the item disappears with a small animation and a sound. It is cool and intuative. It works well when moving items does not cause a scroll. I have got used to the same way of deleteing breakpoints in XCode and wanted to add the same functionality to the labels in the timeline.
Looking at XCode the way that markers work is quite straight forward
- Drag up or down and the breakpoint marker moves
- Drag out of the breakpoint column and the cursor changes to the disappearingItemCursor cursor.
- Release outside this column and the animation plays
Setting the cursor is quite simple just call [[NSCursor disappearingItemCursor] set]
The animation proved a little harder to track down. I quite quickly found a private API it is listed in a number of places that apparently did the job. My experiance of private APIs is that they disappear even quicker than the public ones.
The good news is that Apple supply it as a public API - NSShowAnimationEffect. It is explained in apple's web site. I found this link in cocoadev usefull.
Monday, March 1, 2010
TimeLine view - Labels
I am continuing to work on the header of the timeline. In addition to showing the current time frame and a selection the timeline can show labels and actions. I have been working on the visualization of labels.
In the PC version of Cello the labels are shown as flags.
The flags are named points in the timeline. I have taken this in the OSX Cello and changed it very slightly. Keeping the flags I have dropped them down to a second new marker line. This is the sort of visualization you often see for tab markers in a word processing column.
Single Frame Selected (current frame)
Subscribe to:
Posts (Atom)