/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import java.awt.GraphicsConfiguration;
import java.awt.Cursor;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.util.*;
import javax.media.opengl.*;
import javax.media.opengl.awt.*;
import javax.media.opengl.glu.GLU;
public class HavenPanel extends GLCanvas implements Runnable, Console.Directory {
UI ui;
boolean inited = false, rdr = false;
int w, h;
long fd = 20, fps = 0;
double idle = 0.0;
Queue<InputEvent> events = new LinkedList<InputEvent>();
private String cursmode = "tex";
private Resource lastcursor = null;
public Coord mousepos = new Coord(0, 0);
public Profile prof = new Profile(300);
private Profile.Frame curf = null;
public static final GLState.Slot<GLState> global = new GLState.Slot<GLState>(GLState.Slot.Type.SYS, GLState.class);
public static final GLState.Slot<GLState> proj2d = new GLState.Slot<GLState>(GLState.Slot.Type.SYS, GLState.class, global);
private GLState gstate, rtstate, ostate;
private GLState.Applier state = null;
private GLConfig glconf = null;
private static GLCapabilities stdcaps() {
GLProfile prof = GLProfile.getDefault();
GLCapabilities cap = new GLCapabilities(prof);
cap.setDoubleBuffered(true);
cap.setAlphaBits(8);
cap.setRedBits(8);
cap.setGreenBits(8);
cap.setBlueBits(8);
cap.setSampleBuffers(true);
cap.setNumSamples(4);
cap.setDepthBits(24);
return(cap);
}
public HavenPanel(int w, int h, GLCapabilitiesChooser cc) {
super(stdcaps(), cc, null, null);
setSize(this.w = w, this.h = h);
newui(null);
initgl();
if(Toolkit.getDefaultToolkit().getMaximumCursorColors() >= 256)
cursmode = "awt";
setCursor(Toolkit.getDefaultToolkit().createCustomCursor(TexI.mkbuf(new Coord(1, 1)), new java.awt.Point(), ""));
}
public HavenPanel(int w, int h) {
this(w, h, null);
}
private void initgl() {
final Thread caller = Thread.currentThread();
final haven.error.ErrorHandler h = haven.error.ErrorHandler.find();
addGLEventListener(new GLEventListener() {
public void display(GLAutoDrawable d) {
GL2 gl = d.getGL().getGL2();
if(inited && rdr)
redraw(gl);
GLObject.disposeall(gl);
}
public void init(GLAutoDrawable d) {
GL gl = d.getGL();
glconf = GLConfig.fromgl(gl, d.getContext(), getChosenGLCapabilities());
glconf.pref = GLSettings.load(glconf, true);
ui.cons.add(glconf);
if(h != null) {
h.lsetprop("gl.vendor", gl.glGetString(gl.GL_VENDOR));
h.lsetprop("gl.version", gl.glGetString(gl.GL_VERSION));
h.lsetprop("gl.renderer", gl.glGetString(gl.GL_RENDERER));
h.lsetprop("gl.exts", Arrays.asList(gl.glGetString(gl.GL_EXTENSIONS).split(" ")));
h.lsetprop("gl.caps", d.getChosenGLCapabilities().toString());
h.lsetprop("gl.conf", glconf);
}
Config.setglpref(glconf.pref);
gstate = new GLState() {
public void apply(GOut g) {
GL2 gl = g.gl;
gl.glColor3f(1, 1, 1);
gl.glPointSize(4);
gl.setSwapInterval(1);
gl.glEnable(GL.GL_BLEND);
//gl.glEnable(GL.GL_LINE_SMOOTH);
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
if(g.gc.glmajver >= 2)
gl.glBlendEquationSeparate(GL.GL_FUNC_ADD, GL2.GL_MAX);
if(g.gc.havefsaa()) {
/* Apparently, having sample
* buffers in the config enables
* multisampling by default on
* some systems. */
g.gl.glDisable(GL.GL_MULTISAMPLE);
}
GOut.checkerr(gl);
}
public void unapply(GOut g) {
}
public void prep(Buffer buf) {
buf.put(global, this);
}
};
}
public void reshape(GLAutoDrawable d, final int x, final int y, final int w, final int h) {
ostate = OrthoState.fixed(new Coord(w, h));
rtstate = new GLState() {
public void apply(GOut g) {
GL2 gl = g.gl;
g.st.matmode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0, w, 0, h, -1, 1);
}
public void unapply(GOut g) {
}
public void prep(Buffer buf) {
buf.put(proj2d, this);
}
};
HavenPanel.this.w = w;
HavenPanel.this.h = h;
}
public void displayChanged(GLAutoDrawable d, boolean cp1, boolean cp2) {}
public void dispose(GLAutoDrawable d) {}
});
}
public static abstract class OrthoState extends GLState {
protected abstract Coord sz();
public void apply(GOut g) {
GL2 gl = g.gl;
Coord sz = sz();
g.st.matmode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0, sz.x, sz.y, 0, -1, 1);
}
public void unapply(GOut g) {
}
public void prep(Buffer buf) {
buf.put(proj2d, this);
}
public static OrthoState fixed(final Coord sz) {
return(new OrthoState() {
protected Coord sz() {return(sz);}
});
}
}
public void init() {
setFocusTraversalKeysEnabled(false);
newui(null);
addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent e) {
synchronized(events) {
events.add(e);
events.notifyAll();
}
}
public void keyPressed(KeyEvent e) {
synchronized(events) {
events.add(e);
events.notifyAll();
}
}
public void keyReleased(KeyEvent e) {
synchronized(events) {
events.add(e);
events.notifyAll();
}
}
});
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
synchronized(events) {
events.add(e);
events.notifyAll();
}
}
public void mouseReleased(MouseEvent e) {
synchronized(events) {
events.add(e);
events.notifyAll();
}
}
});
addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
synchronized(events) {
events.add(e);
}
}
public void mouseMoved(MouseEvent e) {
synchronized(events) {
events.add(e);
}
}
});
addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent e) {
synchronized(events) {
events.add(e);
events.notifyAll();
}
}
});
inited = true;
}
UI newui(Session sess) {
if(ui != null)
ui.destroy();
ui = new UI(new Coord(w, h), sess);
ui.root.gprof = prof;
if(getParent() instanceof Console.Directory)
ui.cons.add((Console.Directory)getParent());
ui.cons.add(this);
if(glconf != null)
ui.cons.add(glconf);
return(ui);
}
private static Cursor makeawtcurs(BufferedImage img, Coord hs) {
java.awt.Dimension cd = Toolkit.getDefaultToolkit().getBestCursorSize(img.getWidth(), img.getHeight());
BufferedImage buf = TexI.mkbuf(new Coord((int)cd.getWidth(), (int)cd.getHeight()));
java.awt.Graphics g = buf.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
return(Toolkit.getDefaultToolkit().createCustomCursor(buf, new java.awt.Point(hs.x, hs.y), ""));
}
void redraw(GL2 gl) {
//gl = new DebugGL(gl);
if((state == null) || (state.gl != gl))
state = new GLState.Applier(gl, glconf);
GLState.Buffer ibuf = new GLState.Buffer(glconf);
gstate.prep(ibuf);
ostate.prep(ibuf);
GOut g = new GOut(gl, getContext(), glconf, state, ibuf, new Coord(w, h));
UI ui = this.ui;
state.set(ibuf);
g.state(rtstate);
TexRT.renderall(g);
if(curf != null)
curf.tick("texrt");
g.state(ostate);
g.apply();
gl.glClearColor(0, 0, 0, 1);
gl.glClear(GL.GL_COLOR_BUFFER_BIT);
if(curf != null)
curf.tick("cls");
synchronized(ui) {
ui.draw(g);
}
if(curf != null)
curf.tick("draw");
if(Config.dbtext) {
int y = h - 20;
FastText.aprintf(g, new Coord(10, y -= 15), 0, 1, "FPS: %d (%d%% idle)", fps, (int)(idle * 100.0));
Runtime rt = Runtime.getRuntime();
long free = rt.freeMemory(), total = rt.totalMemory();
FastText.aprintf(g, new Coord(10, y -= 15), 0, 1, "Mem: %,011d/%,011d/%,011d/%,011d", free, total - free, total, rt.maxMemory());
FastText.aprintf(g, new Coord(10, y -= 15), 0, 1, "Tex-current: %d", TexGL.num());
FastText.aprintf(g, new Coord(10, y -= 15), 0, 1, "RT-current: %d", TexRT.current.get(gl).size());
FastText.aprintf(g, new Coord(10, y -= 15), 0, 1, "GL progs: %d", g.st.numprogs());
GameUI gi = ui.gui;
if((gi != null) && (gi.map != null)) {
try {
FastText.aprintf(g, new Coord(10, y -= 15), 0, 1, "MV pos: %s (%s)", gi.map.getcc(), gi.map.camera);
} catch(Loading e) {}
}
if(Resource.qdepth() > 0)
FastText.aprintf(g, new Coord(10, y -= 15), 0, 1, "RQ depth: %d (%d)", Resource.qdepth(), Resource.numloaded());
}
Object tooltip;
try {
synchronized(ui) {
tooltip = ui.root.tooltip(mousepos, ui.root);
}
} catch(Loading e) {
tooltip = "...";
}
Tex tt = null;
if(tooltip != null) {
if(tooltip instanceof Text) {
tt = ((Text)tooltip).tex();
} else if(tooltip instanceof Tex) {
tt = (Tex)tooltip;
} else if(tooltip instanceof Indir<?>) {
Indir<?> t = (Indir<?>)tooltip;
Object o = t.get();
if(o instanceof Tex)
tt = (Tex)o;
} else if(tooltip instanceof String) {
if(((String)tooltip).length() > 0)
tt = (Text.render((String)tooltip)).tex();
}
}
if(tt != null) {
Coord sz = tt.sz();
Coord pos = mousepos.add(sz.inv());
if(pos.x < 5)
pos.x = 5;
if(pos.y < 5)
pos.y = 5;
g.chcolor(35, 35, 35, 192);
g.frect(pos.add(-2, -2), sz.add(4, 4));
g.chcolor(244, 247, 21, 192);
g.rect(pos.add(-3, -3), sz.add(6, 6));
g.chcolor();
g.image(tt, pos);
}
synchronized(ui) {
ui.lastdraw(g);
}
ui.lasttip = tooltip;
Resource curs = ui.root.getcurs(mousepos);
if(!curs.loading) {
if(cursmode == "awt") {
if(curs != lastcursor) {
try {
setCursor(makeawtcurs(curs.layer(Resource.imgc).img, curs.layer(Resource.negc).cc));
lastcursor = curs;
} catch(Exception e) {
cursmode = "tex";
}
}
} else if(cursmode == "tex") {
Coord dc = mousepos.add(curs.layer(Resource.negc).cc.inv());
g.image(curs.layer(Resource.imgc), dc);
}
}
state.clean();
if(glconf.pref.dirty) {
glconf.pref.save();
glconf.pref.dirty = false;
}
}
void dispatch() {
synchronized(events) {
InputEvent e = null;
while((e = events.poll()) != null) {
if(e instanceof MouseEvent) {
MouseEvent me = (MouseEvent)e;
if(me.getID() == MouseEvent.MOUSE_PRESSED) {
ui.mousedown(me, new Coord(me.getX(), me.getY()), me.getButton());
} else if(me.getID() == MouseEvent.MOUSE_RELEASED) {
ui.mouseup(me, new Coord(me.getX(), me.getY()), me.getButton());
} else if(me.getID() == MouseEvent.MOUSE_MOVED || me.getID() == MouseEvent.MOUSE_DRAGGED) {
mousepos = new Coord(me.getX(), me.getY());
ui.mousemove(me, mousepos);
} else if(me instanceof MouseWheelEvent) {
ui.mousewheel(me, new Coord(me.getX(), me.getY()), ((MouseWheelEvent)me).getWheelRotation());
}
} else if(e instanceof KeyEvent) {
KeyEvent ke = (KeyEvent)e;
if(ke.getID() == KeyEvent.KEY_PRESSED) {
ui.keydown(ke);
} else if(ke.getID() == KeyEvent.KEY_RELEASED) {
ui.keyup(ke);
} else if(ke.getID() == KeyEvent.KEY_TYPED) {
ui.type(ke);
}
}
ui.lastevent = System.currentTimeMillis();
}
}
}
public void uglyjoglhack() throws InterruptedException {
try {
rdr = true;
display();
} catch(RuntimeException e) {
if(e.getCause() instanceof InterruptedException) {
throw((InterruptedException)e.getCause());
} else {
throw(e);
}
} finally {
rdr = false;
}
}
public void run() {
try {
long now, fthen, then;
int frames = 0, waited = 0;
fthen = System.currentTimeMillis();
while(true) {
Debug.cycle();
UI ui = this.ui;
then = System.currentTimeMillis();
if(Config.profile)
curf = prof.new Frame();
synchronized(ui) {
if(ui.sess != null)
ui.sess.glob.ctick();
dispatch();
ui.tick();
if((ui.root.sz.x != w) || (ui.root.sz.y != h))
ui.root.resize(new Coord(w, h));
}
if(curf != null)
curf.tick("dsp");
uglyjoglhack();
ui.audio.cycle();
if(curf != null)
curf.tick("aux");
frames++;
now = System.currentTimeMillis();
if(now - then < fd) {
synchronized(events) {
events.wait(fd - (now - then));
}
waited += System.currentTimeMillis() - now;
}
if(curf != null)
curf.tick("wait");
if(now - fthen > 1000) {
fps = frames;
idle = ((double)waited) / ((double)(now - fthen));
frames = 0;
waited = 0;
fthen = now;
}
if(curf != null)
curf.fin();
if(Thread.interrupted())
throw(new InterruptedException());
}
} catch(InterruptedException e) {
} finally {
ui.destroy();
}
}
public GraphicsConfiguration getconf() {
return(getGraphicsConfiguration());
}
private Map<String, Console.Command> cmdmap = new TreeMap<String, Console.Command>();
{
cmdmap.put("hz", new Console.Command() {
public void run(Console cons, String[] args) {
fd = 1000 / Integer.parseInt(args[1]);
}
});
}
public Map<String, Console.Command> findcmds() {
return(cmdmap);
}
}