SoFunction
Updated on 2024-11-13

Exploring the Principles of Using the Python Textual Text User Interface Library

Python Textual Text User Interface Library

Textual is a TUI (Text User Interface) library for Python, a sister project to and dependent on Rich, which supports Rich's Renderable class and has its own interactive component, the Widget class. Complex user interfaces can be built using a simple Python API to run in shell tools or browsers.

Textual runs on Linux, macOS and Windows.

Textual requires Python 3.7 or later.

Textual Principle

  • Textual uses Rich to render rich text, so anything Rich can render can be used in Textual.
  • Textual's event handling is asynchronous (using the async and await keywords). widgets (UI components) can be updated independently and communicate with each other via messaging.
  • Textual has more in common with modern web development in that layouts are done in CSS and themes can be customized in CSS. Other techniques are borrowed from JS frameworks such as Vue and Reactive.

Install Textual

pip install textual

If you plan to develop a text application, you should also install the development tools using the following command:

pip install textual-dev

After installing Textual, run the following command to see what it can do:

python -m textual

Run results:

Widgets

Button #Button
Checkbox #Checkbox
Collapsible #stowed/folded
ContentSwitcher#Content Switcher
DataTable#DataTable
Digits
DirectoryTree #DirectoryTree
Footer
Header#Header
Input#Input
Label#Label
ListView#List
LoadingIndicator#Loading Indicator
Log#Log
MarkdownViewer#Markdown Viewer
Markdown#Markdown
OptionList#Option List
Placeholder#Placeholder
Pretty#Formatting
ProgressBar#Progress Bar
RadioButton#Radio Button
RadioSet#CheckButton
RichLog#rich log
Rule#Split Line
Select#Select
SelectionList#Selection List
Sparkline#Bar Chart
Static# static files
Switch#Switch
Tabs#
TabbedContent tab content
TextArea#Text Area
Tree#Tree

Textual applies styles to widgets using CSS.

Example of a countdown

from time import monotonic
from  import App, ComposeResult
from  import ScrollableContainer
from  import reactive
from  import Button, Footer, Header, Static
class TimeDisplay(Static):
    """A widget to display elapsed time."""
    start_time = reactive(monotonic)
    time = reactive(0.0)
    total = reactive(0.0)
    def on_mount(self) -> None:
        """Event handler called when widget is added to the app."""
        self.update_timer = self.set_interval(1 / 60, self.update_time, pause=True)
    def update_time(self) -> None:
        """Method to update time to current."""
         =  + (monotonic() - self.start_time)
    def watch_time(self, time: float) -> None:
        """Called when the time attribute changes."""
        minutes, seconds = divmod(time, 60)
        hours, minutes = divmod(minutes, 60)
        (f"{hours:02,.0f}:{minutes:02.0f}:{seconds:05.2f}")
    def start(self) -> None:
        """Method to start (or resume) time updating."""
        self.start_time = monotonic()
        self.update_timer.resume()
    def stop(self) -> None:
        """Method to stop the time display updating."""
        self.update_timer.pause()
         += monotonic() - self.start_time
         = 
    def reset(self) -> None:
        """Method to reset the time display to zero."""
         = 0
         = 0
class Stopwatch(Static):
    """A stopwatch widget."""
    def on_button_pressed(self, event: ) -> None:
        """Event handler called when a button is pressed."""
        button_id = 
        time_display = self.query_one(TimeDisplay)
        if button_id == "start":
            time_display.start()
            self.add_class("started")
        elif button_id == "stop":
            time_display.stop()
            self.remove_class("started")
        elif button_id == "reset":
            time_display.reset()
    def compose(self) -> ComposeResult:
        """Create child widgets of a stopwatch."""
        yield Button("Start", , variant="success")
        yield Button("Stop", , variant="error")
        yield Button("Reset", )
        yield TimeDisplay()
class StopwatchApp(App):
    """A Textual app to manage stopwatches."""
    CSS_PATH = ""
    BINDINGS = [("d", "toggle_dark", "Toggle dark mode")]
    def compose(self) -> ComposeResult:
        """Called to add widgets to the app."""
        yield Header()
        yield Footer()
        yield ScrollableContainer(Stopwatch(), Stopwatch(), Stopwatch())
    def action_toggle_dark(self) -> None:
        """An action to toggle dark mode."""
         = not 
if __name__ == "__main__":
    app = StopwatchApp()
    ()

css style

Stopwatch {
    layout: horizontal;
    background: $boost;
    height: 5;
    margin: 1;
    min-width: 50;
    padding: 1;
}
TimeDisplay {
    content-align: center middle;
    text-opacity: 60%;
    height: 3;
}
Button {
    width: 16;
}
#start {
    dock: left;
}
#stop {
    dock: left;
    display: none;
}
#reset {
    dock: right;
}
.started {
    text-style: bold;
    background: $success;
    color: $text;
}
.started TimeDisplay {
    text-opacity: 100%;
}
.started #start {
    display: none
}
.started #stop {
    display: block
}
.started #reset {
    visibility: hidden
}

operational effect

Other examples can be found on github

Refer to the documentation:

Project github:/Textualize/textual 

Official Documentation:/ 

Above is the Python Textual text user interface library to use the principle of exploration of the details, more information about the Python Textual library please pay attention to my other related articles!