/*
* 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.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import net.puppygames.applet.effects.SFX;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Cursor;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.util.ReadablePoint;
import org.lwjgl.util.ReadableRectangle;
import org.lwjgl.util.Rectangle;
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.GLRenderable;
import com.shavenpuppy.jglib.opengl.GLString;
import com.shavenpuppy.jglib.resources.Feature;
import com.shavenpuppy.jglib.sprites.Sprite;
import com.shavenpuppy.jglib.sprites.SpriteAllocator;
import com.shavenpuppy.jglib.sprites.SpriteEngine;
import com.shavenpuppy.jglib.sprites.SpriteImage;
import com.shavenpuppy.jglib.sprites.StaticSpriteEngine;
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;
private static final int MINIMUM_MUSIC_FADE_DURATION = 30;
/*
* Static data
*/
/** All the open screens, in the order they should be rendered */
private static final List<Screen> SCREENS = new ArrayList<Screen>(4);
/** Extra ticking to do as a result of trying to tick within tick */
private static final List<Screen> EXTRA_TICKING = new ArrayList<Screen>(1);
/** Monkeying: slow tick speed */
private static final int SLOW_TICK_SPEED = 4;
/** Lazily created empty cursor */
private static Cursor emptyCursor = null;
private static boolean emptyCursorCreated;
/*
* 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;
/** Allcaps areas */
private boolean allCaps;
/*
* Transient data
*/
/** Sprite engine */
private transient StaticSpriteEngine spriteEngine;
/** A timer */
private transient int timer;
/** Already ticking */
private transient boolean alreadyTicking;
/** 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;
// /** Constrain mouse */
// private transient ReadableRectangle constrainMouse;
/** Monkeying */
private transient MonkeyRenderable monkeyRenderable;
private class MonkeyRenderable extends TickableObject {
@Override
protected void render() {
// Draw all area borders
glRender(new GLRenderable() {
@Override
public void render() {
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;
glRender(new GLRenderable() {
@Override
public void render() {
glLineStipple(1, (short)(0xF0F0F >> (selectTick >> 3)));
glEnable(GL_LINE_STIPPLE);
}
});
}
short idx = glVertex2f(r.getX(), r.getY());
glVertex2f(r.getX() + r.getWidth(), r.getY());
glVertex2f(r.getX() + r.getWidth(), r.getY() + r.getHeight());
glVertex2f(r.getX(), r.getY() + r.getHeight());
glRender(GL_LINE_LOOP, new short[] {(short) (idx + 0), (short) (idx + 1), (short) (idx + 2), (short) (idx + 3)});
if (stippled) {
glRender(new GLRenderable() {
@Override
public void render() {
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(this);
glColor3f(1, 1, 1);
s.setLocation(r.getX(), r.getY() + r.getHeight());
s.render(this);
GLString s2 = new GLString(r.getX()+", "+r.getY(), Res.getTinyFont());
glColor3f(0, 0, 0);
s2.setLocation(r.getX() + 1, r.getY() - 1);
s2.render(this);
glColor3f(1, 1, 1);
s2.setLocation(r.getX(), r.getY());
s2.render(this);
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(this);
glColor3f(1, 1, 1);
s3.setLocation(r.getX() + r.getWidth() - s3.getBounds(null).getWidth(), r.getY() + r.getHeight() - s3.getBounds(null).getHeight());
s3.render(this);
glRender(new GLRenderable() {
@Override
public void render() {
glDisable(GL_TEXTURE_2D);
}
});
}
if (!stippled) {
glRender(new GLRenderable() {
@Override
public void render() {
glLineStipple(1, (short) 0xFFFF);
glDisable(GL_LINE_STIPPLE);
}
});
}
}
}
/*
* 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;
}
@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();
if (allCaps) {
area.setAllCaps(true);
}
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) {
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!");
}
if (alreadyTicking) {
if (!EXTRA_TICKING.contains(this)) {
EXTRA_TICKING.add(this);
}
return;
}
alreadyTicking = true;
oldMouseX = mouseX;
oldMouseY = mouseY;
mouseX = (int) Game.physicalXtoLogicalX(Game.getMouseX());
mouseY = (int) Game.physicalYtoLogicalY(Game.getMouseY());
// if (!isMouseVisible()) {
// boolean setMousePosition = false;
// int minX = constrainMouse == null ? 0 : constrainMouse.getX();
// int minY = constrainMouse == null ? 0 : constrainMouse.getY();
// int maxW = constrainMouse == null ? getWidth() : constrainMouse.getWidth();
// int maxH = constrainMouse == null ? getHeight() : constrainMouse.getHeight();
// if (mouseX < minX) {
// mouseX = minX;
// setMousePosition = true;
// } else if (mouseX >= maxW) {
// mouseX = maxW - 1;
// setMousePosition = true;
// }
// if (mouseY < minY) {
// mouseY = minY;
// setMousePosition = true;
// } else if (mouseY >= maxH) {
// mouseY = maxH - 1;
// setMousePosition = true;
// }
// if (setMousePosition) {
// 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;
System.out.println("No longer Monkeying");
if (monkeyRenderable != null) {
monkeyRenderable.remove();
monkeyRenderable = null;
}
Mouse.setGrabbed(wasMouseGrabbed);
SFX.gameOver();
} else {
monkeying = true;
System.out.println("Monkeying");
monkeyRenderable = new MonkeyRenderable();
monkeyRenderable.setLayer(100);
monkeyRenderable.spawn(this);
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();
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();
postTick();
}
break;
default:
assert false;
}
alreadyTicking = false;
}
public final void update() {
updateEverything();
doUpdate();
}
protected void doUpdate() {
}
/**
* 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);
}
/**
* 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) {
clearKeyboardEvents();
initHotkeys();
for (Area area : areas) {
area.waitForMouse();
}
onUnblocked();
} else if (newPhase == BLOCKED && oldPhase != BLOCKED) {
onBlocked();
} else if (newPhase == OPENING) {
clearKeyboardEvents();
}
}
protected void onUnblocked() {}
protected void onBlocked() {}
private static void clearKeyboardEvents() {
while (Keyboard.next()) {
// Do nothing
}
}
/**
* Force this screen into "open" state instantly.
*/
public void forceOpen() {
phase = OPEN;
}
/**
* 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);
tick();
}
}
/**
* 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, Math.max(MINIMUM_MUSIC_FADE_DURATION, transitionFeature.getOpeningDuration()));
}
if (streamResource != null) {
Game.playMusic(streamResource, Math.max(MINIMUM_MUSIC_FADE_DURATION, transitionFeature.getOpeningDuration()));
}
for (Area area : areas) {
area.init();
}
initHotkeys();
resized();
onOpen();
tick();
}
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 sprites
renderSprites(spriteEngine, alpha);
// // Render areas
// for (Area area : areas) {
// area.render();
// }
// Render foreground, eg. text etc.
renderForeground();
// Do any post processing
postRender();
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 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) {
Sprite ret = spriteEngine.allocateSprite(owner);
ret.setAllocator(this);
return ret;
}
private static boolean mouseCurrentlyVisible = true;
/**
* Tick all screens
*/
public static void tickAllScreens() {
// Constrain mouse to window
if (!mouseCurrentlyVisible) {
int x = Mouse.getX();
int y = Mouse.getY();
boolean set = false;
if (x < 0) {
x = 0;
set = true;
}
if (y < 0) {
y = 0;
set = true;
}
if (set) {
Mouse.setCursorPosition(x, y);
}
}
// 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);
}
}
for (int i = 0; i < EXTRA_TICKING.size(); i ++) {
EXTRA_TICKING.get(i).tick();
}
EXTRA_TICKING.clear();
try {
if (!isMouseVisible() && mouseCurrentlyVisible) {
mouseCurrentlyVisible = false;
if (!emptyCursorCreated) {
emptyCursorCreated = true;
try {
emptyCursor = new Cursor(1, 1, 0, 0, 1, BufferUtils.createIntBuffer(1), null);
} catch (LWJGLException e) {
e.printStackTrace(System.err);
}
}
Mouse.setNativeCursor(emptyCursor);
Mouse.setGrabbed(true);
} else if (isMouseVisible() && !mouseCurrentlyVisible) {
mouseCurrentlyVisible = true;
Mouse.setNativeCursor(null);
Mouse.setGrabbed(false);
}
} catch (LWJGLException e) {
e.printStackTrace(System.err);
}
}
/**
* 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 (Game.isPaused()) {
return true;
}
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);
}
}
}
/**
* @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);
}
public int getWidth() {
return Game.getWidth();
}
public int getHeight() {
return Game.getHeight();
}
// /**
// * Constrain the mouse
// * @param constrainMouse Mouse constraints (logical coordinates); or null to clear
// */
// public void setConstrainMouse(ReadableRectangle constrainMouse) {
// this.constrainMouse = constrainMouse;
// }
}