Share 'Tutorial: Creating a Button with Jakute Styling Engine' on Delicious Share 'Tutorial: Creating a Button with Jakute Styling Engine' on Facebook Share 'Tutorial: Creating a Button with Jakute Styling Engine' on Google Bookmarks Share 'Tutorial: Creating a Button with Jakute Styling Engine' on Twitter

Article | Published: 18. Januar 2011 | Changed: 15. Dezember 2014 | Category: Jakute | 1 Comment
  1. About Jakute Styling Engine (JCSS)
  2. About this tutorial
  3. Button requirements
  4. Step 1: Creating a static button
    1. Anatomy of a button
    2. The label
    3. The background skin
    4. The button
    5. Testing the button
  5. Step 2: Adding styles
    1. Setup JCSS
    2. Adding label styles
    3. Adding background skin styles
    4. Adding button styles
    5. Removing the public draw() methods
    6. Final version of step 2
  6. Step 3: Implementing the states
    1. Setting default styles for child components
    2. Preventing child components from being styled accidentally
  7. The final code and swf
  8. Comments (1)
  9. Leave a Comment

About Jakute Styling Engine (JCSS)

The Jakute Styling Engine (JCSS) is a CSS framework for Flash. Its features are comparable to those of HTML/CSS while being focused on the specifics of Flash and custom applications.

About this tutorial

In this tutorial we are going to create a button component. Not a simple button. We create a full-fledged push button with dynamic label. Here is the final button:

ButtonExampleFinal.swf

If you want to see the final code first, jump to the last section of this tutorial.

The tutorial focuses on the following techniques:

  • Creating nested components
  • Implementing states
  • Declaring contextual styles
  • Declaring stateful styles
  • Preventing styles from being overridden

The tutorial covers the following concepts:

  • Component life cycle
  • Component states
  • Default styles

Note, all examples and example steps are committed to the Jakute Git repository.

Button requirements

A button is a core component in any application. There is no ui element that is so often reused like the button. The button should therefore be highly customizable. These are our requirements to the button:

  • Setting background and border
  • Optional specification of a button label
  • Setting the label’s color and size
  • Interactive look and feel by resonding to mouse events such as over or down
  • Dispatching of a click event
  • Possibility to customize all properties using styles
  • Support of runtime style changes

You see, there is nothing special with that button. But, we will create it on our own and we use styles to configure the button’s appearance.

Step 1: Creating a static button

Anatomy of a button

There are different ways to implement a button. In our tutorial we simply develop the button as a mouse sensitive container that contains a background shape (the skin) and a label.

There are some good reasons to encapsulate button and label. The containing button is the only participant the user gets in contact with. For example, the labels’s text is set to the button and not to the nested label. The button instance is also responsible to provide and manage interactivity such as mouse or keyboard events.

We develop background and label independently and reusable. This requires that neither of them references the button.

For simplicity’s sake, we do not allow the button’s background skin to be replaced.

And here comes our first and so far rather static button version.

The label

The Label hosts an inner Flash TextField instance. I provides for performance reasons a draw() method that is called externally. The text format properties are currently hard coded into the file. To enable the button to align according to the text dimensions, the actual label size can be retrieved by calling the innerWidth() and innerHeight() methods.

Label.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package buttontutorial.step1 {

  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;

  public class Label extends Sprite {
    // properties
    private var _text : String = "";
    // children
    private var _textField : TextField;

    public function Label() {
      _textField = new TextField();
      _textField.autoSize = TextFieldAutoSize.LEFT;
      _textField.selectable = false;
      addChild(_textField);
    }

    public function draw() : void {
      // text field
      _textField.textColor = 0x333333;
      _textField.text = _text;

      // text format
      var textFormat : TextFormat = new TextFormat();
      textFormat.font = "_sans";
      textFormat.size = 11;
      _textField.setTextFormat(textFormat);
    }

    public function set text(text : String) : void {
      _text = text;
    }

    public function get innerWidth() : uint {
      return _textField.width;
    }

    public function get innerHeight() : uint {
      return _textField.height;
    }
  }
}

The background skin

The ButtonSkin needs to get a size before its drawing. The skin cannot be smaller than 20x20px. Again, here is the component drawing commanded externally via the public draw() method. Within this function the border and background are drawn using hard coded gradient colors, border radius and border size.

ButtonSkin.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package buttontutorial.step1 {

  import flash.display.GradientType;
  import flash.display.Sprite;
  import flash.geom.Matrix;

  public class ButtonSkin extends Sprite {
    // properties
    private var _w : uint = 100;
    private var _h : uint = 100;

    public function ButtonSkin() {
    }

    public function draw() : void {
      // gradient
      var matrix : Matrix = new Matrix();
      matrix.createGradientBox(_w, _h, Math.PI / 180 * 45, 0, 0);

      with (graphics) {
        clear();

        // border
        lineStyle(2);
        lineGradientStyle(GradientType.LINEAR, [0xCCCCCC, 0x666666], [1, 1], [0, 255], matrix);

        // background
        beginGradientFill(GradientType.LINEAR, [0xF7F7F7, 0xC7C7C7], [1, 1], [0, 255], matrix);
        drawRoundRect(0, 0, _w, _h, 10);
      }
    }

    public function setSize(w : uint, h : uint) : void {
      _w = w > 20 ? w : 20;
      _h = h > 20 ? h : 20;
    }
  }
}

The button

The button seems to be a bit more comprehensive. Skin and label are created within the constructor. There is a public draw() method that is to call in order to update the button after some properties are set. The current button state is managed using the private _over and _down properties. The state getter returns a literal state description for debugging purposes. We use it in our example application to find everything working well.

Our button currently supports only the specification of a label. The button is supposed to resize according to the label dimensioins.

Button.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package buttontutorial.step1 {

  import flash.display.Sprite;
  import flash.events.Event;
  import flash.events.MouseEvent;

  public class Button extends Sprite {
    // event constants
    public static const EVENT_OVER : String = "button_over";
    public static const EVENT_OUT : String = "button_out";
    public static const EVENT_DOWN : String = "button_down";
    public static const EVENT_UP : String = "button_up";
    public static const EVENT_CLICK : String = "button_click";
    public static const EVENT_STATE_CHANGE : String = "button_state_change";
    // properties
    private var _text : String;
    // internal
    private var _over : Boolean;
    private var _down : Boolean;
    // children
    private var _skin : ButtonSkin;
    private var _label : Label;

    public function Button() {
      mouseChildren = false;

      // background
      _skin = new ButtonSkin();
      addChild(_skin);

      // label
      _label = new Label();
      _label.x = 8;
      _label.y = 4;
      addChild(_label);

      addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
      addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
      addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
    }

    public function draw() : void {
      _label.text = _text;
      _label.draw();

      _skin.setSize(_label.innerWidth + 16, _label.innerHeight + 8);
      _skin.draw();
    }

    public function set text(text : String) : void {
      _text = text;
    }

    public function get state() : String {
      var state : String = "";
      if (_over) state += ":over";
      if (_down) state += ":down";
      return state;
    }

    private function mouseOverHandler(event : MouseEvent) : void {
      _over = true;
      dispatchStateChange();
    }

    private function mouseOutHandler(event : MouseEvent) : void {
      _over = false;
      dispatchStateChange();
    }

    private function mouseDownHandler(event : MouseEvent) : void {
      _down = true;
      stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      dispatchStateChange();
    }

    private function mouseUpHandler(event : MouseEvent) : void {
      stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      _down = false;
      dispatchStateChange();
      if (_over) dispatchEvent(new Event(EVENT_CLICK, true));
    }

    private function dispatchStateChange() : void {
      dispatchEvent(new Event(EVENT_STATE_CHANGE, true));
    }
  }
}

Testing the button

And here comes our main application. The “ControlPanel” is not from interest for this tutorial. It provides control elements that let us test and check the button component. You can find the control panel source code in the Git repository. The panel incorporates the AS DataProvider Controls library.

ButtonExample.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package buttontutorial.step1 {

  import buttontutorial.step1.panel.ControlPanel;
  import flash.display.Sprite;

  public class ButtonExample extends Sprite {
    private var _button : Button;

    public function ButtonExample() {
      // button
      _button = new Button();
      _button.text = "Click";
      _button.draw();
      addChild(_button);

      // button control panel
      addChild(new ControlPanel(_button));
    }
  }
}
ButtonExample.swf

Looks already pretty good, doesn’t it? Let’s go to adding styles.

Step 2: Adding styles

We know from the Hello World tutorial that we need to setup JCSS for our application and to create an JCSS adapter for each of our components to be styled. That’s what we are going to do here.

There are 3 ways to implement a component adapter. In the Hello World tutorial we have used the default adapter configuration. Here we take the simpler approach and let our components inherit from JCSS_Sprite. We can do so since our components do not have a custom super class. In that case we better would use custom adapter configuration.

1
2
3
4
5
public class Label extends JCSS_Sprite {
...
public class Button extends JCSS_Sprite {
...
public class ButtonSkin extends JCSS_Sprite {

Setup JCSS

There is no special requirement to setup JCSS. We only need to import the library in our project.

Adding label styles

Setting name and defining styles

By the requirements above we need to define styles for the label color and size. To enable the label to be selected in style rules, we also need to assign a JCSS component name. We here simply use “Label”, but we could take any other name.

1
2
3
4
5
6
7
8
public function Label() {
  jcss_cssName = "Label";
  jcss_defineStyle("color", 0x333333, JCSS.FORMAT_COLOR);
  jcss_defineStyle("size", 11, JCSS.FORMAT_NUMBER);
 
  _textField = new TextField();
  _textField.autoSize = TextFieldAutoSize.LEFT;
  ...

Switching the code to use styles

Within the source code we replace the static declarations with the appropriate style getter invocations. To allow the draw() method to be executed only when styles are already present, we put an evaluation of the jcss_initialized property in the first line.

1
2
3
4
5
6
7
8
9
10
11
12
13
public function draw() : void {
  if (!jcss_initialized) return;
     
  // text field
  _textField.textColor = jcss_getStyle("color");
  _textField.text = _text;

  // text format
  var textFormat : TextFormat = new TextFormat();
  textFormat.font = "_sans";
  textFormat.size = jcss_getStyle("size");
  _textField.setTextFormat(textFormat);
}

Catching the component life cycle events

In the last label configuration step we implement event handlers for the JCSS component events “component initialized” and “styles changed”. Otherwise the label never would get notice about styles existent or updated.

1
2
3
4
5
6
7
override protected function jcss_onStylesInitialized(styles : Object) : void {
  draw();
}

override protected function jcss_onStylesChanged(styles : Object) : void {
  draw();
}

Adding background skin styles

The styles we would like to configure externally are:

  • background color
  • border color
  • border size
  • border radius

Computed color values for gradient and states

To get not an unlimited list of style properties (topLeftBackgroundColor, bottomRightBackgroundColor, …), we will create our gradients from a single color value by using an algorithm which we exclude in a new ColorUtil class.

To achieve the push effect, our button should simply invert the gradient colors while being in the down state. In the over state the background color should appear slightly lighter.

Setting name and defining styles

We want the background to be managed by JCSS using the name “ButtonSkin”. The styles defined are then:

1
2
3
4
5
6
7
8
9
public function ButtonSkin() {
  jcss_cssName = "ButtonSkin";
  jcss_defineStyle("gradientDirection", "bright_to_dark", JCSS.FORMAT_STRING); / for :down
  jcss_defineStyle("backgroundColor", 0xDFDFDF, JCSS.FORMAT_COLOR);
  jcss_defineStyle("backgroundColorOffset", 0, JCSS.FORMAT_NUMBER); // for :over
  jcss_defineStyle("borderColor", 0x999999, JCSS.FORMAT_COLOR);
  jcss_defineStyle("borderSize", 2, JCSS.FORMAT_NUMBER);
  jcss_defineStyle("borderRadius", 10, JCSS.FORMAT_NUMBER);
}

Switching the code to use styles

We also replace the static properties with style values. To allow the draw() method to be executed only when styles are already present, we put an evaluation of the jcss_initialized property in the first line.

1
2
3
4
5
6
7
8
9
10
11
public function draw() : void {
  if (!jcss_initialized) return;
  ...
 
  // border
  var borderSize : uint = jcss_getStyle("borderSize");
  var borderColor : uint = jcss_getStyle("borderColor");

  var backgroundColor : uint = jcss_getStyle("backgroundColor");
  var offset : int = jcss_getStyle("backgroundColorOffset");
  ...

Catching the component life cycle events

Finally we have to implement the style event handlers, and we just copy the code from our label.

1
2
3
4
5
6
7
override protected function jcss_onStylesInitialized(styles : Object) : void {
  draw();
}

override protected function jcss_onStylesChanged(styles : Object) : void {
  draw();
}

Adding button styles

Our button does not have any own styles. We therefore only assign a component name and skip the definition of button styles.

1
2
3
4
5
6
7
8
public function Button() {
  jcss_cssName = "Button";
 
  mouseChildren = false;
 
  // background
  _skin = new ButtonSkin();
  ...
ButtonExampleStep2.swf

The styles seem to apply, but the initial size of the button is not satisfying. What happended?

Removing the public draw() methods

In step 1 of this tutorial we have defined a public draw() method to each of our components. Later we have updated label and background to skip the drawing for the case the component has not been initialized yet. Our problem is the intial call of the button’s draw() method. Neither label nor button are initialized at that point.

1
2
3
4
5
6
7
public function draw() : void {
  _label.text = _text;
  _label.draw(); // <-- skipped by the label

  _skin.setSize(_label.innerWidth + 16, _label.innerHeight + 8); // <-- inner size is 0
  _skin.draw(); // <-- skipped by the skin
}

However we need the label’s dimension to bring the skin in a right state. We help us by letting the label dispatch a size changed event which we pass to the background skin.

In Label.as:

1
2
3
4
5
6
7
public function draw() : void {
  ...
  textFormat.size = jcss_getStyle("size");
  _textField.setTextFormat(textFormat);

  dispatchEvent(new Event(EVENT_CHANGE, true));
}

And in Button.as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function Button() {
  jcss_cssName = "Button";
  ...
 
  _label.y = 4;
  _label.addEventListener(Label.EVENT_CHANGE, labelChangeHandler);
  addChild(_label);
  ...
}

...

private function labelChangeHandler(event : Label) : void {
  _skin.setSize(_label.innerWidth + 16, _label.innerHeight + 8);
  _skin.draw();
}
ButtonExampleStep2b.swf

JCSS and the component life cycle

As we already know, we can use absolutely independent components together with JCSS. We only have to provide an adapter for each component. There are 3 ways to create the adapter. In this tutorial we have used the most convenient approach by extending the ui base class JCSS_Sprite.

JCSS communicates with its registered components in a very reduced manner. There are exactly three situations when JCSS needs to notify a component:

  1. The component has been registered in JCSS. This event is only important to components that are registered by type and not by instance (what we did here). A component is notified only once of the “component registered” event.
  2. The styles have been computed initially and are now ready for use. The component is supposed to draw its initial state. A component is notified only once of the “component initialized” event.
  3. Styles have changed. The component should update. A component may receive the “styles changed” notification multiple times.

Following the succession of this notifications, a JCSS component provides an implicit life cycle. In any case we need to implement a handler for the component initialization event. If the component should update at runtime, we also need to respond to the “styles changed” event.

Our current button component uses public draw() methods. These methods conflict with the concept of an internal component life cycle. We never know if an instance has been already initialized or not. In consequence, we will remove all the public drawings and move the update control to the particular property getter.

In ButtonSkin.as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function setSize(w : uint, h : uint) : void {
  _w = w > 20 ? w : 20;
  _h = h > 20 ? h : 20;
  draw(); // <-- added
}

...

private function draw() : void { // <-- no private
  if (!jcss_initialized) return;
 
  with (graphics) {
    clear();
    ...

Final version of step 2

We have finished step 2 of our tutorial. We have modified our independent components from step 1 to use styles instead of hard coded values. Now we would like to see everything in action. We will create a global style sheet and setup a number of styles that should apply to our button.

ButtonExample.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package buttontutorial.step2 {

  import buttontutorial.step2.panel.ControlPanel;

  import com.sibirjak.jakute.JCSS;
  import com.sibirjak.jakute.JCSS_Sprite;

  import flash.display.Sprite;

  public class ButtonExample extends Sprite {
    private var _button : Button;

    public function ButtonExample() {
      // JCSS
      JCSS_Sprite.jcss = new JCSS();
      JCSS_Sprite.jcss.setStyleSheet(CSS.styles);

      // button
      _button = new Button();
      _button.text = "Click";
      addChild(_button);

      // button control panel
      addChild(new ControlPanel(_button));
    }
  }
}

internal class CSS {
  public static var styles : String = <styles><![CDATA[
    Label {
      color: #FFFFFF;
      size: 20;
    }

    ButtonSkin {
      backgroundColor: #CC0000;
      borderColor: #990000;
      borderSize: 4;
      borderRadius: 15;
    }
  ]]></styles>.toString();
}

I have added some controls to the ControlPanel.as, so we can right away test the runtime capabilities of our button.

ButtonExampleStep2c.swf

The final sources of step 2 can be found in the Git repository.

That’s already a good button, and it kindly responses to our runtime updates. However there is still no visual interactivity effect. In the last step of our tutorial we add the states to the button.

Step 3: Implementing the states

The button takes up three different states that we want to reference in our later style sheet.

State Effect
default no effect
over brighter background
over and down reverse gradient direction for border and background

To tell JCSS about the current state we need to modify our button code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private function mouseOverHandler(event : MouseEvent) : void {
  _over = true;
  jcss_setState("over", "true"); // <-- added
  dispatchStateChange();
}

private function mouseOutHandler(event : MouseEvent) : void {
  _over = false;
  jcss_setState("over", "false"); // <-- added
  dispatchStateChange();
}

private function mouseDownHandler(event : MouseEvent) : void {
  _down = true;
  jcss_setState("down", "true"); // <-- added
  stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
  dispatchStateChange();
}

private function mouseUpHandler(event : MouseEvent) : void {
  stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
  _down = false;
  jcss_setState("down", "false"); // <-- added
  dispatchStateChange();
  if (_over) dispatchEvent(new Event(EVENT_CLICK, true));
}

Whenever now the button gets a new state, it notifies JCSS which on his part processes all necessary operations to find and update those components for that a stateful style rule applies.
We only have to declare our stateful style rules and have the button almost finished. To do so, we move the style declarations from our main class to the button and append two new stateful rules. This way we get a clean default configuration for the button and its children. You will notice that I have also added a label shifting style to the button.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public function Button() {
  jcss_cssName = "Button";
  jcss_defineStyle("labelOffset", 0, JCSS.FORMAT_NUMBER);
 
  jcss_setStyleSheet(<styles><![CDATA[
    Label { /* <-- moved from ButtonExample.as */
      color: #FFFFFF;
      size: 20;
    }

    ButtonSkin { /* <-- moved from ButtonExample.as */
      backgroundColor: #CC0000;
      borderColor: #990000;
      borderSize: 4;
      borderRadius: 15;
    }
   
    :over ButtonSkin { /* <-- added */
      backgroundColorOffset: 20
    }

    :over:down ButtonSkin { /* <-- added */
      gradientDirection: dark_to_bright
    }
       
    :over:down { /* <-- added */
      labelOffset: 1;
    }
  ]]></styles>.toString());
 
  mouseChildren = false;
  ...

And thats the intermediate result. I have removed the gradient direction control element and renamed the background color offset to “:over offset”. The states work as expected.

ButtonExampleStep3.swf

Here we could actually finish the tutorial. But there are two important aspects left when styling components.

  1. We have assigned the stateful style rules to the button. Can we override these styles globally?
  2. When using the label or the button skin in other contextes, how we can style them separately without losing the button states?

Setting default styles for child components

Lets update our main class and declare some global style rules. The button should now appear initially in blue, the label should be small.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package buttontutorial.step3 {
  import com.sibirjak.jakute.JCSS;
  import flash.display.Sprite;

  public class ButtonExample2 extends Sprite {
   
    private var _button : Button;

    public function ButtonExample2() {
      // JCSS
      JCSS.getInstance().setStyleSheet(CSS.styles);
     
      // button
      _button = new Button();
      _button.text = "Click";
      addChild(_button);
    }

  }
}

internal class CSS {
  public static var styles : String = <styles><![CDATA[
    Label {
      color: #CCCCFF;
      size: 10;
    }

    ButtonSkin {
      backgroundColor: #6666CC;
      borderColor: #0000CC;
      borderSize: 2;
      borderRadius: 10;
    }
  ]]></styles>.toString();
}

Compiling the class we find, that the global rules do not apply. The button still remains red and big.

ButtonExampleStep3b.swf

The reason is that styles declared to component instances have precedence over styles declared to ancestors of these components and over global styles. The same behavior shows HTML/CSS where inline styles have higher priority than inherited styles.
To anyhow enable the button to be globally customized, we need to modify the style rules in the button component and add a default flag to each style declaration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function Button() {
  jcss_cssName = "Button";
 
  jcss_setStyleSheet(<styles><![CDATA[
    Label {
      color: #FFFFFF default;
      size: 20 default;
    }

    ButtonSkin {
      backgroundColor: #CC0000 default;
      borderColor: #990000 default;
      borderSize: 4 default;
      borderRadius: 15 default;
    }
   
    :over ButtonSkin {
      backgroundColorOffset: 20 default;
    }

    :over:down ButtonSkin {
      gradientDirection: dark_to_bright default;
    }

    :over:down {
      labelOffset: 1;
    }
  ]]></styles>.toString());
 
  mouseChildren = false;
  ...

Now global or ancestor styles can override these button component default styles.

ButtonExampleStep3c.swf

Preventing child components from being styled accidentally

The button skin’s color and gradient direction depend on the button state because of a stateful style declared within the button constructor. What happens with our states when we globally setup some different styles for the button skin?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal class CSS {
  public static var styles : String = <styles><![CDATA[
    ButtonSkin {
      gradientDirection: bright_to_dark;

      backgroundColor: #CCCCCC;
      backgroundColorOffset: 0

      borderColor: #888888;
      borderSize: 4;
      borderRadius: 15;
    }
  ]]></styles>.toString();
}
ButtonExampleStep3d.swf

The button does not longer use the styles from our stateful style rules. But this sounds right then we have declared the style in those rules as default styles. It is indeed the expected behavior. All the buttons rules are default rules that we allow to override. To prevent the button skin styles from being overridden by accident we may flag the button to enclose its children. Any rule that does not contain a selector for our button would be then ignored.

Example: We declare a stateful rule for the button label within the button’s constructor.

1
2
3
4
5
6
7
8
9
10
11
in Button.as:

:over Label {
  underline: true default;
}

in global style sheet:

Label {
  underline: false;
}

The label here never would be underlined. When we now flag the button to enclose its children, we are safe to write any label styles in our global style sheet. If we really would update the buttons label styles, we would need to include the button in a style rule selector.

In Button.as:

1
2
3
4
5
public function Button() {
  jcss_cssName = "Button";
  jcss_encloseChildren(); // <-- added
  jcss_defineStyle("labelOffset", 0, JCSS.FORMAT_NUMBER);
  ...

In the global style sheet:

1
2
3
4
5
6
7
Label { /* does not apply to the button label */
  underline: false;
}

Button Label { /* applies to the button label since Button included in selector */
  underline: false;
}

That’s all for our tutorial. We have successfully created a styleable button with the Jakute Styling Engine. Wasn’t it relatively easy? At this point you know all the techniques and concepts of JCSS and may start creating rich CSS applications with this framework.

The final code and swf

Label.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package buttontutorial.step3 {

  import com.sibirjak.jakute.JCSS_Sprite;
  import com.sibirjak.jakute.constants.JCSS_StyleValueFormat;
  import com.sibirjak.jakute.events.JCSS_ChangeEvent;

  import flash.events.Event;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;

  public class Label extends JCSS_Sprite {
    // event
    public static const EVENT_CHANGE : String = "label_change";
    // children
    private var _textField : TextField;

    public function Label() {
      jcss_cssName = "Label";
      jcss_defineStyle("color", 0x333333, JCSS_StyleValueFormat.FORMAT_HTML_COLOR);
      jcss_defineStyle("size", 11, JCSS_StyleValueFormat.FORMAT_NUMBER);

      _textField = new TextField();
      _textField.autoSize = TextFieldAutoSize.LEFT;
      _textField.selectable = false;
      addChild(_textField);
    }

    public function set text(text : String) : void {
      _textField.text = text;
      draw();
    }

    public function get innerWidth() : uint {
      return _textField.width;
    }

    public function get innerHeight() : uint {
      return _textField.height;
    }

    override protected function jcss_onStylesInitialized() : void {
      draw();
    }

    override protected function jcss_onStylesChanged(changeEvent : JCSS_ChangeEvent) : void {
      draw();
    }

    private function draw() : void {
      if (!jcss_initialized) return;

      // text field
      _textField.textColor = jcss_getStyle("color");

      // text format
      var textFormat : TextFormat = new TextFormat();
      textFormat.font = "_sans";
      textFormat.size = jcss_getStyle("size");
      _textField.setTextFormat(textFormat);

      dispatchEvent(new Event(EVENT_CHANGE, true));
    }
  }
}
ButtonSkin.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package buttontutorial.step3 {

  import com.sibirjak.jakute.JCSS_Sprite;
  import com.sibirjak.jakute.constants.JCSS_StyleValueFormat;
  import com.sibirjak.jakute.events.JCSS_ChangeEvent;
  import common.ColorUtil;
  import flash.display.GradientType;
  import flash.geom.Matrix;


  public class ButtonSkin extends JCSS_Sprite {
    // properties
    private var _w : uint = 100;
    private var _h : uint = 100;

    public function ButtonSkin() {
      jcss_cssName = "ButtonSkin";
      jcss_defineStyle("gradientDirection", "bright_to_dark", JCSS_StyleValueFormat.FORMAT_STRING); // :down
      jcss_defineStyle("backgroundColor", 0xDFDFDF, JCSS_StyleValueFormat.FORMAT_HTML_COLOR);
      jcss_defineStyle("backgroundColorOffset", 0, JCSS_StyleValueFormat.FORMAT_NUMBER); // :over
      jcss_defineStyle("borderColor", 0x999999, JCSS_StyleValueFormat.FORMAT_HTML_COLOR);
      jcss_defineStyle("borderSize", 2, JCSS_StyleValueFormat.FORMAT_NUMBER);
      jcss_defineStyle("borderRadius", 10, JCSS_StyleValueFormat.FORMAT_NUMBER);
    }

    public function setSize(w : uint, h : uint) : void {
      _w = w > 20 ? w : 20;
      _h = h > 20 ? h : 20;
      draw();
    }

    override protected function jcss_onStylesInitialized() : void {
      draw();
    }

    override protected function jcss_onStylesChanged(changeEvent : JCSS_ChangeEvent) : void {
      draw();
    }

    private function draw() : void {
      if (!jcss_initialized) return;

      // gradient
      var matrix : Matrix = new Matrix();
      matrix.createGradientBox(_w, _h, Math.PI / 180 * 45, 0, 0);
      var gradientDirection : String = jcss_getStyle("gradientDirection");
      var gradient : Array;

      // border
      var borderSize : uint = jcss_getStyle("borderSize");
      var borderColor : uint = jcss_getStyle("borderColor");

      var backgroundColor : uint = jcss_getStyle("backgroundColor");
      var offset : int = jcss_getStyle("backgroundColorOffset");
      if (offset) backgroundColor = ColorUtil.lightenBy(backgroundColor, offset);

      with (graphics) {
        clear();

        // border
        if (borderSize) {
          lineStyle(borderSize);
          gradient = ColorUtil.getGradient(borderColor, 60, gradientDirection);
          lineGradientStyle(GradientType.LINEAR, gradient, [1, 1], [0, 255], matrix);
        }

        // background
        gradient = ColorUtil.getGradient(backgroundColor, 60, gradientDirection);
        beginGradientFill(GradientType.LINEAR, gradient, [1, 1], [0, 255], matrix);
        drawRoundRect(0, 0, _w, _h, jcss_getStyle("borderRadius"));
      }
    }
  }
}
Button.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package buttontutorial.step3 {

  import com.sibirjak.jakute.JCSS_Sprite;
  import com.sibirjak.jakute.constants.JCSS_StyleValueFormat;
  import com.sibirjak.jakute.events.JCSS_ChangeEvent;

  import flash.events.Event;
  import flash.events.MouseEvent;

  public class Button extends JCSS_Sprite {
    // event constants
    public static const EVENT_OVER : String = "button_over";
    public static const EVENT_OUT : String = "button_out";
    public static const EVENT_DOWN : String = "button_down";
    public static const EVENT_UP : String = "button_up";
    public static const EVENT_CLICK : String = "button_click";
    public static const EVENT_STATE_CHANGE : String = "button_state_change";
    // internal
    private var _over : Boolean;
    private var _down : Boolean;
    // children
    private var _skin : ButtonSkin;
    private var _label : Label;

    public function Button() {
      jcss_cssName = "Button";
      jcss_encloseChildren();
      jcss_defineStyle("labelOffset", 0, JCSS_StyleValueFormat.FORMAT_NUMBER);

      jcss_setStyleSheet(<styles><![CDATA[
        Label {
          color: #FFFFFF default;
          size: 20 default;
        }
   
        ButtonSkin {
          backgroundColor: #CC0000 default;
          borderColor: #990000 default;
          borderSize: 4 default;
          borderRadius: 15 default;
        }
       
        :over ButtonSkin {
          backgroundColorOffset: 20 default;
        }

        :over:down ButtonSkin {
          gradientDirection: dark_to_bright default;
        }
       
        :over:down {
          labelOffset: 1 default;
        }
      ]]></styles>.toString());

      mouseChildren = false;

      // background
      _skin = new ButtonSkin();
      addChild(_skin);

      // label
      _label = new Label();
      _label.x = 8;
      _label.y = 4;
      _label.addEventListener(Label.EVENT_CHANGE, labelChangeHandler);
      addChild(_label);

      addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
      addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
      addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
    }

    public function set text(text : String) : void {
      _label.text = text;
    }

    public function get state() : String {
      var state : String = "";
      if (_over) state += ":over";
      if (_down) state += ":down";
      return state;
    }

    override protected function jcss_onStylesChanged(changeEvent : JCSS_ChangeEvent) : void {
      var offset : uint = jcss_getStyle("labelOffset");
      _label.x = 8 + offset;
      _label.y = 4 + offset;
    }

    private function mouseOverHandler(event : MouseEvent) : void {
      _over = true;
      jcss_setState("over", "true");
      dispatchStateChange();
    }

    private function mouseOutHandler(event : MouseEvent) : void {
      _over = false;
      jcss_setState("over", "false");
      dispatchStateChange();
    }

    private function mouseDownHandler(event : MouseEvent) : void {
      _down = true;
      jcss_setState("down", "true");
      stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      dispatchStateChange();
    }

    private function mouseUpHandler(event : MouseEvent) : void {
      stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      _down = false;
      jcss_setState("down", "false");
      dispatchStateChange();
      if (_over) dispatchEvent(new Event(EVENT_CLICK, true));
    }

    private function dispatchStateChange() : void {
      dispatchEvent(new Event(EVENT_STATE_CHANGE, true));
    }

    private function labelChangeHandler(event : Event) : void {
      _skin.setSize(_label.innerWidth + 16, _label.innerHeight + 8);
    }
  }
}
ButtonExample.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package buttontutorial.step3 {

  import buttontutorial.step3.panel.ControlPanel;

  import com.sibirjak.jakute.JCSS;
  import com.sibirjak.jakute.JCSS_Sprite;

  import flash.display.Sprite;

  public class ButtonExample extends Sprite {
    private var _button : Button;

    public function ButtonExample() {
      // JCSS
      JCSS_Sprite.jcss = new JCSS();

      // button
      _button = new Button();
      _button.text = "Click";
      addChild(_button);

      // button control panel
      addChild(new ControlPanel(_button));
    }
  }
}
ButtonExampleFinal.swf


1 Comment

  1. Jakute Flash styling engine - Flashforum

    18. Januar 2011

    [...] Tutorial: Creating a Button with Jakute Styling Engine __________________ Jakute Styling Engine AS3Commons Collections ActionScript Data Provider [...]

Leave a Comment

You have a question or have experienced an issue? Please post it in the forum: http://sibirjak.tenderapp.com/ in order to make the discussion available at a more central place.