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.4.0.tar.gz (98.7 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.4.0-py3-none-any.whl (66.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyos_tui-0.4.0.tar.gz
  • Upload date:
  • Size: 98.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.6

File hashes

Hashes for pyos_tui-0.4.0.tar.gz
Algorithm Hash digest
SHA256 8b7c918fc73636b24bb3c13d7f46f888c94a1cb78f523a165a9a662beb342532
MD5 5e1a9e026e691a669016c90672f091d9
BLAKE2b-256 5a8ca597a1bb7ed6106ca6f152f23c66dfece57b33ced1659da3721e2f9f5ffa

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyos_tui-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 66.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.6

File hashes

Hashes for pyos_tui-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3975b27d358a90558d20f6d95e517df4b424377514277526dc5142d5fa729611
MD5 511e40cf69e47340fbb06c8e99d79363
BLAKE2b-256 2b1b5936535a0b4768d48acf995e13c49081d3724caa7081a7a9f195d0db62ce

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