Java tutorial
/** * Copyright 2015 StrongJoshua (strongjoshua@hotmail.com) * * 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.strongjoshua.console; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.TextField; import com.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldListener; import com.badlogic.gdx.scenes.scene2d.ui.Window; import com.badlogic.gdx.utils.Array; /** A simple console that allows live logging, and live execution of methods, from within an application. Please see the <a * href="https://github.com/StrongJoshua/libgdx-inGameConsole">GitHub Repository</a> for more information. * * @author StrongJoshua */ public class GUIConsole extends AbstractConsole { private int keyID = Input.Keys.GRAVE; private ConsoleDisplay display; private boolean hidden = true; private boolean usesMultiplexer = false; private InputProcessor appInput; private InputMultiplexer multiplexer; private Stage stage; private CommandHistory commandHistory; private CommandCompleter commandCompleter; private Window consoleWindow; /** Creates the console using the default skin.<br> * <b>***IMPORTANT***</b> Call {@link Console#dispose()} to make your {@link InputProcessor} the default processor again (this * console uses a multiplexer to circumvent it). * @see Console#dispose() */ public GUIConsole() { this(new Skin(Gdx.files.classpath("default_skin/uiskin.json"))); } /** Creates the console.<br> * <b>***IMPORTANT***</b> Call {@link Console#dispose()} to make your {@link InputProcessor} the default processor again (this * console uses a multiplexer to circumvent it). * @param skin Uses skins for Label, TextField, and Table. Skin <b>must</b> contain a font called 'default-font'. * @see Console#dispose() */ public GUIConsole(Skin skin) { this(skin, true); } /** Creates the console.<br> * <b>***IMPORTANT***</b> Call {@link Console#dispose()} to make your {@link InputProcessor} the default processor again (this * console uses a multiplexer to circumvent it). * @param useMultiplexer If internal multiplexer should be used * @see Console#dispose() */ public GUIConsole(boolean useMultiplexer) { this(new Skin(Gdx.files.classpath("default_skin/uiskin.json")), useMultiplexer); } /** Creates the console.<br> * <b>***IMPORTANT***</b> Call {@link Console#dispose()} to make your {@link InputProcessor} the default processor again (this * console uses a multiplexer to circumvent it). * @param skin Uses skins for Label, TextField, and Table. Skin <b>must</b> contain a font called 'default-font'. * @param useMultiplexer If internal multiplexer should be used * @see Console#dispose() */ public GUIConsole(Skin skin, boolean useMultiplexer) { stage = new Stage(); display = new ConsoleDisplay(skin); commandHistory = new CommandHistory(); commandCompleter = new CommandCompleter(); logToSystem = false; usesMultiplexer = useMultiplexer; if (useMultiplexer) { resetInputProcessing(); } display.pad(4); display.padTop(22); display.setFillParent(true); consoleWindow = new Window("Console", skin); consoleWindow.setMovable(true); consoleWindow.setResizable(true); consoleWindow.setKeepWithinStage(true); consoleWindow.addActor(display); consoleWindow.setTouchable(Touchable.disabled); stage.addActor(consoleWindow); stage.setKeyboardFocus(display); setSizePercent(50, 50); setPositionPercent(50, 50); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#setMaxEntries(int) */ @Override public void setMaxEntries(int numEntries) { if (numEntries > 0 || numEntries == UNLIMITED_ENTRIES) { log.setMaxEntries(numEntries); } else { throw new IllegalArgumentException( "Maximum entries must be greater than 0 or use Console.UNLIMITED_ENTRIES."); } } /* (non-Javadoc) * @see com.strongjoshua.console.Console#clear() */ @Override public void clear() { log.getLogEntries().clear(); display.refresh(); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#setSize(int, int) */ @Override public void setSize(int width, int height) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("Pixel size must be greater than 0."); } consoleWindow.setSize(width, height); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#setSizePercent(float, float) */ @Override public void setSizePercent(float wPct, float hPct) { if (wPct <= 0 || hPct <= 0) { throw new IllegalArgumentException("Size percentage must be greater than 0."); } if (wPct > 100 || hPct > 100) { throw new IllegalArgumentException("Size percentage cannot be greater than 100."); } float w = Gdx.graphics.getWidth(), h = Gdx.graphics.getHeight(); consoleWindow.setSize(w * wPct / 100.0f, h * hPct / 100.0f); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#setPosition(int, int) */ @Override public void setPosition(int x, int y) { consoleWindow.setPosition(x, y); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#setPositionPercent(float, float) */ @Override public void setPositionPercent(float xPosPct, float yPosPct) { if (xPosPct > 100 || yPosPct > 100) { throw new IllegalArgumentException("Error: The console would be drawn outside of the screen."); } float w = Gdx.graphics.getWidth(), h = Gdx.graphics.getHeight(); consoleWindow.setPosition(w * xPosPct / 100.0f, h * yPosPct / 100.0f); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#resetInputProcessing() */ @Override public void resetInputProcessing() { usesMultiplexer = true; appInput = Gdx.input.getInputProcessor(); if (appInput != null) { if (hasStage(appInput)) { log("Console already added to input processor!", LogLevel.ERROR); Gdx.app.log("Console", "Already added to input processor!"); return; } multiplexer = new InputMultiplexer(); multiplexer.addProcessor(stage); multiplexer.addProcessor(appInput); Gdx.input.setInputProcessor(multiplexer); } else { Gdx.input.setInputProcessor(stage); } } /** Compares the given processor to the console's stage. If given a multiplexer, it is iterated through recursively to check all * of the multiplexer's processors for comparison. * @param processor * @return processor == this.stage */ private boolean hasStage(InputProcessor processor) { if (!(processor instanceof InputMultiplexer)) { return processor == stage; } InputMultiplexer im = (InputMultiplexer) processor; Array<InputProcessor> ips = im.getProcessors(); for (InputProcessor ip : ips) { if (hasStage(ip)) { return true; } } return false; } /* (non-Javadoc) * @see com.strongjoshua.console.Console#getInputProcessor() */ @Override public InputProcessor getInputProcessor() { return stage; } /* (non-Javadoc) * @see com.strongjoshua.console.Console#draw() */ @Override public void draw() { if (disabled) { return; } stage.act(); if (hidden) { return; } stage.draw(); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#refresh() */ @Override public void refresh() { this.refresh(true); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#refresh(boolean) */ @Override public void refresh(boolean retain) { float oldWPct = 0, oldHPct = 0, oldXPosPct = 0, oldYPosPct = 0; if (retain) { oldWPct = consoleWindow.getWidth() / stage.getWidth() * 100; oldHPct = consoleWindow.getHeight() / stage.getHeight() * 100; oldXPosPct = consoleWindow.getX() / stage.getWidth() * 100; oldYPosPct = consoleWindow.getY() / stage.getHeight() * 100; } int width = Gdx.graphics.getWidth(), height = Gdx.graphics.getHeight(); stage.getViewport().setWorldSize(width, height); stage.getViewport().update(width, height, true); if (retain) { this.setSizePercent(oldWPct, oldHPct); this.setPositionPercent(oldXPosPct, oldYPosPct); } } /* (non-Javadoc) * @see com.strongjoshua.console.Console#log(java.lang.String, com.strongjoshua.console.GUIConsole.LogLevel) */ @Override public void log(String msg, LogLevel level) { super.log(msg, level); display.refresh(); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#setDisabled(boolean) */ @Override public void setDisabled(boolean disabled) { if (disabled && !hidden) { ((KeyListener) display.getListeners().get(0)).keyDown(null, keyID); } this.disabled = disabled; } /* (non-Javadoc) * @see com.strongjoshua.console.Console#getKeyID() */ @Override public int getKeyID() { return keyID; } /* (non-Javadoc) * @see com.strongjoshua.console.Console#setKeyID(int) */ @Override public void setKeyID(int code) { if (code == Keys.ENTER) { return; } keyID = code; } private Vector3 stageCoords = new Vector3(); /* (non-Javadoc) * @see com.strongjoshua.console.Console#hitsConsole(float, float) */ @Override public boolean hitsConsole(float screenX, float screenY) { if (disabled || hidden) { return false; } stage.getCamera().unproject(stageCoords.set(screenX, screenY, 0)); return stage.hit(stageCoords.x, stageCoords.y, true) != null; } private class ConsoleDisplay extends Table { private Table logEntries; private TextField input; private Skin skin; private Array<Label> labels; protected ConsoleDisplay(Skin skin) { super(skin); this.setFillParent(false); this.skin = skin; labels = new Array<Label>(); logEntries = new Table(skin); input = new TextField("", skin); input.setTextFieldListener(new FieldListener()); scroll = new ScrollPane(logEntries, skin); scroll.setFadeScrollBars(false); scroll.setScrollbarsOnTop(false); scroll.setOverscroll(false, false); this.add(scroll).expand().fill().pad(4).row(); this.add(input).expandX().fillX().pad(4); this.addListener(new KeyListener(input)); } protected void refresh() { Array<LogEntry> entries = log.getLogEntries(); logEntries.clear(); // expand first so labels start at the bottom logEntries.add().expand().fill().row(); int size = entries.size; for (int i = 0; i < size; i++) { LogEntry le = entries.get(i); Label l; // recycle the labels so we don't create new ones every refresh if (labels.size > i) { l = labels.get(i); } else { l = new Label("", skin, "default-font", LogLevel.DEFAULT.getColor()); l.setWrap(true); labels.add(l); } l.setText(le.toConsoleString()); l.setColor(le.getColor()); logEntries.add(l).expandX().fillX().top().left().padLeft(4).row(); } scroll.validate(); scroll.setScrollPercentY(1); } } private ScrollPane scroll; private class FieldListener implements TextFieldListener { @Override public void keyTyped(TextField textField, char c) { if (("" + c).equalsIgnoreCase(Keys.toString(keyID))) { String s = textField.getText(); textField.setText(s.substring(0, s.length() - 1)); } } } private class KeyListener extends InputListener { private TextField input; protected KeyListener(TextField tf) { input = tf; } @Override public boolean keyDown(InputEvent event, int keycode) { if (disabled) { return false; } // reset command completer because input string may have changed if (keycode != Keys.TAB) { commandCompleter.reset(); } if (keycode == Keys.ENTER && !hidden) { String s = input.getText(); if (s.length() == 0 || s.equals("") || s.split(" ").length == 0) { return false; } if (exec != null) { commandHistory.store(s); execCommand(s); } else { log("No command executor has been set. Please call setCommandExecutor for this console in your code and restart.", LogLevel.ERROR); } input.setText(""); return true; } else if (keycode == Keys.UP && !hidden) { input.setText(commandHistory.getPreviousCommand()); input.setCursorPosition(input.getText().length()); return true; } else if (keycode == Keys.DOWN && !hidden) { input.setText(commandHistory.getNextCommand()); input.setCursorPosition(input.getText().length()); return true; } else if (keycode == Keys.TAB && !hidden) { String s = input.getText(); if (s.length() == 0) { return false; } if (commandCompleter.isNew()) { commandCompleter.set(exec, s); } input.setText(commandCompleter.next()); input.setCursorPosition(input.getText().length()); return true; } else if (keycode == keyID) { hidden = !hidden; if (hidden) { input.setText(""); stage.setKeyboardFocus(display); consoleWindow.setTouchable(Touchable.disabled); } else { stage.setKeyboardFocus(input); consoleWindow.setTouchable(Touchable.childrenOnly); } return true; } return false; } } /* (non-Javadoc) * @see com.strongjoshua.console.Console#dispose() */ @Override public void dispose() { if (usesMultiplexer && appInput != null) { Gdx.input.setInputProcessor(appInput); } stage.dispose(); } /* (non-Javadoc) * @see com.strongjoshua.console.Console#isHidden() */ @Override public boolean isHidden() { return hidden; } }