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:
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.
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.
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.
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.
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)); } } } |
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(); ... |
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(); } |
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:
- 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.
- 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.
- 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.
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.
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.
Here we could actually finish the tutorial. But there are two important aspects left when styling components.
- We have assigned the stateful style rules to the button. Can we override these styles globally?
- 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.
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.
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(); } |
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
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)); } } } |
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")); } } } } |
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); } } } |
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)); } } } |

RSS





1 Comment
Jakute Flash styling engine - Flashforum
[...] Tutorial: Creating a Button with Jakute Styling Engine __________________ Jakute Styling Engine AS3Commons Collections ActionScript Data Provider [...]