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 * Created by Tommy Ettinger on 4/15/2016. 020 */ 021public class VisualInput extends SquidInput { 022 023 public SquidPanel left, right; 024 protected TextCellFactory tcfLeft, tcfRight; 025 protected TreeMap<Character, String> availableKeys; 026 protected SquidMouse mouseLeft, mouseRight; 027 private int sectionWidth, sectionHeight; 028 private float screenWidth = -1, screenHeight = -1; 029 protected boolean initialized = false; 030 public Color color = Color.WHITE; 031 protected CharArray clicks; 032 public boolean eightWay = true, forceButtons = false; 033 public Stage stage; 034 protected ShrinkPartViewport spv; 035 private void init() 036 { 037 initialized = true; 038 sectionWidth = Gdx.graphics.getWidth() / 8; 039 sectionHeight = Gdx.graphics.getHeight(); 040 041 tcfLeft = DefaultResources.getStretchableFont().width(sectionWidth / 4).height(sectionHeight / 16).initBySize(); 042 tcfRight = DefaultResources.getStretchableFont().width(sectionWidth / 12).height(sectionHeight / 24).initBySize(); 043 044 left = new SquidPanel(4, 16, tcfLeft); 045 if(eightWay) { 046 left.put(0, 7, new char[][]{ 047 new char[]{'\\', '-', '/'}, 048 new char[]{'|', 'O', '|'}, 049 new char[]{'/', '-', '\\'}, 050 }, color); 051 } 052 else 053 { 054 left.put(0, 7, new char[][]{ 055 new char[]{' ', SquidInput.LEFT_ARROW, ' '}, 056 new char[]{SquidInput.UP_ARROW, 'O', SquidInput.DOWN_ARROW}, 057 new char[]{' ', SquidInput.RIGHT_ARROW, ' '}, 058 }, color); } 059 right = new SquidPanel(12, 24, tcfRight, null, Gdx.graphics.getWidth() - sectionWidth, 0); 060 061 mouseLeft = new SquidMouse(left.cellWidth(), left.cellHeight(), left.gridWidth, left.gridHeight, 062 0, 0, new InputAdapter() 063 { 064 @Override 065 public boolean touchUp(int screenX, int screenY, int pointer, int button) { 066 if(screenX < 3 && screenY >= 5 && screenY <= 7 && left.contents[screenX][screenY + 2] != null && 067 !left.contents[screenX][screenY + 2].equals(" ")) 068 { 069 switch ((screenY - 5) * 3 + screenX) 070 { 071 case 0: queue.add(SquidInput.UP_LEFT_ARROW); 072 break; 073 case 1: queue.add(SquidInput.UP_ARROW); 074 break; 075 case 2: queue.add(SquidInput.UP_RIGHT_ARROW); 076 break; 077 case 3: queue.add(SquidInput.LEFT_ARROW); 078 break; 079 case 4: queue.add(SquidInput.CENTER_ARROW); 080 break; 081 case 5: queue.add(SquidInput.RIGHT_ARROW); 082 break; 083 case 6: queue.add(SquidInput.DOWN_LEFT_ARROW); 084 break; 085 case 7: queue.add(SquidInput.DOWN_ARROW); 086 break; 087 case 8: queue.add(SquidInput.DOWN_RIGHT_ARROW); 088 break; 089 default: 090 return false; 091 } 092 queue.add('\u0000'); 093 return true; 094 } 095 else 096 return false; 097 } 098 }); 099 //if(mouse != null) 100 // mouse.setOffsetX(-sectionWidth); 101 stage = new Stage(new StretchViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight())); 102 stage.addActor(left); 103 stage.addActor(right); 104 } 105 private void fillActions() 106 { 107 if(!initialized) 108 return; 109 int y = 0; 110 clicks = new CharArray(right.getGridHeight()); 111 for(Map.Entry<Character, String> kv : availableKeys.entrySet()) 112 { 113 switch (kv.getKey()) 114 { 115 case SquidInput.UP_LEFT_ARROW: 116 case SquidInput.UP_ARROW: 117 case SquidInput.UP_RIGHT_ARROW: 118 case SquidInput.LEFT_ARROW: 119 case SquidInput.CENTER_ARROW: 120 case SquidInput.RIGHT_ARROW: 121 case SquidInput.DOWN_LEFT_ARROW: 122 case SquidInput.DOWN_ARROW: 123 case SquidInput.DOWN_RIGHT_ARROW: 124 break; 125 default: 126 right.put(1, y, kv.getValue(), color); 127 clicks.add(kv.getKey()); 128 y++; 129 } 130 if(y > right.getGridHeight()) 131 break; 132 } 133 mouseRight = new SquidMouse(right.cellWidth(), right.cellHeight(), right.gridWidth(), right.gridHeight(), 134 Gdx.graphics.getWidth() - sectionWidth, Math.round(sectionHeight - right.getHeight()), 135 new InputAdapter() 136 { 137 @Override 138 public boolean touchUp(int screenX, int screenY, int pointer, int button) { 139 if(screenY < clicks.size) 140 { 141 queue.add(clicks.get(screenY)); 142 queue.add('\u0000'); 143 return true; 144 } 145 return false; 146 } 147 }); 148 } 149 /** 150 * Convenience method that does essentially the same thing as init(Map<Character, String>). 151 * This assumes that each String passed to this is an action, and the first letter of a String is the character that 152 * a keypress would generate to perform the action named by the String. For example, calling this with 153 * {@code init("fire", "Fortify")} would register "fire" under 'f' and "Fortify" under 'F', displaying the Strings 154 * instead of the characters if possible. The first char of each String should be unique in the arguments. 155 * <br> 156 * This also initializes the displayed buttons if there is no hardware keyboard available. This uses the color and 157 * eightWay fields to determine what color the buttons will be drawn with and if directions should be 8-way or 158 * 4-way, respectively. These fields should be set before calling init() if you don't want the defaults, which are 159 * white buttons and 8-way directions. 160 * @param enabled an array or vararg of Strings that name actions; the first char of each String should be unique 161 */ 162 public void init(String... enabled) 163 { 164 if(!forceButtons && Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)) 165 return; 166 init(); 167 availableKeys = new TreeMap<>(); 168 if(enabled == null) 169 return; 170 for (int i = 0; i < enabled.length; i++) { 171 if(enabled[i] != null && !enabled[i].isEmpty()) 172 availableKeys.put(enabled[i].charAt(0), enabled[i]); 173 } 174 fillActions(); 175 } 176 /** 177 * For each char and String in available, registers each keyboard key (as a char, such as 'A' or 178 * SquidInput.LEFT_ARROW) with a String that names the action that key is used to perform, and makes these Strings 179 * available as buttons on the right side of the screen if on a device with no hardware keyboard. Arrows will be 180 * provided on the left side of the screen for directions. 181 * <br> 182 * This uses the color and eightWay fields to determine what color the buttons will be drawn with and if directions 183 * should be 8-way or 4-way, respectively. These fields should be set before calling init() if you don't want the 184 * defaults, which are white buttons and 8-way directions. 185 * @param available a Map of Character keys representing keyboard keys and Strings for the actions they trigger 186 */ 187 public void init(Map<Character, String> available) 188 { 189 if(!forceButtons && Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)) 190 return; 191 init(); 192 if(available != null) { 193 availableKeys = new TreeMap<>(available); 194 fillActions(); 195 } 196 } 197 private VisualInput() { 198 199 } 200 201 public VisualInput(KeyHandler keyHandler) { 202 super(keyHandler); 203 } 204 205 public VisualInput(KeyHandler keyHandler, boolean ignoreInput) { 206 super(keyHandler, ignoreInput); 207 } 208 209 public VisualInput(KeyHandler keyHandler, SquidMouse mouse) { 210 super(keyHandler, mouse); 211 } 212 213 public VisualInput(KeyHandler keyHandler, SquidMouse mouse, boolean ignoreInput) { 214 super(keyHandler, mouse, ignoreInput); 215 } 216 217 @Override 218 public boolean touchDown(int screenX, int screenY, int pointer, int button) { 219 if (initialized && mouseLeft.onGrid(screenX, screenY)) 220 return mouseLeft.touchDown(screenX, screenY, pointer, button); 221 else if (initialized && mouseRight.onGrid(screenX, screenY)) 222 return mouseRight.touchDown(screenX, screenY, pointer, button); 223 if(spv != null) { 224 screenX *= spv.getScreenWidth() / (spv.getScreenWidth() - spv.barWidth); 225 } 226 return (!initialized || mouse.onGrid(screenX, screenY)) && super.touchDown(screenX, screenY, pointer, button); 227 } 228 229 @Override 230 public boolean touchUp(int screenX, int screenY, int pointer, int button) { 231 if (initialized && mouseLeft.onGrid(screenX, screenY)) 232 return mouseLeft.touchUp(screenX, screenY, pointer, button); 233 else if (initialized && mouseRight.onGrid(screenX, screenY)) 234 return mouseRight.touchUp(screenX, screenY, pointer, button); 235 if(spv != null) { 236 screenX *= spv.getScreenWidth() / (spv.getScreenWidth() - spv.barWidth); 237 } 238 239 return (!initialized || mouse.onGrid(screenX, screenY)) && super.touchUp(screenX, screenY, pointer, button); 240 } 241 242 @Override 243 public boolean touchDragged(int screenX, int screenY, int pointer) { 244 if (initialized && mouseLeft.onGrid(screenX, screenY)) 245 return mouseLeft.touchDragged(screenX, screenY, pointer); 246 else if (initialized && mouseRight.onGrid(screenX, screenY)) 247 return mouseRight.touchDragged(screenX, screenY, pointer); 248 if(spv != null) { 249 screenX *= spv.getScreenWidth() / (spv.getScreenWidth() - spv.barWidth); 250 } 251 return (!initialized || mouse.onGrid(screenX, screenY)) && super.touchDragged(screenX, screenY, pointer); 252 253 } 254 @Override 255 public boolean mouseMoved (int screenX, int screenY) { 256 if(spv != null) { 257 screenX *= spv.getScreenWidth() / (spv.getScreenWidth() - spv.barWidth); 258 } 259 260 if(ignoreInput || !mouse.onGrid(screenX, screenY)) return false; 261 return mouse.mouseMoved(screenX, screenY); 262 } 263 public void reinitialize(float cellWidth, float cellHeight, float gridWidth, float gridHeight, 264 int offsetX, int offsetY, float screenWidth, float screenHeight) 265 { 266 if(!initialized) 267 { 268 mouse.reinitialize(cellWidth, cellHeight, gridWidth, gridHeight, offsetX, offsetY); 269 return; 270 } 271 if(this.screenWidth > 0) 272 sectionWidth *= screenWidth / this.screenWidth; 273 else 274 sectionWidth *= screenWidth / Gdx.graphics.getWidth(); 275 if(this.screenHeight > 0) 276 sectionHeight *= screenHeight / this.screenHeight; 277 else 278 sectionHeight *= screenHeight / Gdx.graphics.getHeight(); 279 cellWidth /= screenWidth / (screenWidth - sectionWidth); 280 float leftWidth = screenWidth / 8f / 4f, rightWidth = screenWidth / 8f / 12f, 281 leftHeight = screenHeight / 12f, rightHeight = screenHeight / 24f; 282 mouse.reinitialize(cellWidth, cellHeight, gridWidth, gridHeight, 283 offsetX - MathUtils.round((screenWidth / 8f) * (screenWidth / (screenWidth - sectionWidth)) + cellWidth /2f), offsetY); 284 mouseLeft.reinitialize(leftWidth, leftHeight, 4, 16, offsetX, offsetY); 285 mouseRight.reinitialize(rightWidth, rightHeight, 12, 24, 286 MathUtils.ceil(offsetX - (screenWidth - sectionWidth)), 287 MathUtils.round(offsetY - rightHeight / 2f + (right.getGridHeight() * rightHeight - screenHeight))); 288 this.screenWidth = screenWidth; 289 this.screenHeight = screenHeight; 290 if(spv != null) 291 spv.barWidth = sectionWidth; 292 } 293 public ShrinkPartViewport resizeInnerStage(Stage insides) 294 { 295 if(!initialized) 296 return null; 297 /* 298 insides.getViewport().setWorldWidth(insides.getViewport().getWorldWidth() - screenWidth * 2); 299 insides.getViewport().setScreenX(screenWidth); 300 insides.getViewport().setScreenY(0); 301 */ 302 spv = new ShrinkPartViewport(insides.getWidth(), insides.getHeight(), sectionWidth); 303 insides.setViewport(spv); 304 return spv; 305 } 306 307 public int getSectionWidth() { 308 return sectionWidth; 309 } 310 311 public int getSectionHeight() { 312 return sectionHeight; 313 } 314 315 public void update(int width, int height, boolean centerCamera) { 316 if(initialized) 317 { 318 stage.getViewport().update(width, height, centerCamera); 319 } 320 } 321 322 public void show() { 323 if(initialized) { 324 stage.getViewport().apply(true); 325 stage.draw(); 326 stage.act(); 327 } 328 } 329}