package forge; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.utils.Clipboard; import forge.animation.ForgeAnimation; import forge.assets.AssetsDownloader; import forge.assets.FSkin; import forge.assets.FSkinFont; import forge.assets.ImageCache; import forge.error.BugReporter; import forge.error.ExceptionHandler; import forge.interfaces.IDeviceAdapter; import forge.model.FModel; import forge.properties.ForgeConstants; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.screens.FScreen; import forge.screens.SplashScreen; import forge.screens.home.HomeScreen; import forge.screens.home.NewGameMenu; import forge.screens.match.MatchController; import forge.sound.MusicPlaylist; import forge.sound.SoundSystem; import forge.toolbox.*; import forge.util.Callback; import forge.util.FileUtil; import forge.util.Utils; import java.util.ArrayList; import java.util.List; import java.util.Stack; public class Forge implements ApplicationListener { public static final String CURRENT_VERSION = "1.6.4.001"; private static final ApplicationListener app = new Forge(); private static Clipboard clipboard; private static IDeviceAdapter deviceAdapter; private static int screenWidth; private static int screenHeight; private static Graphics graphics; private static FScreen currentScreen; private static SplashScreen splashScreen; private static KeyInputAdapter keyInputAdapter; private static boolean exited; private static int continuousRenderingCount = 1; //initialize to 1 since continuous rendering is the default private static final Stack screens = new Stack(); private static boolean textureFiltering = false; public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0) { if (GuiBase.getInterface() == null) { clipboard = clipboard0; deviceAdapter = deviceAdapter0; GuiBase.setInterface(new GuiMobile(assetDir0)); } return app; } private Forge() { } @Override public void create() { //install our error handler ExceptionHandler.registerErrorHandling(); graphics = new Graphics(); splashScreen = new SplashScreen(); Gdx.input.setInputProcessor(new MainInputProcessor()); ForgePreferences prefs = new ForgePreferences(); String skinName; if (FileUtil.doesFileExist(ForgeConstants.MAIN_PREFS_FILE)) { skinName = prefs.getPref(FPref.UI_SKIN); } else { skinName = "default"; //use default skin if preferences file doesn't exist yet } FSkin.loadLight(skinName, splashScreen); textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING); //load model on background thread (using progress bar to report progress) FThreads.invokeInBackgroundThread(new Runnable() { @Override public void run() { //see if app or assets need updating AssetsDownloader.checkForUpdates(splashScreen); if (exited) { return; } //don't continue if user chose to exit or couldn't download required assets FModel.initialize(splashScreen.getProgressBar(), null); splashScreen.getProgressBar().setDescription("Loading fonts..."); FSkinFont.preloadAll(); splashScreen.getProgressBar().setDescription("Finishing startup..."); Gdx.app.postRunnable(new Runnable() { @Override public void run() { afterDbLoaded(); } }); } }); } private void afterDbLoaded() { stopContinuousRendering(); //save power consumption by disabling continuous rendering once assets loaded FSkin.loadFull(splashScreen); SoundSystem.instance.setBackgroundMusic(MusicPlaylist.MENUS); //start background music Gdx.input.setCatchBackKey(true); Gdx.input.setCatchMenuKey(true); openScreen(HomeScreen.instance); splashScreen = null; boolean isLandscapeMode = isLandscapeMode(); if (isLandscapeMode) { //open preferred new game screen by default if landscape mode NewGameMenu.getPreferredScreen().open(); } //update landscape mode preference if it doesn't match what the app loaded as if (FModel.getPreferences().getPrefBoolean(FPref.UI_LANDSCAPE_MODE) != isLandscapeMode) { FModel.getPreferences().setPref(FPref.UI_LANDSCAPE_MODE, isLandscapeMode); FModel.getPreferences().save(); } } public static Clipboard getClipboard() { return clipboard; } public static IDeviceAdapter getDeviceAdapter() { return deviceAdapter; } public static void startContinuousRendering() { if (++continuousRenderingCount == 1) { //only set continuous rendering to true if needed Gdx.graphics.setContinuousRendering(true); } } public static void stopContinuousRendering() { if (continuousRenderingCount > 0 && --continuousRenderingCount == 0) { //only set continuous rendering to false if all continuous rendering requests have been ended Gdx.graphics.setContinuousRendering(false); } } public static void showMenu() { if (currentScreen == null) { return; } endKeyInput(); //end key input before menu shown if (FOverlay.getTopOverlay() == null) { //don't show menu if overlay open currentScreen.showMenu(); } } public static boolean onHomeScreen() { return screens.size() == 1; } public static void back() { if (screens.size() < 2) { exit(false); //prompt to exit if attempting to go back from home screen return; } currentScreen.onClose(new Callback() { @Override public void run(Boolean result) { if (result) { screens.pop(); setCurrentScreen(screens.lastElement()); } } }); } //set screen that will be gone to on pressing Back before going to current Back screen public static void setBackScreen(final FScreen screen0, boolean replace) { screens.remove(screen0); //remove screen from previous position in navigation history int index = screens.size() - 1; if (index > 0) { screens.add(index, screen0); if (replace) { //remove previous back screen if replacing back screen screens.remove(index - 1); } } } public static void restart(boolean silent) { if (exited) { return; } //don't allow exiting multiple times Callback callback = new Callback() { @Override public void run(Boolean result) { if (result) { exited = true; deviceAdapter.restart(); } } }; if (silent) { callback.run(true); } else { FOptionPane.showConfirmDialog("Are you sure you wish to restart Forge?", "Restart Forge", "Restart", "Cancel", callback); } } public static void exit(boolean silent) { if (exited) { return; } //don't allow exiting multiple times Callback callback = new Callback() { @Override public void run(Boolean result) { if (result) { exited = true; deviceAdapter.exit(); } } }; if (silent) { callback.run(true); } else { FOptionPane.showConfirmDialog("Are you sure you wish to exit Forge?", "Exit Forge", "Exit", "Cancel", callback); } } public static void openScreen(final FScreen screen0) { openScreen(screen0, false); } public static void openScreen(final FScreen screen0, final boolean replaceBackScreen) { if (currentScreen == screen0) { return; } if (currentScreen == null) { screens.push(screen0); setCurrentScreen(screen0); return; } currentScreen.onSwitchAway(new Callback() { @Override public void run(Boolean result) { if (result) { if (replaceBackScreen && !screens.isEmpty()) { screens.pop(); } if (screens.peek() != screen0) { //prevent screen being its own back screen screens.push(screen0); } setCurrentScreen(screen0); } } }); } public static boolean isTextureFilteringEnabled() { return textureFiltering; } public static boolean isLandscapeMode() { return screenWidth > screenHeight; } public static int getScreenWidth() { return screenWidth; } public static int getScreenHeight() { return screenHeight; } public static FScreen getCurrentScreen() { return currentScreen; } private static void setCurrentScreen(FScreen screen0) { try { endKeyInput(); //end key input before switching screens ForgeAnimation.endAll(); //end all active animations before switching screens currentScreen = screen0; currentScreen.setSize(screenWidth, screenHeight); currentScreen.onActivate(); } catch (Exception ex) { graphics.end(); BugReporter.reportException(ex); } } @Override public void render() { try { ImageCache.allowSingleLoad(); ForgeAnimation.advanceAll(); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // Clear the screen. FContainer screen = currentScreen; if (screen == null) { screen = splashScreen; if (screen == null) { return; } } graphics.begin(screenWidth, screenHeight); screen.screenPos.setSize(screenWidth, screenHeight); if (screen.getRotate180()) { graphics.startRotateTransform(screenWidth / 2, screenHeight / 2, 180); } screen.draw(graphics); if (screen.getRotate180()) { graphics.endTransform(); } for (FOverlay overlay : FOverlay.getOverlays()) { if (overlay.isVisibleOnScreen(currentScreen)) { overlay.screenPos.setSize(screenWidth, screenHeight); overlay.setSize(screenWidth, screenHeight); //update overlay sizes as they're rendered if (overlay.getRotate180()) { graphics.startRotateTransform(screenWidth / 2, screenHeight / 2, 180); } overlay.draw(graphics); if (overlay.getRotate180()) { graphics.endTransform(); } } } graphics.end(); } catch (Exception ex) { graphics.end(); BugReporter.reportException(ex); } } @Override public void resize(int width, int height) { try { screenWidth = width; screenHeight = height; if (currentScreen != null) { currentScreen.setSize(width, height); } else if (splashScreen != null) { splashScreen.setSize(width, height); } } catch (Exception ex) { graphics.end(); BugReporter.reportException(ex); } } @Override public void pause() { if (MatchController.getHostedMatch() != null) { MatchController.getHostedMatch().pause(); } } @Override public void resume() { if (MatchController.getHostedMatch() != null) { MatchController.getHostedMatch().resume(); } } @Override public void dispose() { if (currentScreen != null) { FOverlay.hideAll(); currentScreen.onClose(null); currentScreen = null; } screens.clear(); graphics.dispose(); SoundSystem.instance.dispose(); try { ExceptionHandler.unregisterErrorHandling(); } catch (Exception e) {} } //log message to Forge.log file public static void log(Object message) { System.out.println(message); } public static void startKeyInput(KeyInputAdapter adapter) { if (keyInputAdapter == adapter) { return; } if (keyInputAdapter != null) { keyInputAdapter.onInputEnd(); //make sure previous adapter is ended } keyInputAdapter = adapter; Gdx.input.setOnscreenKeyboardVisible(true); } public static boolean endKeyInput() { if (keyInputAdapter == null) { return false; } keyInputAdapter.onInputEnd(); keyInputAdapter = null; MainInputProcessor.keyTyped = false; MainInputProcessor.lastKeyTyped = '\0'; Gdx.input.setOnscreenKeyboardVisible(false); return true; } public static abstract class KeyInputAdapter { public abstract FDisplayObject getOwner(); public abstract boolean allowTouchInput(); public abstract boolean keyTyped(char ch); public abstract boolean keyDown(int keyCode); public abstract void onInputEnd(); //also allow handling of keyUp but don't require it public boolean keyUp(int keyCode) { return false; } public static boolean isCtrlKeyDown() { return Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Keys.CONTROL_RIGHT); } public static boolean isShiftKeyDown() { return Gdx.input.isKeyPressed(Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Keys.SHIFT_RIGHT); } public static boolean isAltKeyDown() { return Gdx.input.isKeyPressed(Keys.ALT_LEFT) || Gdx.input.isKeyPressed(Keys.ALT_RIGHT); } public static boolean isModifierKey(int keyCode) { switch (keyCode) { case Keys.CONTROL_LEFT: case Keys.CONTROL_RIGHT: case Keys.SHIFT_LEFT: case Keys.SHIFT_RIGHT: case Keys.ALT_LEFT: case Keys.ALT_RIGHT: return true; } return false; } } private static class MainInputProcessor extends FGestureAdapter { private static final List potentialListeners = new ArrayList(); private static char lastKeyTyped; private static boolean keyTyped, shiftKeyDown; @Override public boolean keyDown(int keyCode) { if (keyCode == Keys.MENU) { showMenu(); return true; } if (keyCode == Keys.SHIFT_LEFT || keyCode == Keys.SHIFT_RIGHT) { shiftKeyDown = true; } if (keyInputAdapter == null) { if (KeyInputAdapter.isModifierKey(keyCode)) { return false; //don't process modifiers keys for unknown adapter } //if no active key input adapter, give current screen or overlay a chance to handle key FContainer container = FOverlay.getTopOverlay(); if (container == null) { container = currentScreen; if (container == null) { return false; } } return container.keyDown(keyCode); } return keyInputAdapter.keyDown(keyCode); } @Override public boolean keyUp(int keyCode) { keyTyped = false; //reset on keyUp if (keyCode == Keys.SHIFT_LEFT || keyCode == Keys.SHIFT_RIGHT) { shiftKeyDown = false; } if (keyInputAdapter != null) { return keyInputAdapter.keyUp(keyCode); } return false; } @Override public boolean keyTyped(char ch) { if (keyInputAdapter != null) { if (ch >= ' ' && ch <= '~') { //only process this event if character is printable //prevent firing this event more than once for the same character on the same key down, otherwise it fires too often if (lastKeyTyped != ch || !keyTyped) { keyTyped = true; lastKeyTyped = ch; return keyInputAdapter.keyTyped(ch); } } } return false; } private void updatePotentialListeners(int x, int y) { potentialListeners.clear(); //base potential listeners on object containing touch down point for (FOverlay overlay : FOverlay.getOverlaysTopDown()) { if (overlay.isVisibleOnScreen(currentScreen)) { overlay.buildTouchListeners(x, y, potentialListeners); if (overlay.preventInputBehindOverlay()) { return; } } } if (currentScreen != null) { currentScreen.buildTouchListeners(x, y, potentialListeners); } } @Override public boolean touchDown(int x, int y, int pointer, int button) { if (pointer == 0) { //don't change listeners when second finger goes down for zoom updatePotentialListeners(x, y); if (keyInputAdapter != null) { if (!keyInputAdapter.allowTouchInput() || !potentialListeners.contains(keyInputAdapter.getOwner())) { endKeyInput(); //end key input if needed } } } return super.touchDown(x, y, pointer, button); } @Override public boolean press(float x, float y) { try { for (FDisplayObject listener : potentialListeners) { if (listener.press(listener.screenToLocalX(x), listener.screenToLocalY(y))) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } @Override public boolean release(float x, float y) { try { for (FDisplayObject listener : potentialListeners) { if (listener.release(listener.screenToLocalX(x), listener.screenToLocalY(y))) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } @Override public boolean longPress(float x, float y) { try { for (FDisplayObject listener : potentialListeners) { if (listener.longPress(listener.screenToLocalX(x), listener.screenToLocalY(y))) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } @Override public boolean tap(float x, float y, int count) { if (shiftKeyDown && flick(x, y)) { return true; //give flick logic a chance to handle Shift+click } try { for (FDisplayObject listener : potentialListeners) { if (listener.tap(listener.screenToLocalX(x), listener.screenToLocalY(y), count)) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } @Override public boolean flick(float x, float y) { try { for (FDisplayObject listener : potentialListeners) { if (listener.flick(listener.screenToLocalX(x), listener.screenToLocalY(y))) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } @Override public boolean fling(float velocityX, float velocityY) { try { for (FDisplayObject listener : potentialListeners) { if (listener.fling(velocityX, velocityY)) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } @Override public boolean pan(float x, float y, float deltaX, float deltaY, boolean moreVertical) { try { for (FDisplayObject listener : potentialListeners) { if (listener.pan(listener.screenToLocalX(x), listener.screenToLocalY(y), deltaX, deltaY, moreVertical)) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } @Override public boolean panStop(float x, float y) { try { for (FDisplayObject listener : potentialListeners) { if (listener.panStop(listener.screenToLocalX(x), listener.screenToLocalY(y))) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } @Override public boolean zoom(float x, float y, float amount) { try { for (FDisplayObject listener : potentialListeners) { if (listener.zoom(listener.screenToLocalX(x), listener.screenToLocalY(y), amount)) { return true; } } return false; } catch (Exception ex) { BugReporter.reportException(ex); return true; } } //mouseMoved and scrolled events for desktop version private int mouseMovedX, mouseMovedY; @Override public boolean mouseMoved(int x, int y) { mouseMovedX = x; mouseMovedY = y; return true; } @Override public boolean scrolled(int amount) { updatePotentialListeners(mouseMovedX, mouseMovedY); if (KeyInputAdapter.isCtrlKeyDown()) { //zoom in or out based on amount return zoom(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amount); } boolean handled; if (KeyInputAdapter.isShiftKeyDown()) { handled = pan(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amount, 0, false); } else { handled = pan(mouseMovedX, mouseMovedY, 0, -Utils.AVG_FINGER_HEIGHT * amount, true); } if (panStop(mouseMovedX, mouseMovedY)) { handled = true; } return handled; } } }