1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.backends.lwjgl; 18 19 import java.awt.Canvas; 20 import java.awt.Cursor; 21 import java.awt.Dimension; 22 import java.awt.EventQueue; 23 import java.util.HashMap; 24 import java.util.Map; 25 26 import org.lwjgl.opengl.AWTGLCanvas; 27 import org.lwjgl.opengl.Display; 28 29 import com.badlogic.gdx.Application; 30 import com.badlogic.gdx.ApplicationListener; 31 import com.badlogic.gdx.Audio; 32 import com.badlogic.gdx.Files; 33 import com.badlogic.gdx.Gdx; 34 import com.badlogic.gdx.Graphics; 35 import com.badlogic.gdx.Input; 36 import com.badlogic.gdx.LifecycleListener; 37 import com.badlogic.gdx.Net; 38 import com.badlogic.gdx.Preferences; 39 import com.badlogic.gdx.backends.lwjgl.audio.OpenALAudio; 40 import com.badlogic.gdx.utils.Array; 41 import com.badlogic.gdx.utils.Clipboard; 42 import com.badlogic.gdx.utils.SharedLibraryLoader; 43 44 /** An OpenGL surface on an AWT Canvas, allowing OpenGL to be embedded in a Swing application. This uses 45 * {@link Display#setParent(Canvas)}, which is preferred over {@link AWTGLCanvas} but is limited to a single LwjglCanvas in an 46 * application. All OpenGL calls are done on the EDT. Note that you may need to call {@link #stop()} or a Swing application may 47 * deadlock on System.exit due to how LWJGL and/or Swing deal with shutdown hooks. 48 * @author Nathan Sweet */ 49 public class LwjglCanvas implements Application { 50 static boolean isWindows = System.getProperty("os.name").contains("Windows"); 51 52 LwjglGraphics graphics; 53 OpenALAudio audio; 54 LwjglFiles files; 55 LwjglInput input; 56 LwjglNet net; 57 ApplicationListener listener; 58 Canvas canvas; 59 final Array<Runnable> runnables = new Array(); 60 final Array<Runnable> executedRunnables = new Array(); 61 final Array<LifecycleListener> lifecycleListeners = new Array<LifecycleListener>(); 62 boolean running = true; 63 int logLevel = LOG_INFO; 64 Cursor cursor; 65 66 public LwjglCanvas (ApplicationListener listener) { 67 LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); 68 initialize(listener, config); 69 } 70 71 public LwjglCanvas (ApplicationListener listener, LwjglApplicationConfiguration config) { 72 initialize(listener, config); 73 } 74 75 private void initialize (ApplicationListener listener, LwjglApplicationConfiguration config) { 76 LwjglNativesLoader.load(); 77 78 canvas = new Canvas() { 79 private final Dimension minSize = new Dimension(1, 1); 80 81 public final void addNotify () { 82 super.addNotify(); 83 if (SharedLibraryLoader.isMac) { 84 EventQueue.invokeLater(new Runnable() { 85 public void run () { 86 create(); 87 } 88 }); 89 } else 90 create(); 91 } 92 93 public final void removeNotify () { 94 stop(); 95 super.removeNotify(); 96 } 97 98 public Dimension getMinimumSize () { 99 return minSize; 100 } 101 }; 102 canvas.setSize(1, 1); 103 canvas.setIgnoreRepaint(true); 104 105 graphics = new LwjglGraphics(canvas, config) { 106 public void setTitle (String title) { 107 super.setTitle(title); 108 LwjglCanvas.this.setTitle(title); 109 } 110 111 public boolean setWindowedMode (int width, int height, boolean fullscreen) { 112 if (!super.setWindowedMode(width, height)) return false; 113 if (!fullscreen) LwjglCanvas.this.setDisplayMode(width, height); 114 return true; 115 } 116 117 public boolean setFullscreenMode (DisplayMode displayMode) { 118 if (!super.setFullscreenMode(displayMode)) return false; 119 LwjglCanvas.this.setDisplayMode(displayMode.width, displayMode.height); 120 return true; 121 } 122 }; 123 graphics.setVSync(config.vSyncEnabled); 124 if (!LwjglApplicationConfiguration.disableAudio) audio = new OpenALAudio(); 125 files = new LwjglFiles(); 126 input = new LwjglInput(); 127 net = new LwjglNet(); 128 this.listener = listener; 129 130 Gdx.app = this; 131 Gdx.graphics = graphics; 132 Gdx.audio = audio; 133 Gdx.files = files; 134 Gdx.input = input; 135 Gdx.net = net; 136 } 137 138 protected void setDisplayMode (int width, int height) { 139 } 140 141 protected void setTitle (String title) { 142 } 143 144 @Override 145 public ApplicationListener getApplicationListener () { 146 return listener; 147 } 148 149 public Canvas getCanvas () { 150 return canvas; 151 } 152 153 @Override 154 public Audio getAudio () { 155 return audio; 156 } 157 158 @Override 159 public Files getFiles () { 160 return files; 161 } 162 163 @Override 164 public Graphics getGraphics () { 165 return graphics; 166 } 167 168 @Override 169 public Input getInput () { 170 return input; 171 } 172 173 @Override 174 public Net getNet () { 175 return net; 176 } 177 178 @Override 179 public ApplicationType getType () { 180 return ApplicationType.Desktop; 181 } 182 183 @Override 184 public int getVersion () { 185 return 0; 186 } 187 188 void create () { 189 try { 190 graphics.setupDisplay(); 191 192 listener.create(); 193 listener.resize(Math.max(1, graphics.getWidth()), Math.max(1, graphics.getHeight())); 194 195 start(); 196 } catch (Exception ex) { 197 stopped(); 198 exception(ex); 199 return; 200 } 201 202 EventQueue.invokeLater(new Runnable() { 203 int lastWidth = Math.max(1, graphics.getWidth()); 204 int lastHeight = Math.max(1, graphics.getHeight()); 205 206 public void run () { 207 if (!running || Display.isCloseRequested()) { 208 running = false; 209 stopped(); 210 return; 211 } 212 try { 213 Display.processMessages(); 214 if (cursor != null || !isWindows) canvas.setCursor(cursor); 215 216 boolean shouldRender = false; 217 218 int width = Math.max(1, graphics.getWidth()); 219 int height = Math.max(1, graphics.getHeight()); 220 if (lastWidth != width || lastHeight != height) { 221 lastWidth = width; 222 lastHeight = height; 223 Gdx.gl.glViewport(0, 0, lastWidth, lastHeight); 224 resize(width, height); 225 listener.resize(width, height); 226 shouldRender = true; 227 } 228 229 if (executeRunnables()) shouldRender = true; 230 231 // If one of the runnables set running to false, for example after an exit(). 232 if (!running) return; 233 234 input.update(); 235 shouldRender |= graphics.shouldRender(); 236 input.processEvents(); 237 if (audio != null) audio.update(); 238 239 if (shouldRender) { 240 graphics.updateTime(); 241 graphics.frameId++; 242 listener.render(); 243 Display.update(false); 244 } 245 246 Display.sync(getFrameRate()); 247 } catch (Throwable ex) { 248 exception(ex); 249 } 250 EventQueue.invokeLater(this); 251 } 252 }); 253 } 254 255 public boolean executeRunnables () { 256 synchronized (runnables) { 257 for (int i = runnables.size - 1; i >= 0; i--) 258 executedRunnables.addAll(runnables.get(i)); 259 runnables.clear(); 260 } 261 if (executedRunnables.size == 0) return false; 262 do 263 executedRunnables.pop().run(); 264 while (executedRunnables.size > 0); 265 return true; 266 } 267 268 protected int getFrameRate () { 269 int frameRate = Display.isActive() ? graphics.config.foregroundFPS : graphics.config.backgroundFPS; 270 if (frameRate == -1) frameRate = 10; 271 if (frameRate == 0) frameRate = graphics.config.backgroundFPS; 272 if (frameRate == 0) frameRate = 30; 273 return frameRate; 274 } 275 276 protected void exception (Throwable ex) { 277 ex.printStackTrace(); 278 stop(); 279 } 280 281 /** Called after {@link ApplicationListener} create and resize, but before the game loop iteration. */ 282 protected void start () { 283 } 284 285 /** Called when the canvas size changes. */ 286 protected void resize (int width, int height) { 287 } 288 289 /** Called when the game loop has stopped. */ 290 protected void stopped () { 291 } 292 293 public void stop () { 294 EventQueue.invokeLater(new Runnable() { 295 public void run () { 296 if (!running) return; 297 running = false; 298 try { 299 Display.destroy(); 300 if (audio != null) audio.dispose(); 301 } catch (Throwable ignored) { 302 } 303 Array<LifecycleListener> listeners = lifecycleListeners; 304 synchronized (listeners) { 305 for (LifecycleListener listener : listeners) { 306 listener.pause(); 307 listener.dispose(); 308 } 309 } 310 listener.pause(); 311 listener.dispose(); 312 } 313 }); 314 } 315 316 @Override 317 public long getJavaHeap () { 318 return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); 319 } 320 321 @Override 322 public long getNativeHeap () { 323 return getJavaHeap(); 324 } 325 326 Map<String, Preferences> preferences = new HashMap<String, Preferences>(); 327 328 @Override 329 public Preferences getPreferences (String name) { 330 if (preferences.containsKey(name)) { 331 return preferences.get(name); 332 } else { 333 Preferences prefs = new LwjglPreferences(name, ".prefs/"); 334 preferences.put(name, prefs); 335 return prefs; 336 } 337 } 338 339 @Override 340 public Clipboard getClipboard () { 341 return new LwjglClipboard(); 342 } 343 344 @Override 345 public void postRunnable (Runnable runnable) { 346 synchronized (runnables) { 347 runnables.add(runnable); 348 Gdx.graphics.requestRendering(); 349 } 350 } 351 352 @Override 353 public void debug (String tag, String message) { 354 if (logLevel >= LOG_DEBUG) { 355 System.out.println(tag + ": " + message); 356 } 357 } 358 359 @Override 360 public void debug (String tag, String message, Throwable exception) { 361 if (logLevel >= LOG_DEBUG) { 362 System.out.println(tag + ": " + message); 363 exception.printStackTrace(System.out); 364 } 365 } 366 367 public void log (String tag, String message) { 368 if (logLevel >= LOG_INFO) { 369 System.out.println(tag + ": " + message); 370 } 371 } 372 373 @Override 374 public void log (String tag, String message, Throwable exception) { 375 if (logLevel >= LOG_INFO) { 376 System.out.println(tag + ": " + message); 377 exception.printStackTrace(System.out); 378 } 379 } 380 381 @Override 382 public void error (String tag, String message) { 383 if (logLevel >= LOG_ERROR) { 384 System.err.println(tag + ": " + message); 385 } 386 } 387 388 @Override 389 public void error (String tag, String message, Throwable exception) { 390 if (logLevel >= LOG_ERROR) { 391 System.err.println(tag + ": " + message); 392 exception.printStackTrace(System.err); 393 } 394 } 395 396 @Override 397 public void setLogLevel (int logLevel) { 398 this.logLevel = logLevel; 399 } 400 401 @Override 402 public int getLogLevel () { 403 return logLevel; 404 } 405 406 @Override 407 public void exit () { 408 postRunnable(new Runnable() { 409 @Override 410 public void run () { 411 LwjglCanvas.this.listener.pause(); 412 LwjglCanvas.this.listener.dispose(); 413 if (audio != null) audio.dispose(); 414 System.exit(-1); 415 } 416 }); 417 } 418 419 /** @param cursor May be null. */ 420 public void setCursor (Cursor cursor) { 421 this.cursor = cursor; 422 } 423 424 @Override 425 public void addLifecycleListener (LifecycleListener listener) { 426 synchronized (lifecycleListeners) { 427 lifecycleListeners.add(listener); 428 } 429 } 430 431 @Override 432 public void removeLifecycleListener (LifecycleListener listener) { 433 synchronized (lifecycleListeners) { 434 lifecycleListeners.removeValue(listener, true); 435 } 436 } 437 } 438