Skip to content

Releases: bczsalba/pytermgui

v0.4.0: Better managed WindowManager

05 Sep 20:25
Compare
Choose a tag to compare

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 🚀

v0.3.0: The Mark(up) of the Snake!

31 Aug 18:32
Compare
Choose a tag to compare

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 🚀

v0.2.1: The Backwards Compatible update!

13 Aug 09:14
Compare
Choose a tag to compare

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. 🚀

v0.2.0: The Widget update!

12 Aug 21:49
Compare
Choose a tag to compare

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. 🚀

v0.1.4: Better syntax!

04 Aug 12:41
Compare
Choose a tag to compare

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 🚀

v0.1.3: Window Managers!

26 Jul 15:56
Compare
Choose a tag to compare

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! 🚀