Macca Blog

My life experiences with code and design

Flippin’ Awesome JavaFX

Posted on by Mark

Update: 28th November 2008
Here is a link to the webstart deployment of this sample JavaFX app

I thought that when the inevitably soon release of the JavaFX SDK is released there will very quickly be a large number of UI components that users will be developing and releasing for all the world to use.

JavaFX is extreamly easy to create components with, you just need to extend CustomNode and away you go.

so I was just thinking of a quick little example I could whip up, and I thought of a “flipping tiles” effect that could be a bit of fun.

So after quickly implementing it, I found that this could be used in games as well, from card games (like poker, memory, etc…) to many others.

So I decided to create a little sample app that would allow a user to flip over a card on a table.

Here are some images that I used of the cards:

And here is what it looks like:

Here im flipping over the 3 card. The black rectangle in just a box that i added that flips over all of the card when you move your mouse over it.

I have really 2 main classes: AbstractFlippingTile and FlippingCard (sub-class).

The abstract class is really just used to force the developer to implement the flip method (although the implementation of this method is kind of the point but you get what I was trying to do) and some basic shared fields.

The code for each is below:

AbstractFlippingTile

import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.scene.Group;
import javafx.scene.geometry.Rectangle;
import javafx.scene.paint.Color;
import javafx.lang.Duration;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;
import javafx.input.MouseEvent;

import java.lang.System;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;

/**
* @author Mark Macumber
*/

public abstract class AbstractFlippingTile extends CustomNode {

/**
* private members
*/
protected attribute facingFront:Boolean = true;
protected attribute currentlyFlipping:Boolean = false;
protected attribute dragged:Boolean = false;

protected attribute shiftInterval:Integer = 1;

protected attribute startingFill:Color = facingFill;

protected attribute tileImage:Image;

protected attribute originalX:Number = 0.0;
protected attribute originalWidth:Number = 0.0;
protected attribute xTranslation:Number;
protected attribute yTranslation:Number;
protected attribute startingDragX:Number;
protected attribute startingDragY:Number;

/**
* public members
*/
public attribute x:Number = 0.0;
public attribute y:Number = 0.0;

public attribute width:Number = 0.0;
public attribute height:Number = 0.0;
public attribute arcWidth:Number = 12.0;
public attribute arcHeight:Number = 12.0;

public attribute facingFill:Color = Color.BLACK;
public attribute backFill:Color = Color.GREEN;

public attribute flipSpeed:Duration = 600ms;
public attribute flipOnClick:Boolean = true;
public attribute draggable:Boolean = true;

/**
* overridden members
*/
override attribute translateX = bind xTranslation;
override attribute translateY = bind yTranslation;

abstract public function flipTile():Void;
}

FlippingCard

import java.lang.System;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.input.MouseEvent;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;
/**
* @author macumbem
*/

public class FlippingCard extends AbstractFlippingTile{

public attribute tileImageFrontSrc = "";
public attribute tileImageBackSrc = "";
private attribute currentTileImageSrc = "";

public function create():Node{
originalWidth = width;
originalX = x;
startingFill = facingFill;

currentTileImageSrc = "{__DIR__}{tileImageFrontSrc}";

var tile = ImageView {
x: bind x
y: bind y
blocksMouse: true;

image: bind Image{
url: currentTileImageSrc;
width: width;
height: height
};

override attribute onMousePressed = function(e:MouseEvent):Void {
if (draggable){
startingDragX = e.getDragX() - xTranslation;
startingDragY = e.getDragY() - yTranslation;
}
}

override attribute onMouseDragged = function(e:MouseEvent):Void {
if (draggable){
xTranslation = e.getDragX() - startingDragX;
yTranslation = e.getDragY() - startingDragY;
dragged = true;
}
}

override attribute onMouseReleased = function( e: MouseEvent ):Void {
flipTile();
dragged = false;
}
}

tile;
}

public function flipTile(){
if (flipOnClick and not dragged and not currentlyFlipping) {
currentlyFlipping = true;

Timeline {
keyFrames : [
KeyFrame {
time : flipSpeed / 2
values: [
x => x + (originalWidth / 2) tween Interpolator.LINEAR,
width => 0 tween Interpolator.LINEAR,
currentTileImageSrc => "{__DIR__}{getOppositeSide()}"
]
},

KeyFrame {
time : flipSpeed
values: [
x => originalX tween Interpolator.LINEAR,
width => originalWidth tween Interpolator.LINEAR
]
action: function() {
facingFront = not facingFront;
currentlyFlipping = false;
}
}
]
}.start();
}
}

private function getOppositeSide(){
if (facingFront) {
tileImageBackSrc;
} else{
tileImageFrontSrc;
}
}
}

as well, we have the main class:

import javafx.application.Frame;
import javafx.application.Stage;
import javafx.scene.paint.Color;
import javafx.scene.geometry.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.Font;
import javafx.scene.FontStyle;
import javafx.input.MouseEvent;

/**
* @author macumbem
*/

var tileNodes:FlippingCard[];
var back = "images/b1fv.png";
var f = 5; var w = 71; var h = 96; var off = 4; var y = 100;
var ace_clubs:FlippingCard = FlippingCard {
x: f + (0 * (w + off)); y: y; width: w; height: h;
tileImageFrontSrc: "images/c1.png"; tileImageBackSrc: back;
}
var two_clubs:FlippingCard = FlippingCard {
x: f + (1 * (w + off)); y: y; width: w; height: h;
tileImageFrontSrc: "images/c2.png"; tileImageBackSrc: back;
}
var three_clubs:FlippingCard = FlippingCard {
x: f + (2 * (w + off)); y: y; width: w; height: h;
tileImageFrontSrc: "images/c3.png"; tileImageBackSrc: back;
}
var four_clubs:FlippingCard = FlippingCard {
x: f + (3 * (w + off)); y: y; width: w; height: h;
tileImageFrontSrc: "images/c4.png"; tileImageBackSrc: back;
}

insert ace_clubs into tileNodes;
insert two_clubs into tileNodes;
insert three_clubs into tileNodes;
insert four_clubs into tileNodes;

Frame {
title: "Flipping Tile"
width: 500
height: 350
closeAction: function() {
java.lang.System.exit( 0 );
}
visible: true

stage: Stage {
content: [
Rectangle {
x: 5,
y: 50
width: 120,
height: 25
fill: Color.BLACK

onMouseMoved: function( e: MouseEvent ):Void {
for (tile:FlippingCard in tileNodes){
tile.flipTile();
}
}
},
tileNodes
]
}
}

so there you have it… I havent cleaned up the code too much before posting this, so there may be redundant variables here and there. And Im certainly not claiming this to be the best way of doing this type of thing. But it works.

I hope you enjoy this demo…

(Note: There is a known bug where sometimes the cards images flicker a bit, not too sure why this happens yet).

This entry was posted in games programming, Java, JavaFX. Bookmark the permalink.

Leave a Comment

Your email address will not be published. Required fields are marked *


*