001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.Gdx; 004import com.badlogic.gdx.Input; 005import com.badlogic.gdx.InputAdapter; 006import com.badlogic.gdx.graphics.Color; 007import com.badlogic.gdx.math.MathUtils; 008import com.badlogic.gdx.scenes.scene2d.Stage; 009import com.badlogic.gdx.utils.CharArray; 010import com.badlogic.gdx.utils.viewport.StretchViewport; 011 012import java.util.Map; 013import java.util.TreeMap; 014 015/** 016 * Acts like SquidInput but displays available keys as visible buttons if the device has no physical keyboard. 017 * Only relevant if you want keyboard input to always be available, so this doesn't implement the SquidInput 018 * constructors that don't handle any keys (the buttons this shows will act like keys even on Android and iOS). 019 * <br> 020 * This doesn't currently work well when used with a SquidPanel or SquidLayers that pans a camera over an area 021 * that is larger than one screen (you only need to worry about this in "standard" SquidLib usage if you use 022 * the {@link SquidPanel#SquidPanel(int, int, TextCellFactory, squidpony.IColorCenter, float, float, char[][])}) or 023 * {@link SquidLayers#SquidLayers(int, int, int, int, TextCellFactory, SquidColorCenter, SquidColorCenter, char[][])} 024 * constructors with a larger 2D char array than the given gridWidth and/or gridHeight). It works fine if the 025 * area that can be seen on one screen is as big as it gets, such as in the normal text-based mode of games like 026 * Rogue and Brogue. It can also work if you manually handle movement over a larger area without using libGDX 027 * Viewport and Camera code to accomplish that, since this class needs to handle the Viewport to some extent. 028 * <br> 029 * Created by Tommy Ettinger on 4/15/2016. 030 */ 031public class VisualInput extends SquidInput { 032 033 public SquidPanel left, right; 034 protected TextCellFactory tcfLeft, tcfRight; 035 protected TreeMap<Character, String> availableKeys; 036 protected SquidMouse mouseLeft, mouseRight; 037 private int sectionWidth, sectionHeight; 038 private float screenWidth = -1, screenHeight = -1; 039 protected boolean initialized; 040 public Color color = Color.WHITE; 041 protected CharArray clicks; 042 public boolean eightWay = true, forceButtons; 043 public Stage stage; 044 protected ShrinkPartViewport spv; 045 private void ready(TextCellFactory font) 046 { 047 initialized = true; 048 sectionWidth = Gdx.graphics.getWidth() / 8; 049 sectionHeight = Gdx.graphics.getHeight(); 050 051 tcfLeft = font.copy().width(sectionWidth / 4).height(sectionHeight / 16).initBySize(); 052 tcfRight = font.copy().width(sectionWidth / 12).height(sectionHeight / 24).initBySize(); 053 054 left = new SquidPanel(4, 16, tcfLeft); 055 if(eightWay) { 056 left.put(0, 7, new char[][]{ 057 new char[]{'\\', '-', '/'}, 058 new char[]{'|', 'O', '|'}, 059 new char[]{'/', '-', '\\'}, 060 }, color); 061 } 062 else 063 { 064 left.put(0, 7, new char[][]{ 065 new char[]{' ', SquidInput.LEFT_ARROW, ' '}, 066 new char[]{SquidInput.UP_ARROW, 'O', SquidInput.DOWN_ARROW}, 067 new char[]{' ', SquidInput.RIGHT_ARROW, ' '}, 068 }, color); } 069 right = new SquidPanel(12, 24, tcfRight, null, Gdx.graphics.getWidth() - sectionWidth, 0); 070 071 mouseLeft = new SquidMouse(left.cellWidth(), left.cellHeight(), left.gridWidth, left.gridHeight, 072 0, 0, new InputAdapter() 073 { 074 @Override 075 public boolean touchUp(int screenX, int screenY, int pointer, int button) { 076 if(screenX < 3 && screenY >= 5 && screenY <= 7 && left.contents[screenX][screenY + 2] != 0 && 077 left.contents[screenX][screenY + 2] != ' ') 078 { 079 switch ((screenY - 5) * 3 + screenX) 080 { 081 case 0: queue.add(SquidInput.UP_LEFT_ARROW); 082 break; 083 case 1: queue.add(SquidInput.UP_ARROW); 084 break; 085 case 2: queue.add(SquidInput.UP_RIGHT_ARROW); 086 break; 087 case 3: queue.add(SquidInput.LEFT_ARROW); 088 break; 089 case 4: queue.add(SquidInput.CENTER_ARROW); 090 break; 091 case 5: queue.add(SquidInput.RIGHT_ARROW); 092 break; 093 case 6: queue.add(SquidInput.DOWN_LEFT_ARROW); 094 break; 095 case 7: queue.add(SquidInput.DOWN_ARROW); 096 break; 097 case 8: queue.add(SquidInput.DOWN_RIGHT_ARROW); 098 break; 099 default: 100 return false; 101 } 102 queue.add('\u0000'); 103 return true; 104 } 105 else 106 return false; 107 } 108 }); 109 //if(mouse != null) 110 // mouse.setOffsetX(-sectionWidth); 111 stage = new Stage(new StretchViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight())); 112 stage.addActor(left); 113 stage.addActor(right); 114 } 115 private void fillActions() 116 { 117 if(!initialized) 118 return; 119 int y = 0; 120 clicks = new CharArray(right.getGridHeight()); 121 for(Map.Entry<Character, String> kv : availableKeys.entrySet()) 122 { 123 switch (kv.getKey()) 124 { 125 case SquidInput.UP_LEFT_ARROW: 126 case SquidInput.UP_ARROW: 127 case SquidInput.UP_RIGHT_ARROW: 128 case SquidInput.LEFT_ARROW: 129 case SquidInput.CENTER_ARROW: 130 case SquidInput.RIGHT_ARROW: 131 case SquidInput.DOWN_LEFT_ARROW: 132 case SquidInput.DOWN_ARROW: 133 case SquidInput.DOWN_RIGHT_ARROW: 134 break; 135 default: 136 right.put(1, y, kv.getValue(), color); 137 clicks.add(kv.getKey()); 138 y++; 139 } 140 if(y > right.getGridHeight()) 141 break; 142 } 143 mouseRight = new SquidMouse(right.cellWidth(), right.cellHeight(), right.gridWidth(), right.gridHeight(), 144 Gdx.graphics.getWidth() - sectionWidth, Math.round(sectionHeight - right.getHeight()), 145 new InputAdapter() 146 { 147 @Override 148 public boolean touchUp(int screenX, int screenY, int pointer, int button) { 149 if(screenY < clicks.size) 150 { 151 queue.add(clicks.get(screenY)); 152 queue.add('\u0000'); 153 return true; 154 } 155 return false; 156 } 157 }); 158 } 159 /** 160 * Convenience method that does essentially the same thing as init(Map<Character, String>). 161 * This assumes that each String passed to this is an action, and the first letter of a String is the character that 162 * a keypress would generate to perform the action named by the String. For example, calling this with 163 * {@code init("fire", "Fortify")} would register "fire" under 'f' and "Fortify" under 'F', displaying the Strings 164 * instead of the characters if possible. The first char of each String should be unique in the arguments. 165 * <br> 166 * This also initializes the displayed buttons if there is no hardware keyboard available. This uses the color and 167 * eightWay fields to determine what color the buttons will be drawn with and if directions should be 8-way or 168 * 4-way, respectively. These fields should be set before calling init() if you don't want the defaults, which are 169 * white buttons and 8-way directions. The font with this overload of init is set to Inconsolata-LGC-Custom, which 170 * is normally available via {@link DefaultResources#getStretchableFont()} and requires the .fnt file and .png file 171 * listed in the documentation for that method. If you don't have those files, but have some other font (preferably 172 * some kind of distance field font, since this resizes the font significantly), you can use 173 * {@link #init(TextCellFactory, String...)} or {@link #init(TextCellFactory, Map)} to have this use your font. 174 * @param enabled an array or vararg of Strings that name actions; the first char of each String should be unique 175 */ 176 public void init(String... enabled) { 177 init(DefaultResources.getStretchableFont(), enabled); 178 } 179 /** 180 * Convenience method that does essentially the same thing as init(Map<Character, String>). 181 * This assumes that each String passed to this is an action, and the first letter of a String is the character that 182 * a keypress would generate to perform the action named by the String. For example, calling this with 183 * {@code init("fire", "Fortify")} would register "fire" under 'f' and "Fortify" under 'F', displaying the Strings 184 * instead of the characters if possible. The first char of each String should be unique in the arguments. 185 * <br> 186 * This also initializes the displayed buttons if there is no hardware keyboard available. This uses the color and 187 * eightWay fields to determine what color the buttons will be drawn with and if directions should be 8-way or 188 * 4-way, respectively. These fields should be set before calling init() if you don't want the defaults, which are 189 * white buttons and 8-way directions. 190 * @param font the TextCellFactory to use for the buttons, usually a distance field font from DefaultResources 191 * @param enabled an array or vararg of Strings that name actions; the first char of each String should be unique 192 */ 193 public void init(TextCellFactory font, String... enabled) 194 { 195 if(!forceButtons && Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)) 196 return; 197 ready(font); 198 availableKeys = new TreeMap<>(); 199 if(enabled == null) 200 return; 201 for (int i = 0; i < enabled.length; i++) { 202 if(enabled[i] != null && !enabled[i].isEmpty()) 203 availableKeys.put(enabled[i].charAt(0), enabled[i]); 204 } 205 fillActions(); 206 } 207 /** 208 * For each char and String in available, registers each keyboard key (as a char, such as 'A' or 209 * SquidInput.LEFT_ARROW) with a String that names the action that key is used to perform, and makes these Strings 210 * available as buttons on the right side of the screen if on a device with no hardware keyboard. Arrows will be 211 * provided on the left side of the screen for directions. 212 * <br> 213 * This also initializes the displayed buttons if there is no hardware keyboard available. This uses the color and 214 * eightWay fields to determine what color the buttons will be drawn with and if directions should be 8-way or 215 * 4-way, respectively. These fields should be set before calling init() if you don't want the defaults, which are 216 * white buttons and 8-way directions. The font with this overload of init is set to Inconsolata-LGC-Custom, which 217 * is normally available via {@link DefaultResources#getStretchableFont()} and requires the .fnt file and .png file 218 * listed in the documentation for that method. If you don't have those files, but have some other font (preferably 219 * some kind of distance field font, since this resizes the font significantly), you can use 220 * {@link #init(TextCellFactory, String...)} or {@link #init(TextCellFactory, Map)} to have this use your font. 221 * @param available a Map of Character keys representing keyboard keys and Strings for the actions they trigger 222 */ 223 public void init(Map<Character, String> available) 224 { 225 init(DefaultResources.getStretchableFont(), available); 226 } 227 /** 228 * For each char and String in available, registers each keyboard key (as a char, such as 'A' or 229 * SquidInput.LEFT_ARROW) with a String that names the action that key is used to perform, and makes these Strings 230 * available as buttons on the right side of the screen if on a device with no hardware keyboard. Arrows will be 231 * provided on the left side of the screen for directions. 232 * <br> 233 * This uses the color and eightWay fields to determine what color the buttons will be drawn with and if directions 234 * should be 8-way or 4-way, respectively. These fields should be set before calling init() if you don't want the 235 * defaults, which are white buttons and 8-way directions. 236 * @param font the TextCellFactory to use for the buttons, usually a distance field font from DefaultResources 237 * @param available a Map of Character keys representing keyboard keys and Strings for the actions they trigger 238 */ 239 public void init(TextCellFactory font, Map<Character, String> available) 240 { 241 if(!forceButtons && Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)) 242 return; 243 ready(font); 244 if(available != null) { 245 availableKeys = new TreeMap<>(available); 246 fillActions(); 247 } 248 } 249 private VisualInput() { 250 251 } 252 253 public VisualInput(KeyHandler keyHandler) { 254 super(keyHandler); 255 } 256 257 public VisualInput(KeyHandler keyHandler, boolean ignoreInput) { 258 super(keyHandler, ignoreInput); 259 } 260 261 public VisualInput(KeyHandler keyHandler, SquidMouse mouse) { 262 super(keyHandler, mouse); 263 } 264 265 public VisualInput(KeyHandler keyHandler, SquidMouse mouse, boolean ignoreInput) { 266 super(keyHandler, mouse, ignoreInput); 267 } 268 269 @Override 270 public boolean touchDown(int screenX, int screenY, int pointer, int button) { 271 if (initialized && mouseLeft.onGrid(screenX, screenY)) 272 return mouseLeft.touchDown(screenX, screenY, pointer, button); 273 else if (initialized && mouseRight.onGrid(screenX, screenY)) 274 return mouseRight.touchDown(screenX, screenY, pointer, button); 275 if(spv != null) { 276 screenX *= spv.getScreenWidth() / (spv.getScreenWidth() - spv.barWidth); 277 } 278 return (!initialized || (mouse != null && mouse.onGrid(screenX, screenY))) && super.touchDown(screenX, screenY, pointer, button); 279 } 280 281 @Override 282 public boolean touchUp(int screenX, int screenY, int pointer, int button) { 283 if (initialized && mouseLeft.onGrid(screenX, screenY)) 284 return mouseLeft.touchUp(screenX, screenY, pointer, button); 285 else if (initialized && mouseRight.onGrid(screenX, screenY)) 286 return mouseRight.touchUp(screenX, screenY, pointer, button); 287 if(spv != null) { 288 screenX *= spv.getScreenWidth() / (spv.getScreenWidth() - spv.barWidth); 289 } 290 291 return (!initialized || (mouse != null && mouse.onGrid(screenX, screenY))) && super.touchUp(screenX, screenY, pointer, button); 292 } 293 294 @Override 295 public boolean touchDragged(int screenX, int screenY, int pointer) { 296 if (initialized && mouseLeft.onGrid(screenX, screenY)) 297 return mouseLeft.touchDragged(screenX, screenY, pointer); 298 else if (initialized && mouseRight.onGrid(screenX, screenY)) 299 return mouseRight.touchDragged(screenX, screenY, pointer); 300 if(spv != null) { 301 screenX *= spv.getScreenWidth() / (spv.getScreenWidth() - spv.barWidth); 302 } 303 return (!initialized || (mouse != null && mouse.onGrid(screenX, screenY))) && super.touchDragged(screenX, screenY, pointer); 304 305 } 306 @Override 307 public boolean mouseMoved (int screenX, int screenY) { 308 if(spv != null) { 309 screenX *= spv.getScreenWidth() / (spv.getScreenWidth() - spv.barWidth); 310 } 311 312 if(ignoreInput || mouse == null || !mouse.onGrid(screenX, screenY)) return false; 313 return mouse.mouseMoved(screenX, screenY); 314 } 315 public void reinitialize(float cellWidth, float cellHeight, float gridWidth, float gridHeight, 316 int offsetX, int offsetY, float screenWidth, float screenHeight) 317 { 318 if(!initialized) 319 { 320 if(mouse != null) 321 mouse.reinitialize(cellWidth, cellHeight, gridWidth, gridHeight, offsetX, offsetY); 322 return; 323 } 324 if(this.screenWidth > 0) 325 sectionWidth *= screenWidth / this.screenWidth; 326 else 327 sectionWidth *= screenWidth / Gdx.graphics.getWidth(); 328 if(this.screenHeight > 0) 329 sectionHeight *= screenHeight / this.screenHeight; 330 else 331 sectionHeight *= screenHeight / Gdx.graphics.getHeight(); 332 cellWidth /= screenWidth / (screenWidth - sectionWidth * 0.75f); 333 float leftWidth = screenWidth / 32f, rightWidth = screenWidth / 96f, 334 leftHeight = screenHeight / 12f, rightHeight = screenHeight / 24f; 335 if(mouse != null) 336 mouse.reinitialize(cellWidth, cellHeight, gridWidth, gridHeight, 337 offsetX - MathUtils.round((screenWidth * 0.125f) * (screenWidth / (screenWidth - sectionWidth)) + cellWidth * 0.5f), offsetY); 338 mouseLeft.reinitialize(leftWidth, leftHeight, 4, 16, offsetX, offsetY); 339 mouseRight.reinitialize(rightWidth, rightHeight, 12, 24, 340 MathUtils.ceil(offsetX - (screenWidth - sectionWidth)), 341 MathUtils.round(offsetY - rightHeight * 0.5f + (right.getGridHeight() * rightHeight - screenHeight))); 342 this.screenWidth = screenWidth; 343 this.screenHeight = screenHeight; 344 if(spv != null) 345 spv.barWidth = sectionWidth; 346 } 347 public ShrinkPartViewport resizeInnerStage(Stage insides) 348 { 349 if(!initialized) 350 return null; 351 /* 352 insides.getViewport().setWorldWidth(insides.getViewport().getWorldWidth() - screenWidth * 2); 353 insides.getViewport().setScreenX(screenWidth); 354 insides.getViewport().setScreenY(0); 355 */ 356 spv = new ShrinkPartViewport(insides.getWidth(), insides.getHeight(), sectionWidth); 357 insides.setViewport(spv); 358 return spv; 359 } 360 361 public int getSectionWidth() { 362 return sectionWidth; 363 } 364 365 public int getSectionHeight() { 366 return sectionHeight; 367 } 368 369 public void update(int width, int height, boolean centerCamera) { 370 if(initialized) 371 { 372 stage.getViewport().update(width, height, centerCamera); 373 } 374 } 375 376 public void show() { 377 if(initialized) { 378 stage.getViewport().apply(true); 379 stage.draw(); 380 stage.act(); 381 } 382 } 383}