Python TUI framework with mouse support, modular widget system, customizable and rapid terminal markup language and more!
Really small update this time, mostly dealing with long-standing issues. Haven't had the time to work on any programming for a minute (a multiple-month spanning minute, at that), so apologies for it taking so long.
inline
widget referring to hover
mouse mode🚀
Minor release to allow building our documentation.
Remove (undocumented) shortening behaviour from Button
(#124)
Normally this would be considered an API breaking change, but since it was never documented (and didn't even work properly in most scenarios) it's only a minor-level change.
The standout feature from this update is finally getting windows mouse support working, courtesy of @Tired-Fox!
getch
WindowManager
programatically by not blocking on getch
callsLots and lots of things have happened since my previous time writing a release like this. It will all make sense soon (you can check my PyPi for a sneak peak!), but for the time being there's some changes to document.
Add meta tokens for saving & restoring styles (bcf0a2ef091175407316204c2408e3c208cd0312)
This adds the new meta tokens #stash
, #pop
and their clearing-variants #stash/
and #/pop
, which allow you to save a snapshot of the currently applied styles and restore it at a later point within the same markup.
It can be super useful for when you want to insert special styles for a single word.
[bold 141]This is an example with a [#stash/ @black grey]code-snippet [#/pop] in the middle.
Add support for alt/ctrl+backspace to remove whole words (@i404788, #109)
Add support for word move actions (Ctrl/Alt+<L>/<R>) & Home/End move (@i404788, #109)
The primary focus for this release is the new documentation, but there is a lot of minor changes that slipped through in the meantime. The new documentation is great; it has much more in-depth content on most parts of the library, it looks amazing and it's a lot less of a hassle to maintain! It also has build-time screenshot generation using this very module, so all the SVGs will always remain up to date.
SHIFT+
scroll events (74722db6787b733b75e3515dfc086cf0338235b8)Palette.print
(06f2ec78d1b31d2a059035e72e8d1b014c929b81)inline
widget runner (ba19d36f51f5b16c9871070953fa348b6553504f)InputField
cursor (7281c5716e5662d1cfa50d11c5da50d8f3e44d28)New MkDocs
based documentation
While the previous documentation was alright, it missed a lot of things. MkDocs
makes it a bit easier to create step-by-step style documentation pages, which was exactly what we needed. We also made use of Termage to make it a lot more dynamic.
Change terminal.py
-> term.py
and serializer.py
-> serialization.py
to avoid naming conflicts (e38603ee390141dcdb7b0d090f8684f4e331b68f)
Improve pseudo token behaviour by parsing it as a new token type (f19b897ba99c8270c71e6f34c30e2a33ef4390f9)
Start generating semantic colors (success, warning, error) by blending with the primary (2daee90a3abe47aa632a7ca876151062946f0784)
Remove is_bindable
widget attribute (866cd0951d3f3471ae0e62631ba59834912fd8e1)
This was only used as an arbitrary limitation, one that was not needed in the library.
This version brings the long-awaited (at least by myself) color palette system!
Before that, the new #auto
TIM pseudo-tag deserves a mention. It makes the parser look at the currently applied background color, and it is replaced with a color that properly contrasts it while parsing! This way, [alias #auto]Text
, will always be legible, regardless of the meaning of alias
. It is also automatically used by all widget styles (whenever there is no foreground specified), so you can just define a style as @surface+1
, and PTG will make sure it looks nice and readable!
Anyways, the palette. Here is the gist of it:
palettes
provides a Palette
classptg.palette
and in markupTIM gains the following sets of new aliases:
primary
secondary
tertiary
accent
surface
surface2
surface3
surface4
success
warning
error
Each color has 7 shades, and each shade a foreground and background variant. The base color (i.e. with no shade modifications) use the name as written above, and every other alias is defined as {name}{+/-}{shade_amount}
, like primary-3
for the darkest shade of primary, and
surface+2for the second-brightest surface shade. Each alias comes complete with a background variant bound to
@{alias}`, such as @secondary+2
.
It's easier to show than tell, so here is the new default palette, as exported by ptg --palette --export-svg <filename>
:
#auto
TIM pseudo-tag that always gives properly contrasted foreground textpalettes
module for framework-wide color generation & configurationSynchronized Output
supportFancyReprWidget
ptg --palette
flagparsing.eval_alias
& MarkupLanguage.alias
ptg
program & all builting widgets use the global paletteansi-
Here is my home-grown project launcher, lens
, using the new default color palette (and an upcoming new button widget, but that's for the next release notes):
...and now, using it's custom-defined palette, using the primary color #58A46F
:
Here is ptg
in the new default color palette:
...and now with a bunch of randomly generated ones:
This minor release mostly aims to fix up some issues caused in previous releases, as well as tighten up InputField
cursor behaviour.
Note that the release size of the project became enormous since the move to pyproject.toml
for building. This should be resolved by the next update.
InputField
prompt attribute (#90)ptg --version
(#89)InputField
cursor when it goes outside of the given width (634c1b5eb13e58ece182dee0c34555939fcb5e1e)KeyboardButton
label generation (#88)Here is a sneak-peak of the upcoming update to the file-definition system, which will add things like selector-based targeting, native markup support and more!
This release brings completely re-written TIM implementation to the library. It is significantly faster (~2x for markup without macros, >10x for markup with macros) than the previous version, and it's a lot easier to maintain and improve upon.
The previous hyperlink syntax ([!link(https://example.org)]Example site[/!link]
) is now deprecated in favour of a new, much simpler one:
[~https://example.org]Example site[/~]
There have also been a couple of changes to SVG exports to make them more accurate and aesthetically pleasing. We also support the inverse
style for them as well!
Changes marked in bold are API-breaking.
ignore_any
parameter to Widget.execute_binding
(68866e63fcba0dd36f5a1165966cb5b2377bc43b)InputField
handling clicks & drags started outside of it (#72, #75)CTRL_C
not killing compositor thread by making it a daemon (#78)widget.positioned_line_buffer
being duplicated before and after vertical alignment (#70)pyproject.toml
-based builds (#82)visibility=hidden
in SVG exports (d070724a591f6c0f62016944e2d49ed1382d9805)get_applied_sequences
helper function (a7d41bd819abd1ea481dc536090199a98c9330e7)Type | Change | Alternative | Comment |
---|---|---|---|
Removal | get_applied_sequences |
A custom tokenization based implementation | This function was no longer used internally, and if someone needed it the new tokenizers are a much more performant and smart way to go about implementing it. |
Removal | MarkupLanguage.prettify_markup |
highlight_tim |
Using regex-based highlighting allows the output markup to be completely identical char-by-char to the source, whereas the previous token based implementation had a tendency to change things around. It's also much less LOC and faster. |
Deprecation | Markup hyperlink syntax !link(https://example.org) |
The new ~https://example.org syntax |
Previously hyperlinks were implemented as macros, but they were very messy under the hood. A first-class syntax for them is vastly superior. |
Refactor | The role of StyledText |
N/A | Previously, StyledText was meant to be barely detectable when used. It was returned in various places, but wanted to not be a thing you thought about often. Now it's on an opt-in basis, and it no longer tries to pass as a str . It's immutable, and is only meant to be generated by MarkupLanguage.group_styles or StyledText.group_styles . You can also now create them from markup, not just ANSI-coded text. |
Note that since the above image, tim-v3
went down to ~180-190 ns per parse
This release brings the ability to use on_{mouse_event}
functions to handle specific events, instead of having to sort manually within handle_mouse
. This is done within Widget
's handle_mouse
method, so it will be implemented by all widgets that call super().handle_mouse
before doing their own handling. It also improves general mouse handling behaviour, and makes it all a bit more predictable.
There is a minor change in return-value semantics as well. Previously, handle_mouse() == True
stood for "I was able to handle this event, do not bubble it up". This wasn't very specific, and there were some special cases where undefined behaviour would occur. It has now been changed to "I want to handle more mouse events, even if they aren't directly targeting me". This essentially allows a widget to declare itself "sticky" for certain mouse events, and "unsticky" when it is no longer needed.
Button
(dcf5b0d8f2981211a33aa30f12009ad0ddb371e3)Improve mouse input cascade logic (ae971020fe6090eaa3c80cecd3b6d9d60f87ef64)
Implement semantic mouse handlers (f11f524a31375b4ccb19e7bbff96395bcf38e8f8)
All widgets now have some "implicit" callbacks for each specific mouse event. They are only called when defined, so it is fully backwards compatible. They all follow the syntax of:
on_{event}
Where event
can be any snakeified mouse action name. For example, LEFT_CLICK
calls on_left_click
, and SCROLL_UP
calls on_scroll_up
. For most events, there is a lesser layer of specificity allowed. For example, you usually want to handle both scroll directions with the same function. Because of this, we also allow defining on_scroll
to deal with both events. Whenever multiple specificity level handlers are defined for the same action, the most specific will be used. For example, a widget with both on_drag
and on_left_drag
will invoke on_drag
for RIGHT_DRAG
, and on_left_drag
for LEFT_DRAG
.
Here is a little mouse handling demo I created while writing this version. All mouse actions are defined with custom on_{event}
handlers, where both drag events return True, and everything else False. This makes those events stick to the tile that started handling them.