FXEyes
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); } }
実行すると、下の絵のようになります。