• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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