Upload
dodat
View
220
Download
0
Embed Size (px)
Citation preview
Extreme GUI Makeover:Hybrid Swing and JavaFX™
Amy Fowler, Sun Microsystems, Inc.David Grieve, Sun Microsystems, Inc.Paru Somashekar, Sun Microsystems, Inc.Jasper Potts, Sun Microsystems, Inc.
Redux
• The Before & After - Amy• Mixing Swing & JavaFX - Amy• Easy Eye Candy - Amy• Styling with CSS - David• 3D Illusion - Paru• Junk - Jasper
2
Agenda
> Leverage Java/Swing code/expertise> Swingʼs rich component suite
• stable & mature (lots of tire-kicking)• 3rd party extensions
> Java ideal for application internals• multi-threading• well defined patterns, data-models, practices, ...
4
Why Go Hybrid?and use Swing
> JavaFX script designed for rich interface construction• object literal syntax• binding• function pointers
> JavaFX platform graphics• 2D scene graph• Animation• Media (video)
> And JavaFX compiles to Java bytecodes5
Why Go Hybrid?and use JavaFX
Mixing Swing & JavaFX> Create JavaFX Stage> Embed Swing components in scene> Create listeners to wire JavaFX to Swing
9
FX Stage
Swing JList
Swing JTextPane
Create JavaFX Stage
def STAGE_MIN_WIDTH = 845;def STAGE_MIN_HEIGHT = 700;
def stage = Stage { style: StageStyle.TRANSPARENT scene: scene = Scene { width: STAGE_MIN_WIDTH height: STAGE_MIN_HEIGHT fill: null content: CaspianFrame { // scene contents.... } }}
// CaspianFrame is home-grown class implementing top-level // window shape/decorations (authored by Jasper Potts)
10
Embedding Swing Components
> Any Swing class can be embedded in scenejavafx.ext.swing.SwingComponent
> Can be treated like any node:• opacity (!), rotate, scale, translate, etc• Resizable and work with scene graph layout
> But treated as single node• node transforms only on whole component• cannot transform or animate component internals
11
Embedding Swing Components continued
> Can invoke Java/Swing apis from JavaFX scriptdef map = new HashMap();
var table = new JTable();
> Cannot use JavaFX script features on Components • no binding to Java bean properties
> Can create JavaFX wrapper classes to help• SDK provides wrappers for many Swing classes
javafx.ext.swing.*
> JavaFX strictly single-threaded• non-gui thread execution must happen in Java code
12
Embed Swing Components in Scenevar body:Panel;var toolbar:ToolBar;var mailboxArea:BackDrop;var messageListArea:RoundedPanel;var messageArea:RoundedPanel;
var swingMessageList = new MessageListPanel(); // Swing classvar swingMessagePanel = new MessagePanel(); //Swing class body = Panel { content: [ toolbar = ToolBar { ... } mailboxArea = BackDrop { ... } messageListArea = RoundedPanel { node: SwingComponent.wrap(swingMessageList); } messageHeaderArea = BackDrop { ... } messageArea = RoundedPanel { node: SwingComponent.wrap(swingMessagePane); } ]}
13
Create Listeners for Wiring JavaFX to Swing
14
class PropChangeListener extends PropertyChangeListener { public var onPropChange:function(event:PropertyChangeEvent):Void; override function propertyChange(event:PropertyChangeEvent):Void { onPropChange(event); }}
def pcl:PropChangeListener = PropChangeListener { onPropChange: function(event:PropertyChangeEvent):Void { if (event.getPropertyName().equals("selectedMessage")) { // handle property change in FX } } }// add listener to Swing componentmessageListPanel.addPropertyChangeListener(pcl);
Reflections
def reflection = Reflection { fraction: 0.33 }; // effects can be shared!
composeButtonLabel = VBox { nodeHPos:HPos.CENTER spacing: 4 content: bind [ ImageView { image: Image { url: "{__DIR__}resources/compose.png"} effect: reflection cache: true }, label = Text { ... } ]}
17
Rollover Effect
var label:Text;var fadein:FadeTransition = FadeTransition { node: bind label fromValue: 0 toValue: 1.0 duration: 100ms // fade-in is fast };var fadeout:FadeTransition = FadeTransition { node: bind label fromValue: 1.0 toValue: 0.0 duration: 500ms // fade-out is slower};
...
18
...composeButtonLabel = VBox { nodeHPos:HPos.CENTER spacing: 4 content: bind [ ImageView { ... }, label = Text { content: “Compose” fill: TEXT_FILL font: Font.font("Verdana", FontWeight.BOLD, 10) textAlignment: TextAlignment.CENTER opacity: 0.0 } ] onMouseEntered:function(event:MouseEvent):Void { fadein.playFromStart(); } onMouseExited:function(event:MouseEvent):Void { fadeout.playFromStart(); }}
19
Rollover Effect continued
Rounded Panel JavaFX Node Case
21
public class BackDrop extends CustomNode, Resizable { public var node:Node; public var fill:Paint = Color.TRANSPARENT; public var stroke:Paint; public var strokeWidth:Number = 0; public var arcLength:Number = 0; override function create():Node { Panel { width: bind this.width height: bind this.height content: bind [ Rectangle { width: bind this.width height: bind this.height arcWidth: bind arcLength arcHeight: bind arcLength fill: bind fill stroke: bind stroke strokeWidth: bind strokeWidth effect: InnerShadow {} } node // the node backed by rounded panel ] onLayout: function():Void { ... } // lays out node within rectangle } }
Since Mailbox Tree has transparent background,
this works
2. do shape-subtract3. add drop shadow4. clip
Rounded Panel Swing Wrapper Case
22
Since Swing Componentcannot have a transparent background, we have to do
it differently
1. create rectangle
23
public class RoundedPanel extends CustomNode, Resizable { public var node:Node; // node contained in rounded panel
public var fill:Paint = Color.TRANSPARENT; public var stroke:Paint; public var strokeWidth:Number = 0; public var arcLength:Number = 0; var clipper:Panel; override function create():Node { Panel { width: bind this.width height: bind this.height content: bind [ Rectangle { width: bind this.width height: bind this.height arcWidth: bind arcLength arcHeight: bind arcLength fill: bind fill stroke: bind stroke strokeWidth: bind strokeWidth } clipper = Panel { ...
Rounded Panel Swing Wrapper Case
24
clipper = Panel { clip: Rectangle { x: -1 y: -1 width: bind this.width - 2*strokeWidth + 2 height: bind this.height - 2*strokeWidth + 2 arcWidth: bind arcLength arcHeight: bind arcLength } content: [ node, // the Swing Component wrapper ShapeSubtract { layoutX: -1 layoutY: -1 effect: DropShadow { spread: .30 } cache: true a: bind Rectangle { width: this.width - 2*strokeWidth + 4 height: this.height - 2*strokeWidth + 4 } b: bind Rectangle { x: 1 y: 1 width: this.width - 2*strokeWidth + 2 height: this.height - 2*strokeWidth + 2 arcWidth: bind arcLength arcHeight: bind arcLength }; } ] onLayout: function():Void { ... } }
Rounded Panel Swing Wrapper Case
Animating Tree
> Build tree control with nested VBoxes that collapse/expand with animation
25
open closedclosing
Animating Tree
26
public class ExpandingVBox extends VBox {
var expansion:Number = 1.0 on replace { requestLayout(); // size needs to change }
override function getPrefHeight(width:Number):Number { expansion * super.getPrefHeight(width) }
override function doLayout():Void { // allow VBox to set layoutX/layoutY as normal super.doLayout(); for (node in getManaged(content)) { // changing translateX/translateY will not disturb layout! // translation is added on top of layout translation node.translateX = if (indexof node mod 2 > 0) slideTx else -slideTx; } }
...
Animating Tree continued
27
... public var duration:Duration = 20000ms; var slideTx:Number = 0; var blurWidth:Number = 0; def boxBlur = BoxBlur { width: bind blurWidth }; var expanimator:Timeline = Timeline { rate: -1 keyFrames: [ KeyFrame { time: 0s values: [ expansion => 1.0, opacity => 1.0, slideTx => 0 tween Interpolator.EASEBOTH, blurWidth => 0 ] action: toggleBlur } KeyFrame { time: bind duration values: [ expansion => 0.0, opacity => 0.0, slideTx => 200 tween Interpolator.EASEBOTH, blurWidth => 5 ] action: toggleBlur } ] }
Animating Tree continued
28
....
function toggleBlur():Void { // toggle blur so its only on during animation effect = if (effect == boxBlur) null else boxBlur }
function toggleExpansion():Void { if (not expanimator.running) { expanimator.rate = -1 * expanimator.rate; // reverse expanimator.play(); } }}
Styling with CSS
> JavaFX supports styling with CSS• Node has style variables:
public var styleClass:String
public var style:String
> Style sheets can be set on Scene> Shapes can be styled
• fill, stroke, font, etc> Controls can be styled
• styling wired to skins under the covers29
Letʼs give this thing a mullet! Styling with CSS
stage = Stage { style: StageStyle.TRANSPARENT scene: scene = Scene { stylesheets: [ "{__DIR__}resources/style.css" ] width: STAGE_MIN_WIDTH height: STAGE_MIN_HEIGHT...
30
Letʼs give this thing a mullet! Styling with CSS
mailboxArea = BackDrop { id: “mailBoxArea” styleClass: "roundedPanel" arcLength: 20 fill: AREA_FILL stroke: Color.BLACK strokeWidth: 1 topMargin: 4 leftMargin: 4 node: mailboxTree}
31
Letʼs give this thing a mullet! Styling with CSSstyle.css
32
.roundedPanel { arcLength: 20; }
.roundedButton { arcLength: 12; }
.treeNodeLabel { font: 12pt "Veranda"; }RoundedPanel, #mailBoxArea { fill: linear (0%, 0%) to (0%, 100%) stops (0.00, #808080), (1.00, #e0e0e0);}
eyeContactsAddress Book with page flipping
> Builds on Chris Campbellʼs book panel sample • http://www.javafx.com/samples/BookPanel/
index.html
} 33
eyeContactsAddress Book with page flipping
> The AddressBook is implemented as a transparent Stage with a background Image
var bookStage = Stage {style:
StageStyle.TRANSPARENTscene: scene = Scene {
fill: nullGroup {
content: [{//background image}, {//Pages }] ...
}
34
eyeContactsAddress Book with page flipping
> A Book with Pages are added to the Stage
ImageView { layoutX: 0 layoutY : 50 fitWidth: bind scene.width fitHeight: bind bookHeight image: Image { url:
"{__DIR__}resources/ addressbook.png"},Book pages: [
for ( i in [0..5]) { createPage(i); }] }
35
eyeContactsAddress Book with page flipping
> A Page in the book is a CustomNodeclass Page extends CustomNode { public var data: Node; public var profileImageUrl: String;
override function create():Node { return Group {
content: [ImageView {
fitWidth: 100 fitHeight: 100 image: bind Image {url: profileImageUrl;}
}, Group {content: data ...} ] blocksMouse: true
...
36
eyeContactsAddress Book with page flipping
> Connecting Visuals to java datavar contactInfoList: ContactInfoPanel[];// populate contactInfoList sequence from java AddressBook// class
function createPage(i:Integer) : Page {Page {
fill: Color.WHITEdata: contactInfoList[i]
profileImageUrl: contactInfoList[i].contact.getImageLocation().toString()
}
}
37
eyeContactsAddress Book with page flipping
> ContactInfoPanel - a CustomNode with layoutdef displayNameLayout = LayoutInfo { height : 55 };
public var contact : Contact; // Contact is a java class
var displayName = contact.getDisplayName(); override function create():Node {
VBox { content: [
HBox { content: [ Label { text: bind displayName hpos: HPos.LEADING
font: Font.font("Verdana", 28) textFill: Color.web("#1285c3") layoutInfo: displayNameLayout }]
}, HBox { ... }
38
eyeContactsAddress Book with page flipping> Page Flip Effect
• Book is a CustomNode and has• an array of pages• gripLeft and gripRight of type Grip (explained below)
• to track mouse events over bottomLeftCorner and bottomRightCorner
• Grip is a CustomNode that handles mouse events (enter, press, move, drag, release, exit) on a 50x50 rectangle and calls functions to simulate the page flip.
39
eyeContactsAddress Book with page flipping> Ilustration of flipping page B over bottom right corner
• Flip effect is achieved by a combination of
• Translate• Rotate• Clip
40
CBA
D
C
eyeContactsAddress Book with page flipping> Mouse event handling
• The Page Node sets blocksMouse to true• In order to consume mouse events with out passing them up the
scenegraph. • This separates the draggable feature of the Address book with page
flipping.
• Sound effect for page flip var mediaPlayer = MediaPlayer {
media: Media{source: "{__DIR__}page-flip.mp3"} };
mediaPlayer.play();
41
Exploding JunkExplosion Animation
var animation = SequentialTransition { content: [ TranslateTransition{ // open missile hatch node: junkButton byX: -36 duration: 200ms } ...
43
Exploding JunkExplosion Animation
ParallelTransition { content: [ FadeTransition { // fade in missile node: missile toValue: 1 duration: 200ms } PathTransition { // fly missile node: missile path: AnimationPath.createFromPath(missilePath) duration: 1.2s orientation: OrientationType.ORTHOGONAL_TO_TANGENT interpolator: Interpolator.EASEIN action: function() { missileGroup.content = fireBall } } TranslateTransition{ // close missile hatch node: junkButton byX: 36 duration: 500ms } ]}
44
Exploding JunkExplosion Animation
ParallelTransition { content: [ PauseTransition { duration: 1s action: deleteMessage } fireBall.timeline // explosion ] } ] };
var sound = MediaPlayer{ media:Media { source:"{__DIR__}fire/bomb.mp3" } };
animation.play(); sound.play();
45
Exploding JunkExplosion Graphicsvar images:Image[] = for(i in [101..178]) Image{ url:"{__DIR__}fire/Sequence 0{i}.png" }
public class FireBall extends CustomNode { var index:Integer = 0; var view:ImageView =
ImageView{ image: bind images[index] }; public var timeline:Timeline = Timeline { keyFrames: [ at (0s) {index => 0}, at (3s) {index => (sizeof images-1)} ] }; public override function create():Node{ view }}
46
Summary
You can have the best of both worlds:Java/Swing for stability, maturity, leverageJavaFX for easier graphics & animation
Give JavaFX a try....you might even have some fun
47
Amy Fowler [email protected] Grieve [email protected] Somashekar [email protected] Potts [email protected]