I'm experimenting with javax.swing
. Here's a simple task I was going to do: to make a circle change color once a button (a sibling component) is pressed. Here's my code. I'm enthusiastic about design patterns so you see all these singletons, builders, and whatnot (I hope you don't mind)
public class App {
public static void main(String[] args) {
MyUI ui = new MyUI();
ui.display();
}
}
public class MyUI {
public void display() {
UIUtil.getUIBuilder()
.withButton(SOUTH, () -> {
JButton button = new JButton("click me!");
button.setFont(new Font(SANS_SERIF, BOLD, 28));
button.setSize(150, 50);
button.addMouseListener(MouseListeningPanel.getInstance());
return button;
})
.withPanel(CENTER, MouseListeningPanel::getInstance)
.withFrameSize(300, 300)
.withDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
.visualize();
}
}
public class MouseListeningPanel extends JPanel implements MouseListener {
public static final MouseListeningPanel INSTANCE = new MouseListeningPanel();
private Color startColor = UIUtil.getRandomColor();
private Color endColor = UIUtil.getRandomColor();
private MouseListeningPanel() {}
public static MouseListeningPanel getInstance() {
return INSTANCE;
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D twoDGraphics = (Graphics2D) g;
GradientPaint gradient = new GradientPaint(0, 0, startColor, getMiddle(), getMiddle(), endColor);
twoDGraphics.setPaint(gradient);
twoDGraphics.fillOval(calculateX(), calculateY(), calculateWidth(), calculateWidth());
}
private float getMiddle() {
return getWidth() / 2f;
}
private int calculateX() {
return getWidth() / 2 - calculateWidth() / 2;
}
private int calculateY() {
return getHeight() / 2 - calculateWidth() / 2;
}
private int calculateWidth() {
return Math.min(getWidth(), getHeight()) / 2;
}
@Override
public void mouseClicked(MouseEvent e) {
startColor = UIUtil.getRandomColor();
endColor = UIUtil.getRandomColor();
repaint();
}
// dummy implementations for other MouseListener methods
// I can't extend JPanel and MouseAdapter at the same time so I have to provide them
}
public class UIUtil {
public static UIBuilder getUIBuilder() {
return new UIBuilder();
}
public static UIBuilder getUIBuilder(Supplier<Container> contentPaneSupplier) {
return new UIBuilder(contentPaneSupplier);
}
public static Color getRandomColor() {
int red = Util.randomInt(255);
int green = Util.randomInt(255);
int blue = Util.randomInt(255);
return new Color(red, green, blue);
}
public static SimpleRectangle getRandomRectangle(int parentWidth, int parentHeight) {
return getRandomRectangle(parentWidth, parentHeight, 0.5F);
}
public static SimpleRectangle getRandomRectangle(int parentWidth, int parentHeight, float recDimensionToParentDimensionRatio) {
int x = Util.randomInt(parentWidth);
int y = Util.randomInt(parentHeight);
int width = Util.randomInt((int) (parentWidth * recDimensionToParentDimensionRatio));
int height = Util.randomInt((int) (parentHeight * recDimensionToParentDimensionRatio));
return new SimpleRectangle(x, y, width, height);
}
public record SimpleRectangle(int x, int y, int width, int height) {}
@NoArgsConstructor
public static class UIBuilder {
private final JFrame frame;
private final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
{
frame = new JFrame();
this.withFrameSize(300, 300)
.withDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public UIBuilder(@NotNull Supplier<Container> contentPaneSupplier) {
Objects.requireNonNull(contentPaneSupplier);
frame.setContentPane(contentPaneSupplier.get());
}
public UIBuilder withButton(@NotNull String position, @NotNull Supplier<JButton> buttonSupplier) {
Stream.of(position, buttonSupplier).forEach(Objects::requireNonNull);
JButton button = buttonSupplier.get();
frame.getContentPane().add(position, button);
return this;
}
public UIBuilder withPanel(@NotNull String position, @NotNull Supplier<JPanel> panelSupplier) {
Stream.of(position, panelSupplier).forEach(Objects::requireNonNull);
JPanel panel = panelSupplier.get();
JScrollPane scrollPane = new JScrollPane(panel);
frame.getContentPane().add(position, scrollPane);
return this;
}
public UIBuilder withFrameSize(int width, int height) {
checkAgainstScreenSize(width, height);
int x = (screenSize.width - width) / 2;
int y = (screenSize.height - height) / 2;
frame.setBounds(x, y, width, height);
return this;
}
private void checkAgainstScreenSize(int width, int height) {
if (screenSize.width < width) {
throw new IllegalArgumentException("Frame width cannot be greater than screen width");
} else if (screenSize.height < height) {
throw new IllegalArgumentException("Frame height cannot be greater than screen height");
}
}
public UIBuilder withDefaultCloseOperation(int windowConstant) {
frame.setDefaultCloseOperation(windowConstant);
return this;
}
public void visualize() {
frame.setVisible(true);
}
}
}
It kind of works, but here's the snag: once I press the button, it gets cloned, and the copy is put in the NORTH
section of the pane. I can't imagine why Java did that
Since regular resizing doesn't produce that result and mouseClicked()
only sets colors and calls repaint()
, repaint()
is the only suspect. I checked the doc for the method
Repaints this component
This! The button is not "this", is it? The button is a sibling, not a component of the panel. "This" must refer to the panel itself, and its paintComponent()
doesn't add any buttons
Why is it happening and how do I fix it?