DefaultSpinners.java
package net.morimekta.terminal.progress;
import net.morimekta.strings.chr.Char;
import net.morimekta.strings.chr.Color;
import net.morimekta.strings.chr.Unicode;
import java.time.Duration;
import java.util.List;
import static net.morimekta.collect.UnmodifiableList.asList;
import static net.morimekta.strings.StringUtil.clipWidth;
import static net.morimekta.strings.StringUtil.printableWidth;
import static net.morimekta.strings.StringUtil.rightPad;
/**
* Which spinner to show. Some may require extended unicode font to be used in the console without just showing '?'.
*/
public enum DefaultSpinners implements Spinner {
/**
* Simple ASCII spinner using '|', '/', '-', '\'. This variant will work in any terminal.
*/
ASCII(new Unicode('#'),
new Unicode('-'),
new Unicode('v'),
new Unicode[]{
new Unicode('|'),
new Unicode('/'),
new Unicode('-'),
new Unicode('\\')
}),
/**
* Using a block char that bounces up and down to show progress. Not exactly <i>spinning</i>, but does the job.
* Using unicode chars 0x2581 -> 0x2588;
* <p>
* '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'
*/
BLOCKS(new Unicode('▓'),
new Unicode('⋅'),
new Unicode('✓'),
new Unicode[]{
new Unicode('▁'), // 1/8 block
new Unicode('▂'), // 2/8 block
new Unicode('▃'), // ...
new Unicode('▄'), //
new Unicode('▅'), //
new Unicode('▆'), // ...
new Unicode('▇'), // 7/8 block
new Unicode('█'), // 8/8 (full) block
new Unicode('▇'), // 7/8 block
new Unicode('▆'), // ...
new Unicode('▅'), //
new Unicode('▄'), //
new Unicode('▂'), // ...
new Unicode('▁'), // 2/8 block
}),
;
@Override
public String atProgress(double fraction, int spinnerPos, Duration remaining, int width) {
var progressWidth = width - offset;
var doneWidth = (int) Math.round(progressWidth * Math.min(1.0, fraction));
var remainingWidth = progressWidth - doneWidth;
var percent = (int) Math.round(fraction * 100.0);
return String.format("[%s%s%s%s%s] %3d%% %s%s%s%s",
Color.GREEN,
done.toString().repeat(doneWidth),
Color.YELLOW,
remain.toString().repeat(remainingWidth),
Color.CLEAR,
percent,
new Color(Color.YELLOW, Color.BOLD),
spinner.get(spinnerPos % spinner.size()),
Color.CLEAR,
remaining == null
? " "
: " + " + formatDuration(remaining));
}
@Override
public String atComplete(Duration spent, int width) {
var progressWidth = width - offset;
return String.format("[%s%s%s] 100%% %s%s%s @ %s",
Color.GREEN,
done.toString().repeat(progressWidth),
Color.CLEAR,
new Color(Color.GREEN, Color.BOLD),
complete,
Color.CLEAR,
formatDuration(spent));
}
@Override
public String atStopped(double fraction, String message, int width) {
var progressWidth = width - offset;
var doneWidth = (int) Math.round(progressWidth * Math.min(1.0, fraction));
var remainingWidth = progressWidth - doneWidth;
var percent = (int) Math.round(fraction * 100.0);
return String.format("[%s%s%s%s%s] %3d%% %s%s%s",
Color.GREEN,
done.toString().repeat(doneWidth),
Color.YELLOW,
remain.toString().repeat(remainingWidth),
Color.CLEAR,
percent,
new Color(Color.RED, Color.BOLD),
rightPad(clipWidth(message, 13), 13),
Color.CLEAR);
}
private final Char done;
private final Char remain;
private final Char complete;
private final List<Char> spinner;
private final int offset;
DefaultSpinners(Char done,
Char remain,
Char complete,
Char[] spinner) {
this.done = done;
this.remain = remain;
this.complete = complete;
this.spinner = asList(spinner);
this.offset = 20 + printableWidth(spinner[0].toString());
}
private static String formatDuration(Duration duration) {
long h = duration.toHours();
long m = duration.minusHours(h).toMinutes();
if (h > 0) {
return String.format("%2d:%02d Hrs", h, m);
}
long s = duration.minusHours(h).minusMinutes(m).getSeconds();
if (m > 0) {
return String.format("%2d:%02d min", m, s);
}
long ms = duration.minusHours(h).minusMinutes(m).minusSeconds(s).toMillis();
return String.format(" %2d.%1d s", s, ms / 100);
}
}