1 package camidion.chordhelper.anogakki;
3 import java.awt.BasicStroke;
5 import java.awt.Component;
6 import java.awt.Graphics;
7 import java.awt.Graphics2D;
9 import java.awt.Rectangle;
10 import java.awt.Stroke;
11 import java.awt.geom.AffineTransform;
12 import java.util.Iterator;
13 import java.util.LinkedList;
14 import java.util.List;
16 import javax.swing.JComponent;
17 import javax.swing.JLayeredPane;
18 import javax.swing.SwingUtilities;
19 import javax.swing.Timer;
22 * Innocence「あの楽器」風の表示を行う拡張クラスです。
24 * <p>{@link #start()} メソッドで表示を開始でき、
25 * 時間が経過すると表示が自然に消えるようになっています。
27 * <p>画面を「あの楽器」化するには、その画面とともに
28 * このクラスのインスタンスを {@link JLayeredPane} で重ね合わせます。
31 public class AnoGakkiPane extends JComponent {
35 private static final int INTERVAL_MS = 15;
39 private static final int INITIAL_COUNT = 20;
43 private static final double MAX_OMEGA = 0.005;
47 private static enum Shape {
50 public void draw(Graphics2D g2, QueueEntry entry) {
56 public void draw(Graphics2D g2, QueueEntry entry) {
62 public void draw(Graphics2D g2, QueueEntry entry) {
68 public void draw(Graphics2D g2, QueueEntry entry) {
69 entry.drawTriangle(g2);
76 public static Shape randomShape() {
77 return values()[(int)(Math.random() * values().length)];
82 * @param entry キューエントリ
84 public abstract void draw(Graphics2D g2, QueueEntry entry);
87 * 色(RGBA、Aは「不透明度」を表すアルファ値)
89 private static final Color color = new Color(0,255,255,192);
93 private static final Stroke stroke = new BasicStroke((float)5);
95 * いま描画すべき図形を覚えておくためのキュー
97 private List<QueueEntry> queue = new LinkedList<QueueEntry>();
101 private class QueueEntry {
103 private int countdown = INITIAL_COUNT;
104 /** スタートからの経過時間(ミリ秒) */
108 AnoGakkiPane.Shape shape = Shape.randomShape();
110 private Point clickedPoint;
116 private double omega = 0;
117 /** 現在の回転角(アフィン変換) */
118 AffineTransform affineTransform = null;
121 * @param clickedPoint クリックされた場所
123 public QueueEntry(Point clickedPoint) {
124 this.clickedPoint = clickedPoint;
125 if( shape != Shape.CIRCLE ) {
127 //(○は回転しても見かけ上何も変わらなくて無駄なので除外)
128 affineTransform = AffineTransform.getRotateInstance(
129 2 * Math.PI * Math.random(),
133 omega = MAX_OMEGA * (1.0 - 2.0 * Math.random());
137 * このキューエントリをカウントダウンします。
138 * @return カウントダウン値(0 でタイムアウト)
140 public int countDown() {
141 if( countdown > 0 ) {
148 if( shape == Shape.SQUARE || shape == Shape.TRIANGLE ) {
149 // 角度を θ=ωt で求めると、移動距離 l=rθ が
150 // t の2乗のオーダーで伸びるため、加速しているように見えてしまう。
151 // 一定の速度に見せるために t を平方根にして角度を計算する。
152 affineTransform.rotate(
153 omega * Math.sqrt((double)tms),
165 public void drawCircle(Graphics2D g2) {
167 g2.drawOval( clickedPoint.x-r, clickedPoint.y-r, d, d );
173 public void drawLines(Graphics2D g2) {
174 int width2 = 2 * getSize().width;
175 int y = clickedPoint.y;
176 g2.transform(affineTransform);
177 g2.drawLine( -width2, y-r, width2, y-r );
178 g2.drawLine( -width2, y+r, width2, y+r );
184 public void drawSquare(Graphics2D g2) {
186 g2.transform(affineTransform);
187 g2.drawRect( clickedPoint.x-r, clickedPoint.y-r, d, d );
193 public void drawTriangle(Graphics2D g2) {
194 int x = clickedPoint.x;
195 int y = clickedPoint.y;
196 g2.transform(affineTransform);
197 g2.drawLine( x-r, y, x+r, y-r );
198 g2.drawLine( x-r, y, x+r, y+r );
199 g2.drawLine( x+r, y-r, x+r, y+r );
203 * キューを更新するアニメーション用タイマー
205 public AnoGakkiPane() {
207 timer = new Timer(INTERVAL_MS, e->{
208 synchronized(queue) {
209 Iterator<QueueEntry> i = queue.iterator();
210 while(i.hasNext()) if(i.next().countDown() <= 0) i.remove();
212 if(queue.isEmpty()) timer.stop();
215 {setCoalesce(true);setRepeats(true);}
220 public void paint(Graphics g) {
221 if(queue.isEmpty()) return;
222 Graphics2D g2 = (Graphics2D)g;
223 g2.setStroke(stroke);
225 synchronized(queue) { queue.forEach(e -> e.shape.draw(g2, e)); }
228 * 指定された長方形領域({@link Rectangle})の中央から図形の表示を開始します。
229 * @param source AWTコンポーネント
230 * @param rect AWTコンポーネント内の座標系を基準とした長方形領域
232 public void start(Component source, Rectangle rect) {
233 Point point = rect.getLocation();
234 point.translate( rect.width/2, rect.height/2 );
235 start(source, point);
237 private long prevStartedAt = System.nanoTime();
239 * 指定された場所から図形の表示を開始します。
240 * @param source AWTコンポーネント
241 * @param point AWTコンポーネント内の座標系を基準とした場所
243 public void start(Component source, Point point) {
244 long startedAt = System.nanoTime();
245 if( startedAt - prevStartedAt < (INTERVAL_MS * 1000)*50 ) {
249 point = SwingUtilities.convertPoint(source, point, this);
250 synchronized(queue) { queue.add(new QueueEntry(point)); }
252 prevStartedAt = startedAt;