Java Translucent Selection Box - java

For custom rendering, I've created a class that extends JPanel and overrides the paintComponent method. In the custom paintComponent I rendering multiple shape objects held in a array. What I would like to add is the ability to drag and select 1 or more of the shapes. While dragging I would like to show a translucent rectangle defining the selection region akin to what is seen in Windows Explorer. Can any provide a starting point for accomplishing this?
Thanks.

I saw an interesting way of doing this in JFreeChart's source code. You can draw a marquee over a section of the chart, and when you release the mouse the chart zooms in on the selected are. Re-rending the chart is expensive and unfortunately JFreeChart doesn't support partial paints of a chart. So to draw the marquee they do some sort of bitwise operation to the colors of the component, in a reversible fashion. Every time the mouse moves while selecting a marquee, you reverse the previous bitwise operation on the old coordinates, then redo it on the new coordinates.
Take a look at ChartPanel.java in JFreeChart
private void drawZoomRectangle(Graphics2D g2) {
// Set XOR mode to draw the zoom rectangle
g2.setXORMode(Color.gray);
if (this.zoomRectangle != null) {
if (this.fillZoomRectangle) {
g2.fill(this.zoomRectangle);
}
else {
g2.draw(this.zoomRectangle);
}
}
// Reset to the default 'overwrite' mode
g2.setPaintMode();
}

You could use JXLayer. Span it across the whole form and do the painting in a custom LayerUI.

All,
Thanks for the suggestions. Ended up resolving this by adapting some the code used in this rather clever demo. http://forums.sun.com/thread.jspa?threadID=5299064&start=19
public class ExamplePanel extends JPanel
{
Rectangle2D.Double selectionRect;
Point mouseDown, mouseHere;
...
protected void paintComponent(Graphics g)
{
AlphaComposite ta = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f);
g2d.setComposite(ta);
g2d.setColor(Color.BLUE);
g2d.fill(selectionRect);
g2d.setComposite(AlphaComposite.SrcOver);
g2d.setColor(Color.BLACK);
g2d.draw(selectionRect);
}
}
public class ExammpleMouseListener extends MouseAdapter
{
#Override
public void mousePressed(MouseEvent e)
{
super.mousePressed(e);
// store the mouse down location
pnl.mouseDown = e.getPoint();
}
/**
* #see java.awt.event.MouseAdapter#mouseDragged(java.awt.event.MouseEvent)
*/
#Override
public void mouseDragged(MouseEvent e)
{
super.mouseDragged(e);
// check for left mouse button
if ((e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) == 0)
{
return;
}
// store the current location
pnl.mouseHere = e.getPoint();
// calculate the size of the selection rectangle
double downX = pnl.mouseDown.getX();
double downY = pnl.mouseDown.getY();
double hereX = pnl.mouseHere.getX();
double hereY = pnl.mouseHere.getY();
double l = Math.min(downX, hereX);
double t = Math.min(downY, hereY);
double w = Math.abs(downX - hereX);
double h = Math.abs(downY - hereY);
pnl.selectionRect = new Rectangle2D.Double(l, t, w, h);
// queue a repaint of the panel
pnl.repaint();
}
#Override
public void mouseReleased(MouseEvent e)
{
super.mouseReleased(e);
// clear the selection rectangle
pnl.selectionRect = null;
// queue a repaint of the panel
pnl.repaint();
}
}
}

Sure, here's an simple example with methods for creating and moving the shape.
class MyShape implements Shape {
private Shape shape;
public void createShape(Point p1, Point p2, ShapeType t) {
switch(t) {
case RECTANGLE: {
shape = new Rectangle2D.Double(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
break;
}
... (other shapes)
}
}
public void moveShape(Point lastPoint, Point newPoint, ShapeType t) {
int xOffset = newPoint.x - lastPoint.x;
int yOffset = newPoint.y - lastPoint.y;
switch(t) {
case RECTANGLE: {
double x1 = shape.getBounds().getX() + xOffset;
double y1 = shape.getBounds().getY() + yOffset;
double w = shape.getBounds().getWidth();
double h = shape.getBounds().getHeight();
shape = new Rectangle2D.Double(x1, y1, w, h);
break;
}
... (other shapes)
}
}
}

For some components ( I don't know if this apply to your components while being dragged ) you can set the background and use a "transparent color"
The Color class does implements Transparency.
To use it you may specify the alpha value in the Color constructor.
For instance this is a semi transparent black background:
// 0: totally transparent
// 255: totally opaque,
// 192 semy transparent.
this.setBackground(new Color( 0, 0, 0, 192 ));
See the [constructor][1]
Again, I'm not sure if this applies to you. Give it a try
[1]: http://java.sun.com/javase/6/docs/api/java/awt/Color.html#Color(int, int, int, int)

Related

Draw Line While Dragging The Mouse

What I'm trying to do is to draw circles and lines.
When the mouse is first pressed, I draw a small circle. Then, I need
to draw a line connecting the original point to the current
position of the mouse. When the mouse is released, the line
remains, but when I click again, everything disappears and I draw a
circle and a line all over again.
This is the code I have so far:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Canvas4 extends JComponent implements MouseListener, MouseMotionListener {
//constructor
public Canvas4() {
super();
addMouseListener(this);
addMouseMotionListener(this);
}
//some variables I may or may not use
int pressedX;
int pressedY;
int currentX;
int currentY;
int startDragX;
int startDragY;
int endDragX;
int endDragY;
int mouseReleasedX;
int mouseReleasedY;
//mouse events
public void mouseClicked(MouseEvent event) { }
public void mouseEntered(MouseEvent event) { }
public void mouseExited(MouseEvent event) { }
public void mousePressed(MouseEvent event) {
pressedX = event.getX();
pressedY = event.getY();
drawCircle();
startDragX = pressedX;
startDragY = pressedY;
}
public void mouseReleased(MouseEvent event) {
mouseReleasedX = event.getX();
mouseReleasedY = event.getY();
//repaint() here maybe???
}
//mouse motion events
public void mouseDragged(MouseEvent event) {
System.out.println("You dragged the mouse.");
endDragX = event.getX();
endDragY = event.getY();
drawLine();
}
public void mouseMoved(MouseEvent event) { }
//draw circle when mouse pressed
//this method works fine
public void drawCircle() {
Graphics g1 = this.getGraphics();
g1.setColor(Color.CYAN);
g1.fillOval(pressedX, pressedY, 10, 10);
}
//draw line when mouse dragged
//this is where I need help
public void drawLine() {
Graphics g2 = this.getGraphics();
g2.setColor(Color.RED);
g2.drawLine(pressedX, pressedY, mouseReleasedX, mouseReleasedY);
}
}
Then of course, there's a main method that creates the class object and adds it to a frame and whatnot.
My two specific questions are:
how do I draw a line as it's dragged? The code I have currently only draws a line to the last point of mouse release.
When do I repaint? If I repaint in the drawCircle() method, the circle blinks in and out instead of disappearing on the next click.
If you want to draw circles and lines then you need to keep an ArrayList of Shapes to draw. You would add an Ellipse2D.Double for the circle and a Line2D.Double for the line.
In the mousePressed event you add the Ellipse2D.Double object to the ArrayList, then you set up a temporary Line2D.Double object to contain your line information.
In the mouseDragged event you update Line2D.Double object with the new end point and then invoke repaint().
In the mouseReleased event you add the Line2D.Double object to the ArrayList and clear the variable referencing the Line2D.Double object.
Then in the paintComponent() method you add logic to:
iterate through the ArrayList to paint each Shape.
draw the Line2D.Double object when not null
Check out the Draw On Component example found in Custom Painting Approaches. This will show you the basic concept of this approach.
In the example the ArrayList only contains information on Rectangles so you will need to make it more generic to hold a Shape object. Both Ellispse2D.Double and Line2D.Double implement the Shape interface.
For drawing lines i have this.When you click mouse left you retain a point next click will retain another point making a line between them , and with mouse right you make the line between first point and last point (you can delete this "if (isClosed)" if you dont want)
Another thing : it's not a good precision because pointlocation return a double and drawline need an integer and the cast loses precision.
public class PolygonOnClick extends JPanel implements MouseListener, MouseMotionListener {
ArrayList<Point> points = new ArrayList<>();
static boolean isClosed = false;
PolygonOnClick() {
JFrame frame = new JFrame("Polygon ON CLICK");
frame.addMouseListener(this);
frame.setLocation(80, 50);
frame.setSize(1000, 700);
frame.add(this);
frame.setResizable(false);
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
graphics.drawString("Click stanga pentru a incepe a desena , click dreapta pentru a inchide poligonul ", 15, 15);
for (int i = 1; i < points.size(); i++) {
graphics.drawLine((int) points.get(i - 1).getX(), (int) points.get(i - 1).getY(), (int) points.get(i).getX(), (int) points.get(i).getY());
}
if (isClosed) {
graphics.drawLine((int) points.get(points.size() - 1).getX(), (int) points.get(points.size() - 1).getY(), (int) points.get(0).getX(), (int) points.get(0).getY());
}
}
#Override
public void mousePressed(MouseEvent e) {
if (!isClosed) {
if (e.getButton() == MouseEvent.BUTTON1) {
points.add(e.getPoint().getLocation());
}
}
if (e.getButton() == MouseEvent.BUTTON3) {
isClosed = true;
}
repaint();
}

Java create a brush stroke action

When you draw a freehand line with any paint application with whatever brush it ends up stacking together multiple points of that brush to form a brush stroke.
For example a basic pen stroke would stack up 1 pixel as you drag the mouse.
In more advanced applications you have a brush which is just a fancy Shape, say: a star for example, and stroking the canvas with the "star brush" would just cause the paint application to draw multiple stars as you drag your mouse over the canvas.
Please correct me if I'm wrong.
I already have implemented the "brush" ( i.e. a basic circle ) and whenever the user drags their mouse over the canvas while holding the left-mouse-button the application draws a circle for each new mouse position.
My problem is the "undo feature" if you may call it this way.
When I undo an action, my application only deletes the last Shape ( circle ) drawn, while I want it to delete the whole free-hand drawing ( collection of Shapes / circles ) from the user first press of the left-mouse-button to the release.
How do I "pack" a collection a Shape objects into one ?
A problem is also the repainting of all those circles, I want the repainting of maybe 30000 circles to be fast, just like a BufferedImage.
I already use a BufferedImage as the background of my image.
Every Shape that is "older" than 50 gets permanently stored in the BufferedImage background.
Currently I store the last 50 Shape objects in an ArrayList and the 51st ( the oldest ) gets permanently stored in the BufferedImage.
So the user can't undo 50 actions but rather 50 Shapes.
Thank You!
stripped down code sample:
public class GraphicPanel extends JComponent{
private int x = 0;
private int y = 0;
private int compXLen = 100;
private int compYLen = 100;
private boolean isCompFilled = false;
private boolean isAreaToBePainted = false;
private boolean isFocused = false;
private Shape ghost;
private ArrayList<Shape> shapeBuffer = new ArrayList<Shape>();
private BufferedImage img = new BufferedImage( PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB );
private static final int PREF_W = 800;
private static final int PREF_H = 500;
#Override
public void paintComponent( Graphics gPlain ){
super.repaint();
Graphics2D g = (Graphics2D)gPlain;
//paint background
if (img != null){
g.drawImage(img, 0, 0, null);
}
ghost = new Ellipse2D.Double( x-( compXLen/2 ), y-( compYLen/2 ), compXLen, compYLen );
if( isAreaToBePainted ){
//add ghost Shape to ArrayList
add( g, ghost )
}
//paint arrayList
for( Shape s : shapeBuffer ){
g.fill( s );
}
if( isFocused ){
// draw ghost shape
g.draw( ghost );
}
}
/**
* adds circles to arrayList
*/
private void add( Graphics2D g, Shape s ){
//fetch last arrayList element in Shape shp
//add ghost shape at the top of arrayList
Graphics2D g2 = img.createGraphics();
shapeBuffer.add( shp );
g2.fill( shp );
g2.dispose();
}
public void clearArea(){
shapeBuffer = new ArrayList<Shape>();
img = new BufferedImage( PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB );
repaint();
}
private class GraphicPanelMouseListen implements MouseListener, MouseMotionListener{
/**
* #param e Mouse Event
* #since 0.1
*/
#Override
public void mouseClicked( MouseEvent e ){}
public void mousePressed( MouseEvent e ){
x = e.getX();
y = e.getY();
isAreaToBePainted = true;
repaint();
}
public void mouseReleased( MouseEvent e ){}
public void mouseEntered( MouseEvent e ){
isFocused = true;
}
public void mouseExited( MouseEvent e ){
isFocused = false;
repaint();
}
public void mouseDragged( MouseEvent e ){
isAreaToBePainted = true;
x = e.getX();
y = e.getY();
repaint();
}
public void mouseMoved( MouseEvent e ){
x = e.getX();
y = e.getY();
repaint();
}
}//public class GraphicPanelMouseListen implements MouseListener
}//public class GraphicPanel
You can avoid using these lists altogether by storing every finished brush stroke, or any action for that matter, in its own image. You can then layer these in order they were made. It will be fewer of them, you can easily define how many actions in past you want to have while the bottom most one containing everything that didn't fit in the history.
Pixel blit is relatively fast operation, unlike stuff you do with shapes, and you can easily make it faster by using VolatileImage instead of buffered image for collecting all actions in history.
In my opinion is this approach much faster, and less restricting for actions added in the future. You can easily just mark certain layers as invisible and move back and forth through history like in Photoshop for instance, but without any dependency on actual contents of said layers.

Drawing lines in GUI with arrow keys using keylistener in Java

I am working on a keylistener exercise for my java class, but have been stuck for the past week. I appreciate any helpful suggestions. The exercise is:
"Write a program that draws line segments using the arrow keys. The
line starts from the center of the frame and draws toward east, north,
west, or south when the right-arrow key, up-arrow key, left-arrow key,
or down-arrow key is clicked."
Through debugging I figured out that the KeyListener works to the point
of getting to drawComponent(Graphics g), but it only draws when I press
down or right and that only works the first couple times. Here is my code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class EventProgrammingExercise8 extends JFrame {
JPanel contentPane;
LinePanel lines;
public static final int SIZE_OF_FRAME = 500;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
EventProgrammingExercise8 frame = new EventProgrammingExercise8();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public EventProgrammingExercise8() {
setTitle("EventExercise8");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(SIZE_OF_FRAME, SIZE_OF_FRAME);
contentPane = new JPanel();
lines = new LinePanel();
contentPane.add(lines);
setContentPane(contentPane);
contentPane.setOpaque(true);
lines.setOpaque(true);
lines.setFocusable(true);
lines.addKeyListener(new ArrowListener());
}
private class LinePanel extends JPanel {
private int x;
private int y;
private int x2;
private int y2;
public LinePanel() {
x = getWidth() / 2;
y = getHeight() / 2;
x2 = x;
y2 = y;
}
protected void paintComponent(Graphics g) {
g.drawLine(x, y, x2, y2);
x = x2;
y = y2;
}
public void drawEast() {
x2 += 5;
repaint();
}
public void drawWest() {
x2 -= 5;
repaint();
}
public void drawNorth() {
y2 -= 5;
repaint();
}
public void drawSouth() {
y2 += 5;
repaint();
}
}
private class ArrowListener extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_RIGHT) {
lines.drawEast();
} else if (key == KeyEvent.VK_LEFT) {
lines.drawWest();
} else if (key == KeyEvent.VK_UP) {
lines.drawNorth();
} else {
lines.drawSouth();
}
}
}
}
Thanks.
A few things jump out at me...
public LinePanel() {
x = getWidth() / 2;
y = getHeight() / 2;
This will be an issue, because at the time you construct the class, it's size is 0x0
Apart from the fact that you haven't called super.paintComponent which breaks the paint chain, you seem to think that painting is accumaltive...
protected void paintComponent(Graphics g) {
g.drawLine(x, y, x2, y2);
x = x2;
y = y2;
}
Painting in Swing is destructive. That is, you are expected to erase to the Graphics context and rebuild the output from scratch. The job paintComponent is to clear the Graphics context ready for painting, but you've not called super.paintComponent, breaking the paint chain and opening yourself up to a number of very ugly paint artifacts
Calling setSize(SIZE_OF_FRAME, SIZE_OF_FRAME); on a frame is dangerous, as it makes no guarantee about the frames border insets, which will reduce the viewable area available to you.
This....
contentPane = new JPanel();
lines = new LinePanel();
contentPane.add(lines);
setContentPane(contentPane);
Is not required, it just adds clutter to your code. It's also a good hint as to what is going wrong with your code.
JPanel uses a FlowLayout by default. A FlowLayout uses the component's preferred size to determine how best to layout the components. The default preferred size of a component is 0x0
You could use...
lines = new LinePanel();
add(lines);
instead or set the contentPane to use a BorderLayout which will help...
Try adding lines.setBorder(new LineBorder(Color.RED)); add see what you get...
Oddly, during my testing, your KeyListener worked fine...
Basically...
Override the getPreferredSize method of the LinePanel and return the size of the panel you would like to use.
Use a java.util.List to maintain a list of Points that need to be painted.
In you paintComponent method, use the Point List to actually render you lines. This will be a bit tricky, as you need two points and the List may contain an odd number of points, but's doable.
Calculate the start Point either by using the preferred size or some other means (like using a ComponentListener and monitoring the componentResized method. This becomes tricky as your component may be resized a number of times when it is first created and released to the screen and you will want to ignore future events once you have your first point)

Java MouseEvent position is inaccurate

I've got a problem in Java using a "canvas" class I created, which is an extended JPanel, to draw an animated ring chart. This chart is using a MouseListener to fetch click events.
The problem is that the mouse position does not seem to be accurate, meaning it does not seem to be relative to the "canvas" but instead relative to the window (in the left, upper corner I got about 30px for y coord).
This is my code:
I created a class, that extends JPanel and does have a BufferedImage as member.
public class Canvas extends JPanel {
public BufferedImage buf;
private RingChart _parent;
public Canvas(int width, int height, RingChart parent){
buf = new BufferedImage(width, height, 1);
...
In the paint component method I just draw the buffered image, so I am able to paint on the canvas from 'outside' by painting on the buffered image, which is public.
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(buf, null, 0, 0);
}
Now there's a class RingChart which contains a "canvas":
public class RingChart extends JFrame{
public Canvas c;
...
And I create a Graphics2D from the bufferedImage in the canvas class. This g2d is used for painting:
public RingChart(){
c = new Canvas(1500,980,this);
add(c);
setSize(1500, 1000);
setTitle("Hans");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
g2d = (Graphics2D)c.buf.createGraphics();
...
What I now was trying to achieve, was a mouse listener that listened to mouse events happening on the canvas. So when the user clicks on the canvas I could retrieve the position he clicked on, upon the canvas, through the event variable.
So I created a mouse listener:
class MouseHandler implements MouseListener {
#Override
public void mouseClicked(MouseEvent e){
RingChart r = ((Canvas)e.getSource()).getParent();
r.mouseClick(e);
}
...
...and added this mouse listener to the canvas of the RingChart class (myChart is an instance of RingChart and c is the canvas it contains):
...
MouseHandler mouse = new MouseHandler();
myChart.c.addMouseListener(mouse);
...
But as I mentioned above, the mouse position, that's returned when the click event is called, does not seem to be accurate. I think the mistake must be somehow in the way I created that mouseListener or maybe assigned it to the wrong element or something like that. But I've tried quite a couple of things and it didn't change. Can maybe someone tell me, what I've done wrong?
UPDATE:
The code of the function "mouseClick" that is a member of RingChart and is called in the mouse listener:
public void mouseClick(MouseEvent evt){
//evt = SwingUtilities.convertMouseEvent(this, evt, c);
if(evt.getButton() == MouseEvent.BUTTON1 && animation == null){
for(Element e : elements){
if(e.getShape() != null && e.getShape().contains(evt.getPoint())){
//do some stuff
}
}
}
}
Again, the hierarchy of my classes:
RingChart --contains a--> Canvas --got a--> MouseListener.
The shapes in this function are shapes that have been painted on the canvas c. Now I want to check, if the user has clicked on one of them. So as I thought, the shapes should be in canvas-coordinates and the event position should be in canvas-coordinates and everything should fit together. But it doesn't.
Now user MadProgrammer told me, to use the ConvertMouseEvent function. But I currently don't see which exact way I should use this sensibly.
UPDATE:
I found a solution: All I had to do is adding the canvas not directly to the JFrame but to the ContentPane of the JFrame instead:
So instead:
public RingChart(){
c = new Canvas(1500,980,this);
add(c);
...
I do:
public RingChart(){
c = new Canvas(1500,980,this);
getContentPane().add(c);
...
Then I give the MouseListener to the ContentPane.
getContentPane().addMouseListener(new MouseHandler());
getContentPane().addMouseMotionListener(new MouseMoveHandler());
I don't know, if this is an elegant solution, but it works.
The mouse event is automatically converted to be relative to the component that it occurred in that is, point 0x0 is always the top left corner of the component.
By using RingChart r = ((Canvas)e.getSource()).getParent(), you've effectively changed the reference, which now means the location is no longer valid.
You need to convert the location so that its coordinates are in the context of the parent component. Take a look at SwingUtilities.convertMouseEvent(Component, MouseEvent, Component)
UPDATE with PICTURES
Lets take this example...
The blue box has a relative position of 50px x 50px to the red box. If you click in the blue box, lets say at 25x25, the mouse coordinates will be relative to the blue box (0x0 will be the top left of the blue box).
If you then pass this event to the red box and try and use the coordinates from it, you will find that the coordinates will now be half way between the top left of the red box and the blue box, because the coordinates are context sensitive.
In order to get it to work, you need to translate the mouse events location from the blue box to the red box, which would make it 75x75
Now, I don't know what you're doing when you pass the mouse event to the RingChart so I'm only guessing that this is the issue you're facing.
UPDATED with Click Code
Okay, lets say, you have a Canvas at 100x100. You click on that Canvas at 50x50. You then pass that value back up the chain.
public void mouseClick(MouseEvent evt){
//evt = SwingUtilities.convertMouseEvent(this, evt, c);
if(evt.getButton() == MouseEvent.BUTTON1 && animation == null){
for(Element e : elements){
// Here, we are asking the shape if it contains the point 50x50...
// Not 150x150 which would be the relative position of the click
// in the context to the RingChart, which is where all your objects
// are laid out.
// So even the original Canvas you clicked on will return
// false because it's position + size (100x100x width x height)
// does not contain the specified point of 50x50...
if(e.getShape() != null && e.getShape().contains(evt.getPoint())){
//do some stuff
}
}
}
}
UPDATED
I think you have your references around the wrong way...
public static MouseEvent convertMouseEvent(Component source,
MouseEvent sourceEvent,
Component destination)
I think it should read something like
evt = SwingUtilities.convertMouseEvent(evt.getComponent(), evt, this);
UPDATE with Code Example
Okay, so, I put this little example together...
public class TestMouseClickPoint extends JFrame {
private ContentPane content;
public TestMouseClickPoint() throws HeadlessException {
setSize(600, 600);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
content = new ContentPane();
add(content);
}
protected void updateClickPoint(MouseEvent evt) {
content.updateClickPoint(evt);
}
protected class ContentPane extends JPanel {
private Point relativePoint;
private Point absolutePoint;
public ContentPane() {
setPreferredSize(new Dimension(600, 600));
setLayout(null); // For testing purpose only...
MousePane mousePane = new MousePane();
mousePane.setBounds(100, 100, 400, 400);
add(mousePane);
}
protected void updateClickPoint(MouseEvent evt) {
absolutePoint = new Point(evt.getPoint());
evt = SwingUtilities.convertMouseEvent(evt.getComponent(), evt, this);
relativePoint = new Point(evt.getPoint());
System.out.println(absolutePoint);
System.out.println(relativePoint);
repaint();
}
protected void paintCross(Graphics2D g2d, Point p) {
g2d.drawLine(p.x - 5, p.y - 5, p.x + 5, p.y + 5);
g2d.drawLine(p.x - 5, p.y + 5, p.x + 5, p.y - 5);
}
/*
* This is not recommended, but I want to paint ontop of everything...
*/
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
if (relativePoint != null) {
g2d.setColor(Color.BLACK);
paintCross(g2d, relativePoint);
}
if (absolutePoint != null) {
g2d.setColor(Color.RED);
paintCross(g2d, absolutePoint);
}
}
}
protected class MousePane extends JPanel {
private Point clickPoint;
public MousePane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
clickPoint = e.getPoint();
TestMouseClickPoint.this.updateClickPoint(e);
repaint();
}
});
setBorder(new LineBorder(Color.RED));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLUE);
if (clickPoint != null) {
g2d.drawLine(clickPoint.x, clickPoint.y - 5, clickPoint.x, clickPoint.y + 5);
g2d.drawLine(clickPoint.x - 5, clickPoint.y, clickPoint.x + 5, clickPoint.y);
}
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
new TestMouseClickPoint().setVisible(true);
}
}
Basically, it will paint three points. The point that the mouse was clicked (relative to the source of the event), the unconverted point in the parent container and the converted point with the parent container.
The next thing you need to do is determine the mouse location is actually been converted, failing that. I'd probably need to see a working example of your code to determine what it is you're actually doing.

java move a rotated shape

I am implementing a java swing application which has a JPanel that serves as a drawing surface. The surface renders (active) different elements. Each element has its own shape and an affine transform which is used for rendering and collision detection (each element is drawn with local coordinates and than transformed with the affine transform - like opengl).
The elements can be moved and rotated on the surface (this is done through transforms). Every time a transform is applied a Area object is created with the shape and the transformation (for accurate collision detection).
The problem is when I rotate the element (for 45 degrees) and then move it by 10 px. When I move it the element moves in the rotated direction which I don't want.
Is there any simple way I can overcome this?
(If my description isnt enough I'll post some example code).
EDIT:
class Element
{
private AffineTransform transform = new AffineTransform();
private Shape shape = new Rectangle(0,0,100,100);
private Area boundingBox;
public void onMouseDrag(MouseEvent e)
{
translate(dx,dy); // dx,dy are calculated from event
}
public void onMouseMove(MouseEvent e)
{
rotate(Math.atan2(dx/dy); // dx,dy are calculated from event
}
public void translate(int dx,int dy)
{
transform.translate(dx,dy);
boundingBox = new Area(shape);
boundingBox.transform(transform);
}
public void rotate(double angle)
{
transform.rotate(Math.toRadians(angle));
boundingBox = new Area(shape);
boundingBox.transform(transform);
}
public void draw(Graphics2D g2d)
{
g2d.setTransform(transform);
g2d.draw(shape);
...
}
}
I've modified your code by giving Element position and orientation fields, which you might want to expose through getters/setters (probably defensively copying the Point instance before returning/setting it). updateTransform() simply re-builds the AffineTransform based on the Element's current position and orientation.
public class Element {
private Point position = new Point();
private float orientation;
private AffineTransform transform = new AffineTransform();
private Shape shape = new Rectangle(0, 0, 100, 100);
private Area boundingBox;
public void onMouseDrag(MouseEvent e) {
translate(dx, dy);
}
public void onMouseMove(MouseEvent e) {
rotate(Math.atan2(dx / dy));
}
public void translate(int dx, int dy) {
position.translate(dx, dy);
updateTransform();
}
public void rotate(double angle) {
orientation += Math.toRadians(angle);
updateTransform();
}
private void updateTransform() {
transform.setToIdentity();
transform.translate(position.x, position.y);
transform.rotate(orientation);
// ... update bounding box here ...
}
public void draw(Graphics2D g2d) {
g2d.setTransform(transform);
g2d.draw(shape);
}
}
Also, consider the following approach:
Change draw(Graphics2D) to draw(Graphics2D, AffineTransform transform)
Create AffineTransform Element.getCompositeTransform() which builds the current AffineTransform and returns it (like updateTransform()).
Remove the transform field from Element.
In your render method, do something like:
.
for (Element element : elements) {
AffineTransform transform = element.getCompositeTransform();
element.draw(graphics, transform);
}
Why? This gives you the flexibility to control Element's drawing transform externally. This is useful if you'll build a node graph (like when an Element can have Element children) and you'll recursively render the graph, concatenating transforms. Btw, this approach is pretty much standard in 3D programming.
Reverse the order you are applying the transformations. Or unrotate the object, move it, then re-rotate the object. We would really need to see your code (or even pseudocode) to give you a better answer than this.

Categories