Base widget is a common object that share among widgets, it contains all the necessary attributes and function to manipulate the looks and feel of a widget.
Alignment = enum
Left, Center, Right
Mode = enum
Normal, Filter
SelectionStyle = enum
Highlight, Arrow, HighlightArrow
ViMode = enum
Normal, Insert, Visual
CursorStyle = enum
Block, Ibeam, Underline
WidgetSize = range[0.0 .. 1.0]
WidgetStyle = object
fgColor*: ForegroundColor
bgColor*: BackgroundColor
border*: bool
paddingX1*: int
paddingX2*: int
paddingY1*: int
paddingY2*: int
pressedBgcolor*: BackgroundColor
WidgetBgEvent = object
widgetId*: string
event*: string
args*: seq[string]
error*: string
BaseWidget = object of RootObj
width*: int
height*: int
posX*: int
posY*: int
size*: int
id*: string = ""
title*: string
focus*: bool = false
tb*: TerminalBuffer
style*: WidgetStyle
cursor*: int = 0
rowCursor*: int = 0
colCursor*: int = 0
statusbar*: bool = true
statusbarText*: string = ""
statusbarSize*: int = 0
useCustomStatusbar*: bool = false
visibility*: bool = true
groups*: bool = false
debug*: bool = false
rpms*: int = 50
illwillInit*: bool = false
blocking*: bool = false
helpText*: string = ""
enableHelp*: bool = true
origWidth*: int
origHeight*: int
origPosX*: int
origPosY*: int
The event function should use this type in order to register on the widget.
EventFn[T] = proc (wg: T; args: varargs[string]): void
The event function should use this type in order to register on the widget which return true
or false
result, for example, checkbox widget.
BoolEventFn[T] = proc (wg: T; arg: bool): void
Error type when trying to register a function to forbidden key.
EventKeyError = object of CatchableError
Error type when widget does not init with correct posX
and posY
XYInitError = object of CatchableError
Extending from terminalWidth()
function to fit the size of the terminal buffer
proc consoleWidth(): int
Extending from terminalHeight()
function to fit the size of the terminal buffer
proc consoleHeight(): int
Widget is focused and running in blocking mode, all the key event will be direct to the widget that is currently onControl
. On UI, you should see the focused widget has a bright title (when border is off) or double line border (when border is on).
To exit the onControl loop, you can:
-
[tab]
key event -
widget.focus = false
method onControl(this: ref BaseWidget): void {.base.}
Sending key event from main loop to widget. If there are any custom key events is set, it will be call within this method.
method onUpdate(this: ref BaseWidget; key: Key): void {.base.}
Calling the registered event of the widget. Nothing will be perform if there is no such event.
method call(this: ref BaseWidget; event: string; args: varargs[string]): void {. base.}
Example
var button = newButton(id="btn")
button.on("press", proc (btn: Button, args: vargs[string]) = btn.label = "pressed")
...
button.call("press")
Calling the registered event of the widget. Nothing will be perform if there is no such event.
method call(this: ref BaseWidget; event: string; args: bool): void {.base, raises: [].}
Example
var ch = newCheckbox(id="ch1")
let chEv = proc (ch: Checkbox, checked: bool) =
# some function
ch.on("check", chEv)
...
ch.call("check")
Calling the registered event of the widget. Nothing will be perform if there is no such event. Similar to previous call function but accept non ref
object
method call(this: BaseWidget; event: string; args: varargs[string]): void {. base.}
Calling the registered event of the widget. Nothing will be perform if there is no such event. Similar to previous call function but accept non ref
object
method call(this: BaseWidget; event: string; args: bool): void {.base, raises: [].}
After event finished run in background, you can choose to notify the widget or not. If the notify event is called, the task result will be store in widget channel. The poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(this: ref BaseWidget): void {.base.}
Widget's channel to store event result.
proc channel=(this: ref BaseWidget; channel: Chan[WidgetBgEvent])
Widget's channel to store event result.
proc channel(this: ref BaseWidget): var Chan[WidgetBgEvent]
Widget's channel to store event result.
proc channel=(this: var BaseWidget; channel: Chan[WidgetBgEvent])
Widget's channel to store event result.
proc channel(this: var BaseWidget): var Chan[WidgetBgEvent]
Create a reference from the variable.
Note: the reference created is new, for widget reference created using this proc
will be synced.
proc asRef[T](x: T): ref T
Render the widget.
method render(this: ref BaseWidget): void {.base.}
Get the widget instance
method wg(this: ref BaseWidget): ref BaseWidget {.base.}
Set child widget to use the parent terminalBuffer
that is group
under Container
widget. For custom created widget that also group widgets under another, you should set group=true
and run this method.
method setChildTb(this: ref BaseWidget; tb: TerminalBuffer): void {.base, raises: [].}
Widget error handling
method onError(this: ref BaseWidget; errorCode: string) {.base.}
Set widget background color.
proc bg(bw: ref BaseWidget; bgColor: BackgroundColor)
Set widget foreground color.
proc fg(bw: ref BaseWidget; fgColor: ForegroundColor)
Get widget background color
proc bg(bw: ref BaseWidget): BackgroundColor
Get widget foreground color
proc fg(bw: ref BaseWidget): ForegroundColor
Set widget render with border
proc border(bw: ref BaseWidget; bordered: bool)
Get widget border attribute (whether it is enable or not)
proc border(bw: ref BaseWidget): bool
Set widget render with border
proc border=(bw: ref BaseWidget; bordered: bool)
Set widget padding
proc padding(bw: ref BaseWidget; x1: int; x2: int; y1: int; y2: int)
Set widget padding on the left and right
proc paddingX(bw: ref BaseWidget; x1: int; x2: int)
Set widget padding on the top and bottom
proc paddingY(bw: ref BaseWidget; y1: int; y2: int)
Set widget padding on the left
proc paddingX1(bw: ref BaseWidget): int
Set widget padding on the right
proc paddingX2(bw: ref BaseWidget): int
Set widget padding on top
proc paddingY1(bw: ref BaseWidget): int
Set widget padding on bottom
proc paddingY2(bw: ref BaseWidget): int
Get widget starting x
position with padding.
proc widthPaddLeft(bw: ref BaseWidget): int
Get widget ending x
position with padding.
proc widthPaddRight(bw: ref BaseWidget): int
Get widget starting y
position with padding.
proc heightPaddTop(bw: ref BaseWidget): int
Get widget ending y
position with padding.
proc heightPaddBottom(bw: ref BaseWidget): int
Get widget starting x
position with offset padding.
proc offsetLeft(bw: ref BaseWidget): int
Get widget ending x
position with offset padding.
proc offsetRight(bw: ref BaseWidget): int
Get widget starting y
position with offset padding.
proc offsetTop(bw: ref BaseWidget): int
Get widget ending y
position with offset padding.
proc offsetBottom(bw: ref BaseWidget): int
Shortcut for widthPaddLeft
proc x1(bw: ref BaseWidget): int
Shortcut for widthPaddRight
proc y1(bw: ref BaseWidget): int
Shortcut for heightPaddTop
proc x2(bw: ref BaseWidget): int
Shortcut for heightPaddBottom
proc y2(bw: ref BaseWidget): int
Convert float value to int value by current console width
proc toConsoleWidth(w: float): int
Convert float value to int value by current console height
proc toConsoleHeight(h: float): int
Called when widget is resize. Internally, it perform a calculation on y1
and y2
.
method resize(bw: ref BaseWidget): void {.base.}
Keep track of widget original size for later widget resize action.
proc keepOriginalSize(bw: ref BaseWidget)
Override illwill fill with diff foreground and background Fills a rectangular area with the ch character using the current text attributes. The rectangle is clipped to the extends of the terminal buffer and the call can never fail.
proc fill(tb: var TerminalBuffer; x1, y1, x2, y2: Natural;
bgColor: BackgroundColor; fgColor: ForegroundColor; ch: string = " ")
Render widget border if border is enable
proc renderBorder(bw: ref BaseWidget)
Render widget title if there is title
proc renderTitle(bw: ref BaseWidget; index: int = 0)
Clean the row at y
positon
proc renderCleanRow(bw: ref BaseWidget; index = 0; cleanWith = " ")
Clean a rectangle area base on x1, y1, x2, y2
positon
proc renderCleanRect(bw: ref BaseWidget; x1, y1, x2, y2: int; cleanWith = " ")
Render a rectangle area base on x1, y1, x2, y2
positon
proc renderRect(bw: ref BaseWidget; x1, y1, x2, y2: int;
bgColor: BackgroundColor; fgColor: ForegroundColor;
fillWith = " ")
Render a row based on y
positon
proc renderRow(bw: ref BaseWidget; content: string; index: int = 0)
Render a row based on y
positon with background and foreground color
proc renderRow(bw: ref BaseWidget; bgColor: BackgroundColor;
fgColor: ForegroundColor; content: string; index: int = 0;
withoutPadding = false)
Clear the screen. Default will be clearing with widget background and foreground color.
proc clear(bw: ref BaseWidget)
Calling clear
and render
proc rerender(bw: ref BaseWidget) {.raises: [Exception], tags: [RootEffect].}
Reset widget cursor. Use it when there is a new content being replaced in the widget
method resetCursor(bw: ref BaseWidget): void {.base.}
Show widget
proc show(bw: ref BaseWidget; resetCursor = false) {.raises: [Exception], tags: [RootEffect].}
Hide widget
proc hide(bw: ref BaseWidget)
Render the experimental tag1 on widget
proc experimental(bw: ref BaseWidget)
Note since all the widgets are inherit from BaseWidget
, the method above will be available to them.
ButtonObj = object of BaseWidget
disabled*: bool = false
events*: Table[string, EventFn[ref ButtonObj]]
keyEvents*: Table[Key, EventFn[ref ButtonObj]]
Button = ref ButtonObj
proc newButton(px, py, w, h: int;
label: string;
id = "";
disabled = false;
bgColor = bgBlue;
fgColor = fgWhite;
pressedBgColor = bgGreen;
tb = newTerminalBuffer(w + 2, h + py)): Button
proc newButton(px, py: int;
w, h: WidgetSize;
label: string;
id = "";
disabled = false;
bgColor = bgBlue;
fgColor = fgWhite;
pressedBgColor = bgGreen;
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): Button
proc newButton(id: string): Button
Register an event
proc on(bt: Button; event: string; fn: EventFn[Button])
Register an key-bind event
proc on(bt: Button; key: Key; fn: EventFn[Button]) {.raises: [EventKeyError].}
Call the registered event on widget
proc call(bt: Button; event: string)
Call the key-binded event on widget
method resize(bt: Button)
Render the widget
method render(bt: Button) {.raises: [IllwillError, IOError, ValueError], tags: [WriteIOEffect].}
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(bt: Button) {.raises: [Exception], tags: [RootEffect].}
Passing key event to widget
method onUpdate(bt: Button; key: Key) {.raises: [Exception], tags: [RootEffect].}
Widget main loop when focused
method onControl(bt: Button) {.raises: [IllwillError, Exception], tags: [RootEffect, TimeEffect].}
Return current widget
method wg(bt: Button): ref BaseWidget
Register on enter action for widget. This is register using on
function with enter
as event name.
proc onEnter(bt: Button; eventFn: EventFn[Button])
Register on enter action for widget. This is register using on
function with enter
as event name.
proc onEnter=(bt: Button; eventFn: EventFn[Button])
Set button label
proc label(bt: Button): string
Set button label
proc label=(bt: Button; label: string) {.raises: [Exception], tags: [RootEffect].}
Set button label
proc label(bt: Button; label: string) {.raises: [Exception], tags: [RootEffect].}
CheckboxObj = object of BaseWidget
value*: string = ""
checkMark*: char = 'X'
keyEvents*: Table[Key, BoolEventFn[ref CheckboxObj]]
Checkbox = ref CheckboxObj
proc newCheckbox(px, py, w, h: int;
id = "";
title = "";
label = "";
value = "";
checked = false;
checkMark = 'X';
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(w + 2, h + py)): Checkbox
proc newCheckbox(px, py: int;
w, h: WidgetSize;
id = "";
title = "";
label = "";
value = "";
checked = false;
checkMark = 'X';
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): Checkbox
proc newCheckbox(id: string): Checkbox
Register an event
proc on(ch: Checkbox; event: string; fn: BoolEventFn[Checkbox])
Register an key-bind event
proc on(ch: Checkbox; key: Key; fn: BoolEventFn[Checkbox]) {.raises: [EventKeyError].}
Call the registered event
proc call(ch: Checkbox; event: string; arg: bool)
Render the widget
method render(ch: Checkbox) {.raises: [IllwillError, IOError, ValueError], tags: [WriteIOEffect].}
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(ch: Checkbox) {.raises: [Exception], tags: [RootEffect].}
Passing key event to widget
method onUpdate(ch: Checkbox; key: Key) {.raises: [Exception], tags: [RootEffect, TimeEffect].}
Widget main loop when focused
method onControl(ch: Checkbox) {.raises: [IllwillError, Exception], tags: [RootEffect].}
Return current widget
method wg(ch: Checkbox): ref BaseWidget
Return checkbox check status
proc checked(ch: Checkbox): bool
Set checkbox check status
proc checked(ch: Checkbox; state: bool)
Set checkbox check status
proc checked=(ch: Checkbox; state: bool)
Register on enter action for widget. This is register using on
function with enter
as event name.
proc onEnter=(ch: Checkbox; enterEv: BoolEventFn[Checkbox])
Register on enter action for widget. This is register using on
function with enter
as event name.
proc onEnter(ch: Checkbox; enterEv: BoolEventFn[Checkbox])
Set checkbox label
proc val(ch: Checkbox; label: string) {.raises: [Exception], tags: [RootEffect].}
Return checkbox label
proc label(ch: Checkbox): string
Set checkbox label
proc label=(ch: Checkbox; label: string) {.raises: [Exception], tags: [RootEffect].}
Set checkbox label
proc label(ch: Checkbox; label: string) {.raises: [Exception], tags: [RootEffect].}
Unavailable for now, due to missing dependencies. Chart required asciigraph for rendering the chart UI. It is more towards display purpose, accuracy is not the focus. Chart have some limitation, it do not aggregate the data when display.
AxisObj = object
Axis = ref AxisObj
ChartObj = object of BaseWidget
events*: Table[string, EventFn[ref ChartObj]]
keyEvents*: Table[Key, EventFn[ref ChartObj]]
Chart = ref ChartObj
proc newAxis(
title: string = "";
data: seq[float64] = newSeq()): Axis
proc newChart(
px, py, w, h: int;
id = "";
axis: Axis = newAxis();
title = "";
border = true;
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(w + 2, h + py)): Chart
proc newChart(
px, py: int;
w, h: WidgetSize;
id = ""; axis:
Axis = newAxis();
title = "";
border = true;
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): Chart
proc newChart(id: string): Chart
Render chart widget
method render(c: Chart) {.raises: [IllwillError, IOError, ValueError], tags: [WriteIOEffect].}
Return chart widget object
method wg(c: Chart): ref BaseWidget
Register event on widget
proc on(c: Chart; event: string; fn: EventFn[Chart])
Register key-bind event on widget
proc on(c: Chart; key: Key; fn: EventFn[Chart])
Call registered event on ref widget
method call(c: Chart; event: string; args: varargs[string])
Call registered event on widget object
method call(c: ChartObj; event: string; args: varargs[string])
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(c: Chart) {.raises: [Exception], tags: [RootEffect].}
Passing key event to widget
method onUpdate(c: Chart; key: Key) {.raises: [Exception], tags: [RootEffect, TimeEffect].}
Widget main loop when focused
method onControl(c: Chart) {.raises: [IllwillError, Exception], tags: [RootEffect].}
Set chart axis
proc axis=(c: Chart; axis: Axis) {.raises: [Exception], tags: [RootEffect].}
Set chart axis
proc axis(c: Chart; axis: Axis) {.raises: [Exception], tags: [RootEffect].}
Container is a special widget which used to group widgets under one. You can use it for a section, a modal pop up, etc. Widgets added into container will be layout inline automatically based on percentage defined (float value).
For the below layout:
+-------------------------------+
| input box (1.0, 0.2) |
+-------------------------------+
| display (1.0, 0.4) |
| |
| |
+-------------------------------+
| listview | table |
+-------------------------------+
| (0.6, 0.4) | (0.4, 0.4) |
| | |
| | |
+-------------------------------+
input box (1.0, 0.2)
-> input box will occupied 100% width and 20% height of the container size.
display (1.0, 0.4)
-> display will occupied 100% width and 40% height of the container
listview (0.6, 0.4)
-> listview will occupied 60% of width and 40% of height of the container
table (0.4, 0.4)
-> table will occupied 40% of width and 40% of height of the container.
ContainerObj = object of BaseWidget
Container = ref ContainerObj
proc newContainer(
px, py, w, h: int;
id = "";
title = "";
border = true;
bgColor = bgNone;
fgColor = fgWhite;
widgets = newSeq();
tb = newTerminalBuffer(w + 2, h + py)): Container
proc newContainer(
px, py: int;
w, h: WidgetSize;
id = "";
title = "";
border = true;
bgColor = bgNone;
fgColor = fgWhite;
widgets = newSeq();
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): Container
proc newContainer(id: string): Container
Adding new widget to the container based on float value ([0.0..1.0]
). Overflow of widget does not supported.
proc add(ctr: Container; wg: ref BaseWidget; width: float; height: float)
Set child widget with parent terminal buffer.
method setChildTb(ctr: Container; tb: TerminalBuffer): void
Register event on widget
proc on(ctr: Container; event: string; fn: EventFn[Container])
Register key-bind event on widget
proc on(ctr: Container; key: Key; fn: EventFn[Container]) {.raises: [EventKeyError].}
Call registered event (ref Container)
method call(ctr: Container; event: string; args: varargs[string])
Call registered event
method call(ctr: ContainerObj; event: string; args: varargs[string])
Render widget
method render(ctr: Container) {.raises: [Exception, IllwillError, IOError, ValueError], tags: [RootEffect, WriteIOEffect].}
Passing key event to widget
method onUpdate(ctr: Container; key: Key) {.raises: [IllwillError, Exception], tags: [RootEffect].}
Widget main loop when focused
method onControl(ctr: Container) {.raises: [Exception, IllwillError], tags: [RootEffect].}
Return current widget
method wg(ctr: Container): ref BaseWidget
Display used for display text on the panel, it support for scrolling and wordwrap for the content.
Note When rendering larger content, depends on the terminal emulator, it may not clean up the screen buffer fully which result in distortion on the display
To override the current text split into rows function.
Note this would change the behavior on how Display
render itself
CustomRowRecal = proc (text: string; dp: Display): seq[string]
DisplayObj = object of BaseWidget
wordwrap*: bool = false
useCustomTextRow* = false
customRowRecal*: Option[CustomRowRecal]
events*: Table[string, EventFn[Display]]
keyEvents*: Table[Key, EventFn[Display]]
Display = ref DisplayObj
proc newDisplay(
px, py, w, h: int;
id = "";
title: string = "";
text: string = "";
border: bool = true;
statusbar = true;
wordwrap = false;
enableHelp = false;
bgColor: BackgroundColor = bgNone;
fgColor: ForegroundColor = fgWhite;
customRowRecal: Option[CustomRowRecal] = none(CustomRowRecal);
tb: TerminalBuffer = newTerminalBuffer(w + 2, h + py)): Display {.raises: [EventKeyError], tags: [RootEffect].}
proc newDisplay(
px, py: int;
w, h: WidgetSize;
id = "";
title = "";
text = "";
border = true;
statusbar = true;
wordwrap = false;
enableHelp = false;
bgColor = bgNone;
fgColor = fgWhite;
customRowRecal: Option[CustomRowRecal] = none(CustomRowRecal);
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): Display {.raises: [EventKeyError], tags: [ReadEnvEffect, RootEffect].}
proc newDisplay(id: string): Display {.raises: [EventKeyError], tags: [RootEffect].}
Recalculate the size required to render display widget
method resize(dp: Display)
Render the widget
method render(dp: Display) {.raises: [Exception, IllwillError, IOError, ValueError], tags: [RootEffect, WriteIOEffect].}
Reset display cursor. text=
or text(val: string)
function will call this function, you can also explicitly calling it to reset the cursor.
proc resetCursor(dp: Display)
Register event on widget
proc on(dp: Display; event: string; fn: EventFn[Display])
Register key-bind event on widget
proc on(dp: Display; key: Key; fn: EventFn[Display]) {.raises: [EventKeyError].}
Call the registered event (ref Display)
method call(dp: Display; event: string; args: varargs[string])
Call the registered event
method call(dp: DisplayObj; event: string; args: varargs[string])
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(dp: Display) {.raises: [Exception], tags: [RootEffect].}
Passing key event to widget
method onUpdate(dp: Display; key: Key) {.raises: [Exception], tags: [RootEffect].}
Widget main loop when focused
method onControl(dp: Display) {.raises: [Exception, IllwillError], tags: [RootEffect, TimeEffect].}
Return current widget
method wg(dp: Display): ref BaseWidget
Return text stored in Display widget
proc text(dp: Display): string
Set display text content
proc text=(dp: Display; text: string) {.raises: [Exception], tags: [RootEffect].}
Set display text content
proc text=(dp: Display; text: string;
customRowRecal: proc (text: string; dp: Display): seq[string]) {.raises: [Exception], tags: [RootEffect].}
Toggle wordwrap
proc wordwrap=(dp: Display; wrap: bool) {.raises: [Exception], tags: [RootEffect].}
Add new text content to Display
proc add(dp: Display; text: string; autoScroll = false) {.raises: [Exception], tags: [RootEffect].}
InputBoxObj = object of BaseWidget
events*: Table[string, EventFn[InputBox]]
keyEvents*: Table[Key, EventFn[InputBox]]
InputBox = ref InputBoxObj
proc on(ib: InputBox; key: Key; fn: EventFn[InputBox]): void {.raises: [EventKeyError].}
proc newInputBox(
px, py, w, h: int;
title = "";
val = "";
modeChar = '>';
border = true;
statusbar = false;
bgColor = bgNone;
fgColor = fgWhite;
tb: TerminalBuffer = newTerminalBuffer(w + 2, h + py)): InputBox {.raises: [EventKeyError], tags: [RootEffect].}
proc newInputBox(
px, py: int;
w, h: WidgetSize;
title = "";
val = "";
modeChar = '>';
border = true;
statusbar = false;
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): InputBox {.raises: [EventKeyError], tags: [ReadEnvEffect, RootEffect].}
proc newInputBox(id: string): InputBox
Render widget
method render(ib: InputBox) {.raises: [Exception, IllwillError, IOError, ValueError], tags: [RootEffect, WriteIOEffect].}
Remove inputbox, similar to clear() but without filled with color
proc remove(ib: InputBox)
Register event on widget
proc on(ib: InputBox; event: string; fn: EventFn[InputBox])
Call registered event on widget (ref Inputbox)
method call(ib: InputBox; event: string; args: varargs[string])
Call registered event on widget
method call(ib: InputBoxObj; event: string; args: varargs[string])
Passing key event to widget
method onUpdate(ib: InputBox; key: Key) {.raises: [Exception], tags: [RootEffect].}
Widget main loop when focused
method onControl(ib: InputBox) {.raises: [IllwillError, Exception], tags: [RootEffect].}
Return current widget
method wg(ib: InputBox): ref BaseWidget
Set InputBox value
proc value=(ib: InputBox; val: string) {.raises: [EncodingError, OSError, Exception], tags: [RootEffect].}
Set InputBox value
proc value(ib: InputBox; val: string) {.raises: [EncodingError, OSError, Exception], tags: [RootEffect].}
Return InputBox value
proc value(ib: InputBox): string
Register "enter" event via on
function
proc onEnter(ib: InputBox; enterEv: EventFn[InputBox])
Register "enter" event via on
function
proc onEnter=(ib: InputBox; enterEv: EventFn[InputBox])
Maximum value of 1000.0
GaugePercent = range[0.0 .. 1000.0]
Gauge render background color, each 20%
PercentileColor = enum
Tweenty, Fourthy, Sixty, Eighty, Hundred
GaugeObj = object of BaseWidget
loadedBlock*: char = ' '
loadingBlock*: char = '|'
percentileColor*: array[PercentileColor, ForegroundColor]
events*: Table[string, EventFn[Gauge]]
Gauge = ref GaugeObj
proc newGauge(
px, py, w, h: int;
id = "";
border = true;
percent: GaugePercent = 0.0;
loadingBlock: char = ' ';
loadedBlock: char = '|';
bgColor: BackgroundColor = bgNone;
fgColor: ForegroundColor = fgWhite;
percentileColor: array[PercentileColor, ForegroundColor] = [fgGreen, fgBlue, fgYellow, fgMagenta, fgRed];
tb: TerminalBuffer = newTerminalBuffer(w + 2, h + py)): Gauge
Create new Gauge, setting width and height with percentage
proc newGauge(
px, py: int;
w, h: WidgetSize; id = "";
border = true;
percent: GaugePercent = 0.0;
loadingBlock: char = ' ';
loadedBlock: char = '|';
bgColor = bgNone;
fgColor = fgWhite;
percentileColor: array[PercentileColor, ForegroundColor] = [fgGreen, fgBlue, fgYellow, fgMagenta, fgRed];
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): Gauge
proc newGauge(id: string): Gauge
Render widget
method render(g: Gauge) {.raises: [ValueError, IllwillError, IOError], tags: [WriteIOEffect].}
Return widget
method wg(g: Gauge): ref BaseWidget
Set Gauge's point
proc set(g: Gauge; point: float) {.raises: [Exception], tags: [RootEffect].}
Set Gauge to completed
proc completed(g: Gauge) {.raises: [Exception], tags: [RootEffect].}
Register event on widget
proc on(g: Gauge; event: string; fn: EventFn[Gauge])
Call registered event (ref GaugeObj)
method call(g: Gauge; event: string; args: varargs[string])
Call registered event
method call(g: GaugeObj; event: string; args: varargs[string])
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(g: Gauge) {.raises: [Exception], tags: [RootEffect].}
Alignment of text within Label
LabelObj = object of BaseWidget
align*: Alignment = Left
Label = ref LabelObj
proc newLabel(
px, py, w, h: int;
id = "";
text = "";
border = false;
align = Left;
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(w + 2, h + py)): Label
Set widget width and height with percentage of console size
proc newLabel(
px, py: int;
w, h: WidgetSize;
id = "";
text = "";
border = false;
align = Left;
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): Label
proc newLabel(id: string): Label
Render widget
method render(lb: Label) {.raises: [IllwillError, IOError, ValueError], tags: [WriteIOEffect].}
Return widget
method wg(lb: Label): ref BaseWidget
Set text to Label
proc text=(lb: Label; text: string) {.raises: [Exception], tags: [RootEffect].}
Set text to Label
proc text(lb: Label; text: string) {.raises: [Exception], tags: [RootEffect].}
Register event on widget (ref LabelObj)
proc on(lb: Label; event: string; fn: EventFn[Label])
Register event on widget
method call(lb: Label; event: string; args: varargs[string])
Call registered event
method call(lb: LabelObj; event: string; args: varargs[string])
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(lb: Label) {.raises: [Exception], tags: [RootEffect].}
ListView
used to render a list of ListRow
object, by leverage on the registered event, we can transform it easily to act like a Selection.
ListRowObj = object
value*: string
ListRow = ref ListRowObj
ListViewObj = object of BaseWidget
selectionStyle*: SelectionStyle
events*: Table[string, EventFn[ListView]]
keyEvents*: Table[Key, EventFn[ListView]]
ListView = ref ListViewObj
proc newListRow(
index: int;
text: string;
value: string;
align = Center;
bgColor = bgNone;
fgColor = fgWhite;
visible = true;
selected = false): ListRow
Note When using Highlight
and HighlightArrow
selection style, you will need to set the bgColor
of the row.
When creating a ListView
with rows parameters pass in, the rows item's index will be overwrite based on its position.
proc newListView(
px, py, w, h: int;
id = "";
title = "";
border = true;
statusbar = true;
statusbarText = "[?]";
enableHelp = false;
rows: seq[ListRow] = newSeq();
bgColor = bgNone;
fgColor = fgWhite;
selectionStyle: SelectionStyle = Highlight;
tb: TerminalBuffer = newTerminalBuffer(w + 2, h + py + 4)): ListView {.raises: [EventKeyError], tags: [RootEffect].}
Set widget width and height with percentage of console size
proc newListView(
px, py: int;
w, h: WidgetSize;
id = "";
title = "";
border = true;
statusbar = true;
statusbarText = "[?]";
enableHelp = false;
rows: seq[ListRow] = newSeq();
bgColor = bgNone;
fgColor = fgWhite;
selectionStyle: SelectionStyle = Highlight;
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py + 4)): ListView {.raises: [EventKeyError], tags: [ReadEnvEffect, RootEffect].}
proc newListView(id: string): ListView {.raises: [EventKeyError], tags: [RootEffect].}
Recalculate ListView size when console resized
method resize(lv: ListView)
Render widget
method render(lv: ListView) {.raises: [Exception, IllwillError, IOError, ValueError], tags: [RootEffect, WriteIOEffect].}
Get selected ListRow
. It will return the current selected
ListRow.
proc selected(lv: ListView): ListRow
Set the selected ListRow
by index
proc selectedRow=(lv: ListView; i: int)
Reset ListView cursor. When setting rows=
value, this function will be called.
proc resetCursor(lv: ListView)
Register event on widget
proc on(lv: ListView; event: string; fn: EventFn[ListView])
Register key-bind event on widget
proc on(lv: ListView; key: Key; fn: EventFn[ListView]) {.raises: [EventKeyError].}
Call registered event
proc call(lv: ListView; event: string; args: varargs[string])
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(lv: ListView) {.raises: [Exception], tags: [RootEffect].}
Passing key event to widget
method onUpdate(lv: ListView; key: Key) {.raises: [Exception], tags: [RootEffect, TimeEffect].}
Widget main loop when focused
method onControl(lv: ListView): void {.raises: [IllwillError, Exception], tags: [RootEffect].}
Return current widget
method wg(lv: ListView): ref BaseWidget
Set "enter" event on widget
proc onEnter=(lv: ListView; enterEv: EventFn[ListView])
Set "enter" event on widget
proc onEnter(lv: ListView; enterEv: EventFn[ListView])
Return rows of ListView
proc rows(lv: ListView): seq[ListRow]
Set rows to ListView
proc rows=(lv: ListView; rows: seq[ListRow])
Get ListRow index
proc index(lr: ListRow): int
Get ListRow text label
proc text(lr: ListRow): string
Get ListRow value
proc value(lr: ListRow): string
Get ListRow background color
proc bgColor(lr: ListRow): BackgroundColor
Get ListRow foreground color
proc fgColor(lr: ListRow): ForegroundColor
Get ListRow visibility
proc visible(lr: ListRow): bool
Get the selected status of ListRow
proc selected(lr: ListRow): bool
Get ListRow alignment
proc align(lr: ListRow): Alignment
Set ListRow text label
proc text=(lr: ListRow; text: string)
Set ListRow value
proc value=(lr: ListRow; value: string)
Set ListRow background color
proc bgColor=(lr: ListRow; bgColor: BackgroundColor)
Set ListRow foreground color
proc fgColor=(lr: ListRow; fgColor: ForegroundColor)
Set ListRow visibility
proc visible=(lr: ListRow; visible: bool)
Set ListRow selected
proc selected=(lr: ListRow; selected: bool)
Set ListRow alignment
proc align=(lr: ListRow; align: Alignment)
Percentage range from 0.0 to 100.0
Percent = range[0.0 .. 100.0]
ProgressBarObj = object of BaseWidget
events*: Table[string, EventFn[ProgressBar]]
ProgressBar = ref ProgressBarObj
Define a different string value for loadedBlock
and loadingBlock
to change how progress bar render
proc newProgressBar(
px, py, w, h: int;
id = "";
border = true;
percent: Percent = 0.0;
loadedBlock = "█";
loadingBlock = "-";
showPercentage = true;
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(w + 2, h + py)): ProgressBar
Set width and height with percentage based on console size
proc newProgressBar(
px, py: int;
w, h: WidgetSize;
id = "";
border = true;
percent: Percent = 0.0;
loadedBlock = "█";
loadingBlock = "-";
showPercentage = true;
bgColor = bgNone;
fgColor = fgWhite;
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): ProgressBar
proc newProgressBar(id: string): ProgressBar
Render widget
method render(pb: ProgressBar) {.raises: [ValueError, IllwillError, IOError], tags: [WriteIOEffect].}
Return widget
method wg(pb: ProgressBar): ref BaseWidget
Update progress bar, adding new float point to progress
proc update(pb: ProgressBar; point: float) {.raises: [Exception], tags: [RootEffect].}
Set progress bar, overwrite progress value
proc set(pb: ProgressBar; point: float) {.raises: [Exception], tags: [RootEffect].}
Set progress bar to complete
proc completed(pb: ProgressBar) {.raises: [Exception], tags: [RootEffect].}
Register event on widget
proc on(pb: ProgressBar; event: string; fn: EventFn[ProgressBar])
Call registered event on widget (ref ProgressBarObj)
method call(pb: ProgressBar; event: string; args: varargs[string])
Call registered event on widget
method call(pb: ProgressBarObj; event: string; args: varargs[string])
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(pb: ProgressBar) {.raises: [Exception], tags: [RootEffect].}
Table widget to render tabular data, it can load from seq
or csv
file.
ColumnType = enum
Header, Column
TableColumnObj = object
index*: int
text*: string
key*: string
value*: string = ""
TableColumn = ref TableColumnObj
TableRowObj = object
index*: int
columns*: seq[TableColumn]
selected*: bool = false
value*: string = ""
TableRow = ref TableRowObj
TableObj = object of BaseWidget
selectionStyle*: SelectionStyle
events*: Table[string, EventFn[ref TableObj]]
keyEvents*: Table[Key, EventFn[ref TableObj]]
Table = ref TableObj
proc on(table: Table; key: Key; fn: EventFn[Table]) {.raises: [EventKeyError].}
A column within a table row
proc newTableColumn(
width: int;
height: int = 1;
text = "";
key = "";
index = 0;
overflow: bool = false;
bgColor = bgNone;
fgColor = fgWhite;
align = Left; columnType = Column): TableColumn
Instantiate a minimal table column with default value and column type as column
proc newTableColumn*(text = "", columnType = Column): TableColumn
A table row within the table
proc newTableRow(
width: int;
height = 1;
columns: seq[TableColumn] = newSeq();
index = 0;
bgColor = bgNone;
fgColor = fgWhite;
selected = false): TableRow
Instantiate a minimal table row with default value
proc newTableRow*(): TableRow
Set columns to table row
proc `columns=`*(tr: TableRow, columns: seq[TableColumn])
Set columns to table row
proc columns*(tr: TableRow, columns: seq[string])
Add column with text provided to table row, this function set both text and key as the same value
proc addColumn*(tr: TableRow, column: string)
Add column to table row
proc addColumn*(tr: TableRow, column: TableColumn)
Create table with header and rows given, rows will be reassign with new index value according to its sequence.
proc newTable(
px, py, w, h: int;
rows: seq[TableRow];
headers: Option[TableRow] = none(TableRow);
id = "";
title = "";
border = true;
statusbar = true;
enableHelp = false;
bgColor = bgNone;
fgColor = fgWhite;
selectionStyle: SelectionStyle;
maxColWidth = w;
tb = newTerminalBuffer(w + 2, h + py + 4)): Table {.raises: [EventKeyError], tags: [RootEffect].}
Create table without providing header and rows.
proc newTable(
px, py, w, h: int;
id = "";
title = "";
border = true;
statusbar = true;
enableHelp = false;
bgColor = bgNone;
fgColor = fgWhite;
selectionStyle: SelectionStyle = Highlight;
maxColWidth = w;
tb = newTerminalBuffer(w + 2, h + py + 4)): Table {.raises: [EventKeyError], tags: [RootEffect].}
Set width and height with percentage of current console size
proc newTable(
px, py: int;
w, h: WidgetSize;
rows: seq[TableRow];
headers: Option[TableRow] = none(TableRow);
id = "";
title = "";
border = true;
statusbar = true;
enableHelp = false;
bgColor = bgNone;
fgColor = fgWhite;
selectionStyle: SelectionStyle = Highlight;
maxColWidth = w.toInt;
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py + 4)): Table {.raises: [EventKeyError], tags: [ReadEnvEffect, RootEffect].}
proc newTable(id: string): Table {.raises: [EventKeyError], tags: [RootEffect].}
Recalculate table size when console resized
method resize(table: Table)
Render table
method render(table: Table): void {.raises: [Exception, IllwillError, IOError, ValueError], tags: [RootEffect, WriteIOEffect].}
Register event on widget
proc on(table: Table; event: string; fn: EventFn[Table])
Call registered event on widget
proc call(table: Table; event: string; args: varargs[string])
poll
method used to get the result and call the necessary registered event in the widget. This method normally consume by the main loop.
method poll(table: Table) {.raises: [Exception], tags: [RootEffect].}
Passing key event to widget
method onUpdate(table: Table; key: Key) {.raises: [Exception, EventKeyError], tags: [RootEffect, TimeEffect].}
Widget main loop when focused
method onControl(table: Table): void {.raises: [IllwillError, Exception], tags: [RootEffect].}
Return current widget
method wg(table: Table): ref BaseWidget
Set table header
proc header=(table: Table; header: TableRow)
Set table header
proc header(table: Table; header: TableRow)
Get table header
proc header(table: Table): Option[TableRow]
Add new row to table
proc addRow(table: Table; tablerow: TableRow; index: Option[int] = none(int)): void
Remove row from table
proc removeRow(table: Table; index: int)
Clear all the rows in table
proc clearRows*(table: Table)
Get current selected table row
proc selected(table: Table): TableRow
Table load data from csv file
proc loadFromCsv(
table: Table;
filepath: string;
withHeader = false;
withIndex = false): void {.raises: [IOError], tags: [ReadIOEffect, WriteIOEffect].}
Create table header from array
proc headerFromArray(
table: Table;
header: openArray[string],
bgColor: illwill.BackgroundColor = bgNone,
fgColor: illwill.ForegroundColor = fgWhite
)
Create table rows from seq
proc loadFromSeq(
table: Table;
rows: openArray[seq[string]]) {.raises: [ValueError, SizeDiffError].}
The textarea widget has two mode, vi-enabled mode and edit mode. For vi-enabled mode, it supported basic keybinding of vi and comes with (Normal, Visual, Insert) mode. It is currently under experimental release.
Edit mode textarea works similar to a html textarea or an InputBox
with multiple rows. ViMode is currently under experiment, test it before you are using it.
Normal mode (vi-enabled) minimal supported of vi keybinding
i, Insert = switch to insert mode
v = switch to visual mode
A = append at end of line
Delete = delete at cursor
Tab = exit widget
Left, <-, H = move backward
Right, L = move forward
Up, K = move upward
Down, J = move downward
Home, ^ = goto beginning of line
End, $ = goto end of line
w = goto next word
b = goto previous word
x = cut text at cursor
p = paste last history at cursor
u = undo last change
dd = delete whole line
Escape = back to normal mode
Visual mode (vi-enabled) minimal supported of vi keybinding
v = switch to visual mode
Delete = delete selected text
d = delete selected text
x = cut text at cursor
y = copy/yank selected text
Tab = exit widget
Left, <-, H = move backward
Right, L = move forward
Up, K = move upward
Down, J = move downward
Home, ^ = goto beginning of line
End, $ = goto end of line
w = goto next word
b = goto previous word
Escape = back to normal mode
Customize vi-enabled style
ViStyle = object
normalBg*: BackgroundColor
insertBg*: BackgroundColor
visualBg*: BackgroundColor
normalFg*: ForegroundColor
insertFg*: ForegroundColor
visualFg*: ForegroundColor
cursorAtLineBg*: BackgroundColor
cursorAtLineFg*: ForegroundColor
TextAreaObj = object of BaseWidget
viStyle*: ViStyle
events*: Table[string, EventFn[TextArea]]
editKeyEvents*: Table[Key, EventFn[TextArea]]
normalKeyEvents*: Table[Key, EventFn[TextArea]]
visualKeyEvents*: Table[Key, EventFn[TextArea]]
TextArea = ref TextAreaObj
vimode
is default disabled, enabled it by setting enableViMode=true
proc newTextArea(
px, py, w, h: int;
title = "";
val = " ";
border = true;
statusbar = false;
enableHelp = false;
bgColor = bgNone;
fgColor = fgWhite;
cursorBg = bgBlue;
cursorFg = fgWhite;
cursorStyle = Block;
enableViMode = false;
vimode: ViMode = Normal;
viStyle: ViStyle = newViStyle();
tb = newTerminalBuffer(w + 2, h + py)): TextArea
Set width and height percentage based on console size
vimode
is default disabled, enabled it by setting enableViMode=true
proc newTextArea(
px, py: int;
w, h: WidgetSize;
title = "";
val = " ";
border = true;
statusbar = false;
enableHelp = false;
bgColor = bgNone;
fgColor = fgWhite;
cursorBg = bgBlue;
cursorFg = fgWhite;
cursorStyle = Block;
enableViMode = false;
vimode: ViMode = Normal;
viStyle: ViStyle = newViStyle();
tb = newTerminalBuffer(toInt(w) + 2, toInt(h) + py)): TextArea
proc newTextArea(id: string): TextArea
Register event on widget
proc on(t: TextArea; event: string; fn: EventFn[TextArea])
Register key-bind event on widget. Passing ViMode
to register on a different mode, default to Insert
mode.
proc on(t: TextArea; key: Key; fn: EventFn[TextArea]; vimode: ViMode = Insert) {.raises: [EventKeyError].}
Call registered event, if vi-enabled
, it will call the event registered respectively according to ViMode
proc call(t: TextArea; event: string; args: varargs[string])
Render widget
method render(t: TextArea) {.raises: [IllwillError, IOError, ValueError], tags: [WriteIOEffect].}
Reset cursor of widget
proc resetCursor(t: TextArea)
Running register event based on ViMode
, trigger this by typing :
. Similar to vi command key binding, example, :q!
can map to exit textarea
proc commandEvent(t: TextArea) {.raises: [EventKeyError, Exception], tags: [RootEffect].}
Passing key event to widget
method onUpdate(t: TextArea; key: Key) {.raises: [Exception, IllwillError, EventKeyError], tags: [RootEffect, TimeEffect].}
Widget main loop when focused
method onControl(t: TextArea) {.raises: [IllwillError, Exception, EventKeyError], tags: [RootEffect, TimeEffect].}
Return current widget
method wg(t: TextArea): ref BaseWidget
Get textarea value
proc value(t: TextArea): string
Set textarea value
proc value=(t: TextArea; val: string) {.raises: [Exception], tags: [RootEffect].}
Set textarea value
proc value(t: TextArea; val: string) {.raises: [Exception], tags: [RootEffect].}