/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.backends.lwjgl;
import java.awt.Canvas;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.AWTGLCanvas;
import org.lwjgl.opengl.PixelFormat;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Audio;
import com.badlogic.gdx.Files;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Graphics;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.LifecycleListener;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.backends.lwjgl.audio.OpenALAudio;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Clipboard;
import com.badlogic.gdx.utils.GdxRuntimeException;
/** An OpenGL surface on an AWT Canvas, allowing OpenGL to be embedded in a Swing application. All OpenGL calls are done on the
* EDT. This is slightly less efficient then a dedicated thread, but greatly simplifies synchronization. Note that you may need to
* call {@link #stop()} or a Swing application may deadlock on System.exit due to how LWJGL and/or Swing deal with shutdown hooks.
* @author Nathan Sweet */
public class LwjglAWTCanvas implements Application {
final LwjglGraphics graphics;
final OpenALAudio audio;
final LwjglFiles files;
final LwjglAWTInput input;
final LwjglNet net;
final ApplicationListener listener;
final AWTGLCanvas canvas;
final List<Runnable> runnables = new ArrayList();
final List<Runnable> executedRunnables = new ArrayList();
final Array<LifecycleListener> lifecycleListeners = new Array<LifecycleListener>();
boolean running = true;
int lastWidth;
int lastHeight;
int logLevel = LOG_INFO;
private Cursor cursor;
public LwjglAWTCanvas (ApplicationListener listener) {
this(listener, null);
}
public LwjglAWTCanvas (ApplicationListener listener, LwjglAWTCanvas sharedContextCanvas) {
LwjglNativesLoader.load();
AWTGLCanvas sharedDrawable = sharedContextCanvas != null ? sharedContextCanvas.canvas : null;
try {
canvas = new AWTGLCanvas(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(), new PixelFormat(),
sharedDrawable) {
private final Dimension minSize = new Dimension(0, 0);
public Dimension getMinimumSize () {
return minSize;
}
@Override
public void initGL () {
create();
}
@Override
public void paintGL () {
try {
LwjglAWTCanvas.this.render();
swapBuffers();
repaint();
} catch (LWJGLException ex) {
throw new GdxRuntimeException(ex);
}
}
};
} catch (LWJGLException ex) {
throw new GdxRuntimeException(ex);
}
graphics = new LwjglGraphics(canvas) {
public void setTitle (String title) {
super.setTitle(title);
LwjglAWTCanvas.this.setTitle(title);
}
public boolean setDisplayMode (int width, int height, boolean fullscreen) {
if (!super.setDisplayMode(width, height, fullscreen)) return false;
if (!fullscreen) LwjglAWTCanvas.this.setDisplayMode(width, height);
return true;
}
public boolean setDisplayMode (DisplayMode displayMode) {
if (!super.setDisplayMode(displayMode)) return false;
LwjglAWTCanvas.this.setDisplayMode(displayMode.width, displayMode.height);
return true;
}
};
if (!LwjglApplicationConfiguration.disableAudio && Gdx.audio == null) {
audio = new OpenALAudio();
Gdx.audio = audio;
} else {
audio = null;
}
if (Gdx.files == null) {
files = new LwjglFiles();
Gdx.files = files;
} else {
files = null;
}
if (Gdx.net == null) {
net = new LwjglNet();
Gdx.net = net;
} else {
net = null;
}
input = new LwjglAWTInput(canvas);
this.listener = listener;
setGlobals();
}
protected void setDisplayMode (int width, int height) {
}
protected void setTitle (String title) {
}
@Override
public ApplicationListener getApplicationListener () {
return listener;
}
public Canvas getCanvas () {
return canvas;
}
@Override
public Audio getAudio () {
return Gdx.audio;
}
@Override
public Files getFiles () {
return files;
}
@Override
public Graphics getGraphics () {
return graphics;
}
@Override
public Input getInput () {
return input;
}
@Override
public Net getNet () {
return net;
}
@Override
public ApplicationType getType () {
return ApplicationType.Desktop;
}
@Override
public int getVersion () {
return 0;
}
void setGlobals () {
Gdx.app = this;
if (audio != null) Gdx.audio = audio;
if (files != null) Gdx.files = files;
if (net != null) Gdx.net = net;
Gdx.graphics = graphics;
Gdx.input = input;
}
void create () {
try {
setGlobals();
graphics.initiateGLInstances();
listener.create();
lastWidth = Math.max(1, graphics.getWidth());
lastHeight = Math.max(1, graphics.getHeight());
listener.resize(lastWidth, lastHeight);
start();
} catch (Exception ex) {
stopped();
throw new GdxRuntimeException(ex);
}
}
void render () {
if (!running) return;
setGlobals();
canvas.setCursor(cursor);
graphics.updateTime();
int width = Math.max(1, graphics.getWidth());
int height = Math.max(1, graphics.getHeight());
if (lastWidth != width || lastHeight != height) {
lastWidth = width;
lastHeight = height;
Gdx.gl.glViewport(0, 0, lastWidth, lastHeight);
resize(width, height);
listener.resize(width, height);
}
synchronized (runnables) {
executedRunnables.clear();
executedRunnables.addAll(runnables);
runnables.clear();
for (int i = 0; i < executedRunnables.size(); i++) {
try {
executedRunnables.get(i).run();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
input.processEvents();
if (running) {
listener.render();
if (audio != null) {
audio.update();
}
}
}
/** Called after {@link ApplicationListener} create and resize, but before the game loop iteration. */
protected void start () {
}
/** Called when the canvas size changes. */
protected void resize (int width, int height) {
}
/** Called when the game loop has stopped. */
protected void stopped () {
}
public void stop () {
if (!running) return;
running = false;
setGlobals();
Array<LifecycleListener> listeners = lifecycleListeners;
synchronized (listeners) {
for (LifecycleListener listener : listeners) {
listener.pause();
listener.dispose();
}
}
listener.pause();
listener.dispose();
Gdx.app = null;
Gdx.graphics = null;
if (audio != null) {
audio.dispose();
Gdx.audio = null;
}
if (files != null) Gdx.files = null;
if (net != null) Gdx.net = null;
stopped();
}
@Override
public long getJavaHeap () {
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
}
@Override
public long getNativeHeap () {
return getJavaHeap();
}
Map<String, Preferences> preferences = new HashMap<String, Preferences>();
@Override
public Preferences getPreferences (String name) {
if (preferences.containsKey(name)) {
return preferences.get(name);
} else {
Preferences prefs = new LwjglPreferences(name, ".prefs/");
preferences.put(name, prefs);
return prefs;
}
}
@Override
public Clipboard getClipboard () {
return new LwjglClipboard();
}
@Override
public void postRunnable (Runnable runnable) {
synchronized (runnables) {
runnables.add(runnable);
}
}
@Override
public void debug (String tag, String message) {
if (logLevel >= LOG_DEBUG) {
System.out.println(tag + ": " + message);
}
}
@Override
public void debug (String tag, String message, Throwable exception) {
if (logLevel >= LOG_DEBUG) {
System.out.println(tag + ": " + message);
exception.printStackTrace(System.out);
}
}
public void log (String tag, String message) {
if (logLevel >= LOG_INFO) {
System.out.println(tag + ": " + message);
}
}
@Override
public void log (String tag, String message, Throwable exception) {
if (logLevel >= LOG_INFO) {
System.out.println(tag + ": " + message);
exception.printStackTrace(System.out);
}
}
@Override
public void error (String tag, String message) {
if (logLevel >= LOG_ERROR) {
System.err.println(tag + ": " + message);
}
}
@Override
public void error (String tag, String message, Throwable exception) {
if (logLevel >= LOG_ERROR) {
System.err.println(tag + ": " + message);
exception.printStackTrace(System.err);
}
}
@Override
public void setLogLevel (int logLevel) {
this.logLevel = logLevel;
}
@Override
public int getLogLevel() {
return logLevel;
}
@Override
public void exit () {
postRunnable(new Runnable() {
@Override
public void run () {
stop();
System.exit(-1);
}
});
}
/** Make the canvas' context current. It is highly recommended that the context is only made current inside the AWT thread (for
* example in an overridden paintGL()). */
public void makeCurrent () {
try {
canvas.makeCurrent();
setGlobals();
} catch (LWJGLException ex) {
throw new GdxRuntimeException(ex);
}
}
/** Test whether the canvas' context is current. */
public boolean isCurrent () {
try {
return canvas.isCurrent();
} catch (LWJGLException ex) {
throw new GdxRuntimeException(ex);
}
}
/** @param cursor May be null. */
public void setCursor (Cursor cursor) {
this.cursor = cursor;
}
@Override
public void addLifecycleListener (LifecycleListener listener) {
synchronized (lifecycleListeners) {
lifecycleListeners.add(listener);
}
}
@Override
public void removeLifecycleListener (LifecycleListener listener) {
synchronized (lifecycleListeners) {
lifecycleListeners.removeValue(listener, true);
}
}
}