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}