package squidpony.gdx.tests; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Files; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.*; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.viewport.StretchViewport; import squidpony.FakeLanguageGen; import squidpony.squidai.DijkstraMap; import squidpony.squidgrid.gui.gdx.*; import squidpony.squidgrid.mapping.DungeonGenerator; import squidpony.squidgrid.mapping.DungeonUtility; import squidpony.squidmath.Coord; import squidpony.squidmath.CoordPacker; import squidpony.squidmath.RNG; import java.util.ArrayList; import java.util.Arrays; /** * Trying to test how SplitPane s from libGDX can be used to differentiate parts of the UI. Not currently working. * Created by Tommy Ettinger on 12/29/2016. */ public class SplitDemo extends ApplicationAdapter { private Stage stage; private SpriteBatch batch; private RNG rng; private Skin skin; private SquidLayers display; private TextPanel<Color> messages; private VerticalGroup images; private TextureAtlas atlas; private Array<TextureAtlas.AtlasRegion> regions; private SplitPane innerSplit, outerSplit; private DungeonGenerator dungeonGen; private char[][] decoDungeon, bareDungeon, lineDungeon; private int[][] colorIndices, bgColorIndices; /** In number of cells */ private int gridWidth; /** In number of cells */ private int gridHeight; /** The pixel width of a cell */ private int cellWidth; /** The pixel height of a cell */ private int cellHeight; private SquidInput input; private Color bgColor; private DijkstraMap playerToCursor; private Coord cursor, player; private ArrayList<Coord> toCursor; private ArrayList<Coord> awaitedMoves; private float secondsWithoutMoves; private String[] lang; private int langIndex = 0; @Override public void create () { //These variables, corresponding to the screen's width and height in cells and a cell's width and height in //pixels, must match the size you specified in the launcher for input to behave. //This is one of the more common places a mistake can happen. //In our desktop launcher, we gave these arguments to the configuration: // config.width = 80 * 8; // config.height = 40 * 18; //Here, config.height refers to the total number of rows to be displayed on the screen. //We're displaying 32 rows of dungeon, then 8 more rows of text generation to show some tricks with language. //gridHeight is 32 because that variable will be used for generating the dungeon and handling movement within //the upper 32 rows. Anything that refers to the full height, which happens rarely and usually for things like //screen resizes, just uses gridHeight + 8. Next to it is gridWidth, which is 80 because we want 80 grid spaces //across the whole screen. cellWidth and cellHeight are 8 and 18, and match the multipliers for config.width and //config.height, but in this case don't strictly need to because we soon use a "Stretchable" font. While //gridWidth and gridHeight are measured in spaces on the grid, cellWidth and cellHeight are the pixel dimensions //of an individual cell. The font will look more crisp if the cell dimensions match the config multipliers //exactly, and the stretchable fonts (technically, distance field fonts) can resize to non-square sizes and //still retain most of that crispness. gridWidth = 80; gridHeight = 24; cellWidth = 14; cellHeight = 21; // gotta have a random number generator. We can seed an RNG with any long we want, or even a String. rng = new RNG("SquidLib!"); atlas = DefaultResources.getIconAtlas(); regions = atlas.getRegions(); //Some classes in SquidLib need access to a batch to render certain things, so it's a good idea to have one. batch = new SpriteBatch(); //Here we make sure our Stage, which holds any text-based grids we make, uses our Batch. stage = new Stage(new StretchViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()), batch); display = new SquidLayers(gridWidth, gridHeight, cellWidth, cellHeight, DefaultResources.getStretchableCodeFont()); /* default-vertical: { handle: default-splitpane-vertical }, default-horizontal: { handle: default-splitpane } */ skin = new Skin(atlas); SplitPane.SplitPaneStyle splitStyleV = new SplitPane.SplitPaneStyle(skin.getDrawable("default-splitpane-vertical")), splitStyleH = new SplitPane.SplitPaneStyle(skin.getDrawable("default-splitpane")); skin.add("default-vertical", splitStyleV); skin.add("default-horizontal", splitStyleH); List.ListStyle listStyle = new List.ListStyle(DefaultResources.getIncludedFont(), Color.PURPLE, Color.WHITE, skin.getDrawable("selection")); skin.add("default", listStyle); images = new VerticalGroup(); images.addActor(new Image(regions.get(rng.nextIntHasty(regions.size)))); images.addActor(new Image(regions.get(rng.nextIntHasty(regions.size)))); images.addActor(new Image(regions.get(rng.nextIntHasty(regions.size)))); images.addActor(new Image(regions.get(rng.nextIntHasty(regions.size)))); images.addActor(new Image(regions.get(rng.nextIntHasty(regions.size)))); images.addActor(new Image(regions.get(rng.nextIntHasty(regions.size)))); images.setSize(128, 250); messages = new TextPanel<Color>(null, DefaultResources.getStretchablePrintFont()); // a bit of a hack to increase the text height slightly without changing the size of the cells they're in. // this causes a tiny bit of overlap between cells, which gets rid of an annoying gap between vertical lines. // if you use '#' for walls instead of box drawing chars, you don't need this. display.setTextSize(cellWidth, cellHeight + 1); // this makes animations rather slow, which is good for attack animations. display.setAnimationDuration(0.1f); //This uses the seeded RNG we made earlier to build a procedural dungeon using a method that takes rectangular //sections of pre-drawn dungeon and drops them into place in a tiling pattern. It makes good "ruined" dungeons. dungeonGen = new DungeonGenerator(gridWidth, gridHeight, rng); //uncomment this next line to randomly add water to the dungeon in pools. //dungeonGen.addWater(15); //decoDungeon is given the dungeon with any decorations we specified. (Here, we didn't, unless you chose to add //water to the dungeon. In that case, decoDungeon will have different contents than bareDungeon, next.) decoDungeon = dungeonGen.generate(); //getBareDungeon provides the simplest representation of the generated dungeon -- '#' for walls, '.' for floors. bareDungeon = dungeonGen.getBareDungeon(); //When we draw, we may want to use a nicer representation of walls. DungeonUtility has lots of useful methods //for modifying char[][] dungeon grids, and this one takes each '#' and replaces it with a box-drawing character. lineDungeon = DungeonUtility.hashesToLines(decoDungeon); // it's more efficient to get random floors from a packed set containing only (compressed) floor positions. short[] placement = CoordPacker.pack(bareDungeon, '.'); //Coord is the type we use as a general 2D point, usually in a dungeon. //Because we know dungeons won't be incredibly huge, Coord performs best for x and y values less than 256. cursor = Coord.get(-1, -1); //player is, here, just a Coord that stores his position. In a real game, you would probably have a class for //creatures, and possibly a subclass for the player. player = dungeonGen.utility.randomCell(placement); //This is used to allow clicks or taps to take the player to the desired area. toCursor = new ArrayList<>(100); awaitedMoves = new ArrayList<>(100); //DijkstraMap is the pathfinding swiss-army knife we use here to find a path to the latest cursor position. playerToCursor = new DijkstraMap(decoDungeon, DijkstraMap.Measurement.MANHATTAN); bgColor = SColor.DARK_SLATE_GRAY; colorIndices = DungeonUtility.generatePaletteIndices(decoDungeon); bgColorIndices = DungeonUtility.generateBGPaletteIndices(decoDungeon); // this creates an array of sentences, where each imitates a different sort of language or mix of languages. // this serves to demonstrate the large amount of glyphs SquidLib supports. lang = new String[] { FakeLanguageGen.ENGLISH.sentence(5, 10, new String[]{",", ",", ",", ";"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.17, gridWidth - 4), FakeLanguageGen.GREEK_AUTHENTIC.sentence(5, 11, new String[]{",", ",", ";"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.2, gridWidth - 4), FakeLanguageGen.GREEK_ROMANIZED.sentence(5, 11, new String[]{",", ",", ";"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.2, gridWidth - 4), FakeLanguageGen.LOVECRAFT.sentence(3, 9, new String[]{",", ",", ";"}, new String[]{".", ".", "!", "!", "?", "...", "..."}, 0.15, gridWidth - 4), FakeLanguageGen.FRENCH.sentence(4, 12, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.17, gridWidth - 4), FakeLanguageGen.RUSSIAN_AUTHENTIC.sentence(6, 13, new String[]{",", ",", ",", ",", ";", " -"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.25, gridWidth - 4), FakeLanguageGen.RUSSIAN_ROMANIZED.sentence(6, 13, new String[]{",", ",", ",", ",", ";", " -"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.25, gridWidth - 4), FakeLanguageGen.JAPANESE_ROMANIZED.sentence(5, 13, new String[]{",", ",", ",", ",", ";"}, new String[]{".", ".", ".", "!", "?", "...", "..."}, 0.12, gridWidth - 4), FakeLanguageGen.SWAHILI.sentence(4, 9, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?"}, 0.12, gridWidth - 4), FakeLanguageGen.SOMALI.sentence(4, 9, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?"}, 0.12, gridWidth - 4), FakeLanguageGen.HINDI_ROMANIZED.sentence(4, 9, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?"}, 0.12, gridWidth - 4), FakeLanguageGen.NORSE.sentence(4, 9, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?"}, 0.12, gridWidth - 4), FakeLanguageGen.INUKTITUT.sentence(4, 9, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?"}, 0.12, gridWidth - 4), FakeLanguageGen.NAHUATL.sentence(4, 9, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?"}, 0.12, gridWidth - 4), FakeLanguageGen.FANTASY_NAME.sentence(4, 8, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.22, gridWidth - 4), FakeLanguageGen.FANCY_FANTASY_NAME.sentence(4, 8, new String[]{",", ",", ",", ";", ";"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.22, gridWidth - 4), FakeLanguageGen.FRENCH.mix(FakeLanguageGen.JAPANESE_ROMANIZED, 0.65).sentence(5, 9, new String[]{",", ",", ",", ";"}, new String[]{".", ".", ".", "!", "?", "?", "..."}, 0.14, gridWidth - 4), FakeLanguageGen.ENGLISH.addAccents(0.5, 0.15).sentence(5, 10, new String[]{",", ",", ",", ";"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.17, gridWidth - 4), FakeLanguageGen.SWAHILI.mix(FakeLanguageGen.JAPANESE_ROMANIZED, 0.5).mix(FakeLanguageGen.FRENCH, 0.35) .mix(FakeLanguageGen.RUSSIAN_ROMANIZED, 0.25).mix(FakeLanguageGen.GREEK_ROMANIZED, 0.2).mix(FakeLanguageGen.ENGLISH, 0.15) .mix(FakeLanguageGen.FANCY_FANTASY_NAME, 0.12).mix(FakeLanguageGen.LOVECRAFT, 0.1) .sentence(5, 10, new String[]{",", ",", ",", ";"}, new String[]{".", ".", ".", "!", "?", "..."}, 0.2, gridWidth - 4), }; messages.init(1000, 400, Color.WHITE, lang[(langIndex) % lang.length], lang[(langIndex + 1) % lang.length], lang[(langIndex + 2) % lang.length], lang[(langIndex + 3) % lang.length], lang[(langIndex + 4) % lang.length], lang[(langIndex + 5) % lang.length]); // this is a big one. // SquidInput can be constructed with a KeyHandler (which just processes specific keypresses), a SquidMouse // (which is given an InputProcessor implementation and can handle multiple kinds of mouse move), or both. // keyHandler is meant to be able to handle complex, modified key input, typically for games that distinguish // between, say, 'q' and 'Q' for 'quaff' and 'Quip' or whatever obtuse combination you choose. The // implementation here handles hjkl keys (also called vi-keys), numpad, arrow keys, and wasd for 4-way movement. // Shifted letter keys produce capitalized chars when passed to KeyHandler.handle(), but we don't care about // that so we just use two case statements with the same body, i.e. one for 'A' and one for 'a'. // You can also set up a series of future moves by clicking within FOV range, using mouseMoved to determine the // path to the mouse position with a DijkstraMap (called playerToCursor), and using touchUp to actually trigger // the event when someone clicks. input = new SquidInput(new SquidInput.KeyHandler() { @Override public void handle(char key, boolean alt, boolean ctrl, boolean shift) { switch (key) { case SquidInput.UP_ARROW: case 'k': case 'w': case 'K': case 'W': { //-1 is up on the screen move(0, -1); break; } case SquidInput.DOWN_ARROW: case 'j': case 's': case 'J': case 'S': { //+1 is down on the screen move(0, 1); break; } case SquidInput.LEFT_ARROW: case 'h': case 'a': case 'H': case 'A': { move(-1, 0); break; } case SquidInput.RIGHT_ARROW: case 'l': case 'd': case 'L': case 'D': { move(1, 0); break; } case 'Q': case 'q': case SquidInput.ESCAPE: { Gdx.app.exit(); break; } } } });//, /* //The second parameter passed to a SquidInput can be a SquidMouse, which takes mouse or touchscreen //input and converts it to grid coordinates (here, a cell is 12 wide and 24 tall, so clicking at the // pixel position 15,51 will pass screenX as 1 (since if you divide 15 by 12 and round down you get 1), // and screenY as 2 (since 51 divided by 24 rounded down is 2)). new SquidMouse(cellWidth, cellHeight, gridWidth, gridHeight, 0, 0, new InputAdapter() { // if the user clicks and there are no awaitedMoves queued up, generate toCursor if it // hasn't been generated already by mouseMoved, then copy it over to awaitedMoves. @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { if(awaitedMoves.isEmpty()) { if (toCursor.isEmpty()) { cursor = Coord.get(screenX, screenY); //This uses DijkstraMap.findPath to get a possibly long path from the current player position //to the position the user clicked on. toCursor = playerToCursor.findPath(100, null, null, player, cursor); } awaitedMoves = new ArrayList<>(toCursor); } return false; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { return mouseMoved(screenX, screenY); } // causes the path to the mouse position to become highlighted (toCursor contains a list of points that // receive highlighting). Uses DijkstraMap.findPath() to find the path, which is surprisingly fast. @Override public boolean mouseMoved(int screenX, int screenY) { if(!awaitedMoves.isEmpty()) return false; if(cursor.x == screenX && cursor.y == screenY) { return false; } cursor = Coord.get(screenX, screenY); toCursor = playerToCursor.findPath(100, null, null, player, cursor); return false; } })); */ // innerSplit = new SplitPane(new StretchContainer(display), new StretchContainer(images), false, skin); // // outerSplit = new SplitPane(innerSplit, new StretchContainer(messages.getScrollPane()), true, skin); innerSplit = new SplitPane(display, images, false, skin); outerSplit = new SplitPane(innerSplit, messages.getScrollPane(), true, skin); //Setting the InputProcessor is ABSOLUTELY NEEDED TO HANDLE INPUT Gdx.input.setInputProcessor(new InputMultiplexer(stage, input)); //You might be able to get by with the next line instead of the above line, but the former is preferred. //Gdx.input.setInputProcessor(input); // and then add display, our one visual component, to the list of things that act in Stage. stage.addActor(outerSplit); } /** * Move the player if he isn't bumping into a wall or trying to go off the map somehow. * In a fully-fledged game, this would not be organized like this, but this is a one-file demo. * @param xmod * @param ymod */ private void move(int xmod, int ymod) { int newX = player.x + xmod, newY = player.y + ymod; if (newX >= 0 && newY >= 0 && newX < gridWidth && newY < gridHeight && bareDungeon[newX][newY] != '#') { player = player.translate(xmod, ymod); } // loops through the text snippets displayed whenever the player moves langIndex = (langIndex + 1) % lang.length; } /** * Draws the map, applies any highlighting for the path to the cursor, and then draws the player. */ public void putMap() { for (int i = 0; i < gridWidth; i++) { for (int j = 0; j < gridHeight; j++) { display.put(i, j, lineDungeon[i][j], colorIndices[i][j], bgColorIndices[i][j], 40); } } for (Coord pt : toCursor) { // use a brighter light to trace the path to the cursor, from 170 max lightness to 0 min. display.highlight(pt.x, pt.y, 100); } //places the player as an '@' at his position in orange (6 is an index into SColor.LIMITED_PALETTE). display.put(player.x, player.y, '@', 6); //this helps compatibility with the HTML target, which doesn't support String.format() char[] spaceArray = new char[gridWidth]; Arrays.fill(spaceArray, ' '); String spaces = String.valueOf(spaceArray); messages.init(1000, 400, Color.WHITE, lang[(langIndex) % lang.length], lang[(langIndex + 1) % lang.length], lang[(langIndex + 2) % lang.length], lang[(langIndex + 3) % lang.length], lang[(langIndex + 4) % lang.length], lang[(langIndex + 5) % lang.length]); } @Override public void render () { // standard clear the background routine for libGDX Gdx.gl.glClearColor(bgColor.r / 255.0f, bgColor.g / 255.0f, bgColor.b / 255.0f, 1.0f); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // need to display the map every frame, since we clear the screen to avoid artifacts. putMap(); // if the user clicked, we have a list of moves to perform. if(!awaitedMoves.isEmpty()) { // this doesn't check for input, but instead processes and removes Points from awaitedMoves. secondsWithoutMoves += Gdx.graphics.getDeltaTime(); if (secondsWithoutMoves >= 0.1) { secondsWithoutMoves = 0; Coord m = awaitedMoves.remove(0); toCursor.remove(0); move(m.x - player.x, m.y - player.y); } } // if we are waiting for the player's input and get input, process it. else if(input.hasNext()) { input.next(); } // stage has its own batch and must be explicitly told to draw(). stage.draw(); stage.act(); } @Override public void resize(int width, int height) { super.resize(width, height); //very important to have the mouse behave correctly if the user fullscreens or resizes the game! input.getMouse().reinitialize((float) width / this.gridWidth, (float)height / (this.gridHeight), this.gridWidth, this.gridHeight, 0, 0); } public static void main (String[] arg) { LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.title = "SquidLib Test: Split"; config.width = 1000; config.height = 650; config.addIcon("Tentacle-16.png", Files.FileType.Internal); config.addIcon("Tentacle-32.png", Files.FileType.Internal); config.addIcon("Tentacle-128.png", Files.FileType.Internal); new LwjglApplication(new SplitDemo(), config); } }