Skip to content

Instantly share code, notes, and snippets.

@jpf91
Created October 9, 2011 13:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpf91/1273678 to your computer and use it in GitHub Desktop.
Save jpf91/1273678 to your computer and use it in GitHub Desktop.
// Copyright Jens K. Mueller
// Boost License 1.0
// easy use by writec* in write fashion
// using the shared terminals
// or using .backgroundColor = Color.black
// What if the user wants to persistently change the terminal settings.
// Is this useful? I think it's not recommended.
// TODO
// how get unset attributes like bold etc. on Posix
// TODO
// how to properly test this module
module terminal;
import std.string;
import std.exception : enforce;
import std.conv;
import std.math;
// TODO
// move to src/core/sys/windows/windows.d
version(Windows)
{
enum
{
COMMON_LVB_REVERSE_VIDEO = 0x4000, //
COMMON_LVB_UNDERSCORE = 0x8000, //
}
}
// interface to curses
version(Posix) extern(C)
{
int setupterm(const(char)* term, int fildes, int *errret);
const(char)* tigetstr(const(char)* capname);
char* tparm(const(char)* str, ...);
int tputs(const(char)* str, int affcnt, int function(int) fp);
}
__gshared
{
Terminal stdout;
Terminal stderr;
}
static this()
{
stdout = Terminal(std.stdio.stdout);
stderr = Terminal(std.stdio.stderr);
}
static this()
{
version(Posix)
{
// TODO
// do I have to initialize it on stderr as well
// initialize terminfo
enforce(!setupterm(null, core.stdc.stdio.fileno(core.stdc.stdio.stdout), null));
// initialize all capabilities
assert(capabilities.length == numCapabilities);
foreach (cap; __traits(allMembers, Capability))
{
enum capability = mixin("Capability"~"."~cap);
enum capabilityString = PosixCapability[capability];
auto str = tigetstr(toStringz(capabilityString));
assert(str != cast(const(char)*)-1, format("%s is not a string capability", capabilityString));
capabilities[capability] = str;
}
}
}
static ~this()
{
stdout.restoreDefaults;
stderr.restoreDefaults;
}
/// Terminal colors
enum Color
{
black = 0,
red = 1,
green = 2,
yellow = 3,
blue = 4,
magenta = 5,
cyan = 6,
white = 7,
noChange = 8
}
/// Return the name of the terminal.
@property
static string name()
{
version(Posix)
{
return std.process.getenv("TERM");
}
else version(Windows)
{
return "Windows";
}
else
{
static assert(NOT_IMPLEMENTED);
}
}
unittest
{
version(Posix)
{
assert(terminal.name == std.process.getenv("TERM"));
}
else version(Windows)
{
assert(terminal.name == "Windows");
}
}
/// Return true if terminal supports colors. Otherwise false.
@property
bool hasColors()
{
version(Posix)
{
return hasCapability(Capability.foreground) &&
hasCapability(Capability.background);
}
else version(Windows)
{
return true;
}
else
{
static assert(NOT_IMPLEMENTED);
}
}
unittest
{
// test color support here
assert(hasColors == true);
}
/// Return true if terminal supports bold font. Otherwise false.
@property
bool hasCapability(in Capability capability)
{
version(Posix)
{
return capabilities[capability] != null;
}
else version(Windows)
{
// TODO
return true;
}
else
{
static assert(NOT_IMPLEMENTED);
}
}
void writecf(T...)(in Color color, T args)
{
auto oldColor = stdout.foregroundColor(color);
scope(exit) stdout.foregroundColor(oldColor);
writef(args);
}
unittest
{
foreach (color; __traits(allMembers, Color))
writecf(mixin("Color." ~ color), "%s ", color);
writeln();
}
void writecfln(T...)(in Color color, T args)
{
auto oldColor = stdout.foregroundColor(color);
scope(exit) stdout.foregroundColor(oldColor);
writefln(args);
}
unittest
{
foreach (color; __traits(allMembers, Color))
writecf(mixin("Color." ~ color), "%s ", color);
writeln();
}
void writec(T...)(in Color color, T args)
{
auto oldColor = stdout.foregroundColor(color);
scope(exit) stdout.foregroundColor(oldColor);
write(args);
}
unittest
{
foreach (color; __traits(allMembers, Color))
writecf(mixin("Color." ~ color), "%s ", color);
writeln();
}
void writecln(T...)(in Color color, T args)
{
auto oldColor = stdout.foregroundColor(color);
scope(exit) stdout.foregroundColor(oldColor);
writeln(args);
}
unittest
{
foreach (color; __traits(allMembers, Color))
writecf(mixin("Color." ~ color), "%s ", color);
writeln();
}
private:
// terminal capabilities
enum Capability
{
foreground,
background,
boldFace,
blinkFace,
reverseFace,
underlineFace,
allOff,
}
version(Posix)
{
enum PosixCapability = [
Capability.foreground : "setaf",
Capability.background : "setab",
Capability.boldFace : "bold",
Capability.blinkFace : "blink",
Capability.reverseFace : "rev",
Capability.underlineFace : "smul",
Capability.allOff : "sgr0", // turns all attributes off
];
enum numCapabilities = Capability.max - Capability.min + 1;
static const(char)* capabilities[numCapabilities];
}
else version(Windows)
{
import core.sys.windows.windows;
import std.windows.syserror;
enum WORD[Color] WindowsForegroundColor = [
Color.black : 0,
Color.red : FOREGROUND_RED,
Color.green : FOREGROUND_GREEN,
Color.yellow : FOREGROUND_RED | FOREGROUND_GREEN,
Color.blue : FOREGROUND_BLUE,
Color.magenta : FOREGROUND_RED | FOREGROUND_BLUE,
Color.cyan : FOREGROUND_BLUE | FOREGROUND_GREEN,
Color.white : FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED,
];
enum Color[WORD] WindowsForegroundColorReverse = [
0 : Color.black,
FOREGROUND_RED : Color.red,
FOREGROUND_GREEN : Color.green,
FOREGROUND_RED | FOREGROUND_GREEN : Color.yellow,
FOREGROUND_BLUE : Color.blue,
FOREGROUND_RED | FOREGROUND_BLUE : Color.magenta,
FOREGROUND_BLUE | FOREGROUND_GREEN : Color.cyan,
FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED : Color.white,
];
enum WORD[Color] WindowsBackgroundColor = [
Color.black : 0,
Color.red : BACKGROUND_RED,
Color.green : BACKGROUND_GREEN,
Color.yellow : BACKGROUND_RED | BACKGROUND_GREEN,
Color.blue : BACKGROUND_BLUE,
Color.magenta : BACKGROUND_RED | BACKGROUND_BLUE,
Color.cyan : BACKGROUND_BLUE | BACKGROUND_GREEN,
Color.white : BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED,
];
enum Color[WORD] WindowsBackgroundColorReverse = [
0 : Color.black,
BACKGROUND_RED : Color.red,
BACKGROUND_GREEN : Color.green,
BACKGROUND_RED | BACKGROUND_GREEN : Color.yellow,
BACKGROUND_BLUE : Color.blue,
BACKGROUND_RED | BACKGROUND_BLUE : Color.magenta,
BACKGROUND_BLUE | BACKGROUND_GREEN : Color.cyan,
BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED : Color.white,
];
}
import std.stdio;
enum NOT_IMPLEMENTED = "Not implemented for your OS. Please file an enhancement request.";
struct Terminal
{
this(File file)
in
{
assert(file == std.stdio.stdout || file == std.stdio.stderr);
}
body
{
_file = file;
version(Windows) _startUpAttributes = getCharacterAttributes();
// switch to own console buffer
}
/// Set the terminal's foreground color. Return old foreground color (see
/// foregroundColor()).
@property
Color foregroundColor(in Color color)
{
auto oldColor = foregroundColor();
setCapability(Capability.foreground, color);
return oldColor;
}
/// Return the terminal's current foreground color. On Posix systems the
/// color will be the terminal's default color.
@property
Color foregroundColor() const
{
version(Posix)
{
// TODO
// is this standard compliant
// how get the default color?
// what about capability with name op (see man 5 terminfo)
return cast(Color) 9;
}
else version(Windows)
{
WORD characterAttributes = getCharacterAttributes() & 0x0007;
return WindowsForegroundColorReverse[characterAttributes];
}
else
{
static assert(NOT_IMPLEMENTED);
}
}
unittest
{
write("Foreground: ");
foreach (color; __traits(allMembers, Color))
{
auto oldColor = stdout.foregroundColor(mixin("Color." ~ color));
scope(exit) stdout.foregroundColor(oldColor);
writef("%s ", color);
}
writeln();
}
/// Set the terminal's background color. Return old background color (see
/// backgroundColor()).
@property
Color backgroundColor(in Color color)
{
auto oldColor = backgroundColor();
setCapability(Capability.background, color);
return oldColor;
}
/// Return the terminal's current background color. On Posix systems the
/// color will be the terminal's default color.
@property
Color backgroundColor() const
{
version(Posix)
{
// TODO
return cast(Color) 9;
}
else version(Windows)
{
WORD characterAttributes = getCharacterAttributes() & 0x0070;
return WindowsBackgroundColorReverse[characterAttributes];
}
else
{
static assert(NOT_IMPLEMENTED);
}
}
unittest
{
write("Background: ");
foreach (color; __traits(allMembers, Color))
{
auto oldColor = stdout.backgroundColor(mixin("Color." ~ color));
writef("%s", color);
stdout.backgroundColor(oldColor);
write(" ");
}
writeln();
}
unittest
{
writeln("Background\\Foreground:");
foreach (backgroundColor; __traits(allMembers, Color))
{
writef("%10s: ", backgroundColor);
auto oldBackgroundColor = stdout.backgroundColor(mixin("Color." ~ backgroundColor));
foreach (foregroundColor; __traits(allMembers, Color))
{
auto oldForegroundColor = stdout.foregroundColor(mixin("Color." ~ foregroundColor));
scope(exit) stdout.foregroundColor(oldForegroundColor);
writef("%s ", foregroundColor);
}
stdout.backgroundColor(oldBackgroundColor);
writeln();
}
}
/// Turn all capabilities off.
@property
void restoreDefaults()
{
setCapability(Capability.allOff);
}
/// Set bold on (true) or off (false).
@property
void bold(in bool on)
{
setFace(Capability.boldFace, on);
}
unittest
{
stdout.bold = true;
write("BOLD ");
write("Foreground: ");
foreach (color; __traits(allMembers, Color))
{
auto oldColor = stdout.foregroundColor(mixin("Color." ~ color));
scope(exit) stdout.foregroundColor(oldColor);
writef("%s ", color);
}
writeln();
write("Background: ");
foreach (color; __traits(allMembers, Color))
{
auto oldColor = stdout.backgroundColor(mixin("Color." ~ color));
scope(exit) stdout.backgroundColor(oldColor);
writef("%s ", color);
}
writeln();
stdout.bold = false;
write("NORMAL ");
write("Foreground: ");
foreach (color; __traits(allMembers, Color))
{
auto oldColor = stdout.foregroundColor(mixin("Color." ~ color));
scope(exit) stdout.foregroundColor(oldColor);
writef("%s ", color);
}
writeln();
write("Background: ");
foreach (color; __traits(allMembers, Color))
{
auto oldColor = stdout.backgroundColor(mixin("Color." ~ color));
scope(exit) stdout.backgroundColor(oldColor);
writef("%s ", color);
}
writeln();
}
/// Set blink on (true) or off (false). Note that blink on Windows
/// intensifies the background.
@property
void blink(in bool on)
{
setFace(Capability.blinkFace, on);
}
unittest
{
stdout.blink = true;
write("BLINK");
stdout.blink = false;
write(" NORMAL");
writeln();
}
/// Set reverse on (true) or off (false)
@property
void reverse(in bool on)
{
setFace(Capability.reverseFace, on);
}
unittest
{
stdout.reverse = true;
write("REVERSE");
stdout.reverse = false;
write(" NORMAL");
writeln();
}
/// Set underline on (true) or off (false).
/// BUGS: Does not work on Windows.
@property
void underline(in bool on)
{
setFace(Capability.underlineFace, on);
}
unittest
{
stdout.underline = true;
write("UNDERLINE");
stdout.underline = false;
write(" NORMAL");
writeln();
}
private:
version(Windows) WORD _startUpAttributes;
File _file;
alias _file this;
void setFace(in Capability param, in bool on)
{
if (on) setCapability(param);
else restoreDefaults;
}
version(Windows)
{
WORD getCharacterAttributes() const
{
// TODO
// use std.stdio.stdout handle here
// possible to use _file here?
HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
enforce(stdoutHandle != INVALID_HANDLE_VALUE, sysErrorString(GetLastError()));
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
enforce(GetConsoleScreenBufferInfo(stdoutHandle, &consoleInfo), sysErrorString(GetLastError()));
return consoleInfo.wAttributes;
}
}
void setCapability(T...)(in Capability capability, in T args) if (T.length <= 1)
{
// TODO
// how to set stderr?
// setting color on stderr
// may effect color on stdout as well if stderr is forwarded to stdout
extern(C) static int putcharcout(int c)
{
return fputc(c, std.stdio.stdout.getFP);
}
extern(C) static int putcharcerr(int c)
{
return fputc(c, std.stdio.stderr.getFP);
}
version(Posix)
{
auto capabilityCString = enforce(capabilities[capability],
format("terminal does not support capability '%s'", to!string(capability)));
// TODO
// quick hack
if (_file == std.stdio.stdout)
enforce(!tputs(tparm(capabilityCString, args), 1, &putcharcout));
else if (_file == std.stdio.stderr)
enforce(!tputs(tparm(capabilityCString, args), 1, &putcharcerr));
else assert(false);
}
else version(Windows)
{
// flush to make sure that all characters have been written with
// current terminal settings
_file.flush();
WORD characterAttributes = getCharacterAttributes();
final switch(capability)
{
case Capability.foreground:
static if (args.length == 1)
{
characterAttributes = (characterAttributes & ~0x0007) |
WindowsForegroundColor[args];
break;
}
else assert(false, "This can never happen.");
case Capability.background:
static if (args.length == 1)
{
characterAttributes = (characterAttributes & ~0x0070) |
WindowsBackgroundColor[args];
break;
}
else assert(false, "This can never happen.");
case Capability.boldFace:
characterAttributes |= FOREGROUND_INTENSITY;
break;
case Capability.blinkFace:
// blink on Windows is an intensified background
characterAttributes |= BACKGROUND_INTENSITY;
break;
case Capability.reverseFace:
characterAttributes |= COMMON_LVB_REVERSE_VIDEO;
// emulating reverse
// as it does not work on Windows for some reason
characterAttributes = (characterAttributes & ~0x0077) |
WindowsForegroundColor[backgroundColor] |
WindowsBackgroundColor[foregroundColor];
break;
case Capability.underlineFace:
characterAttributes |= COMMON_LVB_UNDERSCORE;
break;
case Capability.allOff:
characterAttributes = _startUpAttributes;
break;
}
HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
enforce(SetConsoleTextAttribute(stdoutHandle, characterAttributes), sysErrorString(GetLastError()));
}
else
{
static assert(NOT_IMPLEMENTED);
}
}
}
@trusted private void writeRepeat(string text, uint times)
{
for(uint i = 0; i < times; i++)
write(text);
}
struct SimpleProgress
{
private:
float _maxValue = 100.0;
float _curValue;
uint _width = 80;
float _step; //maxValue / _width
public:
Color fgColor = Color.noChange;
@safe this(float maxValue, uint width = 80)
{
_maxValue = maxValue;
_width = width;
_step = maxValue / (cast(float)(width - 2));
}
@trusted void render()
{
auto pg = cast(uint)round((_curValue / _step));
Color oldFGColor;
if(fgColor != Color.noChange)
{
oldFGColor = stdout.foregroundColor(fgColor);
}
try
{
write("[");
if(pg == _width - 2)
writeRepeat("=", pg);
else if(pg > 0)
{
writeRepeat("=", pg - 1);
write(">");
}
writeRepeat(" ", _width - 2 - pg);
write("]");
}
finally
{
if(fgColor != Color.noChange)
{
stdout.foregroundColor(oldFGColor);
}
}
}
@trusted void resize(uint newWidth)
{
_width = newWidth;
_step = _maxValue / (cast(float)(newWidth - 2));
}
@safe void update(float newValue)
{
value = newValue;
}
@safe @property float value()
{
return _curValue;
}
@safe @property void value(float newValue)
{
assert(newValue <= _maxValue);
if(value > _maxValue)
_curValue = _maxValue;
else
_curValue = newValue;
}
}
struct Label
{
private:
string _text;
uint _width;
public:
Color fgColor = Color.noChange;
@safe this(string text)
{
_text = text;
}
@trusted void render()
{
Color oldFGColor;
if(fgColor != Color.noChange)
{
oldFGColor = stdout.foregroundColor(fgColor);
}
try
{
if(_text.length > _width)
{
write(_text[0 .. _width]);
}
else
{
write(_text);
writeRepeat(" ", _width - _text.length);
}
}
finally
{
if(fgColor != Color.noChange)
{
stdout.foregroundColor(oldFGColor);
}
}
}
@trusted void resize(uint newWidth)
{
_width = newWidth;
}
@safe void update(string newText, bool adjWidth = false)
{
_text = newText;
if(adjWidth)
resize(newText.length);
}
@safe @property string text()
{
return _text;
}
@safe @property void text(string newValue)
{
_text = newValue;
}
}
struct Spinner
{
private:
string _text;
uint _width;
ubyte state = 0;
void drawSpinner()
{
switch(state)
{
case 0:
state++;
write("[-]");
break;
case 1:
state++;
write("[\\]");
break;
case 2:
state++;
write("[|]");
break;
case 3:
state = 0;
write("[/]");
break;
default:
assert(false);
}
}
void drawText()
{
write("[");
text.render();
write("]");
return;
}
public:
Label text;
@trusted void render()
{
if(text._text == "")
{
drawSpinner();
}
else
{
drawText();
}
}
@safe void update(string newText, bool adjWidth = false)
{
text.update(newText, adjWidth);
_width = 2 + text._width;
}
}
version(unittest)
{
import core.thread;
}
unittest
{
writeln("Simple spinner test");
auto spin = Spinner();
for(int i = 0; i <= 100; i++)
{
write("\r");
spin.render();
stdout.flush();
Thread.sleep(dur!("msecs")(50));
}
writeln();
}
struct SpinnerLine
{
private:
Spinner _spinner;
uint _width, _reserved;
@trusted void render(bool force)
{
//if(isatty() && !force)
// return;
//if(!isatty())
write("\r");
if(_reserved < _spinner.text._width)
{
leftLabel.resize(_width - _spinner._width);
leftLabel.render();
}
else
{
leftLabel.resize(_width - _reserved - 3);
leftLabel.render();
writeRepeat(" ", _reserved - _spinner.text._width);
}
_spinner.render();
stdout.flush();
}
public:
Label leftLabel;
this(string left = "", uint reserve = 0, uint width = 80)
{
_spinner = Spinner();
leftLabel.update(left, true);
_width = width;
_reserved = reserve;
}
@safe void setState(string state, Color color)
{
_spinner.update(state, true);
_spinner.text.fgColor = color;
render();
}
@trusted void render()
{
render(false);
}
@trusted void finish()
{
//if(isatty())
writeln();
/*else
{
render(true);
writeln();
}*/
}
}
unittest
{
writeln("Spinner line:");
auto spin = SpinnerLine("Doing something interesting:", 1);
for(int i = 0; i <= 100; i++)
{
spin.render();
Thread.sleep(dur!("msecs")(50));
}
spin.setState("OK", Color.green);
writeln();
}
unittest
{
writeln("Long spinner line:");
auto spin = SpinnerLine("Doing something interesting padpadpadpadpadpad"
"padpadpadpadpadpadpadpadpadpadpadpadpadpad:", 1);
for(int i = 0; i <= 100; i++)
{
spin.render();
Thread.sleep(dur!("msecs")(50));
}
spin.setState("FAILED", Color.red);
writeln();
}
unittest
{
writeln("Long spinner line:");
auto spin = SpinnerLine("Doing something interesting(reserved) padpadpadpadpadpad"
"padpadpadpadpadpadpadpadpadpadpadpadpadpad:", 5);
for(int i = 0; i <= 100; i++)
{
spin.render();
Thread.sleep(dur!("msecs")(50));
}
spin.setState("FAILED", Color.red);
writeln();
}
enum Position
{
left,
right,
none
}
struct ProgressLine
{
private:
Label progressLabel;
SimpleProgress progressBar;
uint _width;
void clearLine()
{
write("\r");
writeRepeat(" ", _width);
}
void finishLine()
{
writeln();
}
@trusted void render(bool force)
{
//if(isatty() && !force)
// return;
write("\r");
uint pLabelWidth = 0;
if(displayPercentage != Position.none)
{
progressLabel.update(format(" %.0f%% ",
(progressBar.value / progressBar._maxValue) * 100), true);
pLabelWidth = progressLabel._width;
}
progressBar.resize(_width - pLabelWidth - leftLabel._width
- rightLabel._width);
leftLabel.render();
if(displayPercentage == Position.left)
progressLabel.render();
progressBar.render();
if(displayPercentage == Position.right)
progressLabel.render();
rightLabel.render();
stdout.flush();
}
public:
Position displayPercentage = Position.right;
Label leftLabel, rightLabel;
this(float maxValue, Position pos = Position.right,
string left = "", string right = "", uint width = 80)
{
progressBar = SimpleProgress(maxValue, 60);
displayPercentage = pos;
leftLabel.update(left, true);
rightLabel.update(right, true);
_width = width;
}
void updateProgress(float newValue)
{
progressBar.value = newValue;
render();
}
@trusted void render()
{
render(false);
}
@property void progressBarColor(Color value)
{
progressBar.fgColor = value;
}
@property void progressLabelColor(Color value)
{
progressLabel.fgColor = value;
}
@trusted void finish()
{
//if(isatty())
writeln();
/*else
{
render(true);
writeln();
}*/
}
}
unittest
{
writeln("Progress label left:");
ProgressLine line = ProgressLine(100, Position.left);
line.progressBarColor = Color.green;
line.progressLabelColor = Color.red;
for(int i = 0; i <= 100; i++)
{
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
}
unittest
{
writeln("Progress label right:");
auto line = ProgressLine(100);
for(int i = 0; i <= 100; i++)
{
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
}
unittest
{
writeln("No percentage label:");
auto line = ProgressLine(100);
line.displayPercentage = Position.none;
for(int i = 0; i <= 100; i++)
{
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
}
unittest
{
auto line = ProgressLine(100, Position.right, "Progress with text: ");
for(int i = 0; i <= 100; i++)
{
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
}
unittest
{
auto line = ProgressLine(100, Position.right, "Progress with fixedsize text: ");
for(int i = 0; i <= 100; i++)
{
if(i == 25)
line.leftLabel.update("Doing something else now: ");
if(i == 50)
line.leftLabel.update("Still work to do: ");
if(i == 75)
line.leftLabel.update("Boring: ");
if(i == 100)
line.leftLabel.update("Finally done: ");
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
}
unittest
{
writeln("Aligned progress bars:");
auto line = ProgressLine(10, Position.right, "A: ");
line.leftLabel.resize(20);
for(int i = 0; i <= 10; i++)
{
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
line = ProgressLine(10, Position.right, "B: ");
line.leftLabel.resize(20);
for(int i = 0; i <= 10; i++)
{
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
line = ProgressLine(10, Position.right, "a middle size text: ");
line.leftLabel.resize(20);
for(int i = 0; i <= 10; i++)
{
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
line = ProgressLine(10, Position.right, "very, very, very, very, long text: ");
line.leftLabel.resize(20);
for(int i = 0; i <= 10; i++)
{
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
}
unittest
{
auto line = ProgressLine(100, Position.right, "Text: ", "on both sides!");
for(int i = 0; i <= 100; i++)
{
if(i == 50)
line.rightLabel.update("can be updated!");
line.updateProgress(i);
Thread.sleep(dur!("msecs")(50));
}
writeln();
}
void main()
{
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment