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.
Threading — CentralDispatch 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd96a23ec51b1b9e85c8b651cfe79075d4dba2544ca7aef13fb65ed00c458c02
|
|
| MD5 |
cd765d8e6534b8c8573b15f1c2d08da3
|
|
| BLAKE2b-256 |
dbb9e3faa1e23503b3a6cf531b85f39728f6510f3643916ea935211a7ee65d8a
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02bce7a5e75248fb492c86cbb5b4865183aeef2738a2ea0c70d1f5c04ede5adb
|
|
| MD5 |
c239abdecfd5ddf5df076f8f6b35af87
|
|
| BLAKE2b-256 |
21deef40e5113f9ee432835d5d9722333e61824581679d8271eef3a9c44cda2b
|