Java tutorial
/* * * The MIT License (MIT) * * Copyright (c) <2015> <Andreas Modahl> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ package org.ams.testapps.paintandphysics.physicspuzzle; import com.badlogic.gdx.*; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.*; import com.badlogic.gdx.scenes.scene2d.actions.Actions; import com.badlogic.gdx.scenes.scene2d.ui.*; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.viewport.ScreenViewport; import org.ams.core.Timer; import org.ams.core.Util; import org.ams.prettypaint.PrettyPolygonBatch; import org.ams.prettypaint.TexturePolygon; import static org.ams.core.SceneUtil.*; import java.util.HashMap; import java.util.Map; /** * A game menu for {@link PhysicsPuzzle }. */ public class PhysicsPuzzleGameMenu extends ApplicationAdapter { private static final int default_row_count = 4; private static final int default_column_count = 4; private static final int default_interval = -1; private PhysicsPuzzle physicsPuzzle; // the game, a new one is created for each "game" private Preferences preferences; // used to remember settings private Skin skin; private Stage stage; // image selection menu things private Image selectedThumbnail; private Runnable onResize; // used to lay out menus after resize private InputMultiplexer inputMultiplexer; private boolean hideAndPauseGame = true; // background stuff private OrthographicCamera backgroundCamera; private TexturePolygon background; private PrettyPolygonBatch polygonBatch; // puzzle textures private Array<String> availableRegions; private TextureAtlas textureAtlas; // thumbnails and backgrounds in this atlas private TextureRegion currentPuzzleTextureRegion; // stored here so it can be dispose()'ed // maps thumbnail regions to names used to load a normal sized region when game starts private Map<TextureRegion, String> puzzleNames = new HashMap<TextureRegion, String>(); private static final int MAIN_MENU = 0, SETTINGS_MENU = 1, IMAGE_MENU = 2, IN_GAME = 3; private int currentScreen = MAIN_MENU, lastScreen; private Timer timer; @Override public void create() { timer = new Timer(); Gdx.app.setLogLevel(Application.LOG_ERROR); Gdx.app.log("PhysicsPuzzleGameMenu", "Creating application PhysicsPuzzleGameMenu"); preferences = Gdx.app.getPreferences("PhysicsPuzzle"); Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); // ui stuff ScreenViewport sv = new ScreenViewport(); float ui_scale = (float) Math.abs(Math.log(Gdx.graphics.getDensity())) * 2f; sv.setUnitsPerPixel(1f / ui_scale); this.stage = new Stage(sv); skin = new Skin(Gdx.files.internal("ui/custom/custom.json")); // backgrounds and thumbnails are here textureAtlas = new TextureAtlas("images/packed/packed.atlas"); availableRegions = findLinesThatContain("images/packed/packed.atlas", "thumbnails"); // input inputMultiplexer = new InputMultiplexer(); Gdx.input.setInputProcessor(inputMultiplexer); inputMultiplexer.addProcessor(stage); // background stuff polygonBatch = new PrettyPolygonBatch(); backgroundCamera = new OrthographicCamera(); showMainMenu(); } /** This is a really crappy solution. */ private Array<String> getAvailableBackgrounds(String match) { Array<String> backgrounds = new Array<String>(); String path = "images/backgrounds-dark/escheresque_ste.png"; if (path.contains(match)) backgrounds.add(path); path = "images/backgrounds-light/giftly.png"; if (path.contains(match)) backgrounds.add(path); path = "images/backgrounds-light/sativa.png"; if (path.contains(match)) backgrounds.add(path); path = "images/backgrounds-light/restaurant_icons.png"; if (path.contains(match)) backgrounds.add(path); return backgrounds; } /** Returns all the lines in the file that contain one or more of the Strings given. */ private Array<String> findLinesThatContain(String path, String... searchFor) { String file = Gdx.files.internal(path).readString(); String[] lines = file.split("\\n"); Array<String> result = new Array<String>(); for (String s : lines) { boolean match = false; for (String contain : searchFor) { if (s.contains(contain)) { match = true; break; } } if (match) result.add(s); } return result; } /** Update the background so it looks proper. Must be done after every resize. */ private void updateBackgroundBounds(TexturePolygon texturePolygon) { backgroundCamera.setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); Array<Vector2> vertices = new Array<Vector2>(); float halfWidth = Gdx.graphics.getWidth() * 0.5f; float halfHeight = Gdx.graphics.getHeight() * 0.5f; vertices.add(new Vector2(-halfWidth, -halfHeight)); vertices.add(new Vector2(halfWidth, -halfHeight)); vertices.add(new Vector2(halfWidth, halfHeight)); vertices.add(new Vector2(-halfWidth, halfHeight)); texturePolygon.setVertices(vertices); texturePolygon.setPosition(halfWidth, halfHeight); texturePolygon.setTextureScale(1); } /** Set a new background. */ private TexturePolygon setRandomBackground(Array<String> selectFrom) { if (background != null) { background.getTextureRegion().getTexture().dispose(); } Texture texture = new Texture(selectFrom.random()); texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest); TexturePolygon texturePolygon = new TexturePolygon(); texturePolygon.setTextureRegion(new TextureRegion(texture)); updateBackgroundBounds(texturePolygon); return texturePolygon; } /** * Loads the big region for the selectedThumbnail and loads * game settings from {@link #preferences}. * Then it starts the game. */ private void startGame() { if (currentPuzzleTextureRegion != null) currentPuzzleTextureRegion.getTexture().dispose(); // load the normally sized texture TextureRegion thumbnailRegion = getTextureRegion(selectedThumbnail); currentPuzzleTextureRegion = getBigRegion(thumbnailRegion); String name = puzzleNames.get(thumbnailRegion).replace("thumbnails/", ""); // some game settings int rows = preferences.getInteger("Rows", default_row_count); int columns = preferences.getInteger("Columns", default_column_count); float interval = preferences.getFloat("Interval", default_interval); startGame(currentPuzzleTextureRegion, name, rows, columns, interval); } /** Start puzzling. */ private void startGame(TextureRegion textureRegion, String textureRegionName, int rows, int columns, float interval) { Gdx.app.log("PhysicsPuzzleGameMenu", "Starting game with textureRegion=" + puzzleNames.get(textureRegion) + ", rows=" + rows + ", columns=" + columns + ", interval=" + interval); if (physicsPuzzle != null) { physicsPuzzle.dispose(); } final PhysicsPuzzleDef physicsPuzzleDef = new PhysicsPuzzleDef(); physicsPuzzleDef.columns = columns; physicsPuzzleDef.rows = rows; physicsPuzzleDef.interval = interval; physicsPuzzleDef.outlineColor.set(Color.BLACK); physicsPuzzleDef.textureRegionName = textureRegionName; physicsPuzzle = new PhysicsPuzzle(); physicsPuzzle.create(inputMultiplexer, textureRegion, physicsPuzzleDef, new PhysicsPuzzle.Callback() { @Override public void gameOver(boolean win) { showGameOverUi(); } }); physicsPuzzle.setZoom(0.75f); physicsPuzzle.setPosition(0, 0.5f); resumeGame(); } /** Clear other ui and show just a new game button. */ private void showGameOverUi() { onResize = new Runnable() { @Override public void run() { showGameOverUi(); } }; showImageSelectionMenu(Align.bottom, Color.BLACK); float zoom = (stage.getHeight() - computePreferredImageHeight() - computePreferredPadding()) / stage.getHeight(); physicsPuzzle.setZoom(zoom * 0.9f); physicsPuzzle.setPosition(0, -0.9f); } private void addMainMenuButton(Color color) { Table buttonTable = createButtonTable(color); stage.addActor(buttonTable); float x = computePreferredButtonWidth() * 0.5f; float y = stage.getHeight() - buttonTable.getPrefHeight() * 0.5f; buttonTable.setPosition(x, y); } private boolean canResumeGame() { return physicsPuzzle != null; } /** * Resumes game if there is any. * Clears other menus and shows some buttons in the top left corner. * Changes background. */ private void resumeGame() { stage.clear(); lastScreen = currentScreen; currentScreen = IN_GAME; Gdx.app.log("PhysicsPuzzleGameMenu", "Resuming game"); hideAndPauseGame = false; final Table inGameButtonTable = new Table(); // sets position of buttons to top left corner onResize = new Runnable() { @Override public void run() { inGameButtonTable.setPosition(inGameButtonTable.getPrefWidth() * 0.5f, stage.getHeight() - inGameButtonTable.getPrefHeight() * 0.5f); } }; // create buttons final TextButton menuButton = new TextButton("Menu", skin); menuButton.setColor(Color.BLACK); menuButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { showMainMenu(); } }); final TextButton pauseButton = new TextButton(physicsPuzzle.isPaused() ? "Resume" : "Pause", skin); pauseButton.setColor(Color.BLACK); pauseButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { physicsPuzzle.setPaused(!physicsPuzzle.isPaused()); pauseButton.setText(physicsPuzzle.isPaused() ? "Resume" : "Pause"); if (physicsPuzzle.isPaused()) { inGameButtonTable.add(menuButton).width(pauseButton.getPrefWidth()); } else inGameButtonTable.removeActor(menuButton); onResize.run(); } }); // show some buttons inGameButtonTable.add(pauseButton).padBottom(computePreferredPadding()).row(); stage.addActor(inGameButtonTable); if (physicsPuzzle.isPaused()) { inGameButtonTable.add(menuButton).width(pauseButton.getPrefWidth()); } else inGameButtonTable.removeActor(menuButton); background = setRandomBackground(getAvailableBackgrounds("light")); onResize.run(); } /** Clear other menus and show menu for customizing a new game. */ private void showCustomizationMenu() { lastScreen = currentScreen; currentScreen = SETTINGS_MENU; Gdx.app.log("PhysicsPuzzleGameMenu", "Showing customization menu"); onResize = new Runnable() { @Override public void run() { showCustomizationMenu(); } }; Table customizeMenu = createCustomizationComponents(); stage.clear(); stage.addActor(customizeMenu); fillAndCenter(stage, customizeMenu); } /** Create components for customizing the game after selecting a picture. */ private Table createCustomizationComponents() { Gdx.app.log("PhysicsPuzzleGameMenu", "Creating customization components"); int minRows = 2, maxRows = 20; int minColumns = 2, maxColumns = 20; float minInterval = 0.2f, maxInterval = 7f, intervalStep = 0.1f; boolean tallScreen = Gdx.graphics.getHeight() > Gdx.graphics.getWidth(); float preferredLabelWidth = new Label("0000", skin).getPrefWidth(); float preferredPadding = computePreferredPadding(); float buttonWidth = computePreferredButtonWidth(); // prepare sliders Table controlsTable = new Table(skin); // row count slider and labels Cell<Label> cell = controlsTable.add("Rows"); if (tallScreen) cell.row(); final Slider rowSlider = new Slider(minRows, maxRows, 1, false, skin); rowSlider.setValue(preferences.getInteger("Rows", default_row_count)); controlsTable.add(rowSlider).padBottom(preferredPadding).width(buttonWidth); final Label rowCounter = new Label(String.valueOf(rowSlider.getValue()), skin); cell = controlsTable.add(rowCounter).width(preferredLabelWidth); cell.row(); if (tallScreen) cell.pad(preferredPadding); rowSlider.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { rowCounter.setText(String.valueOf(rowSlider.getValue())); } }); // column count slider and labels cell = controlsTable.add("Columns"); if (tallScreen) cell.row(); final Slider columnSlider = new Slider(minColumns, maxColumns, 1, false, skin); columnSlider.setValue(preferences.getInteger("Columns", default_column_count)); controlsTable.add(columnSlider).padBottom(preferredPadding).width(buttonWidth); final Label columnCounter = new Label(String.valueOf(columnSlider.getValue()), skin); cell = controlsTable.add(columnCounter).width(preferredLabelWidth); cell.row(); if (tallScreen) cell.pad(preferredPadding); columnSlider.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { columnCounter.setText(String.valueOf(columnSlider.getValue())); } }); // prepare interval controls final CheckBox intervalCheckBox = new CheckBox("Drop interval", skin); intervalCheckBox.setChecked(preferences.getBoolean("EnableInterval", false)); final Slider intervalSlider = new Slider(minInterval, maxInterval, intervalStep, false, skin); intervalSlider.setValue(preferences.getFloat("Interval", default_interval)); intervalSlider.setVisible(intervalCheckBox.isChecked()); final Label intervalCounter = new Label(Util.safeSubstring(intervalSlider.getValue(), 4) + " s", skin); intervalCounter.setVisible(intervalCheckBox.isChecked()); intervalSlider.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { intervalCounter.setText(Util.safeSubstring(intervalSlider.getValue(), 4) + " s"); } }); intervalCheckBox.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { boolean enabled = intervalCheckBox.isChecked(); intervalSlider.setVisible(enabled); intervalCounter.setVisible(enabled); preferences.putBoolean("EnableInterval", enabled); } }); // add interval controls Cell<CheckBox> cell3 = controlsTable.add(intervalCheckBox); if (tallScreen) cell3.row(); controlsTable.add(intervalSlider).padBottom(preferredPadding).width(buttonWidth); cell = controlsTable.add(intervalCounter).width(preferredLabelWidth); cell.row(); if (tallScreen) cell.pad(preferredPadding); // prepare buttons Table buttonTable = new Table(); Cell<TextButton> cell1 = null; boolean addPlayButton = lastScreen == IMAGE_MENU; TextButton backButton = new TextButton("Back", skin); Cell<TextButton> cell2 = buttonTable.add(backButton).width(buttonWidth).pad(preferredPadding); cell2.row(); backButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { showMainMenu(); // remember to next time int rows = (int) rowSlider.getValue(); int columns = (int) columnSlider.getValue(); float interval = intervalCheckBox.isChecked() ? intervalSlider.getValue() : -1; preferences.putInteger("Rows", rows); preferences.putInteger("Columns", columns); preferences.putFloat("Interval", interval); preferences.flush(); } }); if (!tallScreen && addPlayButton) swapActors(cell1, cell2); // prepare the table Table customizeMenu = new Table(); customizeMenu.add(controlsTable).pad(preferredPadding).row(); customizeMenu.add(buttonTable).pad(preferredPadding).align(Align.center).row(); return customizeMenu; } /** Get the big version of the thumbnail. */ private TextureRegion getBigRegion(TextureRegion thumbnailRegion) { String name = "images/puzzles/" + puzzleNames.get(thumbnailRegion).replace("thumbnails/", ""); TextureRegion textureRegion; try { textureRegion = new TextureRegion(new Texture(name + ".jpg")); } catch (GdxRuntimeException e) { textureRegion = new TextureRegion(new Texture(name + ".png")); } return textureRegion; } /** Get the TextureRegion that the image is drawing. */ private TextureRegion getTextureRegion(Image image) { TextureRegionDrawable drawable = (TextureRegionDrawable) selectedThumbnail.getDrawable(); return drawable.getRegion(); } private float computePreferredPadding() { float preferred = Math.min(stage.getWidth(), stage.getHeight()) * 0.005f; float minimum = (float) Math.sqrt(Gdx.graphics.getDensity()) * 2f; if (preferred < minimum) preferred = minimum; return preferred; } private float computePreferredButtonWidth() { float preferred = Math.min(stage.getWidth(), stage.getHeight()) * 0.3f; float minimum = 250f; if (preferred < minimum) preferred = minimum; return preferred; } private float computePreferredImageHeight() { return computePreferredImageHeight(0.5f); } private float computePreferredImageHeight(float scale) { return Math.min(stage.getWidth(), stage.getHeight()) * scale; } /** Clear other menus. Show main menu. */ private void showMainMenu() { lastScreen = currentScreen; currentScreen = MAIN_MENU; Gdx.app.log("PhysicsPuzzleGameMenu", "Showing main menu"); onResize = new Runnable() { @Override public void run() { showMainMenu(); } }; background = setRandomBackground(getAvailableBackgrounds("dark")); Table mainMenu = createMainMenuComponents(); stage.clear(); stage.addActor(mainMenu); hideAndPauseGame = true; fillAndCenter(stage, mainMenu); } private Table createMainMenuComponents() { Gdx.app.log("PhysicsPuzzleGameMenu", "Creating main menu components"); Table buttonTable = new Table(); float buttonWidth = computePreferredButtonWidth(); if (canResumeGame()) { TextButton resumeButton = new TextButton("Resume", skin); buttonTable.add(resumeButton).width(buttonWidth).pad(computePreferredPadding()).row(); resumeButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { resumeGame(); if (physicsPuzzle.isGameOver()) showGameOverUi(); } }); } TextButton playButton = new TextButton("New Game", skin); buttonTable.add(playButton).width(buttonWidth).pad(computePreferredPadding()).row(); playButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { showImageSelectionMenu(Align.center, Color.WHITE); onResize = new Runnable() { @Override public void run() { showImageSelectionMenu(Align.center, Color.WHITE); } }; } }); TextButton settingsButton = new TextButton("Settings", skin); buttonTable.add(settingsButton).width(buttonWidth).pad(computePreferredPadding()).row(); settingsButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { showCustomizationMenu(); } }); TextButton exitButton = new TextButton("Exit", skin); buttonTable.add(exitButton).width(buttonWidth).pad(computePreferredPadding()).row(); exitButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { Gdx.app.exit(); } }); Table mainMenu = new Table(); mainMenu.add(buttonTable); return mainMenu; } /** Clear other menus. Show a scroll pane with thumbnails. Also some buttons. */ private void showImageSelectionMenu(final int align, final Color buttonColor) { lastScreen = currentScreen; currentScreen = IMAGE_MENU; Gdx.app.log("PhysicsPuzzleGameMenu", "Showing image selection menu"); selectedThumbnail = null; Table imageSelectionMenu = createImageSelectionComponents(computePreferredImageHeight(), align != Align.bottom); stage.clear(); stage.addActor(imageSelectionMenu); float relativeHeight = 0.5f; float preferredImageHeight = computePreferredImageHeight(relativeHeight); imageSelectionMenu.setWidth(stage.getWidth()); imageSelectionMenu.setHeight(stage.getHeight() * relativeHeight * 1.4f); float x = stage.getWidth() * 0.5f; float y = 0; if (align == Align.bottom) { y = preferredImageHeight * 0.5f + preferredImageHeight * 0.02f; } else if (align == Align.center) { y = stage.getHeight() * 0.5f; } imageSelectionMenu.setPosition(x, y, Align.center); addMainMenuButton(buttonColor); } /** * Fills an array with images found in the thumbnails folder. The textures * are from the texture atlas. Also stores the names of corresponding * normally sized textures in the map {@link #puzzleNames}. */ private Array<Image> preparePuzzleThumbnails(final float preferredHeight) { Gdx.app.log("PhysicsPuzzleGameMenu", "Loading puzzle thumbnails"); Array<Image> images = new Array<Image>(); puzzleNames.clear(); for (final String name : availableRegions) { if (!name.contains("thumbnails")) continue; TextureRegion textureRegion = textureAtlas.findRegion(name); // add image final Image image = new Image(textureRegion) { // implement frustum culling Rectangle cullingArea = new Rectangle(); @Override public void draw(Batch batch, float parentAlpha) { validate(); getActorBounds(this, cullingArea); // polygonBatch.frustum will be up to date as long as we are drawing backgrounds if (cullingArea.overlaps(polygonBatch.frustum)) super.draw(batch, parentAlpha); } }; images.add(image); image.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { if (image == selectedThumbnail) { stage.clear(); startGame(); // clicked on already selected thumbnail } } else selectThumbnail(image, preferredHeight); } }); // save name for later (we need it to find the normal sized image before game starts) puzzleNames.put(textureRegion, name); } return images; } /** Create the scrolling images and some buttons. */ private Table createImageSelectionComponents(float preferredImageHeight, boolean scrollBarBottom) { Gdx.app.log("PhysicsPuzzleGameMenu", "Creating image selection components"); // prepare images Table imageTable = new Table(); final Array<Image> puzzleImages = preparePuzzleThumbnails(preferredImageHeight); for (int i = 0; i < puzzleImages.size; i++) { Image image = puzzleImages.get(i); float imageWidth = image.getDrawable().getMinWidth(); float imageHeight = image.getDrawable().getMinHeight(); float height = preferredImageHeight; float width = height * imageWidth / imageHeight; float bigPad = height * 0.1f; float smallPad = height * 0.02f; float padLeft = i == 0 ? bigPad : smallPad; float padRight = i == puzzleImages.size - 1 ? bigPad : smallPad; image.addAction(Actions.sizeTo(width, height)); imageTable.add(image).width(width).height(height).align(Align.center).pad(bigPad, padLeft, bigPad, padRight); } final ScrollPane imageScroller = new ScrollPane(imageTable, skin); imageScroller.setScrollingDisabled(false, true); imageScroller.setScrollBarPositions(scrollBarBottom, true); // scroll to random position // timer.runAfterNRender(new Runnable() { @Override public void run() { Image image = puzzleImages.random(); Rectangle bounds = getActorBounds(image, new Rectangle()); imageScroller.scrollTo(bounds.x, bounds.y, bounds.width, bounds.height, true, true); } }, 2); Table imageSelectionMenu = new Table(); imageSelectionMenu.add(imageScroller); return imageSelectionMenu; } private Table createButtonTable(Color buttonColor) { Table buttonTable = new Table(); // prepare buttons float buttonWidth = computePreferredButtonWidth(); TextButton backButton = new TextButton("Main Menu", skin); backButton.setColor(buttonColor); buttonTable.add(backButton).width(buttonWidth); backButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { showMainMenu(); } }); return buttonTable; } /** Animate the image so it looks bigger. Also resize the previously selected image to normal size. */ private void selectThumbnail(Image image, float preferredHeight) { if (image == null) { Gdx.app.log("PhysicsPuzzleGameMenu", "Selecting null image"); return; } Gdx.app.log("PhysicsPuzzleGameMenu", "Selecting image " + image.getName()); // scale down previous image if (selectedThumbnail != null && selectedThumbnail != image) { float imageWidth = selectedThumbnail.getDrawable().getMinWidth(); float imageHeight = selectedThumbnail.getDrawable().getMinHeight(); float height = preferredHeight; float width = height * imageWidth / imageHeight; selectedThumbnail.addAction(Actions.sizeTo(width, height, 0.2f, Interpolation.pow2)); selectedThumbnail.addAction(Actions.moveBy(width * 0.2f, height * 0.2f, 0.2f, Interpolation.pow2)); } // scale up current image if (selectedThumbnail != image) { float imageWidth = image.getDrawable().getMinWidth(); float imageHeight = image.getDrawable().getMinHeight(); float height = computePreferredImageHeight(); float width = height * imageWidth / imageHeight; image.addAction(Actions.sizeTo(width * 1.4f, height * 1.4f, 0.2f, Interpolation.pow2)); image.addAction(Actions.moveBy(-width * 0.2f, -height * 0.2f, 0.2f, Interpolation.pow2)); image.toFront(); } selectedThumbnail = image; } @Override public void render() { Gdx.gl20.glClearColor(0, 0, 0, 1); Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT); polygonBatch.begin(backgroundCamera); background.draw(polygonBatch); polygonBatch.end(); if (!hideAndPauseGame && physicsPuzzle != null) { physicsPuzzle.render(); } stage.act(); stage.draw(); timer.step(); } @Override public void dispose() { Gdx.app.log("PhysicsPuzzleGameMenu", "Disposing resources"); if (skin != null) skin.dispose(); if (stage != null) stage.dispose(); if (physicsPuzzle != null) physicsPuzzle.dispose(); if (polygonBatch != null) polygonBatch.dispose(); if (textureAtlas != null) textureAtlas.dispose(); if (preferences != null) preferences.flush(); if (currentPuzzleTextureRegion != null) currentPuzzleTextureRegion.getTexture().dispose(); skin = null; stage = null; physicsPuzzle = null; polygonBatch = null; textureAtlas = null; preferences = null; currentPuzzleTextureRegion = null; timer = null; Gdx.app.log("PhysicsPuzzleGameMenu", "Finished disposing resources"); } @Override public void resize(int width, int height) { Gdx.app.log("PhysicsPuzzleGameMenu", "Resizing to width=" + width + ", height = " + height + "."); if (stage != null) stage.getViewport().update(width, height, true); if (onResize != null) onResize.run(); if (physicsPuzzle != null) physicsPuzzle.resize(width, height); if (background != null) updateBackgroundBounds(background); } }