001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.Gdx;
004import com.badlogic.gdx.scenes.scene2d.Group;
005
006import squidpony.IColorCenter;
007import squidpony.SquidTags;
008import squidpony.panel.IColoredString;
009import squidpony.panel.ICombinedPanel;
010import squidpony.panel.ISquidPanel;
011
012/**
013 * An implementation of {@link ICombinedPanel} that extends libGDX's Group. If
014 * you're a new user or need only a foreground and background, it's likely what
015 * you should use.
016 *
017 * this is a concrete implementation of {@link ICombinedPanel} that you should
018 * use if you're concretely in need of a panel to display/write to, without
019 * doing fancy GUI stuff. Because it extends libGDX's {@link Group}, it offers a
020 * lot of features.
021 * 
022 * @see SquidLayers for a more advanced Group that supports multiple layers.
023 * @author smelC
024 * 
025 * @param <T>
026 *            The type of colors.
027 */
028public class GroupCombinedPanel<T> extends Group implements ICombinedPanel<T> {
029
030        protected/* @Nullable */ISquidPanel<T> bg;
031        protected/* @Nullable */ISquidPanel<T> fg;
032
033        /**
034         * @param bg
035         *            The backing background panel. Typically a {@link SquidPanel}.
036         * @param fg
037         *            The backing foreground panel. Typically a {@link SquidPanel}.
038         * @throws IllegalStateException
039         *             In various cases of errors regarding sizes of panels.
040         */
041        public GroupCombinedPanel(ISquidPanel<T> bg, ISquidPanel<T> fg) {
042                if (bg != null && fg != null)
043                        setPanels(bg, fg);
044        }
045
046        /**
047         * Constructor that defer providing the backing panels. Useful for
048         * subclasses that compute their size after being constructed. Use
049         * {@link #setPanels(ISquidPanel, ISquidPanel)} to set the panels (required
050         * before calling any {@code put} method).
051         *
052         * <p>
053         * Width and height are computed using the provided panels.
054         * </p>
055         */
056        public GroupCombinedPanel() {
057        }
058
059        /**
060         * Sets the backing panels.
061         *
062         * @param bg
063         *            Typically a {@link SquidPanel}.
064         * @param fg
065         *            Typically a {@link SquidPanel}.
066         * @throws IllegalStateException
067         *             In various cases of errors regarding sizes of panels.
068         */
069        public final void setPanels(ISquidPanel<T> bg, ISquidPanel<T> fg) {
070                if (bg == null || fg == null) {
071                        Gdx.app.log(SquidTags.LAYOUT,
072                                        "You should not call this method with a null panel. Avoiding an NPE, but you should fix that.");
073                        return;
074                }
075
076                if (this.bg != null)
077                        throw new IllegalStateException("Cannot change the background panel");
078                this.bg = bg;
079
080                if (this.fg != null)
081                        throw new IllegalStateException("Cannot change the foreground panel");
082                this.fg = fg;
083
084                final int bgw = bg.gridWidth();
085                final int bgh = bg.gridHeight();
086
087                if (bgw != fg.gridWidth())
088                        throw new IllegalStateException("Cannot build a combined panel with backers of different widths");
089                if (bgh != fg.gridHeight())
090                        throw new IllegalStateException(
091                                        "Cannot build a combined panel with backers of different heights");
092
093                addActors();
094        }
095
096        @Override
097        public void putFG(int x, int y, char c) {
098                checkFG();
099                fg.put(x, y, c);
100        }
101
102        @Override
103        public void putFG(int x, int y, char c, T color) {
104                checkFG();
105                fg.put(x, y, c, color);
106        }
107
108        @Override
109        public void putFG(int x, int y, String string, T foreground) {
110                checkFG();
111                fg.put(x, y, string, foreground);
112        }
113
114        @Override
115        public void putFG(int x, int y, IColoredString<T> cs) {
116                checkFG();
117                fg.put(x, y, cs);
118        }
119
120        @Override
121        public void putBG(int x, int y, T color) {
122                checkBG();
123                bg.put(x, y, color);
124        }
125
126        @Override
127        public void put(int x, int y, char c, T background, T foreground) {
128                checkFG();
129                checkBG();
130                bg.put(x, y, background);
131                fg.put(x, y, c, foreground);
132        }
133
134        @Override
135        public void put(int x, int y, T background, IColoredString<T> cs) {
136                checkFG();
137                checkBG();
138                final int w = getGridWidth();
139                final int l = cs.length();
140                for (int i = x; i < l && i < w; i++) {
141                        bg.put(i, y, background);
142                }
143                fg.put(x, y, cs);
144        }
145
146        @Override
147        public void put(int x, int y, String s, T background, T foreground) {
148                checkFG();
149                checkBG();
150                final int w = getGridWidth();
151                final int l = s.length();
152                for (int i = x; i < l && i < w; i++) {
153                        bg.put(i, y, background);
154                }
155                fg.put(x, y, s, foreground);
156        }
157
158        /**
159         * Writes {@code string} at the bottom right. If {@code string} is wider
160         * than {@code this}, its end will be stripped.
161         * 
162         * @param string
163         */
164        public void putBottomRight(IColoredString<? extends T> string) {
165                final int width = bg.gridWidth();
166                final int len = string.length();
167                final int x = len < width ? width - len : 0;
168                fg.put(x, bg.gridHeight() - 1, string);
169        }
170
171        /**
172         * Writes {@code string} at the bottom left. If {@code string} is wider than
173         * {@code this}, its end will be stripped.
174         * 
175         * @param string
176         */
177        public void putBottomLeft(IColoredString<? extends T> string) {
178                fg.put(0, bg.gridHeight() - 1, string);
179        }
180
181        @Override
182        public void fill(What what, T color) {
183                final int gridWidth = getGridWidth();
184                final int gridHeight = getGridHeight();
185
186                final boolean bfg = what.hasFG();
187                final boolean bbg = what.hasBG();
188
189                for (int x = 0; x < gridWidth; x++) {
190                        for (int y = 0; y < gridHeight; y++) {
191                                if (bfg)
192                                        putFG(x, y, ' ', color);
193                                if (bbg)
194                                        putBG(x, y, color);
195                        }
196                }
197        }
198
199        @Override
200        public boolean hasActiveAnimations() {
201                return (bg != null && bg.hasActiveAnimations()) || (fg != null && fg.hasActiveAnimations());
202        }
203
204        @Override
205        public void setColorCenter(IColorCenter<T> icc) {
206                bg.setColorCenter(icc);
207                fg.setColorCenter(icc);
208        }
209
210        /**
211         * @return The backer's width
212         * @throws IllegalStateException
213         *             If backers aren't set yet.
214         */
215        public int getGridWidth() {
216                if (bg == null)
217                        throw new NullPointerException("The background panel must be set before requesting the width");
218                return bg.gridWidth();
219        }
220
221        /**
222         * @return The backer's height
223         * @throws IllegalStateException
224         *             If backers aren't set yet.
225         */
226        public int getGridHeight() {
227                if (bg == null)
228                        throw new NullPointerException("The background panel must be set before requesting the height");
229                return bg.gridHeight();
230        }
231
232        /**
233         * @return The width of cells in the backing panel.
234         */
235        public int cellWidth() {
236                return bg.cellWidth();
237        }
238
239        /**
240         * @return The height of cells in the backing panel.
241         */
242        public int cellHeight() {
243                return bg.cellHeight();
244        }
245
246        protected void addActors() {
247                addActor((SquidPanel) bg.getBacker());
248                addActor((SquidPanel) fg.getBacker());
249        }
250
251        protected void checkFG() {
252                if (fg == null)
253                        throw new NullPointerException("The foreground panel must be set before writing to it");
254        }
255
256        protected void checkBG() {
257                if (bg == null)
258                        throw new NullPointerException("The background panel must be set before writing to it");
259        }
260
261        @Override
262        public String toString() {
263                return this.getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
264        }
265
266}