You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Textual has been a constant source of programming challenges, often frustrating but never boring.
21
+
The challenges arise because the terminal "specification" says nothing about how to build a modern User Interface on top of it.
22
+
The building blocks are there.
23
+
After some effort you can move the cursor, write colored text, read keys and mouse coordinates, but that's about it.
24
+
Everything else we had to build from scratch: from the most basic [button](https://textual.textualize.io/widget_gallery/#button) to a syntax highlighted [TextArea](https://textual.textualize.io/widget_gallery/#textarea), and everything along the way.
25
+
26
+
I wanted to write-up some of the more interesting solutions we came up with.
27
+
The 1.0 milestone we just passed makes this the perfect time.
28
+
29
+
If you haven't followed along with Textual's development, here is a demo of what it can do.
30
+
This is a Textual app, running remotely, served by your browser:
I cheaped out on the VMs — so if the demo is down, you could run it locally (with [uv](https://docs.astral.sh/uv/)):
17
37
38
+
```
39
+
uvx --python 3.12 textual-demo
40
+
```
41
+
42
+
43
+
44
+
## The Compositor
45
+
46
+
The first component of Textual I want to cover is the *compositor*.
18
47
The job of the compositor is to combine widgets in to a single view.
19
48
20
49
We do this because the terminal itself has no notion of overlapping windows in the way a desktop does.
@@ -39,18 +68,18 @@ Textual's way of handling this is inherited from [Rich](https://github.com/Textu
39
68
Anything you print in Rich, first generates a list of [Segments](https://github.com/Textualize/rich/blob/master/rich/segment.py) which consist of a string and associated style.
40
69
These Segments are only converted into text with [ansi escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code) at the very last moment.
41
70
71
+
42
72
Textual works with same Segment object.
43
-
Widgets all produce a list of segments, which are further processed by the compositor.
73
+
Widgets all produce a list of segments, which is further processed by the compositor.
44
74
45
75
!!! tip "Switch the Primitive"
46
-
47
-
I hope I've earned the opertunity to dispense unsolicited pearls of wisdom.
76
+
48
77
If a problem is intractable, it can often be simplified by changing what you consider to be the fundamental atomic data and operations you are working with.
49
78
I call this "switching the primitive".
50
79
51
80
### Thinking in Segments
52
81
53
-
In the following illustration we have an app with three widgets; the "screen" (in blue) plus two floating widgets (in red and green).
82
+
In the following illustration we have an app with three widgets; the background "screen" (in blue) plus two floating widgets (in red and green).
54
83
There will be many more widgets in a typical app, but this is enough to show how it works.
55
84
56
85
@@ -73,10 +102,9 @@ It would appear something like the following:
Those lines are produced when the widget is rendered and consist of a list of segments.
105
+
We can't yet display the output as it would require writing each "layer" independantly, potentially making the terminal flicker, and certainly writing more data than neccesary.
77
106
78
-
We want to combine these lists into a single list that covers the width of the terminal.
79
-
That way we can update everything in a single write.
107
+
We need a few more steps to combine these lines in to a single line.
80
108
81
109
82
110
### Step 1. Finding the cuts.
@@ -96,25 +124,28 @@ This will produce smaller lists of segments, which in the compositor code we ref
0 commit comments