/*
 * This file is part of lanterna (http://code.google.com/p/lanterna/).
 *
 * lanterna is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2010-2020 Martin Berglund
 */
package com.googlecode.lanterna.gui2;

import com.googlecode.lanterna.Symbols;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TerminalTextUtils;
import com.googlecode.lanterna.graphics.ThemeDefinition;

This GUI element gives a visual indication of how far a process of some sort has progressed at any given time. It's a classic user interface component that most people are familiar with. It works based on a scale expressed as having a minimum, a maximum and a current value somewhere along that range. When the current value is the same as the minimum, the progress indication is empty, at 0%. If the value is the same as the maximum, the progress indication is filled, at 100%. Any value in between the minimum and the maximum will be indicated proportionally to where on this range between minimum and maximum it is.

In order to add a label to the progress bar, for example to print the % completed, this class supports adding a format specification. This label format, before drawing, will be passed in through a String.format(..) with the current progress of value from minimum to maximum expressed as a float passed in as a single vararg parameter. This parameter will be scaled from 0.0f to 100.0f. By default, the label format is set to "%2.0f%%" which becomes a simple percentage string when formatted.

Author:Martin
/** * This GUI element gives a visual indication of how far a process of some sort has progressed at any given time. It's * a classic user interface component that most people are familiar with. It works based on a scale expressed as having * a <i>minimum</i>, a <i>maximum</i> and a current <i>value</i> somewhere along that range. When the current * <i>value</i> is the same as the <i>minimum</i>, the progress indication is empty, at 0%. If the <i>value</i> is the * same as the <i>maximum</i>, the progress indication is filled, at 100%. Any <i>value</i> in between the * <i>minimum</i> and the <i>maximum</i> will be indicated proportionally to where on this range between <i>minimum</i> * and <i>maximum</i> it is. * <p> * In order to add a label to the progress bar, for example to print the % completed, this class supports adding a * format specification. This label format, before drawing, will be passed in through a {@code String.format(..)} with * the current progress of <i>value</i> from <i>minimum</i> to <i>maximum</i> expressed as a {@code float} passed in as * a single vararg parameter. This parameter will be scaled from 0.0f to 100.0f. By default, the label format is set to * "%2.0f%%" which becomes a simple percentage string when formatted. * @author Martin */
public class ProgressBar extends AbstractComponent<ProgressBar> { private int min; private int max; private int value; private int preferredWidth; private String labelFormat;
Creates a new progress bar initially defined with a range from 0 to 100. The
/** * Creates a new progress bar initially defined with a range from 0 to 100. The */
public ProgressBar() { this(0, 100); }
Creates a new progress bar with a defined range of minimum to maximum
Params:
  • min – The minimum value of this progress bar
  • max – The maximum value of this progress bar
/** * Creates a new progress bar with a defined range of minimum to maximum * @param min The minimum value of this progress bar * @param max The maximum value of this progress bar */
public ProgressBar(int min, int max) { this(min, max, 0); }
Creates a new progress bar with a defined range of minimum to maximum and also with a hint as to how wide the progress bar should be drawn
Params:
  • min – The minimum value of this progress bar
  • max – The maximum value of this progress bar
  • preferredWidth – Width size hint, in number of columns, for this progress bar. The renderer may choose to not use this hint. 0 or less means that there is no hint.
/** * Creates a new progress bar with a defined range of minimum to maximum and also with a hint as to how wide the * progress bar should be drawn * @param min The minimum value of this progress bar * @param max The maximum value of this progress bar * @param preferredWidth Width size hint, in number of columns, for this progress bar. The renderer may choose to * not use this hint. 0 or less means that there is no hint. */
public ProgressBar(int min, int max, int preferredWidth) { if(min > max) { min = max; } this.min = min; this.max = max; this.value = min; this.labelFormat = "%2.0f%%"; if(preferredWidth < 1) { preferredWidth = 1; } this.preferredWidth = preferredWidth; }
Returns the current minimum value for this progress bar
Returns:The minimum value of this progress bar
/** * Returns the current <i>minimum</i> value for this progress bar * @return The <i>minimum</i> value of this progress bar */
public int getMin() { return min; }
Updates the minimum value of this progress bar. If the current maximum and/or value are smaller than this new minimum, they are automatically adjusted so that the range is still valid.
Params:
  • min – New minimum value to assign to this progress bar
Returns:Itself
/** * Updates the <i>minimum</i> value of this progress bar. If the current <i>maximum</i> and/or <i>value</i> are * smaller than this new <i>minimum</i>, they are automatically adjusted so that the range is still valid. * @param min New <i>minimum</i> value to assign to this progress bar * @return Itself */
public synchronized ProgressBar setMin(int min) { if(min > max) { setMax(min); } if(min > value) { setValue(min); } if(this.min != min) { this.min = min; invalidate(); } return this; }
Returns the current maximum value for this progress bar
Returns:The maximum value of this progress bar
/** * Returns the current <i>maximum</i> value for this progress bar * @return The <i>maximum</i> value of this progress bar */
public int getMax() { return max; }
Updates the maximum value of this progress bar. If the current minimum and/or value are greater than this new maximum, they are automatically adjusted so that the range is still valid.
Params:
  • max – New maximum value to assign to this progress bar
Returns:Itself
/** * Updates the <i>maximum</i> value of this progress bar. If the current <i>minimum</i> and/or <i>value</i> are * greater than this new <i>maximum</i>, they are automatically adjusted so that the range is still valid. * @param max New <i>maximum</i> value to assign to this progress bar * @return Itself */
public synchronized ProgressBar setMax(int max) { if(max < min) { setMin(max); } if(max < value) { setValue(max); } if(this.max != max) { this.max = max; invalidate(); } return this; }
Returns the current value of this progress bar, which represents how complete the progress indication is.
Returns:The progress value of this progress bar
/** * Returns the current <i>value</i> of this progress bar, which represents how complete the progress indication is. * @return The progress value of this progress bar */
public int getValue() { return value; }
Updates the value of this progress bar, which will update the visual state. If the value passed in is outside the minimum-maximum range, it is automatically adjusted.
Params:
  • value – New value of the progress bar
Returns:Itself
/** * Updates the <i>value</i> of this progress bar, which will update the visual state. If the value passed in is * outside the <i>minimum-maximum</i> range, it is automatically adjusted. * @param value New value of the progress bar * @return Itself */
public synchronized ProgressBar setValue(int value) { if(value < min) { value = min; } if(value > max) { value = max; } if(this.value != value) { this.value = value; invalidate(); } return this; }
Returns the preferred width of the progress bar component, in number of columns. If 0 or less, it should be interpreted as no preference on width and it's up to the renderer to decide.
Returns:Preferred width this progress bar would like to have, or 0 (or less) if no preference
/** * Returns the preferred width of the progress bar component, in number of columns. If 0 or less, it should be * interpreted as no preference on width and it's up to the renderer to decide. * @return Preferred width this progress bar would like to have, or 0 (or less) if no preference */
public int getPreferredWidth() { return preferredWidth; }
Updated the preferred width hint, which tells the renderer how wide this progress bar would like to be. If called with 0 (or less), it means no preference on width and the renderer will have to figure out on its own how wide to make it.
Params:
  • preferredWidth – New preferred width in number of columns, or 0 if no preference
/** * Updated the preferred width hint, which tells the renderer how wide this progress bar would like to be. If called * with 0 (or less), it means no preference on width and the renderer will have to figure out on its own how wide * to make it. * @param preferredWidth New preferred width in number of columns, or 0 if no preference */
public void setPreferredWidth(int preferredWidth) { this.preferredWidth = preferredWidth; }
Returns the current label format string which is the template for what the progress bar would like to be the label printed. Exactly how this label is printed depends on the renderer, but the default renderer will print the label centered in the middle of the progress indication.
Returns:The label format template string this progress bar is currently using
/** * Returns the current label format string which is the template for what the progress bar would like to be the * label printed. Exactly how this label is printed depends on the renderer, but the default renderer will print * the label centered in the middle of the progress indication. * @return The label format template string this progress bar is currently using */
public String getLabelFormat() { return labelFormat; }
Sets the label format this progress bar should use when the component is drawn. The string would be compatible with String.format(..), the class will pass the string through that method and pass in the current progress as a single vararg parameter (passed in as a float in the range of 0.0f to 100.0f). Setting this format string to null or empty string will turn off the label rendering.
Params:
  • labelFormat – Label format to use when drawing the progress bar, or null to disable
Returns:Itself
/** * Sets the label format this progress bar should use when the component is drawn. The string would be compatible * with {@code String.format(..)}, the class will pass the string through that method and pass in the current * progress as a single vararg parameter (passed in as a {@code float} in the range of 0.0f to 100.0f). Setting this * format string to null or empty string will turn off the label rendering. * @param labelFormat Label format to use when drawing the progress bar, or {@code null} to disable * @return Itself */
public synchronized ProgressBar setLabelFormat(String labelFormat) { this.labelFormat = labelFormat; invalidate(); return this; }
Returns the current progress of this progress bar's value from minimum to maximum, expressed as a float from 0.0f to 1.0f.
Returns:current progress of this progress bar expressed as a float from 0.0f to 1.0f.
/** * Returns the current progress of this progress bar's <i>value</i> from <i>minimum</i> to <i>maximum</i>, expressed * as a float from 0.0f to 1.0f. * @return current progress of this progress bar expressed as a float from 0.0f to 1.0f. */
public synchronized float getProgress() { return (float)(value - min) / (float)max; }
Returns the label of this progress bar formatted through String.format(..) with the current progress value.
Returns:The progress bar label formatted with the current progress
/** * Returns the label of this progress bar formatted through {@code String.format(..)} with the current progress * value. * @return The progress bar label formatted with the current progress */
public synchronized String getFormattedLabel() { if(labelFormat == null) { return ""; } return String.format(labelFormat, getProgress() * 100.0f); } @Override protected ComponentRenderer<ProgressBar> createDefaultRenderer() { return new DefaultProgressBarRenderer(); }
Default implementation of the progress bar GUI component renderer. This renderer will draw the progress bar on a single line and gradually fill up the space with a different color as the progress is increasing.
/** * Default implementation of the progress bar GUI component renderer. This renderer will draw the progress bar * on a single line and gradually fill up the space with a different color as the progress is increasing. */
public static class DefaultProgressBarRenderer implements ComponentRenderer<ProgressBar> { @Override public TerminalSize getPreferredSize(ProgressBar component) { int preferredWidth = component.getPreferredWidth(); if(preferredWidth > 0) { return new TerminalSize(preferredWidth, 1); } else if(component.getLabelFormat() != null && !component.getLabelFormat().trim().isEmpty()) { return new TerminalSize(TerminalTextUtils.getColumnWidth(String.format(component.getLabelFormat(), 100.0f)) + 2, 1); } else { return new TerminalSize(10, 1); } } @Override public void drawComponent(TextGUIGraphics graphics, ProgressBar component) { TerminalSize size = graphics.getSize(); if(size.getRows() == 0 || size.getColumns() == 0) { return; } ThemeDefinition themeDefinition = component.getThemeDefinition(); int columnOfProgress = (int)(component.getProgress() * size.getColumns()); String label = component.getFormattedLabel(); int labelRow = size.getRows() / 2; // Adjust label so it fits inside the component int labelWidth = TerminalTextUtils.getColumnWidth(label); // Can't be too smart about this, because of CJK characters if(labelWidth > size.getColumns()) { boolean tail = true; while (labelWidth > size.getColumns()) { if(tail) { label = label.substring(0, label.length() - 1); } else { label = label.substring(1); } tail = !tail; labelWidth = TerminalTextUtils.getColumnWidth(label); } } int labelStartPosition = (size.getColumns() - labelWidth) / 2; for(int row = 0; row < size.getRows(); row++) { graphics.applyThemeStyle(themeDefinition.getActive()); for(int column = 0; column < size.getColumns(); column++) { if(column == columnOfProgress) { graphics.applyThemeStyle(themeDefinition.getNormal()); } if(row == labelRow && column >= labelStartPosition && column < labelStartPosition + labelWidth) { char character = label.charAt(TerminalTextUtils.getStringCharacterIndex(label, column - labelStartPosition)); graphics.setCharacter(column, row, character); if(TerminalTextUtils.isCharDoubleWidth(character)) { column++; if(column == columnOfProgress) { graphics.applyThemeStyle(themeDefinition.getNormal()); } } } else { graphics.setCharacter(column, row, themeDefinition.getCharacter("FILLER", ' ')); } } } } }
This progress bar renderer implementation takes slightly more space (three rows) and draws a slightly more complicates progress bar with fixed measurers to mark 25%, 50% and 75%. Maybe you have seen this one before somewhere?
/** * This progress bar renderer implementation takes slightly more space (three rows) and draws a slightly more * complicates progress bar with fixed measurers to mark 25%, 50% and 75%. Maybe you have seen this one before * somewhere? */
public static class LargeProgressBarRenderer implements ComponentRenderer<ProgressBar> { @Override public TerminalSize getPreferredSize(ProgressBar component) { int preferredWidth = component.getPreferredWidth(); if(preferredWidth > 0) { return new TerminalSize(preferredWidth, 3); } else { return new TerminalSize(42, 3); } } @Override public void drawComponent(TextGUIGraphics graphics, ProgressBar component) { TerminalSize size = graphics.getSize(); if(size.getRows() == 0 || size.getColumns() == 0) { return; } ThemeDefinition themeDefinition = component.getThemeDefinition(); int columnOfProgress = (int)(component.getProgress() * (size.getColumns() - 4)); int mark25 = -1; int mark50 = -1; int mark75 = -1; if(size.getColumns() > 9) { mark50 = (size.getColumns() - 2) / 2; } if(size.getColumns() > 16) { mark25 = (size.getColumns() - 2) / 4; mark75 = mark50 + mark25; } // Draw header, if there are at least 3 rows available int rowOffset = 0; if(size.getRows() >= 3) { graphics.applyThemeStyle(themeDefinition.getNormal()); graphics.drawLine(0, 0, size.getColumns(), 0, ' '); if(size.getColumns() > 1) { graphics.setCharacter(1, 0, '0'); } if(mark25 != -1) { if(component.getProgress() < 0.25f) { graphics.applyThemeStyle(themeDefinition.getInsensitive()); } graphics.putString(1 + mark25, 0, "25"); } if(mark50 != -1) { if(component.getProgress() < 0.50f) { graphics.applyThemeStyle(themeDefinition.getInsensitive()); } graphics.putString(1 + mark50, 0, "50"); } if(mark75 != -1) { if(component.getProgress() < 0.75f) { graphics.applyThemeStyle(themeDefinition.getInsensitive()); } graphics.putString(1 + mark75, 0, "75"); } if(size.getColumns() >= 7) { if(component.getProgress() < 1.0f) { graphics.applyThemeStyle(themeDefinition.getInsensitive()); } graphics.putString(size.getColumns() - 3, 0, "100"); } rowOffset++; } // Draw the main indicator for(int i = 0; i < Math.max(1, size.getRows() - 2); i++) { graphics.applyThemeStyle(themeDefinition.getNormal()); graphics.drawLine(0, rowOffset, size.getColumns(), rowOffset, ' '); if (size.getColumns() > 2) { graphics.setCharacter(1, rowOffset, Symbols.SINGLE_LINE_VERTICAL); } if (size.getColumns() > 3) { graphics.setCharacter(size.getColumns() - 2, rowOffset, Symbols.SINGLE_LINE_VERTICAL); } if (size.getColumns() > 4) { graphics.applyThemeStyle(themeDefinition.getActive()); for(int columnOffset = 2; columnOffset < size.getColumns() - 2; columnOffset++) { if(columnOfProgress + 2 == columnOffset) { graphics.applyThemeStyle(themeDefinition.getNormal()); } if(mark25 == columnOffset - 1) { graphics.setCharacter(columnOffset, rowOffset, Symbols.SINGLE_LINE_VERTICAL); } else if(mark50 == columnOffset - 1) { graphics.setCharacter(columnOffset, rowOffset, Symbols.SINGLE_LINE_VERTICAL); } else if(mark75 == columnOffset - 1) { graphics.setCharacter(columnOffset, rowOffset, Symbols.SINGLE_LINE_VERTICAL); } else { graphics.setCharacter(columnOffset, rowOffset, ' '); } } } if(((int)(component.getProgress() * ((size.getColumns() - 4) * 2))) % 2 == 1) { graphics.applyThemeStyle(themeDefinition.getPreLight()); graphics.setCharacter(columnOfProgress + 2, rowOffset, '|'); } rowOffset++; } // Draw footer if there are at least 2 rows, this one is easy if(size.getRows() >= 2) { graphics.applyThemeStyle(themeDefinition.getNormal()); graphics.drawLine(0, rowOffset, size.getColumns(), rowOffset, Symbols.SINGLE_LINE_T_UP); graphics.setCharacter(0, rowOffset, ' '); if (size.getColumns() > 1) { graphics.setCharacter(size.getColumns() - 1, rowOffset, ' '); } if (size.getColumns() > 2) { graphics.setCharacter(1, rowOffset, Symbols.SINGLE_LINE_BOTTOM_LEFT_CORNER); } if (size.getColumns() > 3) { graphics.setCharacter(size.getColumns() - 2, rowOffset, Symbols.SINGLE_LINE_BOTTOM_RIGHT_CORNER); } } } } }