A simple yet powerful TUI framework for your Python (3.7+) applications

Overview

title

A simple yet powerful TUI framework for your Python (3.7+) applications

pip3 install pytermgui

PyPI version Pylint quality

Core principles

PTG was written with some core ideas in mind, such as:

  • Pythonic syntax
  • Flexible systems
  • High quality code
  • Extensibility by design

What we provide

An example to get started with

# Note: This example uses the auto-conversion syntax. 
#       For more info, check out `help(pytermgui.auto)`.

import sys
from pytermgui import WindowManager, Window

manager = WindowManager()
window = (
    Window(min_width=50)
    + "[210 bold]My first Window!"
    + ""
    + "[157]Try resizing the window by dragging the right border"
    + "[157]Or drag the top border to move the window"
    + "[193 bold]Alt-Tab[/bold 157] cycles windows"
    + "[193 bold]CTRL_C[/bold 157] exits the program"
    + ""
    + ["New window", lambda *_: manager.add(window.copy().center())]
    + ["Close current", lambda _, button: manager.close(button.parent)]
    + ["Exit program", lambda *_: sys.exit(0)]
)

manager.add(window)
manager.run()

readme wm gif

Some screenshots

hello_world bezo calc

Documentation

As the project is in its infancy, dedicated documentation is not yet available.

If you are interested in help about anything the module provides, you can read its docstring:

)" ">
python3 -c "help(pytermgui.)"

However, proper documentation is coming once the API is stable.

Issues
  • Render plot correctly

    Render plot correctly

    I'm looking to create multiple plot trend lines inside of a tui.

    Another library I'm utilizing produces a string such as '⠀⠀⠀⠀⢠⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀\n⠀⠀⠀⠀⡸⡇⠀⠀⠀⠀⠀⠀⠀⠀⢠⢳⠀⠀⠀⠀⠀⣧⠀⠀⡤⡄⠀⠀⠀⠀\n⠀⠀⠀⢀⠇⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠘⡄⠀⠀⡀⢠⢻⠀⢀⠇⡇⠀⢀⠀⠀\n⡆⠀⢀⠎⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⡇⠀⠀⡇⢸⢸⠀⢸⠀⢇⠀⢸⠀⠀\n⢣⠀⡸⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢇⠀⢰⢇⢸⠈⡆⢸⠀⢸⠀⡿⡀⠀\n⢸⠀⡇⠀⠀⢸⠀⢀⠤⡆⠀⠀⠀⢸⠀⠀⢸⠀⢸⢸⢸⠀⡇⡎⠀⢸⠀⡇⡇⠀\n⠘⣼⠀⠀⠀⢸⠀⢸⠀⢱⠀⠀⠀⡎⠀⠀⢸⠀⢸⢸⢸⠀⡇⡇⠀⢸⢠⠃⡇⢀\n⠀⠇⠀⠀⠀⢸⠀⡎⠀⠘⡄⠀⢸⠀⠀⠀⠘⡄⡜⢸⢸⠀⢣⠇⠀⢸⢸⠀⡇⢸\n⠀⠀⠀⠀⠀⢸⠀⡇⠀⠀⡇⠀⡇⠀⠀⠀⠀⡇⡇⠸⡇⠀⢸⠀⠀⠀⣿⠀⢣⢸\n⠀⠀⠀⠀⠀⢸⠀⡇⠀⠀⢸⢰⠁⠀⠀⠀⠀⡇⡇⠀⡇⠀⠀⠀⠀⠀⡇⠀⢸⢸\n⠀⠀⠀⠀⠀⠸⣰⠁⠀⠀⠀⠇⠀⠀⠀⠀⠀⢳⠁⠀⠇⠀⠀⠀⠀⠀⠁⠀⢸⢸\n⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡸\n⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡇\n⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇\n⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃'

    When printed from the python interpreter this formats correctly

    ⠀⠀⠀⠀⢠⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⡸⡇⠀⠀⠀⠀⠀⠀⠀⠀⢠⢳⠀⠀⠀⠀⠀⣧⠀⠀⡤⡄⠀⠀⠀⠀ ⠀⠀⠀⢀⠇⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠘⡄⠀⠀⡀⢠⢻⠀⢀⠇⡇⠀⢀⠀⠀ ⡆⠀⢀⠎⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⡇⠀⠀⡇⢸⢸⠀⢸⠀⢇⠀⢸⠀⠀ ⢣⠀⡸⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢇⠀⢰⢇⢸⠈⡆⢸⠀⢸⠀⡿⡀⠀ ⢸⠀⡇⠀⠀⢸⠀⢀⠤⡆⠀⠀⠀⢸⠀⠀⢸⠀⢸⢸⢸⠀⡇⡎⠀⢸⠀⡇⡇⠀ ⠘⣼⠀⠀⠀⢸⠀⢸⠀⢱⠀⠀⠀⡎⠀⠀⢸⠀⢸⢸⢸⠀⡇⡇⠀⢸⢠⠃⡇⢀ ⠀⠇⠀⠀⠀⢸⠀⡎⠀⠘⡄⠀⢸⠀⠀⠀⠘⡄⡜⢸⢸⠀⢣⠇⠀⢸⢸⠀⡇⢸ ⠀⠀⠀⠀⠀⢸⠀⡇⠀⠀⡇⠀⡇⠀⠀⠀⠀⡇⡇⠸⡇⠀⢸⠀⠀⠀⣿⠀⢣⢸ ⠀⠀⠀⠀⠀⢸⠀⡇⠀⠀⢸⢰⠁⠀⠀⠀⠀⡇⡇⠀⡇⠀⠀⠀⠀⠀⡇⠀⢸⢸ ⠀⠀⠀⠀⠀⠸⣰⠁⠀⠀⠀⠇⠀⠀⠀⠀⠀⢳⠁⠀⠇⠀⠀⠀⠀⠀⠁⠀⢸⢸ ⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡸ ⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡇ ⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ ⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃

    However, when added to a pytermgui widget it does not render correctly.

    Is there any easy way to overcome this?

    Also -- thank you for sharing your library, it really is awesome.

    opened by Tbruno25 2
  • AttributeError: module 'signal' has no attribute 'SIGWINCH'

    AttributeError: module 'signal' has no attribute 'SIGWINCH'

    This module looks really cool, but running the example program (the one from README.md) gives the error,

    AttributeError: module 'signal' has no attribute 'SIGWINCH'
    

    I'm on Windows, which seems like it could be related, but README.md says it supports windows. Any ideas? TIA!

    bug 
    opened by fun840 2
Releases(v0.4.1)
  • v0.4.1(Sep 12, 2021)

    This release brings some more granularity to input handling for the Widget class.

    Overview

    • New Widget.handle_mouse
    • New Widget.handle_key
    • New Slider widget
    • Rethink Widget selection system

    Details

    Widget.handle_mouse(MouseEvent, MouseTarget) -> bool, Widget.handle_key(str) -> bool

    • These method should be called by the higher level implementation (HLI) after bindings are handled.
    • A return of True means the event has been handled, and the HLI should move onto the next one.

    Widget selection system

    • The old system was based on a static dict of "selectable" items, which was messy and hard to understand
    • This dict exists in a new form, a list[tuple[Widget, i]] which is generated every time it is required, allowing for variable selectables_length values
    • This is mostly a backend change, but it allows for a lot more customization.

    End result

    slider demo

    ok, bye :rocket:

    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Sep 5, 2021)

    Overview

    This version focuses on the window_manager submodule, and aims to be the final release before v1.0.0.

    • Improve mouse event API

      • Regex based mouse-code parsing
      • Decouple mouse clicking from finding mouse target
    • Rewrite window_manager

      • Frame-based, lazy renderer
      • Inactive window caching
      • Multi-threading

    Details

    New mouse event API

    • Instead of Widget.click(), a MouseTarget can be acquired using Widget.get_target()
    • This allows finding a Widget target without implicitly calling its click() method

    New window_manager

    • The old version was a weekend-project with not much thoughtful structure behind it
    • This version aims to be a fully integrated part of pytermgui, and the easiest starting point for usage of the module
    • The frame-based rendering currently defaults to 300 fps, the inaccuracy of time.sleep() messes with low framerates
    • The main thread handles input, while the secondary thread does all the printing:
      • Main thread: WindowManager.process_input()
      • Side thread: WindowManager._start_display_loop/_loop()
    • Windows can tell the WM to print by returning True in their bind() callbacks
    • For now, all input is handled globally by WindowManager. This is likely to change in the future.

    End result

    Nothing visual has changed in this patch, so have this nice screenshot of me testing ptg:

    ptg test

    ok, bye :rocket:

    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Aug 31, 2021)

    Overview

    This release focuses on the parser submodule, rebuilding it from the ground up and adding some much needed features & performance in the process.

    • New support for differing line-lengths in Splitter

    • Rewrite pytermgui.parser:

      • The module is now instance based, allowing for isolated macro & tag definitions
      • Much more robust markup parsing
      • Up to 60% speed improvement compared to 0.2.1
      • Better macro syntax: [!align]30:center text -> [!align(30,center)]text
      • Introduce cache system for MarkupLanguage.parse
      • Note: The WindowManager doesn't play too nicely with this system. This will be fixed next patch.
    • Improve terminal size handling:

      • Add new pytermgui.ansi_interface.terminal object
      • This object is used for getting size, width & height of the current terminal
      • Support subscribing to SIGWINCH event to gracefully handle resize events
    • Remove Widget focus system

      • This was introduced specifically for InputField, which no longer uses it
      • Widget.selected_index should be used instead

    Details

    parser.py & MarkupLanguage

    • The module provides a MarkupLanguage instance under the name markup, which is instantiated at import-time, which should be used for all global language settings & definitions
    • Additional instances may be created to host potentially unsafe macros or non-global aliases
    • Under the hood, there is a much more robust & performant set of tokenizer methods
    • Name changes:
      • define_tag() -> MarkupLanguage.alias()
      • define_macro() -> MarkupLanguage.define()
      • markup() -> MarkupLanguage.get_markup()
      • ansi() -> MarkupLanguage.parse()
      • tokenize_markup() -> MarkupLanguage.tokenize_markup()
      • tokenize_ansi() -> MarkupLanguage.tokenize_ansi()

    End result

    parser.py

    The speed difference can be showcased using a simple script to render a MarkupApplication window:

    import time
    import pytermgui
    from pytermgui.cmd import MarkupApplication
    
    formatter = pytermgui.MarkupFormatter("[60 bold]{item}")
    
    for obj in [pytermgui.Window, pytermgui.Container]:
        obj.set_style("border", formatter)
        obj.set_style("corner", formatter)
    
    manager = pytermgui.WindowManager()
    app = MarkupApplication(manager)
    window = app.construct_window()
    
    lines = window.get_lines()
    
    for _ in range(2):
        nums = []
        for i in range(100):
            start = time.time()
            window.get_lines()
            took = time.time() - start
            nums.append(took)
    
        print("avg:", sum(nums) / len(nums))
        print("max:", max(nums))
        print("min:", min(nums))
    

    Version 0.2.1: Screenshot 2021-08-31 at 20 25 48

    Version 0.3.0: Screenshot 2021-08-31 at 20 26 33

    If you do the math, 0.00418 / 0.00970 comes up to 43%, meaning the improvement in this scenario is around 55%. This can be even larger in more complex applications.

    Funnily enough, the aforementioned issue with WindowManager is that it cannot handle this speed. It gets greedy with frame-rendering, and overloads what my old MacBook can handle.

    ok, bye :rocket:

    Source code(tar.gz)
    Source code(zip)
  • v0.2.1(Aug 13, 2021)

    This patch removes all uses of syntax that broke Python versions before 3.9, and uses the from __future__ import annotations to further improve backwards compatibility. With this, the library is supported as far back as 3.7!

    ok, bye. :rocket:

    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Aug 12, 2021)

    Overview:

    This version primarily focuses on improving the included Widget subclasses and their interactions

    • New module: pytermgui.widgets.buttons to store all the Button subclasses

      • Button(Widget) : Same as before
      • Checkbox(Button) : A button with a checked and unchecked state, and toggles between them
      • Toggle(Button) : A button that toggles between two labels
      • Dropdown(Container) : Essentially a proof of concept. Will need support for printing lines at custom locations as overlays.
    • Rewrite Splitter:

      • Increase stability
      • Fix various spacing issues
      • Properly inherit from Container
      • 2 new auto() structures
      • Note: Currently Splitter-s only really work with widgets of the same height. Check below for more info.
    • Rewrite InputField:

      • Inherit from Label, use its get_lines()
      • Support keybinding (more info below)
      • Rework styles, support full line styles
    • Rewrite pytermgui.cmd:

      • Use WindowManager with a custom Application framework
      • Implement old utilities as Application subclasses
      • Support launching a specific app and dumping its output, as well as running as long as the User wants it to
    • Remove unmaintained widgets:

      • Some, like ProgressBar will come back in the future.

    Details:

    Widget.bind(key, action, description)

    Assign an action and a description to a dictionary of bindings, which is called by Widget._execute_binding(key).

    • key: str - The key that activates the binding
    • action: Callable[[Widget, str], Any] - The function that gets called
    • description: Optional[str] - Description of the binding
    • WindowManager implements & handles this, but otherwise the user should implement it.

    Splitter()

    • The auto() method now takes two new forms to create a Splitter()

      • {left: right} -> What used to be Prompt, run auto() on both sides and align them to the outside
      • (item1, item2, item3, ...) -> Shorthand for Splitter(item1, item2, item3, ...)
    • For now, you should use line lists of equal length to display multi-length Widget-s in Splitter.

    • As such, the itertools.zip_longest method is really useful for creating Splitter-s:

      from itertools import zip_longest
      from pytermgui import Container, Splitter
      
      left = ["one", "two", "three"]
      right = ["one", "two", "three", "four", "five"]
      
      container = (
          Container()
          + Splitter(
              *[{lhs: rhs for lhs, rhs in zip_longest(left, right, fillvalue="")]
          )
      )
      

    cmd.py rewrite

    • A cleaner implementation, including a whole Application framework
    • Better cli:
      • ptg -> open WindowManager with all the utilities, exit on CTRL_C
      • ptg --app {MarkApp, Getch} -> launch either of the utilities, dump their output once input has finished

    End result

    Combining all of the aforementioned changes, this is how ptg looks at the moment:

    end result

    ok, bye. 🚀

    Source code(tar.gz)
    Source code(zip)
  • v0.1.4(Aug 4, 2021)

    Overview:

    This version brings some great improvements for creating Widget-s, along with some utilities.

    • Improve method auto(): Better conversions & implicit calling by Container
    • Rewrite method WindowManager.run(): More modular, has better performance & stability
    • New method WindowManager.bind(key, action): Assign a callback for a keypress
    • New feature setting arbitrary attributes in Widget construction

    Details:

    auto()

    • Implicit calling whenever a non-widget is added to a Container (or subclass, as long as its _add_widget() method is not overwritten)
    • Better conversions: "text" -> Label("text") ["label", lambda target, button: ...] -> Button(label="label", onclick=lambda target, button: ...) ("Any", ["widget"], ...) -> Splitter(Label("Any"), Button("widget", onclick=None), ...)
    • Note: There are some additional conversion models, but they are likely to change in the next release.

    WindowManager.bind(key, action)

    • key - str: The trigger key
    • action - Callable[[WindowManager], Any]: The method to be called
    • Note: If the WindowManager could find & execute a binding it assumes the key to be handled, and does not do any built-in handling.

    Arbitrary attribute setting for Widget

    • Widget is now passed **attrs on construction
    • All of these attributes are applied using setattr(), regardless of their respective fields
    • These changes should be integrated into custom Widget subclasses:
    class MyWidget(Widget):
        def __init__(self) -> None:
            super().__init__()
    

    ⬇️

    class MyWidget(Widget):
        def __init__(self, **attrs: Any) -> None:
            super().__init__(**attrs)
    

    End result

    All of this combined results in the syntax for creating a simple window going from:

    root = Window()
    root.forced_width = 50
    root.id = "root"
    
    root += Label("[157 italic]This is a title")
    root += Label()
    root += Button("Exit", lambda *_: sys.exit()]
    

    To:

    root = (
        Window(forced_width=50, id="root")
        + "[157 italic]This is a title"
        + ""
        + ["Exit", lambda *_: sys.exit()]
    )
    

    Do note, however, that the traditional syntax is still available. For more information on this version, check out the linked commit.

    ok, bye :rocket:

    Source code(tar.gz)
    Source code(zip)
  • v0.1.3(Jul 26, 2021)

    This is the first tagged-release of this project, and it might be one of its most significant ever.

    The biggest addition is the new window_manager module, with all of its utilities. It provides:

    • The WindowManager class, with built in mouse handling & window management (duh)
    • The Window class, which can be used as a drop-in replacement to Container
    • A simple test window (DebugWindow) with some buttons, a mouse & a window debugger (MouseDebugger & WindowDebugger)
    • Many improvements to the mouse-handling API

    Please let me know if you have any issues with this version, or any ideas for the future! Thanks! 🚀

    Source code(tar.gz)
    Source code(zip)
Owner
super frictionless workflow.
null
A simple yet powerful TUI framework for your Python (3.7+) applications

A simple yet powerful TUI framework for your Python (3.7+) applications

null 179 Oct 25, 2021
Textual is a TUI (Text User Interface) framework for Python inspired by modern web development.

Textual is a TUI (Text User Interface) framework for Python inspired by modern web development.

Will McGugan 5.8k Oct 18, 2021
A library for building modern declarative desktop applications in WX.

re-wx is a library for building modern declarative desktop applications. It's built as a management layer on top of WXPython, which means you get all the goodness of a mature, native, cross-platform UI kit, wrapped up in a modern, React inspired API.

Chris 92 Oct 12, 2021
Dear PyGui: A fast and powerful Graphical User Interface Toolkit for Python with minimal dependencies

(This library is available under a free and permissive license however, if you Enjoy Dear PyGui please consider becoming a Sponsor) Dear PyGui is a si

Jonathan Hoffstadt 6.2k Oct 22, 2021
Dear PyGui: A fast and powerful Graphical User Interface Toolkit for Python with minimal dependencies

(This library is available under a free and permissive license however, if you Enjoy Dear PyGui please consider becoming a Sponsor) Dear PyGui is a si

Jonathan Hoffstadt 6.2k Oct 23, 2021
Write desktop and web apps in pure Python

Flexx Want to stay up-to-date about (changes to) Flexx? Subscribe to the NEWS issue. Introduction Flexx is a pure Python toolkit for creating graphica

flexxui 2.8k Oct 23, 2021
Write desktop and web apps in pure Python

Flexx Want to stay up-to-date about (changes to) Flexx? Subscribe to the NEWS issue. Introduction Flexx is a pure Python toolkit for creating graphica

flexxui 2.8k Oct 23, 2021
Turn (almost) any Python command line program into a full GUI application with one line

Gooey Turn (almost) any Python 2 or 3 Console Program into a GUI application with one line Support this project Table of Contents Gooey Table of conte

Chris 15k Oct 22, 2021
Edifice: a declarative GUI library for Python

Edifice is a Python library for building reactive UI, inspired by modern Javascript libraries such as React.

David Ding 78 Oct 25, 2021
Tukaan is the new framework that aims to replace Tkinter

Tukaan is the new, pythonic and colorful (like a keel-billed toucan) framework that aims to replace Tkinter. It has everything (on my computer, not at GitHub) that you need to develop cross-platform GUIs.

Tukaan 11 Oct 16, 2021
Yasb is a highly configurable and hackable taskbar written in python with Qt6.

Yasb: Yet Another Status Bar Yasb is a highly configurable and hackable taskbar written in python with Qt6. This project is still in (very) early deve

Dan 1 Oct 24, 2021
Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS

Kivy Innovative user interfaces made easy. Kivy is an open source, cross-platform Python framework for the development of applications that make use o

Kivy 13.6k Oct 22, 2021
Declarative User Interfaces for Python

Welcome to Enaml Enaml is a programming language and framework for creating professional-quality user interfaces with minimal effort. What you get A d

null 1.1k Oct 22, 2021
A little Python library for making simple Electron-like HTML/JS GUI apps

Eel Eel is a little Python library for making simple Electron-like offline HTML/JS GUI apps, with full access to Python capabilities and libraries. Ee

Chris Knott 4.6k Oct 22, 2021
Learn to build a Python Desktop GUI app using pywebview, Python, JavaScript, HTML, & CSS.

Python Desktop App Learn how to make a desktop GUI application using Python, JavaScript, HTML, & CSS all thanks to pywebview. pywebview is essentially

Coding For Entrepreneurs 34 Oct 8, 2021
Dress up your code with a beautiful graphical user interface !

Dresscode Dress up your code with a beautiful graphical user interface ! This project is part of the Pyrustic Ecosystem. Look powered by the cyberpunk

null 22 Sep 2, 2021
pyglet is a cross-platform windowing and multimedia library for Python, for developing games and other visually rich applications.

pyglet pyglet is a cross-platform windowing and multimedia library for Python, intended for developing games and other visually rich applications. It

null 950 Oct 22, 2021
Delphi's FireMonkey framework as a Python module for Windows, MacOS, Linux, and Android GUI development.

DelphiFMX4Python Delphi's FireMonkey framework as a Python module for Windows, MacOS, Linux, and Android GUI development. About: The delphifmx library

Embarcadero Technologies 29 Oct 14, 2021