Thursday, August 20, 2009

a button with a slide out panel

This is patterned after the volume button in the YouTube player, which has a slide out volume slider that appears on rolling over the button. Enjoy.


<?xml version="1.0" encoding="utf-8"?>
<mx:VBox
xmlns:mx="http://www.adobe.com/2006/mxml"
width="{ BUTTON_WIDTH }"
height="{ BUTTON_HEIGHT + ROLLOUT_HEIGHT }"
verticalGap="0"
verticalScrollPolicy="off"
creationComplete="{ onCreationComplete() }"
rollOut="{ onRollOut() }"
rollOver="{ onRollOver() }">

<mx:Script>
<![CDATA[
// the width and height of the button
private static const BUTTON_HEIGHT:Number = 20;
private static const BUTTON_WIDTH:Number = 20;

// the width and height of the roll out
private static const ROLLOUT_HEIGHT:Number = 100;
private static const ROLLOUT_WIDTH:Number = 20;

private function onCreationComplete():void
{
rollOutContainer.y = -ROLLOUT_HEIGHT;
rollOutContainer.visible = true;
}

private function onRollOut():void
{
hideEffect.play();
}

private function onRollOver():void
{
showEffect.play();
}
]]>
</mx:Script>

<mx:Move
id="showEffect"
target="{ rollOutContainer }"
yFrom="{ -ROLLOUT_HEIGHT }"
yTo="0"/>

<mx:Move
id="hideEffect"
target="{ rollOutContainer }"
yFrom="0"
yTo="{ -ROLLOUT_HEIGHT }"/>

<!-- rollover button -->
<mx:Button
width="{ BUTTON_WIDTH }"
height="{ BUTTON_HEIGHT }"/>

<mx:Canvas
width="{ ROLLOUT_WIDTH }"
height="{ ROLLOUT_HEIGHT }"
verticalScrollPolicy="off">

<mx:Canvas
id="rollOutContainer"
visible="false"
width="{ ROLLOUT_WIDTH }"
height="{ ROLLOUT_HEIGHT }"
backgroundColor="#ffffff">

<!-- content goes here -->

</mx:Canvas>
</mx:Canvas>

</mx:VBox>

Monday, July 27, 2009

SVG dimensions & whitespace

Perhaps this is documented somewhere, but I haven't found it.

Let's say you have an SVG image called "whitespacetest.svg" that's 200 by 50 pixels. However, let's say that it only contains a circle that's 50 by 50 on the far right, so there's 150 pixels of whitespace on the left side of the image.

What's the width of the image? According to Flex, it's 50. However, when it goes to draw the image, it will use the full width of the SVG.

Here's the test code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
[Embed(source="whitespaceTest.svg")]
[Bindable]
public var whitespaceTest;
]]>
</mx:Script>

<mx:VBox>
<mx:HBox width="200" height="10" backgroundColor="#ff0000"/>
<mx:Image id="testImage" source="{embed}"/>
<mx:Label text="{testImage.width}"/>
</mx:VBox>
</mx:Application>


This will display 50 for the width, but the circle will be flush with the right edge of the colored box.

My takeaway is that if you need to know the width of an SVG, don't use whitespace in it. Kind of sucky.

Tuesday, July 21, 2009

Minimum size on a scrollbar thumb, using a ProgrammaticSkin

I had to make a programmatic skin for a scroll bar. I don't think this is something most people should do, frankly. Graphic skinning is much easier and more flexible, and allows programmers to program and artists to...well, art. Why is there no verb for making art, I wonder? But I digress.

Anyway, this was a special case. I needed complete control over the colors within the scrollbar, and couldn't use a ColorFilter, as I was under a ColorFilterInjunctivitis jinx brought on by re-reading Harry Potter way too many times or something.

So anyway, here's the problem: there doesn't seem to be any way to set the minimum size of a scrollbar thumb through CSS. Or if there is, I can't find it. It looks to be fairly hard-coded in ScrollBar.as.

Here's my solution. I can't say I'm proud, but the damn thing works. Even if it is a butt-ugly solution, my hope here is that my blog will one day get famous, I'll be acknowledged as an expert in the field, and then this solution will be regarded as the "official workaround" and not an egregious hack.


public class VScrollBarSkin extends ProgrammaticSkin
{
private static const THUMB_MIN_HEIGHT:Number = 20;
override public function get measuredHeight():Number
{
// way to style this, it's not at all obvious.
if (parent && (parent is ScrollThumb))
ScrollThumb(parent).minHeight = THUMB_MIN_HEIGHT;

return SKIN_HEIGHT;
}

Revision to calculateDerivedColor()

First, an explanation. What I'm trying to do is answer the question: what color is to blue as green is to beige? It's kind of a silly question, but it's very useful for recoloring an entire theme.

Without further ado, here's the code:


public class ColorUtils
{
/**
* Returns result, where a is to result as b is to c. The parameters and result are between 0 and 1.
*
* This is somewhat ambiguous. When b is 0.5 and c is 1, should this return a * 2, or a + 0.5?
* There's no simple answer, so use both approaches and return the average.
*/
private static function applyDifference(a:Number, b:Number, c:Number):Number
{
var linearAdjusted:Number = a + c - b;
var multiplierAdjusted:Number;
if (b == 0)
multiplierAdjusted = 2;
else
{
multiplierAdjusted = a * c / b;
multiplierAdjusted = Math.min(multiplierAdjusted, 2);
}
a = (linearAdjusted + multiplierAdjusted) / 2;
return Math.max(0, Math.min(a, 1));
}


/**
* Return a color derived from baseColor using sampleStartColor and sampleEndColor as a guide.
* For example, this function would return dark blue if baseColor was light blue, sampleStartColor
* was pink and sampleEndColor was dark red.
*
* It's like an SAT question: red is to x as dark green is to greenish gray. What's x?
*/
public static function calculateDerivedColor(baseColor:uint, sampleStartColor:uint, sampleEndColor:uint):uint
{
var baseColorHSL:HslColor = new HslColor(baseColor);

var sampleStartColorHSL:HslColor = new HslColor(sampleStartColor);
var sampleEndColorHSL:HslColor = new HslColor(sampleEndColor);

// rotate the hue and offset the saturation and lightness based on the inputs
baseColorHSL.h = (baseColorHSL.h + sampleEndColorHSL.h - sampleStartColorHSL.h + 360) % 360;

baseColorHSL.s = applyDifference(baseColorHSL.s, sampleStartColorHSL.s, sampleEndColorHSL.s);
baseColorHSL.l = applyDifference(baseColorHSL.l, sampleStartColorHSL.l, sampleEndColorHSL.l);

return baseColorHSL.rgb;
}

Wednesday, July 15, 2009

Color manipulation in Flex (HSL class +)

Flex has some great color manipulation tools built in. The ColorMatrixFilter class is amazing, particularly when paired with the ColorMatrix class found here:
http://www.quasimondo.com/archives/000599.php

However, for my current project, I've also needed the ability to recolor specific RGB values. The reason is simple: I'm not necessarily using a filter, but am in some cases changing CSS values on the fly. Let's say the DataGrid is defined like so in CSS:


DataGrid {
borderColor:#1b3244;
rollOverColor: #3c5c80;
selectionColor: #bacfe7;
...


Now let's say I want to change borderColor in the DataGrid to an arbitrary color and have all the other colors change along with it, but maintain their relationships to the first color (e.g., rollOverColor is somewhat lighter, selectionColor is much lighter), et cetera.

To solve this problem, I made two classes, the first based on the great C# project found here:
http://www.codeproject.com/KB/recipes/colorspace1.aspx


public class HslColor
{
/**
* adapted from:
* http://www.codeproject.com/KB/recipes/colorspace1.aspx
*
* A simple class to convert between RGB and HSL values. This can be constructed with a RGB value,
* HSL values may be extracted and manipulated, and the result can be returned as a RGB value.
*/

/**
* 0 to 359
*/
public var h:uint;

/**
* 0 to 1
*/
public var s:Number;

/**
* 0 to 1
*/
public var l:Number;

/**
* Construct a HslColor from a rgb value. The h, s, l values can be manipulated, and the rgb
* value can be then retreived using getRgbColor().
*/
public function HslColor(color:uint)
{
rgb = color;
}

static public function fromString(color:String):HslColor
{
return new HslColor(uint(color.replace("#", "0x")));
}

static public function toRGB(h:Number, s:Number, l:Number):uint
{
var hslColor:HslColor = new HslColor(0);
hslColor.h = h;
hslColor.s = s;
hslColor.l = l;
return hslColor.rgb;
}

public function get rgb():uint
{
var r:uint =0;
var g:uint = 0;
var b:uint = 0;

if(s == 0)
{
r = g = b = l * 255;
}
else
{
var q:Number = (l<0.5)?(l * (1.0+s)):(l+s - (l*s));
var p:Number = (2.0 * l) - q;

var Hk:Number = h/360.0;
var T:Array = new Array(3);
T[0] = Hk + (1.0/3.0); // Tr
T[1] = Hk; // Tb
T[2] = Hk - (1.0/3.0); // Tg

for(var i:int = 0; i < 3; i++)
{
if(T[i] < 0) T[i] += 1.0;
if(T[i] > 1) T[i] -= 1.0;

if((T[i]*6) < 1)
{
T[i] = p + ((q-p)*6.0*T[i]);
}
else if((T[i]*2.0) < 1)
{
T[i] = q;
}
else if((T[i]*3.0) < 2)
{
T[i] = p + (q-p) * ((2.0/3.0) - T[i]) * 6.0;
}
else T[i] = p;
}

r = uint(255 * T[0]);
g = uint(255 * T[1]);
b = uint(255 * T[2]);
}

return (r << 16) | (g << 8) | b;
}

public function set rgb(color:uint):void
{
var r:Number = color >> 16 & 0xFF;
var g:Number = color >> 8 & 0xFF;
var b:Number = color & 0xFF;

h = 0;
s = l = 0;

// normalizes red-green-blue values
var rFraction:Number = Number(r/255);
var gFraction:Number = Number(g/255);
var bFraction:Number = Number(b/255);

var max:Number = Math.max(rFraction, Math.max(gFraction, bFraction));
var min:Number = Math.min(rFraction, Math.min(gFraction, bFraction));

// h
if(max == min)
{
h = 0; // undefined
}
else if (max == rFraction && gFraction >= bFraction)
{
h = 60.0 * (gFraction - bFraction) / (max - min);
}
else if (max == rFraction && gFraction < bFraction)
{
h = 60.0 * (gFraction - bFraction) / (max - min) + 360.0;
}
else if (max == gFraction)
{
h = 60.0 * (bFraction - rFraction) / (max - min) + 120.0;
}
else if (max == bFraction)
{
h = 60.0 * (rFraction - gFraction) / (max - min) + 240.0;
}

// luminance
l = (max + min) / 2.0;

// s
if(l == 0 || max == min)
{
s = 0;
}
else if (0 < l && l <= 0.5)
{
s = (max - min) / (max + min);
}
else if (l > 0.5)
{
s = (max - min) / (2 - (max + min)); //(max-min > 0)?
}
}
}


This is a very useful little class. You can create one with a RGB value passed in and read its HSL(hue, saturation and lightness) properties. What's more, you can modify the h, s or l properties, and get the rgb property.

For example, you could desaturate a color as follows:


var hslColor:HslColor = new HslColor(rgb);
hslColor.s *= .5;
rgb = hslColor.rgb;


Going back to the original example, how do I change the DataGrid.borderColor to an arbitrary color and have all the other color values change correspondingly?

Well, with the aid of the HslColor class and the following ColorUtils class I wrote:

public class ColorUtils
{
/**
* Return a color derived from baseColor using sampleStartColor and sampleEndColor as a guide.
* For example, this function would return dark blue if baseColor was light blue, sampleStartColor
* was pink and sampleEndColor was dark red.
*
*/
public static function calculateDerivedColor(baseColor:uint, sampleStartColor:uint, sampleEndColor:uint):uint
{
var baseColorHSL:HslColor = new HslColor(baseColor);

var sampleStartColorHSL:HslColor = new HslColor(sampleStartColor);
var sampleEndColorHSL:HslColor = new HslColor(sampleEndColor);

// rotate the hue and offset the saturation and lightness based on the inputs
baseColorHSL.h = (baseColorHSL.h + sampleEndColorHSL.h - sampleStartColorHSL.h + 360) % 360;
baseColorHSL.s = Math.min(1,Math.max(0, baseColorHSL.s + sampleEndColorHSL.s - sampleStartColorHSL.s));
baseColorHSL.l = Math.max(0, baseColorHSL.l + sampleEndColorHSL.l - sampleStartColorHSL.l);

return baseColorHSL.rgb;
}

}


Here calculateDerivedColor() returns a new color based on baseColor but with the differences between sampleStartColor and sampleEndColor applied to it. So if sampleStartColor and sampleEndColor were the same color, it should simply return baseColor.

To set DataGrid's borderColor to an arbitrary color and set rollOverColor and selectionColor to a related color, we can do the following:


var datagridCSS:CSSStyleDeclaration = StyleManager.getStyleDeclaration("DataGrid");
datagridCSS.setStyle('borderColor', newBorderColor);
datagridCSS.setStyle('rollOverColor', ColorUtils.calculateDerivedColor(newBorderColor, 0x1b3244, 0x3c5c80));
datagridCSS.setStyle('selectionColor', ColorUtils.calculateDerivedColor(newBorderColor, 0x1b3244, 0xbacfe7));


Note that 0x1b3244 was the original borderColor (which we replaced with newBorderColor) and 0x3c5c80 and 0xbacfe7 were the original rollOverColor and selectionColor values, respectively.

Feel free to use this code however you see fit. A link back here would be appreciated.

Friday, July 3, 2009

Problem de jour

Okay, I'm hoping to publish some cool tips n' tricks I've picked up or developed one day...but at the moment, I'm stuck on a new issue. Using Flex SDK 3.3 and Internet Explorer 8, I am suddenly getting this crash whenever I launch:

undefined
at mx.controls::SWFLoader/doScaleContent()
at mx.controls::SWFLoader/updateDisplayList()
at mx.controls::Image/updateDisplayList()
at mx.core::UIComponent/validateDisplayList()
at mx.managers::LayoutManager/validateDisplayList()
at mx.managers::LayoutManager/doPhasedInstantiation()
at Function/http://adobe.com/AS3/2006/builtin::apply()
at mx.core::UIComponent/callLaterDispatcher2()
at mx.core::UIComponent/callLaterDispatcher()

There was a SWFLoader in this project, but commenting it out didn't solve the problem.

SOLVED: the "fix" seems to be to switch to Firefox. Hopefully this is a debug issue only.

Wednesday, July 1, 2009

Problem from Hell (part III - back to hell with you)

My partner took a look at this and somehow came up with this "solution".

In the project, there's a MXML file called something like TestHohoCanvasHelper.mxml, containing this declaration:

<containers:HohoCanvas
id="foo"
width="100%"
height="100%">

Elsewhere there's an AS file called something like TestHohoCanvas.as which creates a TestHohoCanvasHelper dynamically like this:

private var
testHohoCanvasHelper:TestHohoCanvasHelper;
private var testHohoCanvas:HohoCanvas;

override public function setUp():void
{
testHohoCanvasHelper = new TestHohoCanvasHelper();
Application(Application.application).addChild(testHohoCanvasHelper );
testHohoCanvas = testHohoCanvasHelper.foo;
}


This used to work fine, but I think the Flex compiler detected I was working long hours, figured I was on deadline, and decided to crap out with compilation errors galore - even when building applications that didn't even reference this file.

The solution was to remove "id=" from TestHohoCanvasHelper.mxml and reference the HohoCanvas a different way.

As a friend of mine put it, "ah, life with open sores".

Hope this was useful to someone.