- Während der Entwicklung von Textual zeigte sich, dass sich auch auf Basis alter Terminal-Protokolle flüssige TUI-Animationen umsetzen lassen, wenn Bildschirmaktualisierung und Ausgabesynchronisierung sorgfältig behandelt werden
- Um Flackern zu reduzieren, ist es wichtig, den Bildschirm nicht zu löschen, sondern zu überschreiben, auf die Standardausgabe in einem einzigen Schreibvorgang zu schreiben und das Synchronized-Output-Protokoll zu nutzen
- Die
dict.items()-Views von Python und@lru_cachesind nützlich, um Änderungen beim Rendering zu berechnen und kleine, sehr häufig aufgerufene Funktionen zu optimieren; die C-Implementierung von CPython unterstützt dabei die Cache-Performance - Nahezu unveränderliche Objekte, Unicode-Boxzeichnungsdiagramme und
fractions.Fractionerhöhen jeweils die Genauigkeit bei Schlussfolgerungen, Caching und Tests, beim Verständnis der Dokumentation sowie bei Layout-Berechnungen - Terminal-Emoji sind wegen Unterschieden bei Unicode-Versionen, Zeichenbreite und Rendering mit mehreren Codepoints schwer vorherzusagen; in der Praxis sind einfachere Emoji auf dem Stand von Unicode 9 stabiler
Terminal-Animationen und Flackerkontrolle
- Moderne Terminal-Emulatoren laufen auf alten Protokollen, doch viele Implementierungen verwenden Grafiktechniken, die auch in Videospielen eingesetzt werden
- Visuelle Effekte im Terminal neigen leicht zu Flackern oder Tearing, aber unter einigen Bedingungen lassen sich flüssige Animationen umsetzen
- Die erste Methode zur Verringerung von Flackern ist nicht „clear und dann draw“, sondern das Überschreiben vorhandener Inhalte
- Wenn der Bildschirm gelöscht und dann neuer Inhalt hinzugefügt wird, können kurzzeitig leere Frames oder nur teilweise gezeichnete Frames sichtbar werden
- Die zweite Methode ist, Aktualisierungen mit einem einzigen write auf die Standardausgabe zu schreiben
- Werden mehrfach
file.write-Aufrufe ausgeführt, können Teilaktualisierungen für Nutzer sichtbar werden
- Werden mehrfach
- Die dritte Methode ist die Verwendung des Protokolls Synchronized Output
- Wenn der Beginn und das Ende eines Frames dem Terminal mitgeteilt werden, kann das Terminal dies für flackerfreie Aktualisierungen nutzen
- Textual orientiert sich an 60 fps; darüber hinaus ist der sichtbare Unterschied möglicherweise nicht groß
- Nicht jede Animation ist gleich wertvoll
- Ein von links hereingleitender Sidebar-Effekt sieht gut aus, bringt für die Nutzererfahrung aber möglicherweise keinen echten Vorteil
- Sanftes Scrollen hilft dabei, in langen Texten die aktuelle Position zu behalten
- Textual will deshalb einen Mechanismus vorsehen, um bestimmte Animationen zu deaktivieren
Render-Änderungen mit DictView finden
- Die Python-
dict-Methodenkeys()unditems()geben jeweilsKeysViewundItemsViewzurück und bieten eine Schnittstelle ähnlich zu Mengen - Der Layout-Prozess von Textual erzeugt eine Render-Map, also eine Zuordnung von Widgets zu Bildschirmpositionen
- In der früheren Methode wurde der gesamte Bildschirm ineffizient aktualisiert, selbst wenn sich nur die Position eines einzigen Widgets geändert hatte
- Mit der symmetrischen Differenz zweier
ItemsViews lassen sich neue oder geänderte Einträge ermitteln- Diese Operation wird auf C-Ebene verarbeitet
- Wenn sich CSS-Eigenschaften ändern, nutzt Textual dies, um veränderte Bildschirmbereiche zu finden und für optimierte Updates zu verwenden
lru_cache und die Optimierung kleiner Funktionen
@lru_cacheaus der Standardbibliothekfunctoolsist ein Dekorator zum Cachen von Funktionsrückgaben- Mit
maxsizelässt sich verhindern, dass der Cache unbegrenzt wächst - Das
lru_cachevon CPython verwendet eine C-Implementierung und ist sowohl bei Cache-Treffern als auch bei Fehlzugriffen schnell - In Textual gibt es kleine Funktionen, die zwar nicht langsam sind, aber sehr häufig aufgerufen werden, und viele davon eigneten sich gut für Caching
- Typischerweise reichte eine Einstellung von
maxsizeauf etwa 1000 bis 4000 aus, damit die meisten Aufrufe Cache-Treffer wurden - Bei der Nutzung eines Caches sollte
cache_info()geprüft werden- Bei einem effektiven Cache sollte man erwarten, dass
hitsschneller steigen alsmisses
- Bei einem effektiven Cache sollte man erwarten, dass
- Die
intersection-Methode zur Berechnung der Überlappung zweier rechteckiger Bereiche ist für sich genommen nicht aufwendig, wird aber tausendfach aufgerufen und profitiert daher vom Caching
Die Einfachheit unveränderlicher Objekte
- Python kennt keine wirklich unveränderlichen Objekte im strengen Sinn, aber mit Tuple,
NamedTupleund frozen dataclasses lassen sich viele Vorteile erzielen - Die Einschränkung, ein Objekt nicht verändern zu können, mag willkürlich wirken, ist im echten Code aber oft hilfreich
- Code in Textual, der unveränderliche Objekte verwendet, ist leichter zu durchdenken und vorteilhaft für Cache und Tests
- Der wichtigste Grund ist, dass sich so leichter Code ohne Seiteneffekte schreiben lässt
- Übergibt man Klasseninstanzen an Funktionen, ist es schwieriger, Seiteneffekte auf dieselbe Weise zu vermeiden
Unicode-Art und Dokumentation
- Wenn sich technische Inhalte nur schwer mit Worten erklären lassen, helfen mit Unicode-Boxzeichen erstellte Diagramme sehr beim Verständnis der Dokumentation
- In den Docstrings von Textual werden Boxzeichnungsdiagramme verwendet, um eine Methode zu erklären, die einen Bereich in vier Teilbereiche aufteilt
- Solche Diagramme ersetzen keine gut geschriebenen Docstrings, können das Verständnis aber in Kombination deutlich verbessern
- Als Werkzeug zum Erstellen der Diagramme wird monodraw verwendet
- Monodraw ist nur für macOS verfügbar
- Für andere Plattformen könnte es Alternativen geben
Weniger Layout-Fehler mit fractions.Fraction
- Das
fractions-Modul der Python-Standardbibliothek gibt es seit Python 2.6 Fractionist eine andere Art, Zahlen darzustellen, und kann nach der Erstellung anstelle von Float verwendet werden- Gleitkommazahlen haben bekannte Grenzen
- Zum Beispiel ist
0.1 + 0.1 + 0.1 == 0.3False
- Zum Beispiel ist
- In Textual verursachen Rundungsfehler von Gleitkommazahlen Probleme bei Layouts, die den Bildschirm in mehrere Verhältnisse aufteilen
- Wenn ein Drittel der Bildschirmbreite für ein Panel verwendet und die verbleibenden zwei Drittel erneut aufgeteilt werden, kann eine Lücke von einem Zeichen entstehen
- Wenn Float durch
Fractionersetzt wird, lassen sich solche Rundungsfehler vermeiden- Die Summe von drei
Fraction(1, 10)ist gleichFraction(3, 10)
- Die Summe von drei
- In einem Beispiel, in dem 24 Zeichen in 7 Teile aufgeteilt werden, fehlte in der Float-Version ein Zeichen, während die Fraction-Version die gesamte Länge ausfüllte
Die Schwierigkeiten mit Terminal-Emoji
- Die Unterstützung von Emoji ist seit den Anfängen von Rich ein fortlaufendes Problem, und Textual hat dasselbe Problem übernommen
- Im Terminal belegen Zeichen normalerweise eine oder zwei Zellen, manche Zeichen haben eine Breite von 0
- CJK-Zeichen beanspruchen doppelt so viel Platz wie westliche Alphabetzeichen
- Für grundlegende Formatierungen wie Zentrierung oder das Zeichnen von Boxen muss die Terminalbreite des auszugebenden Textes bekannt sein
- Deshalb lässt sich die im Terminal belegte Breite nicht allein mit
len(text)bestimmen
- Die Unicode-Datenbank enthält Zuordnungen für einfach breite und doppelt breite Zeichen
- Rich und Textual schlagen für jedes auszugebende Zeichen in dieser Datenbank nach
- Diese Arbeit ist nicht leichtgewichtig, lässt sich aber mit Engineering und Caching ausreichend schnell machen
- Auch wenn Emoji in der Unicode-Datenbank enthalten sind, werden mit jeder neuen Unicode-Version neue Emoji hinzugefügt, was die Ergebnisse schwer vorhersagbar macht
- Gibt man neue Emoji im Terminal aus, können sie mit einer oder zwei Zellen dargestellt werden
- Es kann auch sein, dass sie gar nicht korrekt gerendert werden
- Es gibt keine standardisierte Möglichkeit, die vom Terminal-Emulator verwendete Unicode-Version zuverlässig zu erkennen
- Es gibt auch keine Standard-Umgebungsvariable
- Es existieren Heuristiken, bei denen bestimmte Sequenzen ausgegeben und Cursorpositionen abgefragt werden; in Tests renderte das Terminal Emoji jedoch unvorhersehbar, selbst wenn man annahm, die Unicode-Datenbank zu kennen
- Emoji mit mehreren Codepoints machen das Problem noch komplizierter
- Zum Beispiel besteht 👨🏻🦰 aus 4 Codepoints
- Manche Terminals rendern dies als 4 Zeichen, 2 Zeichen, 1 Zeichen, eine oder zwei Zellen oder als 4
?
- Praktisch betrachtet sind Emoji aus Unicode 9 auf verschiedenen Plattformen vergleichsweise stabil
- Neuere Emoji und Emoji mit mehreren Codepoints sollte man besser vermeiden, auch wenn sie im aktuellen Terminal gut dargestellt werden
Textualize stellt ein
- Textualize sucht Mitarbeitende, um ein TUI-Framework zu entwickeln, das Teile der Aufgaben eines Browsers ersetzen kann
- Stellenangebote finden sich unter Jobs
1 Kommentare
Hacker-News-Kommentare
Es ist interessant, dass alle TUI-Entwickler irgendwann auf Unicode stoßen und dass die korrekte Behandlung internationaler Zeichen und Emojis zu einem separaten Projekt wird, das ähnlich groß oder sogar größer ist als das ursprüngliche TUI-Projekt.
Bei rivo/tview war es genauso, und durch das daraus entstandene Paket rivo/uniseg wurde deutlich, dass auch die Maintainer anderer TUI-Bibliotheken mit demselben Problem kämpfen.
Letztlich ist die Zeichenbreite nicht standardisiert und Terminals sind kompliziert, deshalb erfindet jeder seine eigene Lösung. Der Autor des Beitrags unterstützt nur Unicode 9, während Unicode aktuell bei 15.1 ist; daher ist schwer zu sagen, ob das eine gute Lösung ist, weil irgendwann Beschwerden kommen werden, dass bestimmte Emojis oder internationale Zeichen falsch gerendert werden.
Ich pflege zwei TUI-Bibliotheken, die diese Technik verwenden, und die Emoji-Unterstützung war nahezu hervorragend. Eine davon nutzt außerdem die uniseg-Bibliothek.
https://mitchellh.com/writing/grapheme-clusters-in-terminals
Meine größte Beschwerde über textual ist, dass es wie React sein will. Ich verstehe, warum: Es ist ein populäres Framework, mit dem viele vertraut sind. Aber ich halte das nicht für eine gute Art, Benutzeroberflächen zu bauen.
Reaktives Design an sich ist ein bewährter Weg, und das Systemdesign auf Methoden zu stützen, von denen bekannt ist, dass sie gut funktionieren, ist eine gute Möglichkeit, das Projektrisiko zu senken.
Aber dann auch noch eine CSS-Variante zu verwenden, wirkt übertrieben. Das React-Modell bevorzugt standardisierte Komponenten und hat das Konzept von CSS in vielerlei Hinsicht bereits untergraben; dass Entwickler Komponenten per CSS anpassen, wirkt eher wie ein Nebeneffekt der React-Evolution als wie eine solide Architektur.
Als ich es zuletzt ausprobiert habe, musste man CSS verwenden, und es gab keine guten Standardkomponenten, sodass man sie selbst bauen musste. Statt dass eine Komponente eine sauber gekapselte Python-Klasse ist, zeigte die offizielle Dokumentation, wie man eine Listenkomponente erstellt und sie über ein externes Stylesheet gestaltet.
In Python sollte es einen offensichtlichen Weg geben, etwas zu tun – möglichst genau einen. Indem man React zu eng folgt, scheint man auch die schlimmsten Unsitten der JavaScript-Community zu übernehmen.
Auch die Aussage, es gebe keine guten Standardkomponenten, scheint schon eine Weile nicht mehr überprüft worden zu sein: https://textual.textualize.io/widget_gallery/
Ich musste auch keine eigenen Komponenten bauen, und CSS kannte ich bereits. Eher haben mich gelegentlich die kleinen Layout-Unterschiede und die Abweichungen von CSS verwirrt.
Man kann es sich so vorstellen, dass eine Komponente in einem normalen GUI-Fenster ein Terminalfenster ist. Ich frage mich, ob es so etwas gibt oder ob man einfach eine Textansicht mit Festbreitenschrift verwenden sollte.
Diese TUI ist hübsch, aber ich kann mir schwer vorstellen, dass ich sie tatsächlich nutzen und sogar dafür bezahlen würde. Vielleicht bin ich einfach nicht in der passenden Umgebung, aber meiner Erfahrung nach sind Leute entweder mit wirklich minimalistischen Tools zufrieden oder versuchen gleich, Nutzer mit einer GUI zufriedenzustellen.
Der YouTube-Link im Artikel zeigte zum Beispiel eine Tabelle, in der Zellen hervorgehoben werden; ich verstehe nicht, warum man das als TUI machen sollte. Wenn man die hervorgehobene aktive Zelle verschieben muss, braucht man am Ende viele weitere Funktionen, und dann wird man wohl doch eine richtige GUI brauchen.
Allerdings bin ich mir nicht sicher, was eine „richtige GUI“ sein soll. Für Terminals gibt es weit verbreitete offene Standards, und diese Protokolle sind standardisierter und interoperabler als jedes GUI-Framework und werden auf allen Systemen unterstützt.
Wenn hier Elemente wie Video hinzukommen und einige Lücken geschlossen werden, wirkt es durchaus plausibel, mit dem Browser zu konkurrieren oder die gesamte Computing-Erfahrung ins Terminal zu holen.
Natürlich würde ein Terminal, das Video hinzufügt, die Rendering-Pipeline einer „richtigen GUI“ nachahmen; es wäre aber eine Verbesserung der Terminal-Erfahrung, und bei großem Erfolg könnte man es auch so sehen, dass es UNIX-artige Einfachheit in GUIs bringt.
Die Mitarbeiter arbeiten mit Tastaturkürzeln äußerst effizient, und jedes Mal, wenn jemand versucht, AS400 durch eine moderne Web-App zu ersetzen, sinkt die Produktivität um den Faktor 100.
Es ist dieselbe Situation wie der Unterschied zwischen Vim/Emacs-Power-Usern und einer GUI, die zwar glatt aussieht, aber keine Tastaturkürzel hat.
Man könnte Nutzer per SSH auf einen Server verbinden lassen und die TUI-App wie eine Shell verwenden; ich gebe zu, dass das ein etwas konstruiertes Szenario ist.
Persönlich fand ich Textual in der Nutzung etwas seltsam, aber besser als ncurses. Allerdings lieferte es nicht das gewünschte Ergebnis. Alte mainframe-artige TUI-Anwendungen fühlten sich enorm effizient an.
Manchmal funktioniert es gut, aber nur in begrenzten Fällen, etwa bei X-Forwarding über eine Verbindung mit hoher Bandbreite. In anderen Fällen wird die GUI völlig träge.
Eine textbasierte GUI scheint die Vorteile beider Seiten mitnehmen zu können.
Der Ansatz „Der erste Trick ist: nicht löschen, sondern überschreiben“ ist derselbe, mit dem Spiele vor DirectX geschrieben wurden.
Man schrieb direkt in den Framebuffer und zeichnete statt alles zu löschen und neu zu zeichnen nur die veränderten Bereiche sowie das Drumherum und Darunter neu. Denn es blieb nicht genug Zeit, den ganzen Bildschirm rechtzeitig zu aktualisieren.
Die erste bestand darin, in einen anderen Buffer zu schreiben und am Ende des Frames den gesamten Buffer auf einmal zu kopieren, sodass jedes Pixel nur einmal geändert wurde. Dieser Buffer konnte normalerweise im normalen RAM statt im Video-RAM liegen.
Die zweite bestand darin, in einen anderen Buffer im Video-RAM zu schreiben und Register der Grafikhardware zu ändern, damit die Pixel, die auf dem Monitor angezeigt werden, aus diesem Buffer erzeugt werden.
Die Trade-offs waren unterschiedlich. Das Kopieren des gesamten Buffers war teuer, das Ändern von Adressregistern war billig. Die Details der Register konnten jedoch hardwareabhängig sein, und ein richtiges Grafiktreiber-Framework gab es auch nicht.
Für einfaches „Buffer-Flipping“ musste man die Pixel auch beim Rendern in den Offscreen-Buffer ins Video-RAM schicken, und soweit ich mich erinnere, waren Zugriffe auf Video-RAM langsamer als auf normalen RAM. Im Grunde war das eine NUMA-Struktur; je nachdem, wie oft Pixel überzeichnet wurden, konnte Rendering im normalen RAM insgesamt schneller sein, selbst wenn man die abschließende Kopie einrechnete.
Allerdings halte ich die Fähigkeit, dem Terminal Anfang und Ende eines Frames mitzuteilen, für deutlich mächtiger.
Ich verstehe wirklich nicht, warum Software Engineers so sehr an TUIs interessiert sind. Gute Kommandozeilenprogramme mag ich, aber TUIs reizen mich nicht.
Dazu kommt ein Hacker-Flair. Stil ist nicht stark vom Inhalt getrennt, und die anpassbaren Stiloptionen sind begrenzt, sodass TUI-Apps im Allgemeinen zu einem einheitlichen Stil konvergieren. Dieser Stil erinnert an Hackerfilme und coole Science-Fiction.
Es ist auch nicht der Bereich, den Corporate Designer mögen, und Unternehmen schließen keine Verkäufe an Firmen oder Konsumenten auf Grundlage einer TUI ab. Weil dieser kommerzielle Druck fehlt, werden TUIs von Entwicklern für Entwickler entworfen.
Deshalb verstärkt sich das TUI-Meme selbst. Entwickler sehen und nutzen TUIs, merken, dass es in der Regel Tools sind, die mit Entwicklern im Sinn gebaut wurden, und wollen dann wieder ihre eigene TUI bauen.
Plattformübergreifende Unterstützung gibt es fast gratis, man muss keine pixelperfekte GUI bauen, was ohnehin nicht nötig und von vornherein unmöglich ist, und man braucht keine Icons oder Grafiken, weil man Grafiken nicht anzeigen kann.
Man kann weiterhin den Terminal-Emulator mit der bevorzugten Schriftart verwenden und trotzdem eine GUI-artige Oberfläche haben.
Alle TUI-Apps, die ich bisher genutzt habe, hatten niedrige Antwortzeiten. GUI-Apps sind zwar meist ebenfalls schnell, aber alle Apps, die frustrierend langsam waren, waren GUI-Apps.
Als Alternative zu Monodraw gibt es ascii-draw.
https://github.com/Nokse22/ascii-draw
Fast alles, was ich nach TurboVision gesehen habe, war zwar inspiriert davon, aber in der Praxis nicht wirklich ausgereift. Wenn man die Toolkits richtig ausprobiert, sind sie zwar nett, fühlen sich aber unfertig an
Anders gesagt könnte man auch sagen, dass viele TUI-Frameworks, die ich gesehen habe, sehr „opinionated“ waren
Vielleicht bin ich einfach schwerfällig und unkreativ, aber seit ich in den 90ern von DOS zu Linux gewechselt bin, war ich nie wieder so produktiv wie damals bei der Arbeit mit B800
Es ist gut möglich, dass ich lange gebraucht habe, um die Notwendigkeit von Double Buffering, den Unterschied zwischen lokalem direktem Textmodus und Terminal sowie Escape-Sequenzen zu verstehen. Trotzdem habe ich jedes Mal, wenn ich versucht habe, mit ncurses selbst etwas zu machen, wegen eines deutlichen Gefühls der Unzufriedenheit fast aufgegeben. Es war etwas völlig anderes als das, was mit dem einfachen Ideal von B800 möglich war
Vielleicht war mein Blickfeld so stark eingeschränkt wie eine ganze Stadt, aber GUI-Frameworks nach der Message Pump waren für mich allesamt kaum zu verstehen
Insbesondere habe ich nicht verstanden, wie objektorientierte Frameworks das Problem von Multithreaded UIs tatsächlich lösen. In Win32 habe ich den Haupt-Renderer in einen Thread gelegt, während Neben-Renderer parallel Modelle erstellt und anschließend nur die Unterschiede aktualisiert haben. Der Code wurde dadurch nicht besonders unbeherrschbar groß
Wenn man kitty ausführt, kann man noch viel mehr machen: https://m.youtube.com/watch?v=ft1Q-DwGWIs
https://notcurses.com/
Beide können mit demselben Protokoll Grafiken anzeigen. Wenn man wirklich Grafik braucht, ist ein Terminal fast nie die richtige Lösung. Für kleine Dinge wie Icons ist es aber gelegentlich nützlich
Interessant, dass sie einstellen. Ich frage mich, wie sie mit einem TUI-Framework Geld verdienen wollen
Zur Info: Ich habe etwa ein Dutzend kompilierbare TUI-Bibliotheken evaluiert, und FTXUI war am einfachsten zu verwenden und am stabilsten
https://github.com/ArthurSonzogni/FTXUI
Ein gutes Werkzeug, um interaktive Dashboards mit Tastatur- und Mausunterstützung zu bauen