Skip to main content

A sophisticated, layered TUI framework with weight-based layouts and focus management.

Project description

Vibe-TUI

A TUI library based in pure python that uses weighting to align your program perfectly.

Version Creator

Why should I use this?

  • No asyncio: Do what you want and manage as you like and display when you're finished; no rush here!
  • Weight based alignment: Remove the hassle of calculating widths and heights to the weight system! (I don't see hackers need to be game devs to make good TUIs)

What Nodes (or widgets) can I use?

  • UIBox(Node): A basic container box.
  • UIImage(Node): Displays an image as block characters (requires term-image and Pillow).
  • PyCodeText(UIBox) (In progress): Python text editor widget.
  • UILabel(UIBox): A box without edges, used for text display.
  • UIBar(UIBox): Loading bar, requires vibe_loadbar to be installed.
  • ColorPicker(Node): A color selection widget.
  • UICheckbox(UIBox): A checkbox widget.
  • UIButton(UIBox): A clickable button.
  • UIInput(Node): A text input field.
  • UISelect(UIBox): A selection/list manager.
  • TabManagerH(UiContainerHorizontal): A horizontal tab manager.
  • UIScrollText(Node): A scrollable text view.
  • UIEditor(UIBox): A full-featured text editor.
  • UITerminal(UiContainerVertical): A terminal emulator widget.
  • UITree(UIBox): A tree view widget.
  • UIToast(Node): A toast notification overlay.
  • UIModal(Node): A basic modal overlay.
  • UIModalNode(UIModal): A modal that can contain another widget.
  • CanvasNode(Node): A canvas for custom drawing. and more...

What layouts can I use?

  • UiContainerHorizontal(Node): Horizontal layout.
  • UiContainerVertical(Node): Vertical layout.

You can also use FocusManager to manage Node focus.

File Structure:

.
├── architecture.svg
├── pyproject.toml
├── README.md
├── vibe_browser.py
├── example.py
└── vibe_tui
    ├── UI
    │   ├── __init__.py
    │   ├── base_widgets.py
    │   ├── interactive
    │   │   ├── __init__.py
    │   │   ├── clickable.py
    │   │   ├── images
    │   │   │   ├── __init__.py
    │   │   │   └── image.py
    │   │   └── textinput.py
    │   └── widgets.py
    ├── __init__.py
    ├── base
    │   ├── __init__.py
    │   ├── basic.py
    │   ├── colors.py
    │   └── theme.py
    ├── event
    │   └── eventmanager.py
    ├── keyinput
    │   ├── __init__.py
    │   └── key.py
    ├── layouts
    │   ├── __init__.py
    │   └── baselayouts.py
    ├── managers
    │   ├── __init__.py
    │   ├── app.py
    │   └── manager.py
    ├── node
    │   ├── __init__.py
    │   └── basenode.py
    └── statements.py

Class structure:

class

New:

  • VibeApp() class makes a custom app for you without having to manage the event loop.

Example code:

Click to view the code
from vibe_tui import (
    VibeApp,
    UiContainerVertical,
    UiContainerHorizontal,
    UISelect,
    UIScrollText,
    UIInput,
    UILabel,
    Colors,
    Theme
)
import random
import string
from vibe_tui.base.colors import LIGHT_BLUE_THEME

Theme.set_color_theme(LIGHT_BLUE_THEME)

class SimpleApp:
    def __init__(self):
        # Sample data for demonstration
        self.all_items = items = [
            f"root/{'sys' if i % 3 == 0 else 'usr'}/{i:02d}/{''.join(random.choices(string.ascii_lowercase, k=12))}.cfg"
            for i in range(123)
        ]
        
        # 2. Define Layout
        self.root = UiContainerVertical()
        
        # Header with bold colors
        self.header = UILabel(weight=1, text=f" {Colors.BOLD}{Colors.BLUE}VIBE-TUI ENHANCED DEMO{Colors.RESET}")
        
        # Interactive Search/Filter bar
        self.search_bar = UIInput(weight=1, label="   Filter: ")
        
        # Main content area using weights (1:2 ratio)
        self.main_layout = UiContainerHorizontal(weight=8)
        self.list_box = UISelect(weight=1, title=" SELECTOR ")
        self.preview_box = UIScrollText(weight=2, title=" PREVIEW ", show_line_numbers=True)
        
        self.main_layout.add(self.list_box).add(self.preview_box)
        
        # Informative Footer
        self.footer = UILabel(weight=1, text=f" {Colors.DIM}Press 'q' to Quit | Tab: Switch Focus | Arrows: Navigate{Colors.RESET}")

        # Assemble the UI tree
        self.root.add(self.header).add(self.search_bar).add(self.main_layout).add(self.footer)

        # 3. Setup Logic & Events
        self.list_box.options = self.all_items
        
        # Bind events: Update preview on selection, and filter on search change
        self.list_box.on("change", self.on_selection_change)
        self.search_bar.on("change", self.on_search_change)

        # 4. Initialize the App Engine
        self.app = VibeApp(self.root)
        self.on_selection_change(None) # Initial preview load

    def on_selection_change(self, _):
        """Updates the preview pane based on the current selection."""
        if not self.list_box.options:
            self.preview_box.set_text("\n   No matches found.")
            return
            
        selected = self.list_box.options[self.list_box.selection]
        details = f"{Colors.BOLD}Details for:{Colors.RESET} {Colors.CYAN}{selected}{Colors.RESET}\n"
        details += "═" * 40 + "\n\n"
        details += "This UI is built using weight-based alignment,\n"
        details += "ensuring it scales perfectly with your terminal size.\n\n"
        details += f"Current Index: {Colors.YELLOW}{self.list_box.selection}{Colors.RESET}\n"
        details += f"Status: {Colors.GREEN}Active{Colors.RESET}" if self.list_box.selection % random.choice([i for i in range(1, 16)]) != 0 else f"Status: {Colors.RED}DISABLED{Colors.RESET}"
        
        self.preview_box.set_text(details)

    def on_search_change(self, query):
        """Filters the list selection as you type."""
        filtered = [item for item in self.all_items if query.lower() in item.lower()]
        self.list_box.options = filtered
        
        # Ensure selection index remains valid
        if self.list_box.selection >= len(filtered):
            self.list_box.selection = max(0, len(filtered) - 1)
        self.on_selection_change(None)

    def run(self):
        """Extends the input handler to support global shortcuts."""
        original_handle = self.app.fm.handle_input
        
        def custom_handle(key):
            # Global quit shortcut (when not typing in the search bar)
            if key == 'q' and self.app.fm.current != self.search_bar:
                self.app.stop()
                return
            original_handle(key)
        
        self.app.fm.handle_input = custom_handle
        self.app.run()

if __name__ == "__main__":
    try:
        SimpleApp().run()
    except Exception as e:
        print(f"Error starting app: {e}")

Made with time and code by Adam Hany

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

vibe_tui-0.7.2.tar.gz (25.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vibe_tui-0.7.2-py3-none-any.whl (28.7 kB view details)

Uploaded Python 3

File details

Details for the file vibe_tui-0.7.2.tar.gz.

File metadata

  • Download URL: vibe_tui-0.7.2.tar.gz
  • Upload date:
  • Size: 25.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for vibe_tui-0.7.2.tar.gz
Algorithm Hash digest
SHA256 eb924052a4a137ec0eef0ed6810614c176712d4e1f3d8acd0a540f4d3181fdff
MD5 ec625bcf04a76fca2de4942df3869538
BLAKE2b-256 c89738286ae80524574b08712abfcce9b7daa2b6fc5c9d36862dbc64b023fee0

See more details on using hashes here.

File details

Details for the file vibe_tui-0.7.2-py3-none-any.whl.

File metadata

  • Download URL: vibe_tui-0.7.2-py3-none-any.whl
  • Upload date:
  • Size: 28.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for vibe_tui-0.7.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d2839a4469e66fbe99c336145b4feaae9c9968dcbbd84d98e99a006bd60b8651
MD5 576b60a463d7c15bb21e5250d728bf12
BLAKE2b-256 fa378a908984516bc8ef154c227e621f8aac24a4c6150e6cfe52a9f4b2c4bc5b

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