1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.scenes.scene2d.ui; 18 19 import com.badlogic.gdx.graphics.Color; 20 import com.badlogic.gdx.graphics.g2d.Batch; 21 import com.badlogic.gdx.graphics.g2d.BitmapFont; 22 import com.badlogic.gdx.graphics.g2d.BitmapFontCache; 23 import com.badlogic.gdx.graphics.g2d.GlyphLayout; 24 import com.badlogic.gdx.math.Vector2; 25 import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 26 import com.badlogic.gdx.utils.Align; 27 import com.badlogic.gdx.utils.StringBuilder; 28 29 /** A text label, with optional word wrapping. 30 * <p> 31 * The preferred size of the label is determined by the actual text bounds, unless {@link #setWrap(boolean) word wrap} is enabled. 32 * @author Nathan Sweet */ 33 public class Label extends Widget { 34 static private final Color tempColor = new Color(); 35 static private final GlyphLayout prefSizeLayout = new GlyphLayout(); 36 37 private LabelStyle style; 38 private final GlyphLayout layout = new GlyphLayout(); 39 private final Vector2 prefSize = new Vector2(); 40 private final StringBuilder text = new StringBuilder(); 41 private BitmapFontCache cache; 42 private int labelAlign = Align.left; 43 private int lineAlign = Align.left; 44 private boolean wrap; 45 private float lastPrefHeight; 46 private boolean prefSizeInvalid = true; 47 private float fontScaleX = 1, fontScaleY = 1; 48 private String ellipsis; 49 Label(CharSequence text, Skin skin)50 public Label (CharSequence text, Skin skin) { 51 this(text, skin.get(LabelStyle.class)); 52 } 53 Label(CharSequence text, Skin skin, String styleName)54 public Label (CharSequence text, Skin skin, String styleName) { 55 this(text, skin.get(styleName, LabelStyle.class)); 56 } 57 58 /** Creates a label, using a {@link LabelStyle} that has a BitmapFont with the specified name from the skin and the specified 59 * color. */ Label(CharSequence text, Skin skin, String fontName, Color color)60 public Label (CharSequence text, Skin skin, String fontName, Color color) { 61 this(text, new LabelStyle(skin.getFont(fontName), color)); 62 } 63 64 /** Creates a label, using a {@link LabelStyle} that has a BitmapFont with the specified name and the specified color from the 65 * skin. */ Label(CharSequence text, Skin skin, String fontName, String colorName)66 public Label (CharSequence text, Skin skin, String fontName, String colorName) { 67 this(text, new LabelStyle(skin.getFont(fontName), skin.getColor(colorName))); 68 } 69 Label(CharSequence text, LabelStyle style)70 public Label (CharSequence text, LabelStyle style) { 71 if (text != null) this.text.append(text); 72 setStyle(style); 73 if (text != null && text.length() > 0) setSize(getPrefWidth(), getPrefHeight()); 74 } 75 setStyle(LabelStyle style)76 public void setStyle (LabelStyle style) { 77 if (style == null) throw new IllegalArgumentException("style cannot be null."); 78 if (style.font == null) throw new IllegalArgumentException("Missing LabelStyle font."); 79 this.style = style; 80 cache = style.font.newFontCache(); 81 invalidateHierarchy(); 82 } 83 84 /** Returns the label's style. Modifying the returned style may not have an effect until {@link #setStyle(LabelStyle)} is 85 * called. */ getStyle()86 public LabelStyle getStyle () { 87 return style; 88 } 89 90 /** @param newText May be null, "" will be used. */ setText(CharSequence newText)91 public void setText (CharSequence newText) { 92 if (newText == null) newText = ""; 93 if (newText instanceof StringBuilder) { 94 if (text.equals(newText)) return; 95 text.setLength(0); 96 text.append((StringBuilder)newText); 97 } else { 98 if (textEquals(newText)) return; 99 text.setLength(0); 100 text.append(newText); 101 } 102 invalidateHierarchy(); 103 } 104 textEquals(CharSequence other)105 public boolean textEquals (CharSequence other) { 106 int length = text.length; 107 char[] chars = text.chars; 108 if (length != other.length()) return false; 109 for (int i = 0; i < length; i++) 110 if (chars[i] != other.charAt(i)) return false; 111 return true; 112 } 113 getText()114 public StringBuilder getText () { 115 return text; 116 } 117 invalidate()118 public void invalidate () { 119 super.invalidate(); 120 prefSizeInvalid = true; 121 } 122 scaleAndComputePrefSize()123 private void scaleAndComputePrefSize () { 124 BitmapFont font = cache.getFont(); 125 float oldScaleX = font.getScaleX(); 126 float oldScaleY = font.getScaleY(); 127 if (fontScaleX != 1 || fontScaleY != 1) font.getData().setScale(fontScaleX, fontScaleY); 128 129 computePrefSize(); 130 131 if (fontScaleX != 1 || fontScaleY != 1) font.getData().setScale(oldScaleX, oldScaleY); 132 } 133 computePrefSize()134 private void computePrefSize () { 135 prefSizeInvalid = false; 136 GlyphLayout prefSizeLayout = Label.prefSizeLayout; 137 if (wrap && ellipsis == null) { 138 float width = getWidth(); 139 if (style.background != null) width -= style.background.getLeftWidth() + style.background.getRightWidth(); 140 prefSizeLayout.setText(cache.getFont(), text, Color.WHITE, width, Align.left, true); 141 } else 142 prefSizeLayout.setText(cache.getFont(), text); 143 prefSize.set(prefSizeLayout.width, prefSizeLayout.height); 144 } 145 layout()146 public void layout () { 147 BitmapFont font = cache.getFont(); 148 float oldScaleX = font.getScaleX(); 149 float oldScaleY = font.getScaleY(); 150 if (fontScaleX != 1 || fontScaleY != 1) font.getData().setScale(fontScaleX, fontScaleY); 151 152 boolean wrap = this.wrap && ellipsis == null; 153 if (wrap) { 154 float prefHeight = getPrefHeight(); 155 if (prefHeight != lastPrefHeight) { 156 lastPrefHeight = prefHeight; 157 invalidateHierarchy(); 158 } 159 } 160 161 float width = getWidth(), height = getHeight(); 162 Drawable background = style.background; 163 float x = 0, y = 0; 164 if (background != null) { 165 x = background.getLeftWidth(); 166 y = background.getBottomHeight(); 167 width -= background.getLeftWidth() + background.getRightWidth(); 168 height -= background.getBottomHeight() + background.getTopHeight(); 169 } 170 171 GlyphLayout layout = this.layout; 172 float textWidth, textHeight; 173 if (wrap || text.indexOf("\n") != -1) { 174 // If the text can span multiple lines, determine the text's actual size so it can be aligned within the label. 175 layout.setText(font, text, 0, text.length, Color.WHITE, width, lineAlign, wrap, ellipsis); 176 textWidth = layout.width; 177 textHeight = layout.height; 178 179 if ((labelAlign & Align.left) == 0) { 180 if ((labelAlign & Align.right) != 0) 181 x += width - textWidth; 182 else 183 x += (width - textWidth) / 2; 184 } 185 } else { 186 textWidth = width; 187 textHeight = font.getData().capHeight; 188 } 189 190 if ((labelAlign & Align.top) != 0) { 191 y += cache.getFont().isFlipped() ? 0 : height - textHeight; 192 y += style.font.getDescent(); 193 } else if ((labelAlign & Align.bottom) != 0) { 194 y += cache.getFont().isFlipped() ? height - textHeight : 0; 195 y -= style.font.getDescent(); 196 } else { 197 y += (height - textHeight) / 2; 198 } 199 if (!cache.getFont().isFlipped()) y += textHeight; 200 201 layout.setText(font, text, 0, text.length, Color.WHITE, textWidth, lineAlign, wrap, ellipsis); 202 cache.setText(layout, x, y); 203 204 if (fontScaleX != 1 || fontScaleY != 1) font.getData().setScale(oldScaleX, oldScaleY); 205 } 206 draw(Batch batch, float parentAlpha)207 public void draw (Batch batch, float parentAlpha) { 208 validate(); 209 Color color = tempColor.set(getColor()); 210 color.a *= parentAlpha; 211 if (style.background != null) { 212 batch.setColor(color.r, color.g, color.b, color.a); 213 style.background.draw(batch, getX(), getY(), getWidth(), getHeight()); 214 } 215 if (style.fontColor != null) color.mul(style.fontColor); 216 cache.tint(color); 217 cache.setPosition(getX(), getY()); 218 cache.draw(batch); 219 } 220 getPrefWidth()221 public float getPrefWidth () { 222 if (wrap) return 0; 223 if (prefSizeInvalid) scaleAndComputePrefSize(); 224 float width = prefSize.x; 225 Drawable background = style.background; 226 if (background != null) width += background.getLeftWidth() + background.getRightWidth(); 227 return width; 228 } 229 getPrefHeight()230 public float getPrefHeight () { 231 if (prefSizeInvalid) scaleAndComputePrefSize(); 232 float height = prefSize.y - style.font.getDescent() * fontScaleY * 2; 233 Drawable background = style.background; 234 if (background != null) height += background.getTopHeight() + background.getBottomHeight(); 235 return height; 236 } 237 getGlyphLayout()238 public GlyphLayout getGlyphLayout () { 239 return layout; 240 } 241 242 /** If false, the text will only wrap where it contains newlines (\n). The preferred size of the label will be the text bounds. 243 * If true, the text will word wrap using the width of the label. The preferred width of the label will be 0, it is expected 244 * that the something external will set the width of the label. Wrapping will not occur when ellipsis is enabled. Default is 245 * false. 246 * <p> 247 * When wrap is enabled, the label's preferred height depends on the width of the label. In some cases the parent of the label 248 * will need to layout twice: once to set the width of the label and a second time to adjust to the label's new preferred 249 * height. */ setWrap(boolean wrap)250 public void setWrap (boolean wrap) { 251 this.wrap = wrap; 252 invalidateHierarchy(); 253 } 254 getLabelAlign()255 public int getLabelAlign () { 256 return labelAlign; 257 } 258 getLineAlign()259 public int getLineAlign () { 260 return lineAlign; 261 } 262 263 /** @param alignment Aligns all the text within the label (default left center) and each line of text horizontally (default 264 * left). 265 * @see Align */ setAlignment(int alignment)266 public void setAlignment (int alignment) { 267 setAlignment(alignment, alignment); 268 } 269 270 /** @param labelAlign Aligns all the text within the label (default left center). 271 * @param lineAlign Aligns each line of text horizontally (default left). 272 * @see Align */ setAlignment(int labelAlign, int lineAlign)273 public void setAlignment (int labelAlign, int lineAlign) { 274 this.labelAlign = labelAlign; 275 276 if ((lineAlign & Align.left) != 0) 277 this.lineAlign = Align.left; 278 else if ((lineAlign & Align.right) != 0) 279 this.lineAlign = Align.right; 280 else 281 this.lineAlign = Align.center; 282 283 invalidate(); 284 } 285 setFontScale(float fontScale)286 public void setFontScale (float fontScale) { 287 this.fontScaleX = fontScale; 288 this.fontScaleY = fontScale; 289 invalidateHierarchy(); 290 } 291 setFontScale(float fontScaleX, float fontScaleY)292 public void setFontScale (float fontScaleX, float fontScaleY) { 293 this.fontScaleX = fontScaleX; 294 this.fontScaleY = fontScaleY; 295 invalidateHierarchy(); 296 } 297 getFontScaleX()298 public float getFontScaleX () { 299 return fontScaleX; 300 } 301 setFontScaleX(float fontScaleX)302 public void setFontScaleX (float fontScaleX) { 303 this.fontScaleX = fontScaleX; 304 invalidateHierarchy(); 305 } 306 getFontScaleY()307 public float getFontScaleY () { 308 return fontScaleY; 309 } 310 setFontScaleY(float fontScaleY)311 public void setFontScaleY (float fontScaleY) { 312 this.fontScaleY = fontScaleY; 313 invalidateHierarchy(); 314 } 315 316 /** When non-null the text will be truncated "..." if it does not fit within the width of the label. Wrapping will not occur 317 * when ellipsis is enabled. Default is false. */ setEllipsis(String ellipsis)318 public void setEllipsis (String ellipsis) { 319 this.ellipsis = ellipsis; 320 } 321 322 /** When true the text will be truncated "..." if it does not fit within the width of the label. Wrapping will not occur when 323 * ellipsis is true. Default is false. */ setEllipsis(boolean ellipsis)324 public void setEllipsis (boolean ellipsis) { 325 if (ellipsis) 326 this.ellipsis = "..."; 327 else 328 this.ellipsis = null; 329 } 330 331 /** Allows subclasses to access the cache in {@link #draw(Batch, float)}. */ getBitmapFontCache()332 protected BitmapFontCache getBitmapFontCache () { 333 return cache; 334 } 335 toString()336 public String toString () { 337 return super.toString() + ": " + text; 338 } 339 340 /** The style for a label, see {@link Label}. 341 * @author Nathan Sweet */ 342 static public class LabelStyle { 343 public BitmapFont font; 344 /** Optional. */ 345 public Color fontColor; 346 /** Optional. */ 347 public Drawable background; 348 LabelStyle()349 public LabelStyle () { 350 } 351 LabelStyle(BitmapFont font, Color fontColor)352 public LabelStyle (BitmapFont font, Color fontColor) { 353 this.font = font; 354 this.fontColor = fontColor; 355 } 356 LabelStyle(LabelStyle style)357 public LabelStyle (LabelStyle style) { 358 this.font = style.font; 359 if (style.fontColor != null) fontColor = new Color(style.fontColor); 360 background = style.background; 361 } 362 } 363 } 364