このエントリーは しょぼちむ Advent Calendar 2014 の 8 日目です
昨日は @shiget84 さんの しょぼちむさんをきっかけに価値を届けることについて考えた でした。
明日は @cocoatomo さんです。
はるか昔、neko というアプリケーションがあったのを皆さんごぞんじでしょうか?
一瞬かわいいのですが、使っているとうざくてしかたないアプリケーションです。
なぜ、こんなアプリケーションを思い出したかというと、うらがみさんにかまって、かまってと言い続けるしょぼちむの存在があったからこそです。
そこで、かつての neko を現代によみがえらせた syobo を作成してみたいと思います。
もちろん、そこは櫻庭が作るものですから、JavaFX です。
ところが、JavaFX では 1 つだけ問題があります。マウスカーソルの位置を検出できないという問題です。
アプリケーションを表示している領域であれば、マウス移動のイベントが取得できるので、マウスカーソルの位置も取得できるのですが、何も表示していない場所ではこのイベントが発生しないのです。
しかたないので、AWT です ><
AWT には MouseInfo というクラスがあり、いつでもマウスカーソルの位置を取得することができます。そこで、タイマで定期的にカーソル位置を取得し、しょぼちむの位置を更新するということを行っていきます。
JavaFX で Swing を扱うのですが、ここでは直接 Swing のコンポーネントを扱うわけではないので、Swing の EDT を使用するだけで大丈夫です。
Swing の Timer クラスで 50 ミリ秒ごとにマウスカーソルの位置を検出するには次のように記述します。
private void startSwingEDT() {
// AWTでマウスの位置を 50 秒ごとに検出
SwingUtilities.invokeLater(() -> {
Timer timer = new Timer(50, e -> {
PointerInfo info = MouseInfo.getPointerInfo();
Platform.runLater(() -> updateLocation(info.getLocation().getX(), info.getLocation().getY()));
});
timer.start();
});
}SwingUtilities.invokeLater メソッドで Swing の EDT を起動し、そこでタイマを生成して 50 ミリ秒ごとに処理を行わせています。
MouseInfo クラスの getPinterInfo メソッドでマウスカーソルの位置が取得できるので、後は JavaFX のスレッドで位置を更新します。JavaFX のスレッドで処理するためには Platform クラスの runLater メソッドを使用します。
Swing と JavaFX を両方使っていると、スレッドの違いがうざいのですが、しかたありません。
updateLocation メソッドではカーソルの位置に追従して、しょぼちむを表示させます。たいした数学ではないので、分かるでしょう。
private void updateLocation(double mx, double my) {
cursor.setTranslateX(mx);
cursor.setTranslateY(my);
double tx = syobochim.getTranslateX();
double ty = syobochim.getTranslateY();
// 近傍であれば位置の更新を行わない
double d = (mx - tx) * (mx - tx) + (my - ty) * (my - ty);
if (d < DISTANCE * DISTANCE) {
return;
}
// カーソルとsyobochimの角度を算出
double theta = Math.atan2(my - ty, mx - tx);
// 移動分を算出
double dx = DISTANCE * Math.cos(theta);
double dy = DISTANCE * Math.sin(theta);
syobochim.setTranslateX(tx + dx);
syobochim.setTranslateY(ty + dy);
// 角度に応じて回転
// カーソルの右側にsyobocimが位置している場合は反転
syobochim.getTransforms().removeIf(trans -> trans.equals(rotate));
if (theta > PI / 2.0 || theta < -PI / 2.0) {
syobochim.getTransforms().add(rotate);
syobochim.setRotate(theta * 180.0 / PI - 180.0);
} else {
syobochim.setRotate(theta * 180.0 / PI);
}
}後は、透明ステージにするとか、常にトップにもってくるなどを行ってから表示を行っています。
@Override
public void start(Stage stage) {
Group root = new Group();
initImage(root);
// Scene をスクリーンと同サイズに設定
Screen screen = Screen.getPrimary();
Scene scene = new Scene(root, screen.getBounds().getWidth(), screen.getBounds().getHeight());
// 背景を透過にする
scene.setFill(null);
// カーソルを表示しない
scene.setCursor(Cursor.NONE);
stage.setScene(scene);
// 透明ステージにする
stage.initStyle(StageStyle.TRANSPARENT);
stage.setAlwaysOnTop(true);
stage.show();
// Swing の EDT でタイマ処理を行う
startSwingEDT();
}さて、これで完成です。実行してみましょう。

これで、ずっとうらがみさんを追い続けるしょぼちむのできあがりです。
ただ、しょぼちむのイメージはもっと大きくてもよかったなぁ。
それに今回は手抜きなのでちゃんとアニメーションしていないし。時間があったら手や足を動かして、追うようにします。
ついでに、カーソル動かなかったら、寝てしまうというのも取り込みたいなぁ...
ソースは GitHub で公開しています!