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&lt;Character, String&gt;).
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}