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