001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.Gdx;
004import com.badlogic.gdx.graphics.Color;
005import com.badlogic.gdx.graphics.g2d.Batch;
006import com.badlogic.gdx.graphics.g2d.TextureRegion;
007import com.badlogic.gdx.scenes.scene2d.Actor;
008import com.badlogic.gdx.utils.IntMap;
009import squidpony.IColorCenter;
010import squidpony.StringKit;
011
012import java.util.ArrayList;
013import java.util.Collection;
014
015/**
016 * Displays text and images in a grid pattern, like SquidPanel, but will automatically render certain chars as images.
017 * Supports basic animations, such as sliding, wiggling, or fading the contents of a cell.
018 * <br>
019 * Grid width and height settings are in terms of number of cells. Cell width and height are in terms of number of
020 * pixels (when there is no stretching taking place due to viewport or window size).
021 * <br>
022 * @author Eben Howard - http://squidpony.com - howard@squidpony.com
023 * @author Tommy Ettinger
024 */
025public class ImageSquidPanel extends SquidPanel {
026
027    public IntMap<TextureRegion> imageMap;
028
029    /**
030     * Creates a bare-bones panel with all default values for text rendering.
031     *
032     * @param gridWidth the number of cells horizontally
033     * @param gridHeight the number of cells vertically
034     */
035    public ImageSquidPanel(int gridWidth, int gridHeight) {
036        this(gridWidth, gridHeight, new TextCellFactory().defaultSquareFont());
037    }
038
039    /**
040     * Creates a panel with the given grid and cell size. Uses a default square font.
041     *
042     * @param gridWidth the number of cells horizontally
043     * @param gridHeight the number of cells vertically
044     * @param cellWidth the number of horizontal pixels in each cell
045     * @param cellHeight the number of vertical pixels in each cell
046     */
047    public ImageSquidPanel(int gridWidth, int gridHeight, int cellWidth, int cellHeight) {
048        this(gridWidth, gridHeight, new TextCellFactory().defaultSquareFont().width(cellWidth).height(cellHeight).initBySize());
049    }
050
051    /**
052     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
053     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions.
054     *
055     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
056     * then a default one will be created and initialized.
057     *
058     * @param gridWidth the number of cells horizontally
059     * @param gridHeight the number of cells vertically
060     * @param factory the factory to use for cell rendering
061     */
062    public ImageSquidPanel(int gridWidth, int gridHeight, TextCellFactory factory) {
063        this(gridWidth, gridHeight, factory, DefaultResources.getSCC());
064    }
065
066    /**
067     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
068     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions.
069     *
070     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
071     * then a default one will be created and initialized.
072     *
073     * @param gridWidth the number of cells horizontally
074     * @param gridHeight the number of cells vertically
075     * @param factory the factory to use for cell rendering
076     * @param center
077     *                  The color center to use. Can be {@code null}, but then must be set later on with
078     *          {@link SquidPanel#setColorCenter(IColorCenter)}.
079     */
080    public ImageSquidPanel(int gridWidth, int gridHeight, TextCellFactory factory, IColorCenter<Color> center) {
081        this(gridWidth, gridHeight, factory, center, 0f, 0f);
082    }
083
084    /**
085     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
086     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions.
087     *
088     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
089     * then a default one will be created and initialized.
090     *
091     * @param gridWidth the number of cells horizontally
092     * @param gridHeight the number of cells vertically
093     * @param factory the factory to use for cell rendering
094     * @param center
095     *                  The color center to use. Can be {@code null}, but then must be set later on with
096     *          {@link SquidPanel#setColorCenter(IColorCenter)}.
097     */
098    public ImageSquidPanel(int gridWidth, int gridHeight, TextCellFactory factory, IColorCenter<Color> center,
099                           float xOffset, float yOffset) {
100        this(gridWidth, gridHeight, factory, center, xOffset, yOffset, null);
101    }
102    /**
103     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
104     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions. Importantly,
105     * this constructor takes a 2D char array argument that can be sized differently than the displayed area. The
106     * displayed area is gridWidth by gridHeight in cells, but the actualMap argument can be much larger, and only a
107     * portion will be displayed at a time. This requires some special work with the Camera and Viewports to get working
108     * correctly; in the squidlib module's examples, EverythingDemo may be a good place to see how this can be done.
109     * You can pass null for actualMap, which will simply create a char array to use internally that is exactly
110     * gridWidth by gridHeight, in cells.
111     * <br>
112     * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null
113     * then a default one will be created and initialized. The xOffset and yOffset arguments are measured in pixels or
114     * whatever sub-cell unit of measure your game uses (world coordinates, in libGDX parlance), and change where the
115     * SquidPanel starts drawing by simply adding to the initial x and y coordinates. 0 and 0 are usually fine.
116     *
117     * @param gridWidth the number of cells horizontally
118     * @param gridHeight the number of cells vertically
119     * @param factory the factory to use for cell rendering
120     * @param center
121     *                  The color center to use. Can be {@code null}, but then must be set later on with
122     *          {@link SquidPanel#setColorCenter(IColorCenter)}.
123     * @param xOffset the x offset to start rendering at, in pixels (or some other sub-cell measurement your game uses)
124     * @param yOffset the y offset to start rendering at, in pixels (or some other sub-cell measurement your game uses)
125     * @param actualMap will often be a different size than gridWidth by gridHeight, which enables camera scrolling
126     */
127    public ImageSquidPanel(int gridWidth, int gridHeight, TextCellFactory factory, IColorCenter<Color> center,
128                           float xOffset, float yOffset, char[][] actualMap) {
129        super(gridWidth, gridHeight, factory, center, xOffset, yOffset, actualMap);
130        imageMap = new IntMap<>(128);
131    }
132    /**
133     * Draws this ImageSquidPanel and any {@link #autoActors} it has, and calls {@link Actor#act(float)} for each
134     * AnimatedEntity this contains in {@link #animatedEntities} or {@link #autoActors}.
135     * <br>
136     * This will set the shader of {@code batch} if using a distance field or MSDF font and the shader is currently not
137     * configured for such a font; it does not reset the shader to the default so that multiple Actors can all use the
138     * same shader and so specific extra glyphs or other items can be rendered after calling draw(). If you need to draw
139     * both a distance field font and full-color art, you should set the shader on the Batch to null when you want to
140     * draw full-color art, and end the Batch between drawing this object and the other art.
141     * @param batch a Batch such as a {@link FilterBatch} that must be between a begin() and end() call; usually done by Stage
142     * @param parentAlpha only used when drawing children of this ImageSquidPanel
143     */
144    @Override
145    public void draw (Batch batch, float parentAlpha) {
146        textFactory.configureShader(batch);
147        int inc = onlyRenderEven ? 2 : 1, widthInc = inc * cellWidth, heightInc = inc * cellHeight;
148        TextureRegion tr;
149        float screenX = xOffset - (gridOffsetX <= 0 ? 0 : cellWidth) + getX(),
150                screenY_base = 1f + yOffset + (gridOffsetY <= 0 ? 0 : cellHeight) + gridHeight * cellHeight + getY(), screenY;
151        char c;
152        for (int x = Math.max(0, gridOffsetX-1), xx = (gridOffsetX <= 0) ? 0 : -1; xx <= gridWidth && x < contents.length; x += inc, xx += inc, screenX += widthInc) {
153            screenY = screenY_base;
154            for (int y = Math.max(0, gridOffsetY-1), yy = (gridOffsetY <= 0) ? 0 : -1; yy <= gridHeight && y < contents[x].length; y += inc, yy += inc, screenY -= heightInc) {
155                c = contents[x][y];
156                tr = imageMap.get(c);
157                if(tr == null)
158                    textFactory.draw(batch, c, colors[x][y], screenX, screenY);
159                else
160                    textFactory.draw(batch, tr, colors[x][y], screenX, screenY);
161            }
162        }
163        super.draw(batch, parentAlpha);
164        int len = animatedEntities.size();
165        for (int i = 0; i < len; i++) {
166
167            animatedEntities.getAt(i).actor.act(Gdx.graphics.getDeltaTime());
168        }
169        len = autoActors.size();
170        Actor a;
171        for (int i = 0; i < len; i++) {
172            a = autoActors.getAt(i);
173            if(a == null) continue;
174            drawActor(batch, parentAlpha, a);
175            a.act(Gdx.graphics.getDeltaTime());
176        }
177    }
178
179    /**
180     * Draws one AnimatedEntity, specifically the Actor it contains. Batch must be between start() and end()
181     * @param batch Must have start() called already but not stop() yet during this frame.
182     * @param parentAlpha This can be assumed to be 1.0f if you don't know it
183     * @param ae The AnimatedEntity to draw; the position to draw ae is stored inside it.
184     */
185    public void drawActor(Batch batch, float parentAlpha, AnimatedEntity ae)
186    {
187        drawActor(batch, parentAlpha, ae.actor);
188    }
189
190    /**
191     * Draws one AnimatedEntity, specifically the Actor it contains. Batch must be between start() and end()
192     * @param batch Must have start() called already but not stop() yet during this frame.
193     * @param parentAlpha This can be assumed to be 1.0f if you don't know it
194     * @param ac The Actor to draw; the position to draw ac is modified and reset based on some fields of this object
195     */
196    public void drawActor(Batch batch, float parentAlpha, Actor ac)
197    {
198        float prevX = ac.getX(), prevY = ac.getY();
199        ac.setPosition(prevX - gridOffsetX * cellWidth, prevY + gridOffsetY * cellHeight);
200        ac.draw(batch, parentAlpha);
201        ac.setPosition(prevX, prevY);
202    }
203
204    @Override
205        public void setDefaultForeground(Color defaultForeground) {
206        this.defaultForeground = defaultForeground;
207    }
208
209        @Override
210        public Color getDefaultForegroundColor() {
211                return defaultForeground;
212        }
213
214    public AnimatedEntity getAnimatedEntityByCell(int x, int y) {
215        for(AnimatedEntity ae : animatedEntities)
216        {
217            if(ae.gridX == x && ae.gridY == y)
218                return ae;
219        }
220        return  null;
221    }
222
223    /**
224     * Create an AnimatedEntity at position x, y, using the char c in the given color.
225     * @param x
226     * @param y
227     * @param c
228     * @param color
229     * @return
230     */
231    public AnimatedEntity animateActor(int x, int y, char c, Color color)
232    {
233        return animateActor(x, y, false, c, color);
234    }
235
236    /**
237     * Create an AnimatedEntity at position x, y, using the char c in the given color. If doubleWidth is true, treats
238     * the char c as the left char to be placed in a grid of 2-char cells.
239     * @param x
240     * @param y
241     * @param doubleWidth
242     * @param c
243     * @param color
244     * @return
245     */
246    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, char c, Color color)
247    {
248        TextureRegion tr = imageMap.get(c);
249        if(tr != null)
250            return animateActor(x, y, doubleWidth, tr, color, String.valueOf(c));
251        Actor a = textFactory.makeActor(c, color);
252        a.setName(String.valueOf(c));
253        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
254        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
255        animatedEntities.add(ae);
256        return ae;
257    }
258
259    /**
260     * Create an AnimatedEntity at position x, y, using the String s in the given color.
261     * @param x
262     * @param y
263     * @param s
264     * @param color
265     * @return
266     */
267    public AnimatedEntity animateActor(int x, int y, String s, Color color)
268    {
269        return animateActor(x, y, false, s, color);
270    }
271
272    /**
273     * Create an AnimatedEntity at position x, y, using the String s in the given color. If doubleWidth is true, treats
274     * the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
275     * @param x
276     * @param y
277     * @param doubleWidth
278     * @param s
279     * @param color
280     * @return
281     */
282    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, String s, Color color)
283    {
284        Actor a = textFactory.makeActor(s, color);
285        a.setName(s);
286        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
287        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
288        animatedEntities.add(ae);
289        return ae;
290    }
291    /**
292     * Create an AnimatedEntity at position x, y, using the String s in the given color. If doubleWidth is true, treats
293     * the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
294     * @param x
295     * @param y
296     * @param doubleWidth
297     * @param s
298     * @param colors
299     * @return
300     */
301    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, String s, Collection<Color> colors)
302    {
303        return animateActor(x, y, doubleWidth, s, colors, 2f);
304    }
305    /**
306     * Create an AnimatedEntity at position x, y, using the String s in the given color. If doubleWidth is true, treats
307     * the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
308     * @param x
309     * @param y
310     * @param doubleWidth
311     * @param s
312     * @param colors
313     * @param loopTime
314     * @return
315     */
316    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, String s, Collection<Color> colors, float loopTime)
317    {
318        Actor a = textFactory.makeActor(s, colors, loopTime, doubleWidth);
319        a.setName(s);
320        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
321        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
322        animatedEntities.add(ae);
323        return ae;
324    }
325    /**
326     * Create an AnimatedEntity at position x, y, using the char c in the given colors to cycle through. If doubleWidth
327     * is true, treats the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
328     * @param x
329     * @param y
330     * @param doubleWidth
331     * @param c
332     * @param colors
333     * @return
334     */
335    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, char c, Collection<Color> colors)
336    {
337        return animateActor(x, y, doubleWidth, c, colors, 2f);
338    }
339    /**
340     * Create an AnimatedEntity at position x, y, using the char c in the given colors to cycle through. If doubleWidth
341     * is true, treats the String s as starting in the left cell of a pair to be placed in a grid of 2-char cells.
342     * @param x
343     * @param y
344     * @param doubleWidth
345     * @param c
346     * @param colors
347     * @param loopTime
348     * @return
349     */
350    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, char c, Collection<Color> colors, float loopTime)
351    {
352        TextureRegion tr = imageMap.get(c);
353        if(tr != null)
354            return animateActor(x, y, doubleWidth, tr, colors, loopTime, String.valueOf(c));
355        Actor a = textFactory.makeActor(c, colors, loopTime, doubleWidth);
356        a.setName(String.valueOf(c));
357        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
358        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
359        animatedEntities.add(ae);
360        return ae;
361    }
362    /**
363     * Create an AnimatedEntity at position x, y, using '^' as its contents, but as an image so it can be rotated.
364     * Uses the given colors in a looping pattern, that doesn't count as an animation. If doubleWidth is true, treats
365     * the '^' as starting in the middle of a 2-char cell.
366     * @param x
367     * @param y
368     * @param doubleWidth
369     * @param colors
370     * @param loopTime
371     * @return
372     */
373    public AnimatedEntity directionMarker(int x, int y, boolean doubleWidth, Collection<Color> colors, float loopTime)
374    {
375        Actor a = textFactory.makeDirectionMarker(colors, loopTime, doubleWidth);
376        a.setName("^");
377        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
378        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
379        animatedEntities.add(ae);
380        return ae;
381    }
382    public AnimatedEntity directionMarker(int x, int y, boolean doubleWidth, Color color)
383    {
384        Actor a = textFactory.makeDirectionMarker(color);
385        a.setName("^");
386        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
387        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
388        animatedEntities.add(ae);
389        return ae;
390    }
391    /**
392     * Create an AnimatedEntity at position x, y, using the char c with a color looked up by index in palette.
393     * @param x
394     * @param y
395     * @param c
396     * @param index
397     * @param palette
398     * @return
399     */
400    public AnimatedEntity animateActor(int x, int y, char c, int index, ArrayList<Color> palette)
401    {
402        return animateActor(x, y, c, palette.get(index));
403    }
404
405    /**
406     * Create an AnimatedEntity at position x, y, using the String s with a color looked up by index in palette.
407     * @param x
408     * @param y
409     * @param s
410     * @param index
411     * @param palette
412     * @return
413     */
414    public AnimatedEntity animateActor(int x, int y, String s, int index, ArrayList<Color> palette)
415    {
416        return animateActor(x, y, s, palette.get(index));
417    }
418
419    /**
420     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which will be
421     * stretched to fit one cell.
422     * @param x
423     * @param y
424     * @param texture
425     * @return
426     */
427    public AnimatedEntity animateActor(int x, int y, TextureRegion texture)
428    {
429        return animateActor(x, y, false, texture, Color.WHITE);
430        /*
431        Actor a = textFactory.makeActor(texture, Color.WHITE);
432        a.setName("");
433        a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
434
435        AnimatedEntity ae = new AnimatedEntity(a, x, y);
436        animatedEntities.add(ae);
437        return ae;
438        */
439    }
440
441    /**
442     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
443     * stretched to fit one cell.
444     * @param x
445     * @param y
446     * @param texture
447     * @param color
448     * @return
449     */
450    public AnimatedEntity animateActor(int x, int y, TextureRegion texture, Color color)
451    {
452        return animateActor(x, y, false, texture, color);
453        /*
454        Actor a = textFactory.makeActor(texture, color);
455        a.setName("");
456        a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
457
458        AnimatedEntity ae = new AnimatedEntity(a, x, y);
459        animatedEntities.add(ae);
460        return ae;
461        */
462    }
463
464    /**
465     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which will be
466     * stretched to fit one cell, or two cells if doubleWidth is true.
467     * @param x
468     * @param y
469     * @param doubleWidth
470     * @param texture
471     * @return
472     */
473    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture)
474    {
475        return animateActor(x, y, doubleWidth, texture, Color.WHITE);
476        /*
477        Actor a = textFactory.makeActor(texture, Color.WHITE, (doubleWidth ? 2 : 1) * cellWidth, cellHeight);
478
479        a.setName("");
480        if(doubleWidth)
481            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
482        else
483            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
484
485        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
486        animatedEntities.add(ae);
487        return ae;
488        */
489    }
490
491    /**
492     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
493     * stretched to fit one cell, or two cells if doubleWidth is true.
494     * @param x
495     * @param y
496     * @param doubleWidth
497     * @param texture
498     * @param color
499     * @return
500     */
501    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Color color){
502        return animateActor(x, y, doubleWidth, texture, color, "");
503    }
504
505    /**
506     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
507     * stretched to fit one cell, or two cells if doubleWidth is true.
508     * @param x
509     * @param y
510     * @param doubleWidth
511     * @param texture
512     * @param color
513     * @return
514     */
515    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Color color, String name){
516        Actor a = textFactory.makeActor(texture, color, (doubleWidth ? 2 : 1) * cellWidth, cellHeight);
517        a.setName(name);
518        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
519        /*
520        if (doubleWidth)
521            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
522        else
523            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
524        */
525        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
526        animatedEntities.add(ae);
527        return ae;
528    }
529    /**
530     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
531     * stretched to fit one cell, or two cells if doubleWidth is true.
532     * @param x
533     * @param y
534     * @param doubleWidth
535     * @param texture
536     * @param colors
537     * @return
538     */
539    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Collection<Color> colors){
540        return animateActor(x, y, doubleWidth, texture, colors, 2f);
541    }
542    /**
543     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
544     * stretched to fit one cell, or two cells if doubleWidth is true.
545     * @param x
546     * @param y
547     * @param doubleWidth
548     * @param texture
549     * @param colors
550     * @return
551     */
552    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Collection<Color> colors, float loopTime) {
553        return animateActor(x, y, doubleWidth, texture, colors, loopTime, "");
554    }
555    /**
556     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which will be
557     * stretched to fit one cell, or two cells if doubleWidth is true.
558     * @param x
559     * @param y
560     * @param doubleWidth
561     * @param texture
562     * @param colors
563     * @return
564     */
565    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, TextureRegion texture, Collection<Color> colors, float loopTime, String name) {
566        Actor a = textFactory.makeActor(texture, colors, loopTime, doubleWidth, (doubleWidth ? 2 : 1) * cellWidth, cellHeight);
567        a.setName(name);
568        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
569        /*
570        if (doubleWidth)
571            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
572        else
573            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
574        */
575        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
576        animatedEntities.add(ae);
577        return ae;
578    }
579
580    /**
581     * Create an AnimatedEntity at position x, y, using a TextureRegion with no color modifications, which, if and only
582     * if stretch is true, will be stretched to fit one cell, or two cells if doubleWidth is true. If stretch is false,
583     * this will preserve the existing size of texture.
584     * @param x
585     * @param y
586     * @param doubleWidth
587     * @param stretch
588     * @param texture
589     * @return
590     */
591    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, boolean stretch, TextureRegion texture)
592    {
593        Actor a = (stretch)
594                ? textFactory.makeActor(texture, Color.WHITE, (doubleWidth ? 2 : 1) * cellWidth, cellHeight)
595                : textFactory.makeActor(texture, Color.WHITE, texture.getRegionWidth(), texture.getRegionHeight());
596        a.setName("");
597        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
598        /*
599        if(doubleWidth)
600            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight - textFactory.getDescent() + getY());
601        else
602            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight  - textFactory.getDescent() + getY());
603        */
604        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
605        animatedEntities.add(ae);
606        return ae;
607    }
608
609    /**
610     * Create an AnimatedEntity at position x, y, using a TextureRegion with the given color, which, if and only
611     * if stretch is true, will be stretched to fit one cell, or two cells if doubleWidth is true. If stretch is false,
612     * this will preserve the existing size of texture.
613     * @param x
614     * @param y
615     * @param doubleWidth
616     * @param stretch
617     * @param texture
618     * @param color
619     * @return
620     */
621    public AnimatedEntity animateActor(int x, int y, boolean doubleWidth, boolean stretch, TextureRegion texture, Color color) {
622
623        Actor a = (stretch)
624                ? textFactory.makeActor(texture, color, (doubleWidth ? 2 : 1) * cellWidth, cellHeight)
625                : textFactory.makeActor(texture, color, texture.getRegionWidth(), texture.getRegionHeight());
626        a.setName("");
627        a.setPosition(adjustX(x, doubleWidth), adjustY(y));
628        /*
629        if (doubleWidth)
630            a.setPosition(x * 2 * cellWidth + getX(), (gridHeight - y - 1) * cellHeight  - textFactory.getDescent() + getY());
631        else
632            a.setPosition(x * cellWidth + getX(), (gridHeight - y - 1) * cellHeight  - textFactory.getDescent() + getY());
633            */
634        AnimatedEntity ae = new AnimatedEntity(a, x, y, doubleWidth);
635        animatedEntities.add(ae);
636        return ae;
637    }
638
639    /**
640         * Use this method if you use your own {@link IColorCenter} and want this
641         * panel not to allocate its own colors (or fill
642         * {@link DefaultResources#getSCC()} but instead to the provided center.
643         * 
644         * @param scc
645         *            The color center to use. Should not be {@code null}.
646         * @throws NullPointerException
647         *             If {@code scc} is {@code null}.
648         */
649        public void setColorCenter(IColorCenter<Color> scc) {
650            super.setColorCenter(scc);
651        }
652
653    /**
654     * Gets a "snapshot" of the data represented by this ImageSquidPanel; stores the dimensions, the char data, and the
655     * color data in a way that can be set back to a SquidPanel or ImageSquidPanel using
656     * {@link #setFromSnapshot(String, int, int, int, int)} or its overload that takes a StringBuilder. The actual
657     * contents of the returned StringBuilder are unlikely to be legible in any way if read as text, and are meant to be
658     * concise and stable across versions.
659     * <br>
660     * NOTE: For this version, the mapping of chars to images is not stored in the snapshot, allowing alternate mappings
661     * to be used, such as while graphics are being updated frequently. This also allows the snapshot to be read in from
662     * both normal SquidPanels and ImageSquidPanels.
663     * @return a StringBuilder representation of this SquidPanel's data that can be passed later to {@link #setFromSnapshot(StringBuilder, int, int, int, int)} or converted to String and passed to its overload
664     */
665    public StringBuilder getSnapshot()
666    {
667        return getSnapshot(0, 0, gridWidth, gridHeight);
668    }
669    /**
670     * Gets a "snapshot" of the data represented by this ImageSquidPanel; stores the dimensions, the char data, and the
671     * color data in a way that can be set back to a SquidPanel or ImageSquidPanel using
672     * {@link #setFromSnapshot(String, int, int, int, int)} or its overload that takes a StringBuilder. The actual
673     * contents of the returned StringBuilder are unlikely to be legible in any way if read as text, and are meant to be
674     * concise and stable across versions. This overload allows the first x and y position used to be specified, as well
675     * as the width and height to use (the actual width and height stored may be different if this SquidPanel's
676     * gridWidth and/or gridHeight are smaller than the width and/or height given).
677     * <br>
678     * NOTE: For this version, the mapping of chars to images is not stored in the snapshot, allowing alternate mappings
679     * to be used, such as while graphics are being updated frequently. This also allows the snapshot to be read in from
680     * both normal SquidPanels and ImageSquidPanels.
681     * @param startX the first x position to use in the snapshot, inclusive
682     * @param startY the first y position to use in the snapshot, inclusive
683     * @param width how wide the snapshot area should be; x positions from startX to startX + width - 1 will be used
684     * @param height how tall the snapshot area should be; y positions from startY to startY + height - 1 will be used
685     * @return a StringBuilder representation of this SquidPanel's data that can be passed later to {@link #setFromSnapshot(StringBuilder, int, int, int, int)} or converted to String and passed to its overload
686     */
687    public StringBuilder getSnapshot(int startX, int startY, int width, int height) {
688        width = Math.min(gridWidth - startX, width);
689        height = Math.min(gridHeight - startY, height);
690        StringBuilder sb = new StringBuilder(width * height * 9 + 12);
691        sb.append(width).append('x').append(height).append(':');
692        for (int x = startX, i = 0; i < width; x++, i++) {
693            sb.append(contents[x], startY, height);
694        }
695        char[] reuse = new char[8];
696        for (int x = startX, i = 0; i < width; x++, i++) {
697            for (int y = startY, j = 0; j < height; y++, j++) {
698                sb.append(SColor.floatToChars(reuse, colors[x][y]));
699            }
700        }
701        return sb;
702    }
703
704    /**
705     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
706     * ImageSquidPanel from 0,0 (inclusive) up to the dimensions stored in the snapshot to match the snapshot's data.
707     * @param snapshot a StringBuilder in a special format as produced by {@link #getSnapshot(int, int, int, int)}
708     * @return this after setting, for chaining
709     */
710    public ImageSquidPanel setFromSnapshot(StringBuilder snapshot)
711    {
712        return setFromSnapshot(snapshot, 0, 0, -1, -1);
713    }
714    /**
715     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
716     * ImageSquidPanel from the position given by putX,putY (inclusive) up to the dimensions stored in the snapshot
717     * (considering putX and putY as offsets) so they have the values stored in the snapshot.
718     * @param snapshot a StringBuilder in a special format as produced by {@link #getSnapshot(int, int, int, int)}
719     * @param putX where to start placing the data from the snapshot, x position
720     * @param putY where to start placing the data from the snapshot, y position
721     * @return this after setting, for chaining
722     */
723    public ImageSquidPanel setFromSnapshot(StringBuilder snapshot, int putX, int putY)
724    {
725        return setFromSnapshot(snapshot, putX, putY, -1, -1);
726    }
727    /**
728     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
729     * ImageSquidPanel from the position given by putX,putY (inclusive) to putX+limitWidth,putY+limitHeight (exclusive)
730     * so they have the values stored in the snapshot. If limitWidth or limitHeight is negative, this uses the full
731     * width and height of the snapshot (stopping early if it would extend past the gridWidth or gridHeight of this
732     * ImageSquidPanel).
733     * @param snapshot a StringBuilder in a special format as produced by {@link #getSnapshot(int, int, int, int)}
734     * @param putX where to start placing the data from the snapshot, x position
735     * @param putY where to start placing the data from the snapshot, y position
736     * @param limitWidth if negative, uses all of snapshot's width as possible, otherwise restricts the width allowed
737     * @param limitHeight if negative, uses all of snapshot's height as possible, otherwise restricts the height allowed
738     * @return this after setting, for chaining
739     */
740    public ImageSquidPanel setFromSnapshot(StringBuilder snapshot, int putX, int putY, int limitWidth, int limitHeight)
741    {
742        if(putX >= gridWidth || putY >= gridHeight || snapshot == null || snapshot.length() < 4) return this;
743        if(putX < 0) putX = 0;
744        if(putY < 0) putY = 0;
745        int start = snapshot.indexOf(":")+1, width = StringKit.intFromDec(snapshot),
746                height = StringKit.intFromDec(snapshot, snapshot.indexOf("x") + 1, start),
747                run = start;
748        if(limitWidth < 0)
749            limitWidth = Math.min(width, gridWidth - putX);
750        else
751            limitWidth = Math.min(limitWidth, Math.min(width, gridWidth - putX));
752
753        if(limitHeight < 0)
754            limitHeight = Math.min(height, gridHeight - putY);
755        else
756            limitHeight = Math.min(limitHeight, Math.min(height, gridHeight - putY));
757        for (int x = putX, i = 0; i < limitWidth; x++, i++, run += height) {
758            snapshot.getChars(run, run + limitHeight, contents[x], putY);
759        }
760        run = start + width * height;
761        for (int x = putX, i = 0; i < limitWidth; x++, i++) {
762            for (int y = putY, j = 0; j < limitHeight; y++, j++) {
763                colors[x][y] = SColor.charsToFloat(snapshot, run);
764                run += 8;
765            }
766        }
767        return this;
768    }
769
770    /**
771     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
772     * ImageSquidPanel from 0,0 (inclusive) up to the dimensions stored in the snapshot to match the snapshot's data.
773     * <br>
774     * This overload takes a String instead of a StringBuilder for potentially-easier loading from files.
775     * @param snapshot a String in a special format as produced by {@link #getSnapshot(int, int, int, int)}
776     * @return this after setting, for chaining
777     */
778    public ImageSquidPanel setFromSnapshot(String snapshot)
779    {
780        return setFromSnapshot(snapshot, 0, 0, -1, -1);
781    }
782    /**
783     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
784     * ImageSquidPanel from the position given by putX,putY (inclusive) up to the dimensions stored in the snapshot
785     * (considering putX and putY as offsets) so they have the values stored in the snapshot.
786     * <br>
787     * This overload takes a String instead of a StringBuilder for potentially-easier loading from files.
788     * @param snapshot a String in a special format as produced by {@link #getSnapshot(int, int, int, int)}
789     * @param putX where to start placing the data from the snapshot, x position
790     * @param putY where to start placing the data from the snapshot, y position
791     * @return this after setting, for chaining
792     */
793    public ImageSquidPanel setFromSnapshot(String snapshot, int putX, int putY)
794    {
795        return setFromSnapshot(snapshot, putX, putY, -1, -1);
796    }
797
798    /**
799     * Given a "snapshot" from {@link #getSnapshot(int, int, int, int)}, this assigns the chars and colors in this
800     * ImageSquidPanel from the position given by putX,putY (inclusive) to putX+limitWidth,putY+limitHeight (exclusive)
801     * so they have the values stored in the snapshot. If limitWidth or limitHeight is negative, this uses the full
802     * width and height of the snapshot (stopping early if it would extend past the gridWidth or gridHeight of this
803     * ImageSquidPanel).
804     * <br>
805     * This overload takes a String instead of a StringBuilder for potentially-easier loading from files.
806     * @param snapshot a String in a special format as produced by {@link #getSnapshot(int, int, int, int)}
807     * @param putX where to start placing the data from the snapshot, x position
808     * @param putY where to start placing the data from the snapshot, y position
809     * @param limitWidth if negative, uses all of snapshot's width as possible, otherwise restricts the width allowed
810     * @param limitHeight if negative, uses all of snapshot's height as possible, otherwise restricts the height allowed
811     * @return this after setting, for chaining
812     */
813
814    public ImageSquidPanel setFromSnapshot(String snapshot, int putX, int putY, int limitWidth, int limitHeight)
815    {
816        if(putX >= gridWidth || putY >= gridHeight || snapshot == null || snapshot.length() < 4) return this;
817        if(putX < 0) putX = 0;
818        if(putY < 0) putY = 0;
819        int start = snapshot.indexOf(":")+1, width = StringKit.intFromDec(snapshot),
820                height = StringKit.intFromDec(snapshot, snapshot.indexOf("x") + 1, start),
821                run = start;
822        if(limitWidth < 0)
823            limitWidth = Math.min(width, gridWidth - putX);
824        else
825            limitWidth = Math.min(limitWidth, Math.min(width, gridWidth - putX));
826
827        if(limitHeight < 0)
828            limitHeight = Math.min(height, gridHeight - putY);
829        else
830            limitHeight = Math.min(limitHeight, Math.min(height, gridHeight - putY));
831        for (int x = putX, i = 0; i < limitWidth; x++, i++, run += height) {
832            snapshot.getChars(run, run + limitHeight, contents[x], putY);
833        }
834        run = start + width * height;
835        for (int x = putX, i = 0; i < limitWidth; x++, i++) {
836            for (int y = putY, j = 0; j < limitHeight; y++, j++) {
837                colors[x][y] = SColor.charsToFloat(snapshot, run);
838                run += 8;
839            }
840        }
841        return this;
842    }
843
844    /**
845     * Makes it so when the char swapOut would be drawn, the TextureRegion swapIn is drawn instead.
846     * This will apply for as long as this ImageSquidPanel is in use unless swapIn is removed with
847     * {@link #removeImageSwap(char)} or directly changing this object's {@link #imageMap}.
848     * @param swapOut the char to avoid rendering and replace with an image
849     * @param swapIn the image to replace the character with
850     */
851    public void setImageSwap(final char swapOut, final TextureRegion swapIn)
852    {
853        if(swapIn != null)
854            imageMap.put(swapOut, swapIn);
855    }
856
857    /**
858     * Removes the char toRemove from the mapping of chars to replace with images, or does nothing if this did not
859     * already replace toRemove with an image. This means toRemove will render as itself, if present in the font.
860     * @param toRemove the char to render as a glyph instead of a texture
861     */
862    public void removeImageSwap(final char toRemove)
863    {
864        imageMap.remove(toRemove);
865    }
866
867    /**
868     * If there is a TextureRegion that would replace the char toFind when drawn, this will return that TextureRegion,
869     * otherwise it returns null.
870     * @param toFind the char to find the corresponding TextureRegion that it could be mapped to
871     * @return the corresponding TextureRegion if it exists, or null otherwise
872     */
873    public TextureRegion getImageSwap(final char toFind)
874    {
875        return imageMap.get(toFind);
876    }
877
878    /**
879     * Meant for taking easy-to-write chars and generating chars that can map to images, while still allowing the
880     * original easy-to-write char to be used as its own non-image char. Given a char that should be from the
881     * "relatively common" parts of Unicode (essentially, anything with a codepoint before 0x1000, or with a small risk
882     * of collision, anything before 0x8000), this returns a character from the "Private Use Area" of Unicode that will
883     * probably be unique for most kinds of input. 4096 possible chars can be returned by this method.
884     *
885     * As an example, you may want to map the char 'g' to an image of a goblin, but still sometimes show the actual 'g'
886     * for some other monster or a variant on goblins you don't have an image for. You could call
887     * {@code char goblinChar = ImageSquidPanel.getUnusedChar('g');} and then register goblinChar in the image mapping
888     * with {@code myImageSquidPanel.setImageSwap(goblinChar, goblinImage);}, which would allow you to draw 'g' directly
889     * by placing a 'g' with put(), or draw goblinImage by placing either goblinChar or
890     * {@code ImageSquidPanel.getUnusedChar('g')} with put().
891     *
892     * @param initial a char that ideally should be from the earlier parts of Unicode (before 0x1000 is ideal)
893     * @return a char from Unicode's private use area that is unlikely to be found otherwise or accidentally
894     */
895    public static char getUnusedChar(final char initial)
896    {
897        return (char)(((initial & 0x7000) >>> 3 ^ initial) & 0x0FFF | 0xE000);
898    }
899}