What is the best UI element dragging algorithm - java

I made a GUI application but I am stuck with a workaround algorithm to drag/move ui element relatively to another surface (screen, canvas, etc.). In my case I use it for a window relative to the screen, but that's beside the point because this algorithm should work anywhere possible. Here is my algorithm:
code-listing-1.
MouseMotionAdapter(){
int prevX = -1000, prevY = -1000, getX, getY;
public void mouseDragged(MouseEvent e) {
//if initial cursor position isn't set
if(prevX==-1000){
prevX = e.getLocationOnScreen().x;
prevY = e.getLocationOnScreen().y;
getX = e.getX();
getY = e.getY();
}
//move element to new position
theFrame.setBounds(theFrame.getBounds().x+e.getX()-getX, theFrame.getBounds().y+e.getY()-getY, 880, 583);
prevX=e.getLocationOnScreen().x;
prevY=e.getLocationOnScreen().y;
}
The trouble with this algorithm is that the mouse cursor position is definitely fixed relatively to the element and if I try to move/drag the element clicking in another position/part of the element the whole element moves so that the mouse cursor is positioned at the "initial position", which is not the behaviour I want (I want it to have the behaviour we mostly know like when we move an icon on desktop or a window on the screen, etc.)
Can anyone help with that? Thanks in advance.

The usual approach in such a case is to compute the difference between the current and the previous position. This difference is then "the movement", that is added to the position of the dragged object.
(BTW: You seem to not use the prev... values in your computation at all!)
Inside a MouseMotionListener, this could roughly look as follows:
class MouseDraggingControl implements MouseMotionListener
{
private Point previousPoint = new Point();
#Override
public void mouseMoved(MouseEvent e)
{
previousPoint = e.getPoint();
}
#Override
public void mouseDragged(MouseEvent e)
{
int movementX = e.getX() - previousPoint.x;
int movementY = e.getY() - previousPoint.y;
doMovement(movementX, movementY);
previousPoint = e.getPoint();
}
}
In your case, the doMovement method might be implemented like this:
private void doMovement(int movementX, int movementY)
{
int oldX = theFrame.getX();
int oldY = theFrame.getY();
int newX = oldX + movementX;
int newY = oldY + movementY;
theFrame.setLocation(newX, newY);
}
(or similar, using the getBounds/setBounds calls)
EDIT If the mouse motion listener is attached directly to the component that should be dragged, you might have to use the "location on screen" instead:
class MouseDraggingControl implements MouseMotionListener
{
private Point previousPoint = new Point();
#Override
public void mouseMoved(MouseEvent e)
{
previousPoint = e.getLocationOnScreen();
}
#Override
public void mouseDragged(MouseEvent e)
{
Point p = e.getLocationOnScreen();
int movementX = p.x - previousPoint.x;
int movementY = p.y - previousPoint.y;
doMovement(movementX, movementY);
previousPoint = e.getLocationOnScreen();
}
}
If this does not solve your issue, I'd like to emphasize the comment from MadProgrammer: An example that demonstrates your actual problem would help here.

Related

Getting Graphics2D to draw properly JAVA

I am writing a 'sketchPad' type program in Java. My FREEHAND and LINE implementation works fine, but when I do a RECT or an OVAL if I draw from top left to bottom right it works fine, but the opposite way doesn't function. It will draw from my starting point then down to the bottom right again depending on the size I try to draw. I have learned a little bit about Affine Transform and thought that may be a way to go, but I need help implementing it properly, or doing it a better way.
Here is the effected portion of the code:
public void paint(){
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
oldX = e.getX();
oldY = e.getY();
graphics2D.setPaint(toolBar.getCurrentColor());
repaint();
}
});
addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
String currentPaintType = toolBar.getPaintType();
if(currentPaintType != "FREEHAND"){
currentX = e.getX();
currentY = e.getY();
if(currentPaintType == "LINE"){
if (graphics2D != null){
graphics2D.drawLine(oldX, oldY, currentX, currentY);
}
}else if(currentPaintType == "OVAL"){
if (graphics2D != null){
graphics2D.drawOval(oldX, oldY, getWidthHeight(oldX,currentX), getWidthHeight(oldY,currentY));
}
}else if(currentPaintType == "RECT"){
if (graphics2D != null){
graphics2D.drawRect(oldX, oldY, getWidthHeight(oldX,currentX), getWidthHeight(oldY,currentY));
}
}
repaint();
}
}
});
}
//Code to calculate the proper width and height based on X/Y points
public int getWidthHeight(int old, int current){
int i=0;
if(old>current){
i=old-current;
}else{
i=current-old;
}
return i;
}

Keep mousePressed active after one click

So what I want to do is after one click is keep the mousePressed method 'on' even though im not holding it down myself. Then after another click it will turn it 'off'
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
// save coord x,y when mouse is pressed
oldX = e.getX();
oldY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
// coord x,y when drag mouse
currentX = e.getX();
currentY = e.getY();
if (g2 != null) {
// draw oval if g2 context not null
g2.drawOval(oldX, oldY, width, height);
g2.fillOval(oldX, oldY, width, height);
// refresh draw area to repaint
repaint();
// store current coords x,y as olds x,y
oldX = currentX;
oldY = currentY;
}
}
});
Handle the mousePressed() event. Then keep a variable in your class (lets say "clickCounter") that you increment every time the event is generated.
Then you will need to handle the mouseMoved() event to know when the mouse is moving.
So now your logic in the mouseMoved() event can check if the variable is odd, which would indicate the mouse has just been clicked:
if (clickCounter % 2 == 1)
{
add your logic here
}
Or you could keep a boolean variable that you toggle off/on for every mouse click.

Panel not correctly repainting image in Java?

Right now I am trying to get it so whenever I click inside of the oval that I have painted, and drag the mouse it will move positions by repainting. However, even though the MouseEvents are being detected correctly, the oval image is not updating. I am confused to why that is. Here is the code that deals with the oval, MouseEvents, and updating:
public class DrawOval extends JPanel {
private int size = 50;
private int locX = 0; //vector points
private int locY = 0;
private boolean isPressed = false;
private Shape oval = new Ellipse2D.Double(locX, locY, size, size * 2);
public DrawOval(int size){
this.size = size;
Dimension dims = new Dimension(size, size);
setPreferredSize(dims);
setMaximumSize(dims);
setMinimumSize(dims);
MouseAdapter m = new MouseAdapter(){
public void mouseReleased(MouseEvent e){
isPressed = false;
update(e);
System.out.println("Mouse is released!");
}
public void mousePressed(MouseEvent e){
isPressed = true;
update(e);
System.out.println("Mouse is pressed!");
}
public void mouseDragged(MouseEvent e){
if(isPressed){
update(e);
System.out.println("Mouse is dragged!");
}
}
public void update(MouseEvent e){
System.out.println("X: " + e.getX() + ", Y: " + e.getY());
if(oval.contains(e.getX(), e.getY())){
setX(e.getX()); setY(e.getY());
repaint();
}
//does not update if the mouses click coordinates are outside of the oval
}
};
addMouseListener(m); //for pressing and releasing
addMouseMotionListener(m); //for dragging
}
public void setX(int _x){
this.locX = _x;
}
public void setY(int _y){
this.locY = _y;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
g2.fill(oval);
}
}
I cannot figure out why it is not updating correctly. I had it partially working before, but it would update all the time, even if where the user clicked was not within the oval.
I think doing setX and setY you forget to update x and y in your oval. You have at least three options:
1) recreate Ellipse2D.Double every time you change this.locX and this.locY.
2) expect that your oval is created with x=0, y=0 once and for all, check mouse event switching to relative coordinates (if(oval.contains(e.getX() - locX, e.getY() - locY)){...}) and draw your oval using AffineTransform by g2.transform(...).
3) Declare oval as Ellipse2D.Double oval = new Ellipse2D.Double(...); and then you can change its x and y directly cause they're public:
oval.x = this.locX;
oval.y = this.locY;

MouseInputAdapter on JMenuBar - not all methods get called

I have a JComponent with JMenuBar in it. I would like to be able to drag the component by clicking on it and dragging the mouse to the desired location. I have already implemented this functionality by adding the MouseInputAdapter methods to its Border and it works great.
However, I would like to apply the same functionality on the JMenuBar, the mouseDragged() method does not get called.
Here is the menu listener (stripped version for testing):
menuBar.addMouseListener(new MouseInputAdapter() {
private Point startPos = null;
#Override
public void mousePressed(MouseEvent me) {
startPos = me.getPoint();
// works fine
System.out.println("startPos: " + startPos.toString());
}
#Override
public void mouseDragged(MouseEvent me) {
// this does not get called...
System.out.println("dragging.............");
}
});
I dont think that this is the problem, but here is the Listener used for the whole component. Could these two listeners somehow collide? I tried removingthis listener but it did not solve anything:
// this works with the border
this.addMouseListener(resizeListener);
MouseInputListener resizeListener = new MouseInputAdapter() {
#Override
public void mouseEntered(MouseEvent me) {
// put this widget on top when mouse moves across it
putOnTop();
}
#Override
public void mouseMoved(MouseEvent me) {
// put this widget on top when mouse moves across it
putOnTop();
WidgetBorder border = (WidgetBorder) getBorder();
setCursor(Cursor.getPredefinedCursor(border.getResizeCursor(me)));
}
#Override
public void mouseExited(MouseEvent mouseEvent) {
setCursor(Cursor.getDefaultCursor());
}
private int cursor;
private Point startPos = null;
#Override
public void mousePressed(MouseEvent me) {
WidgetBorder border = (WidgetBorder) getBorder();
cursor = border.getResizeCursor(me);
startPos = me.getPoint();
}
#Override
public void mouseDragged(MouseEvent me) {
if (startPos != null) {
// widget cant be resized under its minimum size
if (widget.getWidth() < WidgetConstants.MIN_WIDTH) {
widget.setBounds(widget.getX(), widget.getY(), WidgetConstants.MIN_WIDTH + 1, widget.getHeight());
return;
}
if (widget.getHeight() < WidgetConstants.MIN_HEIGHT) {
widget.setBounds(widget.getX(), widget.getY(), widget.getWidth(), WidgetConstants.MIN_HEIGHT + 1);
return;
}
int dx = me.getX() - startPos.x;
int dy = me.getY() - startPos.y;
switch (cursor) {
case Cursor.SE_RESIZE_CURSOR:
setBounds(getX(), getY(), getWidth() + dx, getHeight() + dy);
startPos = me.getPoint();
repaintParent();
break;
case Cursor.MOVE_CURSOR:
Rectangle bounds = getBounds();
bounds.translate(dx, dy);
setBounds(bounds);
repaintParent();
}
// cursor shouldn't change while dragging
setCursor(Cursor.getPredefinedCursor(cursor));
}
}
#Override
public void mouseReleased(MouseEvent mouseEvent) {
startPos = null;
}
};
What am I missing here? Thanks for any help...
MouseInputAdapter implements many interfaces (e.g. MouseListener, MouseMotionListener etc.).
You have to add this with multiple methods, e.g. with Component.addMouseListener() if you want mouseClicked() to be called, Component.addMouseMotionListener() if you want mouseDragged() to be called and Component.addWheelListener() if you want mouseWheelMoved() to be called.
You can pass the same reference of course, but you have to add your listener implementation with multiple methods. The reason is because for example Component.addMouseListener() expects a MouseListener interface, and will only call the methods defined in the MouseListener interface, even if you pass an object that implements other methods from other interfaces.

how to draw rectangle on java applet using mouse drag event

i am using java.
i want to draw rectangle based on mousedrag event. if user dragging the mouse, then the rectangle on the applet should increase or decrease basing on current mouse coordinates.
i have the following code.
in the following code i am using [b]SelectionArea[/b] class which extends a canvas on which i am performing drawing operation. i am using [b]image[/b] variable in this class for double buffering to reduce flickering and to save the applet's previous state(i.e drawing content of applet)
but the code is working fine if i draw first rectangle. if i start to draw second rectangle the previously drawn rectangle is disappearing. i want the previously drawn rectangle to be on the screen
can any one tell me how to solve this.
import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;
/*
* This displays a framed area. When the user drags within
* the area, this program displays a rectangle extending from
* where the user first pressed the mouse button to the current
* cursor location.
*/
public class RectangleDemo extends Applet {
SelectionArea drawingPanel;
Label label;
public void init() {
GridBagLayout gridBag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gridBag);
drawingPanel = new SelectionArea(this);
c.fill = GridBagConstraints.BOTH;
c.weighty = 1.0;
c.gridwidth = GridBagConstraints.REMAINDER; //end row
gridBag.setConstraints(drawingPanel, c);
add(drawingPanel);
label = new Label("Drag within the framed area.");
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
c.weighty = 0.0;
gridBag.setConstraints(label, c);
add(label);
drawingPanel.setVisible(true);
validate();
}
public void paint(Graphics g){
drawingPanel.repaint();
}
public void update(Graphics g){
paint(g);
}
}
class SelectionArea extends Canvas implements ActionListener, MouseListener, MouseMotionListener{
Rectangle currentRect;
RectangleDemo controller;
//for double buffering
Image image;
Graphics offscreen;
public SelectionArea(RectangleDemo controller) {
super();
this.controller = controller;
addMouseListener(this);
addMouseMotionListener(this);
}
public void actionPerformed(ActionEvent ae){
repaintoffscreen();
}
public void repaintoffscreen(){
image = createImage(this.getWidth(), this.getHeight());
offscreen = image.getGraphics();
Dimension d = getSize();
if(currentRect != null){
Rectangle box = getDrawableRect(currentRect, d);
//Draw the box outline.
offscreen.drawRect(box.x, box.y, box.width - 1, box.height - 1);
//repaint();
}
}
public void mouseEntered(MouseEvent me) {}
public void mouseExited(MouseEvent me){ }
public void mouseClicked(MouseEvent me){}
public void mouseMoved(MouseEvent me){}
public void mousePressed(MouseEvent me) {
currentRect = new Rectangle(me.getX(), me.getY(), 0, 0);
repaintoffscreen();
}
public void mouseDragged(MouseEvent me) {
System.out.println("here in dragged()");
currentRect.setSize(me.getX() - currentRect.x, me.getY() - currentRect.y);
repaintoffscreen();
repaint();
}
public void mouseReleased(MouseEvent me) {
currentRect.setSize(me.getX() - currentRect.x, me.getY() - currentRect.y);
repaintoffscreen();
repaint();
}
public void update(Graphics g){
paint(g);
}
public void paint(Graphics g) {
g.drawImage(image, 0, 0, this);
}
Rectangle getDrawableRect(Rectangle originalRect, Dimension drawingArea) {
int x = originalRect.x;
int y = originalRect.y;
int width = originalRect.width;
int height = originalRect.height;
//Make sure rectangle width and height are positive.
if (width < 0) {
width = 0 - width;
x = x - width + 1;
if (x < 0) {
width += x;
x = 0;
}
}
if (height < 0) {
height = 0 - height;
y = y - height + 1;
if (y < 0) {
height += y;
y = 0;
}
}
//The rectangle shouldn't extend past the drawing area.
if ((x + width) > drawingArea.width) {
width = drawingArea.width - x;
}
if ((y + height) > drawingArea.height) {
height = drawingArea.height - y;
}
return new Rectangle(x, y, width, height);
}
}
also if i run this code on full screen mode then i am seeing that the rectangle is appering on screen only after i released the mouse. but i want the rectangle to be on the screen while dragging the mouse and it should change it's dimension according to the current mouse coordinates.
can any one help me pls.
homework?
basically what you need to do is:
on mouse down keep the mouse-down coordinates and repaint
on mouse move keep current mouse coordinates and repaint
on mouse up, nullify the mouse-down coordinates to indicate there is no rect, and repaint.
on paint, draw background and then rect between mousedown and cur-mouse coordinates.
if you don't want to keep a background image, you can do a trick with the Graphics xor function, drawing the same rect twice will erase the old rect, so you can use it to restore the old image straight on the graphics object.
Edit: code xor usage sample:
public void paint(Graphics g)
{
g.setXORMode(Color.black);
// draw old rect if there is one. this will erase it
// draw new rect, this will draw xored
g.setDrawMode(); // restore normal draw mode
}
Xor has the an interesting property:
xor(xor(x)) = x
so xoring the same pixel twice restores it's original color.
There are a couple issues that need to be addressed.
First, regarding only one rectangle can be drawn, this is due to the design of your program. In your code, whenever the repaintoffscreen method is called, the currectRect field is used to draw a rectangle. However, there is no provision to keep holding onto rectangles which were made in the past.
One way to keep a hold of past rectangles would be perhaps to make another field which is, for example, a List<Rectangle> which is used to store past rectangles. Then, when the mouse is released, add the current rectangle to that list.
Then, in order for all rectangles, currentRect and past rectangles to appear, repaintoffscreen will need to not only perform getDrawableRect and offscreen.drawRect using the currentRect but also with the past rectangles which are stored in the List<Rectangle>. (Hint, use a for loop to iterate through the list.)
Second, regarding the rectangle not appearing until after releasing the mouse button, rather than using the mouseDragged method, maybe using the mouseMoved method along with a check to see that the mouse button is depressed may be a workaround. (I think I've also had trouble dealing with the mouseDragged method in the past.)
The MouseEvent passed into the mouseMoved method can be used to check if a button is depressed by the getButton method:
public void mouseMoved(MouseEvent e)
{
// Check if button1 is pressed.
if (e.getButton() == MouseEvent.BUTTON1)
{
// Perform sizing of rectangle and off-screen drawing, and repaint.
}
}
My question was about create a select rectangle invert mouse click position, but, in the end I got make this with this method:
... //to set the selection area
private int iniSelX;
private int iniSelY;
private int endSelX;
private int endSelY;
private JPanel myJPanel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.red);
g.drawLine(260, 5, 260, 260);
g.setColor(Color.BLUE);
//verify if go draw the rectangle
if (iniSelX != 0 || endSelX != 0) {
boolean revertX = iniSelX < endSelX;
boolean revertY = iniSelY < endSelY;
//Simple way
//g.drawRect(iniSelX, iniSelY, endSelX - iniSelX, endSelY - iniSelY);
//reverse way
g.drawRect(revertX ? iniSelX : endSelX, revertY ? iniSelY : endSelY,
revertX ? endSelX - iniSelX : iniSelX - endSelX, revertY ? endSelY - iniSelY : iniSelY - endSelY);
}
}
}; ...
addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent m) {
//update selection area
endSelX = m.getX();
endSelY = m.getY();
repaint();
}
#Override
public void mouseMoved(MouseEvent m) {
repaint();
}
});
addMouseListener(new MouseListener() {
...
#Override
public void mousePressed(MouseEvent e) {
//start drawing the selection
iniSelX = e.getX() - 15;
iniSelY = e.getY() - 20;
}
#Override
public void mouseReleased(MouseEvent e) {
//start drawing the selection
iniSelX = 0;
iniSelY = 0;
endSelX = 0;
endSelY = 0;
}
...
});
}
public void log() {
System.out.println("iniSelX" + iniSelX);
System.out.println("iniSelY" + iniSelY);
System.out.println("endSelX" + endSelX);
System.out.println("endSelY" + endSelY);
} ...
I hope this is useful.

Categories

Resources