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}