Havoc-Wreaking Flex Transitions

Flex transitions are damn sexy beasts. They let you animate things like movements and resizing. The trouble is that they do all of this asynchronously, firing every so many milliseconds. The result is that occasionally, a Flex transition will get in the way of a change you make. This isn't a very long post, more of a warning. If for some reason you try to (for example) resize or move an object and find that it doesn't work, make sure that you don't have a transition overriding your size settings somewhere. Occasionally, these can fire at just the wrong moment so that you end up with exactly the wrong result, but without your really being able to see the transition happening. This happens especially if the transition is supposed to happen over a very short period of time.

Flexlib's SuperTabNavigator and Truncated Labels

A quick `uh-oh, bug fix!' post here. We've been using the SuperTabNavigator from flexlib for some of our tabbing needs, and recently realized we had run into a bug: if your navigator is set to limit the width of tabs to a certain amount, thus causing labels to get truncated, then you will have trouble editing the labels on those tabs.


The SuperTabNavgiator provides, amongst other features, the ability to edit a tab label in line. This is pretty cool, and the way it is implemented is even cooler: basically the same textField that is used by the Tab to display the actual label then has its type property set to TextFieldType.INPUT to make it editable. Pretty slick.

The troubles come when the label is truncated. By default, what tabs do when they have to truncate their labels is keep their label property the same, set their tooltip to their full label text, and then set their textField's text to a truncated version of the label with a `...' on the end. This works out nicely, as it means that you have a truncated label for width purposes, and can get the full label by looking at the tab's tooltip. But! Since SuperTabNavigator uses that same text field for editing, when you go to edit the text, it's the truncated version! That is to say, if I had a label title that was `This is a cool tab', that was truncated to `This is a coo...', when I went to edit it, I would be editing the text `This is a coo...', not my real label text!

This is a pretty huge problem, but it's also relatively easy to work around. When the SuperTab is switched into edit mode, we basically set its textField's text back to the value of the tab's label (remember that it is never truncated, only the text in the field is). When the text is updated, the field will get truncated again by the Tab class's own handling of the label property. Thus, we have the right editing functionality and the right viewing functionality.

Implementing this fix is a bit complicated, however. Basically, you need to implement it in a subclass of SuperTab. That's easy enough. The trouble comes with the fact that we need the SuperTabBar to use that subclass of SuperTab. Ok, so we create a subclass of SuperTabBar that instantiates FixedSuperTabs instead of SuperTabs. Now the problem is getting the SuperTabNavigator to use the FixedSuperTabBar instead of the SuperTabBar. So we need a trivial subclass of that. The solution is a set of three classes: FixedSuperTabNavigator, FixedSuperTabBar, and FixedSuperTab, with the real work done in the last one. We'll have a quick look at the code for the fix in FixedSuperTab:


We override the setter for editableLabel (which is where we are switched into editing mode). We let SuperTab do its thing, and then, if we need to, we do our own overriding of the textField's text. Then we also redo the selection of the text -- SuperTab selects the full text when the tab is made editable, but we've just changed the text in the field, so the selection will at best be off and at worst non-existent. Thus, we redo it so that it is correct.

Here is a ZIP file with the three necessary files for a fixed editing experience. I need to submit a patch for this to the flexlib folks, but this will do as a stopgap.

UPDATE: I went ahead and posted a patch to the appropriate issue at http://code.google.com/p/flexlib/issues/detail?id=82

Using Glows to Change Image Color in Flex

In a few cases in our application, we've been using buttons that consist primarily of an image that must then change colors for hover and down/selected states. For each of these, we initially embedded the images for the three states into the application and referred to them from there. The trouble is that this means a bit of bloat in the app itself. Not much for small buttons, more for larger buttons, but regardless if the space is unnecessary, then there's no need to have it there. Flex applications can get relatively large (the main module of our app is around 700K at the moment), and it's always nice to save some space. With that in mind, how can we include fewer assets in these cases?

In our case, we decided to use a Glow effect. The Glow effect provides a certain color tint to a given component. By default, it's an external glow, meaning there is some color emanating from the borders of that component. However, there are a few properties of the effect that we can use to our great advantage:
  • The inner property can be used to switch the glow from an effect that emanates from the borders outwards to one that emanates inwards and over the component.
  • The blurX and blurY properties can be used, at least on relatively small components, to make the glow fill the entire image with the color. Usually the glow color fades, but if these two properties are big enough then the color spreads into the entire image at the same intensity.

A downside of the filter is that, in order to actually modify the color it is glowing with, you have to remove it from the list of a component's filters and then add it back in. Simply changing the color property is not enough.

For all the discussion about rollovers above, the biggest win of using a glow to change colors is the ability to change an image with a color transition. In our case, we have a thumbs up image that changes colors when a user is rated up. The image has a color transition from green to white and then back. Usually, this would be relatively difficult to achieve; with the glow effect, however, we can change the color of the image without needing any additional embedded assets.

Here is and adaptation of that code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
           xmlns:effects="com.darronschall.effects.*"
           creationComplete="init()">
    <mx:Script>
        <![CDATA[
            import mx.events.EffectEvent;

            [Embed('assets/images/thumbs_up.png')]
            private static const HELPFUL_THUMBS_UP:Class;
            private static const THUMBS_UP_DEFAULT_COLOR:Number = 0xC3DA6E;

            [Bindable]
            public var helpfulImageColor:Number = THUMBS_UP_DEFAULT_COLOR;

            public function highlight(newColor:Number = NaN):void
            {
                if (! isNaN(newColor))
                {
                    helpfulImageColor = newColor;
                    reloadHelpfulImageGlow();
                    ratingLabel.setStyle("color", newColor);
                }
            }

            public function fadeHighlight():void
            {
                highlightFader.play();
            }

            /**
             * In order for a glow to have its color changed, it needs to be
             * removed and re-applied. This function does that for the helpful
             * image glow filter.
             */
            private function reloadHelpfulImageGlow():void
            {
                helpfulImage.filters = null;
                helpfulImage.filters = [helpfulImageGlow];
            }
        ]]>
    </mx:Script>
    <mx:Parallel id="highlightFader" duration="2000">
        <effects:AnimateColor target="{ratingLabel}"
                              property="color" isStyle="true"
                              toValue="{THUMBS_UP_DEFAULT_COLOR}" />
        <effects:AnimateColor target="{this}"
                              property="helpfulImageColor"
                              toValue="{THUMBS_UP_DEFAULT_COLOR}"
                              tweenUpdate="reloadHelpfulImageGlow();" />
    </mx:Parallel>

    <mx:GlowFilter id="helpfulImageGlow"
                   color="{helpfulImageColor}"
                   blurX="20" blurY="20"
                   alpha="1"
                   inner="true" />

    <mx:VBox styleName="ratingWrapper" height="24">
        <mx:Label id="ratingLabel" styleName="ratingLabel"
                  width="100%" height="8"
                  text="{data.rating}" />
        <mx:Image id="helpfulImage" styleName="helpfulImage"
                  height="10"
                  scaleContent="true"
                  source="{HELPFUL_THUMBS_UP}"
                  filters="{[helpfulImageGlow]}" />
    </mx:VBox>
</mx:Canvas>

Here, we given the color animation a tweenUpdate function that reloads the glow filter on the image. As I mentioned above, this happens because the filter is not reapplied unless you remove it and re-add it. Simply changing the color property of the filter, as the AnimateColor effect does, does not force that refresh. Other than that most of it is pretty self-explanatory. We have a bindable color that is updated continuously by the AnimateColor effect, and we have a parallel effect that updates the label that accompanies it. The component that contains this in our application changes its own background color at the same time, thus making this color change still result in a legible rating.

All in all, glow filters are more useful than simply applying a glow. This technique can be a real keeper for color transitions on images.

Pulling and merging with git

When you have a multi-user workflow with git, you usually have a central repository that you clone, and then you periodically push your changes back to it and pull the changes others have made down from it. On the surface, this seems sort of like a subversion workflow, only you can also commit locally; in practice, however, it is very different. Each clone of the repository is essentially a potentially different branch of changes. You and Mike (a fictional über-coder) may both have the same version from the central repository, and then you make eight commits and he makes 29. What, then, does git do?
 
In essence, every commit you make in git can have multiple `children'. A given commit is, then, a tree. A lot of times, that tree degenerates to a list: if I commit six times in a row, each new commit is a child of the previous commit, and they happen linearly. This forms a list. However, in the case we mentioned above, the last commit that you and Mike synchronized on from the central repository has two children -- one is your first commit, and one is Mike's first commit. Then you commit linearly, in parallel to each other. How you merge these is the important part.
 
At some point, Mike pushes his changes. He does:
 

# git push
Counting objects: 104, done.
Compressing objects: 100% (73/73), done.
Writing objects: 100% (73/73), 7.13 KiB, done.
Total 73 (delta 58), reused 0 (delta 0)
To git@github.com:magic/mikeFreakingRox0rz.git
  bc06779..d79eb17 master -> master


 
Because Mike pushed his repository to the server before anyone else, what ends up on the server is his version of events: a linear list of commits starting from the last synchronization point. Until anyone else wants to work with this, that's fine. Then, you try to push:
 

# git push
To git@github.com:magic/mikeFreakingRox0rz.git
 ! [rejected]    master -> master (non-fast forward)
error: failed to push some refs to 'git@github.com:magic/mikeFreakingRox0rz.git'


 
Uh-oh. What happened? Well, a push is basically copying your repository back to the server. But if you were to do that now, you would overwrite all of Mike's changes. Since your local set of commits and the remote set of commits have now diverged, git doesn't let you push. The key difference between what you did and what Mike did is that when Mike pushed, the remote server had a subset of his changes, whereas when you pushed the remote server had changes that you have never seen. All in all, this is similar to when you try to commit your changes to an SVN repository when someone else has made commits before you: in order to avoid incoherency in the differences that are stored, you can't do this.
 
In SVN, the next step is to update. This updates the changes, and, if you are fortunate, there are no conflicts with changes you made and you are good to go. In git, the next step is to pull:
 

# git pull
remote: Counting objects: 20, done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 11 (delta 8), reused 0 (delta 0)
Unpacking objects: 100% (11/11), done.
 From git@github.com:magic/mikeFreakingRox0rz.git
  38620e5..058cf00 master   -> origin/master
Merge made by recursive. 
 mike/is/awesome/prove.rb |  4 ++-- 
 1 files changed, 2 insertions(+), 2 deletions(-)


 
Usually you just fire this off, do your push again, and then forget about it. But it's important to understand what is going on in this step. When I pull from the remote repository and I have changes that are not in that remote repository, we need to reconcile those differences. In terms of what the commits look like, the original sync commit had two child lists. Those two lists must now be reconciled and joined into a single commit, which represents a coherent continuation point for everyone. In essence, the two branches that have diverged need to converge again before we push back to the server. Git needs a single head commit -- essentially the commit at the end of your trees -- to work correctly.
 
Anytime you pull from a repository and it has commits that you don't have, those commits are added to your local repository, and then a single merge commit is created that merges the last commit from the remote repository with the last commit from your local repository. In the above transcript from git, that happened automatically:
 

Merge made by recursive.


 
This means git went ahead and merged the two and didn't find any conflicts it couldn't resolve. In this case, git also automatically created the merge commit for you. If you do a git log afterwards, you will see it:
 

# git log commit eb8442977176a95568e27b40c169e2d97ab4e8f7
Merge: 0f6ef2d... 058cf00... 
Author: Antonio Salazar Cardozo  
Date:  Wed Apr 22 16:55:41 2009 -0400

   Merge branch 'master' of git@github.com:magic/mikeFreakingRox0rz.git


 
At this point, your repository is ready to push back, and you can do a git push as above and this time it will work.
 
This is the perfect situation above. There are two important things that can go wrong when you pull. A pull in git is a combination of a fetch command (which pulls the remote repository in its current form into a local copy of that repository) and a merge command (which merges that local copy of the remote repository into your local working repository). Generally, the fetch works (unless you have a connection issue), and the merge occasionally fails. For example:
 

# git pull
remote: Counting objects: 48, done.
remote: Compressing objects: 100% (26/26), done.
remote: Total 26 (delta 20), reused 0 (delta 0)
Unpacking objects: 100% (26/26), done.
 From git@github.com:magic/mikeFreakingRox0rz.git
  38620e5..eb84429 master   -> origin/master
Updating 38620e5..eb84429
mike/is/awesome/prove.rb: needs update
error: Entry 'mike/is/awesome/prove.rb' not uptodate. Cannot merge.


 
This happens when you have made changes locally that haven't yet been committed. Git merges committed files happily, but it doesn't even try to merge changes that are not yet committed into the repository. The solution here is to just commit:
 

# git commit -m "He is awesomer." mike/is/awesome/prove.rb


 
Once that's done, you can do one of two things. You can run a pull again, but that will redo the fetch, and you've already fetched the remote repository. The alternative is faster, which is to just redo the merge part of the pull:
 

# git merge origin/master


 
This merges from the master branch of the origin remote, which is the remote repository and branch we've been working with. At this point, the merge will usually succeed. However, it may fail yet again, with an error that looks more like:
 

Auto-merged mike/is/awesome/prove.rb 
CONFLICT (content): Merge conflict in mike/is/awesome/prove.rb


 
This may happen for multiple files. At this point, you need to resolve the merge error. The repository is left in a very unsteady state. The only way to really move forward is to resolve the conflict and then commit. The commit will be the merge commit we saw in the log above, and will include any automatic merging that git has done successfully. The easiest way to perform the merge is:
 

# git mergetool
merge tool candidates: kdiff3 tkdiff xxdiff meld gvimdiff opendiff emerge vimdiff 
Merging the files: mike/is/awesome/prove.rb 

Normal merge conflict for 'mike/is/awesome/prove.rb': 
  {local}: modified
  {remote}: modified 
Hit return to start merge resolution tool (opendiff):


 
git mergetool essentially walks you through each file with conflicts and offers you the choice of which program you want to resolve the conflict with. It detects what programs are available and makes an intelligent selection based on that. On Mac, it tends to be opendiff. Once you open the file, you can resolve the conflict and then save it. mergetool may prompt you to verify that you've resolved the conflict (that usually only happens if you leave everything unchanged). Then, when you've done all the files, you are returned to the command prompt. At that point, run git status, double-check that everything looks good, add anything you want to commit if you haven't done so yet, and then commit. At that point, your merge commit is done and you can push again.
 
One last thing worth mentioning is an alternative to committing your changes when you get the ``not uptodate'' error. That error means that you have uncommitted changes in your working tree on a file that git is trying to merge. You can commit them, but you may not be done with those changes yet. In these cases, you can use git stash. If you just run git stash, it will stash all changes to your working tree away in a secret place, and leave your working tree clean for a merge. Then, when you've done the merge, you can do git stash apply, which will apply the latest stashed changes to your working tree. This is a good way to pause your work, merge, then continue your work.