Created
September 1, 2022 23:42
-
-
Save RustyKnight/b49d80bae8343f5f8a0be60cccc59144 to your computer and use it in GitHub Desktop.
Example layout of using CardLayout in a slightly unconventional way, supporting a custom background image
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.awt.BorderLayout; | |
import java.awt.CardLayout; | |
import java.awt.Color; | |
import java.awt.Dimension; | |
import java.awt.EventQueue; | |
import java.awt.Graphics; | |
import java.awt.Graphics2D; | |
import java.awt.GridBagConstraints; | |
import java.awt.GridBagLayout; | |
import java.awt.Insets; | |
import java.awt.Point; | |
import java.awt.Shape; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.awt.event.ComponentAdapter; | |
import java.awt.event.ComponentEvent; | |
import java.awt.event.KeyEvent; | |
import java.awt.geom.AffineTransform; | |
import java.awt.geom.Rectangle2D; | |
import java.awt.image.BufferedImage; | |
import java.beans.PropertyChangeEvent; | |
import java.beans.PropertyChangeListener; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.Iterator; | |
import java.util.concurrent.ExecutionException; | |
import javax.imageio.ImageIO; | |
import javax.imageio.ImageReader; | |
import javax.imageio.event.IIOReadProgressListener; | |
import javax.imageio.stream.ImageInputStream; | |
import javax.swing.AbstractAction; | |
import javax.swing.ActionMap; | |
import javax.swing.InputMap; | |
import javax.swing.JButton; | |
import javax.swing.JFileChooser; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JOptionPane; | |
import javax.swing.JPanel; | |
import javax.swing.JProgressBar; | |
import javax.swing.KeyStroke; | |
import javax.swing.SwingUtilities; | |
import javax.swing.SwingWorker; | |
import javax.swing.Timer; | |
import javax.swing.border.EmptyBorder; | |
import javax.swing.filechooser.FileNameExtensionFilter; | |
public class Main { | |
public static void main(String[] args) { | |
new Main(); | |
} | |
public Main() { | |
EventQueue.invokeLater(new Runnable() { | |
@Override | |
public void run() { | |
try { | |
JFrame frame = new JFrame(); | |
frame.add(new MainPane()); | |
frame.pack(); | |
frame.setLocationRelativeTo(null); | |
frame.setVisible(true); | |
} catch (IOException ex) { | |
ex.printStackTrace(); | |
} | |
} | |
}); | |
} | |
public class MainPane extends JPanel { | |
public interface Observer { | |
public void start(); | |
public void quit(); | |
public void didLoadBackground(BufferedImage img); | |
} | |
enum View { | |
MAIN, START | |
} | |
private CardLayout cardLayout; | |
private BackgroundPane backgroundPane; | |
public MainPane() throws IOException { | |
setLayout(new BorderLayout()); | |
BufferedImage background = scaled(ImageIO.read(getClass().getResource("/images/Mando01.jpeg"))); | |
backgroundPane = new BackgroundPane(background); | |
cardLayout = new CardLayout(); | |
backgroundPane.setLayout(cardLayout); | |
add(backgroundPane); | |
backgroundPane.add(new OptionsPane(new Observer() { | |
@Override | |
public void start() { | |
show(View.START); | |
} | |
@Override | |
public void quit() { | |
SwingUtilities.windowForComponent(MainPane.this).dispose(); | |
} | |
@Override | |
public void didLoadBackground(BufferedImage img) { | |
if (img == null) { | |
return; | |
} | |
backgroundPane.setBackgroundImage(img); | |
} | |
}), View.MAIN.name()); | |
backgroundPane.add(new GamePane(), View.START.name()); | |
} | |
protected BufferedImage scaled(BufferedImage original) { | |
BufferedImage scaled = new BufferedImage(original.getWidth() / 2, original.getHeight() / 2, BufferedImage.TYPE_INT_ARGB); | |
Graphics2D g2d = scaled.createGraphics(); | |
g2d.transform(AffineTransform.getScaleInstance(0.5, 0.5)); | |
g2d.drawImage(original, 0, 0, this); | |
g2d.dispose(); | |
return scaled; | |
} | |
public void show(View view) { | |
cardLayout.show(backgroundPane, view.name()); | |
} | |
} | |
public class BackgroundPane extends JPanel { | |
private BufferedImage background; | |
public BackgroundPane(BufferedImage background) { | |
this.background = background; | |
} | |
public void setBackgroundImage(BufferedImage background) { | |
this.background = background; | |
} | |
public BufferedImage getBackgroundImage() { | |
return background; | |
} | |
@Override | |
public Dimension getPreferredSize() { | |
BufferedImage background = getBackgroundImage(); | |
if (background != null) { | |
return new Dimension(background.getWidth(), background.getHeight()); | |
} | |
return super.getPreferredSize(); | |
} | |
@Override | |
protected void paintComponent(Graphics g) { | |
super.paintComponent(g); | |
BufferedImage background = getBackgroundImage(); | |
if (background == null) { | |
return; | |
} | |
int x = (getWidth() - background.getWidth()) / 2; | |
int y = (getHeight() - background.getHeight()) / 2; | |
g.drawImage(background, x, y, this); | |
} | |
} | |
public class OptionsPane extends JPanel { | |
private interface ProgressMonitor { | |
void progressUpdated(float percentageDone); | |
} | |
private MainPane.Observer observer; | |
private JFileChooser filePicker; | |
public OptionsPane(MainPane.Observer observer) { | |
setOpaque(false); | |
setLayout(new GridBagLayout()); | |
JButton start = new JButton("Start"); | |
start.setOpaque(false); | |
start.setBorderPainted(false); | |
start.setForeground(Color.WHITE); | |
JButton backgroundSelector = new JButton("Background"); | |
backgroundSelector.setOpaque(false); | |
backgroundSelector.setBorderPainted(false); | |
backgroundSelector.setForeground(Color.WHITE); | |
JButton quit = new JButton("Quit"); | |
quit.setOpaque(false); | |
quit.setBorderPainted(false); | |
quit.setForeground(Color.WHITE); | |
GridBagConstraints gbc = new GridBagConstraints(); | |
gbc.gridx = 0; | |
gbc.gridy = 0; | |
gbc.weighty = 1; | |
gbc.anchor = GridBagConstraints.SOUTH; | |
gbc.insets = new Insets(4, 4, 4, 4); | |
add(start, gbc); // adds JButton | |
gbc.weighty = 0; | |
gbc.gridy++; | |
add(backgroundSelector, gbc); | |
gbc.gridy++; | |
add(quit, gbc); | |
start.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
observer.start(); | |
} | |
}); | |
quit.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
observer.quit(); | |
} | |
}); | |
backgroundSelector.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
JFileChooser picker = getFilePicker(); | |
if (picker.showOpenDialog(OptionsPane.this) != JFileChooser.APPROVE_OPTION) { | |
return; | |
} | |
File sourceFile = picker.getSelectedFile(); | |
// This "could" use the glassPane, but that's | |
// an additional complexity | |
JPanel panel = new JPanel(new GridBagLayout()); | |
JLabel label = new JLabel("Loading..."); | |
label.setHorizontalAlignment(JLabel.CENTER); | |
JProgressBar progressBar = new JProgressBar(0, 100); | |
GridBagConstraints gbc = new GridBagConstraints(); | |
gbc.gridx = GridBagConstraints.REMAINDER; | |
gbc.insets = new Insets(2, 2, 2, 2); | |
gbc.fill = gbc.HORIZONTAL; | |
panel.setBorder(new EmptyBorder(8, 8, 8, 8)); | |
panel.add(label, gbc); | |
panel.add(progressBar, gbc); | |
gbc = new GridBagConstraints(); | |
gbc.gridx = 0; | |
gbc.gridy = 0; | |
gbc.gridwidth = gbc.REMAINDER; | |
gbc.gridheight = gbc.REMAINDER; | |
add(panel, gbc); | |
revalidate(); | |
repaint(); | |
SwingWorker<BufferedImage, Void> worker = new SwingWorker<BufferedImage, Void>() { | |
@Override | |
protected BufferedImage doInBackground() throws Exception { | |
return readImage(sourceFile, new ProgressMonitor() { | |
@Override | |
public void progressUpdated(float percentageDone) { | |
setProgress((int) percentageDone); | |
} | |
}); | |
} | |
}; | |
worker.addPropertyChangeListener(new PropertyChangeListener() { | |
@Override | |
public void propertyChange(PropertyChangeEvent evt) { | |
String name = evt.getPropertyName(); | |
if (worker.getState() == SwingWorker.StateValue.DONE) { | |
remove(panel); | |
revalidate(); | |
repaint(); | |
try { | |
observer.didLoadBackground(worker.get()); | |
} catch (InterruptedException | ExecutionException ex) { | |
ex.printStackTrace(); | |
JOptionPane.showMessageDialog(OptionsPane.this, "Failed to load image", "Error", JOptionPane.ERROR_MESSAGE); | |
} | |
} else if ("progress".equalsIgnoreCase(name)) { | |
progressBar.setValue((int) evt.getNewValue()); | |
} | |
} | |
}); | |
worker.execute(); | |
} | |
}); | |
} | |
protected JFileChooser getFilePicker() { | |
if (filePicker != null) { | |
filePicker.setSelectedFile(null); | |
return filePicker; | |
} | |
filePicker = new JFileChooser(); | |
FileNameExtensionFilter filter = new FileNameExtensionFilter("Image files", ImageIO.getReaderFileSuffixes()); | |
filePicker.addChoosableFileFilter(filter); | |
return filePicker; | |
} | |
private BufferedImage readImage(File file, ProgressMonitor monitor) throws IOException { | |
try (ImageInputStream iis = ImageIO.createImageInputStream(file)) { | |
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis); | |
if (readers.hasNext()) { | |
ImageReader reader = readers.next(); | |
reader.addIIOReadProgressListener(new IIOReadProgressListener() { | |
@Override | |
public void sequenceStarted(ImageReader source, int minIndex) { | |
} | |
@Override | |
public void sequenceComplete(ImageReader source) { | |
} | |
@Override | |
public void imageStarted(ImageReader source, int imageIndex) { | |
} | |
@Override | |
public void imageProgress(ImageReader source, float percentageDone) { | |
monitor.progressUpdated(percentageDone); | |
} | |
@Override | |
public void imageComplete(ImageReader source) { | |
} | |
@Override | |
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) { | |
} | |
@Override | |
public void thumbnailProgress(ImageReader source, float percentageDone) { | |
} | |
@Override | |
public void thumbnailComplete(ImageReader source) { | |
} | |
@Override | |
public void readAborted(ImageReader source) { | |
} | |
}); | |
try { | |
reader.setInput(iis); | |
return reader.read(0); | |
} finally { | |
reader.removeAllIIOReadProgressListeners(); | |
} | |
} | |
} | |
return null; | |
} | |
} | |
public class GamePane extends JPanel implements DeltaAction.Observer { | |
public class Box { | |
private Shape box = new Rectangle2D.Double(0, 0, 50, 50); | |
private Point p = new Point(); | |
public void paint(Graphics2D g2d) { | |
g2d = (Graphics2D) g2d.create(); | |
g2d.setColor(Color.BLUE); | |
g2d.translate(p.getX(), p.getY()); | |
g2d.fill(box); | |
g2d.dispose(); | |
} | |
public Point getLocation() { | |
return new Point(p); | |
} | |
public void setLocation(Point p) { | |
this.p = p; | |
} | |
public Rectangle2D getBounds() { | |
return new Rectangle2D.Double(p.getX(), p.getY(), box.getBounds2D().getHeight(), box.getBounds2D().getHeight()); | |
} | |
} | |
protected enum KeyInput { | |
PRESSED_UP, RELEASED_UP, | |
PRESSED_DOWN, RELEASED_DOWN, | |
PRESSED_LEFT, RELEASED_LEFT, | |
PRESSED_RIGHT, RELEASED_RIGHT; | |
} | |
private Box box = new Box(); | |
private int xDelta = 0; | |
private int yDelta = 0; | |
private Timer timer; | |
public GamePane() { | |
setOpaque(false); | |
setLayout(new GridBagLayout()); | |
add(new JLabel("Let's get this party started")); | |
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW); | |
ActionMap am = getActionMap(); | |
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), KeyInput.PRESSED_UP); | |
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), KeyInput.RELEASED_UP); | |
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), KeyInput.PRESSED_DOWN); | |
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), KeyInput.RELEASED_DOWN); | |
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), KeyInput.PRESSED_LEFT); | |
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), KeyInput.RELEASED_LEFT); | |
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), KeyInput.PRESSED_RIGHT); | |
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), KeyInput.RELEASED_RIGHT); | |
am.put(KeyInput.PRESSED_UP, new DeltaAction(DeltaAction.Direction.UP, true, this)); | |
am.put(KeyInput.RELEASED_UP, new DeltaAction(DeltaAction.Direction.UP, false, this)); | |
am.put(KeyInput.PRESSED_DOWN, new DeltaAction(DeltaAction.Direction.DOWN, true, this)); | |
am.put(KeyInput.RELEASED_DOWN, new DeltaAction(DeltaAction.Direction.DOWN, false, this)); | |
am.put(KeyInput.PRESSED_LEFT, new DeltaAction(DeltaAction.Direction.LEFT, true, this)); | |
am.put(KeyInput.RELEASED_LEFT, new DeltaAction(DeltaAction.Direction.LEFT, false, this)); | |
am.put(KeyInput.PRESSED_RIGHT, new DeltaAction(DeltaAction.Direction.RIGHT, true, this)); | |
am.put(KeyInput.RELEASED_RIGHT, new DeltaAction(DeltaAction.Direction.RIGHT, false, this)); | |
timer = new Timer(5, new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
Point location = box.getLocation(); | |
location.x += xDelta; | |
location.y += yDelta; | |
box.setLocation(location); | |
Rectangle2D bounds = box.getBounds(); | |
if (bounds.getX() < 0) { | |
location.x = 0; | |
} else if (bounds.getX() + bounds.getWidth() > getWidth()) { | |
location.x = (int) (getWidth() - bounds.getWidth()); | |
} | |
if (bounds.getY() < 0) { | |
location.y = 0; | |
} else if (bounds.getY() + bounds.getHeight() > getHeight()) { | |
location.y = (int) (getHeight() - bounds.getHeight()); | |
} | |
box.setLocation(location); | |
repaint(); | |
} | |
}); | |
addComponentListener(new ComponentAdapter() { | |
@Override | |
public void componentHidden(ComponentEvent e) { | |
timer.stop(); | |
} | |
@Override | |
public void componentShown(ComponentEvent e) { | |
System.out.println("Hello"); | |
timer.start(); | |
} | |
}); | |
} | |
@Override | |
protected void paintComponent(Graphics g) { | |
super.paintComponent(g); | |
Graphics2D g2d = (Graphics2D) g.create(); | |
box.paint(g2d); | |
g2d.dispose(); | |
} | |
@Override | |
public void directionApplied(DeltaAction.Direction direction, boolean activate) { | |
switch (direction) { | |
case UP: | |
yDelta = activate ? -4 : 0; | |
break; | |
case DOWN: | |
yDelta = activate ? 4 : 0; | |
break; | |
case LEFT: | |
xDelta = activate ? -4 : 0; | |
break; | |
case RIGHT: | |
xDelta = activate ? 4 : 0; | |
break; | |
} | |
} | |
} | |
public class DeltaAction extends AbstractAction { | |
public interface Observer { | |
public void directionApplied(Direction direction, boolean activate); | |
} | |
public enum Direction { | |
UP, DOWN, LEFT, RIGHT; | |
} | |
private Direction direction; | |
private boolean activate; | |
private Observer observer; | |
public DeltaAction(Direction direction, boolean activate, Observer observer) { | |
this.direction = direction; | |
this.activate = activate; | |
this.observer = observer; | |
} | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
observer.directionApplied(direction, activate); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment