Visage is a JVM language designed specifically for UI development, with special syntax for hierarchically describing UIs, binding data and behavior, and representing UI specific concepts such as animation, layout, and styles. It also is a full-featured language with a full compiler tool-chain, static compilation to JVM bytecodes, and IDE plug-ins. This talk will demonstrate how to use the Visage language to build UIs for JavaFX 2.0, Vaadin, and Android. Find out how you can take control of your UI development by writing cleaner, more maintainable UI code using the Visage language in your existing Java projects.
Cleaner APIs, Cleaner UIs with Visage (33rd Degrees)
1. Cleaner APIs, Cleaner UIs
with Visage
Stephen Chin
Chief Agile Methodologist – GXS
http://steveonjava.com/
Tweet: @steveonjava
2. The Visage Language http://visage-lang.org/
Statically Compiled
Language
Based on F3 / JavaFX
Script
Planning Support for
Different Platforms:
- JavaFX 2.0
> "Visage is a domain - Vaadin
specific language (DSL)
designed for the express - A Popular Linux-based
purpose of writing user Tablet OS
interfaces."
2
3. What Does Visage Look Like?
Stage {
var input:TextBox;
title: bind input.text
Scene {
input = TextBox {
color: #DDCC33
}
}
}
3
4. What Does Visage Look Like?
Stage {
var input:TextBox;
title: bind input.text
Scene { Hierarchy Models Your
User Interface
input = TextBox {
color: #DDCC33
}
}
}
4
5. What Does Visage Look Like?
Stage {
var input:TextBox;
title: bind input.text
Scene { User Interface Updates
Automatically
input = TextBox {
color: #DDCC33
}
}
}
5
6. What Does Visage Look Like?
Stage {
var input:TextBox;
title: bind input.text
Scene {
input = TextBox { Built-in Constructs for
Building UIs
color: #DDCC33
}
}
}
6
7. What Does Visage Look Like?
Stage {
var input:TextBox;
title: bind input.text
Scene {
input = TextBox { No more NPEs!
color: #DDCC33
}
}
}
7
8. What Does Visage Look Like?
Stage {
var input:TextBox;
title: bind input!.text
Scene {
input = TextBox { Unless you add an
exclamation mark!
color: #DDCC33
}
}
}
8
10. Java vs. Visage DSL
var circles:Circle[];
ublic class VanishingCircles extends Application { Stage {
title: "Vanishing Circles"
Scene {
public static void main(String[] args) { width: 800
height: 600
Application.launch(args); fill: BLACK
Group {
} circles = for (i in [1..50]) {
def c:Circle = Circle {
centerX: random() * 800
centerY: random() * 600
@Override radius: 150
fill: color(random(), random(), random(), .2)
public void start(Stage primaryStage) { effect: BoxBlur {
height: 10
primaryStage.setTitle("Vanishing Circles"); width: 10
iterations: 3
40 Lines
Group root = new Group();
Scene scene = new Scene(root, 800, 600, Color.BLACK);
}
35 Lines
stroke: WHITE
strokeWidth: bind if (c.hover) 5 else 0
1299 Characters 487 Characters
onMouseClicked: function(e) {
List<Circle> circles = new ArrayList<Circle>(); Timeline {at (3s) {c.radius => 0}}.play()
}
for (int i = 0; i < 50; i++) { }
}
final Circle circle = new Circle(150); }
}
circle.setCenterX(Math.random() * 800); }
circle.setCenterY(Math.random() * 600); Timeline {
for (circle in circles) at (40s) {
circle.setFill(new Color(Math.random(), Math.random(), Math.random(), .2)); circle.centerX => random() * 800;
circle.centerY => random() * 600
circle.setEffect(new BoxBlur(10, 10, 3)); }
}.play()
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new
EventHandler<MouseEvent>() {
public void handle(MouseEvent t) {
KeyValue collapse = new KeyValue(circle.radiusProperty(), 0);
new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play();
}
});
circle.setStroke(Color.WHITE);
10
circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty())
11. How about JavaFX on… Visage
Stage {
title: "Vanishing Circles"
scene: Scene {
width: 800
height: 600
fill: BLACK
content: Group {
circles = for (i in [1..50]) {
Circle {
centerX: random() * 800
centerY: random() * 600
}
}
}
}
}
11
12. How about JavaFX on… Visage
Stage {
title: "Vanishing Circles"
scene: Scene {
width: 800
height: 600
fill: BLACK
content: Group {
circles = for (i in [1..50]) {
Circle {
centerX: random() * 800
centerY: random() * 600
}
}
}
}
}
12
13. How about JavaFX on… Visage
Stage {
title: "Vanishing Circles"
Scene {
width: 800
height: 600
fill: BLACK
Group {
circles = for (i in [1..50]) {
Circle {
centerX: random() * 800
centerY: random() * 600
}
}
}
}
}
13
27. Plus
P some more Java…
public class HelloVisage extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedIS) {
super.onCreate(savedIS);
setContentView(R.layout.main);
}
}
27
31. Straight JavaFX Conversion...
public class HelloVisage extends Activity {
override function onCreate(savedInstanceState:Bundle) {
super.onCreate(savedInstanceState);
def context = getApplicationContext();
def layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
def text = new TextView(context);
text.setText("Hello World, Long Visage");
layout.addView(text);
setContentView(layout);
}
}
31
32. Straight JavaFX Conversion...
public class HelloVisage extends Activity {
override function onCreate(savedInstanceState:Bundle) {
super.onCreate(savedInstanceState);
def context = getApplicationContext();
Override is a built-in
def layout = new LinearLayout(context);
keyword
layout.setOrientation(LinearLayout.VERTICAL);
def text = new TextView(context);
text.setText("Hello World, Long Visage");
layout.addView(text);
setContentView(layout);
}
}
32
33. Straight JavaFX Conversion...
public class HelloVisage extends Activity {
override function onCreate(savedInstanceState:Bundle) {
super.onCreate(savedInstanceState);
def context = getApplicationContext();
Functions begin with the
def layout = new LinearLayout(context);
keyword "function"
layout.setOrientation(LinearLayout.VERTICAL);
def text = new TextView(context);
text.setText("Hello World, Long Visage");
layout.addView(text);
setContentView(layout);
}
}
33
34. Straight JavaFX Conversion...
public class HelloVisage extends Activity {
override function onCreate(savedInstanceState:Bundle) {
super.onCreate(savedInstanceState);
def context = getApplicationContext();
def layout = new LinearLayout(context); after variable
Type
layout.setOrientation(LinearLayout.VERTICAL); colon)
(separated by
def text = new TextView(context);
text.setText("Hello World, Long Visage");
layout.addView(text);
setContentView(layout);
}
}
34
35. Straight JavaFX Conversion...
public class HelloVisage extends Activity {
override function onCreate(savedInstanceState:Bundle) {
super.onCreate(savedInstanceState);
def context = getApplicationContext();
def layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
def text = new TextView(context);
text.setText("Hello World, Long Visage");
layout.addView(text);
Variables declarations
setContentView(layout);
start with "def" or "var"
}
}
35
36. Android JavaFX Code
public class HelloVisage extends Activity {
override var view = LinearLayout {
orientation: Orientation.VERTICAL
view: TextView {
text: "Hello World, Beautified Visage"
}
}
}
36
44. Android Settings
Create a Settings Activity
Populate it with the following preferences:
- Text
- Password
- List
Launch it from the Button control
44
45. Settings Class
public class Settings extends PreferenceActivity {
var usernamePref:EditTextPreference;
var passwordPref:EditTextPreference;
var pollingPref:ListPreference;
override var screen = PreferenceScreen {
preferences: [
…
45
50. Sequence Puzzlers
What is the size of this sequence:
[1..10 step -1]
What does this evaluate to:
[10..<20 step 2][k|k>17]
What is the size of this sequence:
sizeof [20..1 step -3]
54. Visage Operators
> Multiplication and division of two durations is allowed, but not
meaningful
> Underflows/Overflows will fail silently, producing inaccurate
results
> Divide by zero will throw a runtime exception
54
57. Data Binding
A variable or a constant can be bound to an
expression
- var x = bind a + b;
The bound expression is remembered
The dependencies of the expression is watched
Variable is updated lazily when possible
57
58. What Bind Updates
var x = bind if(a) then b else c
x is updated if a or b or c changes
var x = bind for (i in [a..b]) { i * i }
Not everything is recalculated
If a = 1 and b = 2, x is [1, 4]
If b changes to 3, only the added element is
calculated
1 4 9
58
59. Binding to Expressions
Binding to a block
Bound block may contain any number of defs
followed by one expression
Dependencies of block is backtraced from the
expression
Binding to function invocation expression
- Regular function: dependencies are parameters
- Bound function: backtraced from final expression
inside function
59
60. Binding to Object Literals
var a = 3; var b = 4;
var p = bind Point { x: a, y: b };
var q = bind Point { x: bind a, y: b };
var r = bind Point { x: bind a, y: bind b };
When a changes:
- p gets a new instance of Point
- q and r keep the old instance with a new x value
- r will never get a new instance of Point
- (the outer bind in r is useless)
60
61. Integrating Visage and Java
Calling Java from Visage
- Can call Java interface or classes directly
- Automatic conversion to and from Arrays and
Collections
- Can even extend Java interfaces and classes
Calling Visage from Java
- Easiest way is to create a Java interface that Visage
extends
- Can invoke Visage as a script and get results back
61
63. Visage Sequences
Represents collections of homogeneous data
A fundamental container data type
Rich set of language facilities
Contributor to declarative syntax
Automatic conversion to and from Java Arrays
and Collections
63
64. Creating Sequences
Explicit sequence expression
- [1, 3, 5, 7, 9]
Elements are separated by commas
Comma may be omitted if element ends with
brace
1 3 5 7 9
64
65. Creating Sequences
Numeric sequence with range expressions:
- [1..10] 1 2 3 4 5 6 7 8 9 10
Can have a step:
- [1..10 step 2] 1 3 5 7 9
- [0.0..0.9 step 0.1] 0 .1 .2 .3 .4 .5 .6 .7 .8 .9
Can be decreasing:
- [10..1 step -3] 10 7 4 1
Beware of step that goes opposite direction:
- [10..1] is []
Exclusive right end
- [1..<5] 1 2 3 4
66. Getting Info from Sequences
ints = [1, 3, 5, 7, 9]
1 3 5 7 9
[0] [1] [2] [3] [4]
sizeof ints is 5
ints[0] is 1, ints[1] is 3, ..., ints[4] is 9
ints[-1] is 0 (default value of Integer), so is
ints[5]
Object sequence has a default of null
66
67. Getting Slices from Sequences
ints = [1, 3, 5, 7, 9]
1 3 5 7 9
[0] [1] [2] [3] [4]
ints[0..2] is [1, 3, 5]
ints[0..<2] is [1, 3]
ints[2..] is [5, 7, 9]
ints[2..<] is [5, 7]
ints[2..0], ints[-2..-1], ints[5..6] are all []s
67
68. Getting Subsets from Sequences
ints = [1, 3, 5, 7, 9]
1 3 5 7 9
[0] [1] [2] [3] [4]
ints[k | k > 6] is:
- [7, 9] (k > 6 is a condition)
ints[k | indexof k < 2] is:
- [1, 3]
ints[k | k > 10] is:
- []
68