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.2.tar.gz (107.6 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.2-py3-none-any.whl (71.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyos_tui-0.5.2.tar.gz
  • Upload date:
  • Size: 107.6 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.2.tar.gz
Algorithm Hash digest
SHA256 dfb9404d9f708b387f023cd29d9feec72d3a519c514530b54c88fda8a1f7eb69
MD5 6b433438b4d427b0dafed2f053817be6
BLAKE2b-256 4f548f903d9172162a85ee2933c4b6b7abb23d8785240f17728e6228b8657cac

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyos_tui-0.5.2-py3-none-any.whl
  • Upload date:
  • Size: 71.4 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e25710e3d2e03a14a5e92c22e5fb3d708a944d70945ddb95a555110c84839b3e
MD5 3ce385d2fcd96a1cc779b33963fc8d63
BLAKE2b-256 6e2831be3b38a31ff49418af5e95c8cf7a00016a26039625ca5a6717bd0d8823

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