Source code for genro_textual.textual_builder

# 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="*", compile_module="textual.containers", compile_class="Container") def container(self): """A generic container widget.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="Vertical") def vertical(self): """A container that arranges children vertically.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="Horizontal") def horizontal(self): """A container that arranges children horizontally.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="Center") def center(self): """A container that centers its children horizontally.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="Middle") def middle(self): """A container that centers its children vertically.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="CenterMiddle") def centermiddle(self): """A container that centers its children both horizontally and vertically.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="Right") def right(self): """A container that aligns its children to the right.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="Grid") def grid(self): """A container with grid layout.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="VerticalScroll") def verticalscroll(self): """A scrollable vertical container.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="HorizontalScroll") def horizontalscroll(self): """A scrollable horizontal container.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="ScrollableContainer") def scrollablecontainer(self): """A scrollable container.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="VerticalGroup") def verticalgroup(self): """A vertical group of widgets.""" ...
[docs] @element(sub_tags="*", compile_module="textual.containers", compile_class="HorizontalGroup") def horizontalgroup(self): """A horizontal group of widgets.""" ...
[docs] @element(sub_tags="*", 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="", 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="", 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="*", 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="", 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="*", 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", 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="", 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="", 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="*", compile_module="textual.widgets", compile_class="Footer") def footer(self, show_command_palette: bool = True, compact: bool = False): """Textual Footer widget.""" ...
[docs] @element(sub_tags="", compile_module="textual.widgets", compile_class="Header") def header( self, content: str = "", show_clock: bool = False, icon: str | None = None, time_format: str | None = None, ): """A header widget with icon and clock.""" ...
[docs] @element(sub_tags="*", compile_module="textual.widgets", compile_class="HelpPanel") def helppanel(self, markup: bool = True): """Textual HelpPanel widget.""" ...
[docs] @element(sub_tags="", 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="*", 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="", 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="*", 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", compile_module="textual.widgets", compile_class="ListView") def listview(self, initial_index: int = 0): """A vertical list view widget.""" ...
[docs] @element(sub_tags="", compile_module="textual.widgets", compile_class="LoadingIndicator") def loadingindicator(self, content: str = ""): """Display an animated loading indicator.""" ...
[docs] @element(sub_tags="", 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="", 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="", 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="", 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="*", 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="", 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="", compile_module="textual.widgets", compile_class="Pretty") def pretty(self, content: str = "", object: str | None = None): """A pretty-printing widget.""" ...
[docs] @element(sub_tags="", 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="", 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", 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="", 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="", 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="", 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="*", 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="", 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="", 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="", 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="", 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", 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", 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", compile_module="textual.widgets", compile_class="Tabs") def tabs(self, active: str | None = None): """A row of tabs.""" ...
[docs] @element(sub_tags="", 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="", 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="", 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="", 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 """