Home | History | Annotate | Download | only in ui
      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 
     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 
     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} */
     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 
     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. */
     96 	public ProgressBarStyle getStyle () {
     97 		return style;
     98 	}
     99 
    100 	@Override
    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
    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 
    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. */
    208 	public float getVisualValue () {
    209 		if (animateTime > 0) return animateInterpolation.apply(animateFromValue, value, 1 - animateTime / animateDuration);
    210 		return value;
    211 	}
    212 
    213 	public float getPercent () {
    214 		return (value - min) / (max - min);
    215 	}
    216 
    217 	public float getVisualPercent () {
    218 		return visualInterpolation.apply((getVisualValue() - min) / (max - min));
    219 	}
    220 
    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. */
    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. */
    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. */
    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. */
    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 
    269 	public void setStepSize (float stepSize) {
    270 		if (stepSize <= 0) throw new IllegalArgumentException("steps must be > 0: " + stepSize);
    271 		this.stepSize = stepSize;
    272 	}
    273 
    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 
    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 
    293 	public float getMinValue () {
    294 		return this.min;
    295 	}
    296 
    297 	public float getMaxValue () {
    298 		return this.max;
    299 	}
    300 
    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. */
    306 	public void setAnimateDuration (float duration) {
    307 		this.animateDuration = duration;
    308 	}
    309 
    310 	/** Sets the interpolation to use for {@link #setAnimateDuration(float)}. */
    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. */
    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. */
    322 	public void setSnapToValues (float[] values, float threshold) {
    323 		this.snapValues = values;
    324 		this.threshold = threshold;
    325 	}
    326 
    327 	/** Returns a snapped 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 
    336 	public void setDisabled (boolean disabled) {
    337 		this.disabled = disabled;
    338 	}
    339 
    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 
    357 		public ProgressBarStyle () {
    358 		}
    359 
    360 		public ProgressBarStyle (Drawable background, Drawable knob) {
    361 			this.background = background;
    362 			this.knob = knob;
    363 		}
    364 
    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