1

I'm trying to move a JButton to the location of another one but the button I want to move moves to a wrong point. My idea is that this happens because I use multiple JPanels. I tried: getLocationOnScreen, getBounds and getLocation, but none of them worked, how to solve it? When an user selects a card on the table or from a player by clicking this card the the target is set, the sender is set by clicking a card from the top panel. playerCardSpotTargetand playerCardSpotSender are both of type Card. When I try to move the for example eight of diamonds this card moves to a point behind the eight and nine of clubs.

enter image description here

Code:

This events belong to the blue cards on the table and the cards for the players(I have to change the name of the event, I know).

private void PlayerOneMouseClicked(java.awt.event.MouseEvent evt){
     playerCardSpotTarget=(Card)evt.getSource();        

    if(playerCardSpotTarget.isBorderPainted()){
        playerCardSpotTarget.setBorderPainted(false);
    }
    else{
        playerCardSpotTarget.setBorderPainted(true);
    }
}

This event belongs to the cards in the top panel.

private void MouseClicked(java.awt.event.MouseEvent evt) {                              
     playerCardSpotSender=(Card)evt.getSource();

     System.out.println(playerCardSpotSender.suit+" "+playerCardSpotSender.kind);


     if (playerCardSpotTarget != null && playerCardSpotTarget.isBorderPainted()) {            

       playerCardSpotSender.setLocation(playerCardSpotTarget.getLocation());
       System.out.println(playerCardSpotTarget.getLocationOnScreen());
       System.out.println(playerCardSpotSender.getLocationOnScreen());

     }
}

Layout for the center panel in the JFrame (BorderLayout.CENTER)

   JPanel centerPanelNorth;
   JPanel centerPanelCenter;
   JPanel centerPanelEast;
   JPanel centerPanelSouth;
   JPanel centerPanelWest;
   JLabel tablePicture;
   JPanel centerPanel;


   centerPanel=new JPanel(new BorderLayout()); 
   tablePicture = new JLabel(new ImageIcon(this.getClass().getResource(Constants.POKERTABLE_ICON)));
   centerPanelNorth=new JPanel();
   centerPanelEast=new JPanel();
   centerPanelSouth=new JPanel();
   centerPanelWest=new JPanel();

   centerPanelCenter=new JPanel();

   centerPanel.add(centerPanelCenter,BorderLayout.CENTER);

   centerPanelCenter.add(tablePicture);  

   //add
   tablePicture.add(boardCard1);
   tablePicture.add(boardCard2);
   tablePicture.add(boardCard3);
   tablePicture.setLayout(new GridBagLayout());

   //PLAYER NORTH
   centerPanel.add(centerPanelNorth,BorderLayout.NORTH);
   centerPanelNorth.add(playerOneCardOne);
   centerPanelNorth.add(playerOneCardTwo);

   //PLAYER EAST
   centerPanel.add(centerPanelEast,BorderLayout.EAST);
   centerPanelEast.setLayout(new BoxLayout(centerPanelEast,BoxLayout.X_AXIS));
   centerPanelEast.add(playerTwoCardOne);
   centerPanelEast.add(playerTwoCardTwo);

   //PLAYER SOUTH
   centerPanel.add(centerPanelSouth,BorderLayout.SOUTH);
   centerPanelSouth.add(playerThreeCardOne);
   centerPanelSouth.add(playerThreeCardTwo);

   //PLAYER WEST
   centerPanel.add(centerPanelWest,BorderLayout.WEST);
   centerPanelWest.setLayout(new BoxLayout(centerPanelWest,BoxLayout.X_AXIS));
   centerPanelWest.add(playerFourCardOne);
   centerPanelWest.add(playerFourCardTwo);

Card.java

public class Card extends JButton{
    int suit;
    int kind;
    boolean known;
    String iconPath;
    Integer boardPosition;
}
Sybren
  • 1,071
  • 3
  • 17
  • 51
  • Why move the button, why not move the information? – MadProgrammer Apr 15 '15 at 08:26
  • @MadProgrammer I am also gonna move information, but first I'm working on moving the card, to give the user a visual representation of whats happening. How can I move the button? – Sybren Apr 15 '15 at 08:30
  • Do you want to animate? Or do you just want the other button updated with the contents of the first? – MadProgrammer Apr 15 '15 at 08:32
  • I want to animate. When an user clicks a card from the top pane it has to move to the table/player and when an user double clicks a card from the table/ player it has to move back to it's starting location in the top panel. – Sybren Apr 15 '15 at 08:36
  • @MadProgrammer how can I achieve that? – Sybren Apr 15 '15 at 08:45
  • The problem isn't the animation or movement, but linking the target to its destination – MadProgrammer Apr 15 '15 at 10:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/75275/discussion-between-sybren-and-madprogrammer). – Sybren Apr 15 '15 at 10:12
  • @camickr don't know why u use finally so much if you know the answers feel free to answer, anyway accepted the answer in my last posting – Sybren Apr 15 '15 at 18:14

2 Answers2

1

Animating the button movement isn't actually the hardest problem, the hardest problem is trying to move the data about in away in which you can manage it and how to connect the source component with the target...

Move me

To start with, you need a means by which you can move a component across container boundaries. While there are probably a few ways to do this, the simplest is to probably use the glass pane of the frame

public class AnimationPane extends JPanel {

    public AnimationPane() {
      setOpaque(false);
      setLayout(null);
    }

}

This is nothing special, it's just a JPanel which is transparent and has no layout manager, normally, not recommended, but in the case, we're going to take control..

Now, we need some way to animate the movement...

  public enum Animator {

    INSTANCE;

    private List<IAnimatable> animatables;

    private Timer timer;

    private Animator() {
      animatables = new ArrayList<>(25);
      timer = new Timer(40, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          IAnimatable[] anins = animatables.toArray(new IAnimatable[animatables.size()]);
          for (IAnimatable animatable : anins) {
            animatable.update();
          }
        }
      });
      timer.start();
    }

    public void addAnimatable(IAnimatable animatable) {
      animatables.add(animatable);
    }

    public void removeAnimatable(IAnimatable animatable) {
      animatables.remove(animatable);
    }

  }

  public interface IAnimatable {

    public void update();

  }

  public interface IMoveAnimatable extends IAnimatable{

    public JComponent getSourceComponent();

    public IImportable getImportable();

  }

So the Animator is the core "engine", it's basically a Swing Timer which simply calls update on any IAnimatables it might be managing. The intention with this approach is you can have a number of animations running, but it won't degrade the system (greatly) as you only have a single update/timer point.

Now, normally I'd just use something like the Timing Framework or the Trident Framework or even the Universal Tween Engine

The IAnimatable interfaces just define the basic contracts that provide functionality for the animation.

We need to define some kind of contract the defines objects which can take part in the animation process and receive information, or the "target"

public interface IImportable {
    public JComponent getView();
    public void importValue(String value);
}

public abstract class AbstractImportable extends JPanel implements IImportable {

    @Override
    public JComponent getView() {
        return this;
    }

}

Now it occurs to me that we could tap into the pre-existing Transferable API, which would allow you to also implement drag-n-drop (and even copy/cut and paste), this would be used to define a lookup mechanism where you match a given data type with potential targets based on the DataFlavor ... but I'll leave you to investigate how that might work... The core mechanism basically removes the source component from it's current container, adds it to the AnimationPane, moves the source component across the AnimationPane and then imports the data into the target...

The problem is, you need to translate the location of component from it's current context to the AnimationPane.

A components location is relative to it's parents context. It's relatively easy to do with SwingUtilities.convertPoint(Component, Point, Component)

We calculate the origin point of the source component and the target point, relative to the AnimationPane. We then, on each call to update, calculate the progress of the animation. Instead of using a "delta" movement, we calculate the different between the time we started and a predefined duration (1 second in this case), this generally produces a more flexible animation

  public class DefaultAnimatable implements IMoveAnimatable {

    public static final double PLAY_TIME = 1000d;

    private Long startTime;
    private JComponent sourceComponent;
    private IImportable importable;
    private JComponent animationSurface;

    private Point originPoint, destinationPoint;

    private String value;

    public DefaultAnimatable(JComponent animationSurface, JComponent sourceComponent, IImportable importable, String value) {
      this.sourceComponent = sourceComponent;
      this.importable = importable;
      this.animationSurface = animationSurface;
      this.value = value;
    }

    public String getValue() {
      return value;
    }

    public JComponent getAnimationSurface() {
      return animationSurface;
    }

    @Override
    public JComponent getSourceComponent() {
      return sourceComponent;
    }

    @Override
    public IImportable getImportable() {
      return importable;
    }

    @Override
    public void update() {
      if (startTime == null) {
        System.out.println("Start");
        IImportable importable = getImportable();
        JComponent target = importable.getView();

        originPoint = SwingUtilities.convertPoint(getSourceComponent().getParent(), getSourceComponent().getLocation(), getAnimationSurface());
        destinationPoint = SwingUtilities.convertPoint(target.getParent(), target.getLocation(), getAnimationSurface());

        destinationPoint.x = destinationPoint.x + ((target.getWidth() - getSourceComponent().getWidth()) / 2);
        destinationPoint.y = destinationPoint.y + ((target.getHeight() - getSourceComponent().getHeight()) / 2);

        Container parent = getSourceComponent().getParent();

        getAnimationSurface().add(getSourceComponent());
        getSourceComponent().setLocation(originPoint);

        parent.invalidate();
        parent.validate();
        parent.repaint();

        startTime = System.currentTimeMillis();
      }
      long duration = System.currentTimeMillis() - startTime;
      double progress = Math.min(duration / PLAY_TIME, 1d);

      Point location = new Point();
      location.x = progress(originPoint.x, destinationPoint.x, progress);
      location.y = progress(originPoint.y, destinationPoint.y, progress);

      getSourceComponent().setLocation(location);
      getAnimationSurface().repaint();

      if (progress == 1d) {
        getAnimationSurface().remove(getSourceComponent());
        Animator.INSTANCE.removeAnimatable(this);
        animationCompleted();
      }
    }

    public int progress(int startValue, int endValue, double fraction) {

      int value = 0;
      int distance = endValue - startValue;
      value = (int) Math.round((double) distance * fraction);
      value += startValue;

      return value;

    }

    protected void animationCompleted() {
      getImportable().importValue(getValue());
    }

  }

Okay, now this produces a linear animation, which is pretty boring, now if you have plenty of time, you could create an easement like this or just use one of the animation frameworks...

Now, we need to put it together...

  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Container;
  import java.awt.Dimension;
  import java.awt.EventQueue;
  import java.awt.GridBagLayout;
  import java.awt.GridLayout;
  import java.awt.Point;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.util.ArrayList;
  import java.util.List;
  import javax.swing.JButton;
  import javax.swing.JComponent;
  import javax.swing.JFrame;
  import javax.swing.JPanel;
  import javax.swing.SwingUtilities;
  import javax.swing.Timer;
  import javax.swing.UIManager;
  import javax.swing.UnsupportedLookAndFeelException;
  import javax.swing.border.LineBorder;

  public class AnimationTest {

    public static void main(String[] args) {
      new AnimationTest();
    }

    public AnimationTest() {
      EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
          try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
          } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
          }

         AnimationPane animationPane = new AnimationPane();

          LeftPane leftPane = new LeftPane(animationPane);
          RightPane rightPane = new RightPane();

          leftPane.setImportabale(rightPane);
          rightPane.setImportabale(leftPane);

          JFrame frame = new JFrame("Testing");
          frame.setLayout(new GridLayout(1, 2));
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.add(leftPane, BorderLayout.WEST);
          frame.add(rightPane, BorderLayout.WEST);
          frame.setGlassPane(animationPane);
          animationPane.setVisible(true);
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
        }
      });
    }

    public class RightPane extends AbstractImportable {

      private IImportable source;
      private JButton imported;

      private String importedValue;

      public RightPane() {
        setLayout(new GridBagLayout());
        setBorder(new LineBorder(Color.DARK_GRAY));
      }

      public void setImportabale(IImportable source) {
        this.source = source;
      }

      @Override
      public void importValue(String value) {
        if (imported != null) {
          // May re-animate the movement back...
          remove(imported);
        }
        importedValue = value;
        imported = new JButton(">> " + value + "<<");
        add(imported);
        revalidate();
        repaint();
      }

      @Override
      public Dimension getPreferredSize() {
        return new Dimension(200, 200);
      }

    }

    public class LeftPane extends AbstractImportable {

      private IImportable importable;

      public LeftPane(AnimationPane animationPane) {
        setLayout(new GridBagLayout());
        JButton btn = new JButton("Lefty");
        btn.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            DefaultAnimatable animatable = new DefaultAnimatable(animationPane, btn, importable, "Lefty");
            Animator.INSTANCE.addAnimatable(animatable);
          }
        });

        add(btn);
        setBorder(new LineBorder(Color.DARK_GRAY));
      }

      public void setImportabale(IImportable target) {
        this.importable = target;
      }

      @Override
      public void importValue(String value) {
      }

      @Override
      public Dimension getPreferredSize() {
        return new Dimension(200, 200);
      }

    }
  }
Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

Maybe use mousePressed(),when you move the card,you press it until the target.And in the process, you get the information about JButton.getLocation() by the event,and than you need to solve the collision problem between two cards.So it's good!Of course, this is my advice ,you should have better idea!

Seven
  • 89
  • 6
  • I prefer adding/removing the cards by mouseclicking rather then drag/drop , you know how to do that? – Sybren Apr 15 '15 at 09:21
  • when you don't need it ,call `JPanel.remove()` ,so it would be disappear.But use this, you need to consider the stratified issue.Sometimes, you draw it but you can not see it. – Seven Apr 15 '15 at 09:25