Examples¶
Layouting Examples¶
Visual Demo¶
from functui import Rect, layout_to_result
from functui.flex import flex, flex_custom, hbox_flex_wrap, hbox_flex, vbox_flex
from functui.common import *
from functui.classes import *
from functui.io.ansi import result_to_str
from functui.io.html import result_to_html_str
from itertools import batched
from functui.rich_text import adaptive_text
import math
def display_char_styles():
out = []
for i in StyleAttr:
out.append(text(i.name) | push_rule(StyleRule(add_attrs=i)))
return hbox(intersperse(out, text(" ")))
def cell_white_text(color8: int):
return text(f"{color8: >3}") | padding | bg_fill | bg(color8) | fg(Color4.BRIGHT_WHITE)
def cell_black_text(color8: int):
return text(f"{color8: >3}") | padding | bg_fill | bg(color8) | fg(16)
def display_color_8():
light_cube = []
dark_cube = []
for i in range(16, 232):
if ((i+2) % 36) < 18:
light_cube.append(
cell_black_text(i)
)
else:
dark_cube.append(
cell_white_text(i)
)
return vbox([
*(hbox(row) for row in batched(dark_cube, 6*3)),
*(hbox(row) for row in batched(light_cube, 6*3)),
])
def title(c: Layout):
return static_box([
hbar,
c | bold | offset(x=1) | shrink,
]) | custom_padding(top = 1, bottom = 0)
def flex_item(string: str):
return text(string) | center | custom_padding(1, 1, 1, 1)
def flex_itemw(string: str):
return text(string) | center | custom_padding(1, 1, 1, 1)
layout = vbox([
hbox_flex([
vbox([
text("4 bit") | fg(Color4.GREEN),
text("8 bit") | fg(45),
text("True\ncolor") | fg(Color4.BRIGHT_RED),
]) | padding | flex,
display_color_8()
]) | border_with_title(text("Colors")),
display_char_styles() | border_with_title(text("Styles")),
hbox_flex([
vbox([
hbox_flex(intersperse([
flex_item("flex") | flex,
flex_item("flex_custom(2)") | flex_custom(2),
flex_item("no flex"),
], sep=vbar)) | border_with_title(text("Flexible Containers")),
adaptive_text(LOREM) | padding | dim | border_with_title(text("Text wrapping")),
]) | flex,
hbox_flex_wrap(
[(text("flex") | custom_padding(1, 1, 1, 1) | styled(border, rule_fg(i+0)) | flex_custom(1, basis=True)) for i in range(10)]
)| border_with_title(text("Flexible and Wrappable Containers")) | flex,
]),
])
result = layout_to_result(layout, Rect(102, 45))
print(result_to_str(result))
Color Capabilities Test¶
from functui.common import *
from functui.classes import *
from functui.rich_text import adaptive_text, span
from functui import Rect, layout_to_result, result_to_str, Color4
from itertools import batched
def cell_white_text(color8: int):
return text(f"{color8: >3}") | padding | bg_fill | bg(color8) | fg(Color4.BRIGHT_WHITE)
def cell_black_text(color8: int):
return text(f"{color8: >3}") | padding | bg_fill | bg(color8) | fg(16)
def display_color_8():
regular = []
intense = []
light_cube = []
dark_cube = []
blacks = []
whites = []
for i in range(8):
regular.append(
cell_white_text(i)
)
intense.append(
cell_black_text(i+8)
)
for i in range(16, 232):
if ((i+2) % 36) < 18:
light_cube.append(
cell_black_text(i)
)
else:
dark_cube.append(
cell_white_text(i)
)
for i in range(232, 244):
blacks.append(
cell_white_text(i)
)
whites.append(
cell_black_text(i+12)
)
return vbox([
text("Regular and bright colors are rendered differently depending on terminal theme"),
hbox([text("Regular: "), *regular]),
hbox([text("Bright: "), *intense]),
text(" "),
text("Color Cube"),
*(hbox(row) for row in batched(dark_cube, 6*3)),
*(hbox(row) for row in batched(light_cube, 6*3)),
text(" "),
text("Color ramps, black and white intentionaly excluded"),
hbox(blacks),
hbox(whites),
])
layout = vbox([
display_color_8() | shrink,
])
result = layout_to_result(layout, Rect(140, 40))
if __name__ == "__main__":
print(result_to_str(result))
Interactive examples using the elm architecture¶
Template for ELM projects using raw IO¶
from functui.classes import *
from functui.common import *
from functui.nav import ROOT_HORIZONTAL, ROOT_VERTICAL, InteractibleID, NavState, DEFAULT_NAV_BINDINGS, interaction_area
from functui.io.raw import terminal
from dataclasses import dataclass
@dataclass
class Model():
nav: NavState
# Add more attributes to contain all of your persistent state.
def update(input: InputEvent, res: Result, m: Model):
# update NavState for keyboard and mouse interactivity.
action = None
if input.key_event in DEFAULT_NAV_BINDINGS:
action = DEFAULT_NAV_BINDINGS[input.key_event]
m.nav = m.nav.update(
res=res,
action=action,
nav_tree=[],
mouse_position=input.mouse_position_event
)
# Put your update code here.
# Create your keyboard navigation tree here.
def view(m: Model):
# Layout rendering code here.
layout = vbox([
text("Hello World!") | border,
text("Press ctrl+c to exit.") | fg(Color4.CYAN) | border,
])
return layout
m = Model(
nav = NavState(),
)
with terminal() as term:
while True:
# render
res = layout_to_result(view(m), term.get_terminal_size())
term.display_result(res)
# wait for input
event = term.block_until_input()
# update
if event.key_event == "ctrl+c":
break
update(event, res, m)
ELM Counter App with ansi IO¶
from dataclasses import dataclass
from functui.common import *
from functui import layout_to_str, Rect, Layout
import os
# To avoid writing boilerplate use a dataclass that is included
# in pythons standard library.
@dataclass
class Model():
counter: int = 0
def update(input: str, m: Model):
if input == "i":
m.counter += 1
elif input == "d":
m.counter -= 1
def view(m: Model) -> Layout:
layout = text(str(m.counter)) | center | border
return layout
m = Model()
# Event loop.
# this example uses the build in input functions instead of io.raw
while True:
# first clear terminal and draw
os.system('cls' if os.name == 'nt' else 'clear')
print(layout_to_str(layout=view(m), dimensions=Rect(11, 3)))
# then get input
i = input()
if i == "exit":
break # exit program
# then update
update(i, m)
Multiple interactibles with ELM¶
from functui.classes import *
from functui.common import *
from functui.nav import ROOT_HORIZONTAL, ROOT_VERTICAL, InteractibleID, NavState, DEFAULT_NAV_BINDINGS, interaction_area
from functui.io.raw import terminal
from dataclasses import dataclass
@dataclass
class Model():
nav: NavState
button_1: InteractibleID
button_2: InteractibleID
def update(input: InputEvent, res: Result, m: Model):
action = None
if input.key_event in DEFAULT_NAV_BINDINGS:
action = DEFAULT_NAV_BINDINGS[input.key_event]
m.nav = m.nav.update(
res=res,
action=action,
nav_tree=[m.button_1, m.button_2],
mouse_position=input.mouse_position_event
)
# If you want ui nodes to be reusable, put them into a function.
def selectable_element(nav: NavState, id: InteractibleID, string: str):
return text(string) | styled(
border,
(rule_fg(Color4.BLUE) if m.nav.is_hover(id) else StyleRule()) | (rule_bg(Color4.BLUE) if m.nav.is_active(id) else StyleRule())
) | interaction_area(id)
def view(m: Model):
layout = vbox([
selectable_element(m.nav, m.button_1, "Hello"),
selectable_element(m.nav, m.button_2, "World"),
]) | border
return layout
root = ROOT_VERTICAL
# for interactible ids that never change it is possible to define them when model is first created.
m = Model(
nav = NavState(),
button_1 = root.child(0),
button_2 = root.child(1),
)
with terminal() as term:
while True:
# render
res = layout_to_result(view(m), term.get_terminal_size())
term.display_result(res)
# wait for input
event = term.block_until_input()
# update
if event.key_event == "ctrl+c":
break
update(event, res, m)
Print Input Event with ELM and curses¶
from functui.classes import *
from functui.common import *
from functui.flex import hbox_flex, flex
from functui.nav import ROOT_HORIZONTAL, ROOT_VERTICAL, InteractibleID, NavState, DEFAULT_NAV_BINDINGS, interaction_area
from functui.io.raw import terminal
from dataclasses import dataclass, field
@dataclass
class Model():
nav: NavState
keycodes: list[str] = field(default_factory=list)
mouse_positions: list[Coordinate] = field(default_factory=list)
def update(input: InputEvent, res: Result, m: Model):
action = None
if input.key_event in DEFAULT_NAV_BINDINGS:
action = DEFAULT_NAV_BINDINGS[input.key_event]
m.nav = m.nav.update(
res=res,
action=action,
nav_tree=[],
mouse_position=input.mouse_position_event
)
if input.key_event is not None:
m.keycodes.append(input.key_event)
if input.mouse_position_event is not None:
m.mouse_positions.append(input.mouse_position_event)
if len(m.keycodes) > 50:
del m.keycodes[0]
if len(m.mouse_positions) > 50:
del m.mouse_positions[0]
def view(m: Model):
layout = hbox_flex([
vbox(
[text(f"<{i}>") | padding for i in m.keycodes],
reverse=True
) | border_with_title(text("[key event]") | center) | flex,
vbox(
[text(f"<{repr(i)}>") | padding for i in m.mouse_positions],
reverse=True
) | border_with_title(text("[mouse position event]") | center)| flex,
]) | padding
return layout
m = Model(
nav = NavState(),
)
with terminal() as term:
while True:
# render
res = layout_to_result(view(m), term.get_terminal_size())
term.display_result(res)
# wait for input
event = term.block_until_input()
# update
if event.key_event == "ctrl+c":
break
update(event, res, m)
Interactive examples with elm and curses¶
Template for ELM projects using curses IO¶
from functui.classes import *
from functui.common import *
from functui.nav import ROOT_HORIZONTAL, ROOT_VERTICAL, InteractibleID, NavState, default_nav_bindings, interaction_area
from functui.io.curses import get_input_event, draw_result, wrapper
import curses
from dataclasses import dataclass
@dataclass
class Model():
nav: NavState
# Add more attributes to contain all of your persistent state.
def update(input: InputEvent, res: Result, m: Model):
# update NavState for keyboard and mouse interactivity.
action = None
if input.key_event in default_nav_bindings:
action = default_nav_bindings[input.key_event]
m.nav = m.nav.update(
res=res,
action=action,
nav_tree=[],
mouse_position=input.mouse_position_event
)
# Put your update code here.
# Create your keyboard navigation tree here.
def view(m: Model):
# Layout rendering code here.
layout = vbox([
text("Hello World!") | border,
text("Press ctrl+c to exit.") | fg(Color4.CYAN) | border,
])
return layout
m = Model(
nav = NavState(),
)
def main(stdscr: curses.window):
while True:
y, x = stdscr.getmaxyx()
res = layout_to_result(view(m), Rect(x, y))
draw_result(res, stdscr)
key: InputEvent = get_input_event(stdscr)
if key.key_event == 'ctrl+c':
break # exit program
update(key, res, m)
if __name__ == "__main__":
wrapper(main)
Print Input Event with ELM and curses¶
from functui.classes import *
from functui.common import *
from functui.flex import hbox_flex, flex
from functui.nav import ROOT_HORIZONTAL, ROOT_VERTICAL, InteractibleID, NavState, DEFAULT_NAV_BINDINGS, interaction_area
from functui.io.curses import get_input_event, draw_result, wrapper
import curses
from dataclasses import dataclass, field
@dataclass
class Model():
nav: NavState
keycodes: list[str] = field(default_factory=list)
mouse_positions: list[Coordinate] = field(default_factory=list)
def update(input: InputEvent, res: Result, m: Model):
action = None
if input.key_event in DEFAULT_NAV_BINDINGS:
action = DEFAULT_NAV_BINDINGS[input.key_event]
m.nav = m.nav.update(
res=res,
action=action,
nav_tree=[],
mouse_position=input.mouse_position_event
)
if input.key_event is not None:
m.keycodes.append(input.key_event)
if input.mouse_position_event is not None:
m.mouse_positions.append(input.mouse_position_event)
if len(m.keycodes) > 50:
del m.keycodes[0]
if len(m.mouse_positions) > 50:
del m.mouse_positions[0]
def view(m: Model):
layout = hbox_flex([
vbox(
[text(f"<{i}>") | padding for i in m.keycodes],
reverse=True
) | border_with_title(text("[key event]") | center) | flex,
vbox(
[text(f"<{repr(i)}>") | padding for i in m.mouse_positions],
reverse=True
) | border_with_title(text("[mouse position event]") | center)| flex,
]) | padding
return layout
m = Model(
nav = NavState(),
)
def main(stdscr: curses.window):
while True:
y, x = stdscr.getmaxyx()
res = layout_to_result(view(m), Rect(x, y))
draw_result(res, stdscr)
key: InputEvent = get_input_event(stdscr)
if key.key_event == 'ctrl+c':
break # exit program
update(key, res, m)
if __name__ == "__main__":
wrapper(main)