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}