001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.ScreenAdapter;
004import com.badlogic.gdx.graphics.Color;
005import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
006import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
007import com.badlogic.gdx.scenes.scene2d.Stage;
008import com.badlogic.gdx.utils.viewport.ScreenViewport;
009
010import squidpony.IColorCenter;
011
012/**
013 * A SquidLib-aware partial implementation of {@link ScreenAdapter}. This is a
014 * very general implementation (on which I rely in my game), please do not
015 * change that too radically.
016 * 
017 * <p>
018 * By implementing {@link #getNext()}, you specify how to switch between screens
019 * (for example: splash screen -> (main menu screen <-> game screen)). To build
020 * your {@link SquidPanel}, you should use the protected methods that this class
021 * provides. In this way, you won't have to worry about the screen size and
022 * resizes.
023 * </p>
024 * 
025 * <p>
026 * Moving from a screen to another is either triggered by libgdx (when it calls
027 * {@link #resize(int, int)} and {@link #dispose()}) or by you (you can call
028 * {@link #dispose()} directly). In both cases, it'll make
029 * {@link SquidApplicationAdapter}'s
030 * {@link com.badlogic.gdx.ApplicationAdapter#render()} method call
031 * {@link #getNext()}, hereby triggering screen change.
032 * </p>
033 * 
034 * <p>
035 * There really is now way around this class being abstract. Very often, the
036 * result of {@link #getNext()} cannot be precomputed. For example after a game
037 * screen, either you'll go to the win screen or the loose screen. And the
038 * latters cannot be precomputed when building the game screen :-(
039 * </p>
040 * 
041 * @author smelC
042 * 
043 * @param <T>
044 *            The type of color
045 */
046public abstract class AbstractSquidScreen<T extends Color> extends ScreenAdapter {
047
048        /**
049         * The current size manager. It is always up-to-date w.r.t. to the actual
050         * screen's size, except when a call to {@link #resize(int, int)} has been
051         * done by libgdx, and {@link #getNext()} wasn't called yet.
052         */
053        protected ScreenSizeManager sizeManager;
054
055        protected final IColorCenter<T> colorCenter;
056        protected final IPanelBuilder ipb;
057
058        /**
059         * It is up to subclassers to initialize this field. Beware that is disposed
060         * if non-null in {@link #dispose()}. Usually it is assigned at construction
061         * time or in {@link #render(float)}.
062         */
063        protected Stage stage;
064
065        protected boolean disposed = false;
066        protected boolean resized = false;
067
068        private ShapeRenderer renderer = null;
069
070        /**
071         * Here's what the content of {@code ssi} must be:
072         * 
073         * <ol>
074         * <li>A size manager that is correct w.r.t. to the current screen size. It
075         * is usually built by inspecting the current screen size (see
076         * {@link com.badlogic.gdx.Gdx#graphics}) and a cell size you want.
077         * 
078         * <p>
079         * The screen's size is not computed automatically by this constructor,
080         * because usually you can build a single instance of
081         * {@link ScreenSizeManager} at startup, and pass it along all
082         * {@link AbstractSquidScreen}. The instance will only change when there's
083         * some resizing (i.e. when ligdx calls
084         * {@link ScreenAdapter#resize(int, int)}).
085         * </p>
086         * </li>
087         * <li>The color center to use.</li>
088         * <li>How to build fresh {@link SquidPanel}.</li>
089         * </ol>
090         */
091        public AbstractSquidScreen(SquidScreenInput<T> ssi) {
092                this.sizeManager = ssi.ssm;
093                this.colorCenter = ssi.icc;
094                this.ipb = ssi.ipb;
095        }
096
097        @Override
098        public void dispose() {
099                if (!disposed) {
100                        /* This should likely make getNext()'s behavior change */
101                        disposed = true;
102
103                        /*
104                         * Either we're moving to a new screen, in which case this avoids
105                         * glitches if the new screen does not paint everything; or we're
106                         * leaving in which case we don't care.
107                         */
108                        clearScreen();
109
110                        if (renderer != null)
111                                renderer.dispose();
112
113                        if (stage != null) {
114                                stage.dispose();
115                                /* It should not be reused, hence: */
116                                stage = null;
117                        }
118                }
119        }
120
121        @Override
122        public void resize(int width, int height) {
123                /* In some implementations, this make getNext()'s behavior change */
124                this.resized |= true;
125                this.sizeManager = sizeManager.changeScreenSize(width, height);
126                if (disposeAtResize())
127                        dispose();
128        }
129
130        /**
131         * Implementations of this method should likely inspect {@link #disposed}
132         * and {@link #resized}. When {@link #resized} holds, this method typically
133         * returns an instance that is a variation of {@code this}, where the font
134         * size has been changed (to get the new size, use {@link #sizeManager}; it
135         * has been updated already). When {@link #disposed} holds, the usual
136         * behavior is for this method to return null to quit the whole application
137         * (that's the assumption that
138         * {@link squidpony.squidgrid.gui.gdx.SquidApplicationAdapter} does) or to
139         * return another screen to move forward (for example when switching from
140         * the main/splash screen to the game's screen).
141         * 
142         * <p>
143         * This method is normally called when the user is moving to the next screen
144         * (so that's your game logic) or when {@link #isDisposed()} holds or when
145         * {@link #hasPendingResize()} holds.
146         * </p>
147         * 
148         * @return The screen to use after this one, or {@code null} if the
149         *         application is quitting.
150         */
151        public abstract /* @Nullable */ AbstractSquidScreen<T> getNext();
152
153        /**
154         * @return Whether libgdx called {@link #dispose()} on {@code this}.
155         */
156        public boolean isDisposed() {
157                return disposed;
158        }
159
160        /**
161         * @return Whether libgdx called {@link #resize(int, int)} on {@code this}.
162         */
163        public boolean hasPendingResize() {
164                return resized;
165        }
166
167        /**
168         * Ideally, you should always go through this method to create a
169         * {@link SquidPanel}.
170         * 
171         * @return How this class builds {@link SquidPanel}.
172         */
173        public IPanelBuilder getPanelbuilder() {
174                return ipb;
175        }
176
177        /**
178         * @return The content required to build another {@link AbstractSquidScreen}
179         *         from {@code this}'s content.
180         */
181        public SquidScreenInput<T> toSquidScreenInput() {
182                return new SquidScreenInput<>(sizeManager, colorCenter, ipb);
183        }
184
185        /**
186         * @param desiredCellSize
187         * @return A screen wide squid panel, margins-aware, and with its position
188         *         set.
189         */
190        protected final SquidPanel buildScreenWideSquidPanel(int desiredCellSize) {
191                return ipb.buildScreenWide(sizeManager.screenWidth, sizeManager.screenHeight, desiredCellSize, null);
192        }
193
194        /**
195         * @return A screen wide squid panel, margins-aware, and with its position
196         *         set. It uses the current cell size.
197         */
198        protected final SquidPanel buildScreenWideSquidPanel() {
199                final SquidPanel result = buildSquidPanel(sizeManager.wCells, sizeManager.hCells);
200                /* TODO smelC Draw margins ? */
201                result.setPosition(sizeManager.leftMargin, sizeManager.botMargin);
202                return result;
203        }
204
205        /**
206         * @param width
207         * @param height
208         * @return A panel of size {@code (width, height)} that uses the default
209         *         cell width/cell height. Its position isn't set.
210         */
211        protected final SquidPanel buildSquidPanel(int width, int height) {
212                return buildSquidPanel(width, height, sizeManager.cellWidth, sizeManager.cellHeight);
213        }
214
215        /**
216         * @param width
217         * @param height
218         * @param cellWidth
219         * @param cellHeight
220         * @return A panel of size {@code (width, height)} that has {@code cellSize}
221         *         . Its position isn't set.
222         */
223        protected final SquidPanel buildSquidPanel(int width, int height, int cellWidth, int cellHeight) {
224                return ipb.buildByCells(width, height, cellWidth, cellHeight, null);
225        }
226
227        /* Default implementation, feel free to override */
228        protected Stage buildStage() {
229                return new Stage(new ScreenViewport());
230        }
231
232        /**
233         * @return Whether this screen should be thrown away when a resize event
234         *         occurs.
235         */
236        /* You should return false if you handle resizing on your own */
237        protected boolean disposeAtResize() {
238                return true;
239        }
240
241        /**
242         * @return The color to use to repaint the screen entirely in
243         *         {@link #clearScreen()} (used in this class when moving from a
244         *         {@link AbstractSquidScreen} to another).
245         */
246        protected T getClearingColor() {
247                return colorCenter.getBlack();
248        }
249
250        protected void clearScreen() {
251                final T c = getClearingColor();
252                if (renderer == null)
253                        renderer = new ShapeRenderer();
254                UIUtil.drawRectangle(renderer, 0, 0, sizeManager.screenWidth, sizeManager.screenHeight,
255                                ShapeType.Filled, c);
256        }
257
258        /**
259         * A dumb container, to avoid having too many parameters to
260         * {@link AbstractSquidScreen}'s constructor.
261         * 
262         * @author smelC
263         * 
264         * @param <T>
265         */
266        public static class SquidScreenInput<T> {
267
268                public final ScreenSizeManager ssm;
269                public final IColorCenter<T> icc;
270                public final IPanelBuilder ipb;
271
272                public SquidScreenInput(ScreenSizeManager ssm, IColorCenter<T> icc, IPanelBuilder ipb) {
273                        this.ssm = ssm;
274                        this.icc = icc;
275                        this.ipb = ipb;
276                }
277
278        }
279}