I want to have a window containing two panels in proportion 3:1 vertically and have the bigger one contain a square panel inside. Additionally I'd like to draw something in the center of the square panel, say circle.
The behavior of my program suggests there is a conflict between the 3:1 proportion and the squareness. While resizing, the shapes are jumping oddly. The end result is incorrect, the first proportion does not hold. In addition the circle is off the center.
I am new to java, therefore I would appreciate any remarks on both what is wrong and how to implement this the correct way.
The result looks like:
I wanted to have: (1) red panel as a square (2) blue panel to be strictly 3 times higher than the green one (3) to have circle in the middle of the red box.
Here is my code:
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ProportionalPanels
{
private JFrame window = new JFrame("Proportional resizable panels");
private JPanel allContaining = new JPanel( new GridBagLayout() );
private JPanel upper = new JPanel( new GridBagLayout() );
private JPanel lower = new JPanel();
private JPanel square = new JPanel()
{
#Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
Container c = getParent();
if (c != null) {
d = c.getSize();
} else {
return new Dimension(10, 10);
}
int w = (int) d.getWidth();
int h = (int) d.getHeight();
int s = (w < h ? w : h);
return new Dimension(s, s);
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.GRAY);
int x = this.getWidth(), y = this.getHeight();
g.fillOval(x/2,y/2,x/4,y/4);
}
};
public ProportionalPanels()
{
// setting colors
allContaining.setBackground(Color.WHITE);
square.setBackground(Color.RED);
upper.setBackground(Color.BLUE);
lower.setBackground(Color.GREEN);
// setting upper
upper.add(square);
// setting allContaining
GridBagConstraints g = new GridBagConstraints();
g.gridx = g.gridy = 0;
g.weightx = 1; g.weighty = 0.75;
g.fill = GridBagConstraints.BOTH;
allContaining.add(upper,g);
g.gridy += 1;
g.weighty = 0.25;
g.fill = GridBagConstraints.BOTH;
allContaining.add(lower,g);
window.add(allContaining);
// setting window
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
window.setLocationByPlatform(true);
window.pack();
window.setSize(400,200);
window.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
new ProportionalPanels();
}
});
}
}
EDIT
Imagine a simple gui for chess, only for the sake of analogy. A window is spit into two panels, upper one (blue) is to contain the chessboard, the lower one (green) some buttons. The only constraint for these panels is to keep the vertical proportion - the upper one is to be 3x higher than the lower one. Then, going deeper into panel hierarchy, the upper panel contains square panel (red), which is a square, has the height of the upper panel and is horizontally placed in the middle of the upper panel. Extra space is on the left and right of the square panel, adjusts accordingly to keep red panel square and green's height 3 times smaller than the red's. Red panel holds some drawings at its center, here a gray circle, but to keep the chess analogy it can be an image of chessboard or anything else.
I'm not 100% sure I understand the question, however, it's never stopped me before...
Basically, this uses a VERY crude, custom layout manager. It will keep the first and last components sized do 3:1 ratio (width is ratio of the height)
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JPanel master = new JPanel(new PropertionalLayoutManager());
master.add(new TestPane(), "left");
master.add(new DrawPane(), "center");
master.add(new TestPane(), "right");
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(master);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class PropertionalLayoutManager implements LayoutManager {
private Component left;
private Component right;
private Component center;
#Override
public void addLayoutComponent(String name, Component comp) {
if ("center".equals(name)) {
center = comp;
} else if ("left".equals(name)) {
left = comp;
} else if ("right".equals(name)) {
right = comp;
}
}
#Override
public void removeLayoutComponent(Component comp) {
}
public Dimension getSize(Dimension leftSize, Dimension centerSize, Dimension rightSize) {
Dimension size = new Dimension();
if (leftSize != null && right != null) {
int width = leftSize.width;
int height = leftSize.height;
size.width = Math.max(width, rightSize.width) * 2;
size.height = Math.max(height, rightSize.height);
} else if (leftSize != null) {
size.width = leftSize.width;
size.height = leftSize.height;
} else if (right != null) {
size.width = rightSize.width;
size.height = rightSize.height;
}
if (center != null) {
size.width += centerSize.width;
size.height = Math.max(centerSize.height, size.height);
}
return size;
}
#Override
public Dimension preferredLayoutSize(Container parent) {
Dimension leftSize = left == null ? null : left.getPreferredSize();
Dimension centerSize = right == null ? null : right.getPreferredSize();
Dimension rightSize = center == null ? null : center.getPreferredSize();
return getSize(leftSize, centerSize, rightSize);
}
#Override
public Dimension minimumLayoutSize(Container parent) {
Dimension leftSize = left == null ? null : left.getMinimumSize();
Dimension centerSize = right == null ? null : right.getMinimumSize();
Dimension rightSize = center == null ? null : center.getMinimumSize();
return getSize(leftSize, centerSize, rightSize);
}
#Override
public void layoutContainer(Container parent) {
// Get rid of anything else that might have been added...
for (Component comp : parent.getComponents()) {
comp.setBounds(0, 0, 0, 0);
}
int width = parent.getWidth();
int height = parent.getHeight();
int outterWidth = height / (3 / 1);
if (left != null) {
left.setBounds(0, 0, outterWidth, height);
}
if (right != null) {
right.setBounds(width - outterWidth, 0, outterWidth, height);
}
if (center != null) {
center.setBounds(outterWidth, 0, width - (outterWidth * 2), height);
}
}
}
public class DrawPane extends JPanel {
public DrawPane() {
setBackground(Color.BLUE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = (getWidth() - (getWidth() / 4)) / 2;
int y = (getHeight()- (getHeight() / 4)) / 2;
g.setColor(Color.GRAY);
g.fillOval(x, y, getWidth() / 4, getHeight() / 4);
}
}
public class TestPane extends JPanel {
private JLabel size;
public TestPane() {
setBackground(Color.GREEN);
setLayout(new GridBagLayout());
size = new JLabel();
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
size.setText(getWidth() + "x" + getHeight());
}
});
add(size);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 300);
}
}
}
Updated
Something more like ...
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JPanel master = new JPanel(new SquareLayoutManager());
master.setBackground(Color.BLUE);
master.add(new DrawPane());
JPanel green = new JPanel();
green.setBackground(Color.GREEN);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 0.66666666666667;
gbc.fill = GridBagConstraints.BOTH;
frame.add(master, gbc);
gbc.gridy++;
gbc.weighty = 0.33333333333333;
frame.add(green, gbc);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class SquareLayoutManager implements LayoutManager {
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
}
#Override
public Dimension preferredLayoutSize(Container parent) {
int width = 0;
int height = 0;
for (Component comp : parent.getComponents()) {
width = Math.max(comp.getPreferredSize().width, width);
height = Math.max(comp.getPreferredSize().width, height);
}
// You could define rows and columns and blah, blah, blah, but I'm to lazy...
return new Dimension(Math.max(width, height) * parent.getComponentCount(), Math.max(width, height));
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int size = (parent.getHeight() - (insets.bottom + insets.top));
int x = (parent.getWidth() - (insets.left + insets.right) - size) / 2;
int y = insets.top;
for (Component comp : parent.getComponents()) {
comp.setBounds(x, y, size, size);
x += size; // Could define a gap, but, lazy...
}
}
}
public class DrawPane extends JPanel {
public DrawPane() {
setBackground(Color.RED);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = (getWidth() - (getWidth() / 4)) / 2;
int y = (getHeight() - (getHeight() / 4)) / 2;
g.setColor(Color.GRAY);
g.fillOval(x, y, getWidth() / 4, getHeight() / 4);
}
}
}
Related
Guys I want to know if there is a way to make this arrow to be dragable just with X axis. I am using a null layout here, and this arrow is a jlabel that has been add into the jframe. Here is the image for more info. Thank you in advance.
(I've edited this to show some of my codes before this questions was solved. The codes here that has been shown are just about the ImageIcon, JLabel, and some part of the JFrame)
public class Level03 extends JFrame implements ActionListener, MouseListener, WindowListener {
// These are the Global Variables of the Level03 Class
ImageIcon slide = new ImageIcon("slide to unlock.png");
ImageIcon slideButton = new ImageIcon("arrow icon.png");
JLabel slideLabel = new JLabel(slide);
JLabel slideArrow = new JLabel(slideButton);
Level03 (){ // This is the constructor
// This is just setting the bounds of the arrow on the JFrame
slideLabel.setBounds(100,350, 400,50);
slideArrow.setBounds(117,350,50,50); // slideArrow is the JLabel with the ImageIcon that looks like an arrow.
slideArrow.addMouseListener(this);
mainFrame.add(slideArrow);
mainFrame.add(slideLabel);
}
NOTE!!!
I also have these overrides below from the implements ActionListener, MouseListener, WindowListener
#Override
public void actionPerformed(ActionEvent e){}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
Doing this kind of thing with a JLabel isn't impossible, it's just, complicated.
Personally, I'd be tempted to just use a custom painted route, as it gives you all the control.
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
public TestPane() throws IOException {
setBorder(new EmptyBorder(32, 32, 32, 32));
setLayout(new GridBagLayout());
add(new SlideToUnlock());
}
}
public class SlideToUnlock extends JPanel {
private String text;
private Image indicatorImage;
private Rectangle indicatorBounds;
private Integer clickXOffset; // I can make it null and then ignore it
private Integer dragX; // The x position of the drag
public SlideToUnlock() throws IOException {
indicatorImage = ImageIO.read(getClass().getResource("/images/ArrowRight.png"));
setText("Slide to unlock");
MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
resetTimer();
Rectangle bounds = getIndiciatorImageBounds();
if (bounds.contains(e.getPoint())) {
clickXOffset = e.getPoint().x - bounds.x;
} else {
clickXOffset = null;
}
dragX = null;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
invalidate();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
dragX = e.getPoint().x;
if (didReachTheOtherSide()) {
// Notifiy some kind of observer
}
repaint();
}
};
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
}
#Override
public Dimension getPreferredSize() {
FontMetrics fm = getFontMetrics(getFont());
Image indicatorImage = getIndicatorImage();
Insets insets = getInsets();
int imageWidth = 0;
int imageHeight = 0;
if (indicatorImage != null) {
imageWidth = indicatorImage.getWidth(this);
imageHeight = indicatorImage.getHeight(this);
}
int height = Math.max(fm.getHeight(), imageHeight)
+ 1 // Border
+ 8; // Inner track
int width = 1 + 8 + fm.stringWidth(getText()) + imageWidth;
width += insets.left + insets.right;
height += insets.top + insets.top;
return new Dimension(width, height);
}
#Override
public void invalidate() {
super.invalidate();
indicatorBounds = null;
clickXOffset = null;
dragX = null;
}
#Override
public void revalidate() {
super.revalidate();
indicatorBounds = null;
clickXOffset = null;
dragX = null;
}
protected boolean didReachTheOtherSide() {
Rectangle bounds = getIndiciatorImageBounds();
return bounds.x + bounds.width >= getWidth() - 1;
}
protected Rectangle getIndiciatorImageBounds() {
if (getParent() == null) {
return null;
}
if (dragX == null && indicatorBounds != null) {
return indicatorBounds;
}
Image indicatorImage = getIndicatorImage();
int indicatorX = 1;
int indicatorY = (getHeight() - indicatorImage.getHeight(this)) / 2;
indicatorBounds = new Rectangle(indicatorX, indicatorY, indicatorImage.getWidth(this), indicatorImage.getHeight(this));
if (dragX != null) {
indicatorBounds.x = (indicatorBounds.x - clickXOffset) + dragX;
if (indicatorBounds.x + indicatorBounds.width > (getWidth() - 1)) {
indicatorBounds.x = getWidth() - indicatorBounds.width - 1;
} else if (indicatorBounds.x < 1) {
indicatorBounds.x = 1;
}
}
return indicatorBounds;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Image getIndicatorImage() {
return indicatorImage;
}
public void setIndicatorImage(Image indicatorImage) {
this.indicatorImage = indicatorImage;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int cornerRadius = 16;
paintText(g2d);
paintOverlay(g2d);
paintIndicator(g2d);
g2d.setColor(getForeground());
g2d.draw(new RoundRectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1, cornerRadius, cornerRadius));
g2d.dispose();
}
protected void paintOverlay(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(getBackground());
Rectangle bounds = getIndiciatorImageBounds();
g2d.fillRect(1, 1, bounds.x + bounds.width, getHeight() - 2);
g2d.dispose();
}
protected void paintText(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(getForeground());
FontMetrics fm = g2d.getFontMetrics();
String text = getText();
int xPos = getWidth() - 1 - 4 - fm.stringWidth(text);
int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, xPos, yPos);
g2d.dispose();
}
protected void paintIndicator(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = getIndiciatorImageBounds();
g2d.translate(bounds.x, bounds.y);
Image indicatorImage = getIndicatorImage();
g2d.drawImage(indicatorImage, 0, 0, this);
g2d.dispose();
}
}
}
Ok, so, that "works", it does the job, but it's missing something 🤔 ... rebound! When the user lets go of the slide control, it should animate the rebound!
(I bet you're sorry you asked now)
Ah, that's better, it's the small details which make it 😉
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
public TestPane() throws IOException {
setBorder(new EmptyBorder(32, 32, 32, 32));
setLayout(new GridBagLayout());
add(new SlideToUnlock());
}
}
public class SlideToUnlock extends JPanel {
private String text;
private Image indicatorImage;
private Rectangle indicatorBounds;
private Integer clickXOffset; // I can make it null and then ignore it
private Integer dragX; // The x position of the drag
private Timer reboundTimer;
public SlideToUnlock() throws IOException {
indicatorImage = ImageIO.read(getClass().getResource("/images/ArrowRight.png"));
setText("Slide to unlock");
MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
resetReboundTimer();
Rectangle bounds = getIndiciatorImageBounds();
if (bounds.contains(e.getPoint())) {
clickXOffset = e.getPoint().x - bounds.x;
} else {
clickXOffset = null;
}
dragX = null;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
startReboundTimer();
}
#Override
public void mouseDragged(MouseEvent e) {
dragX = e.getPoint().x;
if (didReachTheOtherSide()) {
// Notifiy some kind of observer
}
repaint();
}
};
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
}
protected void resetReboundTimer() {
if (reboundTimer == null) {
return;
}
reboundTimer.stop();
reboundTimer = null;
}
protected void startReboundTimer() {
resetReboundTimer();
Rectangle bounds = getIndiciatorImageBounds();
clickXOffset = 0;
dragX = bounds.x + (bounds.width / 2);
int lowerRange = 1 + (bounds.width / 2);
int upperRange = getWidth() - 1 - (bounds.width / 2);
int fullRange = upperRange - lowerRange;
int currentRange = (bounds.x + (bounds.width / 2)) - lowerRange;
double progressRange = currentRange / (double) fullRange;
Duration fullDuration = Duration.ofMillis(250);
Duration desiredDuration = Duration.ofMillis((long) (fullDuration.toMillis() * progressRange));
int remainingRange = (int) (fullRange * progressRange);
reboundTimer = new Timer(5, new ActionListener() {
private Instant startTime = null;
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = Instant.now();
}
Duration runTime = Duration.between(startTime, Instant.now());
double runTimeProgress = runTime.toMillis() / (double) desiredDuration.toMillis();
if (runTimeProgress >= 1.0) {
resetReboundTimer();
invalidate();
} else {
dragX = (int) (remainingRange * (1.0 - runTimeProgress));
}
repaint();
}
});
reboundTimer.setInitialDelay(0);
reboundTimer.start();
}
#Override
public Dimension getPreferredSize() {
FontMetrics fm = getFontMetrics(getFont());
Image indicatorImage = getIndicatorImage();
Insets insets = getInsets();
int imageWidth = 0;
int imageHeight = 0;
if (indicatorImage != null) {
imageWidth = indicatorImage.getWidth(this);
imageHeight = indicatorImage.getHeight(this);
}
int height = Math.max(fm.getHeight(), imageHeight)
+ 1 // Border
+ 8; // Inner track
int width = 1 + 8 + fm.stringWidth(getText()) + imageWidth;
width += insets.left + insets.right;
height += insets.top + insets.top;
return new Dimension(width, height);
}
#Override
public void invalidate() {
super.invalidate();
indicatorBounds = null;
clickXOffset = null;
dragX = null;
}
#Override
public void revalidate() {
super.revalidate();
indicatorBounds = null;
clickXOffset = null;
dragX = null;
}
protected boolean didReachTheOtherSide() {
Rectangle bounds = getIndiciatorImageBounds();
return bounds.x + bounds.width >= getWidth() - 1;
}
protected Rectangle getIndiciatorImageBounds() {
if (getParent() == null) {
return null;
}
if (dragX == null && indicatorBounds != null) {
return indicatorBounds;
}
Image indicatorImage = getIndicatorImage();
int indicatorX = 1;
int indicatorY = (getHeight() - indicatorImage.getHeight(this)) / 2;
indicatorBounds = new Rectangle(indicatorX, indicatorY, indicatorImage.getWidth(this), indicatorImage.getHeight(this));
if (dragX != null) {
indicatorBounds.x = (indicatorBounds.x - clickXOffset) + dragX;
if (indicatorBounds.x + indicatorBounds.width > (getWidth() - 1)) {
indicatorBounds.x = getWidth() - indicatorBounds.width - 1;
} else if (indicatorBounds.x < 1) {
indicatorBounds.x = 1;
}
}
return indicatorBounds;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Image getIndicatorImage() {
return indicatorImage;
}
public void setIndicatorImage(Image indicatorImage) {
this.indicatorImage = indicatorImage;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int cornerRadius = 16;
paintText(g2d);
paintOverlay(g2d);
paintIndicator(g2d);
g2d.setColor(getForeground());
g2d.draw(new RoundRectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1, cornerRadius, cornerRadius));
g2d.dispose();
}
protected void paintOverlay(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(getBackground());
Rectangle bounds = getIndiciatorImageBounds();
g2d.fillRect(1, 1, bounds.x + bounds.width, getHeight() - 2);
g2d.dispose();
}
protected void paintText(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(getForeground());
FontMetrics fm = g2d.getFontMetrics();
String text = getText();
int xPos = getWidth() - 1 - 4 - fm.stringWidth(text);
int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, xPos, yPos);
g2d.dispose();
}
protected void paintIndicator(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = getIndiciatorImageBounds();
g2d.translate(bounds.x, bounds.y);
Image indicatorImage = getIndicatorImage();
g2d.drawImage(indicatorImage, 0, 0, this);
g2d.dispose();
}
}
}
Now, doing this with labels would follow a very similar course, the only addition would be, you'd have to manage the label size and position yourself (as most layout managers won't allow you to this)
The following are some examples of dragging a label via a MouseMotionListener
JLabel is not moving properly using mouse motion listener, Why?
How to prevent JLabel positions from resetting?
JLabels, that store ImageIcons, are returned back to original location when the mouse is clicked in the panel
How to make draggable components with ImageIcon
Now, in your case, you don't really care about the y position, so that can remain centred relative to the container (or the track) and you'd just need to update the x position based on the click offset and the current drag position. Complications arise in the fact that you'd need to be monitoring the label for drags, but converting the position of the movement to the parent container. Doable, but it's an additional complication
With a JLabel
The basic workflow is the same, except now you need to manage the component state of the label (in fact labels, because I assume you'll also want to have some text).
This is a basic example which allows you to drag the a label, it's core workflow is basically the same as the previous examples, but it has the added overhead of needing to manage the component(s) size and positions as well
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
public TestPane() throws IOException {
setBorder(new EmptyBorder(32, 32, 32, 32));
setLayout(new GridBagLayout());
add(new SlideToUnlock());
}
}
public class SlideToUnlock extends JPanel {
private JLabel indicatorLabel;
private JPanel overLayPane;
private JLabel textLabel;
private Integer clickXOffset;
private Integer dragX;
public SlideToUnlock() throws IOException {
setLayout(null);
setBorder(new LineBorder(getForeground(), 1, true));
indicatorLabel = new JLabel(new ImageIcon(ImageIO.read(getClass().getResource("/images/ArrowRight.png"))));
indicatorLabel.setBounds(1, 1, indicatorLabel.getPreferredSize().width, indicatorLabel.getPreferredSize().height);
add(indicatorLabel);
overLayPane = new JPanel();
add(overLayPane);
textLabel = new JLabel("Slide to unlock");
textLabel.setBounds(1, 1, textLabel.getPreferredSize().width, textLabel.getPreferredSize().height);
add(textLabel);
MouseAdapter mouseAdapter = new MouseAdapter() {
protected void resetDrag() {
clickXOffset = null;
dragX = null;
}
#Override
public void mousePressed(MouseEvent e) {
Point point = e.getPoint();
Point localPoint = SwingUtilities.convertPoint(SlideToUnlock.this, point, indicatorLabel);
if (indicatorLabel.getBounds().contains(localPoint)) {
clickXOffset = point.x - indicatorLabel.getBounds().x;
} else {
resetDrag();
}
}
#Override
public void mouseReleased(MouseEvent e) {
resetDrag();
doLayout();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
dragX = e.getPoint().x;
doLayout();
repaint();
}
};
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
}
#Override
public void doLayout() {
Dimension preferredSize = indicatorLabel.getPreferredSize();
int xPos = 1;
if (dragX != null) {
xPos = (1 - clickXOffset) + dragX;
if (xPos + preferredSize.width > (getWidth() - 1)) {
xPos = getWidth() - preferredSize.width - 1;
} else if (xPos < 1) {
xPos = 1;
}
}
indicatorLabel.setBounds(xPos, 1, indicatorLabel.getPreferredSize().width, getHeight() - 2);
overLayPane.setBounds(1, 1, indicatorLabel.getX() + indicatorLabel.getWidth() - 1, getHeight() - 2);
textLabel.setBounds(getWidth() - textLabel.getPreferredSize().width - 4, 1, textLabel.getPreferredSize().width, getHeight() - 2);
}
#Override
public Dimension getPreferredSize() {
Dimension preferredSize = indicatorLabel.getPreferredSize();
preferredSize.width = preferredSize.width * 4;
int height = Math.max(indicatorLabel.getPreferredSize().height, textLabel.getPreferredSize().height);
int width = indicatorLabel.getPreferredSize().width + 4 + textLabel.getPreferredSize().width + 4 + 1;
Insets insets = getInsets();
width += insets.left + insets.right;
height += insets.top + insets.bottom;
return new Dimension(width, height);
}
}
}
This a quick "hack". You'll need to provide addition functionality to change the text, support changes to the background color and update the state when it does change
I am trying to create a 2d grid on a JPanel with Zoom functionality. The user will draw on the grid then if desired zoom in and out. Image of Grid
When I currently zoom in there is a screen shake like issue/bug when I move the mouse around, which I would like to remove.
The mouse wheel is used to zoom in and out.
Grid to be drawn at certain scale factor:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class DrawExample extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private double zoom = 1d;
private int tranx;
private int trany;
DrawPanel drawPanel;
int snapmousepositionx, snapmousepositiony;//current snap coords
int currentmousepositionx,currentmousepositiony;
public DrawExample() {
drawPanel = new DrawPanel();
JPanel containerPanel = new JPanel();
JFrame frame = new JFrame(); // Instance of a JFrame
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(800, 800);
//drawPanel.setSize(800, 800);
containerPanel.setLayout(new GridBagLayout());
containerPanel.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(1, 0, 1)));
containerPanel.add(drawPanel);
frame.add(new JScrollPane(containerPanel));
drawPanel.addMouseWheelListener(new MouseAdapter() { //add wheel listener to drawPanel
#Override
public void mouseWheelMoved(MouseWheelEvent e) { //when wheel is moved
if (e.getPreciseWheelRotation() < 0) {
zoom += 0.1;
} else {
zoom -= 0.1;
}
if (zoom < 0.01) {
zoom = 0.01;
}
drawPanel.repaint();
}
});
drawPanel.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent me) {
super.mouseMoved(me);
drawPanel.createSnapGrid(me.getPoint().x, me.getPoint().y);
tranx=me.getPoint().x;
trany=me.getPoint().y;
drawPanel.repaint();
}
});
frame.setVisible(true);
}
public class DrawPanel extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
zoom=Math.round(zoom*10.0)/10.0;
AffineTransform at = g2d.getTransform();
at.translate(tranx, trany);
at.scale(zoom, zoom);
at.translate(-tranx, -trany);
g2d.setTransform(at);
g2d.setColor(Color.lightGray);
drawGrid(g2d);
}
public void createSnapGrid(int x, int y) {
currentmousepositionx = x;
currentmousepositiony = y;
int remainderx = currentmousepositionx % 10, remaindery = currentmousepositiony % 10;
if (remainderx<800/2) setSnapX(currentmousepositionx - remainderx) ;
else setSnapX(currentmousepositionx + (10-remainderx));
if (remaindery<800/2) setSnapY(currentmousepositiony - remaindery);
else setSnapY(currentmousepositiony + (10)-remaindery);
}
}
public void drawGrid(Graphics2D g) {
g.setColor(Color.lightGray);
g.clearRect(0, 0, 800, 800);
System.out.println(getHeight());
//grid vertical lines
for (int i= (10);i<800;i+=10) {
g.drawLine(i, 0, i, 800);
}
//grid horizontal lines
for (int j= (10);j<800;j+=10) {
g.drawLine(0, j, 800, j);
}
//show the snapped point
g.setColor(Color.BLACK);
if ( getSnapX()>=0 && getSnapY()>=0 && getSnapX()<=800 && getSnapY()<=800) {
// result =true;
g.drawOval((int) ( getSnapX())-4, (int) (getSnapY()-4), 8, 8);
}
}
public int getSnapX(){
return (this.snapmousepositionx);
}
public int getSnapY(){
return (this.snapmousepositiony);
}
public void setSnapX(int snap){
this.snapmousepositionx=(int) (snap);
}
public void setSnapY(int snap){
this.snapmousepositiony=(int) (snap);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DrawExample();
}
});
}
}
I'm trying to create a little game where the user need to click on squares for x seconds. The square should dissapear on mouse click or after 1 sec and at the end I should display the final score he achieved. I'm having troubles making the square repaint every second.
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class RectangleDemo extends JPanel{
JLabel label;
void buildUI(Container container) {
container.setLayout(new BoxLayout(container,BoxLayout.Y_AXIS));
RectangleArea rectangleArea = new RectangleArea(this);
container.add(rectangleArea);
label = new JLabel("Click within the framed area.");
container.add(label);
//Align the left edges of the components.
rectangleArea.setAlignmentX(LEFT_ALIGNMENT);
label.setAlignmentX(LEFT_ALIGNMENT); //unnecessary, but doesn't hurt
}
public void updateLabel(Point point) {
label.setText("Click occurred at coordinate ("
+ point.x + ", " + point.y + ").");
}
//Called only when this is run as an application.
public static void main(String[] args) {
JFrame f = new JFrame();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
RectangleDemo controller = new RectangleDemo();
controller.buildUI(f.getContentPane());
f.pack();
f.setVisible(true);
}
}
class RectangleArea extends JPanel implements ActionListener{
Random rand = new Random();
int a = rand.nextInt(400);
int b = rand.nextInt(400);
Point point = new Point(a,b);
RectangleDemo controller;
Dimension preferredSize = new Dimension(500,500);
int rectWidth = 50;
int rectHeight = 50;
Timer t;
public void actionPerformed (ActionEvent e)
{
}
//final Container panou;
public RectangleArea(RectangleDemo controller) {
this.controller = controller;
t=new Timer(1000, this); t.start();
Border raisedBevel = BorderFactory.createRaisedBevelBorder();
Border loweredBevel = BorderFactory.createLoweredBevelBorder();
Border compound = BorderFactory.createCompoundBorder
(raisedBevel, loweredBevel);
setBorder(compound);
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
if ((point.x<=x)&&(x<=point.x+50)&&(point.y<=y)&&(y<=point.y+50))
{point=null;t.stop();}
repaint();
}
});
}
public Dimension getPreferredSize() {
return preferredSize;
}
public void paintComponent(Graphics g) {
super.paintComponent(g); //paint background
//Paint a filled rectangle at user's chosen point.
if (point != null) {
g.drawRect(point.x, point.y,
rectWidth - 1, rectHeight - 1);
g.setColor(Color.yellow);
g.fillRect(point.x + 1, point.y + 1,
rectWidth - 2, rectHeight - 2);
controller.updateLabel(point);
}
}
}
Also after I click the first square I get get this error message:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at RectangleArea$1.mousePressed(RectangleDemo.java:82)
I'm not sure how I would fix the errors in my program and how I would highlight the option the user is hovering on. I want it to highlight the code for each position, i.e position 1 would be highlighted(as a different color) to start game,etc. and up/down would change position and I would change the position with up ,down, left, right. This is what I have so far. At the moment its bugged and when compiled with my window it comes out as:
Which works for the main game and altered for this titleboard, what am I doing wrong and how do I fix it?
TitleBoard class
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.ArrayList;
//sound + file opening
import java.io.*;
import javax.sound.sampled.*;
public class TitleBoard extends JPanel implements ActionListener{
private ArrayList<String> OptionList;
private Image background;
private ImageIcon bgImageIcon;
private String cheatString;
private int position;
private Timer timer;
public TitleBoard(){
setFocusable(true);
addKeyListener(new TAdapter());
bgImageIcon = new ImageIcon("");
background = bgImageIcon.getImage();
String[] options = {"Start Game","Options","Quit"};
OptionList = new ArrayList<String>();
optionSetup(options);
position = 1;
timer = new Timer(8, this);
timer.start();
/*
1 mod 3 =>1 highlight on start
2 mod 3 =>2 highlight on options
3 mod 3 =>0 highlight on quit
*/
try{
Font numFont = Font.createFont(Font.TRUETYPE_FONT,new File("TwistedStallions.ttf"));
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
ge.registerFont(numFont);
setFont(numFont.deriveFont(24f)); //adjusthislater
}catch(IOException|FontFormatException e){
e.printStackTrace();
}
}
private void optionSetup(String[] s){
for(int i=0; i<s.length;i++) {
OptionList.add(s[i]);
}
}
public void paint(Graphics g){
super.paint(g);
Graphics g2d = (Graphics2D)g;
g2d.drawImage(background,0,0,this);
for (int i=0;i<OptionList.size();i++){
g2d.drawString(OptionList.get(i),200,120+120*i);
}/*
g2d.drawString(OptionList.get(1),400,240);
g2d.drawString(OptionList.get(2),400,360);
//instructions on start screen maybe??
//800x500
//highlighting*/
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
public void actionPerformed(ActionEvent e){
repaint();
}
public class TAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e){
if(e.getKeyCode() == KeyEvent.VK_UP||
e.getKeyCode() == KeyEvent.VK_RIGHT){
position++;
}
if(e.getKeyCode() == KeyEvent.VK_DOWN||
e.getKeyCode() == KeyEvent.VK_LEFT){
position--;
}
}
}
}
Window Class
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Window extends JFrame{
public Window(){
int width = 800, height = 600;
//TO DO: make a panel in TITLE MODE
///////////////////////////////////
//panel in GAME MODE.
add(new TitleBoard());
//set default close
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(width,height);
//centers window
setLocationRelativeTo(null);
setTitle("Title");
setResizable(false);
setVisible(true);
}
public static void main(String[] args){
new Window();
}
}
There are any number of ways you might achieve this, for example, you could use some kind of delegation model.
That is, rather then trying to mange of each element in a single method (or methods), you could devise a delegate which provide a simple interface method that the paint method would call and it would know how to do the rest.
For example, Swing uses this type of concept with it's cell renderers for JList, JTable and JTree.
For example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MyAwesomeMenu {
public static void main(String[] args) {
new MyAwesomeMenu();
}
public MyAwesomeMenu() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<String> menuItems;
private String selectMenuItem;
private String focusedItem;
private MenuItemPainter painter;
private Map<String, Rectangle> menuBounds;
public TestPane() {
setBackground(Color.BLACK);
painter = new SimpleMenuItemPainter();
menuItems = new ArrayList<>(25);
menuItems.add("Start Game");
menuItems.add("Options");
menuItems.add("Exit");
selectMenuItem = menuItems.get(0);
MouseAdapter ma = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
String newItem = null;
for (String text : menuItems) {
Rectangle bounds = menuBounds.get(text);
if (bounds.contains(e.getPoint())) {
newItem = text;
break;
}
}
if (newItem != null && !newItem.equals(selectMenuItem)) {
selectMenuItem = newItem;
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e) {
focusedItem = null;
for (String text : menuItems) {
Rectangle bounds = menuBounds.get(text);
if (bounds.contains(e.getPoint())) {
focusedItem = text;
repaint();
break;
}
}
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "arrowDown");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "arrowUp");
am.put("arrowDown", new MenuAction(1));
am.put("arrowUp", new MenuAction(-1));
}
#Override
public void invalidate() {
menuBounds = null;
super.invalidate();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (menuBounds == null) {
menuBounds = new HashMap<>(menuItems.size());
int width = 0;
int height = 0;
for (String text : menuItems) {
Dimension dim = painter.getPreferredSize(g2d, text);
width = Math.max(width, dim.width);
height = Math.max(height, dim.height);
}
int x = (getWidth() - (width + 10)) / 2;
int totalHeight = (height + 10) * menuItems.size();
totalHeight += 5 * (menuItems.size() - 1);
int y = (getHeight() - totalHeight) / 2;
for (String text : menuItems) {
menuBounds.put(text, new Rectangle(x, y, width + 10, height + 10));
y += height + 10 + 5;
}
}
for (String text : menuItems) {
Rectangle bounds = menuBounds.get(text);
boolean isSelected = text.equals(selectMenuItem);
boolean isFocused = text.equals(focusedItem);
painter.paint(g2d, text, bounds, isSelected, isFocused);
}
g2d.dispose();
}
public class MenuAction extends AbstractAction {
private final int delta;
public MenuAction(int delta) {
this.delta = delta;
}
#Override
public void actionPerformed(ActionEvent e) {
int index = menuItems.indexOf(selectMenuItem);
if (index < 0) {
selectMenuItem = menuItems.get(0);
}
index += delta;
if (index < 0) {
selectMenuItem = menuItems.get(menuItems.size() - 1);
} else if (index >= menuItems.size()) {
selectMenuItem = menuItems.get(0);
} else {
selectMenuItem = menuItems.get(index);
}
repaint();
}
}
}
public interface MenuItemPainter {
public void paint(Graphics2D g2d, String text, Rectangle bounds, boolean isSelected, boolean isFocused);
public Dimension getPreferredSize(Graphics2D g2d, String text);
}
public class SimpleMenuItemPainter implements MenuItemPainter {
public Dimension getPreferredSize(Graphics2D g2d, String text) {
return g2d.getFontMetrics().getStringBounds(text, g2d).getBounds().getSize();
}
#Override
public void paint(Graphics2D g2d, String text, Rectangle bounds, boolean isSelected, boolean isFocused) {
FontMetrics fm = g2d.getFontMetrics();
if (isSelected) {
paintBackground(g2d, bounds, Color.BLUE, Color.WHITE);
} else if (isFocused) {
paintBackground(g2d, bounds, Color.MAGENTA, Color.BLACK);
} else {
paintBackground(g2d, bounds, Color.DARK_GRAY, Color.LIGHT_GRAY);
}
int x = bounds.x + ((bounds.width - fm.stringWidth(text)) / 2);
int y = bounds.y + ((bounds.height - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(isSelected ? Color.WHITE : Color.LIGHT_GRAY);
g2d.drawString(text, x, y);
}
protected void paintBackground(Graphics2D g2d, Rectangle bounds, Color background, Color foreground) {
g2d.setColor(background);
g2d.fill(bounds);
g2d.setColor(foreground);
g2d.draw(bounds);
}
}
}
For here, you could add ActionListener
When a GUI needs a button, use a JButton! The JButton API allows the possibility to add icons for many different circumstances. This example shows different icons for the standard icon, the hover icon, and the pressed icon. Your GUI would obviously use icons with text on them for the required effect.
The icons are pulled directly (hot-linked) from Example images for code and mark-up Q&As.
Standard
Hover over triangle
Press triangle
Code
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.net.URL;
public class IconHoverFocusIndication {
// the GUI as seen by the user (without frame)
// swap the 1 and 0 for single column
JPanel gui = new JPanel(new GridLayout(1,0,50,50));
public static final int GREEN = 0, YELLOW = 1, RED = 2;
String[][] urls = {
{
"http://i.stack.imgur.com/T5uTa.png",
"http://i.stack.imgur.com/IHARa.png",
"http://i.stack.imgur.com/wCF8S.png"
},
{
"http://i.stack.imgur.com/gYxHm.png",
"http://i.stack.imgur.com/8BGfi.png",
"http://i.stack.imgur.com/5v2TX.png"
},
{
"http://i.stack.imgur.com/1lgtq.png",
"http://i.stack.imgur.com/6ZXhi.png",
"http://i.stack.imgur.com/F0JHK.png"
}
};
IconHoverFocusIndication() throws Exception {
// adjust to requirement..
gui.setBorder(new EmptyBorder(15, 30, 15, 30));
gui.setBackground(Color.BLACK);
Insets zeroMargin = new Insets(0,0,0,0);
for (int ii = 0; ii < 3; ii++) {
JButton b = new JButton();
b.setBorderPainted(false);
b.setMargin(zeroMargin);
b.setContentAreaFilled(false);
gui.add(b);
URL url1 = new URL(urls[ii][GREEN]);
BufferedImage bi1 = ImageIO.read(url1);
b.setIcon(new ImageIcon(bi1));
URL url2 = new URL(urls[ii][YELLOW]);
BufferedImage bi2 = ImageIO.read(url2);
b.setRolloverIcon(new ImageIcon(bi2));
URL url3 = new URL(urls[ii][RED]);
BufferedImage bi3 = ImageIO.read(url3);
b.setPressedIcon(new ImageIcon(bi3));
}
}
public JComponent getGUI() {
return gui;
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
try {
IconHoverFocusIndication ihfi =
new IconHoverFocusIndication();
JFrame f = new JFrame("Button Icons");
f.add(ihfi.getGUI());
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
I am creating a simple game where a person clicks on an image the score increases by one.
It seems simple enough, right? Here's the catch-- the images will be hidden partially behind other images!
Currently, I'm using several imageIcons to set up my scene. For instance, my foreground has an image "foreground.png," my background is "background.png", and my image that is hiding between the two is "hiding.png".
My first thought was to simply get the coordinates of the imageIcon hiding, add the height() and width() to them, and create a mouse listener that would only work in that specified region. However, that would give me a rectangle for the user to click which would defeat the purpose of hiding the object (someone could click the rigid boundary of the graphic behind the foreground).
Do you have any suggestions on how to make a mouse action listener work only on the VISIBLE pixels of an imageIcon? Yes, I understand that action listeners can only be applied to components (such as buttons) but "the button" just doesn't do what I want for this project.
Example 1
This basically uses a series of JLabels on a JLayeredPane. Each label has it's own mouse listener and when you mouse over it, will turn red. But, if there is a label above it, it won't respond to mouse events...
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ClickMyImages {
public static void main(String[] args) {
new ClickMyImages();
}
public ClickMyImages() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JLayeredPane {
public TestPane() {
try {
BufferedImage img1 = ImageIO.read("/Image1");
BufferedImage img2 = ImageIO.read("/Image2");
BufferedImage img3 = ImageIO.read("/Image3");
BufferedImage img4 = ImageIO.read("/Image4");
BufferedImage img5 = ImageIO.read("/Image5");
JLabel label1 = new ClickableLabel(new ImageIcon(img1));
JLabel label2 = new ClickableLabel(new ImageIcon(img2));
JLabel label3 = new ClickableLabel(new ImageIcon(img3));
JLabel label4 = new ClickableLabel(new ImageIcon(img4));
JLabel label5 = new ClickableLabel(new ImageIcon(img5));
Dimension masterSize = getPreferredSize();
Dimension size = label1.getPreferredSize();
label1.setBounds((masterSize.width - size.width) / 2, (masterSize.height - size.height) / 2, size.width, size.height);
Point masterPoint = label1.getLocation();
size = label2.getPreferredSize();
label2.setBounds(
masterPoint.x - (size.width / 2),
masterPoint.y - (size.height / 2),
size.width, size.height);
size = label3.getPreferredSize();
label3.setBounds(
masterPoint.x + (size.width / 2),
masterPoint.y - (size.height / 2),
size.width, size.height);
size = label4.getPreferredSize();
label4.setBounds(
masterPoint.x - (size.width / 2),
masterPoint.y + (size.height / 2),
size.width, size.height);
size = label5.getPreferredSize();
label5.setBounds(
masterPoint.x + (size.width / 2),
masterPoint.y + (size.height / 2),
size.width, size.height);
add(label1);
add(label2);
add(label3);
add(label4);
add(label5);
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
}
// This is for demonstration purposes only!
public class ClickableLabel extends JLabel {
private boolean isIn = false;
public ClickableLabel(Icon image) {
super(image);
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
isIn = true;
repaint();
}
#Override
public void mouseExited(MouseEvent e) {
isIn = false;
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (isIn) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
g2d.setColor(Color.RED);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.dispose();
}
}
}
}
Example 2
This examples uses the paintComponent method to renderer the images. It checks the pixel alpha of the image at the mouse point to determine if the mouse event should fall through or not.
I've been a little strict using an alpha value of 255, but you could soften it a little based on your needs (something like 225 instead for example)...
I've hard coded the layers so that the tree is always above the squirrel, but it wouldn't be hard to add all the images to List in the order you want them to appear and simple run down the list till you get a hit.
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ClickMyDrawnImages {
public static void main(String[] args) {
new ClickMyDrawnImages();
}
public ClickMyDrawnImages() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage tree;
private BufferedImage squirrel;
private BufferedImage mouseOver;
public TestPane() {
try {
tree = ImageIO.read(new File("Tree.png"));
squirrel = ImageIO.read(new File("Squirrel.png"));
} catch (IOException exp) {
exp.printStackTrace();
}
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
if (withinTree(e.getPoint())) {
mouseOver = tree;
} else if (withinSquirrel(e.getPoint())) {
mouseOver = squirrel;
} else {
mouseOver = null;
}
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected boolean withinTree(Point p) {
return withinBounds(p, getTreeBounds(), tree);
}
protected boolean withinSquirrel(Point p) {
return !withinBounds(p, getTreeBounds(), tree) && withinBounds(p, getSquirrelBounds(), squirrel);
}
protected Rectangle getTreeBounds() {
int width = getWidth();
int height = getHeight();
int x = (width - tree.getWidth()) / 2;
int y = (height - tree.getHeight()) / 2;
return new Rectangle(x, y, tree.getWidth(), tree.getHeight());
}
protected Rectangle getSquirrelBounds() {
Rectangle bounds = getTreeBounds();
return new Rectangle(
bounds.x - (squirrel.getWidth() / 4),
(getHeight() - squirrel.getHeight()) / 2,
squirrel.getWidth(), squirrel.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth();
int height = getHeight();
int x = (width - tree.getWidth()) / 2;
int y = (height - tree.getHeight()) / 2;
g.drawImage(highlight(squirrel), x - (squirrel.getWidth() / 4), (height - squirrel.getHeight()) / 2, this);
g2d.drawImage(highlight(tree), x, y, this);
g2d.dispose();
}
protected BufferedImage highlight(BufferedImage img) {
BufferedImage highlight = img;
if (img.equals(mouseOver)) {
highlight = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = highlight.createGraphics();
g2d.setColor(Color.RED);
g2d.drawImage(img, 0, 0, this);
g2d.setComposite(AlphaComposite.SrcAtop.derive(0.5f));
g2d.fillRect(0, 0, highlight.getWidth(), highlight.getHeight());
g2d.dispose();
}
return highlight;
}
protected boolean withinBounds(Point p, Rectangle bounds, BufferedImage image) {
boolean withinBounds = false;
if (bounds.contains(p)) {
int x = p.x - bounds.x;
int y = p.y - bounds.y;
int pixel = image.getRGB(x, y);
int a = (pixel >> 24) & 0xFF;
// could use a little weighting, so translucent pixels can be effected
if (a == 255) {
withinBounds = true;
}
}
return withinBounds;
}
}
}