追うしょぼちむ
このエントリーは しょぼちむ 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 で公開しています!