FXCalendar

FXCalendar is a calendar widget that I wrote in JavaFX script. I mainly did it to get a grasp of the language and to familiarize myself with its features. Since it’s my first try, and also because I don’t have that much experience in writing Java GUI’s, I believe the code’s probably not as good as it could be. Nevertheless, it was a good learning experience and I did have fun writing it. JavaFX uses a scene graph to render its GUI elements. This is different from the Model/View/Controller pattern that Swing uses. Also, JavaFX uses a declarative syntax for its GUI, which is different from the traditional imperative approach (though both can be used). Here’s an example of a “Hello World” program in both styles:

Declarative Syntax:

import javafx.ext.swing.*;

SwingFrame {
   title: "Hello World!";
   width: 100;
   height: 50;
   content: Label {
               text: "Hello World!";
            }
   visible: true;
}
                

Traditional Syntax:

import javafx.ext.swing.*;

var myFrame:SwingFrame = new SwingFrame();
var myLabel:Label = new Label();

myLabel.text = "Hello World!";
myFrame.width = 200;
myFrame.height = 50;
myFrame.visible = true;
myFrame.content = myLabel;
                

The declarative style is a lot more intuitive than the traditional approach, and as the complexity of the GUI increases, much easier to read. Though the intent of JavaFX is to create RIA’s (Rich Internet Applications), you can use it to do other stuff as well. The language has a bunch of neat features like incremental/lazy evaluation through binding, list comprehensions, triggers, and closures. Also, since JavaFX is built on top of Java, you can easily use Java objects. For example:

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Date;
import java.text.DateFormatSymbols;
import javafx.ext.swing.*;

var calendar:Calendar = new GregorianCalendar();
var today:Date = new Date();
var dfs:DateFormatSymbols = new DateFormatSymbols();
var monthNames = dfs.getMonths();

calendar.setTime(today);

SwingFrame {
   title: "Today's Date";
   width: 200;
   height: 50;
   content: Label {
               text: "Today is " + monthNames[calendar.get(Calendar.MONTH)] + " " + calendar.get(Calendar.DATE) + 
                     ", " + String.valueOf(calendar.get(Calendar.YEAR));
            }
   visible: true;
}
                

As you can see, you can easily embed Java objects in your JavaFX code. I won’t concentrate much on describing the language right now, because there’s quite a bit to go into. If you’re interested, you can take a look here for an introduction to the language, and here for the preview API. Anyway, so here’s a screenshot of the widget running on Firefox 3 on my Ubuntu laptop (the theme is a Leopard theme):

Calendar widget

The code for that widget is pretty simple:

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Date;
import java.text.DateFormatSymbols;
import java.lang.System;
import javafx.application.Application;
import javafx.application.Stage;
import javafx.scene.*;
import javafx.scene.geometry.Rectangle;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.effect.*;

var widgetWidth = 400;
var widgetHeight = 350;
var row = 0;

var calendar = new GregorianCalendar();
var today = new Date();
var dfs = new DateFormatSymbols();
calendar.setTime(today);

var todaysDate = calendar.get(Calendar.DATE);
var monthNames = dfs.getMonths();
var daysOfTheWeek = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];

Application {
   stage: Stage {
             content: 
                Group {
                   id: "layout"
                   content: [ 
                      Rectangle { 
                         x: 0 y: 0 width: widgetWidth height: widgetHeight arcWidth: 50 arcHeight: 50;
                         fill: LinearGradient {
                                 startX: 0 startY: 0 endX: 0 endY: 1
                                 stops: [
                                          Stop { offset: 0 color: Color.rgb(255,255,255,0.8)},
                                          Stop { offset: 1 color: Color.rgb(0,0,0,0.8) },
                                 ]
                              }
                      },

                      Rectangle { 
                         x: 0 y: 0 width: widgetWidth height: widgetHeight arcWidth: 50 arcHeight: 50;
                         stroke: Color.web("#555");
                         fill: LinearGradient {
                                  startX: 0 startY: 0 endX: 0 endY: 1;
                                  stops: [
                                           Stop { offset: 0 color: Color.web("#b3b3b3") },
                                           Stop { offset: 0.08 color: Color.web("#555555") },
                                           Stop { offset: 0.25 color: Color.web("#131313") },
                                           Stop { offset: 0.75 color: Color.web("#131313") },
                                           Stop { offset: 0.95 color: Color.web("#181818") },
                                           Stop { offset: 1 color: Color.web("#434343") }
                                 ];
                              }
                      },

                      Text {
                         y:15;
                         x:15;
                         textOrigin: TextOrigin.TOP; 
                         content: monthNames[calendar.get(Calendar.MONTH)];
                         font: Font { name: "Arial" size: 20 style: FontStyle.BOLD };
                         fill: Color.web("#FFFFFF");
                         smooth: true;
                         visible: true;
                      },

                      Text {
                         y:15;
                         x:widgetWidth - 20;
                         textOrigin: TextOrigin.TOP;
                         font: Font { name: "Arial" size: 20 style: FontStyle.BOLD };
                         content: String.valueOf(calendar.get(Calendar.YEAR));
                         fill: Color.web("#FFFFFF");
                         smooth: true;
                         visible: true;
                         horizontalAlignment: HorizontalAlignment.RIGHT;
                      },

                      for(dayName in daysOfTheWeek) {

                         Text {
                            y: 50;
                            x: 55 * (indexof dayName + 1) - 22;
                            textOrigin: TextOrigin.TOP;
                            font: Font { name: "Arial" size:15 style: FontStyle.BOLD };
                            content: dayName;
                            fill: Color.web("#FFFFFF");
                            smooth: true;
                            visible: true;
                            horizontalAlignment: HorizontalAlignment.CENTER;
                         }
                      },

                      for(date in [1..calendar.getActualMaximum(Calendar.DAY_OF_MONTH)]) {
                         calendar.set(Calendar.DATE, date);

                         //We don't want to jump to the next row if the 1st is a Sunday 
                         if(calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY and date != 1) {
                            row++;
                         }

                         var dateText = Text {
                                           y:(40 * row) + 90;
                                           x:55 * calendar.get(Calendar.DAY_OF_WEEK) - 20;
                                           textOrigin: TextOrigin.TOP;
                                           font: Font { name: "Arial" size:15 style: FontStyle.BOLD };
                                           content: String.valueOf(date);
                                           fill: Color.web("#CCCCCC");
                                           smooth: true;
                                           visible: true;
                                           horizontalAlignment: HorizontalAlignment.CENTER;
                                        };                         

                         if(todaysDate == calendar.get(Calendar.DATE)) {
                            dateText.fill = Color.web("#FFFFFF");
                         }

                         //In JavaFX (similar to Groovy) the return-value of a block is the result of
                         //the last evaluated expression

                         dateText;
                     } 
                   ]
                }
          }
}
                

I know it kinda looks like a “death by braces” thing, but I found it a whole lot easier to picture the GUI in my mind using the declarative syntax. I also think it looks a whole lot simpler than the Swing equivalent. Notice that the API provides you a bunch of nifty things like linear gradients. There’s a bunch of other stuff in the API too, including support for animations, effects, and transformations. Now for the widget itself. You can embed the widget in a page by using the <applet> tag. So in a sense, JavaFX applications are just applets, but way better. Thought it may seem like Sun is arriving really late to the game by trying to compete with Flash and Silverlight, I think the fact that JavaFX is built on Java and thus has the support of a large community, and a rich API, gives JavaFX a promising future. The only problem I see right now is the pretty ridiculous (first-time) load-time for the widget. It has a few JavaFX jars to load, which is probably why this is happening. But like I mentioned earlier, this is still a previow so I’m pretty sure Sun will iron out all the kinks by the time 1.0 rolls out. Oh yeah, the widget. Here it is: