package org.erikaredmark.monkeyshines.menu;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import org.erikaredmark.monkeyshines.KeyBindings;
import org.erikaredmark.monkeyshines.global.KeySettings;
import org.erikaredmark.monkeyshines.global.PreferencePersistException;
import org.erikaredmark.monkeyshines.global.SoundSettings;
import org.erikaredmark.monkeyshines.global.SoundType;
/**
*
* The main menu. What the user sees when they initialise the game.
* <p/>
* Beyond the actual gameplay, this is their main jump-point to access all game functions.
*
* @author Erika Redmark
*
*/
public class MainMenuWindow extends JPanel {
private static final String CLASS_NAME = "org.erikaredmark.monkeyshines.menu.MainMenuWindow";
private static final Logger LOGGER = Logger.getLogger(CLASS_NAME);
private static final long serialVersionUID = 1L;
// A single instance of the resource is tied to the window. When it is GC'd the image resources
// should be freed as well
private final MainMenuResource rsrc = new MainMenuResource();
// All clickable buttons are the same size. This size is used in constant expressions to calculate
// the absolete locations the buttons will be drawn on the screen (no layout managers)
private static final int BUTTON_SIZE_X = 60;
private static final int BUTTON_SIZE_Y = 60;
// All drawn text indicating the name of the button is drawn this many units to the right
// side of the actual button
private static final int TEXT_MARGIN = 10;
private static final int FONT_HEIGHT = 12;
// --------------- Play Game
private static final int PLAY_GAME_X = 35;
private static final int PLAY_GAME_Y = 150;
private static final int PLAY_GAME_TEXT_X = calcTextX(PLAY_GAME_X);
private static final int PLAY_GAME_TEXT_Y = calcTextY(PLAY_GAME_Y);
// --------------- Controls
private static final int CONTROLS_X = 35;
private static final int CONTROLS_Y = 215;
private static final int CONTROLS_TEXT_X = calcTextX(CONTROLS_X);
private static final int CONTROLS_TEXT_Y = calcTextY(CONTROLS_Y);
// --------------- Music
private static final int MUSIC_X = 35;
private static final int MUSIC_Y = 280;
private static final int MUSIC_TEXT_X = calcTextX(MUSIC_X);
private static final int MUSIC_TEXT_Y = calcTextY(MUSIC_Y);
// --------------- Sound
private static final int SOUND_X = 35;
private static final int SOUND_Y = 345;
private static final int SOUND_TEXT_X = calcTextX(SOUND_X);
private static final int SOUND_TEXT_Y = calcTextY(SOUND_Y);
// --------------- High Scores
private static final int HIGH_X = 87;
private static final int HIGH_Y = 410;
private static final int HIGH_TEXT_X = calcTextX(HIGH_X);
private static final int HIGH_TEXT_Y = calcTextY(HIGH_Y);
// --------------- Help
private static final int HELP_X = 453;
private static final int HELP_Y = 345;
private static final int HELP_TEXT_X = calcTextX(HELP_X);
private static final int HELP_TEXT_Y = calcTextY(HELP_Y);
// --------------- Exit
private static final int EXIT_X = 400;
private static final int EXIT_Y = 410;
private static final int EXIT_TEXT_X = calcTextX(EXIT_X);
private static final int EXIT_TEXT_Y = calcTextY(EXIT_Y);
// --------------- Border
private static final int BORDER_X = 262;
private static final int BORDER_Y = 157;
private static final int BORDER_X2 = 590;
private static final int BORDER_Y2 = 337;
private static final int BORDER_SIZE_X = BORDER_X2 - BORDER_X;
private static final int BORDER_SIZE_Y = BORDER_Y2 - BORDER_Y;
// --------------- Informationals
private static final int INFORMATIONAL_X = 270;
private static final int INFORMATIONAL_Y = 165;
private static final int INFORMATIONAL_X2 = 581;
private static final int INFORMATIONAL_Y2 = 328;
private static final int INFORMATIONAL_SIZE_X = INFORMATIONAL_X2 - INFORMATIONAL_X;
private static final int INFORMATIONAL_SIZE_Y = INFORMATIONAL_Y2 - INFORMATIONAL_Y;
/*
* Static const-expressions to calculate X and Y locations of a given set of text based on button location
*/
private static final int calcTextX(int buttonX) { return buttonX + BUTTON_SIZE_X + TEXT_MARGIN; }
private static final int calcTextY(int buttonY) { return buttonY + ( (BUTTON_SIZE_Y / 2) + FONT_HEIGHT); }
/**
*
* Constructs the main menu window, with callables back to the window manager whenever an action is performed that
* requires transitioning to a different game state.
*
* @param playGameCallback
* runnable for when the game should start
*
* @param highScoresCallback
* runnable for when the high scores page should be displayed
*
*/
public MainMenuWindow(final Runnable playGameCallback, final Runnable highScoresCallback) {
// The buttons are the only thing that uses a Swing component; everything else is explicitly painted
// on. Hence we are mainly focusing on button positioning and functionality here.
setLayout(null);
JButton playGame = menuButton(
new Runnable() {
@Override public void run() { playGameCallback.run(); }
},
rsrc.BUTTON_PLAY_GAME,
PLAY_GAME_X,
PLAY_GAME_Y);
JButton controls = menuButton(
new Runnable() {
@Override public void run() {
KeyBindings newBindings = KeyboardControlDialog.launch(KeySettings.getBindings() );
// return values used to make it easier to remove the singleton if ever required.
KeySettings.setBindings(newBindings);
// TODO dialog
try {
KeySettings.persist();
} catch (PreferencePersistException e) {
LOGGER.log(Level.WARNING,
CLASS_NAME + ": cannot persist preferences: " + e.getMessage(),
e);
}
}
},
rsrc.BUTTON_CONTROLS,
CONTROLS_X,
CONTROLS_Y);
JButton music = menuButton(
new Runnable() {
@Override public void run() {
try {
SoundControlDialog.launch(SoundType.MUSIC);
try {
SoundSettings.persist();
} catch (PreferencePersistException e) {
LOGGER.log(Level.WARNING,
CLASS_NAME + ": cannot persist preferences: " + e.getMessage(),
e);
}
} catch (SoundControlDialogInitException e) {
displayNoSoundDialog(e);
}
}
},
rsrc.BUTTON_MUSIC,
MUSIC_X,
MUSIC_Y);
JButton sound = menuButton(
new Runnable() {
@Override public void run() {
try {
SoundControlDialog.launch(SoundType.SOUND);
try {
SoundSettings.persist();
} catch (PreferencePersistException e) {
LOGGER.log(Level.WARNING,
CLASS_NAME + ": cannot persist preferences: " + e.getMessage(),
e);
}
} catch (SoundControlDialogInitException e) {
displayNoSoundDialog(e);
}
}
},
rsrc.BUTTON_SOUND,
SOUND_X,
SOUND_Y);
JButton highscores = menuButton(
new Runnable() {
@Override public void run() { highScoresCallback.run(); }
},
rsrc.BUTTON_HIGH,
HIGH_X,
HIGH_Y);
JButton help = menuButton(
new Runnable() {
@Override public void run() { HelpDialog.launch(); }
},
rsrc.BUTTON_HELP,
HELP_X,
HELP_Y);
JButton exit = menuButton(
new Runnable() {
@Override public void run() {
System.exit(0);
}
},
rsrc.BUTTON_EXIT,
EXIT_X,
EXIT_Y);
add(playGame);
add(controls);
add(music);
add(sound);
add(highscores);
add(help);
add(exit);
setPreferredSize(new Dimension(640, 480) );
}
private void displayNoSoundDialog(SoundControlDialogInitException e) {
Object[] options = { "Okay" };
JOptionPane.showOptionDialog(
null,
"Sound Control levels are unavailable. The sound system was "
+ "not initialised correctly. See error logs for details: "
+ e.getMessage(),
"Sound System Failure",
JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE,
null,
options,
options[0]
);
}
/**
*
* Creates a menu clickable button with the given action and resource, at the specified location
*
* @param action
* @param images
* @param locationX
* @param locationY
* @return
*/
private JButton menuButton(final Runnable action, final BufferedImage[] images, int locationX, int locationY) {
JButton button =
new JButton(new AbstractAction("", new ImageIcon(images[0]) ) {
private static final long serialVersionUID = 1L;
@Override public void actionPerformed(ActionEvent arg0) {
action.run();
}
});
button.setLocation(locationX, locationY);
button.setSize(BUTTON_SIZE_X, BUTTON_SIZE_Y);
button.setPressedIcon(new ImageIcon(images[1]) );
button.setRolloverEnabled(true);
button.setRolloverIcon(new ImageIcon(images[2]) );
MenuUtils.renderImageOnly(button);
return button;
}
/**
*
* Paint the standard button components for clicking, but everything else can easily just be painted on since they
* aren't interactive.
*
*/
@Override public void paintComponent(Graphics g) {
g.drawImage(rsrc.BACKGROUND,
0, 0,
640, 480,
0, 0,
640, 480,
null);
g.drawImage(rsrc.INFORMATION_BORDER,
BORDER_X, BORDER_Y,
BORDER_X2, BORDER_Y2,
0, 0,
BORDER_SIZE_X, BORDER_SIZE_Y,
null);
g.drawImage(rsrc.INFORMATION_CONTENT,
INFORMATIONAL_X, INFORMATIONAL_Y,
INFORMATIONAL_X2, INFORMATIONAL_Y2,
0, 0,
INFORMATIONAL_SIZE_X, INFORMATIONAL_SIZE_Y,
null);
// Now for text.
g.setColor(Color.GREEN);
g.setFont(new Font("sansserif", Font.BOLD, 24) );
g.drawString(rsrc.TEXT_PLAY_GAME, PLAY_GAME_TEXT_X, PLAY_GAME_TEXT_Y);
g.drawString(rsrc.TEXT_SOUND, SOUND_TEXT_X, SOUND_TEXT_Y);
g.drawString(rsrc.TEXT_MUSIC, MUSIC_TEXT_X, MUSIC_TEXT_Y);
g.drawString(rsrc.TEXT_CONTROLS, CONTROLS_TEXT_X, CONTROLS_TEXT_Y);
g.drawString(rsrc.TEXT_HIGH, HIGH_TEXT_X, HIGH_TEXT_Y);
g.drawString(rsrc.TEXT_EXIT, EXIT_TEXT_X, EXIT_TEXT_Y);
g.drawString(rsrc.TEXT_HELP, HELP_TEXT_X, HELP_TEXT_Y);
// Do not call; destroys background. Components are still painted regardless.
// super.paintComponent(g);
}
}