Andy Hulstkamp

about creating online experiences

22. September 2008

Shiny Toggle Buttons

Here’s a little example of a ToggleButtonSkin that uses some effects to create a subtle chrome-impression when the button is selected.

The gradient is calculated from the backgroundColor-Style of the host component. Then in selected state some Tint and Animate effects are applied to the Strokes that make up the border.

Gumbo Shiny Toggle Buttons

Click here for the example. Needs flash player 10 (beta).

Here’s the source for the Skin:

<?xml version="1.0" encoding="utf-8"?>
<!--
  Testing skins in Gumbo...
  Skin for a ToggleButton that uses some effects in on-mode.
  Andreas Hulstkamp
-->
<Skin xmlns="http://ns.adobe.com/mxml/2009" 
      xmlns:mx="library:adobe/flex/halo" 
      xmlns:f="flex.filters.*" 
      xmlns:effects="flex.effects.*" 
      creationComplete="init()" >
    <Script>
      <![CDATA[
    import mx.utils.ColorUtil;
    import flex.effects.Tint;
    import flex.effects.Animate;
    import mx.effects.IEffectInstance;
    import mx.events.StateChangeEvent;

    //TODO: trace to see if these are update in a bunch
    [Bindable]
    private var baseColor:uint = 0x707070;

    [Bindable]
    private var lightColor:uint = 0xa0a0a0; 

    [Bindable]
    private var darkColor:uint = 0x404040; 

    [Bindable]
    private var glowColor:uint = 0xe0e0e0;

    [Bindable]
    private var chromeColor:uint = 0xffffff;

    private var tintEffInst:IEffectInstance;
    private var animEffInst:IEffectInstance;

    //get backgroundColor from hostComponent and calculate Gradient colors
    override public function stylesInitialized():void {
      super.stylesInitialized();
      baseColor = hostComponent.getStyle("backgroundColor");
      lightColor = ColorUtil.adjustBrightness(baseColor, 0x80);
      darkColor = ColorUtil.adjustBrightness(baseColor, -0x40);
      glowColor = ColorUtil.adjustBrightness(baseColor,  0x50);
    }

    private function init():void {
      this.addEventListener(StateChangeEvent.CURRENT_STATE_CHANGE, stateChangeHandler);
    }

    //would be nice to have current StateGroup exposed by StateChangeEvent
                //We are depending on the halo sequence otherwise the effects could probably be linked to          
                //the enhanced states syntax right in mxml.
    private function stateChangeHandler(event:StateChangeEvent):void {
      if (event.newState == "overAndSelected" && !tintSequence.isPlaying) {
        tintSequence.play([this]);
        rotationAnimate.play([outerStroke]);
      } else if (event.newState == "over" && tintSequence.isPlaying) {
        tintSequence.end();
        rotationAnimate.stop();
      }
    }

    //Give the tint effect a hook to act on
    public function get color():uint {
      return chromeColor;
    }

    public function set color(value:uint):void {
      chromeColor = value;
    }
      ]]>
    </Script>
    <Metadata>
      [HostComponent("flex.component.ToggleButton")]
    </Metadata> 

     <states>
        <State name="up"          stateGroups="off"/>
        <State name="over"          stateGroups="off"/>
        <State name="down"          stateGroups="off"/>
        <State name="disabled"        stateGroups="off"/>
        <State name="upAndSelected"     stateGroups="on"/>
        <State name="overAndSelected"     stateGroups="on"/>
        <State name="downAndSelected"     stateGroups="on"/>
        <State name="disabledAndSelected"   stateGroups="on"/>
    </states>

    <!-- effects for selected states -->
    <Declarations>
      <mx:Sequence id="tintSequence" repeatCount="0">
        <effects:Tint duration="{Math.random()*1000 + 3000}"  colorFrom="{chromeColor}" colorTo="0x000000" />
        <effects:Tint duration="{Math.random()*1000 + 3000}"  colorFrom="0x000000" colorTo="{chromeColor}" />
      </mx:Sequence>
    <effects:Animate id="rotationAnimate" duration="{Math.random()*1000+4000}"  repeatCount="0">
      <effects:propertyValuesList>
        <effects:PropertyValuesHolder property="rotation" values="[0,360]"/>
      </effects:propertyValuesList>
    </effects:Animate>
    </Declarations>

    <content>
        <Group left="0" top="0" right="0" bottom="0">
          <!-- the background with the Gradient -->
            <Rect left="0" top="0" right="0" bottom="0" radiusX="4" radiusY="4" minWidth="25" minHeight="25">
                 <stroke>
                  <LinearGradientStroke id="outerStroke" weight="2" rotation="90" rotation.off="90">
                    <GradientEntry color="{chromeColor}" alpha="1" ratio="0" />
                    <GradientEntry color="{0xffffff - chromeColor}" alpha="1" ratio="1" />
                  </LinearGradientStroke>
                 </stroke>
                <fill>
                   <LinearGradient rotation="90">
                      <GradientEntry color="{lightColor}" alpha="1" ratio="0" />
                    <GradientEntry color="{baseColor}" alpha="1" ratio=".25" ratio.on=".5" />
                      <GradientEntry color="{darkColor}" alpha="1" ratio=".75" ratio.on=".5"/>
                    <GradientEntry color="{glowColor}" alpha="1" ratio="1" />
                   </LinearGradient>
                </fill>
            </Rect>
            <!-- additional inner Stroke for the chrome Effect -->   
             <Rect left="3" top="3" right="3" bottom="3" radiusX="4" radiusY="4" minWidth="25" minHeight="25">
                 <stroke>
                  <LinearGradientStroke weight="1" rotation="90" rotation.off="90">
                    <GradientEntry color="{chromeColor}" alpha="1" ratio="0" />
                    <GradientEntry color="{0xffffff - chromeColor}" alpha="1" ratio="1" />
                  </LinearGradientStroke>
                 </stroke>
            </Rect>        
        </Group>

        <!-- Render content -->
        <Group horizontalCenter="0" verticalCenter="0"
            content="{data.content}" >
            <filters>
              <f:DropShadowFilter color="0x000000" blurX="1" blurX.on="3" blurY.on="3" blurY="1" distance="1" distance.on="0" />
            </filters>
    </Group>
    </content>
</Skin>

Full source is here

further reading

Custom Component using Flex SpriteVisualElement

A much more lightweight approach to create a simple custom Component in Flex Spark. Does not extend from the heavier SkinnableComponent but from SpriteVisualElement.

Gumbo plays generated sound

Plays music based on a simple sequencer using generated sounds. Uses Gumbo (Flex 4 beta) for the player skin.