Skip to main content

Terminal-first mini-OS framework: Activities, event loop, flex layout, and TUI components.

Project description

pyos-tui

A terminal-first mini-OS framework for building curses apps in Python. Provides an activity stack, event loop, flex layout engine, and composable UI components — so you can focus on your app instead of wrestling with curses.

Install

pip install pyos-tui

# Windows users
pip install pyos-tui[windows]

Quick start

import curses
from pyos import Application, Activity
from pyos.EventTypes import KeyStroke
from pyos.Keys import ESC
from pyos.printers.TopBar import TopBar
from pyos.printers.ScrollList import ScrollList
from pyos.printers.BottomBar import BottomBar
from pyos.input_handlers import handle_scroll_list_input, ScrollChange

class HomeActivity(Activity):
    def on_start(self):
        self.application.subscribe(KeyStroke, self, self.on_key)
        self.application.subscribe(ScrollChange, self, self.on_scroll)

        self.display_state = {
            "top": TopBar.display_state(items={"title": "My App", "help": "ESC quit"}),
            "list": ScrollList.display_state(
                screen=self.screen,
                items=[f"Item {i}" for i in range(1, 51)],
                selected_index=0,
                focused=True,
                input_handler=handle_scroll_list_input,
                min_height=5, flex=1,
            ),
            "bottom": BottomBar.display_state(items={"status": "Ready"}),
        }
        self.refresh_screen()

    def on_key(self, event):
        self.display_state["list"]["input_handler"](
            "list", self.display_state["list"], event, self.event_queue
        )
        if event.key == ESC:
            self.application.pop_activity()
        self.refresh_screen()

    def on_scroll(self, event):
        idx = self.display_state["list"]["selected_index"]
        self.display_state["bottom"]["items"]["status"] = f"Selected: {idx}"
        self.refresh_screen()

def main(stdscr):
    app = Application(stdscr)
    app.start(HomeActivity())

if __name__ == "__main__":
    curses.wrapper(main)

Features

Activity stack — Push, pop, and replace screens like a mobile navigation controller. Each Activity owns its UI state and subscriptions; the framework cleans up automatically on pop.

Event system — Subscribe to typed events (KeyStroke, TextBoxSubmit, ScrollChange, etc.). Activities subscribe in on_start and the framework unsubscribes everything on stop.

Flex layout — Regions can be fixed-height, flex (proportional with min/max), or auto-measured. The layout solver distributes terminal rows automatically.

Composable UI components — Built-in printers for common patterns:

Component Description
TopBar / BottomBar Status bars
ScrollList Scrollable list with selection
TextInput Single-line text field with cursor
Table Auto-sized columns with headers
ContextMenuList List items with inline action menus
MultilineText Read-only text block
Accordion Collapsible sections
Spacer Fills remaining space

Input handlers — Plug-in handlers for text fields (handle_text_box_input) and scroll lists (handle_scroll_list_input) that manage cursor, selection, and emit events.

ThreadingCentralDispatch provides serial and concurrent dispatch queues. The main queue is safe for UI mutations; background work marshals updates back via main_thread.submit_async(...).

Error recovery — Unhandled exceptions push a traceback viewer. Press ESC to attempt stack recovery without crashing.

Built-in log viewer — Press F1 to tail application.log in a dedicated activity.

Pytest plugin — Ships a headful test renderer (pyos-headful) as a pytest plugin for watching your TUI tests render in real time.

Activity lifecycle

_start(application)  →  on_start()  →  refresh_screen()  →  _stop()

Override on_start() to set up display_state and event subscriptions. Override on_stop() for cleanup. Navigate with:

self.application.segue_to(NextActivity())              # push
self.application.segue_to(NextActivity(), Segue.REPLACE)  # replace
self.application.pop_activity()                        # pop (empty stack stops the app)

Layout

Each entry in display_state is a dict with a "layout" key:

{"height": 3}                                    # fixed: exactly 3 rows
{"flex": 1, "min_height": 5}                     # flex: share remaining space
{"flex": 2, "min_height": 3, "max_height": 20}   # flex with bounds
# omit both → auto-measured from line_generator output

Threading

Only read/write display_state on the main thread. From background work:

self.main_thread.submit_async(self.refresh_screen)
self.main_thread.submit_async(self.on_new_data, payload)

Requirements

  • Python 3.9+
  • A terminal that supports curses (most Unix terminals; Windows via windows-curses)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyos_tui-0.5.0.tar.gz (106.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyos_tui-0.5.0-py3-none-any.whl (70.3 kB view details)

Uploaded Python 3

File details

Details for the file pyos_tui-0.5.0.tar.gz.

File metadata

  • Download URL: pyos_tui-0.5.0.tar.gz
  • Upload date:
  • Size: 106.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for pyos_tui-0.5.0.tar.gz
Algorithm Hash digest
SHA256 06a81f5e58a6d7af2239012d73921329bbd187334234e0365f47125ba69753fd
MD5 144b104f582ab3089be0a334b01a3301
BLAKE2b-256 9a2140a390272f54e192e850ff17fb8d11edb60e79e2171fba81a7e1a72a2b05

See more details on using hashes here.

File details

Details for the file pyos_tui-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: pyos_tui-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 70.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for pyos_tui-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b49486f007120a019f7612d3c0a375bd0291069f6fb14f8db6c06806eb6c1dbb
MD5 0466f44477966cb6ab84219393ea33e0
BLAKE2b-256 15723c1590a4c222dd7f7c6195c0f0e5c2519a40ee535b68a94c9df2f474f82c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page