# Copyright 2025 Softwell S.r.l. - SPDX-License-Identifier: Apache-2.0
"""TextualBuilder - Builder for Textual TUI widgets.
Elements and components are defined in mixins so that subclasses of
TextualBuilder inherit the full schema. This follows the genro-builders
mixin pattern: _pop_decorated_methods collects decorators from mixin
bases (non-BagBuilderBase) in the MRO.
Component mixins live in genro_textual.components — each file is
a mixin that can be included or excluded when composing a builder.
No rendering logic here — that belongs in TextualCompiler.
"""
from __future__ import annotations
from genro_builders.builder import BagBuilderBase, component, element
from genro_textual.components.foundation import FoundationMixin
[docs]
class TextualWidgetsMixin:
"""All Textual widget @element and @component definitions.
Defined as a mixin so that subclasses of TextualBuilder
automatically inherit the full schema via MRO.
"""
# -------------------------------------------------------------------------
# Container elements (from textual.containers)
# -------------------------------------------------------------------------
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.containers", "compile_class": "Container"}
)
def container(self):
"""A generic container widget."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.containers", "compile_class": "Vertical"}
)
def vertical(self):
"""A container that arranges children vertically."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.containers", "compile_class": "Horizontal"}
)
def horizontal(self):
"""A container that arranges children horizontally."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.containers", "compile_class": "Center"}
)
def center(self):
"""A container that centers its children horizontally."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.containers", "compile_class": "Middle"}
)
def middle(self):
"""A container that centers its children vertically."""
...
[docs]
@element(
sub_tags="*",
_meta={"compile_module": "textual.containers", "compile_class": "CenterMiddle"},
)
def centermiddle(self):
"""A container that centers its children both horizontally and vertically."""
...
[docs]
@element(sub_tags="*", _meta={"compile_module": "textual.containers", "compile_class": "Right"})
def right(self):
"""A container that aligns its children to the right."""
...
[docs]
@element(sub_tags="*", _meta={"compile_module": "textual.containers", "compile_class": "Grid"})
def grid(self):
"""A container with grid layout."""
...
[docs]
@element(
sub_tags="*",
_meta={"compile_module": "textual.containers", "compile_class": "VerticalScroll"},
)
def verticalscroll(self):
"""A scrollable vertical container."""
...
[docs]
@element(
sub_tags="*",
_meta={"compile_module": "textual.containers", "compile_class": "HorizontalScroll"},
)
def horizontalscroll(self):
"""A scrollable horizontal container."""
...
[docs]
@element(
sub_tags="*",
_meta={"compile_module": "textual.containers", "compile_class": "ScrollableContainer"},
)
def scrollablecontainer(self):
"""A scrollable container."""
...
[docs]
@element(
sub_tags="*",
_meta={"compile_module": "textual.containers", "compile_class": "VerticalGroup"},
)
def verticalgroup(self):
"""A vertical group of widgets."""
...
[docs]
@element(
sub_tags="*",
_meta={"compile_module": "textual.containers", "compile_class": "HorizontalGroup"},
)
def horizontalgroup(self):
"""A horizontal group of widgets."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.containers", "compile_class": "ItemGrid"}
)
def itemgrid(self, min_column_width: int = 20):
"""A grid container that arranges items in columns."""
...
# -------------------------------------------------------------------------
# Widget elements (from textual.widgets)
# -------------------------------------------------------------------------
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Button"})
def button(
self,
content: str = "",
label: str | None = None,
variant: str = "default",
tooltip: str | None = None,
action: str | None = None,
):
"""A simple clickable button."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Checkbox"})
def checkbox(
self,
content: str = "",
label: str = "",
value: bool = False,
button_first: bool = True,
tooltip: str | None = None,
compact: bool = False,
):
"""A check box widget that represents a boolean value."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.widgets", "compile_class": "Collapsible"}
)
def collapsible(
self,
title: str = "Toggle",
collapsed: bool = True,
collapsed_symbol: str = "▶",
expanded_symbol: str = "▼",
):
"""A collapsible container."""
...
[docs]
@element(
sub_tags="",
_meta={"compile_module": "textual.widgets", "compile_class": "CollapsibleTitle"},
)
def collapsibletitle(
self,
content: str = "",
label: str | None = None,
collapsed_symbol: str | None = None,
expanded_symbol: str | None = None,
collapsed: str | None = None,
):
"""Title and symbol for the Collapsible."""
...
[docs]
@element(
sub_tags="*",
_meta={"compile_module": "textual.widgets", "compile_class": "ContentSwitcher"},
)
def contentswitcher(self, initial: str | None = None):
"""A widget for switching between different children."""
...
[docs]
@element(
sub_tags="column,row",
_meta={"compile_module": "textual.widgets", "compile_class": "DataTable"},
)
def datatable(
self,
show_header: bool = True,
show_row_labels: bool = True,
fixed_rows: int = 0,
fixed_columns: int = 0,
zebra_stripes: bool = False,
header_height: int = 1,
show_cursor: bool = True,
cursor_foreground_priority: str = "css",
cursor_background_priority: str = "renderable",
cursor_type: str = "cell",
cell_padding: int = 1,
):
"""A tabular widget that contains data."""
...
[docs]
@element(sub_tags="", parent_tags="datatable")
def column(self, label: str = "", key: str | None = None, width: int | None = None):
"""A column definition for DataTable."""
...
[docs]
@element(sub_tags="", parent_tags="datatable")
def row(self, key: str | None = None, label: str | None = None, height: int = 1):
"""A row for DataTable. Value can be a list of cell values."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Digits"})
def digits(self, content: str = "", value: str = ""):
"""A widget to display numerical values using a 3x3 grid of unicode characters."""
...
[docs]
@element(
sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "DirectoryTree"}
)
def directorytree(self, content: str = "", path: str | None = None):
"""A Tree widget that presents files and directories."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.widgets", "compile_class": "HelpPanel"}
)
def helppanel(self, markup: bool = True):
"""Textual HelpPanel widget."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Input"})
def input(
self,
content: str = "",
value: str | None = None,
placeholder: str = "",
password: bool = False,
restrict: str | None = None,
type: str = "text",
max_length: int = 0,
valid_empty: bool = False,
select_on_focus: bool = True,
tooltip: str | None = None,
compact: bool = False,
):
"""A text input widget."""
...
[docs]
@element(sub_tags="*", _meta={"compile_module": "textual.widgets", "compile_class": "KeyPanel"})
def keypanel(
self,
can_focus: str | None = None,
can_focus_children: str | None = None,
can_maximize: str | None = None,
):
"""Textual KeyPanel widget."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Label"})
def label(
self,
content: str = "",
variant: str | None = None,
expand: bool = False,
shrink: bool = False,
markup: bool = True,
):
"""A simple label widget for displaying text-oriented renderables."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Link"})
def link(
self,
content: str = "",
text: str | None = None,
url: str | None = None,
tooltip: str | None = None,
):
"""A simple, clickable link that opens a URL."""
...
[docs]
@element(sub_tags="*", _meta={"compile_module": "textual.widgets", "compile_class": "ListItem"})
def listitem(self, markup: bool = True):
"""A widget that is an item within a `ListView`."""
...
[docs]
@element(
sub_tags="listitem",
_meta={"compile_module": "textual.widgets", "compile_class": "ListView"},
)
def listview(self, initial_index: int = 0):
"""A vertical list view widget."""
...
[docs]
@element(
sub_tags="",
_meta={"compile_module": "textual.widgets", "compile_class": "LoadingIndicator"},
)
def loadingindicator(self, content: str = ""):
"""Display an animated loading indicator."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Log"})
def log(
self,
content: str = "",
highlight: bool = False,
max_lines: str | None = None,
auto_scroll: bool = True,
):
"""A widget to log text."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Markdown"})
def markdown(
self,
content: str = "",
markdown: str | None = None,
parser_factory: str | None = None,
open_links: bool = True,
):
"""Textual Markdown widget."""
...
[docs]
@element(
sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "MarkdownViewer"}
)
def markdownviewer(
self,
content: str = "",
markdown: str | None = None,
show_table_of_contents: bool = True,
parser_factory: str | None = None,
open_links: bool = True,
):
"""A Markdown viewer widget."""
...
[docs]
@element(
sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "MaskedInput"}
)
def maskedinput(
self,
content: str = "",
template: str | None = None,
value: str | None = None,
placeholder: str = "",
valid_empty: bool = False,
select_on_focus: bool = True,
tooltip: str | None = None,
compact: bool = False,
):
"""A masked text input widget."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.widgets", "compile_class": "OptionList"}
)
def optionlist(self, markup: bool = True, compact: bool = False):
"""A navigable list of options."""
...
[docs]
@element(
sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Placeholder"}
)
def placeholder(self, content: str = "", label: str | None = None, variant: str = "default"):
"""A simple placeholder widget to use before you build your custom widgets."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Pretty"})
def pretty(self, content: str = "", object: str | None = None):
"""A pretty-printing widget."""
...
[docs]
@element(
sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "ProgressBar"}
)
def progressbar(
self,
content: str = "",
total: float | int | None = None,
show_bar: bool = True,
show_percentage: bool = True,
show_eta: bool = True,
):
"""A progress bar widget."""
...
[docs]
@element(
sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "RadioButton"}
)
def radiobutton(
self,
content: str = "",
label: str = "",
value: bool = False,
button_first: bool = True,
tooltip: str | None = None,
compact: bool = False,
):
"""A radio button widget that represents a boolean value."""
...
[docs]
@element(
sub_tags="radiobutton",
_meta={"compile_module": "textual.widgets", "compile_class": "RadioSet"},
)
def radioset(self, tooltip: str | None = None, compact: bool = False):
"""Widget for grouping a collection of radio buttons into a set."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "RichLog"})
def richlog(
self,
content: str = "",
max_lines: str | None = None,
min_width: int = 78,
wrap: bool = False,
highlight: bool = False,
markup: bool = False,
auto_scroll: bool = True,
):
"""A widget for logging Rich renderables and text."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Rule"})
def rule(self, content: str = "", orientation: str = "horizontal", line_style: str = "solid"):
"""A rule widget to separate content, similar to a `<hr>` HTML tag."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Select"})
def select(
self,
content: str = "",
options: str | None = None,
prompt: str = "Select",
allow_blank: bool = True,
value: str = None,
type_to_search: bool = True,
tooltip: str | None = None,
compact: bool = False,
):
"""Widget to select from a list of possible options."""
...
[docs]
@element(
sub_tags="*", _meta={"compile_module": "textual.widgets", "compile_class": "SelectionList"}
)
def selectionlist(self, compact: bool = False):
"""A vertical selection list that allows making multiple selections."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Sparkline"})
def sparkline(
self,
content: str = "",
data: str | None = None,
min_color: str | None = None,
max_color: str | None = None,
summary_function: str | None = None,
):
"""A sparkline widget to display numerical data."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Static"})
def static(
self, content: str = "", expand: bool = False, shrink: bool = False, markup: bool = True
):
"""A widget to display simple static content."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Switch"})
def switch(
self,
content: str = "",
value: bool = False,
animate: bool = True,
tooltip: str | None = None,
):
"""A switch widget that represents a boolean value."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Tab"})
def tab(self, content: str = "", label: str | None = None):
"""A Widget to manage a single tab within a Tabs widget."""
...
[docs]
@element(
sub_tags="*",
parent_tags="tabbedcontent",
_meta={"compile_module": "textual.widgets", "compile_class": "TabPane"},
)
def tabpane(self, title: str | None = None):
"""A container for switchable content, with additional title."""
...
[docs]
@element(
sub_tags="tabpane",
_meta={"compile_module": "textual.widgets", "compile_class": "TabbedContent"},
)
def tabbedcontent(self, initial: str = ""):
"""A container with associated tabs to toggle content visibility."""
...
[docs]
@element(sub_tags="tab", _meta={"compile_module": "textual.widgets", "compile_class": "Tabs"})
def tabs(self, active: str | None = None):
"""A row of tabs."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "TextArea"})
def textarea(
self,
content: str = "",
text: str = "",
language: str | None = None,
theme: str = "css",
soft_wrap: bool = True,
tab_behavior: str = "focus",
read_only: bool = False,
show_cursor: bool = True,
show_line_numbers: bool = False,
line_number_start: int = 1,
max_checkpoints: int = 50,
tooltip: str | None = None,
compact: bool = False,
highlight_cursor_line: bool = True,
placeholder: str = "",
):
"""Textual TextArea widget."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Tooltip"})
def tooltip(
self, content: str = "", expand: bool = False, shrink: bool = False, markup: bool = True
):
"""Textual Tooltip widget."""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Tree"})
def tree(
self,
content: str = "",
label: str | None = None,
data: str | None = None,
store: object | None = None,
):
"""A widget for displaying and navigating data in a tree.
Args:
store: A Bag object. The tree is populated recursively from its structure.
"""
...
[docs]
@element(sub_tags="", _meta={"compile_module": "textual.widgets", "compile_class": "Welcome"})
def welcome(
self, content: str = "", expand: bool = False, shrink: bool = False, markup: bool = True
):
"""A Textual welcome widget."""
...
# -------------------------------------------------------------------------
# App configuration elements (not rendered as widgets)
# -------------------------------------------------------------------------
[docs]
@element(sub_tags="")
def css(self, content: str = ""):
"""CSS stylesheet applied to the live app."""
...
[docs]
@element(sub_tags="")
def binding(self, key: str = "", action: str = "", description: str = ""):
"""Key binding: maps a key press to an action method."""
...
# -------------------------------------------------------------------------
# Components (composite elements expanded at compile time)
# -------------------------------------------------------------------------
[docs]
@component(sub_tags="")
def fieldset(self, comp, title="", **kwargs):
"""A group of input fields with a title. Closed: returns parent for chaining."""
if title:
comp.static(title)
[docs]
@component(sub_tags="*")
def form(self, comp, title="", **kwargs):
"""A form container. Open: returns internal bag for adding fields."""
if title:
comp.static(title)
[docs]
class TextualBuilder(FoundationMixin, TextualWidgetsMixin, BagBuilderBase):
"""Builder for Textual TUI elements.
Includes FoundationMixin (app_shell) and TextualWidgetsMixin (all widgets).
Subclass TextualBuilder freely — mixin schemas are inherited via MRO.
To add custom components, define them in a mixin::
from genro_builders.builder import component
class MyMixin:
@component(sub_tags="")
def login_form(self, comp, **kwargs):
comp.input(placeholder="Username")
comp.button("Login")
class MyBuilder(MyMixin, TextualBuilder):
pass
To exclude FoundationMixin, compose your own builder::
class MinimalBuilder(TextualWidgetsMixin, BagBuilderBase):
pass
"""