package photoflockr;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.ext.swing.SwingTextField;
import javafx.scene.Cursor;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.transform.Transform;
import javafx.scene.text.Font;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.lang.FX;
import java.lang.Math;
import java.util.HashMap;
import java.util.Map;
package var flockr: PhotoFlockr;
package class PhotoFlockr extends CustomNode {
var fullScreen: Boolean;
var maxSpeed: Integer = 2*10;
var maxForce: Integer = (0.05*100) as Integer;
var separation: Integer = 75*10;
package var model: Model;
var height: Number;
var width: Number;
var mouseX: Number;
var mouseY: Number;
var boids: Boid[];
package var BOID_COUNT = 15 on replace { createBoids(); };
var radius: Integer = (10*75/2.0) as Integer;
var spriteGroupVisible: Boolean = true;
var tagMap: Map;
var maxTags: Integer = 12;
var tagSearchField: SwingTextField;
var spriteGroup: Group;
var sprites = bind for (b in boids) {
Sprite {
fullScreen: bind fullScreen with inverse
screenHeight: height
screenWidth: width
heading: bind b.vel.heading2D
boid: b
clickAction: function(tag: String): Void {
tagSearchField.text = tag;
model.queryString = tag;
}
}
}
function tagCohesion(boid1: Boid, boid2: Boid): Number {
var tagCount: Number = (sizeof boid1.tags + sizeof boid2.tags);
if (tagCount == 0) {
return 1;
}
var map = tagMap.get(boid1.id) as Map;
if (map == null) {
map = new HashMap();
tagMap.put(boid1.id, map);
}
var result = map.get(boid2.id) as Number;
if (result != null) {
return result;
}
var count = 0.0;
for (t1 in boid1.tags where count < maxTags) {
for (t2 in boid2.tags where count < maxTags) {
if (t1 == t2) {
count++;
}
}
}
result = Math.min(count, maxTags);
map.put(boid2.id, result);
return result;
}
var boidsAnimator: Timeline = Timeline {
keyFrames: KeyFrame {
time: 1s / 25
action: function() {
for (b in boids) {
b.run(boids,
function(b1: Boid, b2: Boid): Number {
var c: Number = tagCohesion(b1, b2);
return if (maxTags == 0) 1 else c/maxTags;
}
);
}
}
canSkip: true
}
repeatCount: Timeline.INDEFINITE
}
init {
javax.imageio.ImageIO.setUseCache(false);
model = Model {
tagMap: tagMap = new HashMap()
maxCount: bind BOID_COUNT
};
spriteGroupVisible = false;
createBoids();
maxTags = 12;
boidsAnimator.play();
Timeline {
keyFrames: at (1s) { spriteGroupVisible => true }
}.play();
}
bound function getPhoto(i: Integer) {
if (i < sizeof model.photos) {
model.photos[i]
} else {
null
}
}
function createBoids(): Void {
if (model != null) {
boids =
for (i in [0 .. < BOID_COUNT]) {
Boid {
id: i
radius: bind radius/10.0
width: width;
height: height;
maxspeed: bind maxSpeed / 10.0
maxforce: bind maxForce / 100.0
photoModel: bind getPhoto(i)
desiredseparation: bind separation / 10.0;
}
}
}
}
package function reorderSprites() {
var _sprites = [ sprites[s | not s.boid.selected], sprites[s | s.boid.selected] ];
spriteGroup.content = _sprites;
}
override function create(): Node {
var tf = new javax.swing.JTextField();
var tfWidth: Number = 0;
var tsfScaleFactor: Number = bind (width - 20) / Math.max(tfWidth, width - 20);
tagSearchField = SwingTextField {
borderless: true
selectOnFocus: false
width: bind (width - 20) / tsfScaleFactor
text: bind model.queryString with inverse
background: Color.color(0, 0, 0, 0)
foreground: Color.WHITE
font: Font { name: "Helvetica", size: 75 }
opacity: 0.8
transforms : bind [ Transform.translate(0, 60),
Transform.scale(tsfScaleFactor, tsfScaleFactor),
Transform.translate(0, -25) ];
}
tf.setFont(tagSearchField.getJTextField().getFont());
tagSearchField.getJComponent().addKeyListener(
java.awt.event.KeyAdapter {
override public function keyReleased(ke: java.awt.event.KeyEvent) {
tf.setText(tagSearchField.getJTextField().getText());
tfWidth = tf.getPreferredSize().width;
if (not tagSearchField.focused) {
tagSearchField.requestFocus();
}
if (fullScreen) {
for (b in boids) {
b.selected = false;
}
}
}
});
return Group {
content: [
spriteGroup = Group {
content: sprites
visible: bind spriteGroupVisible
},
Group {
translateX: 10
translateY: 160
content: tagSearchField
},
CrossHair {
w: width
h: height
cx: bind mouseX
cy: bind mouseY
},
Rectangle {
cursor: Cursor.NONE
height: height
width: width
fill: Color.rgb(0, 0, 0, 0)
onMouseMoved: function(e) {
mouseX = e.x;
mouseY = e.y;
}
onMouseDragged: function(e) {
mouseX = e.x;
mouseY = e.y;
}
smooth: false
},
Group {
translateX: width - 20
content: [
ImageView {
translateX: 5
translateY: 5
image: Image {
url: "{__DIR__}images/close.png"
}
cache: true
},
Rectangle {
width: 20
height: 20
fill: Color.rgb(0, 0, 0, 0)
cursor: Cursor.HAND
cache: true
onMouseClicked: function(e) {
FX.exit();
}
}
]
}
]
}
}
}
function run(__ARGS__: String[]) {
flockr = PhotoFlockr {
width: 500
height: 500
}
Stage {
title: "Photo Flockr - JavaFX"
visible: true
resizable: false
style: StageStyle.UNDECORATED
scene: Scene {
fill: Color.BLACK
content: flockr
}
}
}