Tuesday, January 30, 2007

Napkin skins, stage three: the (current) limits of skinning

After seeing what we can do with graphical and programmatic skins, it is time to explore what you can't do with Flex skins (yet). Unfortunately, you need to subclass ComboBox and DataGrid in order to get to the full napkin theme (click on the screenshot to see the app):

The changes at this stage are reflected in the MXML file (to see the complete file, select View Source from the app's context menu):
    <components:ScrawlGrid id="dg" width="100%" height="50%" dataProvider="{employees.employee}">
<components:columns>
<mx:DataGridColumn dataField="name" headerText="Name"/>
...
<components:ScrawlComboBox width="50%" dataProvider="[Lorem, Ipsum, Dolor, Amet]"/>
[Note the use of <components:columns> instead of <mx:columns>.] We are now using the subclasses of ComboBox and DataGrid: components.ScrawlComboBox and components.ScrawlGrid.

ScrawlComboBox differs from ComboBox only in specifying a different dropdownFactory:
    private var _ddFactory:IFactory = new ClassFactory(ScrawlComboBoxDropdown); 
Subclassing ComboBox allows us to do two things: (1) specify a post-it skin for the dropdown list (in scrawl4.css):
    ScrawlComboBoxDropdown { background-image: Embed('assets/postit.jpg'); }
and (2) specify a cross-hatch pattern for rollover and selection in the dropdown list (in ScrawlComboBoxDropdown.as):
    override protected function drawSelectionIndicator(
indicator:Sprite, xx:Number, yy:Number,
ignoredWidth:Number, h:Number, color:uint,
ignoredRenderer:IListItemRenderer):void
{
var w:int = unscaledWidth - viewMetrics.left - viewMetrics.right;
ScrawlUtil.drawCrossHatch(Sprite(indicator).graphics, w, h, color, 1.0);
indicator.x = xx;
indicator.y = yy;
}
Regarding (1): Since the background-image style is inherited, we could have simply created the style for List, which would have the same effect for this app, without subclassing ComboBox (since the default dropdown for a ComboBox is a List), but that would have meant that all Lists in the UI would have had a post-it background, which is not what we want here.

Regarding (2): Note that ScrollComboBoxDropdown subclasses List and overrides drawHighlightIndicator() and drawSelectionIndicator(). Flex makes it easy to skin rollover and selection in a List or DataGrid if all you want to do is change the color:
    roll-over-color: #E4E444;
selection-color: #C0C022;
However, if you want to draw any patterns in there, you currently have to subclass.

Looking at the code for ScrawlGrid, we see that, in addition to the cross-hatch pattern for rollover and selection, there are other things that require overriding DataGrid:

(1) Drawing the column separator: ScrawlGrid overrides drawVerticalLine() to add some randomness so that the lines are not exactly vertical. To get the full effect, we also need to add the following graphical skin as a DataGrid style:
    header-separator-skin: Embed('assets/header_separator.gif'); /* transparent */
Strange but true: the header separator is skinnable, but the column separator as a whole is not.

(2) Drawing a diagonal line fill in the header: ScrawlGrid overrides drawHeaderBackground() -- once again, flex makes it easy to specify a solid background color for the header, but you have to subclass in order to draw any patterns.

We now get to the most difficult part of creating this theme: drawing rollover and selection indicators in the DataGrid headers. In DataGrid, the drawing code for rollover and selection for headers (but not for the DataGrid's contents) is hidden inside mouseOverHandler() and mouseDownHandler(). So we need the hack of overriding mouse handlers in order to change a purely visual part of the app:
    override protected function mouseDownHandler(event:MouseEvent):void
{
super.mouseDownHandler(event);
headerRendererHack(event, getStyle("selectionColor"));
}
Note the call to super.mouseDownHandler() -- we can't just ignore the superclass' mouse handling code the way we did with the draw***() methods. headerRendererHack() draws the cross-hatch pattern in the renderer for the correct header -- but there's a twist to finding that renderer. It turns out that mouseEventToItemRenderer() will return the correct header renderer except for the case where the mouse is over the sort arrow's hit area. What we want in that case is the renderer for the header of the column being sorted. getColumnHeaderRenderer() finds that renderer for us.

That's it! (whew!) We now have reached the final version of the styled, skinned and tweaked Napkin app. Hopefully the tweaking needed at this stage will decrease over time as it is replaced with a more versatile use of programmatic and even graphical skins.

Thursday, January 25, 2007

Napkin skins, stage two: programmatic skins

We can build on stage one by using programmatic skins (click on the screenshot to see the app):

There were no significant changes to the MXML file: the only change is the reference to the CSS stylesheet. Looking at scrawl3.css (when you select View Source in the app's context menu), we see that the changes to the stylesheet are limited to styles that are specified as ClassReferences, for example:
    track-skin: ClassReference('skins.scrawl.ScrollTrackSkin');
These are references to programmatic skins written in ActionScript, which you can find in the source tree under skins.scrawl. The simplest one, ScrollTrackSkin.as, specifies the skin for the ScrollBar's track (but not for the thumb or the arrow). The drawing happens in the overridden method updateDisplayList() and, aside from a drawing a simple border around the track, does only one interesting thing:
    // fill with zero alpha: if no fill, mouse events are not recognized
drawRoundRect(1, 1, w-2, h-2, 0, 0x000000, 0.0);
I found that, without this line, clicking on the track does not move the ScrollBar thumb to the location of the click -- the mouse event is ignored, presumably because it doesn't hit any area drawn by the track skin. My solution for that was to add an invisible background for the track. I have also used this solution in my other programmatic skins, but I am not sure that this is the "recommended" way to address this issue.

Another simple programmatic skin is ScrollArrowSkin.as. Someone with a different balance of graphical vs. coding skills would have probably created this as a graphical skin. For me, this was the simplest way to go. One advantage of using the programmatic skin is that you can make slight variations for up-/over-/down-skins within the same skin class. In this case, I have used conditions on the skin's name to determine the color and direction of the arrow, which allowed me to use the same skin class for all ScrollBar arrow skins:
    down-arrow-down-skin: ClassReference('skins.scrawl.ScrollArrowSkin');
down-arrow-over-skin: ClassReference('skins.scrawl.ScrollArrowSkin');
down-arrow-up-skin: ClassReference('skins.scrawl.ScrollArrowSkin');
...
up-arrow-down-skin: ClassReference('skins.scrawl.ScrollArrowSkin');
up-arrow-over-skin: ClassReference('skins.scrawl.ScrollArrowSkin');
up-arrow-up-skin: ClassReference('skins.scrawl.ScrollArrowSkin');
Programmatic skins also allow for some nifty tricks. The code in ComboBoxSkin.as that draws the border of the ComboBox uses Math.random() to draw it differently every time:
    g.moveTo(Math.random() * borderThickness, Math.random() * borderThickness);
g.lineTo(w - (Math.random() * borderThickness), Math.random() * borderThickness);
g.moveTo(w - (Math.random() * borderThickness), Math.random() * borderThickness);
g.lineTo(w - (Math.random() * borderThickness), h - (Math.random() * borderThickness));
...
This helps give the ComboBox that ragged scrawled-on-a-napkin look: the border is different for the up-, over- and down-skins. If you play with the box dividers, you'll see that the ComboBox redraws itself with a slightly different border, even for the same skin. A similar principle is at work for the DataGrid's border (in ScrawlBorder.as), as well as for the cross-hatch pattern for the ScrollBar thumb skin (ScrollThumbSkin.drawCrossHatch()) and the ComboBox's down-skin (ScrawlUtil.drawCrossHatch() in the util package):
    // draw \\\\\\\\
for(i = -h; i < w; i += 6)
{
vstart = Math.max(-i, 0);
vdistance = Math.min(h, w - i);
vstartOffset = vstart + 3 - (6 * Math.random());
g.moveTo(i + vstartOffset, vstartOffset);
g.lineTo(i + vdistance, vdistance + 2 - (4 * Math.random()));
}

// draw ////////
for(i = 0; i < w + h; i += 6)
{
vstart = Math.max(i - w, 0);
vdistance = Math.min(h, i);
vstartOffset = vstart + 3 - (6 * Math.random());
g.moveTo(i - vstartOffset, vstartOffset);
g.lineTo(i - vdistance, vdistance + 2 - (4 * Math.random()));
}
Once again, programmatic skins allow us to call this code only for the ComboBox's down-skin while reusing the same class for all three skins:
    down-skin: ClassReference('skins.scrawl.ComboBoxSkin');
over-skin: ClassReference('skins.scrawl.ComboBoxSkin');
up-skin: ClassReference('skins.scrawl.ComboBoxSkin');
As one final touch, the DataGrid's border looks like it was drawn with a marker, with varying pressure on the marker producing color variations along the way. This effect is accomplished by specifying the following gradient type in ScrawlBorder.updateDisplayList():
    g.lineGradientStyle(GradientType.LINEAR, [borderColor, borderColor], [1.0, 0.5], [0, 255],
null, SpreadMethod.REFLECT);

Wednesday, January 24, 2007

Napkin skins, stage one: CSS styles and graphical skins

Quite a bit can be done using only CSS and graphical skins (click on the screenshot to see the app):

If you look at the source -- you can see the source files by selecting View Source from the app's context menu -- you will see that the MXML file only contains one new line:
    <mx:Style source="scrawl2.css"/>
The rest of the work is done by the CSS stylesheet. The font has been changed to AEnigma Scrawl (the font file is in the source tree under /assets, as are the images mentioned below) using the font declaration
    @font-face
{
font-family: scrawlFont;
src: url("assets/aescrawl.ttf");
font-weight: normal;
}
which is referenced in most cases by setting the style
    font-family: scrawlFont;
except for the DataGrid headers, which use the .Scrawl style declaration and
    header-drag-proxy-style-name: Scrawl;
header-style-name: Scrawl;
The background uses the napkin image from the Napkin L&F itself:
    background-image: Embed('assets/napkin.jpg');
and stretches to fill the background:
    background-size: '100%';
But, in order for the DataGrid to show us that background, we need to disable the alternating row colors:
    alternating-item-colors: ClassReference(null);
The DataGrid headers are still opaque; there's nothing we can do about it using only CSS as far as I know, but by the end of the third stage this will also be fixed.

Other graphical skins that are embedded in this version: the horizontal and vertical dividers and the DataGrid's sort arrow:
    divider-skin: Embed('assets/box_divider.gif');
sort-arrow-skin: Embed('assets/sort_arrow.gif');
The stylesheet also sets up the color styles for the components, as well as the border thickness for the DataGrid, in preparation for further skinning in the next stages.

Note: If you are looking to reuse the assets shown here, check out assets/aenigmascrawl.txt and assets/napkin.txt in the source view for the terms governing the use of AEnigma Scrawl font and the napkin (and postit) images. Thanks are due to Brian Kent and Ken Arnold for clarifying these terms for me and for creating these in the first place! The box divider and sort arrow are my creations and are, sadly, close to the limit of my graphical design ability. If you want to use either of those, knock yourself out. ;-)

Napkin skins in Flex

How easy is it to skin a Flex application? As an experiment, I tried to create something that looks like the Napkin L&F for a simple Flex app. (The idea of the Napkin L&F is to have your UI look like it was scrawled on a napkin -- which might be useful if you wanted people to get the fact that your mock-up was just a mock-up and not a fully functional app.)

The application is a slightly modified version of the DataGrid sample from the Flex 2 Component Explorer. Here are the "before" and "after" screenshots (click on the screenshot to see the app):
before:

after:

So, was it easy? I'll break it down into three separate stages. The first stage involves adding CSS styles and graphical skins to the app; the second stage involves using programmatic skins; the third stage deals with subclassing Flex framework classes in order to achieve fine-grained control of the look-and-feel.

Thursday, January 18, 2007

Hi there!

Hi! This blog is a place for me to post my thoughts on Flex. Hopefully I won't ramble too much... :-)

A bit about me: I joined Adobe a couple of months ago. Although I am technically part of the Flex team, I'm not really working on Flex right now, and my Flex coding skills are still pretty rudimentary. My background before joining Adobe is mostly in writing desktop Java apps.