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.3.tar.gz (106.9 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.3-py3-none-any.whl (72.1 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for pyos_tui-0.5.3.tar.gz
Algorithm Hash digest
SHA256 bd96a23ec51b1b9e85c8b651cfe79075d4dba2544ca7aef13fb65ed00c458c02
MD5 cd765d8e6534b8c8573b15f1c2d08da3
BLAKE2b-256 dbb9e3faa1e23503b3a6cf531b85f39728f6510f3643916ea935211a7ee65d8a

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for pyos_tui-0.5.3-py3-none-any.whl
Algorithm Hash digest
SHA256 02bce7a5e75248fb492c86cbb5b4865183aeef2738a2ea0c70d1f5c04ede5adb
MD5 c239abdecfd5ddf5df076f8f6b35af87
BLAKE2b-256 21deef40e5113f9ee432835d5d9722333e61824581679d8271eef3a9c44cda2b

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