非矩形フレーム、あるいは透明フレーム
これもJava SE 6u10 の機能なんですが、JavaFX で非矩形フレームや透明フレームを扱うことができるようになりました。
Java SE 6u10 以前の Java では、java.awt.Robot を使って、擬似透明フレームを作ることはできたんですけど、あくまでも擬似は擬似。
やっと、できるようになってうれしい限りなんですが、Java 単体ではできないのかなぁ。
透明にするのは簡単です。JavaFX Script ではフレームは javafx.application.Frame クラスと javafx.ext.swing.SwingFrame クラスの 2 つがあるのですが、透明にできるのは Frame クラスだけです。
やり方は簡単。WindowStyle を TRANSPARENT にするだけ。後は Stage でコンテンツを指定します。ちなみに、Stage はデフォルトで背景が白になっているので、背景を消すため fill: null とします。
ちなみのちなみ。WindowStyle は他に DECORATED と UNDECORATED があります。
ソースはこんな感じ。これで背景が透明になります。
Frame { width: 100 height: 100 // WindowStyle は TRANSPARENT windowStyle: javafx.application.WindowStyle.TRANSPARENT visible: true stage: Stage { // 背景を塗らないようにする fill: null content: [ ... ] } }
背景が透明にできるというと、やりたくなるのが xeyes。
目がマウスカーソルの方を向くというあれです。
というわけで作ってみました。
参考までにソースを示しておきます。
ドラッグすると移動、マウスの左ボタン以外をクリックするとポップアップメニューで終了させることができます。
import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Frame; import javafx.application.Stage; import javafx.application.WindowStyle; import javafx.input.MouseEvent; import javafx.scene.geometry.Circle; import javafx.scene.geometry.Ellipse; import javafx.scene.paint.Color; import java.lang.Math; import java.lang.System; import java.awt.MouseInfo; import java.awt.Point; import java.awt.PointerInfo; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JPopupMenu; import javax.swing.JMenuItem; var rightX: Number; var rightY: Number; var leftX: Number; var leftY: Number; var strokeWidth: Number; var eyeRadius = bind strokeWidth; var frame: Frame; var width = 150; var height = 100; // ポップアップメニュー用のアクションリスナー class ExitAction extends ActionListener { function actionPerformed(event: ActionEvent) { System.exit(1); } }; // ポップアップメニューを表示 function showPopupmenu(event:MouseEvent) { if (event.getButton() != 1) { var popup = new javax.swing.JPopupMenu(); var menuItem = new javax.swing.JMenuItem("Exit"); menuItem.addActionListener(ExitAction{}); popup.add(menuItem); popup.show(event.awtMouseEvent.getComponent(), event.getX(), event.getY()); } }; // フレームの移動 (ドラッグ時) function moveFrame(event:MouseEvent) { var x = event.getDragX() as Integer; var y = event.getDragY() as Integer; frame.x = frame.x + x; frame.y = frame.y + y; }; // 目玉の位置を計算 function calcEyePosition(x: Number, y: Number, cx: Number, cy: Number): Point { // 角度は arctan で求めます var theta = Math.atan2(y - cy, x - cx); var hr = width/4 - strokeWidth - eyeRadius; var eyeX: Number; var eyeY: Number; // マウスカーソルの位置が目の内部だったら、カーソルの位置に目玉を描画 if (Math.abs(x - cx) > Math.abs(hr * Math.cos(theta))) { eyeX = cx + hr * Math.cos(theta); } else { eyeX = x; } var vr = height/2 - strokeWidth - eyeRadius; if (Math.abs(y - cy) > Math.abs(vr * Math.sin(theta))) { eyeY = height - cy - vr * Math.sin(theta); } else { eyeY = height - y; } return new Point(eyeX as Integer, eyeY as Integer); }; frame = Frame { title: "FXEyes" width: bind width height: bind height // ここで透明にする windowStyle: WindowStyle.TRANSPARENT visible: true stage: Stage { // 背景を透明にする fill: null content: [ Ellipse { // 左目 // フレームの幅と高さで目の大きさを決めているのですが、 // フレームの大きさを可変することはやっていません centerX: bind width/4 centerY: bind height/2 radiusX: bind width/4 - strokeWidth/2 radiusY: bind height/2 - strokeWidth/2 stroke: Color.BLACK strokeWidth: bind strokeWidth fill: Color.WHITE onMousePressed: function(event:MouseEvent) { showPopupmenu(event); } onMouseDragged: function(event:MouseEvent) { moveFrame(event); } }, Circle { // 左の目玉 centerX: bind leftX centerY: bind leftY radius: bind eyeRadius fill: Color.BLACK }, Ellipse { // 右目 centerX: bind width/4 * 3 centerY: bind height/2 radiusX: bind width/4 - strokeWidth/2 radiusY: bind height/2 - strokeWidth/2 stroke: Color.BLACK strokeWidth: bind strokeWidth fill: Color.WHITE onMousePressed: function(event:MouseEvent) { showPopupmenu(event); } onMouseDragged: function(event:MouseEvent) { moveFrame(event); } }, Circle { // 右の目玉 centerX: bind rightX centerY: bind rightY radius: bind eyeRadius fill: Color.BLACK } ] } }; var timeline = Timeline { repeatCount: Timeline.INDEFINITE keyFrames : [ KeyFrame { // 50ms ごとに目玉の位置を更新 time : 50ms action: function() { var point:PointerInfo = MouseInfo.getPointerInfo(); var x = point.getLocation().getX(); var y = point.getLocation().getY(); x = x - frame.x; y = - y + frame.y + frame.height; // ほんとはフレームの大きさは可変にしていないので、 // こんなこといらないんですが... if (width < height) { strokeWidth = if (width > 100) 10 else width/10; } else { strokeWidth = if (height > 100) 10 else height/10; } strokeWidth = if (strokeWidth < 1) 1 else strokeWidth; var p = calcEyePosition(x, y, width/4, height/2); System.out.println("x {p.x} y {p.y}"); leftX = p.x; leftY = p.y; p = calcEyePosition(x, y, width/4*3, height/2); rightX = p.x; rightY = p.y; } } ] }; timeline.start();