How to build click through components in Java swing? - java

I have built a custom component that shows only a line. The line is drawn from the top left corner to the bottom right corner as a Line2D at the paint method. The background is transparent. I extended JComponent. These line components are draggable and change their line color when the mouse pointer is located max. 15 pixels away from the drawn line.
But if I have multiple of these components added to another custom component that extends JPanel they sometimes overlap. I want to implement that if the mouse pointer is more than 15 pixels away from the line the mouse events should fall through the component. How to let it fall through is my problem.
Is that even possible?
Thanks in advance!

I want to implement that if the mouse pointer is more than 15 pixels
away from the line the mouse events should fall through the component.
If your child component has a mouse listener, then it will intercept every mouse event occurring over it. If you want to forward the MouseEvent to the parent Component you should manually do it. For example you can implement your custom mouse listener extending MouseAdapter:
public class yourMouseListener extends MouseAdapter{
//this will be called when mouse is pressed on the component
public void mousePressed(MouseEvent me) {
if (/*do your controls to decide if you want to propagate the event*/){
Component child = me.getComponent();
Component parent = child.getParent();
//transform the mouse coordinate to be relative to the parent component:
int deltax = child.getX() + me.getX();
int deltay = child.getY() + me.getY();
//build new mouse event:
MouseEvent parentMouseEvent =new MouseEvent(parent, MouseEvent.MOUSE_PRESSED, me.getWhen(), me.getModifiers(),deltax, deltay, me.getClickCount(), false)
//dispatch it to the parent component
parent.dispatchEvent( parentMouseEvent);
}
}
}

For my final year project at university I did a whiteboard program and had the same problem. For each shape the user drew on the board I created a JComponent, which was fine when they were drawing rectangles, but more difficult with the free form line tool.
The way I fixed it in the end was to do away with JComponents altogether. I had a JPanel which held a Vector (I think) of custom Shape objects. Each object held its own coordinates and line thicknesses and such. When the user clicked on the board, the mouse listener on the JPanel fired and went through each Shape calling a contains(int x, int y) method on each one (x and y being the coordinates of the event). Because the Shapes were added to the Vector as they were drawn I knew that the last one to return true was the topmost Shape.
This is what I used for a straight line contains method. The maths might be a bit iffy but it worked for me.
public boolean contains(int x, int y) {
// Check if line is a point
if(posX == endX && posY == endY){
if(Math.abs(posY - y) <= lineThickness / 2 && Math.abs(posX - x) <= lineThickness / 2)
return true;
else
return false;
}
int x1, x2, y1, y2;
if(posX < endX){
x1 = posX;
y1 = posY;
x2 = endX;
y2 = endY;
}
else{
x1 = endX;
y1 = endY;
x2 = posX;
y2 = posY;
}
/**** USING MATRIX TRANSFORMATIONS ****/
double r_numerator = (x-x1)*(x2-x1) + (y-y1)*(y2-y1);
double r_denomenator = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
double r = r_numerator / r_denomenator;
// s is the position of the perpendicular projection of the point along
// the line: s < 0 = point is left of the line; s > 0 = point is right of
// the line; s = 0 = the point is along the line
double s = ((y1-y)*(x2-x1)-(x1-x)*(y2-y1) ) / r_denomenator;
double distance = Math.abs(s)*Math.sqrt(r_denomenator);
// Point is along the length of the line
if ( (r >= 0) && (r <= 1) )
{
if(Math.abs(distance) <= lineThickness / 2){
return true;
}
else
return false;
}
// else point is at one end of the line
else{
double dist1 = (x-x1)*(x-x1) + (y-y1)*(y-y1); // distance to start of line
double dist2 = (x-x2)*(x-x2) + (y-y2)*(y-y2); // distance to end of line
if (dist1 < dist2){
distance = Math.sqrt(dist1);
}
else{
distance = Math.sqrt(dist2);
}
if(distance <= lineThickness / 2){
return true;
}
else
return false;
}
/**** END USING MATRIX TRANSFORMATIONS****/
}
posX and posY make up the coordinates of the start of the line and endX and endY are, yep, the end of the line. This returned true if the click is within lineThickness/2 of the centre of the line, otherwise you have to click right along the very middle of the line.
Drawing the Shapes was a case of passing in the JPanel's Graphics object to each Shape and doing the drawing with that.

It's been a while since I touched Swing, but I think you will need to handle your mouse events in the parent component and then loop through the child components with lines and determine which one of them should handle the event (well, the logic of deciding should still remain in the line component, but parent will explicitly invoke that logic until one of the components takes the event).

I believe that the easiest way is to catch the event and call parent.processEvent(). So, you component will be transparent for events because it will propagate them to parent.

I was struggling with this sort of question and tried all the stuff with parents and glasspane until i realised that override of contains method does just what you want. Because when parent fires some sort of getcomponent your 'Line' will reply to it: 'no, its not me, i'm not there!' and the loop will check other components.
Also, when you need to setup a complex depth to your draggable object, you can use JLayeredPane descendant.

Related

Java little ball runs away from cursor

I'm trying to make a graphic project where a Ball runs away from my cursor, I already did the other way around where the ball seeks my cursor and when she arrives she loses velocity so it's like she's running fast until she comes around a range of 10 pixels and then she loses velocity until she touches the cursor.
The thing is, I can't find a way to make the ball run away from the cursor in a way that when I enter a diameter(from the ball), she runs slow, if I approach more she starts to run faster to get away but when my cursor leaves the diameter, she runs slow until she stops once again.
I hope I made it clear, I thought about a solution but I don't know if there's a library or some built function in Java that I could use guys:
-have like a percentage from 0 to 100 where the distance between my cursor and the ball fits inside, 0% is velocity=0, 100% is velocity=4 for example, do you have any idea if there is such thing that I could implement?
Thank you in advance!
I've made a Vector class where I change it and access the X and Y coordinates to make the ball move, I used basic trigonometry to make the vector ALWAYS the same length.
code of my ball (Chaser) class:
public class Chaser {
private double x;
private double y;
private double vel = 1;
private double hyp;
private Vector vector = new Vector(0, 0);
private double distance;
public Chaser(int width, int height){
x = width/2;
y = height/2;
}
public void setVel(Point m){
if(m.x != x)
hyp = Math.sqrt(Math.pow(Math.abs(m.x - x), 2) + Math.pow(Math.abs(m.y - y), 2));
else
hyp = Math.abs(m.y - y);
}
public void setDirection(Point m){
if(hyp == 0) return;
vector.change((m.x - x)/hyp, (m.y - y)/hyp);
}
public void draw(Graphics g){
g.setColor(Color.RED);
g.fillOval((int)x - 10, (int)y - 10, 20, 20);
g.setColor(Color.BLACK);
g.drawLine((int)x, (int)y, (int)(vector.getX()*15*vel) + (int)x, (int)(vector.getY()*15*vel) + (int)y);
}
public void move(Point m){
setVel(m);
setDirection(m);
useVector();
}
public void useVector(){
if(vector == null) return;
x -= vector.getX() * vel;
y -= vector.getY() * vel;
}
public void calculateVelocity(Point m){
if(vector == null) return;
// I don't know what to do yet
}
}
If you want to just push the ball around you can do something simple. Let's use vectors to make it easier to understand. Say ball holds the ball's center (x,y) and mouse contains the mouse position (x,y).
You can compute the distance between ball and mouse, that is (mouse - ball).length() to get how far away the mouse is from the ball.
If the distance > ball radius then the mouse is outside.
Otherwise you can do:
tmp = ball - mouse // get the vector from mouse to the ball.
tmp = tmp / tmp.length() * ball_radious // change the vector's length to match the radious of the ball.
ball = mouse + tmp // Move the ball such that the mouse will be on the edge.
As you move the mouse the ball will get pushed by the mouse.
If you want a bit of inertia, so the ball doesn't just stop when you don't push it anymore then you need to keep an additional vector speed and use tmp to get an acceleration.
Something like this:
tmp = ball - mouse // get the vector from mouse to the ball.
force = max(0, ball_radious - tmp.length()) // how strong we push the ball.
acceleration = tmp / tmp.legnth() * f(force) // compute the acceleration vector. f(force) is some function based on force, try k*f or k*f*f and see what looks better for your setup.
speed = speed * kDrag + acceleration // update the speed, kDrag should be between 0 and 1, start with something like 0.8 and try different values.
ball = ball + speed * time_delta // Update the ball's position.
You can play with the constants to get the right feel that you're looking for. time_delta is meant to normalize the speed between frams so you don't need to worry too much if there's some inconsistency between them. You can use a constant as well, but the movement might become jerky at times.
All operations above are vector operations.

For loop drawing PGraphics in an array [Processing]

I'm trying to build a Drawing Program using Processing. I am currently stuck on using PGrapchics.
When the user draws a rectangle, it shows the shape being drawn. When the user releases their mouse, it then creates a PGraphic of the final shape. I would then like the user to draw on top of that. Here is my problem:
I had to reset the background of the canvas when drawing a rectangle because otherwise, it shows a trail of rectangles. The result is that while the user draws a new rectangle the old ones disappear and come back once the mouse has been releasd
Some thoughts:
I would also like to add features where the user can select on a previously drawn rectangle and change it's colour, stroke, send to back, bring to front etc..
To achieve this, I'm storing all drawn rectangles (PGraphics) into an ArrayList which will be drawn via a for loop. This will allow me to adjust the behaviour by moving the PGraphics elements up and down the ArrayList.
PS: Instead of creating my own class of Shape am I better off using PShape?
int startX;
int startY;
int endX;
int endY;
boolean drawing;
int strokeW = 3;
Shape shape;
PGraphics shapeLayer;
ArrayList<PGraphics> layersList = new ArrayList();
void setup() {
size(500, 500);
cursor(CROSS);
background(255);
smooth();
}
void draw() {
strokeWeight(strokeW);
if (key >= '0' && key <= '9') {
strokeW = key - '0';
}
for(int i = 0; i < layersList.size(); i++) {
image(layersList.get(i), 0, 0);
}
if (drawing) {
shape.createRectangle();
}
}
void mousePressed() {
startX = mouseX;
startY = mouseY;
shapeLayer = createGraphics(width, height);
shapeLayer.beginDraw();
}
void mouseDragged() {
drawing = true;
endX = constrain(mouseX, 0, 500);
endY = constrain(mouseY, 0, 500);
shape = new Shape(startX, startY, endX, endY);
shapeLayer.clear();
}
void mouseReleased() {
drawing = false;
shapeLayer.endDraw();
layersList.add(shapeLayer);
}
Here is the Shape Class:
class Shape {
int startX;
int startY;
int endX;
int endY;
Shape(int x1, int y1, int x2, int y2) {
startX = x1;
startY = y1;
endX = x2;
endY = y2;
}
void createRectangle() {
background(255, 0);
shapeLayer.strokeWeight(strokeW);
shapeLayer.rectMode(CORNERS);
shapeLayer.rect(startX, startY, endX, endY);
rectMode(CORNERS);
rect(startX, startY, endX, endY);
}
}
In the future, please try to narrow your problem down to a MCVE before you post. For example you could have hard-coded it to draw a rectangle when the user drags instead of including all the code for every shape.
But your problem is caused by drawing to the screen and never clearing it out. You need to break your problem down into smaller pieces and then approach those pieces one at a time.
Step 1: Can you create a sketch that just shows a rectangle as you drag, but has the rectangle go away when you let go of the mouse? Start over with a basic sketch that does just this one thing, and get it working perfectly before you move on to the next step.
Step 2: Can you draw shapes to an off-screen buffer? It looks like you've tried this in your current code, but note that you never actually draw any shapes to your buffer, and you never actually draw your buffer to the screen. Again, start with a basic sketch that just does this. Don't even worry about user input or anything yet, just get a hard-coded rectangle drawn to an off-screen buffer, then draw that off-screen buffer to the screen.
Step 3: Can you combine those two to show the rectangle when you're drawing it, then draw it to the off-screen buffer when the user lets go?
Step 4: Only when you have the rectangle working perfectly, then move on to other shapes.
This is how programming works: you have to break your problem down into small steps like this, and then you have to approach each step in isolation. If you get stuck, you can come back with an MCVE showing just one of these steps, and we'll go from there. Good luck.
In addition to Kevin's answer: it does look like you are using another PGraphics buffer to draw into, but the whole sketch could be simpler.
Unless you need an undo/redo mode, where remembering the drawing commands and their order is needed, you can get away with something slightly simpler.
You can find a detailed answer with commented code showing something very similar. You simply need to add the pencil and line modes.

Achieve parallax effect in libGDX game

I'm working in a game with some friends in which we have a large horizontal world and a OrthographicCamera that shows only 1/3 of it. This camera it's moved when the horizontal position of the player change so the camera only move to the left and to the right.
Some of the objects showed in the game are near the player point-of-view but others are far away (for example, islands). With this in consideration, we cannot set fixed positions for elements and move only the camera. We need to achieve a parallax effect taking in consideration the distance of the elements.
Here is a simple image to explain it better:
The viewport to the left shows 3 objects of the game. The green one is near the player, the red ellipse is far and the yellow one is in the middle. In the viewport to the right the camera has been moved to the right so all the objects disappear to the left. The thing is that the relative movement of the green rectangle is greater than the movement of the yellow. In the same way, movement of yellow object is greater than red object movement.
I created all my assets scaled taking in consideration how far they are but now, how can I simulate this perspective using libGDX? Is there any class to do it? If I have to set elements position in each iteration, how could I calculate the right position?
Note that the example below is not tested as I am just recalling how I did it. The idea is simple - create layers with an extra layer for each with initial positions and velocity and move them. If a layer goes off the edge, put another one (that is why we create an extra layer) at the opposite edge.
Say you have a parallax object that takes initial positions, size, and velocity-
public class Parallax extends DynamicGameObject {
public float width, height; // Use setter/getter if you prefer
public Parallax(float x, float y, float width, float height, float velocityX, float velocityY) {
super(x, y, width, height);
velocity.set(velocityX, velocityY);
this.width = width;
this.height = height;
}
public void update(float deltaTime) {
position.add(velocity.x * deltaTime, velocity.y * deltaTime);
}
public void setPosition(float x, float y) {
position.set(x, y);
}
}
DynamicGameObject is taken from SuperJumper demo-
public class DynamicGameObject extends GameObject {
public final Vector2 velocity;
public final Vector2 accel;
public DynamicGameObject(float x, float y, float width, float height) {
super(x, y, width, height);
velocity = new Vector2();
accel = new Vector2();
}
}
GameObject as well-
public class GameObject {
public final Vector2 position;
public final Rectangle bounds;
public GameObject(float x, float y, float width, float height) {
this.position = new Vector2(x,y);
this.bounds = new Rectangle(x - width/2f, y - height/2f, width, height);
}
}
Say we have two layers - one in front and the other goes at back. We have one texture for each. Each texture fills the entire screen. We create two instances for each layer so that when one texture starts going off the screen, the other shows up at the edge to fill the gap. If you have smaller textures, you need to determine first how many textures you need to fill the screen and then create layers with one extra to fill the gap in between.
We can create an array of parallax layers during world creation-
Array<Parallax> parallaxList = new Array<Parallax>(4);
We can create the layers like this-
// Back
/* First parallax for back layer is at 0 x-axis. If you want to move the texture from right to left, the value of BACK_VELOCITY_X should be negative. You can experiment with velocity value for desire pace of movement. We do not want our layer to move on y-axis. Hence, it is set to 0. */
parallaxList.add(new Parallax(0, BACK_TEXTURE_HEIGHT, BACK_TEXTURE_WIDTH, BACK_TEXTURE_HEIGHT, BACK_VELOCITY_X, 0));
/* This one is also for back layer but it is positioned at the right edge of the layer above*/
parallaxList.add(new Parallax(BACK_TEXTURE_WIDTH, BACK_TEXTURE_HEIGHT, BACK_TEXTURE_WIDTH, BACK_TEXTURE_HEIGHT, SOME_VELOCITY_X, 0));
// Front
parallaxList.add(new Parallax(0, 0, FRONT_TEXTURE_WIDTH, FRONT_TEXTURE_HEIGHT, FRONT_VELOCITY_X, 0));
parallaxList.add(new Parallax(FRONT_TEXTURE_WIDTH, 0, FRONT_TEXTURE_WIDTH, FRONT_TEXTURE_HEIGHT, FRONT_VELOCITY_X, 0));
We update the layers on an update call in each frame-
// In our example, TOTAL_LAYERS is 4
for (int i = 0; i < TOTAL_LAYERS; i++) {
int tmpInt;
Parallax parallax = parallaxList.get(i);
parallax.update(deltaTime);
// If one layer is off the edge, put it at the right of the next one
// In this example, layers are moving from right to left
if (parallax.position.x <= -parallax.width) {
// We know that parallaxList's indexes 0 and 1 hold the back layers
// and indexes 2 and 3 have the front layers. You can add additional
// parameters in Parallax class to indicate a group so that you do not
// have to determine the group in dirty way like this
if(i == 0){
tmpInt = 1;
} else if(i == 1) {
tmpInt = 0;
} else if(i == 2) {
tmpInt = 3;
} else {
tmpInt = 2;
}
parallax.setPosition(parallaxList.get(tmpInt).position.x + parallax.width, parallax.position.y);
}
}
You can use an OrthographicCamera and a SpriteBatch to draw the parallax layers. You can actually use the game camera you have but I think using a separate camera is much cleaner. Anyways, parallax textures are usually big enough to be batched in a separate call so using the game camera most probably will not save you a draw call.

Difficulty understanding Java MouseEvent

I'm doing these iTunes Stanford classes, and I've been learning beginning Java. Things are going great, but they recently introduced events-and specifically MouseEvents. I've been reading the chapters in the book, and pouring through the example code, and something is just not clicking right for me...it's always that asynchronous stuff that gives me trouble :-D
Earlier, some people mentioned it was important that I mention that the "addMouseListener" is a class in the Graphics import. As far as I can tell, that just adds a blanket mouse listener to the canvas.
I'm still real new to this, so I may not be describing things as well as I should.
This is a piece of code that I have been trying to simplify in order to better understand it. Currently, it will build a red rectangle, and I can click on it and drag it along the x axis. Great!!!
import java.awt.*;
import java.awt.event.*;
import acm.graphics.*;
import acm.program.*;
/** This class displays a mouse-draggable rectangle and oval */
public class DragObject extends GraphicsProgram {
/* Build a rectangle */
public void run() {
GRect rect = new GRect(100, 100, 150, 100);
rect.setFilled(true);
rect.setColor(Color.RED);
add(rect);
addMouseListeners();
}
/** Called on mouse press to record the coordinates of the click */
public void mousePressed(MouseEvent e) {
lastX = e.getX();
lastY = e.getY();
gobj = getElementAt(lastX, lastY);
}
/** Called on mouse drag to reposition the object */
public void mouseDragged(MouseEvent e) {
if((lastX) > 100){
gobj.move(e.getX() - lastX, 0);
lastX = e.getX();
lastY = e.getY();
}
}
/** Called on mouse click to move this object to the front */
public void mouseClicked(MouseEvent e) {
if (gobj != null) gobj.sendToFront();
}
/* Instance variables */
private GObject gobj; /* The object being dragged */
private double lastX; /* The last mouse X position */
private double lastY; /* The last mouse Y position */
}
If I drag the mouse off the canvas, I want the rectangle to stay within the canvas, and not move off it (the same behavior that a horizontal scroll bar would do if you moved beyond the scroll area with the mouse button still clicked). How can I do that?
I've been trying something along these lines, but it's not working right:
if ( ( lastX > (getWidth() - PADDLE_WIDTH) ) || ( lastX < PADDLE_WIDTH ) ) {
gobj.move(0, 0);
} else {
gobj.move(e.getX() - lastX, 0);
}
Your code is moving the rectangle relative to the last position of the mouse. This works fine when you are simply moving things, but for your needs when you want it to stop at the borders, you need to use absolute positioning.
// When the mouse is pressed, calculate the offset between the mouse and the rectangle
public void mousePressed(MouseEvent e) {
lastX = e.getX();
lastY = e.getY();
gobj = getElementAt(lastX, lastY);
}
public void mouseDragged(MouseEvent e) {
double newX;
// Assuming you can get the absolute X position of the object.
newX = gobj.getX() + e.getX() - lastX;
// Limit the range to fall within your canvas. Adjust for your paddle width as necessary.
newX = Math.max( 0, Math.min( newX, getWidth() ) );
// Set the new position of the paddle, assuming you can set the absolute position.
gobj.setX( newX );
lastX = e.getX();
lastY = e.getY();
}
}
This may not be quite what you want because as soon as you go off the edge, the object will stop moving, but then once you move back toward the canvas, your paddle will move immediately instead of waiting for the mouse to reach the same relative position to the paddle at which it started.
You can probably experiment to get it to do what you want.
In order to do this you will need to know the width of the Canvas object, i'm sure there will be a method that will provide this value. You can then check the current x location of the MouseEvent against the width of the canvas, and not increment the x coordinates of the shape object once you are past the width of the canvas. Depending on how much of the shape you want to remain in the canvas, you may need to take into account the width of the shape object as well.
One thing that helps me when dealing w/ animation and moving objects in a gui is drawing out a few scenarios on paper, and noting how the coordinates change.

How to select a line

So I'm trying to figure out how to implement a method of selecting lines or edges in a drawing area but my math is a bit lacking. This is what I got so far:
A collection of lines, each line has two end points (one to start and one to end the line)
The lines are drawn correctly on a canvas
Mouse clicks events are received when clicking the canvas, so I can get the x and y coordinate of the mouse pointer
I know I can iterate through the list of lines, but I have no idea how to construct an algorithm to select a line by a given coordinate (i.e. the mouse click). Anyone got any ideas or point me to the right direction?
// import java.awt.Point
public Line selectLine(Point mousePoint) {
for (Line l : getLines()) {
Point start = l.getStart();
Point end = l.getEnd();
if (canSelect(start, end, mousePoint)) {
return l; // found line!
}
}
return null; // could not find line at mousePoint
}
public boolean canSelect(Point start, Point end, Point selectAt) {
// How do I do this?
return false;
}
Best way to do this is to use the intersects method of the line. Like another user mentioned, you need to have a buffer area around where they clicked. So create a rectangle centered around your mouse coordinate, then test that rectangle for intersection with your line. Here's some code that should work (don't have a compiler or anything, but should be easily modifiable)
// Width and height of rectangular region around mouse
// pointer to use for hit detection on lines
private static final int HIT_BOX_SIZE = 2;
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Line2D clickedLine = getClickedLine(x, y);
}
/**
* Returns the first line in the collection of lines that
* is close enough to where the user clicked, or null if
* no such line exists
*
*/
public Line2D getClickedLine(int x, int y) {
int boxX = x - HIT_BOX_SIZE / 2;
int boxY = y - HIT_BOX_SIZE / 2;
int width = HIT_BOX_SIZE;
int height = HIT_BOX_SIZE;
for (Line2D line : getLines()) {
if (line.intersects(boxX, boxY, width, height) {
return line;
}
}
return null;
}
Well, first off, since a mathematical line has no width it's going to be very difficult for a user to click exactly ON the line. As such, your best bet is to come up with some reasonable buffer (like 1 or 2 pixels or if your line graphically has a width use that) and calculate the distance from the point of the mouse click to the line. If the distance falls within your buffer then select the line. If you fall within that buffer for multiple lines, select the one that came closest.
Line maths here:
http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
Shortest distance between a point and a line segment
If you use the 2D api then this is already taken care of.
You can use Line2D.Double class to represent the lines. The Line2D.Double class has a contains() method that tells you if a Point is onthe line or not.
Sorry, mathematics are still required... This is from java.awt.geom.Line2D:
public boolean contains(double x,
double y)
Tests if a specified coordinate is inside the boundary of this Line2D.
This method is required to implement
the Shape interface, but in the case
of Line2D objects it always returns
false since a line contains no area.
Specified by:
contains in interface Shape
Parameters:
x - the X coordinate of the specified point to be tested
y - the Y coordinate of the specified point to be tested
Returns:
false because a Line2D contains no area.
Since:
1.2
I recommend Tojis answer

Categories

Resources