August 11th, 2024

Things I've learned building a modern TUI Framework

The development of the Textual framework faces challenges with emoji rendering, emphasizing smooth animations, efficient data handling, and the use of `lru_cache` and the fractions module for performance and accuracy.

Read original articleLink Icon
FrustrationCuriosityNostalgia
Things I've learned building a modern TUI Framework

the terminal's handling of emojis is inconsistent and unpredictable. This has posed significant challenges for the development of Textual, particularly in ensuring proper formatting and rendering of text that includes emojis. The author shares several insights gained from building the Textual framework, emphasizing the importance of smooth animations, efficient data handling with Python's dict views, and the benefits of using immutable objects. The use of the `lru_cache` decorator is highlighted for its performance advantages, particularly in functions that are called frequently. The author also discusses the utility of the fractions module to avoid floating-point inaccuracies in layout calculations. Additionally, the article notes the value of using Unicode art for documentation clarity and the complexities involved in emoji support, which complicates text rendering due to varying character widths and multi-codepoint representations. Overall, the insights reflect a blend of technical challenges and solutions encountered in the development of a modern terminal user interface framework.

- Smooth animations in terminals can be achieved by overwriting content instead of clearing it.

- Utilizing `lru_cache` can significantly enhance performance for frequently called functions.

- Immutable objects simplify reasoning, caching, and testing in code.

- The fractions module helps avoid floating-point errors in calculations.

- Emoji support in terminals remains problematic due to inconsistent rendering and character width issues.

AI: What people are saying
The discussion around the Textual framework reveals several key themes and concerns among users.
  • Challenges with Unicode and emoji rendering are prevalent, with many developers sharing their frustrations and experiences.
  • There is a desire for more modern terminal interfaces that could simplify text formatting and improve usability.
  • Some users express skepticism about the reactive design approach of Textual, preferring more straightforward UI frameworks.
  • Accessibility issues are raised, particularly regarding animations and Unicode features that may hinder screen reader functionality.
  • Overall, there is a mix of interest and skepticism about the utility and future of TUI frameworks like Textual.
Link Icon 31 comments
By @rivo - 5 months
It's funny how every TUI developer eventually stumbles over Unicode and then handling international characters and emojis correctly turns into its own project close to the same scope of (or even bigger than) the original TUI project. It happened to me on rivo/tview and through the resulting rivo/uniseg package, I learned that all other TUI library maintainers deal with the same issues. Finally, everyone invents their own unique solutions to the problem because character width is not standardized and terminals are messy, as noted in the article. OP simply supports Unicode 9 only (Unicode is at version 15.1 at the moment). Sooner or later, users will complain, however, that certain emojis or international characters are not rendered correctly. So I'm not sure that this is a great solution.
By @traverseda - 5 months
My big complaint with textual is that it wants to be react. I can see why it would want to be react, that's a very popular framework that a lot of people are already familiar with, but I don't think it's actually a good way of doing user interfaces. But the basic reactive design is a well trod road, and basing your system design on something that's known to work is a great way to derisk the project. Sure, we'll draw some heavy inspiration from react.

Alright, so we're using some bastardization of CSS as well? That might be going a little bit too far. The react model already breaks the idea of CSS in a lot of ways, preferring standardized components. Sure, developers still use CSS to customize components, but I view that more as a side effect of how react evolved rather than as a justifiable architectural choice. But as long as you don't have to use CSS I suppose it's fine.

Last I tried it, you do have to use CSS. There are no good standard components, so you will be making your own, and instead of having components be one nice self encapsulated Python class the standard docs use things like list components and then style them with an external style sheet.

For those reasons textual just isn't for me yet. In python there should be one, and preferably only one, obvious way to do something. By mirroring react so closely they're also mirroring what I see as the JavaScript communities biggest vice.

By @bogdan-lab - 5 months
This TUI looks pretty, but I cannot imagine situation, when I would actually use it and be ready to pay for it. Probably I am not living in a right environment for it. But in my experience, either people are happy with something truly minimalistic or they try to please a user with GUI right away.

For example, YouTube link in the article showed a possibility to display table with highlighting cells. Why would I need that as TUI? Probably if I want to navigate through table with highlighting active cell I would also need a bunch of other stuff and eventually I would need a proper GUI.

By @emrah - 5 months
> The first trick is "overwrite, don't clear"

This is how games were written back in the day before DirectX was a thing. You'd write directly to the frame buffer and instead of clearing and redrawing, you'd redraw what changed and what was around and under it (because there was no time to refresh the entire view in time in addition to everything else you need to do)

By @mikkelam - 5 months
Why do software engineers care so much about TUI? I really don't get it. I love a good command line program. But TUI just doesn't appeal to me.
By @RunSet - 5 months
> I use monodraw for these diagrams. Monodraw is MacOS only unfortunately, but there are no doubt good alternatives for other platforms.

https://github.com/Nokse22/ascii-draw

By @lynx23 - 5 months
Anyone old and naiv enough to share this observation: Almost everything I looked at after TurboVision was inspired, but actually not really finished. Once you take the toolkit for a ride, you realize its kind of cute but unfinished. Maybe another way of looking at this is to call many of the TUI frameworks I say "opinionated", whatever that exactly means.

I am likely just dense and uncreative, but the truth is, when I switched from DOS to Linux in the 90s, I was never again as productive as I happened to be with B800. Granted, it likely took me a long time to understand the need for double buffering and the difference between a local/direct text mode vs a terminal, let alone escape sequences. But still. Whenever I tried to do something directly in ncurses, I pretty much gave up due to a distinct feeling of being unhappy. Completely different to what I was able to do with the simple ideal of B800.

By @ynniv - 5 months
If you're going to run kitty it can do a lot more than that: https://m.youtube.com/watch?v=ft1Q-DwGWIs

https://notcurses.com/

By @spencerchubb - 5 months
Interesting that they're hiring. I'm curious how they plan to make money from a TUI framework
By @tuieachtheirown - 5 months
FWIW, I evaluated a dozen compiled TUI libraries and found FTXUI to be the easiest to use and most reliable:

https://github.com/ArthurSonzogni/FTXUI

It's a nice tool to build interactive dashboards with both keyboard and mouse support.

By @superlopuh - 5 months
We use Textual as an interactive way to explore compilation pipelines in xDSL [0]. As our compiler is written in Python, it was the perfect tool to build a UI in the same language as the existing codebase. After starting the `xdsl-gui` project, we found Marimo [1], a reactive notebook for Python, which also lets users build apps in Python. It's been interesting to compare these two, especially in the way they handle state and propagate updates. For now we're using both tools, but it might make sense to centralise at some point. Both of these frameworks like immutable data structures, which I find is a positive incentive to use immutability throughout the code, and has been good for the rest of the project.

[0]: https://xdsl.dev/ [1]: https://marimo.io/

By @yoavm - 5 months
I've used Textual to build a quick Swedish-English dictionary that runs in terminal but also works using touch, when using the laptop as a tablet and on my Android phone. It was a pretty smooth experience and was very fast to get something working. The TCSS layout system thing is a little strange, but only because I automatically expected it to be CSS and obviously that doesn't really work in the terminal.

https://yoavmoshe.com/blog/learning-swedish-with-sway-and-an...

By @lofenfew - 5 months
> There is a heuristic where you write various sequences and ask the terminal for the cursor position, which should make an educated guess as the Unicode version. Unfortunately from testing we've discovered that terminals still render emoji unpredictably even if you think you know the Unicode database used.

Rather than using this as a heuristic, couldn't you simply use this whenever you need to determine the width of a string? Write it to the terminal, if it ends up going farther than you expected, then you need to wrap it somewhere. I used a trick like this at one point after I got annoyed with wcwidth.

By @zelphirkalt - 5 months
Is there any terminal, that throws out all the stone age terminal stuff, like weird codes for starting colored output and stopping colored output? A terminal, that instead makes use of some XML tags or so, to mark text in a readable fashion as prompt, input, output, maybe variables and such, colored and non-colored, etc., and then simply has some logic to render that XML correctly?

Of course that would lead to not recognizing weird color codes and so on, but perhaps there could be a plugin system, where one could add a plugin that transforms color codes and such in the output into XML tree representation.

And then one could perhaps log the whole thing in various formats: Only visible text with colors, without colors, whole XML tree, or as JSON, or whatever other format it translates well to. Also could be extended via plugin.

But bare bones it merely treats everything as text.

By @zokier - 5 months
Many people here mention SSH as one motivating uses for TUIs. It's a shame that we don't have standardized HTTP forwarding over SSH that would work as seamlessly as X forwarding can work.
By @gudzpoz - 5 months
> Emojis are terrible

Not just emojis. Recently I've had some fun trying weird Unicode characters in different terminals (e.g. 𒐫):

- QTerminal/Konsole: Tofu

- Xfce terminal: Results in overlaps with characters that comes after it.

- Alacritty: Similar to Xfce terminal, but glitches when the cursor/glyph moves.

- COSMIC term: No overlapping glyphs, except that the line then wraps only after it grows out of screen.

- Kitty/WezTerm: Scales the glyph to fit it into a single column. (Barely legible.)

I don't even known what to expect. It is indeed a mess over there.

By @kbouck - 5 months
This TUI discussion triggered a bit of 80s/90s programming nostalgia -- anyone remember TTT (TechnoJock's Turbo Toolkit)? Pre-gui era UI framework.
By @rtpg - 5 months
RE Fractions, for _many people_, decimal.Decimal will work very well.

For layout concerns, fractions are probably a more natural fit (things like 1/3), but Decimal is a great floating point default for a lot of problems. They aren't exact, but a lot of the "normal" floating point weirdness goes away and your results look a lot more human. Highly recommend if you don't have a perf reason not to.

By @DrMiaow - 5 months
Nice! I had no idea about "Synchronized output" mode.

I'm adding that to my CLI project. https://youtu.be/NxsaHxON350?si=319RyQPsb5AVDQj9

By @sam1r - 5 months
Thank you so much for making this. I use this quite regularly + rigorously for my own personal tracking, scheduling and much more.

Thanks to textualize, I can interact with the external world with full control in a black box.

By @troupo - 5 months
On emojis I highly recommend "Emoji under the hood" https://tonsky.me/blog/emoji/
By @miki123211 - 5 months
As a screen reader user, reading this post makes me want to scream.

If you care about accessibility even one bit, for the love of god, please, don't use any of the features this post mentions.

Things like animation or unicode diagrams break screen readers in horrible ways.

By @emrah - 5 months
In case you missed this: Casey vs Windows terminal https://news.ycombinator.com/item?id=27725559
By @qwerty456127 - 5 months
How comes we had perfect TUIs and no problems on DOS yet apparently always have some problems with TUIs on Linux?
By @cocodill - 5 months
Humanity needs more TUI tools
By @theanonymousone - 5 months
Isn't it written in 2022, hence requiring the year in the title?
By @hggh - 5 months
(2022)
By @snthpy - 5 months
I think this should include (2022) in the title.
By @frontporch - 5 months
> terminals are fast

their slow as hell and incredibly inefficient to build graphics on top of

> but many are powered by the same graphics technologies used in video games

what does that mean? obviously they have to render text to graphics at some point

> The first trick is "overwrite, don't clear". If you clear the "screen" and then add new content, you risk seeing a blank or partially blank frame for a brief moment.

> The second trick would be to write new content in a single write to standard output.

its just luck when any of these work

> The third trick would be to use the Synchronized Output protocol;

this could work...

> With these three tricks in place you can create very smooth animation as long as you can deliver updates at regular intervals. Textual uses 60fps as a baseline. Any more than that probably isnt going to be noticeable.

no, anything above 60hz will be noticeable more smooth and less laggy. also this isnt a question about bigger=better. youll notice jank even if you run 75fps on 60hz or 60fps on 75hz, and of course with unsynchronized rendering there will be jank and other artifacts even with Xfps on Xhz.

> Now that you can have smooth animation in the terminal, the question becomes should you?

nothing you demonstrated was smooth, just better than current broken (terminal) shit. and no, because the thing terminals miss out on is being able to scroll at just a constant pixel/time rate and actually follow the mouse precisely and without lag. as you can see in the video you cant do this because the terminal cant do this period because they can only move one font width/height per step. then if we actually explore this issue well soon find out a 125hz mouse polling rate adds tons of jank, and well discover 60hz is too slow to be smooth even with perfect sync, then that you need a CRT to have legible text at such a slow framerate. your video looks like a slightly faster windows 3.0. which is good for a terminal, but bad compared to what graphics could do even 20 years ago (at 60hz), no your not scrolling at 60hz, because thats impossible in the terminal.

none of this means i want what Windows / GTK / KDE / android / ... came up with where you press a button and the program then starts slowly animating something like its (ironically) a type writer instead of just doing what i said. you lose your position in scrolling because its done wrong, the "smooth scrolling" shit in firefox or whatever is just a poor (oblivious) attempt at the solution