I want to create a game with a fixed width/heigth ratio for the actual screen (so I can easily just scale the game elements and don't have to bother with complex layout). In order to do so I created a JFrame
with a BorderLayout
with the actual screen in the center and 4 spacing-panels on the sides. After every resize I recalculate the required spacing and set the preferred sizes accordingly so the screen gets the maximum ractangle of the given ratio that fits in the frame. The following code does that job, I replaced the actual screen with a simple blue panel and also added output to check on the calculation:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class JFrameResize {
private static JFrame frame;
private static JPanel inside = new JPanel();
private static JPanel spacingTop = new JPanel();
private static JPanel spacingBottom = new JPanel();
private static JPanel spacingLeft = new JPanel();
private static JPanel spacingRight = new JPanel();
private static JPanel compound = new JPanel();
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
public static void createAndShowGUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
inside.setBackground(Color.BLUE);
compound.setLayout(new BorderLayout());
compound.add(inside, BorderLayout.CENTER);
compound.add(spacingTop, BorderLayout.NORTH);
compound.add(spacingBottom, BorderLayout.SOUTH);
compound.add(spacingLeft, BorderLayout.WEST);
compound.add(spacingRight, BorderLayout.EAST);
frame.add(compound);
frame.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
calculateSize();
}
});
calculateSize();
frame.setVisible(true);
}
public static void calculateSize() {
double width = frame.getContentPane().getWidth();
double height = frame.getContentPane().getHeight();
double vSpacing = 0, hSpacing = 0;
System.out.println("frame: " + width + ":" + height);
// ratio is hardcoded to 3:4
if ((width * 3 - height * 4) >= 0) {
hSpacing = (width - (height * 4) / 3) / 2;
width = width - 2 * hSpacing;
} else {
vSpacing = (height - (width * 3) / 4) / 2;
height = height - 2 * vSpacing;
}
System.out.println("spacing: " + hSpacing + " + " + width + " + " + hSpacing + " : "
+ vSpacing + " + " + height + " + " + vSpacing);
spacingBottom.setPreferredSize(new Dimension((int) width, (int) vSpacing));
spacingTop.setPreferredSize(new Dimension((int) width, (int) vSpacing));
spacingLeft.setPreferredSize(new Dimension((int) hSpacing, (int) height));
spacingRight.setPreferredSize(new Dimension((int) hSpacing, (int) height));
inside.setPreferredSize(new Dimension((int) width, (int) height));
frame.revalidate();
frame.repaint();
System.out.println("inside: " + inside.getWidth() + ":" + inside.getHeight()
+ ", " + "border: " + spacingLeft.getWidth() + ":"
+ spacingTop.getHeight());
}
}
This works well for the most time, I get outputs like
frame: 510.0:445.0
spacing: 0.0 + 510.0 + 0.0 : 31.25 + 382.5 + 31.25
inside: 510:385, border: 0:30
and the frame shows the correct rectangle. However if I set the frame to fullscreen I get this output:
frame: 1366.0:705.0
spacing: 213.0 + 940.0 + 213.0 : 0.0 + 705.0 + 0.0
inside: 1366:643, border: 0:31
That is incorrect since my formular calculated the inside to be 940:705 but it became 1312:705. The rectangle also doesn't show correctly anymore. Same goes for using windows + arrow keys or dragging the frame to the screen sides . The calculation input is correct but the repaint/revalidate somehow behaves differently from a normal resize. No different combination of revalidate()
and repaint()
or spamming them across the code seems to change anything.
How does this happen and how do I fix this? Or is the approach in general flawed and should be replaced?