diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/SwingIO.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/SwingIO.java index e40210f..8123357 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/SwingIO.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/SwingIO.java @@ -14,7 +14,6 @@ import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; @@ -22,6 +21,7 @@ import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.KeyStroke; +import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.SimpleAttributeSet; @@ -29,10 +29,13 @@ import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; +import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; @@ -138,6 +141,7 @@ public class SwingIO implements GameIO { public void componentResized(ComponentEvent e) { updateSideWidth(); applyFonts(); + rescaleMenuOverlayIfVisible(); } }); @@ -348,41 +352,12 @@ public class SwingIO implements GameIO { @Override public int choose(String title, List options) { - // Same EDT↔worker handoff as readLine: the worker blocks on take(), - // the EDT (button click / window close) offers the chosen index. + // Full-window overlay (glass pane), not a floating dialog: it covers the game + // chrome so no panels/outlines show behind the menu, and it fills + scales with + // the window. EDT↔worker handoff as in readLine: the worker blocks on take(), + // a button click / Escape offers the chosen index. LinkedBlockingQueue picked = new LinkedBlockingQueue<>(); - SwingUtilities.invokeLater(() -> { - JDialog dialog = new JDialog(frame, title, true); - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - panel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); - panel.add(new JLabel(title)); - panel.add(Box.createVerticalStrut(8)); - for (int i = 0; i < options.size(); i++) { - int idx = i; - JButton b = new JButton(options.get(i)); - b.setAlignmentX(0f); - b.addActionListener(e -> { - dialog.dispose(); - picked.offer(idx); - }); - panel.add(b); - panel.add(Box.createVerticalStrut(4)); - } - // Closing the dialog counts as the last (safe/cancel) option. - dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); - dialog.addWindowListener(new java.awt.event.WindowAdapter() { - @Override - public void windowClosing(java.awt.event.WindowEvent e) { - dialog.dispose(); - picked.offer(options.size() - 1); - } - }); - dialog.setContentPane(panel); - dialog.pack(); - dialog.setLocationRelativeTo(frame); - dialog.setVisible(true); - }); + SwingUtilities.invokeLater(() -> showMenuOverlay(title, options, picked)); try { return picked.take(); } catch (InterruptedException e) { @@ -391,6 +366,123 @@ public class SwingIO implements GameIO { } } + /** Current menu being shown, so a window resize can rebuild it at the new scale. */ + private String menuTitle; + private List menuOptions; + private LinkedBlockingQueue menuPicked; + + /** Rebuilds the visible menu overlay at the current window scale (called on resize). */ + private void rescaleMenuOverlayIfVisible() { + if (menuOptions != null && frame.getGlassPane().isVisible()) { + showMenuOverlay(menuTitle, menuOptions, menuPicked); + } + } + + /** Installs the menu as the frame's glass pane (covers everything) and shows it. */ + private void showMenuOverlay(String title, List options, LinkedBlockingQueue picked) { + menuTitle = title; + menuOptions = options; + menuPicked = picked; + float scale = frameScale() * zoom; // same scaling the rest of the UI uses + + JPanel glass = new JPanel(new GridBagLayout()); + glass.setOpaque(true); + glass.setBackground(MENU_BG); + glass.addMouseListener(new java.awt.event.MouseAdapter() { }); // swallow backdrop clicks + + JPanel card = new JPanel(); + card.setOpaque(false); + card.setLayout(new BoxLayout(card, BoxLayout.Y_AXIS)); + + JLabel heading = new JLabel(title.toUpperCase()); + heading.setForeground(MENU_TITLE); + heading.setFont(baseFont.deriveFont(Font.BOLD, BASE_SIZE * scale * 1.6f)); + heading.setAlignmentX(Component.CENTER_ALIGNMENT); + heading.setBorder(BorderFactory.createEmptyBorder(0, 0, (int) (22 * scale), 0)); + card.add(heading); + + Runnable cancel = () -> { + hideMenuOverlay(); + picked.offer(options.size() - 1); + }; + for (int i = 0; i < options.size(); i++) { + int idx = i; + JButton b = menuButton(options.get(i), scale); + b.addActionListener(e -> { + hideMenuOverlay(); + picked.offer(idx); + }); + card.add(b); + if (i < options.size() - 1) { + card.add(Box.createVerticalStrut((int) (10 * scale))); + } + } + + // Escape selects the last (safe/cancel) option — mirrors the console EOF convention. + glass.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "menuCancel"); + glass.getActionMap().put("menuCancel", action(e -> cancel.run())); + + glass.add(card); // single child → centered by GridBagLayout + frame.setGlassPane(glass); + glass.setVisible(true); + glass.requestFocusInWindow(); + } + + private void hideMenuOverlay() { + menuOptions = null; // stop resize rebuilds once the menu is dismissed + frame.getGlassPane().setVisible(false); + } + + /** Palette for the themed menu overlay, matching the game window's dark look. */ + private static final Color MENU_BG = new Color(0x0b, 0x0e, 0x13); + private static final Color MENU_TITLE = new Color(0xc9, 0x8a, 0xe0); + private static final Color BTN_BG = new Color(0x16, 0x1b, 0x24); + private static final Color BTN_BG_HOVER = new Color(0x22, 0x2a, 0x37); + private static final Color BTN_FG = new Color(0xcf, 0xd6, 0xe0); + private static final Color BTN_LINE = new Color(0x2a, 0x2f, 0x3a); + private static final Color BTN_LINE_HOVER = new Color(0x6f, 0xcf, 0x73); + + /** A flat, dark, fixed-width menu button matching the game palette, with hover feedback. */ + private JButton menuButton(String text, float scale) { + int padV = (int) (11 * scale); + int padH = (int) (18 * scale); + JButton b = new JButton(text); + b.setFont(baseFont.deriveFont(BASE_SIZE * scale)); + b.setForeground(BTN_FG); + b.setBackground(BTN_BG); + b.setFocusPainted(false); + b.setOpaque(true); + b.setHorizontalAlignment(SwingConstants.LEFT); + b.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + b.setAlignmentX(Component.CENTER_ALIGNMENT); + b.setBorder(menuButtonBorder(BTN_LINE, padV, padH)); + Dimension size = new Dimension((int) (340 * scale), b.getPreferredSize().height); + b.setPreferredSize(size); + b.setMinimumSize(size); + b.setMaximumSize(size); + b.addMouseListener(new java.awt.event.MouseAdapter() { + @Override + public void mouseEntered(java.awt.event.MouseEvent e) { + b.setBackground(BTN_BG_HOVER); + b.setBorder(menuButtonBorder(BTN_LINE_HOVER, padV, padH)); + } + + @Override + public void mouseExited(java.awt.event.MouseEvent e) { + b.setBackground(BTN_BG); + b.setBorder(menuButtonBorder(BTN_LINE, padV, padH)); + } + }); + return b; + } + + private static javax.swing.border.Border menuButtonBorder(Color line, int padV, int padH) { + return BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(line, 1), + BorderFactory.createEmptyBorder(padV, padH, padV, padH)); + } + private void addList(List spans, List xs, Style st) { for (int i = 0; i < xs.size(); i++) { if (i > 0) {