Friday, December 11, 2009

Creating Custom Caspian Controls

Creating custom controls in JavaFX is not difficult. Creating custom controls that fit nicely with the default Caspian theme, with its pervasive use of gradients and animation, is a little trickier. This is only because the utility classes and methods that make it simple lie buried within com.sun packages that are not publicly documented. A little digging reveals a few gems that can be used to make your controls fit right in with the core JavaFX controls.
If you are observant, you will not fail to notice that this post also serves to emphasize some of the many reasons that I love, and love working with, Caspian.
Warning #1 This post is not an introduction to writing controls in JavaFX. I will not cover the basics of the Control - Skin - Behavior classes. A quick and simple tutorial on this topic can be found here.
Warning #2 All of the information in this post applies only to JavaFX 1.2.1. One of the dangers of using non-public classes is that they can change significantly from version to version (and they certainly will for JavaFX 1.3). If there is interest, I will update this information after JavaFX 1.3 is released.
Apology #1 I discovered the information I'm about to present here while writing my own controls. None of this should be considered official Caspian documentation since I do not work for Sun and I do not have access to their source code (but hopefully I will when they release the source someday). Everything here is accurate as far as I can tell, but I apologize if I have any of the details wrong. Hopefully someone from Sun will read this and let me know if I've made any mistakes.
Knock-knock Joke #1 (of 100) Knock, knock. Who's there? Yewben. Yewben who? Yewben warned, now on with the post!

Caspian 101 - Colors and Gradients

Caspian controls in JavaFX 1.2.1 all make use of a mixin class called Colorable. This mixin provides the ubiquitous base and accent properties that can be used to style the controls. The base property specifies the base color of the control while the accent property specifies the accent color on those controls that support it. ListView, for example, uses the accent property to specify the color with which it draws the selection highlight in the list. Usage of the Colorable mixin makes Caspian controls incredibly easy to style.
Two other colors are generated from the base color: the over color, which is used when the mouse pointer is over the control, and the pressed color, used when a control is (you guessed it) pressed. Caspian also generates five Paint objects from the base color: the body paint, the text paint, the border paint, the highlight line paint, and the shadow highlight paint. The screenshot below shows an application that displays these various colors and paints. It has been annotated with lines that show where they are used on a Caspian button (click the image to see a larger version).
If you launch the application, you can see that the button is a live control that has been scaled up to four times its normal size. If you hover the mouse cursor over the button or click on it, you will see the control's use of the over and pressed colors as well. Caspian is remarkably clever about generating nice looking controls from a single color.


The Caspian class in the com.sun.javafx.scene.control.caspian package provides easy access to these color-generating capabilities via module level functions:
  • Caspian.getOverColor( base: Color )
  • Caspian.getPressedColor( base: Color )
  • Caspian.getBodyPaint( base: Color )
  • Caspian.getTextPaint( base: Color )
  • Caspian.getBorderPaint( base: Color )
  • Caspian.getHighlightLinePaint( base: Color )
  • Caspian.getShadowHighlightPaint( base: Color )
Therefore, the first step in creating a control that fits in with the native Caspian controls is to use the Colorable mixin class and use it's base property to generate the other colors for your control.
public class MyControlSkin extends Skin, Colorable {

def bodyPaint = bind Caspian.getBodyPaint( base );
def overPaint = bind Caspian.getBodyPaint( Caspian.getOverColor( base ) );
def pressedPaint = bind Caspian.getBodyPaint( Caspian.getPressedColor( base ) );

var mousePressed = false;

def background = Rectangle {
   fill: bind {
       if (mousePressed) {
           pressedPaint
       } else if (background.hover) {
           overPaint
       } else {
           bodyPaint
       }
   }
   stroke: bind Caspian.getBorderPaint( base )
   blocksMouse: true
   onMousePressed: function( me: MouseEvent ) {
       mousePressed = true;
       // ... may also need to notify the Behavior ...
   }
   onMouseReleased: function( me: MouseEvent ) {
       mousePressed = false;
       // ... may also need to notify the Behavior ...
   }
}

// ... I'm too modest to show more skin ...
}
   
With just a few lines of code you get nice Caspian gradients for your control plus your custom control will respond to the same base style property as any core JavaFX control. In this simple example I only changed the body paint in response to mouse events. The core JavaFX controls also tend to update their border and highlight colors in response to hover and pressed events. You can see this when you run the Caspian Colors application above and interact with the button.

Caspian 102 - Animated State Transitions

Getting the colors and gradients right is only half the battle. Caspian also uses nice animated transitions when the control changes its state. For instance, when a button is disabled it doesn't just flip to a disabled state, it animates the transition with a smooth fade of the button's opacity. Similar animations occur when a control gets enabled, gains or loses focus, gets hovered over, or gets clicked. Caspian provides a smooth and fluid user experience.
This is something I want to support in my own controls since I am a subscriber to the Chet Haas school of thought regarding the positive role that good, subtle animation cues can play in a user interface. Animating all of those state transitions could become a real chore, so it's a good thing that you can cheat and take advantage of the hard work already done by the team at Sun. Allow me to introduce you to the States, State, and StateTransition classes. All of these little beauties are found in the com.sun.javafx.animation.transition package.
The State class allows you to define each state of your control. The class has four properties of interest:
idThe name of the state.
defaultState Should be set to true if this state is the one in which your control starts.
active Should be set to true when this state is active. Normally this will be bound to some property of the control. For example, when defining the "hover" state, you would bind the state's active property to the skin node's hover property.
priority An integer value that is used to establish precedence when multiple states are active at the same time.
The StateTransition class derives from Transition and allows you to define the animation that will occur when your control moves from one state to another. The properties of interest in this class are id, fromState, toState, and animation. The fromState property is a string that allows you to specify the id of the state you are transitioning from. Likewise for the toState property.
And finally, the States class keeps track of your control's states and manages the transitions between them. The following code shows an example of the states and transitions for a custom control.
def FAST_TRANSITION = 250ms;
def SLOW_TRANSITION = 500ms;

public class MyControlSkin extends Skin, Colorable {
override var color = base;  // color is another property defined in Colorable

def over = bind Caspian.getOverColor( base );
def pressed = bind Caspian.getPressedColor( base );

var mousePressed = false;

def states = States {
   states: [
       State { id: "disabled", active: bind control.disabled }
       State { id: "armed",    active: bind mousePressed }
       State { id: "hover",    active: bind control.hover }
       State { id: "enabled",  active: bind not control.disabled, defaultState: true }
   ]
   transitions: [
       StateTransition {
           id: "Enter-Enabled"
           toState: "enabled"
           animation: ParallelTransition {
               content: [
                   ColorTransition {
                       colorable: this
                       toValue: base
                       duration: SLOW_TRANSITION
                   }
                   FadeTransition {
                       node: bind node
                       toValue: 1.0
                       duration: FAST_TRANSITION
                   }
               ]
           }
       }
       StateTransition {
           id: "Enter-Hover"
           toState: "hover"
           animation: ColorTransition {
               colorable: this
               toValue: over
               duration: FAST_TRANSITION
           }
       }
       StateTransition {
           id: "Enter-Armed"
           toState: "armed"
           animation: ColorTransition {
               colorable: this
               toValue: pressed
               duration: FAST_TRANSITION
           }
       }
       StateTransition {
           id: "Enter-Disabled"
           toState: "disabled"
           animation: FadeTransition {
               node: bind node
               toValue: 0.33
               duration: FAST_TRANSITION
           }
       }
   ]
}

// ... Move along, nothing more to see ...
}
   
True to the spirit of JavaFX's declarative syntax, all you have to do is declare your states and the transitions between them and the rest is handled for you! You can't ask for easier animations than that.
In addition to those shown above, many controls also have "focused", "focused+hover", and "focused+armed" states defined as well (along with their corresponding transitions). Note that I did not specify any priorities in the State declarations above. That is because the order in which the states are declared establishes a default priority. You only need to specify an explicit priority if you want to override this default. Also note that I didn't specify a fromState in any of my state transitions. I was telling the States class that it can use that transition when entering the target state from any other state. You only need to specify a fromState if you want the transition to be used only when entering a state from one other particular state.
This code makes use of another Caspian gem, the ColorTransition class which is found in the com.sun.javafx.scene.control.caspian package. It will animate the Colorable mixin's color property from one value to another. This is yet another reason to make use of the Colorable mixin in your code - easy color animations!

The Etched Button Control

This control was created because I wanted a close button for the XPane control that looked like it was etched into the background of the title bar. I wanted the etched button to fit in as closely as possible with the other Caspian controls as well. The resulting control is shown in the image below.

The button features animations for over and pressed states as well as the disabled state. There are a few other nice features as well. The button can display text or a graphic, or both. It can optionally apply an etched effect to its content. The effect is similar to the one Jasper shows in a recent blog post.
Clicking on the disable check box will disable both the etched button and the normal Caspian button. You can also compare their over and pressed animations to verify that they are a close match. This control shows that it is possible to match the look and feel of the Caspian controls very closely by simply using some of the classes that are provided with the standard JavaFX runtime.

P.S.

Knock, knock. Who's there? Yewer. Yewer who? Yewer afraid I was going to tell 100 knock-knock jokes weren't you!

Wednesday, December 2, 2009

Kids + Robots = FUN!

Every year from August to December, somewhere between 90 and 99.9 percent of my free time is taken up as a volunteer helping to organize the Poudre Qualifier, a regional FIRST LEGO League robotics tournament. FIRST LEGO League (or FLL) is a robotics competition for teams of kids from 9 to 15 years old. At the Poudre Qualifier, we host over 300 kids on 48 teams. These kids have spent the last 2 months (or more) building a robot using a LEGO MindStorms robotics kit and creating a research project. Their robot will try to do as many missions as possible on a 8-foot by 4-foot playing field in two and a half minutes.
Of all of the FIRST (For Inspiration and Recognition of Science and Technology) robotics competitions, FLL is unique in that the robot is completely autonomous. It must be smart enough to go out and accomplish its missions and return to base all on its own. If the kids have to rescue the robot out on the table they incur a penalty. Here is a video of a robot doing its thing during a past competition. The kids also do a research project based on the theme of that year's competition. Recent themes have included nanotechnology, energy, and transportation. At the tournaments, a team presents their research project to a panel of experts. Teams are also judged on a technical interview, where they answer questions about their robot's design, construction, and programming; as well as a teamwork exercise to test their ability to solve problems as a team.
With well over 100,000 kids on over 15,000 teams all over the world, FLL is a terrific program for getting young people interested in science and technology in an exciting and fun way. By talking to and learning from experts, kids find out that they can use science and technology to make a positive difference in their world. They also learn many valuable life skills for a future engineer or scientist: teamwork, public speaking, being interviewed, working and performing under the pressure of deadlines, and most of all, how to compete while treating teammates and opponents with respect and courtesy. That is a concept called Gracious Professionalism and it is the core value of FIRST.
What FIRST really needs is volunteers who want to share their passion for science and technology with a new generation of enthusiasts. So my request to you, dear reader, is to find a FIRST event near you and volunteer! Be a referee, a judge, a coach... whatever you want. Just get involved! It will only cost you a few hours or a day, and I guarantee that you will have an amazing time. All of the FIRST events are free and open to the public, so you can also come out and watch these kids having a great time solving difficult problems. If you live here in Colorado, we have the state championship tournament coming up. Information can be found here. And by the way, more photos from our tournament can be found here.