Saturday, July 17, 2010

My Bads

Just a quick update (long overdue - sorry!) to correct a couple of errors in my last two posts.

First, going all the way back to my post So What's It All Good For?, I realized while using my 'X' shape on another control that I had flubbed one of the coordinates in the SVG path.  This made the 'X' look a little hinkey.  Although it was really only noticeable at larger sizes, I went back and fixed the SVG path in that post.  Now the 'X' checkbox looks better.

simpleskinning.png

My second mistake was of a larger variety.  I incorrectly claimed in my Into the Background post that you couldn't use a gradient as the background of a ScrollView because once you added other Nodes, the CSS engine would throw an exception.  Turns out it was user error.

You see, I was assigning the gradient to the -fx-background property in the .scene style.  The fact is that this property must evaluate to a color, not a gradient, because it is used later in the default Caspian style sheet as the base color from which other gradients are calculated.  For some reason the CSS engine objected to me trying to force it to using a gradient as a color stop in another gradient.  Go figure!

This is the reason that the error would only manifest once another control was added to the scene as that was when the gradient-based-on-gradient calculation would be triggered.

My mistake was kindly pointed out by David Grieve in response to my initial bug report here.  I have also updated the original article to demonstrate a variation of the technique that David suggested.  Thanks David!

ScrollViewGrad.png

Hopefully these kinds of mistakes will be less likely to happen in the future now that some nice documentation of the JavaFX CSS engine exists.  There is also a new JIRA case opened to add better error handling and reporting to the CSS engine, which should also be a big help.

p.s. If you haven't signed the petition to open source the JavaFX runtime, please do so now!  Having access to the source code could also help avoid these kinds of "misunderstandings" in the future.

Saturday, May 29, 2010

Into the Background

Update: This post has been edited to show you how to use a gradient as the background of a ScrollView.

The new and improved support for CSS in JavaFX 1.3 includes a .scene style class which is a convenient place to initialize all sorts of application settings such as fonts and colors.  Therefore, you would also think that the .scene class is the place to specify the background color for the scene, right?  If you do, you're in good company.  And just like the rest of us, you would be wrong.

It turns out that the only place to set the fill property of the Scene is in your JavaFX code.  But there are some tricks you can use if you want the ability to control the background of your scene from a CSS file.

ScrollViews and Rectangles

The simplest way to create a background that can be styled is to use a ScrollView as the background node in your scene.  The advantage of using a ScrollView is that if the window is resized to be smaller than the content, scrollbars will automatically appear.  This is a handy feature to have in a main window.  The following code demonstrates the technique.

Stage {
    var sceneRef: Scene;
    title: "ScrollView Background"
    scene: sceneRef = Scene {
        width: 400
        height: 300
        stylesheets: "{__DIR__}scrollViewBackground.css"
        content: [
            ScrollView {
                width: bind sceneRef.width
                height: bind sceneRef.height
                node: // Rest of your content goes here
            }
        ]
    }
}

Here I simply placed the ScrollView first in the Scene's content and bound its width and height to the size of the Scene.  Instant background.

If you then create a style sheet that sets the -fx-background property on the .scene selector, the ScrollView will pick it up automatically.

.scene {
    -fx-background: skyblue;
}

The results are pictured below.

ScrollView.png

Using a gradient as a background for the ScrollView is also possible, although it requires a little more work in your style sheet.  You need to add two new styles that redefine the default background of the ScrollView so that it incorporates a gradient rather than a solid color.

.scene {
    -fx-background: skyblue;
    -my-scene-background: linear (0%, 0%) to (0%, 100%) stops (0%, derive(-fx-background, 25%)) (100%, derive(-fx-background, -35%));
}
#sceneBackground {
    -fx-background-color: -fx-box-border, -my-scene-background;
}
#sceneBackground:focused {
    -fx-background-color: -fx-focus-color, -fx-box-border, -my-scene-background;
}

Note that I have used an ID selector for these new styles so that it overrides only the -fx-background-color of the single ScrollView that I have designated as my scene's background.  I was careful to preserve the -fx-box-border and -fx-focus-color settings of the default ScrollView styles (from caspian.css) so that, other than the gradient, the background will look exactly like a standard ScrollView.

I have also defined my background gradient in the .scene section of the style sheet so that it can be reused in both of the #sceneBackground styles. Declaring CSS "variables" like this is a feature of the JavaFX CSS support.

Once these additions to the application's style sheet have been made, you just need to add the proper ID to the ScrollView being used as the scene's background and you are done.  This is what the JavaFX code and our window look like now.

Stage {
    var sceneRef: Scene;
    title: "ScrollView Background"
    scene: sceneRef = Scene {
        width: 400
        height: 300
        stylesheets: "{__DIR__}scrollViewBackground.css"
        content: [
            ScrollView {
                id: "sceneBackground"
                width: bind sceneRef.width
                height: bind sceneRef.height
                node: // Your content goes here...
            }
        ]
    }
}

ScrollViewGrad.png

Rectangle Backgrounds

Another lightweight option for creating a styled background is to use a simple Rectangle.

Stage {
    var sceneRef: Scene;
    title: "Rectangle Background"
    scene: sceneRef = Scene {
        width: 400
        height: 300
        stylesheets: "{__DIR__}rectangleBackground.css"
        content: [
            Rectangle {
                styleClass: "background"
                width: bind sceneRef.width
                height: bind sceneRef.height
            }
            // Rest of your content goes here
        ]
    }
}

This is similar to the ScrollView version except that a Rectangle is inserted as the first node in the Scene and its width and height are bound to the Scene's size.  Note that I assigned a styleClass of "background" to the Rectangle.  The style sheet now looks like this:

.scene {
    -my-scene-background: linear (0%, 0%) to (0%, 100%) stops (0%, derive(skyblue, 25%)) (100%, derive(skyblue,-25%));
}
.background {
    -fx-fill: -my-scene-background;
}

In the style sheet above, I've assigned a gradient to the -my-scene-background property in the .scene section and then used it to set the Rectangle's fill (-fx-fill) in the .background section which was the styleClass for the background Rectangle.  Of course, I could have just assigned the gradient to the -fx-fill property directly, but as was pointed out above, doing it this way makes that gradient available for use by other styles in the style sheet.

Regions: Imagine the Possibilities

For the more adventurous, you can also use a Region for your background.  This gives you the ability to have background colors, borders, and even images!

warningsignsmall.png

Warning!  Non-public APIs in use beyond this point. Proceed at your own risk!  (It's really not that risky)

 

All of the core controls in JavaFX 1.3 are made of Regions.  Because of this, Regions are heavily tied into the new CSS support and have a lot more styling options than any other type of node.  You can assign background colors and images, style borders, and even change the shape of the Region all from a style sheet.

But you don't have to write a control to make use of a Region.  Regions are a subclass of the public Stack container and therefore can be used anywhere in the scene graph.  Which means that a Region makes a great background for a Scene.

RegionGray.png

RegionSpring.png

webstartsmall2.gif

The top image shows a window with a gray gradient background that is typical of a default Caspian color scheme.  The bottom image shows a combination of a background color and a background image that is positioned along the bottom of the window and repeated in the x-direction to fill the window with flowers in a celebration of Spring (with apologies to all of our friends in the Southern Hemisphere).

The code and the stylesheet are shown below in their entirety.

Stage {
    var sceneRef: Scene;
    var backgroundRef: Node;
    title: "Region Background"
    scene: sceneRef = Scene {
        width: 600
        height: 300
        stylesheets: ["{__DIR__}jfxtras.css", "{__DIR__}sceneStyles.css"]
        content: [
            backgroundRef = Region {
                styleClass: "background1"
                width: bind sceneRef.width
                height: bind sceneRef.height
            }
            VBox {
                var fillWidth = LayoutInfo { hfill: true }
                spacing: 10
                padding: Insets { top: 10, right: 10, bottom: 10, left: 10 }
                content: [
                    XEtchedButton {
                        text: "Business As Usual"
                        layoutInfo: fillWidth
                        action: function () {
                            backgroundRef.styleClass = "background1"
                        }
                    }
                    XEtchedButton {
                        text: "Yay, It's Spring!"
                        layoutInfo: fillWidth
                        action: function () {
                            backgroundRef.styleClass = "background2"
                        }
                    }
                ]
            }
        ]
    }
}
.scene {
    -fx-font: 24pt "Amble Condensed"
}

 

.background1 {     -fx-color: lightgray;     -fx-background-color: -fx-body-color;     -fx-background-image: null; }

.background2 { -fx-background-color: #81E2F7;     -fx-background-image: "flowersbluesky.png";     -fx-background-position: left bottom;     -fx-background-repeat: repeat-x; }

Regions definitely provide the most flexibility and power as the background of a Scene.  The only drawback is that Region is not part of the JavaFX runtime's public API.  As such, there is always the possibility that the class may change or disappear in future releases.  In this case, I think the fact that the core controls now rely on Regions means that they will be around in one form or another for a long time.

 

 

Thursday, April 29, 2010

Back Off, Steve

I cannot express my indignation adequately in 140 characters.
Apple has published a well-written open letter from Steve Jobs about the iPhone/iPad vs Flash kerfuffle.
Being used to the JVM, I will admit that the Flash Player does not seem as fast or robust. But I do find it to be a fairly pleasant development environment. And it is very good at what it was designed for.
The real problem I have with the open letter is the last, most important reason Jobs lists. It boils down to: we just want to protect our developers from making bad choices (as defined by us). By force if necessary. This is just so wrong for so many reasons.
If Apple wants to ban the Flash Player from their platforms, fine. But don't try to dictate what languages developers can use under the guise of "supporting open standards." Apple, if the use of ActionScript leads to inferior applications, they will be killed off by the native applications. The point is: fair competition should always be allowed to determine the winner.
If Adobe's tools make it easy for developers to create nice applications that people want to use, you do everyone a disservice by imposing additional artificial restrictions on your already closed-garden app store. Stop hindering free competition!
I really enjoy developing applications on my brand new MacBook Pro. But my next mobile device is going to be powered by a little green robot. Not because I want to make a statement. Just because I want to be able to decide how to write applications for my own device. And for me personally, neither Objective-C nor HTML5 are appealing choices.
Just my worthless opinion. We now return to our regularly scheduled JavaFX addiction.

Tuesday, April 27, 2010

So What's It All Good For?

Ok, so my previous post (which I refer to facetiously as the Encyclopedia of JavaFX Styling) was a tad on the long-ish side. It contained a lot of not-so-trivial-to-understand information. It contained information that was biased toward helping control authors understand the new system rather than application developers.

So I can certainly understand the reactions of some folks: "Holy cow that looks complicated!" or "Why should I learn all of that?" or "What's this new-fangled system even good for?". The reason application developers should learn the new styling system is very simple: you can now do things with a few lines of CSS that used to take many lines of JavaFX code.
Like all of JavaFX, it's all about developer productivity!
Let's say as an application developer you decided that:
  • All of your application buttons should be round, or
  • Your checkboxes should have red X's rather than black checks and the text associated with them should turn red when the checkbox is selected, or
  • You want an awesome slider for your "number of gold stars" rating system complete with a star-shaped thumb bar that casts a drop shadow! (Really? Ok...).
Pre-JavaFX 1.3 you would be doing some combination of styling and overriding skin classes and rewriting skin code. Now with JavaFX 1.3, all of this is just a style sheet away.
.scene {
    -fx-font: 16pt "Amble"
}

.button {
    -fx-base: dodgerblue;
    -fx-shape: "M 50,30  m 0,25  a 1,1 0 0,0 0,-50  a 1,1 0 1,0 0,50";
    -fx-scale-shape: false;
}

.check-box:selected *.label {
    -fx-text-fill: red
}

.check-box:selected *.mark {
    -fx-background-color: red;
    -fx-shape: "M 0,0 H1 L 4,3 7,0 H8 V1 L 5,4 8,7 V8 H7 L 4,5 1,8 H0 V7 L 3,4 0,1 Z";
}

.slider *.track {
    -fx-base: derive( goldenrod, 50% );
}

.slider *.thumb {
    -fx-shape: "M 50,5 L 37,40 5,40 30,60 20,95 50,75 80,95 70,60 95,40 63,40 Z";
    -fx-background-color: derive(goldenrod,-50%), goldenrod;
    -fx-background-radius: 0, 0;
    -fx-background-insets: 0, 2;
    -fx-padding: 10;
    -fx-effect: dropshadow( two-pass-box , rgba(0,0,0,0.6) , 4, 0.0 , 0 , 1 );
}

Just apply this style sheet and BLAM! All your buttons are round, all your checkboxes are X'ed, and all of your sliders are golden. Life is good. Behold.

simpleskinning.png

Although... we may want to re-think that red checkbox label. Ick.

Monday, April 26, 2010

Advanced JavaFX Control Styling

JavaFX v1.3 includes a brand new and much more powerful CSS-based styling and skinning engine. This article will take a peak under the covers and show you how to get the most out of JavaFX styling so you can develop a unique look for your applications. I'll even show you how to do some basic re-skinning of the core controls - changing not just their color scheme but also their shape! - all from the comfort and convenience of your style sheet. That is a lot of ground to cover so we better get started.

Understanding Regions

Warning!! The Region API is not public and is not considered a finished work. It can, and certainly will, change in future versions. Maybe even in JavaFX v1.3.1. The code presented here is for demonstration of the concepts underlying the new styling system in JavaFX 1.3. If you are considering using this information to build your own custom controls, be sure you understand the down side of using private APIs!

In v1.3 the core controls are made up of regions. Regions are basically a shape with one or more backgrounds and borders. All core control skins inherit from SkinBase which extends Region. Take the LabelSkin as an example. Its inheritance hierarchy looks like this:

LabelSkin -> SkinBase -> Region -> Stack

So the controls are regions, but they can also have zero or more sub-regions. Think of a scroll bar control. It is a region, but it also has sub-regions for the thumb button, the track, and the up and down arrow buttons.

Regions are tied into the new CSS system and can be fully specified, right down to their shape, from a style sheet.

Note that SkinBase does not inherit from Skin at any point. A SkinBase can be assigned to a custom control's skin variable through the use of the SkinAdapter class. The SkinAdapter will take care of passing along the control and behavior assignments to the SkinBase when SkinAdapter is set as the Control's skin.

Creating a Demonstration Control

I simply define a class that extends Control, assign the skin using the SkinAdapter, and override the styleClass variable.

public class DemoControl extends Control {
    // This is the class I'll use in style sheets
    override var styleClass = "demo-control";

    // SkinAdapter lets me use a Region as a Skin
    override var skin = SkinAdapter {
        rootRegion: DemoControlRegionSkin{}
    }
}

Next I define a Region-based skin by creating a class that extends SkinBase.

public class DemoControlRegionSkin extends SkinBase {
    // SkinAdapter will set up SkinBase's control and behavior vars
    var cc = bind control as DemoControl;

    // You should always give your control skins a default preferred size
    override function getPrefWidth( height ) { 100 }
    override function getPrefHeight( width ) { 100 }
}

JavaFX Styling Techniques

I think it's best to start with a basic example and build up from there. So I'll begin by creating a simple scene and adding our new demo control.

Stage {
    title: "JavaFX CSS Controls"
    scene: Scene {
        var stack:Stack;

        width: 300
        height: 300
        stylesheets: "{__DIR__}controlStyles.css"

        content: [
            stack = Stack {
                width: bind stack.scene.width
                height: bind stack.scene.height
                content: [
                      DemoControl {
                          layoutInfo: LayoutInfo {
                              width: 250
                              height: 250
                          }
                      }
                ]
            }
        ]
    }
}

I created a simple Scene that is 300 by 300 pixels and has a Stack container that fills it completely. I then created a new instance of my DemoControl and set it's preferred width and height to be 250 pixels. Note that this will override the preferred width and height of 100 that I entered into the control skin itself when I added those functions. By default a Stack will size its children to their preferred size and center them. Therefore, when this program is run we should see a 250 by 250 rectangle (a.k.a. a square) centered in the scene. I'll now add some minimal styling in controlStyles.css (which is added to the Scene above) so the Region's rectangle will be visible.

.scene {
    -fx-font: 16pt "Amble";
    -dean-slightlydarkergreen: derive( green, -25% );
}

.demo-control {
    -fx-background-color: skyblue;
    -fx-border-color: -dean-slightlydarkergreen;
    -fx-border-width: 2;
}

This small style sheet first sets a default font for the Scene. I haven't added any controls with text yet, but I will later and it's always nice to set these things up in one place. Below the font attribute, I demonstrate a feature of JavaFX styling: the ability to derive a new color from an existing one and store it for later use. In this case, I created a darker version of Green and named it -dean-slightlydarkergreen. Not only am I now immortalized in my own style sheet, but the DemoControl can reference this attribute later as shown above for the control's border color. Note that I use the same class string "demo-control" that I hard-coded into my DemoControl class. Let's see how this looks so far.

Gradients

You can also specify linear gradients in your style sheet. The image and code below shows a fancier version of the DemoControl.

#gradient.demo-control {
    -dean-lightnavy: derive(navy, 100%);
    -dean-navygrad: linear (0%,0%) to (100%,0%) stops (0%, -dean-lightnavy) (100%, navy);
    -fx-background-color: linear (0%,0%) to (0%,50%) stops (0%, derive(deepskyblue, 80%)) (30%, deepskyblue) reflect;
    -fx-border-color: -dean-navygrad null -dean-navygrad null;
    -fx-border-width: 8 0 8 0;
}

You can see by the CSS selector "#gradient.demo-control" that this style will be applied to DemoControls with "gradient" for an ID. I didn't have to change any JavaFX script code to make this work other than adding the id: "gradient" attribute to the DemoControl's object literal declaration in the Scene, thereby picking up this new style.

In this style, I also derive a new light navy color and then reference it again when I define my navy gradient (-dean-navygrad). This gradient is then used as the border color. I use it on the top and bottom while setting the left and right border colors to null. Similarly, I set the top and bottom border widths to 8 pixels while the left and right borders have a 0 width. Many style attributes of a Region have this ability to specify top, right, bottom, and left properties individually. On the other hand, if I were to simply say "-fx-border-width: 8" then all borders would be 8 pixels wide.

The Region's background color is a slightly more complicated linear gradient. It goes half way down the rectangle and is then reflected so the bottom half matches the top. Note that you can even embed "derive" functions inside of linear gradient definitions. More power!

Multiple Backgrounds

As I said at the beginning, Regions are made up of a shape and one or more backgrounds and borders. Let's take a look at how you define multiple backgrounds and borders for your region. Once again, I will make no changes to our source code other than the control's ID in the scene and a new style in the style sheet.

#multibg.demo-control {
    -fx-background-color: skyblue, mediumaquamarine;
    -fx-background-radius: 15 0 15 0;
    -fx-background-insets: 0, 20;
    -fx-border-color: navy, darkcyan;
    -fx-border-style: dotted;
    -fx-border-width: 2;
    -fx-border-radius: 15 0 15 0;
    -fx-border-insets: 0, 20;
}

Most of the style attributes of a Region can take multiple items in their declarations. You can see examples of this in the declarations of the -fx-background-color, -fx-background-insets, and the -fx-border-color attributes. In these cases, the first item is applied to the first background, the second item is applied to the second background and so on. Therefore we end up with a skyblue rectangle with navy border and a medium aquamarine rectangle with a dark cyan border. The first background has no insets (it is filled right to the border of the control), while the second rectangle is inset by 20 pixels on all sides.

In cases where there are multiple backgrounds but only one item specified, the first item will be applied to all backgrounds. For example, both rectangles have top-left and bottom-right rounded corners defined by the -fx-background-radius and -fx-border-radius attributes. Since only one set of radius attributes are defined, it will be applied to both backgrounds.

The thing to remember is that commas separate multiple items in a single attribute (as in the two items specified by -fx-background-color above), whereas spaces are used as the separators in an item that can take multiple values (as in the one item having 4 values specified by -fx-border-radius above). Crystal clear? Good. Let's take it up a notch.

Getting Fancier

Here's a twist: many of those attributes that can take multiple values in a single item (like -fx-border-radius) can also take multiple items. This allows you, for example, to specify a different set of border radius values for each background.

#multibgoppcorners.demo-control {
    -fx-background-color: skyblue, mediumaquamarine;
    -fx-background-radius: 15 0 15 0, 0 15 0 15;
    -fx-background-insets: 0, 20 40 20 40;
    -fx-border-color: navy, darkcyan;
    -fx-border-style: dotted, dashed;
    -fx-border-width: 2, 1;
    -fx-border-radius: 15 0 15 0, 0 15 0 15;
    -fx-border-insets: 0, 20 40 20 40;
}

Here I specify a completely different set of values for the -fx-background-radius, -fx-background-insets, -fx-border-radius, and -fx-border-insets attributes for each background. Remember, multiple values within a single item are separated by spaces while multiple items are separated by commas.

Styling Conclusions

In this section I've experimented with the most common style attributes that are used on Regions. You can also use the regular Node style attributes that I covered here. Of interest will be Node's -fx-opacity, -fx-effect, and -fx-cursor attributes. It should also be noted that Regions have a corresponding set of attributes for image-based backgrounds and borders (i.e. -fx-background-image and -fx-border-image and so on). I'm not going to cover them here, but I would expect Oracle to publish full documentation on these attributes very soon.

Shaping Controls

So far, I've just used the default rectangular shape but by using the style sheet I can make a region take on just about any shape that can be imagined. The shape of a Region is specified using SVG path notation. This notation uses a compact encoding to describe shapes using a series of commands and coordinates. Commands are represented by a single letter. For example, M is the move-to command. If the command letter is upper case then the coordinates that follow are considered absolute coordinates whereas the coordinates are interpreted as relative if the letter is lower case. So "M 0 1" means move to location 0,1 while "m 0 1" means move 1 unit in the y direction from the current location. See this page for details of the SVG path syntax.

Movement and Lines

Drawing shapes with one or more lines is extremely easy. The format of the commands is shown in the following table.

Move toM x y m x y
Line toL (x y)+ l (x y)+
Horizontal line toH x
Vertical line toV y
Close the current pathZ z

Suppose I wanted a triangular region instead of the default rectangle.

#line.demo-control {
    -fx-shape: "M 0,1 L 1,1 .5,0 Z";
    -fx-background-color: skyblue, blue;
    -fx-background-insets: 0, 20 20 10 20;
}
The coordinate system used in these paths is the same one used in JavaFX: x increases to the right, y increases down. So our shape is defined as:
  1. Move down one unit in the y direction
  2. Draw a poly-line to the coordinate 1,1 and then 0.5,0
  3. Close the path
By the way, the use of commas to separate the x,y coordinates in a SVG path command are purely optional. I like to use them because it makes the command more readable to me. I'm using capital letters so those coordinates are absolute, not relative to each other. By default, the shape is scaled to fill the area of the control, so the scale of the coordinates doesn't matter. I could have just as easily used 0, 50, and 100 rather than 0, 0.5 and 1.0. You can see that even though the shape has changed, our ability to specify multiple backgrounds with offsets hasn't. One thing to watch our for is that, with non-rectangular shapes, the insets may need to be adjusted to keep the inner shape centered. Here I had to adjust the bottom inset of the second background to make the triangles look centered. I don't know if this is a bug or a feature, but for JavaFX 1.3 be aware that you may need to play with the insets to get things to look exactly as you want. And although the coordinates of the shape are scaled, the insets are still specified in pixels.

Cubic Curves

The SVG path commands for drawing cubic Bezier curves are shown in the following table.

Cubic Bezier curve to Draws a curve from the current point to x,y. x1,y1 is the control point at the beginning of the curve. x2,y2 is the control point at the end of the curve.C (x1 y1 x2 y2 x y)+ c (x1 y1 x2 y2 x y)+
Cubic Bezier curve to (shorthand) Draws a curve from the current point to x,y. The control point at the beginning of the curve is the reflection of the second control point from the previous curve. x2,y2 is the control point at the end of the curve.S (x2 y2 x y)+ s (x2 y2 x y)+

So let's take a look at a region shaped with cubic curves.

#cubic.demo-control {
    -fx-shape: "M100,200 C100,100 250,100 250,200 S400,300 400,200 Z";
    -fx-background-color: skyblue, blue;
    -fx-background-insets: 0, 10;
}
This is admittedly a somewhat fanciful shape for a control. It is important to note that the control's clickable area still takes up the whole 250 by 250 pixels even though the Region's backgrounds take up a smaller area.

Quadratic Curves

The quadratic Bezier curve commands are shown in the table below.
Quadratic Bezier curve to Draws a curve from the current point to x,y. x1,y1 is the control point of the curve.Q (x1 y1 x y)+ q (x1 y1 x y)+
Quadratic Bezier curve to (shorthand) Draws a curve from the current point to x,y. The control point at the beginning of the curve is the reflection of the control point from the previous curve.T (x y)+ t (x y)+
Let's take a look at a quadratic curve example.
#quadratic.demo-control {
    -fx-shape: "M0,200 Q100,50 200,200 T500,100";
    -fx-background-color: skyblue, blue;
    -fx-background-insets: 0, 10;
}
The shape of our region just keeps getting more interesting!

Elliptical Arcs

The arc command is easily the most confusing of the SVG path commands. Use of SVG graphical editors is highly recommended!
Elliptical arc to rx ry - The radius of the resulting arc x-axis-rotation - Rotation of the x in degrees large-arc-flag - If 1 then the largest arc between the start and end points will be drawn. If 0 then the smallest are will be drawn. sweep-flag - Indicates the direction in which the arc is drawn. A 1 specifies the positive direction, a 0 specifies the negative direction. x y - The end point of the resulting arc. These are absolute coordinates or are relative to the start point (the current location) depending on whether the A or a command is used.A (rx,ry x-axis-rotation large-arc-flag sweep-flag x,y)+ a (rx,ry x-axis-rotation large-arc-flag sweep-flag x,y)+
What are arcs good for? Lot's of things, of course, but circles are an obvious choice. However, drawing a circle with arc commands is not as trivial as you would imagine. Here is an example.
#arc.demo-control {
    -fx-shape: "M 50,30  m 0,25  a 1,1 0 0,0 0,-50  a 1,1 0 1,0 0,50";
    -fx-background-color: skyblue, blue;
    -fx-background-insets: 0, 10;
}
As you can see, you actually need to define two arcs: one for each 180 degree ellipse.

Final Thoughts on Shapes

The ability to change, not just the colors, but the actual shapes of the regions that make up the core controls gives application developers unprecedented control over the look of their applications. Need a slider with a gold star for the thumb track? Easy. Buttons that look like clouds? Very do-able. The only limit is your imagination. Just remember, as with many of the things that JavaFX makes easy for developers: with great power comes great responsibility!

Using Caspian Styles

One last thing I want to address is the subject of using that styles in caspian.css with your own controls. The Caspian class in JavaFX 1.2 contained several nice utility functions that would generate nice gradients that tied into the Caspian theme. That class no longer exists in JavaFX 1.3. To use the Caspian theme with your own controls, you will need to use the values defined in the caspian.css style sheet.

Let's return to the DemoControl and make it look like it fits in with the Caspian controls.

#caspian.demo-control {
    -fx-background-color: -fx-body-color;
    -fx-background-radius: 5;
    -fx-border-color: null;
}

As you can see, all I've done is use the -fx-body-color attribute from caspian.css as my background color, added a corner radius to match Caspian's, and turned off the border I would normally inherit from the default demo-control style. In order for the -fx-body-color attribute to be defined, the caspian.css has to be loaded into the scene. Instantiating any core control will take care of this. In this case, I not only instantiated a button, but I also added it to the scene's Stack container just for comparison.

Adding Borders

Now I'll go the whole nine yards and add Caspian-style borders as well. I can take these right out of the button style in caspian.css.
#caspianborders.demo-control {
    -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
    -fx-background-radius: 5, 5, 4, 3;
    -fx-background-insets: 0 0 -1 0, 0, 1, 2;
    -fx-border-width: 0;
}
Here I have actually defined four different backgrounds for the DemoControl's region. The insets are used to make sure they appear properly as a shadow, two borders and the body. Using the Caspian-defined attributes for body, border and shadow color means that my DemoControl will respond to changes in the application-defined -fx-base attribute. This is the attribute you would set if you wanted to change the base color of all Caspian controls in the entire application. I can set -fx-base to a new color in the .scene style and both DemoControl and the button will pick up the change.
.scene {
     -fx-font: 16pt "Amble";
     -fx-base: dodgerblue;
}

Final Conclusion

I really like the power and flexibility of the new CSS support in JavaFX 1.3. Styling and skinning controls is now easier than ever. If you need to, you can create an entirely unique look for your application without having to change any lines of JavaFX source code. It brings us a true separation of presentation and program logic. All in all, I give it a gold star. A gold star button even!
#star.button {
     -fx-shape: "M 50,5 L 37,40 5,40 30,60 20,95 50,75 80,95 70,60 95,40 63,40 Z";
     -fx-base: goldenrod
}

References

I found these things handy while learning about all of this:
  1. The caspian.css style sheet located in com/sun/javafx/scene/control/skin/caspian/caspian.css in the javafx-ui-controls.jar packaged with the JavaFX SDK.
  2. The Style Editor example at: http://www.javafx.com/samples/StyleEditor/index.html

Thursday, April 22, 2010

A Brief Introduction to Styling Controls in JavaFX 1.3

Support for styling the core controls using CSS has been greatly enhanced for JavaFX 1.3. In fact, the default skin for the core controls, called Caspian, is now written as a style sheet rather than in JavaFX Script code. Below is an example of a small JavaFX 1.3 style sheet that I wrote for Jim Weaver's 3D Calendar application:

.scene {
   /* default font for all content, inherited by all nodes */
   -fx-font: 16pt "Amble Cn";

   /* main color palette */
   -fx-base: #AEBBD2;
   -fx-accent: #385589;
   -fx-mark-color: #3E857C;
}

.text-box {
   -fx-effect: innershadow( two-pass-box, rgba(0,0,0,0.2), 10, 0.0, 0, 2 );
   -fx-text-fill: #385589
}

#title.text-box {
   -fx-font-size: 125%;
}

.button:strong {
   -fx-text-fill: #D3DAE9;
}

If you have used JavaFX's CSS support in previous versions, you will notice some changes in how style sheets are now written.

  • Where you used to write Scene, TextBox, and Button, you should now write .scene, .text-box, and .button. These are CSS class selectors, which kind of makes sense since you are selecting JavaFX control classes.
  • All JavaFX style attributes now start with the -fx extension in order to avoid conflicts with standard CSS attributes. For example, you should now write -fx-font rather than just font.
  • This style sheet also contains an example of one of the new features in JavaFX 1.3's style sheet support: the ability to an inner shadow effect. You can also specify a drop shadow using a similar syntax

Here is a table of some more new and fun attributes that can be set from style sheets.

Attribute Example Value(s) Works On Notes
-fx-cursor crosshair | default | hand | move | text | wait Nodes
-fx-opacity 0 .. 1.0 Nodes
-fx-rotate 0 .. 360 Nodes Defaults to 0. Not taken into account in layoutBounds
-fx-scale-x Number Nodes Defaults to 1. Not taken into account in layoutBounds
-fx-scale-y Number Nodes Defaults to 1. Not taken into account in layoutBounds
-fx-scale-z Number Nodes Defaults to 1. Not taken into account in layoutBounds
-fx-translate-x Number Nodes Defaults to 0. Not taken into account in layoutBounds
-fx-translate-y Number Nodes Defaults to 0. Not taken into account in layoutBounds
-fx-translate-z Number Nodes Defaults to 0. Not taken into account in layoutBounds
-fx-focus-color white | #FFEEDD | #FFF Controls Color of the focus outline
-fx-control-inner-background white | #FFEEDD | #FFF TextBox, PasswordBox, ListView
-fx-mark-color blue | #0000FF | #00F CheckBox, RadioButton, ScrollBar
-fx-echo-char "\u263A"; PasswordBox Make your password boxes friendly!

This is just a sample of some of the power of the new CSS support. I haven't even touched on linear and radial gradients, deriving colors using derive( pink, -20% ), the ladder function, or alternative ways of specifying colors: rgb(), rgba(), hsb(), and hsba(). So much left to cover!