package squidpony.squidgrid.gui.gdx;
import com.badlogic.gdx.ScreenAdapter;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import squidpony.IColorCenter;
/**
* A SquidLib-aware partial implementation of {@link ScreenAdapter}. This is a
* very general implementation (on which I rely in my game), please do not
* change that too radically.
*
* <p>
* By implementing {@link #getNext()}, you specify how to switch between screens
* (for example: splash screen -> (main menu screen <-> game screen)). To build
* your {@link SquidPanel}, you should use the protected methods that this class
* provides. In this way, you won't have to worry about the screen size and
* resizing.
* </p>
*
* <p>
* Moving from a screen to another is either triggered by libgdx (when it calls
* {@link #resize(int, int)} and {@link #dispose()}) or by you (you can call
* {@link #dispose()} directly). In both cases, it'll make
* {@link SquidApplicationAdapter}'s
* {@link com.badlogic.gdx.ApplicationAdapter#render()} method call
* {@link #getNext()}, hereby triggering screen change.
* </p>
*
* <p>
* There really is now way around this class being abstract. Very often, the
* result of {@link #getNext()} cannot be precomputed. For example after a game
* screen, either you'll go to the win screen or the lose screen. And the
* latter cannot be precomputed when building the game screen :-(
* </p>
*
* @author smelC
*
* @param <T>
* The type of color
*/
public abstract class AbstractSquidScreen<T extends Color> extends ScreenAdapter {
/**
* The current size manager. It is always up-to-date w.r.t. to the actual
* screen's size, except when a call to {@link #resize(int, int)} has been
* done by libgdx, and {@link #getNext()} wasn't called yet.
*/
protected ScreenSizeManager sizeManager;
protected final IColorCenter<T> colorCenter;
protected final IPanelBuilder ipb;
/**
* It is up to subclassers to initialize this field. Beware that is disposed
* if non-null in {@link #dispose()}. Usually it is assigned at construction
* time or in {@link #render(float)}.
*/
protected Stage stage;
protected boolean disposed = false;
protected boolean resized = false;
private ShapeRenderer renderer = null;
/**
* Here's what the content of {@code ssi} must be:
*
* <ol>
* <li>A size manager that is correct w.r.t. to the current screen size. It
* is usually built by inspecting the current screen size (see
* {@link com.badlogic.gdx.Gdx#graphics}) and a cell size you want.
*
* <p>
* The screen's size is not computed automatically by this constructor,
* because usually you can build a single instance of
* {@link ScreenSizeManager} at startup, and pass it along all
* {@link AbstractSquidScreen}. The instance will only change when there's
* some resizing (i.e. when ligdx calls
* {@link ScreenAdapter#resize(int, int)}).
* </p>
* </li>
* <li>The color center to use.</li>
* <li>How to build fresh {@link SquidPanel}.</li>
* </ol>
*/
public AbstractSquidScreen(SquidScreenInput<T> ssi) {
this.sizeManager = ssi.ssm;
this.colorCenter = ssi.icc;
this.ipb = ssi.ipb;
}
@Override
public void dispose() {
if (!disposed) {
/* This should likely make getNext()'s behavior change */
disposed = true;
/*
* Either we're moving to a new screen, in which case this avoids
* glitches if the new screen does not paint everything; or we're
* leaving in which case we don't care.
*/
clearScreen();
if (renderer != null)
renderer.dispose();
if (stage != null) {
stage.dispose();
/* It should not be reused, hence: */
stage = null;
}
}
}
@Override
public void resize(int width, int height) {
/* In some implementations, this make getNext()'s behavior change */
this.resized |= true;
this.sizeManager = sizeManager.changeScreenSize(width, height);
if (disposeAtResize())
dispose();
}
/**
* Implementations of this method should likely inspect {@link #disposed}
* and {@link #resized}. When {@link #resized} holds, this method typically
* returns an instance that is a variation of {@code this}, where the font
* size has been changed (to get the new size, use {@link #sizeManager}; it
* has been updated already). When {@link #disposed} holds, the usual
* behavior is for this method to return null to quit the whole application
* (that's the assumption that
* {@link squidpony.squidgrid.gui.gdx.SquidApplicationAdapter} does) or to
* return another screen to move forward (for example when switching from
* the main/splash screen to the game's screen).
*
* <p>
* This method is normally called when the user is moving to the next screen
* (so that's your game logic) or when {@link #isDisposed()} holds or when
* {@link #hasPendingResize()} holds.
* </p>
*
* @return The screen to use after this one, or {@code null} if the
* application is quitting.
*/
public abstract /* @Nullable */ AbstractSquidScreen<T> getNext();
/**
* @return Whether libgdx called {@link #dispose()} on {@code this}.
*/
public boolean isDisposed() {
return disposed;
}
/**
* @return Whether libgdx called {@link #resize(int, int)} on {@code this}.
*/
public boolean hasPendingResize() {
return resized;
}
/**
* Ideally, you should always go through this method to create a
* {@link SquidPanel}.
*
* @return How this class builds {@link SquidPanel}.
*/
public IPanelBuilder getPanelBuilder() {
return ipb;
}
/**
* @return The content required to build another {@link AbstractSquidScreen}
* from {@code this}'s content.
*/
public SquidScreenInput<T> toSquidScreenInput() {
return new SquidScreenInput<>(sizeManager, colorCenter, ipb);
}
/**
* @param desiredCellSize
* @return A screen wide squid panel, margins-aware, and with its position
* set.
*/
protected final SquidPanel buildScreenWideSquidPanel(int desiredCellSize) {
return ipb.buildScreenWide(sizeManager.screenWidth, sizeManager.screenHeight, desiredCellSize, null);
}
/**
* @return A screen wide squid panel, margins-aware, and with its position
* set. It uses the current cell size.
*/
protected final SquidPanel buildScreenWideSquidPanel() {
final SquidPanel result = buildSquidPanel(sizeManager.wCells, sizeManager.hCells);
/* TODO smelC Draw margins ? */
result.setPosition(sizeManager.leftMargin, sizeManager.botMargin);
return result;
}
/**
* @param width
* @param height
* @return A panel of size {@code (width, height)} that uses the default
* cell width/cell height. Its position isn't set.
*/
protected final SquidPanel buildSquidPanel(int width, int height) {
return buildSquidPanel(width, height, sizeManager.cellWidth, sizeManager.cellHeight);
}
/**
* @param width
* @param height
* @param cellWidth
* @param cellHeight
* @return A panel of size {@code (width, height)} that has {@code cellSize}
* . Its position isn't set.
*/
protected final SquidPanel buildSquidPanel(int width, int height, int cellWidth, int cellHeight) {
return ipb.buildByCells(width, height, cellWidth, cellHeight, null);
}
/* Default implementation, feel free to override */
protected Stage buildStage() {
return new Stage(new ScreenViewport());
}
/**
* @return Whether this screen should be thrown away when a resize event
* occurs.
*/
/* You should return false if you handle resizing on your own */
protected boolean disposeAtResize() {
return true;
}
/**
* @return The color to use to repaint the screen entirely in
* {@link #clearScreen()} (used in this class when moving from a
* {@link AbstractSquidScreen} to another).
*/
protected T getClearingColor() {
return colorCenter.getBlack();
}
protected void clearScreen() {
final T c = getClearingColor();
if (renderer == null)
renderer = new ShapeRenderer();
UIUtil.drawRectangle(renderer, 0, 0, sizeManager.screenWidth, sizeManager.screenHeight,
ShapeType.Filled, c);
}
/**
* A dumb container, to avoid having too many parameters to
* {@link AbstractSquidScreen}'s constructor.
*
* @author smelC
*
* @param <T>
*/
public static class SquidScreenInput<T> {
public final ScreenSizeManager ssm;
public final IColorCenter<T> icc;
public final IPanelBuilder ipb;
public SquidScreenInput(ScreenSizeManager ssm, IColorCenter<T> icc, IPanelBuilder ipb) {
this.ssm = ssm;
this.icc = icc;
this.ipb = ipb;
}
}
}