• 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.Gdx;
20 import com.badlogic.gdx.Input.Keys;
21 import com.badlogic.gdx.graphics.Color;
22 import com.badlogic.gdx.graphics.g2d.Batch;
23 import com.badlogic.gdx.graphics.g2d.NinePatch;
24 import com.badlogic.gdx.graphics.g2d.TextureRegion;
25 import com.badlogic.gdx.math.Interpolation;
26 import com.badlogic.gdx.math.MathUtils;
27 import com.badlogic.gdx.scenes.scene2d.Stage;
28 import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent;
29 import com.badlogic.gdx.scenes.scene2d.utils.Disableable;
30 import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
31 import com.badlogic.gdx.utils.Pools;
32 
33 /** A progress bar is a widget that visually displays the progress of some activity or a value within given range. The progress bar
34  * has a range (min, max) and a stepping between each value it represents. The percentage of completeness typically starts out as
35  * an empty progress bar and gradually becomes filled in as the task or variable value progresses.
36  * <p>
37  * {@link ChangeEvent} is fired when the progress bar knob is moved. Cancelling the event will move the knob to where it was
38  * previously.
39  * <p>
40  * The preferred height of a progress bar is determined by the larger of the knob and background. The preferred width of progress
41  * bar is 140, a relatively arbitrary size.
42  * @author mzechner
43  * @author Nathan Sweet */
44 public class ProgressBar extends Widget implements Disableable {
45 	private ProgressBarStyle style;
46 	private float min, max, stepSize;
47 	private float value, animateFromValue;
48 	float position;
49 	final boolean vertical;
50 	private float animateDuration, animateTime;
51 	private Interpolation animateInterpolation = Interpolation.linear;
52 	private float[] snapValues;
53 	private float threshold;
54 	boolean disabled;
55 	boolean shiftIgnoresSnap;
56 	private Interpolation visualInterpolation = Interpolation.linear;
57 
ProgressBar(float min, float max, float stepSize, boolean vertical, Skin skin)58 	public ProgressBar (float min, float max, float stepSize, boolean vertical, Skin skin) {
59 		this(min, max, stepSize, vertical, skin.get("default-" + (vertical ? "vertical" : "horizontal"), ProgressBarStyle.class));
60 	}
61 
ProgressBar(float min, float max, float stepSize, boolean vertical, Skin skin, String styleName)62 	public ProgressBar (float min, float max, float stepSize, boolean vertical, Skin skin, String styleName) {
63 		this(min, max, stepSize, vertical, skin.get(styleName, ProgressBarStyle.class));
64 	}
65 
66 	/** Creates a new progress bar. It's width is determined by the given prefWidth parameter, its height is determined by the
67 	 * maximum of the height of either the progress bar {@link NinePatch} or progress bar handle {@link TextureRegion}. The min and
68 	 * max values determine the range the values of this progress bar can take on, the stepSize parameter specifies the distance
69 	 * between individual values.
70 	 * <p>
71 	 * E.g. min could be 4, max could be 10 and stepSize could be 0.2, giving you a total of 30 values, 4.0 4.2, 4.4 and so on.
72 	 * @param min the minimum value
73 	 * @param max the maximum value
74 	 * @param stepSize the step size between values
75 	 * @param style the {@link ProgressBarStyle} */
ProgressBar(float min, float max, float stepSize, boolean vertical, ProgressBarStyle style)76 	public ProgressBar (float min, float max, float stepSize, boolean vertical, ProgressBarStyle style) {
77 		if (min > max) throw new IllegalArgumentException("max must be > min. min,max: " + min + ", " + max);
78 		if (stepSize <= 0) throw new IllegalArgumentException("stepSize must be > 0: " + stepSize);
79 		setStyle(style);
80 		this.min = min;
81 		this.max = max;
82 		this.stepSize = stepSize;
83 		this.vertical = vertical;
84 		this.value = min;
85 		setSize(getPrefWidth(), getPrefHeight());
86 	}
87 
setStyle(ProgressBarStyle style)88 	public void setStyle (ProgressBarStyle style) {
89 		if (style == null) throw new IllegalArgumentException("style cannot be null.");
90 		this.style = style;
91 		invalidateHierarchy();
92 	}
93 
94 	/** Returns the progress bar's style. Modifying the returned style may not have an effect until
95 	 * {@link #setStyle(ProgressBarStyle)} is called. */
getStyle()96 	public ProgressBarStyle getStyle () {
97 		return style;
98 	}
99 
100 	@Override
act(float delta)101 	public void act (float delta) {
102 		super.act(delta);
103 		if (animateTime > 0) {
104 			animateTime -= delta;
105 			Stage stage = getStage();
106 			if (stage != null && stage.getActionsRequestRendering()) Gdx.graphics.requestRendering();
107 		}
108 	}
109 
110 	@Override
draw(Batch batch, float parentAlpha)111 	public void draw (Batch batch, float parentAlpha) {
112 		ProgressBarStyle style = this.style;
113 		boolean disabled = this.disabled;
114 		final Drawable knob = getKnobDrawable();
115 		final Drawable bg = (disabled && style.disabledBackground != null) ? style.disabledBackground : style.background;
116 		final Drawable knobBefore = (disabled && style.disabledKnobBefore != null) ? style.disabledKnobBefore : style.knobBefore;
117 		final Drawable knobAfter = (disabled && style.disabledKnobAfter != null) ? style.disabledKnobAfter : style.knobAfter;
118 
119 		Color color = getColor();
120 		float x = getX();
121 		float y = getY();
122 		float width = getWidth();
123 		float height = getHeight();
124 		float knobHeight = knob == null ? 0 : knob.getMinHeight();
125 		float knobWidth = knob == null ? 0 : knob.getMinWidth();
126 		float percent = getVisualPercent();
127 
128 		batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
129 
130 		if (vertical) {
131 			float positionHeight = height;
132 
133 			float bgTopHeight = 0;
134 			if (bg != null) {
135 				bg.draw(batch, x + (int)((width - bg.getMinWidth()) * 0.5f), y, bg.getMinWidth(), height);
136 				bgTopHeight = bg.getTopHeight();
137 				positionHeight -= bgTopHeight + bg.getBottomHeight();
138 			}
139 
140 			float knobHeightHalf = 0;
141 			if (min != max) {
142 				if (knob == null) {
143 					knobHeightHalf = knobBefore == null ? 0 : knobBefore.getMinHeight() * 0.5f;
144 					position = (positionHeight - knobHeightHalf) * percent;
145 					position = Math.min(positionHeight - knobHeightHalf, position);
146 				} else {
147 					knobHeightHalf = knobHeight * 0.5f;
148 					position = (positionHeight - knobHeight) * percent;
149 					position = Math.min(positionHeight - knobHeight, position) + bg.getBottomHeight();
150 				}
151 				position = Math.max(0, position);
152 			}
153 
154 			if (knobBefore != null) {
155 				float offset = 0;
156 				if (bg != null) offset = bgTopHeight;
157 				knobBefore.draw(batch, x + (int)((width - knobBefore.getMinWidth()) * 0.5f), y + offset, knobBefore.getMinWidth(),
158 					(int)(position + knobHeightHalf));
159 			}
160 			if (knobAfter != null) {
161 				knobAfter.draw(batch, x + (int)((width - knobAfter.getMinWidth()) * 0.5f), y + (int)(position + knobHeightHalf),
162 					knobAfter.getMinWidth(), height - (int)(position + knobHeightHalf));
163 			}
164 			if (knob != null) knob.draw(batch, x + (int)((width - knobWidth) * 0.5f), (int)(y + position), knobWidth, knobHeight);
165 		} else {
166 			float positionWidth = width;
167 
168 			float bgLeftWidth = 0;
169 			if (bg != null) {
170 				bg.draw(batch, x, y + (int)((height - bg.getMinHeight()) * 0.5f), width, bg.getMinHeight());
171 				bgLeftWidth = bg.getLeftWidth();
172 				positionWidth -= bgLeftWidth + bg.getRightWidth();
173 			}
174 
175 			float knobWidthHalf = 0;
176 			if (min != max) {
177 				if (knob == null) {
178 					knobWidthHalf = knobBefore == null ? 0 : knobBefore.getMinWidth() * 0.5f;
179 					position = (positionWidth - knobWidthHalf) * percent;
180 					position = Math.min(positionWidth - knobWidthHalf, position);
181 				} else {
182 					knobWidthHalf = knobWidth * 0.5f;
183 					position = (positionWidth - knobWidth) * percent;
184 					position = Math.min(positionWidth - knobWidth, position) + bgLeftWidth;
185 				}
186 				position = Math.max(0, position);
187 			}
188 
189 			if (knobBefore != null) {
190 				float offset = 0;
191 				if (bg != null) offset = bgLeftWidth;
192 				knobBefore.draw(batch, x + offset, y + (int)((height - knobBefore.getMinHeight()) * 0.5f),
193 					(int)(position + knobWidthHalf), knobBefore.getMinHeight());
194 			}
195 			if (knobAfter != null) {
196 				knobAfter.draw(batch, x + (int)(position + knobWidthHalf), y + (int)((height - knobAfter.getMinHeight()) * 0.5f),
197 					width - (int)(position + knobWidthHalf), knobAfter.getMinHeight());
198 			}
199 			if (knob != null) knob.draw(batch, (int)(x + position), (int)(y + (height - knobHeight) * 0.5f), knobWidth, knobHeight);
200 		}
201 	}
202 
getValue()203 	public float getValue () {
204 		return value;
205 	}
206 
207 	/** If {@link #setAnimateDuration(float) animating} the progress bar value, this returns the value current displayed. */
getVisualValue()208 	public float getVisualValue () {
209 		if (animateTime > 0) return animateInterpolation.apply(animateFromValue, value, 1 - animateTime / animateDuration);
210 		return value;
211 	}
212 
getPercent()213 	public float getPercent () {
214 		return (value - min) / (max - min);
215 	}
216 
getVisualPercent()217 	public float getVisualPercent () {
218 		return visualInterpolation.apply((getVisualValue() - min) / (max - min));
219 	}
220 
getKnobDrawable()221 	protected Drawable getKnobDrawable () {
222 		return (disabled && style.disabledKnob != null) ? style.disabledKnob : style.knob;
223 	}
224 
225 	/** Returns progress bar visual position within the range. */
getKnobPosition()226 	protected float getKnobPosition () {
227 		return this.position;
228 	}
229 
230 	/** Sets the progress bar position, rounded to the nearest step size and clamped to the minimum and maximum values.
231 	 * {@link #clamp(float)} can be overridden to allow values outside of the progress bar's min/max range.
232 	 * @return false if the value was not changed because the progress bar already had the value or it was canceled by a listener. */
setValue(float value)233 	public boolean setValue (float value) {
234 		value = clamp(Math.round(value / stepSize) * stepSize);
235 		if (!shiftIgnoresSnap || (!Gdx.input.isKeyPressed(Keys.SHIFT_LEFT) && !Gdx.input.isKeyPressed(Keys.SHIFT_RIGHT)))
236 			value = snap(value);
237 		float oldValue = this.value;
238 		if (value == oldValue) return false;
239 		float oldVisualValue = getVisualValue();
240 		this.value = value;
241 		ChangeEvent changeEvent = Pools.obtain(ChangeEvent.class);
242 		boolean cancelled = fire(changeEvent);
243 		if (cancelled)
244 			this.value = oldValue;
245 		else if (animateDuration > 0) {
246 			animateFromValue = oldVisualValue;
247 			animateTime = animateDuration;
248 		}
249 		Pools.free(changeEvent);
250 		return !cancelled;
251 	}
252 
253 	/** Clamps the value to the progress bar's min/max range. This can be overridden to allow a range different from the progress
254 	 * bar knob's range. */
clamp(float value)255 	protected float clamp (float value) {
256 		return MathUtils.clamp(value, min, max);
257 	}
258 
259 	/** Sets the range of this progress bar. The progress bar's current value is clamped to the range. */
setRange(float min, float max)260 	public void setRange (float min, float max) {
261 		if (min > max) throw new IllegalArgumentException("min must be <= max");
262 		this.min = min;
263 		this.max = max;
264 		if (value < min)
265 			setValue(min);
266 		else if (value > max) setValue(max);
267 	}
268 
setStepSize(float stepSize)269 	public void setStepSize (float stepSize) {
270 		if (stepSize <= 0) throw new IllegalArgumentException("steps must be > 0: " + stepSize);
271 		this.stepSize = stepSize;
272 	}
273 
getPrefWidth()274 	public float getPrefWidth () {
275 		if (vertical) {
276 			final Drawable knob = getKnobDrawable();
277 			final Drawable bg = (disabled && style.disabledBackground != null) ? style.disabledBackground : style.background;
278 			return Math.max(knob == null ? 0 : knob.getMinWidth(), bg.getMinWidth());
279 		} else
280 			return 140;
281 	}
282 
getPrefHeight()283 	public float getPrefHeight () {
284 		if (vertical)
285 			return 140;
286 		else {
287 			final Drawable knob = getKnobDrawable();
288 			final Drawable bg = (disabled && style.disabledBackground != null) ? style.disabledBackground : style.background;
289 			return Math.max(knob == null ? 0 : knob.getMinHeight(), bg == null ? 0 : bg.getMinHeight());
290 		}
291 	}
292 
getMinValue()293 	public float getMinValue () {
294 		return this.min;
295 	}
296 
getMaxValue()297 	public float getMaxValue () {
298 		return this.max;
299 	}
300 
getStepSize()301 	public float getStepSize () {
302 		return this.stepSize;
303 	}
304 
305 	/** If > 0, changes to the progress bar value via {@link #setValue(float)} will happen over this duration in seconds. */
setAnimateDuration(float duration)306 	public void setAnimateDuration (float duration) {
307 		this.animateDuration = duration;
308 	}
309 
310 	/** Sets the interpolation to use for {@link #setAnimateDuration(float)}. */
setAnimateInterpolation(Interpolation animateInterpolation)311 	public void setAnimateInterpolation (Interpolation animateInterpolation) {
312 		if (animateInterpolation == null) throw new IllegalArgumentException("animateInterpolation cannot be null.");
313 		this.animateInterpolation = animateInterpolation;
314 	}
315 
316 	/** Sets the interpolation to use for display. */
setVisualInterpolation(Interpolation interpolation)317 	public void setVisualInterpolation (Interpolation interpolation) {
318 		this.visualInterpolation = interpolation;
319 	}
320 
321 	/** Will make this progress bar snap to the specified values, if the knob is within the threshold. */
setSnapToValues(float[] values, float threshold)322 	public void setSnapToValues (float[] values, float threshold) {
323 		this.snapValues = values;
324 		this.threshold = threshold;
325 	}
326 
327 	/** Returns a snapped value. */
snap(float value)328 	private float snap (float value) {
329 		if (snapValues == null) return value;
330 		for (int i = 0; i < snapValues.length; i++) {
331 			if (Math.abs(value - snapValues[i]) <= threshold) return snapValues[i];
332 		}
333 		return value;
334 	}
335 
setDisabled(boolean disabled)336 	public void setDisabled (boolean disabled) {
337 		this.disabled = disabled;
338 	}
339 
isDisabled()340 	public boolean isDisabled () {
341 		return disabled;
342 	}
343 
344 	/** The style for a progress bar, see {@link ProgressBar}.
345 	 * @author mzechner
346 	 * @author Nathan Sweet */
347 	static public class ProgressBarStyle {
348 		/** The progress bar background, stretched only in one direction. Optional. */
349 		public Drawable background;
350 		/** Optional. **/
351 		public Drawable disabledBackground;
352 		/** Optional, centered on the background. */
353 		public Drawable knob, disabledKnob;
354 		/** Optional. */
355 		public Drawable knobBefore, knobAfter, disabledKnobBefore, disabledKnobAfter;
356 
ProgressBarStyle()357 		public ProgressBarStyle () {
358 		}
359 
ProgressBarStyle(Drawable background, Drawable knob)360 		public ProgressBarStyle (Drawable background, Drawable knob) {
361 			this.background = background;
362 			this.knob = knob;
363 		}
364 
ProgressBarStyle(ProgressBarStyle style)365 		public ProgressBarStyle (ProgressBarStyle style) {
366 			this.background = style.background;
367 			this.disabledBackground = style.disabledBackground;
368 			this.knob = style.knob;
369 			this.disabledKnob = style.disabledKnob;
370 			this.knobBefore = style.knobBefore;
371 			this.knobAfter = style.knobAfter;
372 			this.disabledKnobBefore = style.disabledKnobBefore;
373 			this.disabledKnobAfter = style.disabledKnobAfter;
374 		}
375 	}
376 }
377