/*
* Copyright (c) 2003-onwards Shaven Puppy Ltd
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'Shaven Puppy' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.puppygames.applet;
import java.io.*;
import java.util.*;
import net.puppygames.applet.effects.Effect;
import net.puppygames.applet.effects.SFX;
import org.lwjgl.input.*;
import org.lwjgl.util.*;
import org.w3c.dom.Element;
import com.shavenpuppy.jglib.XMLResourceWriter;
import com.shavenpuppy.jglib.openal.ALBuffer;
import com.shavenpuppy.jglib.openal.ALStream;
import com.shavenpuppy.jglib.opengl.GLString;
import com.shavenpuppy.jglib.resources.Feature;
import com.shavenpuppy.jglib.sprites.*;
import com.shavenpuppy.jglib.util.XMLUtil;
import static org.lwjgl.opengl.GL11.*;
/**
* $Id: Screen.java,v 1.32 2010/09/02 23:42:44 foo Exp $
* A Screen. This consists of a number of named Areas.
* @author $Author: foo $
* @version $Revision: 1.32 $
*/
public abstract class Screen extends Feature implements SpriteAllocator {
public static final long serialVersionUID = 1L;
/*
* Static data
*/
/** All the open screens, in the order they should be rendered */
private static final List<Screen> screens = new ArrayList<Screen>(4);
/** Monkeying: slow tick speed */
private static final int SLOW_TICK_SPEED = 4;
/*
* Resource data
*/
/** The areas */
private List<Area> areas;
/** Is the mouse visible? */
private boolean mouseVisible;
/** Enable keyboard navigation (space, return, cursor keys & tab/shift tab) */
private boolean keyboardNavigation;
/** Background music */
private String music;
/** Alteratively, use a stream */
private String stream;
/** Hotkeys */
private List<HotKey> hotkeys;
/** Dialog */
private boolean dialog;
/** Offset location on screen - handy for transparent dialogs */
private int offsetX, offsetY;
/** Whether we're using unique sprites */
private boolean uniqueSprites;
/** Transition */
private String transition;
/** Centre the screen according to game scale */
private String centre;
/** Layer above which not to Y-sort */
private int sortLayer;
/*
* Transient data
*/
/** Sprite engine */
private transient StaticSpriteEngine spriteEngine;
/** Background effects */
private transient List<Effect> backgroundEffects;
/** Foreground effects */
private transient List<Effect> foregroundEffects;
/** A timer */
private transient int timer;
/** Phase */
private transient int phase;
/** Alpha */
private transient float alpha;
/** Mouse coords */
private transient int mouseX, mouseY, oldMouseX, oldMouseY;
/** A map of area names to areas */
private transient Map<String, Area> nameToArea;
/** Enabled */
private transient boolean enabled;
/** Tickables */
private transient List<Tickable> tickables;
/** Current focused area */
private transient Area focus;
/** Grabbed area */
private transient Area grabbed;
/** Are we inited? */
private transient boolean inited;
/** Background music */
private transient ALBuffer musicResource;
/** Streamed music */
private transient ALStream streamResource;
/** Pause */
private transient boolean paused;
/** Monkeying mode */
private transient boolean monkeying;
/** Monkey mode: dragging flag */
private transient Area dragging;
/** Monkey mode: mouse was down flag */
private transient boolean mouseWasDown;
/** Monkey mode: was mouse grabbed */
private transient boolean wasMouseGrabbed;
/** Monkey mode: selection tick */
private transient int selectTick;
/** Monkey mode: drag handle */
private transient int dragX, dragY;
/** Monkey mode: slow mode ticker */
private transient int slowTick;
/** Override keyboard navigation */
private transient boolean disableKeyboardNavigation;
/** Transition handling */
private transient Transition transitionFeature;
/*
* Phases
*/
private static final int CLOSED = 0;
private static final int OPENING = 1;
private static final int OPEN = 2;
private static final int CLOSING = 3;
private static final int BLOCKED = 4;
/**
* Describes a Ctrl-key that will fire an onClicked to a screen.
*/
private class HotKey extends Feature {
/** Key modifier */
private String modifier;
/** The key */
private String key;
/** The area ID */
private String area;
/** Command ID */
private String command;
/** Hold down */
private boolean hold;
private transient boolean wasDown;
private transient boolean waitForRelease;
/**
* C'tor
*/
public HotKey() {
setAutoCreated();
setSubResource(true);
}
@Override
public void archive() {
// Don't archive
}
void init() {
int k = Keyboard.getKeyIndex(key);
if (k != Keyboard.KEY_NONE) {
if (Keyboard.isKeyDown(k)) {
int k2 = Keyboard.getKeyIndex(modifier);
if (k2 == Keyboard.KEY_NONE || Keyboard.isKeyDown(k2)) {
// The key was down when the window opened; let's wait until it's up
waitForRelease = true;
}
}
}
}
void check() {
int k = Keyboard.getKeyIndex(key);
if (k != Keyboard.KEY_NONE) {
if (Keyboard.isKeyDown(k)) {
int k2 = Keyboard.getKeyIndex(modifier);
if (k2 != Keyboard.KEY_NONE && !Keyboard.isKeyDown(k2)) {
wasDown = false;
waitForRelease = false;
return;
}
if (waitForRelease) {
return;
}
if (command != null) {
if (wasDown) {
return;
} else {
wasDown = !hold;
onClicked(command);
}
} else {
Area a = getArea(area);
if (a != null && a.isEnabled()) {
if (wasDown) {
return;
} else {
wasDown = !hold;
onClicked(area);
}
}
}
} else {
wasDown = false;
waitForRelease = false;
}
}
}
}
/**
* C'tor
*/
public Screen(String name) {
super(name);
}
/**
* @param transition the transition to set
*/
public void setTransition(Transition transition) {
if (transition == null) {
this.transitionFeature = new InstantTransition();
} else {
this.transitionFeature = transition;
}
}
/**
* @return the transitionFeature
*/
public Transition getTransition() {
return transitionFeature;
}
/**
* Sets this screen to be the default screen on which all sprites are
* deserialized to, when a game is restored.
*/
public final void setDefaultScreen() {
SerializedSprite.setSpriteEngine(spriteEngine);
}
@Override
public void load(Element element, Loader loader) throws Exception {
super.load(element, loader);
List<Element> children = XMLUtil.getChildren(element, "area");
areas = new ArrayList<Area>(children.size());
for (Element child : children) {
Area area = (Area) loader.load(child);// new Area();
areas.add(area);
}
List<Element> hotkeyElements = XMLUtil.getChildren(element, "hotkey");
hotkeys = new ArrayList<HotKey>(hotkeyElements.size());
for (Element hotkeyElement : hotkeyElements) {
HotKey hk = new HotKey();
hk.load(hotkeyElement, loader);
hotkeys.add(hk);
}
}
@Override
protected final void doCreate() {
super.doCreate();
// Create somewhere to stash all our tickables in
if (!inited) {
backgroundEffects = new ArrayList<Effect>(4);
foregroundEffects = new ArrayList<Effect>(8);
tickables = new ArrayList<Tickable>(256);
// The first tickable is a sprite engine
spriteEngine = new StaticSpriteEngine(true, sortLayer, uniqueSprites, 1);
spriteEngine.setName("SpriteEngine["+getName()+"]");
spriteEngine.setLocked(true); // Prevent Feature.defaultDestroy from killing us
spriteEngine.create();
if (transitionFeature == null) {
transitionFeature = new ZoomTransition();
}
inited = true;
}
nameToArea = new HashMap<String, Area>();
for (Area area : areas) {
area.create();
area.spawn(this);
if (area.getID() != null) {
nameToArea.put(area.getID().toLowerCase(), area);
}
}
for (HotKey hk : hotkeys) {
hk.create();
}
doCreateScreen();
}
@Override
protected final void doDestroy() {
super.doDestroy();
// Remove the areas from the tickables list as well
for (Area area : areas) {
area.destroy();
}
for (HotKey hk : hotkeys) {
hk.destroy();
}
nameToArea = null;
doDestroyScreen();
}
protected void doCreateScreen() {
}
protected void doDestroyScreen() {
}
/**
* An Area is requesting focus
*/
public void requestFocus(Area area) {
if (area == null) {
setFocus(null);
} else if (area.isFocusable()) {
setFocus(area.getID());
}
}
/**
* Check keyboard navigation keys
*/
private void checkKeyboardNavigation() {
String changeFocus = null;
int count = 0;
while (count < areas.size()) {
if (Binding.isBindingDown(Binding.FOCUS_LEFT) || Binding.isBindingDown(Binding.FOCUS_LEFT_ALT)) {
if (focus == null) {
findFirstFocus();
} else {
changeFocus = focus.getLeftFocus();
}
} else if (Binding.isBindingDown(Binding.FOCUS_RIGHT) || Binding.isBindingDown(Binding.FOCUS_RIGHT_ALT)) {
if (focus == null) {
findFirstFocus();
} else {
changeFocus = focus.getRightFocus();
}
} else if (Binding.isBindingDown(Binding.FOCUS_UP) || Binding.isBindingDown(Binding.FOCUS_UP_ALT)) {
if (focus == null) {
findFirstFocus();
} else {
changeFocus = focus.getUpFocus();
}
} else if (Binding.isBindingDown(Binding.FOCUS_DOWN) || Binding.isBindingDown(Binding.FOCUS_DOWN_ALT)) {
if (focus == null) {
findFirstFocus();
} else {
changeFocus = focus.getDownFocus();
}
} else if (Game.wasKeyPressed(Keyboard.KEY_TAB)) {
if (focus == null) {
findFirstFocus();
} else {
if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) {
changeFocus = focus.getPrevFocus();
} else {
changeFocus = focus.getNextFocus();
}
}
} else {
return;
}
if (changeFocus == null) {
return;
}
setFocus(changeFocus);
if (focus != null && focus.isFocused()) {
return;
}
}
}
/**
* Find first focusable Area
*/
private void findFirstFocus() {
// Search for default focus first
for (Area area : areas) {
if (area.isFocusable() && area.isDefaultFocus()) {
focus = area;
return;
}
}
// Then just pick the first one
for (Area area : areas) {
if (area.isFocusable()) {
focus = area;
return;
}
}
focus = null;
}
/**
* Set an area to be the focused area
* @param areaName
*/
private void setFocus(String areaName) {
if (areaName == null) {
focus = null;
} else {
focus = getArea(areaName);
}
}
/**
* Tick the screen.
*/
@SuppressWarnings("unused")
public final void tick() {
if (!isCreated()) {
throw new RuntimeException("Screen "+this+"["+System.identityHashCode(this)+"]"+" is not created but is being ticked!");
}
oldMouseX = mouseX;
oldMouseY = mouseY;
mouseX = (int) Game.physicalXtoLogicalX(Game.getMouseX());
mouseY = (int) Game.physicalYtoLogicalY(Game.getMouseY());
if (!isMouseVisible()) {
boolean setMousePosition = false;
if (mouseX < 0) {
mouseX = 0;
setMousePosition = true;
} else if (mouseX >= Game.getWidth()) {
mouseX = Game.getWidth() - 1;
setMousePosition = true;
}
if (mouseY < 0) {
mouseY = 0;
setMousePosition = true;
} else if (mouseY >= Game.getHeight()) {
mouseY = Game.getHeight() - 1;
setMousePosition = true;
}
if (setMousePosition) {
System.out.println((int) Game.logicalXtoPhysicalX(mouseX)+","+ (int) Game.logicalYtoPhysicalY(mouseY));
Mouse.setCursorPosition((int) Game.logicalXtoPhysicalX(mouseX), (int) Game.logicalYtoPhysicalY(mouseY));
}
}
switch (phase) {
case OPENING:
if (++timer >= transitionFeature.getOpeningDuration()) {
setPhase(OPEN);
timer = 0;
} else {
doTick();
tickEverything();
//updateEverything();
postTick();
break;
}
case OPEN:
if (!paused) {
if (enabled && !monkeying) {
if (keyboardNavigation) {
checkKeyboardNavigation();
}
for (int i = 0; i < areas.size(); i ++) {
Area area = areas.get(i);
area.tick();
}
// Check hotkeys
if (hotkeys != null) {
int numKeys = hotkeys.size();
for (int i = 0; i < numKeys; i ++) {
HotKey hotKey = hotkeys.get(i);
hotKey.check();
}
}
}
// Check for monkeying mode
if (Game.DEBUG && Game.wasKeyPressed(Keyboard.KEY_F11)) { // warning suppressed
if (monkeying) {
monkeying = false;
Mouse.setGrabbed(wasMouseGrabbed);
SFX.gameOver();
} else {
monkeying = true;
wasMouseGrabbed = Mouse.isGrabbed();
Mouse.setGrabbed(false);
SFX.textEntered();
}
}
if (monkeying) {
monkey();
} else {
// Custom ticking code now
doTick();
tickEverything();
//updateEverything();
postTick();
}
// Clear unused keyboard events
while (Keyboard.next()) {
// Do nothing
}
// Clear unused mouse events
while (Mouse.next()) {
// Do nothing
}
// Clear unused controller events
while (Controllers.next()) {
// Do nothing
}
}
break;
case CLOSING:
if (++timer >= transitionFeature.getClosingDuration()) {
setPhase(CLOSED);
removeAllTickables();
doCleanup();
} else {
doTick();
tickEverything();
//updateEverything();
postTick();
}
break;
case CLOSED:
break;
case BLOCKED:
// Screen is blocked by a dialog. Still allow it to tick, unless it's paused
if (!paused) {
doTick();
tickEverything();
//updateEverything();
postTick();
}
break;
default:
assert false;
}
}
public final void update() {
updateEverything();
}
/**
* Tick all the screen's tickables
*/
private void tickEverything() {
for (int i = 0; i < tickables.size(); ) {
Tickable tickable = tickables.get(i);
if (tickable.isActive()) {
tickable.tick();
i ++;
} else {
tickables.remove(i);
tickable.remove();
}
}
// Then finally the sprite engine
spriteEngine.tick();
}
/**
* Update everything
*/
protected final void updateEverything() {
for (int i = 0; i < tickables.size(); ) {
Tickable tickable = tickables.get(i);
if (tickable.isActive()) {
tickable.update();
i ++;
} else {
tickables.remove(i);
tickable.remove();
}
}
}
/**
* Has the mouse moved this tick?
* @return boolean
*/
public boolean mouseMoved() {
return mouseX != oldMouseX || mouseY != oldMouseY;
}
/**
* Do ticking
*/
protected void doTick() {
}
/**
* Post-tick, called after everything has been ticked
*/
protected void postTick() {
}
/**
* Add a tickable
*/
public final void addTickable(Tickable tickable) {
assert !tickables.contains(tickable) : "Tickable "+tickable+" already added to "+this;
tickables.add(tickable);
}
/**
* Add a background effect
*/
public final void addBackgroundEffect(Effect effect) {
backgroundEffects.add(effect);
addTickable(effect);
}
/**
* Add a foreground effect
*/
public final void addForegroundEffect(Effect effect) {
foregroundEffects.add(effect);
addTickable(effect);
}
/**
* Remove a background effect
* @param effect
*/
public final void removeBackgroundEffect(Effect effect) {
backgroundEffects.remove(effect);
}
/**
* Remove a foreground effect
* @param effect
*/
public final void removeForegroundEffect(Effect effect) {
foregroundEffects.remove(effect);
}
/**
* Detach a specific tickable. It is removed from the tickable list
* but not "removed" as such
* @param tickable
*/
public final void detachTickable(Tickable tickable) {
tickables.remove(tickable);
}
/**
* Clear all tickables
*/
public final void removeAllTickables() {
// Properly destroy everything
int n = tickables.size();
for (int i = 0; i < n; ) {
Tickable tickable = tickables.get(i);
tickable.remove();
if (tickable.isActive()) {
i ++;
} else {
n --;
tickables.set(i, tickables.get(n));
tickables.remove(n);
}
}
}
/**
* Remove all sprites
*/
protected void removeAllSprites() {
spriteEngine.clear();
}
/**
* Called when an area is clicked on
* @param id The id of the area clicked on
*/
protected void onClicked(String id) {
}
/**
* Called when an area is hovered on or off
* @param id The id of the area
* @param on Whether the area is hovered over or not
*/
protected void onHover(String id, boolean on) {
}
/**
* Is the screen active?
* @return boolean
*/
private boolean isActive() {
return phase != CLOSED;
}
private void setPhase(int newPhase) {
if (phase == newPhase) {
return;
}
int oldPhase = phase;
phase = newPhase;
if (newPhase != BLOCKED && oldPhase == BLOCKED) {
initHotkeys();
for (Area area : areas) {
area.waitForMouse();
}
onUnblocked();
} else if (newPhase == BLOCKED && oldPhase != BLOCKED) {
onBlocked();
}
}
protected void onUnblocked() {}
protected void onBlocked() {}
/**
* Remove the screen
* @param instantly Whether to close the screen instantly
*/
public final void close(boolean instantly) {
if (isClosing() || isClosed()) {
return;
}
setPhase(CLOSING);
timer = 0;
focus = null;
onClose();
if (instantly) {
removeAllTickables();
doCleanup();
setPhase(CLOSED);
} else {
setPhase(CLOSING);
}
}
/**
* Close the screen, with an animation effect
*/
public final void close() {
close(false);
}
/**
* Do things that need doing to clean up the screen
*/
protected void onClose() {
// By default do nothing
}
/**
* Open the screen. This initializes the screen and adds it to the top of the screen stack.
* The current top of the screen stack is told to close, unless this is a dialog screen, in
* which case it is BLOCKED instead.
*/
public final void open() {
if (!isCreated()) {
throw new RuntimeException("Screen "+this+" is not created but is being opened!");
}
if (isOpen() || isOpening()) {
return;
}
for (int i = 0; i < screens.size(); i ++) {
Screen screen = screens.get(i);
if (screen == this) {
screen.removeAllTickables();
screen.doCleanup();
} else if (dialog && screen.phase != CLOSED && screen.phase != CLOSING) {
screen.setPhase(BLOCKED);
} else {
screen.close();
}
}
if (screens.contains(this)) {
// Bring screen to the top
screens.remove(this);
}
screens.add(this);
setPhase(OPENING);
enabled = true;
timer = 0;
focus = null;
if (musicResource != null) {
Game.playMusic(musicResource, transitionFeature.getOpeningDuration());
}
if (streamResource != null) {
Game.playMusic(streamResource, transitionFeature.getOpeningDuration());
}
for (Area area : areas) {
area.init();
}
resized();
initHotkeys();
onOpen();
}
private void initHotkeys() {
for (HotKey hk : hotkeys) {
hk.init();
}
}
private void resized() {
for (Area area : areas) {
area.onResized();
}
onResized();
}
protected void onResized() {}
/**
* Initialize the screen
*/
protected void onOpen() {
// Do nothing by default
}
/**
* Called when a screen becomes the topmost screen again after a blocking dialog
* is removed from on top of it
*/
protected void onReopen() {
}
/**
* Cleanup the screen when it's no longer visible
*/
protected void doCleanup() {
}
/**
* @param alpha the alpha to set
*/
public void setAlpha(float alpha) {
this.alpha = alpha;
}
/**
* Render the screen
*/
public final void render() {
glPushMatrix();
glTranslatef(offsetX, offsetY, 0.0f);
int startPhase = phase;
if (startPhase == OPENING) {
transitionFeature.preRenderOpening(this, timer);
} else if (startPhase == CLOSING) {
transitionFeature.preRenderClosing(this, timer);
} else {
setAlpha(1.0f);
}
// Do stuff before any rendering occurs
preRender();
// Reset the colour
glColor4f(1.0f, 1.0f, 1.0f, alpha);
// Render background (for example, clear the screen)
renderBackground();
// Render background effects
renderEffects(backgroundEffects, alpha);
// Render sprites
renderSprites(spriteEngine, alpha);
// Render areas
for (Area area : areas) {
area.render();
}
// Render foreground, eg. text etc.
renderForeground();
// Render foreground effects
renderEffects(foregroundEffects, alpha);
// Do any post processing
postRender();
// Monkey mode render
if (monkeying) {
renderMonkeying();
}
if (startPhase == OPENING) {
transitionFeature.postRenderOpening(this, timer);
} else if (startPhase == CLOSING) {
transitionFeature.postRenderClosing(this, timer);
}
glPopMatrix();
}
/**
* Render sprites
* @param engine The sprite engine to render
* @param alpha Alpha value, 0.0f ... 1.0f
*/
protected void renderSprites(SpriteEngine engine, float alpha) {
spriteEngine.setAlpha(alpha);
spriteEngine.render();
}
/**
* Render a List of effects
* @param effects A List of Effects to render
* @param alpha Alpha value, 0.0f ... 1.0f
*/
protected void renderEffects(List<Effect> effects, float alpha) {
int n = effects.size();
for (int i = 0; i < n; i ++) {
Effect effect = effects.get(i);
effect.render(); // TODO: refactor to add alpha
}
}
/**
* Render stuff before the sprites
*/
protected void renderBackground() {
}
/**
* Render stuff after the sprites
*/
protected void renderForeground() {
}
/**
* Pre-render. Called before any rendering at all.
*/
protected void preRender() {
}
/**
* Post-render. Called after everything else is rendered.
*/
protected void postRender() {
}
@Override
public Sprite allocateSprite(Serializable owner) {
return spriteEngine.allocate(owner);
}
/**
* Tick all screens
*/
public static void tickAllScreens() {
// Unblock the topmost screen
Screen top = getTopScreen();
if (top != null && top.isBlocked()) {
top.setPhase(OPEN);
top.onReopen();
}
for (int i = 0; i < screens.size(); ) {
Screen screen = screens.get(i);
screen.tick();
if (screen.isActive() && screen.isCreated()) {
i ++;
} else {
screens.remove(i);
}
}
boolean grabbed = Mouse.isGrabbed();
if (grabbed == isMouseVisible() && top != null && !top.monkeying) {
Mouse.setGrabbed(!isMouseVisible());
}
}
/**
* Update all screens
*/
public static void updateAllScreens() {
for (int i = 0; i < screens.size(); i ++) {
Screen screen = screens.get(i);
screen.update();
}
}
/**
* Render all screens
*/
public static void renderAllScreens() {
// Render all the screens from bottom to top
for (int i = 0; i < screens.size(); i ++) {
Screen screen = screens.get(i);
screen.render();
}
}
/**
* Is the mouse visible?
* @return boolean
*/
public static final boolean isMouseVisible() {
if (screens.size() > 0) {
Screen topmost = screens.get(screens.size() - 1);
return topmost.mouseVisible;
}
// Show the mouse if there's no screens
return true;
}
/**
* @return Returns the alpha.
*/
protected final float getAlpha() {
return alpha;
}
/**
* @return Returns the mouseX.
*/
public int getMouseX() {
return mouseX;
}
/**
* @return Returns the mouseY.
*/
public int getMouseY() {
return mouseY;
}
/**
* Get a named area
* @param name
* @return an Area, or null, if the area doesn't exist
*/
public Area getArea(String name) {
return nameToArea.get(name.toLowerCase());
}
/**
* @return true if the window is opening
*/
public final boolean isOpening() {
return phase == OPENING;
}
/**
* @return true if the window is closing
*/
public final boolean isClosing() {
return phase == CLOSING;
}
/**
* @return true if the window is closed
*/
public final boolean isClosed() {
return phase == CLOSED;
}
/**
* @return true if the window is open
*/
public final boolean isOpen() {
return phase == OPEN;
}
/**
* @return true if the screen is blocked by a dialog
*/
public final boolean isBlocked() {
return phase == BLOCKED;
}
/**
* Disable all the areas
*/
public final void setEnabled(boolean enabled) {
this.enabled = enabled;
for (Area area : areas) {
area.setEnabled(enabled);
}
}
/**
* Enable/disable keyboard navigation
* @param keyboardNavigation
*/
public final void setKeyboardNavigationEnabled(boolean keyboardNavigation) {
this.disableKeyboardNavigation = !keyboardNavigation;
}
/**
* Is keyboard navigation enabled?
* @return boolean
*/
public final boolean isKeyboardNavigationEnabled() {
return keyboardNavigation && !disableKeyboardNavigation;
}
/**
* Focus next control
*/
public final void nextFocus() {
}
/**
* Focus previous control
*/
public final void prevFocus() {
}
/**
* Get the currently focused area
* @return Area, or null
*/
public Area getFocus() {
return focus;
}
/**
* Enable / disable a button
* @param enabled
*/
public void setEnabled(String area, boolean enabled) {
Area a = getArea(area);
if (a == null) {
return;
}
a.setEnabled(enabled);
}
/**
* Show / hide a button
* @param visible
*/
public void setVisible(String area, boolean visible) {
Area a = getArea(area);
if (a == null) {
return;
}
a.setVisible(visible);
}
/**
* Enable / disable a group of buttons
* @param group
* @param enabled
*/
public void setGroupEnabled(String group, boolean enabled) {
int n = areas.size();
for (int i = 0; i < n; i ++) {
Area area = areas.get(i);
if (area.isInGroup(group)) {
area.setEnabled(enabled);
}
}
}
/**
* Show / hide a group of buttons
* @param group
* @param visible
*/
public void setGroupVisible(String group, boolean visible) {
int n = areas.size();
for (int i = 0; i < n; i ++) {
Area area = areas.get(i);
if (area.isInGroup(group)) {
area.setVisible(visible);
}
}
}
/**
* Set alpha for a group of buttons
* @param group
* @param visible
*/
public void setGroupAlpha(String group, int alpha) {
int n = areas.size();
for (int i = 0; i < n; i ++) {
Area area = areas.get(i);
if (area.isInGroup(group)) {
area.setAlpha(alpha);
}
}
}
/**
* Get the topmost screen
* @return a Screen or null if no screens are open
*/
public static Screen getTopScreen() {
if (screens.size() == 0) {
return null;
}
return screens.get(screens.size() - 1);
}
public static void onGameResized() {
for (int i = 0; i < screens.size(); i ++) {
Screen s = screens.get(i);
s.resized();
}
}
/**
* Get the visible Areas under a coordinate
* @param x
* @param y
* @returns a List of Areas under x, y
*/
public List<Area> getAreasUnder(int x, int y) {
List<Area> ret = new LinkedList<Area>();
int n = areas.size();
for (int i = 0; i < n; i ++) {
Area area = areas.get(i);
if (area.isVisible() && ((Rectangle) area.getBounds()).contains(x, y)) {
ret.add(area);
}
}
return ret;
}
/**
* Get all areas
* @return an unmodifiable List of Areas in the screen
*/
public List<Area> getAreas() {
return Collections.unmodifiableList(areas);
}
/**
* Get all areas in a group
* @param group The group name
* @return an unmodifiable List of Areas
*/
public List<Area> getAreas(String group) {
List<Area> ret = new ArrayList<Area>(areas.size());
for (Area area : areas) {
if (area.isInGroup(group)) {
ret.add(area);
}
}
return Collections.unmodifiableList(ret);
}
/**
* Sets (or clears) the grabbed area. When an Area is grabbed,
* no other areas are processed by mouse or keyboard.
* @param grabbed the grabbed to set
*/
public void setGrabbed(Area grabbed) {
this.grabbed = grabbed;
}
/**
* @return the grabbed area
*/
public Area getGrabbed() {
return grabbed;
}
/**
* Sets the translation offset for the screen's rendering. Use this to
* make transparent dialogs appear in different locations.
* @param offsetX
* @param offsetY
*/
public void setOffset(int offsetX, int offsetY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
}
/**
* Allow developer to drag Areas around
*/
private void monkey() {
if (Mouse.isButtonDown(0)) {
if (mouseWasDown && dragging != null) {
// Drag
ReadableRectangle r = dragging.getBounds();
dragging.setBounds(mouseX - dragX, mouseY - dragY, r.getWidth(), r.getHeight());
} else {
// Find out what to drag
int n = areas.size();
dragging = null;
for (int i = n; -- i >= 0; ) {
Area area = areas.get(i);
Rectangle r = (Rectangle) area.getBounds();
if (r.contains(mouseX, mouseY)) {
dragging = area;
dragX = mouseX - r.getX();
dragY = mouseY - r.getY();
break;
}
}
}
mouseWasDown = true;
} else {
mouseWasDown = false;
}
if (dragging != null) {
if (slowTick > 0) {
slowTick --;
} else {
Rectangle r = (Rectangle) dragging.getBounds();
ReadablePoint of = dragging.getOffset();
SpriteImage si = dragging.getCurrentImage();
ReadablePoint to = dragging.getTextOffset();
int tx = to.getX();
int ty = to.getY();
if (Keyboard.isKeyDown(Keyboard.KEY_R) && si != null) {
// Size to sprite
r.setSize(si.getWidth(), si.getHeight());
}
boolean ctrlDown = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL);
if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) && Keyboard.isKeyDown(Keyboard.KEY_LMENU)) {
int ox, oy;
if (of == null) {
ox = 0;
oy = 0;
} else {
ox = of.getX();
oy = of.getY();
}
if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
oy ++;
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
oy --;
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) {
ox --;
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) {
ox ++;
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
dragging.setOffset(ox, oy);
} else if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
r.setSize(r.getWidth(), r.getHeight() + 1);
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
r.setSize(r.getWidth(), r.getHeight() - 1);
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) {
r.setSize(r.getWidth() - 1, r.getHeight());
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) {
r.setSize(r.getWidth() + 1, r.getHeight());
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
} else if (Keyboard.isKeyDown(Keyboard.KEY_LMENU)) {
if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
ty ++;
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
ty --;
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) {
tx --;
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) {
tx ++;
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
} else {
if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
r.setLocation(r.getX(), r.getY() + 1);
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
r.setLocation(r.getX(), r.getY() - 1);
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) {
r.setLocation(r.getX() - 1, r.getY());
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) {
r.setLocation(r.getX() + 1, r.getY());
slowTick = ctrlDown ? SLOW_TICK_SPEED : 0;
}
}
dragging.setBounds(r.getX(), r.getY(), r.getWidth(), r.getHeight());
dragging.setTextOffset(tx, ty);
}
}
if (Game.wasKeyPressed(Keyboard.KEY_X)) {
// Write out area XML
try {
PrintWriter pw = new PrintWriter(System.out);
XMLResourceWriter writer = new XMLResourceWriter(pw);
for (Area area : areas) {
area.toXML(writer);
}
pw.flush();
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
}
/**
* Render monkey mode
*/
private void renderMonkeying() {
// Draw all area borders
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
int n = areas.size();
boolean foundHover = false;
for (int i = n; -- i >= 0; ) {
Area area = areas.get(i);
boolean hover = false;
Rectangle r = (Rectangle) area.getBounds();
if (r == null) {
ReadablePoint p = area.getPosition();
SpriteImage si = area.getCurrentImage();
if (si != null && p != null) {
r = new Rectangle(p.getX(), p.getY(), si.getWidth(), si.getHeight());
}
}
if (!foundHover && r != null) {
if (r.contains(mouseX, mouseY)) {
foundHover = true;
hover = true;
}
}
if (r != null) {
renderMonkeyedArea(area.getID(), r, hover);
}
}
}
private void renderMonkeyedArea(String id, Rectangle r, boolean hover) {
if (hover) {
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
} else {
glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
}
boolean stippled = dragging != null && dragging.getID() != null && dragging.getID().equals(id);
if (stippled) {
selectTick = (selectTick + 1) % 64;
glLineStipple(1, (short)(0xF0F0F >> (selectTick >> 3)));
glEnable(GL_LINE_STIPPLE);
}
glBegin(GL_LINE_LOOP);
glVertex2i(r.getX(), r.getY());
glVertex2i(r.getX() + r.getWidth(), r.getY());
glVertex2i(r.getX() + r.getWidth(), r.getY() + r.getHeight());
glVertex2i(r.getX(), r.getY() + r.getHeight());
glEnd();
if (stippled) {
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
GLString s = new GLString(id, Res.getTinyFont());
glColor3f(0, 0, 0);
s.setLocation(r.getX() + 1, r.getY() + r.getHeight() - 1);
s.render();
glColor3f(1, 1, 1);
s.setLocation(r.getX(), r.getY() + r.getHeight());
s.render();
GLString s2 = new GLString(r.getX()+", "+r.getY(), Res.getTinyFont());
glColor3f(0, 0, 0);
s2.setLocation(r.getX() + 1, r.getY() - 1);
s2.render();
glColor3f(1, 1, 1);
s2.setLocation(r.getX(), r.getY());
s2.render();
GLString s3 = new GLString(r.getWidth()+"x"+r.getHeight(), Res.getTinyFont());
glColor3f(0, 0, 0);
s3.setLocation(r.getX() + r.getWidth() - s3.getBounds(null).getWidth() + 1, r.getY() + r.getHeight() - s3.getBounds(null).getHeight() - 1);
s3.render();
glColor3f(1, 1, 1);
s3.setLocation(r.getX() + r.getWidth() - s3.getBounds(null).getWidth(), r.getY() + r.getHeight() - s3.getBounds(null).getHeight());
s3.render();
glDisable(GL_TEXTURE_2D);
}
if (!stippled) {
glLineStipple(1, (short) 0xFFFF);
glDisable(GL_LINE_STIPPLE);
}
}
/**
* @return true if the screen is enabled
*/
public final boolean isEnabled() {
return enabled;
}
/**
* Pause or unpause the screen. This causes tickables to stop ticking.
* Everything is still rendered though.
* @param paused
*/
public void setPaused(boolean paused) {
this.paused = paused;
onSetPaused();
}
protected void onSetPaused() {}
/**
* @return true if the screen is paused
*/
public boolean isPaused() {
return paused;
}
/**
* @return the offsetX
*/
public int getOffsetX() {
return offsetX;
}
/**
* @return the offsetY
*/
public int getOffsetY() {
return offsetY;
}
/**
* @return the timer used for screen transitions
*/
public int getTransitionTick() {
return timer;
}
public boolean isCentredX() {
return "x".equals(centre);
}
public boolean isCentredY() {
return "y".equals(centre);
}
public boolean isCentred() {
return "both".equals(centre);
}
}