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.1.tar.gz (107.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.1-py3-none-any.whl (71.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyos_tui-0.5.1.tar.gz
  • Upload date:
  • Size: 107.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.1.tar.gz
Algorithm Hash digest
SHA256 9c3d9314364b44af5544bc810071bae7911655c2025c5f7562e6152ed335c9be
MD5 b42439b5e04928d3b246729882bfc38a
BLAKE2b-256 d1f8c3b71cf321165e26b149a5e52c0cd11bd43046243dfe1697f0ba68360603

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyos_tui-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 71.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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 03dab03d2e33364a1d90777aa37296a49fe2b7448073bbbebbaf9cdba9a261d0
MD5 a16010a3fb61243522dca1ce8fb825d1
BLAKE2b-256 b39f5e5286479a4ec5f506cd160902a3f19dac74a58f29525a7020270dbab7e3

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