001package squidpony.squidgrid.gui.gdx;
002
003import java.util.HashMap;
004import java.util.Map;
005
006import com.badlogic.gdx.Gdx;
007import com.badlogic.gdx.graphics.Color;
008import com.badlogic.gdx.graphics.g2d.Batch;
009import com.badlogic.gdx.graphics.g2d.BitmapFont;
010import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
011import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
012import com.badlogic.gdx.scenes.scene2d.Actor;
013
014import squidpony.IColorCenter;
015import squidpony.SquidTags;
016import squidpony.panel.IColoredString;
017import squidpony.panel.IMarkup;
018
019/**
020 * An actor capable of drawing {@link IColoredString}s. It is lines-oriented:
021 * putting a line erases the previous line. It is designed for panels to write
022 * text with a serif font (where {@link SquidPanel} is less appropriate if you
023 * want tight serif display).
024 * 
025 * <p>
026 * This
027 * <a href="https://twitter.com/hgamesdev/status/709147572548575233">tweet</a>
028 * shows an example. The panel at the top of the screenshot is implemented using
029 * this class.
030 * </p>
031 * 
032 * <p>
033 * Contrary to {@link SquidMessageBox}, this panel doesn't support scrolling
034 * (for now). So it's suited when it is fine forgetting old messages (as in
035 * brogue's messages area).
036 * </p>
037 * 
038 * <p>
039 * The usual manner in which this panel is used is with the
040 * {@link #put(int, int, IColoredString)} method, using {@code 0} as the first
041 * parameter, and making {@code y} vary to display on different lines.
042 * </p>
043 * 
044 * @author smelC
045 * 
046 * @see SquidMessageBox An alternative, doing similar lines-drawing business,
047 *      but being backed up by {@link SquidPanel}.
048 */
049public class LinesPanel<T extends Color> extends Actor {
050
051        protected final IMarkup<T> markup;
052
053        public final BitmapFont font;
054
055        /** The number of (vertical) lines that this panel covers */
056        public final int vlines;
057
058        protected boolean gdxYStyle = false;
059
060        protected final T defaultTextColor;
061        protected final T bgColor;
062
063        protected final Map<Integer, ToDraw<T>> yToLine;
064
065        /** The offset applied when drawing */
066        protected int yoffset = 0;
067
068        /** The color center to filter colors, or {@code null}. */
069        protected /* @Nullable */ IColorCenter<T> colorCenter;
070
071        public boolean clearBeforeDrawing = true;
072
073        /**
074         * Lazily allocated, and kept in-between calls to
075         * {@link #draw(Batch, float)} to avoid bloating the heap if drawing is done
076         * at every frame.
077         */
078        private /* @Nullable */ ShapeRenderer renderer;
079
080        /**
081         * <b>This call sets markup in {@code font}'s data.</b>
082         * 
083         * @param markup
084         *            The markup to use
085         * @param font
086         *            The font to use.
087         * @param pixelwidth
088         *            The width (in pixels) that {@code this} must have.
089         * @param pixelheight
090         *            The height (in pixels) that {@code this} must have.
091         * @param defaultTextColor
092         *            The color to use when buckets of {@link IColoredString}
093         *            contains {@code null} for the color.
094         * @param bgColor
095         *            The background color to use.
096         */
097        public LinesPanel(IMarkup<T> markup, BitmapFont font, int pixelwidth, int pixelheight, T bgColor,
098                        T defaultTextColor) {
099                this.markup = markup;
100                font.getData().markupEnabled |= true;
101                this.font = font;
102
103                this.vlines = (int) Math.floor(pixelheight / font.getLineHeight());
104                this.defaultTextColor = defaultTextColor;
105                this.bgColor = bgColor;
106                this.yToLine = new HashMap<Integer, ToDraw<T>>();
107
108                setWidth(pixelwidth);
109                setHeight(pixelheight);
110        }
111
112        /**
113         * Writes {@code c} at (x, y).
114         * 
115         * @param x
116         *            The x offset, in terms of width of a character in the font.
117         * @param y
118         *            The y offset, in terms of the height of the font.
119         * @param c
120         *            What to write
121         * @param color
122         *            The color to use. Can be {@code null}.
123         */
124        public void put(int x, int y, char c, /* @Nullable */ T color) {
125                final IColoredString<T> buf = IColoredString.Impl.<T> create();
126                buf.append(c, color);
127                put(x, y, buf);
128        }
129
130        /**
131         * Writes {@code string} at (x, y).
132         * 
133         * @param x
134         *            The x offset, in terms of width of a character in the font.
135         * @param y
136         *            The y offset, in terms of the height of the font.
137         * @param string
138         *            What to write
139         * @param color
140         *            The color to use. Can be {@code null}.
141         */
142        public void put(int x, int y, String string, /* @Nullable */ T color) {
143                final IColoredString<T> buf = IColoredString.Impl.<T> create();
144                buf.append(string, color);
145                put(x, y, buf);
146        }
147
148        /**
149         * Writes {@code ics} at (x, y).
150         * 
151         * @param x
152         *            The x offset, in terms of width of a character in the font.
153         * @param y
154         *            The y offset, in terms of the height of the font.
155         * @param ics
156         *            What to write
157         */
158        public void put(int x, int y, IColoredString<T> ics) {
159                if (vlines <= y) {
160                        Gdx.app.log(SquidTags.LAYOUT, getClass().getSimpleName() + "'s height is " + vlines + " cell"
161                                        + (vlines == 1 ? "" : "s") + ", but it is receiving a request to draw at height " + y);
162                        return;
163                }
164                yToLine.put(gdxYStyle ? vlines - y : y, new ToDraw<T>(x, ics));
165        }
166
167        /**
168         * You should not change this value while text has been drawn already. It
169         * should not be changed after allocating {@code this}. By default it is
170         * {@code false}.
171         * 
172         * @param b
173         *            {@code true} for {@code y=0} to be the bottom (gdx-style).
174         *            {@code false} for {@code y=0} to be the top (squidlib-style).
175         */
176        public void setGdxYStyle(boolean b) {
177                this.gdxYStyle = b;
178        }
179
180        /**
181         * @return The y offset applied to every drawing.
182         */
183        public int getYOffset() {
184                return yoffset;
185        }
186
187        /**
188         * Use that if you wanna draw top-aligned, or bottom-aligned, or are doing
189         * funky stuff.
190         * 
191         * @param offset
192         *            The offset to apply to every drawing (can be negative).
193         */
194        public void setYOffset(int offset) {
195                this.yoffset = offset;
196        }
197
198        @Override
199        public void draw(Batch batch, float parentAlpha) {
200                if (clearBeforeDrawing) {
201                        batch.end();
202
203                        clearArea(bgColor);
204
205                        batch.begin();
206                }
207
208                final float charwidth = font.getSpaceWidth();
209                final float charheight = font.getLineHeight();
210                final float x = getX();
211                final float y = getY() + (charheight / 2);
212
213                /*
214                 * Do not fetch memory (field read) at every loop roll, you're not
215                 * supposed to change #yoffset in the middle of this call anyway.
216                 */
217                final int yoff = yoffset;
218
219                for (Map.Entry<Integer, ToDraw<T>> yAndLine : yToLine.entrySet()) {
220                        final ToDraw<T> todraw = yAndLine.getValue();
221                        final int key = yAndLine.getKey();
222                        final float xdest = x + (todraw.x * charwidth);
223                        final float ydest = y + (key * charheight) + charheight + yoff;
224                        final IColoredString<T> shown = colorCenter == null ? todraw.ics : colorCenter.filter(todraw.ics);
225                        font.draw(batch, shown.presentWithMarkup(markup), xdest, ydest);
226                }
227        }
228
229        public void dispose() {
230                if (renderer != null)
231                        renderer.dispose();
232        }
233
234        /**
235         * @param cc
236         *            The color center to use. Used to change the font color using
237         *            {@link IColorCenter#filter(Object)}.
238         */
239        public void setColorCenter(IColorCenter<T> cc) {
240                this.colorCenter = cc;
241        }
242
243        /**
244         * Paints this panel with {@code color}.
245         * 
246         * @param color
247         */
248        public void clearArea(T color) {
249                UIUtil.drawRectangle(getX(), getY(), getWidth(), getHeight(), ShapeType.Filled, color);
250                if (renderer == null) {
251                        renderer = new ShapeRenderer();
252                        renderer.setColor(color);
253                } else
254                        renderer.setColor(color);
255                renderer.begin(ShapeType.Filled);
256                renderer.rect(getX(), getY(), getWidth(), getHeight());
257                renderer.end();
258        }
259
260        @Override
261        public String toString() {
262                return super.toString() + " vlines:" + vlines + " fontwidth:" + font.getSpaceWidth() + " fontheight:"
263                                + font.getLineHeight();
264        }
265
266        /**
267         * @author smelC
268         * 
269         * @param <T>
270         *            The type of color
271         */
272        protected static class ToDraw<T> {
273
274                final int x;
275                final IColoredString<T> ics;
276
277                ToDraw(int x, IColoredString<T> ics) {
278                        this.x = x;
279                        this.ics = ics;
280                }
281
282                @Override
283                public String toString() {
284                        return (x == 0 ? "" : (">" + x + " ")) + ics.toString();
285                }
286        }
287
288}