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.0.tar.gz (24.9 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.0-py3-none-any.whl (27.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: vibe_tui-0.7.0.tar.gz
  • Upload date:
  • Size: 24.9 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.0.tar.gz
Algorithm Hash digest
SHA256 3b993c876fe0b9bd23a3c6ea37556c499f3c243e177cddd43377b5286c6cda02
MD5 b5c1f02378838233270cbe2b3b16c3cd
BLAKE2b-256 a71ce695f0b989f86d948bf41c6ec88d28dd897646f631a44803acbefb2d9f0e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: vibe_tui-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 27.9 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d390a68d2119f796463c47226c2a974cff065e0d71d2a93533cb49bf7327e8c3
MD5 42842ae0cd370965480b3eb742fe9168
BLAKE2b-256 42f31de6232970c000973df866b86f4f1551329de387a28fbb83aeab7da02fa8

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