Repository navigation
Beans Component Catalog
espresso.beans provides modular, reusable components following The Elm Architecture.
The Spinner component renders animated loading indicators driven by tick commands.
from espresso.beans import Spinner, DOTS, COFFEE
spinner = Spinner(frames=COFFEE, fps=4.0)
# In Parent Model:
def init(self):
return self.spinner.init()
def update(self, msg):
self.spinner, cmd = self.spinner.update(msg)
return self, cmd
def view(self):
return f"Loading {self.spinner.view()}..."-
DOTS:⠋,⠙,⠹,⠸,⠼,⠴,⠦,⠧,⠇,⠏ -
LINE:|,/,-,\ -
PULSE:█,▓,▒,░ -
POINTS:∙∙∙,●∙∙,∙●∙,∙∙● -
COFFEE:☕,♨️,✨ -
GLOBE:🌍,🌎,🌏 -
MOON:🌑,🌒,🌓,🌔,🌕
The TextInput component provides interactive single-line text entry with cursor blinking and navigation.
from espresso.beans import TextInput, EchoMode
input_field = TextInput(
placeholder="Enter your name...",
prompt="Name: ",
echo_mode=EchoMode.NORMAL # Or EchoMode.PASSWORD
)
def update(self, msg):
self.input_field, cmd = self.input_field.update(msg)
return self, cmd- Arrow navigation (Left, Right, Home, End)
- Backspace and Delete editing
- Masking with
EchoMode.PASSWORD - Character length limits (
char_limit) -
focus()andblur()state toggles
The Progress component provides a customizable progress bar.
from espresso.beans import Progress
from espresso.crema import Style
bar = Progress(
width=40,
percent=0.75,
fill_style=Style().foreground("#00E676")
)
def view(self):
return bar.view()The Viewport component creates a scrollable rectangular viewing pane for long text or logs.
from espresso.beans import Viewport
vp = Viewport(width=60, height=10)
vp.set_content("Long text content with many lines...")
def update(self, msg):
self.vp, cmd = self.vp.update(msg)
return self, cmd-
Up/k: Scroll up 1 line -
Down/j: Scroll down 1 line -
PageUp/ctrl+u: Scroll up 1 full page -
PageDown/ctrl+d: Scroll down 1 full page -
Home/g: Jump to top -
End/G: Jump to bottom - Mouse Wheel Up / Down
The Table component displays tabular data with column formatting and keyboard row navigation.
from espresso.beans import Table, Column
from espresso.crema import Align
columns = [
Column("ID", width=6),
Column("Name", width=20),
Column("Role", width=15),
Column("Salary", width=10, align=Align.RIGHT)
]
rows = [
["1", "Alice Jensen", "Engineer", "$120,000"],
["2", "Bob Smith", "Designer", "$110,000"],
["3", "Charlie Brown", "Manager", "$135,000"]
]
table = Table(columns, rows, height=5)
def update(self, msg):
self.table, cmd = self.table.update(msg)
return self, cmdThe TextArea component provides an interactive multi-line text editor with customizable line numbers, viewport scrolling, tab indentation, and cursor navigation.
from espresso.beans import TextArea
from espresso.crema import Style
editor = TextArea(
placeholder="Write your notes here...",
width=60,
height=12,
show_line_numbers=True,
tab_size=4,
line_number_style=Style().foreground("#555555"),
cursor_line_number_style=Style().bold(True).foreground("#7D56F4")
)
def update(self, msg):
self.editor, cmd = self.editor.update(msg)
return self, cmd
def view(self):
return self.editor.view()-
Line Numbers: Optional gutter column with active cursor line highlighting (
show_line_numbers=True,toggle_line_numbers()). -
Cursor Navigation: Arrow keys (
Up,Down,Left,Right),Home/End,PageUp/PageDown. -
Text Editing: Character insertion,
Backspace(merging lines),Delete, andEnter. -
Indentation: Configurable
tab_sizeinserting soft spaces onTab. -
Limits: Optional
char_limitandmax_linesconstraints. - Scroll Synchronization: Automatic vertical and horizontal viewport tracking.
The Help component displays keyboard shortcut documentation. It dynamically toggles between a compact single-line view and a multi-column full reference.
from espresso.beans import Help, KeyBinding
# Define key bindings
bindings = [
KeyBinding("enter", "submit order"),
KeyBinding("tab", "next field"),
KeyBinding("ctrl+c", "quit app"),
KeyBinding("?", "toggle full help", help_key="?")
]
help_view = Help(bindings, width=60, show_all=False)
def update(self, msg):
match msg:
case KeyMsg(key="?"):
self.help_view.toggle()
return self, None
def view(self):
return self.help_view.view()-
Compact View (
show_all=False): Displays a single horizontal line of hotkeys separated byshort_separator(•), automatically truncating items that exceedwidth. -
Full View (
show_all=True): Displays multi-column side-by-side grouped keybindings with aligned descriptions. -
KeyMapProtocol Support: Pass custom container objects implementingshort_help()andfull_help().
The Timer component provides a high-precision countdown timer driven by Tea tick commands.
from espresso.beans import Timer, TimerTimeoutMsg
timer = Timer(timeout=60.0, interval=1.0, auto_start=True, tag="session_timer")
def init(self):
return self.timer.init()
def update(self, msg):
match msg:
case TimerTimeoutMsg(tag="session_timer"):
print("Session expired!")
return self, None
self.timer, cmd = self.timer.update(msg)
return self, cmd
def view(self):
return f"Time Remaining: {self.timer.view()} ({int(self.timer.percent * 100)}%)"-
start()/stop()/toggle(): Controls countdown state. -
reset(timeout=None, start=False): Restores original or specifies a new duration. -
remaining/elapsed: Current time remaining and elapsed in seconds. -
percent: Completion progress from0.0(just started) to1.0(timed out). -
format_fn: Optional custom formatter callback(float) -> str.
The Stopwatch component measures elapsed time with sub-second accuracy.
from espresso.beans import Stopwatch
sw = Stopwatch(interval=0.1, auto_start=True)
def init(self):
return self.sw.init()
def update(self, msg):
self.sw, cmd = self.sw.update(msg)
return self, cmd
def view(self):
return f"Elapsed: {self.sw.view()}" # e.g., "01:23.45"-
start(): Resumes tracking elapsed time. -
stop(): Freezes the stopwatch. -
toggle(): Flips between running and stopped states. -
reset(start=False): Clears elapsed time back to0.0.
The Paginator component calculates page offsets and renders pagination dots, page counts, or item range summaries.
from espresso.beans import Paginator, PaginatorType
items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6"]
paginator = Paginator(per_page=2, total_items=len(items), paginator_type=PaginatorType.DOTS)
def update(self, msg):
self.paginator, cmd = self.paginator.update(msg)
return self, cmd
def view(self):
# Slice the current page items
start, end = self.paginator.slice_bounds
page_items = items[start:end]
content = "\n".join(f"• {item}" for item in page_items)
return f"{content}\n\n{self.paginator.view()}"-
PaginatorType.DOTS: Bullet indicators (• • ◦ ◦) -
PaginatorType.NUMERIC: Page ratio (1/5) -
PaginatorType.COMPACT: Descriptive range (Page 1 of 5 (1-10 of 42))
-
Left/h/PageUp: Previous page (prev_page()) -
Right/l/PageDown: Next page (next_page()) -
slice_bounds: Returns(start, end)tuple for slicing your data -
slice_items(items): Directly slices a list or sequence -
set_total(count): Updates the total item count and clamps current page
The Dialog component renders a centered floating confirmation or decision modal card with action buttons.
from espresso.beans import Dialog, DialogResultMsg
from espresso.crema import place_overlay
dialog = Dialog(
title="Confirm Action",
message="Are you sure you want to delete this file?",
buttons=("Delete", "Cancel"),
width=44
)
def update(self, msg):
match msg:
case DialogResultMsg(action=action, button_index=idx):
if action == "Delete":
delete_target_file()
self.show_dialog = False
return self, None
self.dialog, cmd = self.dialog.update(msg)
return self, cmd
def view(self):
base_view = render_main_ui()
if self.show_dialog:
# Composite modal on top with dimmed background
return place_overlay(base_view, self.dialog.view(), center=True, dim_backdrop=True)
return base_view-
Tab/Right/l: Move focus to next button -
Shift+Tab/Left/h: Move focus to previous button -
Enter/Space: Confirm focused button and emitDialogResultMsg(action, button_index) -
Esc: Auto-selects "Cancel" if present, emittingDialogResultMsg
The List component is an interactive, searchable, and paginated or continuously scrollable list with real-time substring filtering, inspired by charmbracelet/bubbles/list and treilik/bubblelister.
-
Pagination Modes: Discrete pages with
Paginator(PaginationMode.PAGINATED) or smooth continuous line-by-line scrolling (PaginationMode.SCROLL) with dynamic percentage position indicators. -
Badges & Suffixes: First-class right-aligned badges on
ListItem(badge="PROD",badge_style=...) or dynamic suffixes viasuffix_fn. -
Line Numbering: Absolute 1-based indexing (
show_numbers=True) and Vim-style relative distance numbering (relative_numbers=True). -
Tree Guides: Connected continuation guides for multi-line items (
show_tree_guides=Trueusing╭,├,│,╰). -
Custom Renderers: Completely customize item rows with
item_rendereror customize cursor markers withprefix_fn. -
Scrollbar: Visual vertical scrollbar track (
│) and thumb (█) viashow_scrollbar=True. -
Native Mouse Support: Wheel scrolling (
WHEEL_UP/WHEEL_DOWN) and left-click selection emittingListSelectMsg.
from espresso.beans import List, ListItem, ListSelectMsg, PaginationMode
from espresso.crema import Style
items = [
ListItem(title="deploy-prod", description="Roll out Kubernetes cluster", badge="PROD", badge_style=Style().foreground("#00E676").bold(True)),
ListItem(title="db-migrate", description="Execute pending schema migrations", badge="DB", badge_style=Style().foreground("#29B6F6")),
ListItem(title="run-tests", description="Execute comprehensive test suite", badge="CI", badge_style=Style().foreground("#AB47BC")),
]
list_view = List(
items=items,
title="Operations",
per_page=5,
width=50,
pagination_mode=PaginationMode.SCROLL,
show_numbers=True,
relative_numbers=False,
show_tree_guides=True,
show_scrollbar=True,
)
def update(self, msg):
match msg:
case ListSelectMsg(item=item, index=idx):
print(f"Executed: {item.title} (index {idx})")
return self, None
self.list_view, cmd = self.list_view.update(msg)
return self, cmd
def view(self):
return self.list_view.view()-
↑/k: Move cursor up -
↓/j: Move cursor down -
PageUp/PageDown: Jump by page / viewport height -
Home/g: Jump to first item -
End/G: Jump to last item -
/: Open inline search filter field -
Esc: Clear search filter and close filter mode -
Enter: Select highlighted item, emittingListSelectMsg(item, index) -
Mouse Wheel: Scroll list up and down -
Mouse Click: Select and highlight the clicked item row
The FilePicker component provides an interactive terminal directory browser with file size formatting and hidden file filtering.
from espresso.beans import FilePicker, FileSelectMsg, format_file_size
picker = FilePicker(
directory=".",
allowed_extensions=[".py", ".md", ".json"],
show_hidden=False,
file_allowed=True,
dir_allowed=False,
height=10,
width=50
)
def update(self, msg):
match msg:
case FileSelectMsg(path=path, is_dir=is_dir):
print(f"Chosen file: {path}")
return self, None
self.picker, cmd = self.picker.update(msg)
return self, cmd
def view(self):
return self.picker.view()-
↑/k,↓/j: Navigate items -
Enter/→/l: Enter folder or select file (emitsFileSelectMsg) -
Backspace/←/h: Navigate up to parent directory -
.: Toggle hidden file visibility -
format_file_size(size_bytes): Utility function formatting bytes intoB,KB,MB,GB,TB
espresso.beans provides three interactive prompt components for terminal forms and CLI wizards.
from espresso.beans import SelectPrompt, SelectSubmitMsg
prompt = SelectPrompt(
question="Choose your deployment environment:",
options=["Development", "Staging", "Production"],
default_index=0
)
def update(self, msg):
match msg:
case SelectSubmitMsg(selected=choice, index=idx):
print(f"Deploying to: {choice}")
return self, None
self.prompt, cmd = self.prompt.update(msg)
return self, cmdfrom espresso.beans import MultiSelectPrompt, MultiSelectSubmitMsg
multi = MultiSelectPrompt(
question="Select components to install:",
options=["Auth", "Database", "Redis Cache", "Telemetry"],
default_selected=[0, 1]
)
def update(self, msg):
match msg:
case MultiSelectSubmitMsg(selected=items, indices=idxs):
print(f"Selected: {items}")
return self, None
self.multi, cmd = self.multi.update(msg)
return self, cmd-
Space: Toggle checkbox[x] -
a: Toggle select all
from espresso.beans import ConfirmPrompt, ConfirmSubmitMsg
confirm = ConfirmPrompt(question="Proceed with migration?", default=True)
def update(self, msg):
match msg:
case ConfirmSubmitMsg(confirmed=ok):
if ok:
run_migration()
return self, None
self.confirm, cmd = self.confirm.update(msg)
return self, cmd-
y/n: Instant choose Yes or No -
Tab/Left/Right: Toggle active choice,Enterto submit
The ToastManager component manages transient, auto-dismissing notification banners.
from espresso.beans import ToastManager, ToastLevel, ToastDismissMsg
toasts = ToastManager()
def trigger_alert(self):
# add() returns (ToastItem, Cmd) where Cmd is an async auto-dismiss timer
item, cmd = self.toasts.add("Build succeeded!", level=ToastLevel.SUCCESS, duration=3.0)
return cmd
def update(self, msg):
# Handles ToastDismissMsg automatically when timer expires
self.toasts, cmd = self.toasts.update(msg)
return self, cmd
def view(self):
# Render main content with toasts positioned in top corner or overlay
return f"{main_content}\n{self.toasts.view()}"-
ToastLevel.INFO: Cyan accent (ℹ) -
ToastLevel.SUCCESS: Green accent (✔) -
ToastLevel.WARNING: Gold accent (⚠) -
ToastLevel.ERROR: Red accent (✖)
The Tabs component renders a top horizontal tab bar with keyboard navigation and number shortcuts.
from espresso.beans import Tabs, TabStyle, TabChangeMsg
tabs = Tabs(
titles=["Overview", "Logs", "Metrics", "Settings"],
active_tab=0,
tab_style=TabStyle.PILL,
show_numbers=True
)
def update(self, msg):
match msg:
case TabChangeMsg(index=idx, title=title):
self.current_screen = idx
return self, None
self.tabs, cmd = self.tabs.update(msg)
return self, cmd
def view(self):
return f"{self.tabs.view()}\n\n{render_tab_content(self.current_screen)}"-
TabStyle.PILL: High-contrast filled background pill (1 Overview) -
TabStyle.LINE: Underline accent (1 Overview) -
TabStyle.BRACKET: Bracketed indicators ([ 1 Overview ])
-
Tab/Shift+Tab: Next / previous tab -
1through9: Direct hotkey jump to corresponding tab index -
Left/Right/h/l: Step between adjacent tabs
The Tree component displays hierarchical folder or object trees with Unicode branches and expand/collapse support.
from espresso.beans import Tree, TreeNode, TreeNodeSelectMsg
nodes = [
TreeNode("src", children=[
TreeNode("espresso", children=[
TreeNode("core"),
TreeNode("crema"),
TreeNode("beans"),
], expanded=True),
TreeNode("main.py"),
], expanded=True),
TreeNode("README.md"),
]
tree = Tree(nodes=nodes)
def update(self, msg):
match msg:
case TreeNodeSelectMsg(node=node):
print(f"Selected: {node.label}")
return self, None
self.tree, cmd = self.tree.update(msg)
return self, cmd
def view(self):
return self.tree.view()-
↑/k,↓/j: Navigate visible tree rows -
Right/l/Space: Expand collapsed branch or toggle node -
Left/h: Collapse branch, or jump to parent node -
Enter: Select node and emitTreeNodeSelectMsg(node)
The StatusBar component renders responsive, multi-section status bars with priority-based auto-truncation for narrow terminal viewports.
from espresso.beans import StatusBar, StatusSection
from espresso.crema import Style
status_bar = StatusBar(
left=[
StatusSection("NORMAL", style=Style().bold(True).background("#7D56F4").padding(0, 1), priority=3),
StatusSection("main.py", priority=2),
],
center=[
StatusSection("UTF-8", priority=0),
],
right=[
StatusSection("42:15", priority=2),
StatusSection("98% Ready", icon="⚡", style=Style().foreground("#00E676"), priority=3),
],
width=80,
background="#1A1A24"
)
def view(self):
return status_bar.view()- Comfortable Width: Center cluster is perfectly centered between Left and Right clusters.
-
Medium Width: Center cluster is smoothly truncated with ellipsis (
…) while preserving Left and Right. -
Narrow Width: Sections with lowest
priorityare dynamically dropped, guaranteeing high-priority badges remain visible without line wrapping.
The Metric and MetricGroup components render dashboard KPI cards, tags, and summary lists with trend deltas and directional styling.
from espresso.beans import Metric, MetricGroup, MetricLayout, MetricTrend, LayoutDirection
metrics = MetricGroup(
metrics=[
Metric(label="Revenue", value="$42,500", delta="+12.4%", trend=MetricTrend.UP),
Metric(label="Latency", value="45", unit="ms", delta="-8ms", trend=MetricTrend.DOWN, invert_trend=True),
Metric(label="Error Rate", value="0.02%", delta="0%", trend=MetricTrend.NEUTRAL),
],
layout=MetricLayout.CARD,
direction=LayoutDirection.HORIZONTAL
)
def view(self):
return metrics.view()-
MetricLayout.CARD: Bordered stat card with large value, unit, delta, and arrow indicator -
MetricLayout.TAG: Compact inline pill badge ([ Label: Value (delta) ]) -
MetricLayout.LIST: Dotted key-value row (Revenue ......... $42,500 ▲ +12.4%)
-
invert_trend=True: Inverts color logic for metrics where lower is better (e.g. Latency, Error Rate: DOWN is green, UP is red) - Horizontal and vertical stacking via
LayoutDirection
The NavStack component provides view stack routing with automatic breadcrumb navigation (Home › Category › Detail) and history management.
from espresso.beans import NavStack, NavPushMsg, NavPopMsg
nav = NavStack(initial_title="Dashboard", initial_model=DashboardModel())
# Push a sub-view
def open_user_details(self, user_id):
return self.nav.push("User Details", UserDetailModel(user_id))
# Pop back to previous view
def go_back(self):
model, cmd = self.nav.pop()
return cmd
def update(self, msg):
# NavStack automatically delegates messages to the active top model
self.nav, cmd = self.nav.update(msg)
return self, cmd
def view(self):
# Renders breadcrumbs on top followed by the active model view
return self.nav.view()- Automatic message forwarding to the active view on top of the stack
-
auto_pop_on_back=True: Automatically pops the top view onEscorBackspace - Breadcrumbs trail rendering (
breadcrumbs_view()) - Lifecycle notifications (
NavPushMsg,NavPopMsg)
The DatePicker component provides an interactive monthly calendar widget for selecting dates, inspired by EthanEFung/bubble-datepicker. It features keyboard and mouse navigation, month and year focus cycling, day-of-week custom start day, and min/max date boundary clamping.
from datetime import date
from espresso.beans import DatePicker, DateSelectMsg, DateChangeMsg
from espresso.crema import ROUNDED_BORDER
picker = DatePicker(
value=date.today(),
cursor_date=date.today(),
min_date=date(2025, 1, 1),
max_date=date(2027, 12, 31),
first_day_of_week=6, # 6 = Sunday (default), 0 = Monday
show_header=True,
show_help=True,
border=ROUNDED_BORDER,
border_foreground="#7D56F4",
)
def update(self, msg):
match msg:
case DateSelectMsg(date=selected_date):
print(f"Date selected: {selected_date}")
return self, None
case DateChangeMsg(date=active_date):
print(f"Cursor moved: {active_date}")
return self, None
self.picker, cmd = self.picker.update(msg)
return self, cmd
def view(self):
return self.picker.view()-
Calendar Mode:
-
←/h,→/l: Move cursor ±1 day -
↑/k,↓/j: Move cursor ±7 days (previous / next week) -
[/PageUp: Move to previous month -
]/PageDown: Move to next month -
{/}: Move to previous / next year (handles leap days automatically) -
t: Jump cursor to today's date -
Enter/Space: Confirm date selection (emitsDateSelectMsg) -
Tab/Shift+Tab: Cycle focus betweenCALENDAR⇄MONTH⇄YEAR
-
-
Month Mode:
-
←/h/↑/k: Previous month -
→/l/↓/j: Next month -
Enter/Space/Esc: Return focus to calendar grid
-
-
Year Mode:
-
←/h/↓/j: Previous year -
→/l/↑/k: Next year -
Enter/Space/Esc: Return focus to calendar grid
-
-
Header Arrows: Click
◀or▶to advance or retreat months. -
Header Text: Click on the month or year name to switch focus directly to
MONTHorYEAR. -
Date Cells: Click on any date cell to jump to that date, select it, and emit
DateSelectMsg. - Mouse Wheel: Wheel up / down scrolls months backward / forward.
-
selected_date/value: The currently confirmeddate(orNone). - cursor_date: The highlighted
datecursor. -
select_date(d=None): Selects specified or current cursor date. -
set_date(d): Sets both cursor and selected date. -
set_focus(focus): Switch focus betweenCALENDAR,MONTH,YEAR,NONE. -
is_today(d),is_selected(d),is_disabled(d): Date status helpers.
The PipelineProgress component manages multi-stage asynchronous task execution pipelines, inspired by mritd/bubbles/progressbar. It displays a live visual progress bar, per-stage status badges (PENDING, RUNNING, SUCCESS, FAILED, SKIPPED), elapsed execution times, and failure diagnostics.
from espresso.beans import PipelineProgress, PipelineStage, StageStatus
stages = [
PipelineStage(title="Fetch Source & Deps", status=StageStatus.SUCCESS, duration=0.82),
PipelineStage(title="Lint & Static Analysis", status=StageStatus.SUCCESS, duration=1.14),
PipelineStage(title="Run Unit Tests", status=StageStatus.RUNNING, duration=0.14),
PipelineStage(title="Build Optimized Wheel", status=StageStatus.PENDING),
PipelineStage(title="Publish to Registry", status=StageStatus.PENDING),
]
pipeline = PipelineProgress(
stages=stages,
title="Release Pipeline",
width=50,
show_stages=True,
show_timer=True,
)
def update(self, msg):
self.pipeline, cmd = self.pipeline.update(msg)
return self, cmd
def view(self):
return self.pipeline.view()-
Stages: List of
PipelineStageobjects with customactioncallables or manual state tracking. -
Messages: Emits
StageStartMsg,StageCompleteMsg,StageFailedMsg, andPipelineCompleteMsg. -
Keyboard / API:
-
start(): Begin automated async pipeline execution. -
reset(): Reset all stages to initial pending state. -
advance(): Advance the active stage to completion and start the next.
-
The MarkdownViewer component provides a pure Python terminal Markdown document viewer, inspired by mistakenelf/teacup/markdown. It parses and renders headings (H1-H6), bold, italic, code spans, fenced code blocks, blockquotes with ▌ bars, nested bullet and numbered lists, and horizontal rules, wrapped in a smooth scrollable Viewport with scroll percentage footer.
from espresso.beans import MarkdownViewer
doc = """# Release Notes
Welcome to **Espresso 0.2.0**!
- Added `MarkdownViewer`
- Added `CodeViewer`
> Simple, declarative, pure Python.
"""
md_viewer = MarkdownViewer(
markdown=doc,
width=70,
height=20,
show_footer=True,
filename="CHANGELOG.md",
)
def update(self, msg):
self.md_viewer, cmd = self.md_viewer.update(msg)
return self, cmd
def view(self):
return self.md_viewer.view()-
↑/k,↓/j: Scroll line by line. -
PageUp/PageDown: Scroll page by page. -
Home/g,End/G: Jump to document top / bottom. - Mouse Wheel: Wheel up / down scrolls the document viewport.
The CodeViewer component provides a syntax-highlighted source code viewer with line numbers, active cursor line highlight (▶), and smooth viewport scrolling, inspired by mistakenelf/teacup/code. It uses Python's standard library tokenize module for Python syntax and regex tokenizers for JavaScript/TypeScript, Go, Rust, JSON, YAML, SQL, Shell, and Markdown.
from espresso.beans import CodeViewer, THEME_ESPRESSO, THEME_DRACULA
viewer = CodeViewer(
code='def brew(shots=2):\n return f"{shots} shots"',
language="python",
width=60,
height=15,
theme=THEME_ESPRESSO,
show_line_numbers=True,
cursor_line=1,
)
def update(self, msg):
self.viewer, cmd = self.viewer.update(msg)
return self, cmd
def view(self):
return self.viewer.view()-
THEME_ESPRESSO: Hazelnut purple keywords, cyan builtins, vibrant green strings. -
THEME_DRACULA: Dracula palette with pink keywords, yellow strings, and purple numbers. -
THEME_MONOKAI: High-contrast green functions, orange numbers, and cyan operators.
-
↑/k,↓/j: Move cursor line and scroll viewport smoothly. -
PageUp/PageDown: Move cursor and scroll by viewport height. -
Home/g,End/G: Jump to top or bottom line. - Mouse Wheel: Wheel up / down scrolls code viewport.
The QuickFix component is a Neovim-style diagnostic bottom drawer for viewing compiler errors, warnings, and linter messages, inspired by Genekkion/theHermit. It displays items with severity badges (ERR, WARN, INFO, HINT), file paths, line/column numbers, and error codes. It can dock or overlay over any view using wrap_view.
from espresso.beans import QuickFix, QuickFixItem, QuickFixSelectMsg
items = [
QuickFixItem(file="src/brew.py", line=12, col=5, message="variable 'crema' unused", severity="warn", code="W0612"),
QuickFixItem(file="src/brew.py", line=25, col=1, message="syntax error: missing colon", severity="error", code="E0001"),
]
qf = QuickFix(items=items, height=6, toggle_key="ctrl+x")
def update(self, msg):
match msg:
case QuickFixSelectMsg(item=item, index=idx):
print(f"Jump to {item.file}:{item.line}")
return self, None
self.qf, cmd = self.qf.update(msg)
return self, cmd
def view(self):
main_ui = "Main editor content..."
return self.qf.wrap_view(main_ui, width=80, height=24)-
Ctrl+X(configurable): Toggle drawer open / closed. -
↑/k,↓/j: Navigate diagnostic items. -
Enter: Select diagnostic issue and emitQuickFixSelectMsg. -
Esc/q: Close drawer. - Mouse Wheel: Wheel up / down scrolls diagnostic list.
The DetailSelector component combines a single-choice selection list on top with a live synchronized preview card below, inspired by mritd/bubbles/selector. When items are navigated, the card below updates dynamically with item details and metadata.
from espresso.beans import DetailSelector, DetailItem, DetailSelectMsg
items = [
DetailItem(
title="Espresso Runtime",
tag="CORE",
details="Declarative The Elm Architecture event loop.",
metadata={"Version": "0.1.0"},
),
DetailItem(
title="Crema Engine",
tag="STABLE",
details="ANSI truecolor styling, 2D layer compositor.",
metadata={"Engine": "Crema"},
),
]
selector = DetailSelector(items=items, prompt="Select Layer:", width=50, per_page=4)
def update(self, msg):
match msg:
case DetailSelectMsg(item=item, index=idx):
print(f"Selected layer: {item.title}")
return self, None
self.selector, cmd = self.selector.update(msg)
return self, cmd
def view(self):
return self.selector.view()-
↑/k,↓/j: Move selection cursor and update preview card. -
PageUp/PageDown: Move page backward / forward. -
Enter/Space: Select highlighted item and emitDetailSelectMsg. - Mouse Wheel: Wheel up / down scrolls through items.
The ImageViewer component renders images and graphics in the terminal using 24-bit ANSI upper-half blocks (▀) and 10-step grayscale ASCII characters, inspired by mistakenelf/teacup/image. It provides pure Python standard library support for Netpbm PPM (.ppm), uncompressed 24-bit BMP (.bmp), and raw RGB pixel matrices, plus an optional Pillow bridge for PNG/JPEG when installed.
from espresso.beans import ImageViewer, RenderMode
# 1. From raw RGB matrix:
pixels = [
[(255, 0, 0), (0, 255, 0)],
[(0, 0, 255), (255, 255, 0)],
]
viewer = ImageViewer(pixels=pixels, width=40, height=20, mode=RenderMode.HALF_BLOCK)
# 2. From file (PPM or BMP natively):
# viewer = ImageViewer.from_file("assets/logo.ppm", width=60, height=30)
def update(self, msg):
self.viewer, cmd = self.viewer.update(msg)
return self, cmd
def view(self):
return self.viewer.view()-
24-bit ANSI Truecolor Half-Blocks: Combines two vertical RGB pixels into a single
▀character using foreground and background ANSI truecolor codes (\x1b[38;2;R;G;Bm+\x1b[48;2;R;G;Bm). -
ASCII Grayscale Mode: 10-level luminance mapping ramp (
.:-=+*#%@) for monochrome or non-truecolor terminals. - Native Formats: Supports Netpbm P3 (ASCII) and P6 (binary) PPM files, plus uncompressed 24-bit Windows BMP files with zero third-party dependencies.
-
Optional Pillow Bridge: Automatically loads PNG, JPEG, GIF, and WebP if
PILis installed in the environment. - Viewport Navigation: Arrow keys and mouse wheel scroll large images seamlessly.
The Splitter component provides an interactive, draggable two-pane container (Left | Right or Top / Bottom) separated by a customizable divider bar. Users can resize panes directly with the mouse or via keyboard shortcuts.
from espresso.beans import Splitter, SplitterOrientation, SplitterResizeMsg
splitter = Splitter(
pane1=tree_view,
pane2=code_viewer,
orientation=SplitterOrientation.HORIZONTAL,
width=80,
height=24,
ratio=0.4,
min_pane1=15,
min_pane2=20,
)
def update(self, msg):
match msg:
case SplitterResizeMsg(ratio=r, pane1_size=p1, pane2_size=p2):
print(f"Resized: {p1} | {p2} (ratio: {r:.2f})")
return self, None
self.splitter, cmd = self.splitter.update(msg)
return self, cmd
def view(self):
return self.splitter.view()-
Mouse Drag: Click and drag the divider bar (
│or─) smoothly with the mouse. -
Screen Offsets: Configure
set_offset(x, y)oroffset_x, offset_ywhen placing the splitter inside borders, headers, or margins to ensure mouse hits map accurately to the divider. - Child Mouse Routing: Automatically translates and routes child mouse events into each pane's local coordinates.
-
Keyboard Arrows:
←/→(horizontal) or↑/↓(vertical) step by 1 cell. -
Coarse Step:
Ctrl+Arrowssteps by 5 cells. -
Reset:
=orrresets to an even 50/50 split. -
Child Sizing: Automatically propagates dimensions to child models with
set_size(w, h).
Tactile direct-manipulation numeric slider components supporting smooth mouse dragging, click-to-seek, and keyboard step adjustment.
from espresso.beans import Slider, RangeSlider, SliderChangeMsg, RangeSliderChangeMsg
# Single-thumb slider
vol_slider = Slider(
min_val=0,
max_val=100,
value=65,
step=1,
width=35,
label="Volume:",
value_format="{value:.0f}%",
)
# Dual-thumb range slider
eq_slider = RangeSlider(
min_val=20,
max_val=20000,
low=250,
high=8000,
step=10,
width=45,
label="Bandpass:",
value_format="{low:.0f}Hz - {high:.0f}Hz",
)
def update(self, msg):
match msg:
case SliderChangeMsg(value=val, percent=pct):
print(f"Volume adjusted: {val} ({pct*100:.1f}%)")
return self, None
case RangeSliderChangeMsg(low=l, high=h):
print(f"Range adjusted: {l} to {h}")
return self, None
self.vol_slider, c1 = self.vol_slider.update(msg)
self.eq_slider, c2 = self.eq_slider.update(msg)
return self, batch(c1, c2)- Mouse Click: Click anywhere on the track to seek to that value.
-
Mouse Drag: Click and drag thumb knob (
●) smoothly across the track. -
Row Isolation & Offsets: Sliders check row bounds (
local_y == 0), ensuring vertically stacked sliders never move together when one is dragged. Configureset_offset(x, y)to match layout coordinates. -
Mouse Wheel: Wheel up / down increments or decrements by
step. -
Keyboard:
←/→(orh/l),PageUp/PageDown(5× step),Home/End. -
RangeSlider Tab: Press
Tabto switch active thumb betweenlowandhigh.
The Sparkline component renders real-time streaming data visualizations using either high-resolution 2D Unicode Braille curves (4× vertical resolution) or 1D vertical block bars ( ▂▃▄▅▆▇█).
from espresso.beans import Sparkline, SparklineMode, SparklineTickMsg
# Braille 2D curve with Truecolor gradient
sparkline = Sparkline(
width=50,
height=3,
mode=SparklineMode.BRAILLE,
label="CPU Load:",
min_val=0,
max_val=100,
gradient_stops=["#00E5FF", "#7D56F4", "#FF007F"],
)
def update(self, msg):
if isinstance(msg, TelemetryMsg):
# Stream new data point
self.sparkline.push(msg.cpu_usage)
return self, None
return self, None
def view(self):
return self.sparkline.view()-
SparklineMode.BRAILLE: Uses Unicode Braille patterns (U+2800..U+28FF) mapping 2 horizontal dots by 4 vertical dots per cell, achieving ultra-smooth curves in tight terminal spaces. -
SparklineMode.BLOCK: 8-level vertical block character bars (▂▃▄▅▆▇█). -
Telemetry & Stats: Built-in current value badge, min/max/average properties, and directional trend indicators (
↗,↘,→). -
Vertical Gradients: Smoothly colors multi-row sparklines via Crema's
multi_gradient_colors.
The Marquee component provides a fixed-width, smoothly scrolling animated text banner or ticker for news feeds, track titles, and alert headers.
from espresso.beans import Marquee, MarqueeMode, MarqueeTickMsg
marquee = Marquee(
text="🚀 Espresso 2.0: High-performance TUI framework in pure Python • 28+ Beans components • SGR mouse dragging",
width=40,
speed=0.1,
mode=MarqueeMode.LOOP,
separator=" ★ ",
)
def init(self):
return self.marquee.init()
def update(self, msg):
self.marquee, cmd = self.marquee.update(msg)
return self, cmd
def view(self):
return self.marquee.view()-
MarqueeMode.LOOP: Continuous seamless looping with configurable separator. InLOOPmode, text loops infinitely even when it fits within window width (configurable vialoop_if_fits=True). -
MarqueeMode.BOUNCE: Scrolls from beginning to end, pauses forpause_frames, then smoothly reverses direction. Automatically stays static if the string fits withinwidth.
The SortableList component allows users to reorder items dynamically via mouse drag-and-drop or intuitive keyboard shortcuts.
from espresso.beans import SortableList, SortableItem, ItemReorderedMsg
items = [
SortableItem(id="1", title="Write tests"),
SortableItem(id="2", title="Implement feature"),
SortableItem(id="3", title="Deploy release"),
]
sortable = SortableList(items=items, width=40, height=8, offset_x=1, offset_y=6)
def update(self, msg):
match msg:
case ItemReorderedMsg(old_index=old, new_index=new, item=it):
print(f"Moved '{it}' from position {old} to {new}")
return self, None
self.sortable, cmd = self.sortable.update(msg)
return self, cmd
def view(self):
return self.sortable.view()-
Mouse Drag-and-Drop: Click on any row within list bounds, drag it up or down to the target position, and release to commit. Configure
set_offset(x, y)to match layout coordinates. A highlighted[HOLDING]badge and insertion marker (▼) indicate the drop target in real time. -
Keyboard Reordering:
-
Space/Enter: Grab highlighted item into holding mode. -
↑/↓(ork/j): Move the grabbed item up or down. -
Space/Enter: Drop item at current position. -
Esc: Cancel grab and return item to original position. -
Shift+Up/Shift+Down(orAlt+Up/Alt+DownorK/J): Instantly swap the highlighted item directly without entering holding mode.
-
The Spring component brings physics-driven motion and natural tactile feel to terminal interfaces. It solves the exact analytical differential equation for damped harmonic oscillators:
sleep() scheduling variances.
from espresso.beans import Spring, SpringValue, SpringTickMsg
spring = Spring(
value=0.0,
target=100.0,
min_val=0.0,
max_val=100.0,
stiffness=120.0, # Spring tension (k)
damping=0.5, # Damping ratio (zeta: < 1.0 underdamped/bouncy, = 1.0 critical, > 1.0 overdamped)
mass=1.0,
fps=60.0,
width=50,
label="Volume Level:",
)
def init(self):
return self.spring.init()
def update(self, msg):
if isinstance(msg, SpringTickMsg):
self.spring, cmd = self.spring.update(msg)
return self, cmd
# User changes destination:
if msg.key == "right":
cmd = self.spring.set_target(self.spring.target + 10.0)
return self, cmd
return self, None
def view(self):
return self.spring.view()-
Analytical Solver (
SpringValue): Implements closed-form solutions for underdamped ($\zeta < 1.0$ ), critically damped ($\zeta = 1.0$ ), and overdamped ($\zeta > 1.0$ ) states. Never explodes or drifts numerically. -
Overshoot & Oscillation Rendering: Track visuals render overshoot markers (
◀,▶) when momentum carries values outside nominal boundaries before settling back to equilibrium. - Dynamic Telemetry: Live position, destination target, velocity ($v(t)$), and settled status indicators.
-
Interactive Methods:
set_target(val),snap_to(val),is_settled,tick().
The Confetti component provides celebratory visual particle effects for rewards, milestones, form completions, and release banners. Particles animate in 2D terminal coordinates under the influence of gravity, air drag, and velocity.
from espresso.beans import Confetti, ConfettiMode, ConfettiTickMsg
confetti = Confetti(
width=80,
height=24,
gravity=18.0,
drag=0.85,
fps=30.0,
)
def update(self, msg):
if isinstance(msg, ConfettiTickMsg):
self.confetti, cmd = self.confetti.update(msg)
return self, cmd
if msg.key == "c":
# Launch celebratory explosion!
cmd = self.confetti.fire(count=60, mode=ConfettiMode.BURST)
return self, cmd
return self, None
def view(self):
card_text = "🎉 Build Succeeded! All 295 tests passed."
# Overlay particles seamlessly on top of card text without layout shift:
return self.confetti.overlay(card_text)-
ConfettiMode.BURST: Radial explosion expanding outward from an origin point(x, y)with slight upward vertical bias. -
ConfettiMode.CANNON: Dual angled cannons firing inward and upward from the bottom left and bottom right corners. -
ConfettiMode.RAIN: Gentle cascade drifting from the top of the terminal canvas with random lateral air flutter.
-
overlay(background_text): Uses Crema'splace_overlayto insert particle glyphs into the rendered background string line-by-line, allowing particles to rain over tables, cards, dialogs, or text without modifying or shifting underlying layouts.
The DiffViewer component renders unified and side-by-side split Git diffs with line-number gutters and intra-line word-level difference highlighting.
from espresso.beans import DiffViewer, DiffMode
diff_viewer = DiffViewer(
old_text=old_source_code,
new_text=new_source_code,
fromfile="a/src/server.py",
tofile="b/src/server.py",
mode=DiffMode.UNIFIED,
width=80,
height=20,
)
def update(self, msg):
# DiffViewer handles arrows, PageUp/Down, Home/End, mouse wheel, and 'm' to toggle mode
self.diff_viewer, cmd = self.diff_viewer.update(msg)
return self, cmd
def view(self):
return self.diff_viewer.view()-
Unified Diff (
DiffMode.UNIFIED): Standard single-column diff format with old and new line numbers, hunk header badges (@@ -1,5 +1,8 @@), and addition/deletion lines. -
Split Diff (
DiffMode.SPLIT): Dual-pane side-by-side comparison with synchronized vertical scrolling. -
Intra-Line Word Highlighting: Pairings of adjacent deletion (
-) and addition (+) lines are processed viadifflib.SequenceMatcherto highlight exact modified word tokens with high-contrast background highlights. -
Direct Unified Text Input: Supports loading raw diff patches directly via
diff_viewer.set_diff(diff_patch).
The Form component manages multi-field data entry forms with field-level and form-level validation, error badges, automatic keyboard focus cycling, and submission handling.
from espresso.beans import Form, FormField, FormSubmitMsg, TextInput, Slider
def validate_email(val: str) -> str | None:
if "@" not in val or "." not in val:
return "Must be a valid email address"
return None
form = Form(
fields=[
FormField(id="name", label="Full Name", bean=TextInput(placeholder="Ada"), required=True),
FormField(id="email", label="Email", bean=TextInput(placeholder="ada@example.org"), validator=validate_email, required=True),
FormField(id="exp", label="Experience", bean=Slider(min_val=0, max_val=20, value=5), hint="Years in Python"),
],
title="User Registration",
submit_label="Save Profile",
width=60,
offset_y=3,
)
def update(self, msg):
if isinstance(msg, FormSubmitMsg):
print("Received valid submission:", msg.values)
return self, None
self.form, cmd = self.form.update(msg)
return self, cmd
def view(self):
return self.form.view()-
Keyboard Navigation:
-
Tab/Shift+Tabcycles focus forward and backward across all fields and the submit button. -
↑/↓arrow keys quickly navigate between fields. -
Enteron the submit button, orCtrl+Sfrom any field, validates and submits the form.
-
-
Mouse Selection: Click on any field to focus it directly, or click the
[ Submit ]button to trigger validation and submission. -
Live Validation & Error Badges: If required fields are omitted or custom validators return an error string, high-visibility red error badges (
⚠ <error message>) appear inline below the offending field, and focus jumps to the first invalid field. -
Composite Bean Support: Any Espresso
Modelor widget (TextInput,Slider,RangeSlider,DatePicker,TextArea) can serve as aFormField.bean.
The CommandPalette component provides a fuzzy spotlight search and action runner inspired by VS Code (Ctrl+P / Cmd+P) and Neovim Telescope.
from espresso.beans import CommandPalette, PaletteItem, CommandPaletteSelectMsg
palette = CommandPalette(
items=[
PaletteItem(id="git_status", title="Git: View Status", category="Git", shortcut="Ctrl+G"),
PaletteItem(id="file_open", title="File: Open File", category="File", shortcut="Ctrl+O"),
PaletteItem(id="theme_toggle", title="View: Toggle Dark Theme", category="View"),
],
placeholder="Type a command or search...",
width=60,
toggle_key="ctrl+p",
)
def update(self, msg):
if isinstance(msg, CommandPaletteSelectMsg):
print(f"Selected action: {msg.item.id}")
return self, None
self.palette, cmd = self.palette.update(msg)
return self, cmd
def view(self):
base_view = self.render_workspace()
# Centered spotlight modal overlay
return self.palette.overlay(base_view)- Subsequence Fuzzy Matching: Scores items by consecutive matches, word boundaries, and title weighting.
- Recents Priority: Automatically tracks recently selected items and bubbles them to the top when query is empty.
- Category & Shortcut Badges: Displays color-coded category labels and right-aligned keyboard shortcut hints.
-
Non-Destructive Overlay:
palette.overlay(base_screen)seamlessly centers the modal on top of any active background view without disturbing underlying layout.
The GitTree component displays a collapsible project file tree with Git status badges, branch headers, and directory folding, inspired by Neovim Neo-tree and Lazygit.
from espresso.beans import GitTree, GitFileStatus, GitTreeSelectMsg
tree = GitTree.from_paths(
{
"src/main.py": GitFileStatus.MODIFIED,
"src/app.py": GitFileStatus.ADDED,
"tests/test_app.py": GitFileStatus.UNTRACKED,
"README.md": GitFileStatus.CLEAN,
},
branch="main",
width=30,
height=18,
)
def update(self, msg):
if isinstance(msg, GitTreeSelectMsg):
print(f"Opening file: {msg.path}")
return self, None
self.tree, cmd = self.tree.update(msg)
return self, cmd
def view(self):
return self.tree.view()-
Git Status Badges: Displays colored indicators:
[M](Modified - Yellow),[A](Added - Green),[D](Deleted - Red),[?](Untracked - Blue),[U](Unmerged - Magenta),[R](Renamed - Cyan). -
Branch Header: Top status bar displaying active branch name
⎇ mainand aggregate change counts(~1 +1). -
Keyboard & Mouse Folding: Press
Space,Left/Right, or double-click any directory to fold or unfold children. Single click moves selection; double click selects files. -
Hierarchical Path Constructor:
GitTree.from_paths(paths_dict)builds and sorts the nested tree automatically.
The BarChart component renders horizontal and vertical bar charts with sub-character precision, auto-scaling, and TrueColor linear/multi-gradients.
from espresso.beans import BarChart, BarItem, BarOrientation, BarChartSelectMsg
chart = BarChart(
items=[
BarItem(label="CPU 0", value=42.0, formatter=lambda v: f"{v:.0f}%"),
BarItem(label="CPU 1", value=78.0, formatter=lambda v: f"{v:.0f}%"),
BarItem(label="RAM", value=64.0, formatter=lambda v: f"{v:.0f}%"),
BarItem(label="Disk", value=22.0, formatter=lambda v: f"{v:.0f}%"),
],
title="Resource Telemetry",
orientation=BarOrientation.HORIZONTAL, # or BarOrientation.VERTICAL
gradient_colors=("#00E5FF", "#7D56F4", "#F7768E"),
width=40,
height=10,
)
def update(self, msg):
if isinstance(msg, BarChartSelectMsg):
print(f"Selected metric: {msg.item.label} = {msg.item.value}")
return self, None
self.chart, cmd = self.chart.update(msg)
return self, cmd
def view(self):
return self.chart.view()-
Sub-Character Precision: Utilizes Unicode fractional block glyphs (
,▏,▎,▍,▌,▋,▊,▉,█) in horizontal mode and (,▂,▃,▄,▅,▆,▇,█) in vertical mode. -
TrueColor Multi-Gradients: Dynamically evaluates linear gradients across all bars using Crema's
multi_gradient_colors. - Auto-Scaling: Automatically calculates appropriate axis bounds and proportions from input values.
-
Interactive Cursor: Navigate with
↑/↓(horizontal) or←/→(vertical), or click with the mouse to inspect specific bars.
The ScrollView component is a high-level container bean that wraps any child Model (or raw text string) inside a scrollable rectangular viewport.
from espresso.beans import ScrollView, Table
# Wrap any Model bean or long text string
scroll_view = ScrollView(
child=my_table,
width=60,
height=12,
show_scrollbar=True,
scroll_step=1,
)
def update(self, msg):
self.scroll_view, cmd = self.scroll_view.update(msg)
return self, cmd
def view(self):
return self.scroll_view.view()-
Wrap Any Bean or Text: Accepts any TEA
Modelcomponent or string content, dynamically computing height and clamping scroll offsets. -
Lifecycle & Event Forwarding: Forwards
init()and non-scrollupdate(msg)calls to the childModel. - Integrated Scrollbar: Renders a vertical scrollbar with customizable thumb and track Crema styling.
- Mouse Coordinate Translation: Translates mouse event coordinates relative to the current scroll offset so the child receives local coordinates.
-
Navigation Controls:
-
↑/↓/k/j: Scroll by line (when child is not consuming navigation). -
PageUp/PageDown(Ctrl+u/Ctrl+d): Scroll by page height. -
Home/End: Jump to top or bottom. - Mouse Wheel (
WHEEL_UP/WHEEL_DOWN): Smooth mouse scrolling. - Scrollbar click: Directly jump to position.
-
The Accordion component provides collapsible multi-level panel containers that can wrap any child Model bean or text inside each accordion section.
from espresso.beans import Accordion, AccordionItem, TextInput, Table, ScrollView
accordion = Accordion(
items=[
AccordionItem(
id="profile",
title="User Profile",
content=my_form_or_input,
expanded=True,
badge="Required",
),
AccordionItem(
id="metrics",
title="Cluster Metrics",
content=my_table,
badge="4 nodes",
),
AccordionItem(
id="logs",
title="Service Logs",
content=ScrollView(child=long_log_text, width=58, height=8),
),
],
width=64,
allow_multiple=False, # Set True for multi-expand mode
)
def update(self, msg):
self.accordion, cmd = self.accordion.update(msg)
return self, cmd
def view(self):
return self.accordion.view()-
Wrap Any Bean: Every level can wrap any
Model(e.g.TextInput,Table,Form,ScrollView) or plain text. -
Expansion Modes:
-
allow_multiple=False: Classic accordion where expanding one section collapses previously open sections. -
allow_multiple=True: Independent collapsible panels where multiple sections can stay open simultaneously.
-
-
Two-Tier Keyboard Focus:
-
Header Navigation:
↑/↓ork/jnavigate headers;EnterorSpacetoggles expand/collapse;Right/Leftexpands or collapses. -
Child Bean Focus: Pressing
Tabon an expanded section focuses into the child bean; pressingEscorShift+Tabreturns focus to the accordion headers.
-
Header Navigation:
- Mouse Support: Clicking section headers toggles expansion; clicking into expanded content areas routes mouse events to the child bean.
-
Customizable Appearance: Expand/collapse icons (
▼/▶), status badges, borders, and active header highlight styles.
The VirtualList component is a high-performance virtualized list feed that only renders items visible within the terminal viewport window, achieving sub-millisecond updates even with thousands of items.
from espresso.beans import VirtualList, VirtualListChangeMsg, VirtualListSelectMsg
def render_post_card(item: dict, is_selected: bool, width: int) -> str:
prefix = "▶ " if is_selected else " "
return f"{prefix}{item['author']}: {item['content']}"
vlist = VirtualList(
items=my_posts,
render_item=render_post_card,
width=70,
height=20,
show_scrollbar=True,
focused=True,
)
def update(self, msg):
if isinstance(msg, VirtualListSelectMsg):
print(f"Selected item: {msg.item}")
elif isinstance(msg, VirtualListChangeMsg):
print(f"Active cursor moved to index: {msg.index}")
self.vlist, cmd = self.vlist.update(msg)
return self, cmd
def view(self):
return self.vlist.view()-
$O(\text{visible})$ Virtualization: Only invokesrender_itemfor items currently inside the visible height slice, making it suitable for massive timelines and feeds. -
Variable-Height Item Support: Handles multi-line items with dynamic height calculation and internal caching (
_height_cache). -
Reading Anchor Preservation: When prepending new items (e.g. streaming or timeline refreshes),
keep_anchor=Truepreserves the current reading position so the viewport doesn't jump. -
Item-Centric Navigation: Tracks discrete item indices with
selected_index,selected_item, and selection messages (VirtualListSelectMsg,VirtualListChangeMsg). -
Integrated Scrollbar & Mouse: Mouse wheel navigation, scrollbar clicking, and keyboard navigation (
j/k,↑/↓,PageUp/PageDown,Home/End,Enter/Space).
The ArtPlayer component is an interactive terminal art and animation engine supporting modern .3a Animated ASCII Art and classic BBS .ans ANSI Art formats with frame playback controls, border framing, and TEA lifecycle messages.
-
.3aAnimated ASCII Art (modern standard byasciimoth/3a):-
@3aheader parsing:title,author,orig-author,tags,delay(per-frame overrides e.g.delay: 50, 0:100),loop: yes|no,colors: yes|no. - Palette mapping with
col <char> fg:<color> bg:<color>: supports 4-bit standard/bright ANSI (0-f), 256-color palette (0-255), and 24-bit TrueColor hex (#RRGGBB). - Pinned sections:
@color-pin(matrix of colors applied to all body text frames) and@text-pin(matrix of glyphs colored by body frames). - Multi-frame
@bodychunking with whitespace frame separators.
-
-
.ansANSI Art & BBS ANSImations:- IBM-PC Code Page 437 (
cp437) character decoding (█,▀,▄,░,▒,▓,─,│,╔,║). - 128-byte SAUCE metadata parsing (Title, Author, Group/Date) and COMNT stripping.
- Screen-clearing (
\x1b[2J) and cursor home (\x1b[H) frame splitting for multi-frame ANSImations. - Progressive Reveal: Simulates retro 14.4k/28.8k baud modem download line-by-line, ideal for splash screens and logo intros.
- IBM-PC Code Page 437 (
from espresso.beans import (
ArtPlayer,
AnimationDoneMsg,
AnimationLoopMsg,
parse_3a,
parse_ans,
)
from espresso.crema import ROUNDED_BORDER, DOUBLE_BORDER
# Option A: From a .3a format string
player = ArtPlayer.from_3a(
my_3a_text,
border=ROUNDED_BORDER,
border_fg="#FF8800",
show_controls=True,
show_title=True,
center_horizontally=True,
)
# Option B: From an ANSI file (.ans) with progressive splash reveal
player = ArtPlayer.from_ans(
my_ans_bytes,
progressive_lines=True,
lines_per_frame=1,
default_delay_ms=40,
border=DOUBLE_BORDER,
border_fg="#00E5FF",
title="BBS Terminal Gateway",
)
# Option C: From arbitrary text frames
player = ArtPlayer.from_frames(["Frame 1", "Frame 2", "Frame 3"], default_delay_ms=80)
# In Parent Model:
def init(self):
return self.player.init()
def update(self, msg):
# React when a non-looping splash animation completes
if isinstance(msg, AnimationDoneMsg):
print(f"Splash animation '{msg.title}' completed!")
self.transition_to_main_screen()
self.player, cmd = self.player.update(msg)
return self, cmd
def view(self):
return self.player.view()-
Interactive Keyboard Controls:
-
Space/k: Toggle Play / Pause. -
r/R: Restart animation from frame 0. -
←/h: Step backward one frame. -
→/l: Step forward one frame. -
+/]/=: Increase playback speed. -
-/[: Decrease playback speed.
-
-
Crema Framing & Centering:
- Border customization (
ROUNDED_BORDER,DOUBLE_BORDER,THICK_BORDER,BLOCK_BORDER,None). - Auto-embedding titles in top borders (
border_title). - Horizontal centering (
center_horizontally=True) and vertical centering within fixed container bounds.
- Border customization (
-
Lifecycle Events:
-
AnimationDoneMsg(tag, title): Emitted when non-looping animations finish. -
AnimationLoopMsg(tag, loop_count): Emitted on each cycle completion in looping mode.
-
Espresso TUI Documentation • Built with pure Python standard library • GitHub