48
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

Extreme GUI Makeover: Hybrid Swing and JavaFX™ · > Create listeners to wire JavaFX to Swing 9 FX Stage Swing JList Swing JTextPane. Create JavaFX Stage def STAGE_MIN_WIDTH = 845;

  • 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

A Hybrid Approach

Leverage existing Java/Swing application and use JavaFX to give it a face lift

3

> 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

6

before (2006)

after (2006)

The Before: Java Mail(GUI Makeover 2006)

7

Demo......

The After: ExtremeMail

Makeover Regimen

> Mixing Swing & JavaFX> Easy Eye Candy> Styling with CSS> 3D Illusion> Finale

8

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);

Easy Eye Candy

> Reflections and Rollover Effects> Rounded Panels> Animating Tree

15

Reflections and Rollover Effects

16

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 Panels

20

JavaFX node case Swing wrapper case

shadow over and clipped

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

42

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