001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.Input.Keys;
004import com.badlogic.gdx.graphics.Color;
005import com.badlogic.gdx.graphics.g2d.Batch;
006import com.badlogic.gdx.graphics.g2d.BitmapFont;
007import com.badlogic.gdx.graphics.g2d.GlyphLayout;
008import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
009import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
010import com.badlogic.gdx.scenes.scene2d.Actor;
011import com.badlogic.gdx.utils.Pools;
012
013/**
014 * @author smelC
015 */
016public class UIUtil {
017
018        /**
019         * Writes {@code text} at {@code (x, y)} by cutting off using "…" if it gets
020         * wider than {@code width}.
021         * 
022         * @param batch
023         * @param font
024         *            The font to use
025         * @param text
026         *            The text to draw
027         * @param color
028         *            The text's color
029         * @param align
030         *            The alignment (see {@link com.badlogic.gdx.utils.Align}).
031         * @param width
032         *            The desired width of the text
033         * @param x
034         *            Where to draw
035         * @param y
036         *            Where to draw
037         */
038        public static void drawLimitedWidthText(Batch batch, BitmapFont font, String text, Color color,
039                                                                                        int align, float width, float x, float y) {
040                final GlyphLayout glyph = Pools.obtain(GlyphLayout.class);
041                glyph.setText(font, text, 0, text.length(), color, width, align,
042                                /* do not wrap */ false, /* the ellipsis */ "…");
043                font.draw(batch, glyph, x, y);
044                Pools.free(glyph);
045        }
046
047        /**
048         * Draws margins around an actor.
049         * 
050         * @param renderer_
051         *            The renderer to use. If {@code null} a new one will be
052         *            allocated.
053         * @param a
054         *            the actor to draw around
055         * @param margin
056         *            The size of the margin to draw.
057         * @param color
058         *            The margins' colors.
059         */
060        public static void drawMarginsAround(ShapeRenderer renderer_, Actor a, float margin, Color color,
061                        CornerStyle cornerStyle) {
062                drawMarginsAround(renderer_, a.getX(), a.getY(), a.getWidth(), a.getHeight(), margin, color,
063                                cornerStyle, 1f, 1f);
064        }
065
066        /**
067         * Draws margins around a rectangle
068         * 
069         * @param botLeftX
070         *            The rectangle's bottom left.
071         * @param botLeftY
072         *            The rectangle's bottom left.
073         * @param width
074         *            The rectangle's width.
075         * @param height
076         *            The rectangle's height.
077         * @param xmargin
078         *            The size of the left margin and the size of the right margin.
079         * @param ymargin
080         *            The size of the bottom margin and the size of the top margin.
081         * @param c
082         *            The margins' colors.
083         */
084        public static void drawMarginsAround(float botLeftX, float botLeftY, int width, int height, int xmargin,
085                        int ymargin, Color c) {
086                if (xmargin == 0 && ymargin == 0)
087                        return;
088
089                final ShapeRenderer renderer = new ShapeRenderer();
090                renderer.begin(ShapeType.Filled);
091                renderer.setColor(c);
092
093                if (0 < xmargin) {
094                        /* The left rectangle */
095                        renderer.rect(botLeftX - xmargin, botLeftY - ymargin, xmargin, height + (ymargin * 2));
096                        /* The right rectangle */
097                        renderer.rect(botLeftX + width, botLeftY - ymargin, xmargin, height + (ymargin * 2));
098                }
099                if (0 < ymargin) {
100                        /* The bottom rectangle */
101                        renderer.rect(botLeftX, botLeftY - ymargin, width, ymargin);
102                        /* The top rectangle */
103                        renderer.rect(botLeftX, botLeftY + height, width, ymargin);
104                }
105
106                renderer.end();
107                renderer.dispose();
108        }
109
110        /**
111         * @param renderer_
112         *            The renderer to use. If {@code null} a new one will be
113         *            allocated.
114         * @param botLeftX
115         *            The bottom left x cell of the rectangle to draw around.
116         * @param botLeftY
117         *            The bottom left y cell of the rectangle to draw around.
118         * @param width
119         *            The width of the button considered.
120         * @param height
121         *            The width of the button considered.
122         * @param margin
123         *            The size of the margin to draw.
124         * @param color
125         *            The color to draw
126         * @param cornerStyle
127         *            The style with which to draw the margins
128         */
129        public static void drawMarginsAround(ShapeRenderer renderer_, float botLeftX, float botLeftY, float width,
130                        float height, float margin, Color color, CornerStyle cornerStyle) {
131                drawMarginsAround(renderer_, botLeftX, botLeftY, width, height, margin, color, cornerStyle, 1f, 1f);
132        }
133
134        /**
135         * @param renderer_
136         *            The renderer to use. If {@code null} a new one will be
137         *            allocated.
138         * @param botLeftX
139         *            The bottom left x cell of the rectangle to draw around.
140         * @param botLeftY
141         *            The bottom left y cell of the rectangle to draw around.
142         * @param width
143         *            The width of the button considered.
144         * @param height
145         *            The width of the button considered.
146         * @param margin
147         *            The size of the margin to draw.
148         * @param color
149         *            The color to draw
150         * @param cornerStyle
151         *            The style with which to draw the margins
152         * @param zoomX
153         *            A multiplier for the world x-size of non-ShapeRenderer
154         *            objects, that needs to be reversed for this
155         * @param zoomY
156         *            A multiplier for the world y-size of non-ShapeRenderer
157         *            objects, that needs to be reversed for this
158         */
159        public static void drawMarginsAround(ShapeRenderer renderer_, float botLeftX, float botLeftY, float width,
160                        float height, float margin, Color color, CornerStyle cornerStyle, float zoomX, float zoomY) {
161                if (margin == 0 || color == null)
162                        /* Nothing to do */
163                        return;
164
165                botLeftY += 1;
166
167                final boolean reset;
168                final ShapeRenderer renderer = renderer_ == null ? new ShapeRenderer() : renderer_;
169                /*
170                 * No matter the state of the given ShapeRenderer, we'll be fine, thanks
171                 * to this:
172                 */
173                if (!renderer.isDrawing()) {
174                        reset = true;
175                        renderer.begin(ShapeType.Filled);
176                } else
177                        reset = false;
178                renderer.scale(1f / zoomX, 1f / zoomY, 1f);
179                renderer.setColor(color);
180
181                if (cornerStyle == CornerStyle.ROUNDED || cornerStyle == CornerStyle.MISSING) {
182                        /* Left margin */
183                        renderer.rect(botLeftX - margin, botLeftY, margin, height);
184                        /* Right margin */
185                        renderer.rect(botLeftX + width, botLeftY, margin, height);
186                } else {
187                        /* Left margin */
188                        renderer.rect(botLeftX - margin, botLeftY - margin, margin, height + (margin * 2));
189                        /* Right margin */
190                        renderer.rect(botLeftX + width, botLeftY - margin, margin, height + (margin * 2));
191                }
192                /* Bottom margin */
193                renderer.rect(botLeftX, botLeftY - margin, width, margin);
194                /* Top margin */
195                renderer.rect(botLeftX, botLeftY + height, width, margin);
196
197                if (cornerStyle == CornerStyle.ROUNDED) {
198                        /* Bottom left */
199                        renderer.arc(botLeftX, botLeftY, margin, 180, 90);
200                        /* Top left */
201                        renderer.arc(botLeftX, botLeftY + height, margin, 90, 90);
202                        /* Top right */
203                        renderer.arc(botLeftX + width, botLeftY + height, margin, 0, 90);
204                        /* Bottom Right */
205                        renderer.arc(botLeftX + width, botLeftY, margin, 270, 90);
206                }
207
208                if (reset)
209                        renderer.end();
210
211                if (renderer_ == null)
212                        /* I allocated it, I must dispose it */
213                        renderer.dispose();
214        }
215
216        /**
217         * Draws a rectangle using a {@link ShapeRenderer}.
218         * 
219         * @param sRender_
220         *            The renderer to use. If {@code null} a new one will be
221         *            allocated.
222         * @param botLeftX
223         *            The bottom left x of the rectangle.
224         * @param botLeftY
225         *            The bottom left y of the rectangle.
226         * @param width
227         *            The rectangle's width
228         * @param height
229         *            The rectangle's height
230         * @param st
231         *            The style to use
232         * @param color
233         *            The rectangle's color
234         */
235        public static void drawRectangle(/* @Nullable */ShapeRenderer sRender_, float botLeftX, float botLeftY,
236                        float width, float height, ShapeType st, Color color) {
237                final ShapeRenderer sRender = sRender_ == null ? new ShapeRenderer() : sRender_;
238                final boolean reset;
239                /*
240                 * No matter the state of the given ShapeRenderer, we'll be fine, thanks
241                 * to this:
242                 */
243                if (!sRender.isDrawing()) {
244                        reset = true;
245                        sRender.begin(st);
246                } else
247                        reset = false;
248                sRender.setColor(color);
249                sRender.rect(botLeftX, botLeftY, width, height);
250                if (reset)
251                        sRender.end();
252                if (sRender != sRender_)
253                        /* I allocated it */
254                        sRender.dispose();
255        }
256
257        /**
258         * Draws a rectangle using a {@link ShapeRenderer}, allocating a new one for
259         * the occasion.
260         * 
261         * @param botLeftX
262         *            The bottom left x of the rectangle.
263         * @param botLeftY
264         *            The bottom left y of the rectangle.
265         * @param width
266         *            The rectangle's width
267         * @param height
268         *            The rectangle's height
269         * @param st
270         *            The style to use
271         * @param color
272         *            The rectangle's color
273         */
274        public static void drawRectangle(float botLeftX, float botLeftY, float width, float height, ShapeType st,
275                        Color color) {
276                drawRectangle(null, botLeftX, botLeftY, width, height, st, color);
277        }
278
279        /**
280         * @author smelC
281         */
282        public static enum CornerStyle {
283                SQUARE,
284                /**
285                 * Here's an example of this style:
286                 * 
287                 * <br>
288                 * 
289                 * <img src="http://i.imgur.com/AQgWeic.png"/>.
290                 */
291                ROUNDED,
292                /**
293                 * A NES-like style (to my taste..). Try it, I can't explain it with
294                 * sentences. Here's an example:
295                 * 
296                 * <br>
297                 * 
298                 * <img src="http://i.imgur.com/PQSvT0t.png"/>
299                 */
300                MISSING,
301        }
302
303        /**
304         * A vertical move triggered by keyboard keys.
305         * 
306         * @author smelC
307         */
308        public enum YMoveKind {
309                /** The kind corresponding to arrow up */
310                UP,
311                /** The kind corresponding to arrow down */
312                DOWN,
313                /** The kind corresponding to page down */
314                PAGE_DOWN,
315                /** The kind corresponding to page up */
316                PAGE_UP;
317
318                /**
319                 * @return {@code true} if {@code this} is downward.
320                 */
321                public boolean isDown() {
322                        switch (this) {
323                        case DOWN:
324                        case PAGE_DOWN:
325                                return true;
326                        case PAGE_UP:
327                        case UP:
328                                return false;
329                        }
330                        throw new IllegalStateException("Unmatched " + getClass().getSimpleName() + ": " + this);
331                }
332
333                /**
334                 * @param keycode
335                 * @param vim
336                 *            Whether to recognize vim shortcuts (j/k).
337                 * @return The move kind corresponding to {@code keycode}, or
338                 *         {@code null} if none.
339                 */
340                public static YMoveKind of(int keycode, boolean vim) {
341                        if (keycode == Keys.UP || keycode == Keys.NUMPAD_8)
342                                return UP;
343                        else if (keycode == Keys.DOWN || keycode == Keys.NUMPAD_2)
344                                return DOWN;
345                        else if (keycode == Keys.PAGE_UP)
346                                return PAGE_UP;
347                        else if (keycode == Keys.PAGE_DOWN)
348                                return PAGE_DOWN;
349                        else if (vim) {
350                                if (keycode == Keys.J)
351                                        return DOWN;
352                                else if (keycode == Keys.K)
353                                        return UP;
354                                else
355                                        return null;
356                        } else
357                                return null;
358                }
359        }
360
361}