/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2013, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing.render3d.control;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.logging.Level;
import org.geotoolkit.display3d.Map3D;
import org.geotoolkit.display3d.scene.camera.TrackBallCamera;
/**
*
* @author Thomas Rouby (Geomatys)
*/
public class JAllControlDecoration extends JComponent implements PropertyChangeListener {
private static final int ACTION_UNKNOW = -1;
private static final int ACTION_RESET = 0;
private static final int ACTION_MOVE_LEFT = 1;
private static final int ACTION_MOVE_BOTTOM = 2;
private static final int ACTION_MOVE_RIGHT = 3;
private static final int ACTION_MOVE_TOP = 4;
private static final int ACTION_ROTATE_LEFT = 5;
private static final int ACTION_ROTATE_BOTTOM = 6;
private static final int ACTION_ROTATE_RIGHT = 7;
private static final int ACTION_ROTATE_TOP = 8;
private static final int ACTION_ROTATION = 9;
private static final int[] MARGIN = new int[]{5,5,5,5};
private static final int RADIUS_RESET_BUTTON = 4;
private static final int RADIUS_CERCLE_INNER = 16 + RADIUS_RESET_BUTTON;
private static final int RADIUS_CERCLE_OUTER = 18 + RADIUS_CERCLE_INNER;
private static final int RADIUS_CERCLE_ROTATION = 14 + RADIUS_CERCLE_OUTER;
private static final int[] CENTER = new int[]{MARGIN[0] + RADIUS_CERCLE_ROTATION, MARGIN[3] + RADIUS_CERCLE_ROTATION};
private static final int[] SIZE = new int[]{MARGIN[0] + RADIUS_CERCLE_ROTATION*2 + MARGIN[2], MARGIN[3] + RADIUS_CERCLE_ROTATION*2 + MARGIN[1]};
private final BufferedImage buffer = new BufferedImage(SIZE[0], SIZE[1], BufferedImage.TYPE_INT_ARGB);
private final Shape content = new java.awt.geom.Ellipse2D.Float(CENTER[0]-RADIUS_CERCLE_ROTATION, CENTER[1]-RADIUS_CERCLE_ROTATION, RADIUS_CERCLE_ROTATION*2, RADIUS_CERCLE_ROTATION*2);
private final Color white;
private final Color gray;
private final Color black;
private final Color red;
private final Color over;
private final BufferedImage[] ARROWS_MOVE;
private final BufferedImage[] ARROWS_ROTATION;
private boolean mustUpdate = true;
private double rotate = 0.0; // Angle (radian) of the rotation
private final TrackBallCamera camera;
/**
* -1 = no action
* 9 = rotation
*/
private int actionFlag = ACTION_UNKNOW;
/**
* -1 = no button
* 0 = reset button
* 1,2,3,4 = left,bottom,right,top button on inner cercle
* 5,6,7,8 = left,bottom,right,top button on outer cercle
* 9 = rotate button
*/
private int overButton = ACTION_UNKNOW;
public JAllControlDecoration(TrackBallCamera camera){
this.camera = camera;
this.camera.addPropertyChangeListener(this);
white = Color.WHITE;
gray = Color.LIGHT_GRAY;
black = Color.GRAY;
red = Color.RED;
over = Color.DARK_GRAY;
addMouseListener(mouseListener);
addMouseMotionListener(mouseMotionListener);
setOpaque(false);
ARROWS_MOVE = new BufferedImage[4];
ARROWS_ROTATION = new BufferedImage[4];
try{
ARROWS_MOVE[0] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("img/icon/arrow_left.png"));
ARROWS_MOVE[1] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("img/icon/arrow_bottom.png"));
ARROWS_MOVE[2] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("img/icon/arrow_right.png"));
ARROWS_MOVE[3] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("img/icon/arrow_top.png"));
ARROWS_ROTATION[0] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("img/icon/rotate_left.png"));
ARROWS_ROTATION[1] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("img/icon/rotate_bottom.png"));
ARROWS_ROTATION[2] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("img/icon/rotate_right.png"));
ARROWS_ROTATION[3] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("img/icon/rotate_top.png"));
} catch (IOException ex) {
Map3D.LOGGER.log(Level.WARNING, "icon not found", ex);
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
//event from the camera
this.setRotate(Math.toRadians(camera.getRotateZ()));
}
public void rotateMap(double rotate){
camera.setRotateZ((float)Math.toDegrees(rotate));
}
public void setRotate(double rotation){
if (this.rotate != rotation) {
this.rotate = rotation;
this.mustUpdate = true;
this.repaint(this.content.getBounds());
}
}
public double getRotate(){
return this.rotate;
}
private double calculateAngle(final int mouseX, final int mouseY){
final Point pa = new Point( CENTER[0] ,0);
final Point pb = new Point( CENTER[0] , CENTER[1] );
final Point pc = new Point(mouseX,mouseY);
final double a = Math.pow( Math.pow( pc.x - pb.x , 2) + Math.pow( pc.y - pb.y , 2) ,0.5d);
final double b = Math.pow( Math.pow( pa.x - pc.x , 2) + Math.pow( pa.y - pc.y , 2) ,0.5d);
final double c = Math.pow( Math.pow( pa.x - pb.x , 2) + Math.pow( pa.y - pb.y , 2) ,0.5d);
// double angleA = Math.acos( ( Math.pow(b, 2) + Math.pow(c, 2) - Math.pow(a, 2) )/(2*b*c) );
double angleB = Math.acos( ( Math.pow(a, 2) + Math.pow(c, 2) - Math.pow(b, 2) )/(2*a*c) );
// double angleC = Math.acos( ( Math.pow(a, 2) + Math.pow(b, 2) - Math.pow(c, 2) )/(2*a*b) );
if(mouseX > CENTER[1] ){
angleB = 2* Math.PI - angleB;
}
return angleB;
}
public void actionPerformed(int action, double value){
switch (action){
case ACTION_RESET :
rotateMap(0.0);
break;
case ACTION_MOVE_LEFT :
camera.moveLeft((float)(value*camera.getViewScale(this.camera.getLength())));
camera.updateCameraElevation();
break;
case ACTION_MOVE_BOTTOM :
camera.moveBack((float)(value*camera.getViewScale(this.camera.getLength())));
camera.updateCameraElevation();
break;
case ACTION_MOVE_RIGHT :
camera.moveRight((float)(value*camera.getViewScale(this.camera.getLength())));
camera.updateCameraElevation();
break;
case ACTION_MOVE_TOP :
camera.moveFront((float)(value*camera.getViewScale(this.camera.getLength())));
camera.updateCameraElevation();
break;
case ACTION_ROTATE_LEFT :
rotateMap(this.rotate + Math.toRadians(value));
break;
case ACTION_ROTATE_BOTTOM :
camera.rotateDown((float)value);
break;
case ACTION_ROTATE_RIGHT :
rotateMap(this.rotate - Math.toRadians(value));
break;
case ACTION_ROTATE_TOP :
camera.rotateUp((float)value);
break;
case ACTION_ROTATION :
rotateMap(value);
break;
case ACTION_UNKNOW :
default :
break;
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D g2d = (Graphics2D) g;
final Rectangle clip = g2d.getClipBounds();
if(!clip.intersects(0,0,buffer.getWidth(),buffer.getHeight())) return;
if(mustUpdate){
final Graphics2D g2 = buffer.createGraphics();
final Composite origComposite = g2.getComposite();
final Composite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
g2.setStroke(new BasicStroke(1));
g2.setFont(g2d.getFont().deriveFont(Font.BOLD));
g2.setBackground(new Color(0f,0f,0f,0f));
g2.clearRect(0, 0, buffer.getWidth(), buffer.getHeight());
g2.setRenderingHints(g2d.getRenderingHints());
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int justRotationRadius = RADIUS_CERCLE_ROTATION - RADIUS_CERCLE_OUTER;
// DRAW ROTATION CERCLE
g2.setPaint(this.black);
g2.drawOval(CENTER[0]-RADIUS_CERCLE_ROTATION+justRotationRadius/2, CENTER[1]-RADIUS_CERCLE_ROTATION+justRotationRadius/2, RADIUS_CERCLE_ROTATION*2-justRotationRadius-1, RADIUS_CERCLE_ROTATION*2-justRotationRadius-1);
// DRAW OUTER CERCLE
g2.setPaint(this.gray);
g2.fillOval(CENTER[0]-RADIUS_CERCLE_OUTER, CENTER[1]-RADIUS_CERCLE_OUTER, RADIUS_CERCLE_OUTER*2, RADIUS_CERCLE_OUTER*2);
// LEFT ROTATION ARROW
g2.setComposite((overButton == ACTION_ROTATE_LEFT)?alphaComposite:origComposite);
g2.drawImage(this.ARROWS_ROTATION[0], CENTER[0]-RADIUS_CERCLE_OUTER+2, CENTER[1]-this.ARROWS_ROTATION[0].getHeight()/2, null);
// BOTTOM ROTATION ARROW
g2.setComposite((overButton == ACTION_ROTATE_BOTTOM)?alphaComposite:origComposite);
g2.drawImage(this.ARROWS_ROTATION[1], CENTER[0]-this.ARROWS_ROTATION[1].getWidth()/2, CENTER[1]+RADIUS_CERCLE_OUTER-2-this.ARROWS_ROTATION[1].getHeight(), null);
// RIGHT ROTATION ARROW
g2.setComposite((overButton == ACTION_ROTATE_RIGHT)?alphaComposite:origComposite);
g2.drawImage(this.ARROWS_ROTATION[2], CENTER[0]+RADIUS_CERCLE_OUTER-2-this.ARROWS_ROTATION[2].getWidth(), CENTER[1]-this.ARROWS_ROTATION[2].getHeight()/2, null);
// TOP ROTATION ARROW
g2.setComposite((overButton == ACTION_ROTATE_TOP)?alphaComposite:origComposite);
g2.drawImage(this.ARROWS_ROTATION[3], CENTER[0]-this.ARROWS_ROTATION[3].getWidth()/2, CENTER[1]-RADIUS_CERCLE_OUTER+2, null);
// DRAW INNER CERCLE
g2.setComposite(origComposite);
g2.setPaint(this.white);
g2.fillOval(CENTER[0]-RADIUS_CERCLE_INNER, CENTER[1]-RADIUS_CERCLE_INNER, RADIUS_CERCLE_INNER*2, RADIUS_CERCLE_INNER*2);
// LEFT ROTATION ARROW
g2.setComposite((overButton == ACTION_MOVE_LEFT)?alphaComposite:origComposite);
g2.drawImage(this.ARROWS_MOVE[0], CENTER[0]-RADIUS_CERCLE_INNER+2, CENTER[1]-this.ARROWS_MOVE[0].getHeight()/2, null);
// BOTTOM ROTATION ARROW
g2.setComposite((overButton == ACTION_MOVE_BOTTOM)?alphaComposite:origComposite);
g2.drawImage(this.ARROWS_MOVE[1], CENTER[0]-this.ARROWS_MOVE[1].getWidth()/2, CENTER[1]+RADIUS_CERCLE_INNER-2-this.ARROWS_MOVE[1].getHeight(), null);
// RIGHT ROTATION ARROW
g2.setComposite((overButton == ACTION_MOVE_RIGHT)?alphaComposite:origComposite);
g2.drawImage(this.ARROWS_MOVE[2], CENTER[0]+RADIUS_CERCLE_INNER-2-this.ARROWS_MOVE[2].getWidth(), CENTER[1]-this.ARROWS_MOVE[2].getHeight()/2, null);
// TOP ROTATION ARROW
g2.setComposite((overButton == ACTION_MOVE_TOP)?alphaComposite:origComposite);
g2.drawImage(this.ARROWS_MOVE[3], CENTER[0]-this.ARROWS_MOVE[3].getWidth()/2, CENTER[1]-RADIUS_CERCLE_INNER+2, null);
// DRAW RESET CERCLE
g2.setComposite((overButton == ACTION_RESET)?alphaComposite:origComposite);
g2.setPaint(this.black);
g2.fillOval(CENTER[0]-RADIUS_RESET_BUTTON, CENTER[1]-RADIUS_RESET_BUTTON, RADIUS_RESET_BUTTON*2, RADIUS_RESET_BUTTON*2);
// DRAW ROTATION BUTTON
g2.setComposite(origComposite);
g2.rotate(-this.rotate, CENTER[0], CENTER[1]);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
final int posX = CENTER[0] - justRotationRadius/4;
final int posY = CENTER[1] - RADIUS_CERCLE_ROTATION + justRotationRadius/4 ;
g2.setColor(red);
g2.fillOval(posX, posY, justRotationRadius / 2, justRotationRadius / 2);
g2.rotate(this.rotate, CENTER[0], CENTER[1]);
mustUpdate = false;
}
g2d.drawImage(buffer, 0, 0, this);
g2d.dispose();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(buffer.getWidth(),buffer.getHeight());
}
@Override
public Dimension getSize() {
return getPreferredSize();
}
@Override
public Dimension getSize(final Dimension rv) {
if(rv != null){
rv.height = buffer.getHeight();
rv.width = buffer.getWidth();
return rv;
}else{
return getSize();
}
}
@Override
public int getWidth() {
return this.getSize().width;
}
@Override
public int getHeight() {
return this.getSize().height;
}
@Override
public boolean contains(final int x, final int y) {
return content.contains(x, y);
}
private final MouseListener mouseListener = new MouseListener() {
boolean mousePressed = false;
@Override
public void mouseClicked(final MouseEvent e) {
}
@Override
public void mousePressed(final MouseEvent e) {
final Point mouse = e.getPoint();
if(!content.contains(mouse)) return;
Thread actionThread = new Thread(new Runnable() {
@Override
public void run() {
mousePressed = true;
while (mousePressed) {
final double tx = mouse.x - CENTER[0] ;
final double ty = mouse.y - CENTER[1] ;
final double distance = Math.hypot(tx, ty);
if (distance <= RADIUS_RESET_BUTTON){
actionPerformed(ACTION_RESET, 0.0);
mousePressed = false;
} else if (distance <= RADIUS_CERCLE_INNER){
if (tx>ty && -tx>ty) {
actionPerformed(ACTION_MOVE_TOP, 5.0);
} else if (tx<ty && -tx<ty) {
actionPerformed(ACTION_MOVE_BOTTOM, 5.0);
} else if (tx<ty && -tx>ty) {
actionPerformed(ACTION_MOVE_LEFT, 5.0);
} else if (tx>ty && -tx<ty) {
actionPerformed(ACTION_MOVE_RIGHT, 5.0);
}
} else if (distance <= RADIUS_CERCLE_OUTER){
if (tx>ty && -tx>ty) {
actionPerformed(ACTION_ROTATE_TOP, 1.0);
} else if (tx<ty && -tx<ty) {
actionPerformed(ACTION_ROTATE_BOTTOM, 1.0);
} else if (tx<ty && -tx>ty) {
actionPerformed(ACTION_ROTATE_LEFT, 1.0);
} else if (tx>ty && -tx<ty) {
actionPerformed(ACTION_ROTATE_RIGHT, 1.0);
}
} else if (distance <= RADIUS_CERCLE_ROTATION){
actionFlag = ACTION_ROTATION;
actionPerformed(ACTION_ROTATION, calculateAngle(mouse.x, mouse.y));
mousePressed = false;
} else {
actionFlag = ACTION_UNKNOW;
mousePressed = false;
}
if (mousePressed){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
actionThread.setDaemon(true);
actionThread.start();
}
@Override
public void mouseReleased(MouseEvent e) {
mousePressed = false;
actionFlag = ACTION_UNKNOW;
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
mousePressed = false;
if(overButton != ACTION_UNKNOW){
overButton = ACTION_UNKNOW;
mustUpdate = true;
repaint(content.getBounds());
}
}
};
private final MouseMotionListener mouseMotionListener = new MouseMotionListener() {
@Override
public void mouseDragged(final MouseEvent e) {
if (actionFlag == ACTION_ROTATION) {
actionPerformed(ACTION_ROTATION, calculateAngle(e.getX(),e.getY()));
}
}
@Override
public void mouseMoved(final MouseEvent e) {
final Point mouse = e.getPoint();
//we repaint inner buttons
if(content.contains(mouse)){
final int oldOver = overButton;
final double tx = mouse.x - CENTER[0];
final double ty = mouse.y - CENTER[1];
final double distance = Math.hypot(tx, ty);
if (distance <= RADIUS_RESET_BUTTON){
overButton = ACTION_RESET;
} else if (distance <= RADIUS_CERCLE_INNER){
if (tx>ty && -tx>ty) {
overButton = ACTION_MOVE_TOP;
} else if (tx<ty && -tx<ty) {
overButton = ACTION_MOVE_BOTTOM;
} else if (tx<ty && -tx>ty) {
overButton = ACTION_MOVE_LEFT;
} else if (tx>ty && -tx<ty) {
overButton = ACTION_MOVE_RIGHT;
}
} else if (distance <= RADIUS_CERCLE_OUTER){
if (tx>ty && -tx>ty) {
overButton = ACTION_ROTATE_TOP;
} else if (tx<ty && -tx<ty) {
overButton = ACTION_ROTATE_BOTTOM;
} else if (tx<ty && -tx>ty) {
overButton = ACTION_ROTATE_LEFT;
} else if (tx>ty && -tx<ty) {
overButton = ACTION_ROTATE_RIGHT;
}
} else {
overButton = ACTION_UNKNOW;
}
if(oldOver != overButton){
mustUpdate = true;
repaint(content.getBounds());
}
} else if(overButton != ACTION_UNKNOW) {
overButton = ACTION_UNKNOW;
mustUpdate = true;
repaint(content.getBounds());
}
}
};
}