id:aoe-tk さんが xeyes を JavaFX で作られていましたが、それにインスパイアされたので、私も作ってみました。
私の方は以前、JavaFX 1.x で作ったもの をベースにしてあります。
JavaFX 1.x だと JavaFX Script で bind が簡単に使えたので楽だったのですが、JavaFX 2.0 の bind が使いにくくなっているのがつらいですね。自分的には、座標計算の部分が冗長なので、もうちょっと見直してみたいと思ってます。
また、ウィンドウをドラッグして移動するのと、終了のためのポップアップメニューを出す部分ができてません。ここらへんも、JavaFX 1.x と異なっているので、意外にやりにくいです。特にドラッグは全然違います。困ったww
また、終了する時に、JavaFX 側は落ちるのですが、Swing の EDT が落ちないという不具合があります。これもなるべく早く修正しないと。
とりあえず、現状のコードを張っておきます。ある程度できたら GitHub にでもあげようと思ってます。
ちなみに、 id:aoe-tk さんが指摘しているように、現在の方式だと Mac だと動かないかもしれません。
まずは本体部分の FXEyes クラス。
package net.javainthebox.fxeyes;
import java.awt.MouseInfo;
import java.awt.PointerInfo;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class FXEyes extends Application {
private Eyes eyes;
@Override
public void start(Stage stage) throws Exception {
stage.initStyle(StageStyle.TRANSPARENT);
Group root = new Group();
Scene scene = new Scene(root, 200, 200);
scene.setFill(null);
eyes = new Eyes(scene.getWidth(), scene.getHeight());
eyes.locationXProperty().bind(stage.xProperty());
eyes.locationYProperty().bind(stage.yProperty());
root.getChildren().add(eyes);
stage.setScene(scene);
stage.show();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Timer timer = new Timer(50, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PointerInfo info = MouseInfo.getPointerInfo();
eyes.setMouseLocation(info.getLocation().getX(), info.getLocation().getY());
}
});
timer.start();
}
});
}
public static void main(String[] args) {
Application.launch(args);
}
}
次に目の部分の Eyes クラスです。
package net.javainthebox.fxeyes;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Ellipse;
public class Eyes extends Parent {
private DoubleProperty locationX = new SimpleDoubleProperty();
private DoubleProperty locationY = new SimpleDoubleProperty();
private double width;
private double height;
private double strokeWidth;
private double blackEyeRadius;
private DoubleProperty mouseX = new SimpleDoubleProperty();
private DoubleProperty mouseY = new SimpleDoubleProperty();
private DoubleBinding leftX = new DoubleBinding() {
{
super.bind(locationX, locationY, mouseX, mouseY);
}
@Override
protected double computeValue() {
double x = mouseX.get() - locationX.get();
double y = -mouseY.get() + locationY.get() + height;
double cx = width / 4;
double cy = height / 2;
return calcurateEyePosition(x, y, cx, cy).getX();
}
};
private DoubleBinding leftY = new DoubleBinding() {
{
super.bind(locationX, locationY, mouseX, mouseY);
}
@Override
protected double computeValue() {
double x = mouseX.get() - locationX.get();
double y = -mouseY.get() + locationY.get() + height;
double cx = width / 4;
double cy = height / 2;
return calcurateEyePosition(x, y, cx, cy).getY();
}
};
private DoubleBinding rightX = new DoubleBinding() {
{
super.bind(locationX, locationY, mouseX, mouseY);
}
@Override
protected double computeValue() {
double x = mouseX.get() - locationX.get();
double y = -mouseY.get() + locationY.get() + height;
double cx = width / 4 * 3;
double cy = height / 2;
return calcurateEyePosition(x, y, cx, cy).getX();
}
};
private DoubleBinding rightY = new DoubleBinding() {
{
super.bind(locationX, locationY, mouseX, mouseY);
}
@Override
protected double computeValue() {
double x = mouseX.get() - locationX.get();
double y = -mouseY.get() + locationY.get() + height;
double cx = width / 4 * 3;
double cy = height / 2;
return calcurateEyePosition(x, y, cx, cy).getY();
}
};
public Eyes(double width, double height) {
this.width = width;
this.height = height;
if (width < height) {
strokeWidth = (width > 100) ? 10 : width / 10;
} else {
strokeWidth = (height > 100) ? 10 : height / 10;
}
strokeWidth = (strokeWidth < 1) ? 1 : strokeWidth;
blackEyeRadius = strokeWidth;
createEyes();
}
public void setMouseLocation(double x, double y) {
this.mouseX.set(x);
this.mouseY.set(y);
}
public DoubleProperty locationXProperty() {
return locationX;
}
public DoubleProperty locationYProperty() {
return locationY;
}
private Point2D calcurateEyePosition(double x, double y, double cx, double cy) {
// 角度は arctan で求めます
double theta = Math.atan2(y - cy, x - cx);
double hr = width / 4 - strokeWidth - blackEyeRadius;
double eyeX;
double eyeY;
// マウスカーソルの位置が目の内部だったら、カーソルの位置に目玉を描画
if (Math.abs(x - cx) > Math.abs(hr * Math.cos(theta))) {
eyeX = cx + hr * Math.cos(theta);
} else {
eyeX = x;
}
double vr = height / 2 - strokeWidth - blackEyeRadius;
if (Math.abs(y - cy) > Math.abs(vr * Math.sin(theta))) {
eyeY = height - cy - vr * Math.sin(theta);
} else {
eyeY = height - y;
}
return new Point2D(eyeX, eyeY);
}
private void createEyes() {
// Left Eye
Ellipse left = new Ellipse();
left.setCenterX(width / 4);
left.setCenterY(height / 2);
left.setRadiusX(width / 4 - strokeWidth / 2);
left.setRadiusY(height / 2 - strokeWidth / 2);
left.setStroke(Color.BLACK);
left.setStrokeWidth(strokeWidth);
left.setFill(Color.WHITE);
getChildren().add(left);
// Left Black Eye
Circle leftBlackEye = new Circle();
leftBlackEye.centerXProperty().bind(leftX);
leftBlackEye.centerYProperty().bind(leftY);
leftBlackEye.setRadius(blackEyeRadius);
leftBlackEye.setFill(Color.BLACK);
getChildren().add(leftBlackEye);
// Right Eye
Ellipse right = new Ellipse();
right.setCenterX(width / 4 * 3);
right.setCenterY(height / 2);
right.setRadiusX(width / 4 - strokeWidth / 2);
right.setRadiusY(height / 2 - strokeWidth / 2);
right.setStroke(Color.BLACK);
right.setStrokeWidth(strokeWidth);
right.setFill(Color.WHITE);
getChildren().add(right);
// Left Black Eye
Circle rightBlackEye = new Circle();
rightBlackEye.centerXProperty().bind(rightX);
rightBlackEye.centerYProperty().bind(rightY);
rightBlackEye.setRadius(blackEyeRadius);
rightBlackEye.setFill(Color.BLACK);
getChildren().add(rightBlackEye);
}
}
実行すると、下の絵のようになります。
