Loading video player…

Playing With Text Widgets

00:00 In the previous lesson, I showed you the “Hello world!” version of a textual application. In this lesson, I’ll play around a little more with text widgets and show you some of the styling you can apply to them.

00:11 Every displayable component of your TUI application is a widget, so if you want text on the screen, you can’t just print it. You need a text widget. The two most common text widgets are Static, which you saw in the previous lesson, and Label.

00:26 For the most part, they’re similar to each other. They just behave slightly differently when laid out. Label widgets by default are the size of the text they represent, while Static widgets take up the space of their holding container.

00:38 You’ll see the difference in a sec when I give you a demo. The creators of Textual are also the creators of the Rich text library. Like with Rich, the text contents can be specified using a markup language.

00:51 Most texts you type is just text, but you can put styling commands inside of square brackets to change how the text is displayed. For example, "[bold red]" changes the text to bold font and colored red.

01:04 All widgets have a .styles property, which allows you to programmatically get and set the style of the widget. The .styles property itself has properties, which are the things you change.

01:17 Some common ones are background, which controls the widget’s background color, border for specifying an outline, text_align for centering or right-aligning text within a widget, padding for determining the spacing within the widget, and margin for spacing around the widget.

01:35 If you’re familiar with CSS on the web, these styles might be ringing a bell. Textual uses a subset of CSS to control appearances, and so if there’s some styling you can do in CSS, you’ll probably find it in Textual as well.

01:50 In a regular Python script, the code gets executed line by line. If I have three print statements in a program, they run exactly in order. Of course, when you start building functions, this changes a little.

02:02 The declaration order of the functions isn’t necessarily the running order.

02:06 TUIs and GUIs are similar. You typically want code to fire when the user does something to the interface. How most TUI and GUIs accomplish this is with an event system. You create an event handler by declaring a function or method and register it against that event.

02:23 When things happen in the TUI, events fire and any associated handlers get called at that time. Typical events are things like the user pressing a key or clicking a button.

02:35 There are also system-type events that aren’t user-triggered. For example, Textual fires events when a widget gets put into or removed from the interface. This allows you to dynamically change things during the execution of your program.

02:50 Textual gives you several ways of declaring event handlers. The first one I’m going to show you is the built in on prefix. For example, the .on_key() method gets called for each key press, while the .on_mount() method gets called when the application mounts the widgets. This is after the .composed() method fires, allowing you one last chance to do something with your widget before it gets shown.

03:14 Let’s go play with Static and Label widgets some more.

03:19 This new app has two text widgets in it, one Static and one Label. I’m going to get a little fancier than “Hello world!” and show you some of the things you can do to style your text.

03:30 In the previous lesson, you saw how you have to yield a widget inside of the .composed() method to register it with Textual. That’s required, but you can create and store a reference to the widget before yielding it like I’m doing here.

03:44 This allows me to access the widget later on in the code. I’m instantiating this Static widget with some textual markup. Inside of the string, I’ve got square bracket-style indicators.

03:56 This will turn the text bold and red. That style applies to this word, and then square brackets with a slash are what turns it off.

04:07 Anything after this closing tag will be back to normal. When you close the tag, you don’t have to turn everything off. I could have turned off just the bold, and then the rest of the sentence would still be red if I had wanted.

04:18 Once I’ve constructed the object and kept a reference to it, I yield the reference to include it in the interface. Here I’ve done something similar with a Label widget.

04:28 Note the use of the closing shortcut here. Square bracket slash means to close all open markup tags. So this will turn off the yellow and italic, and that means you don’t have as much typing to do.

04:41 When you run a Textual application, it calls your app’s .compose() method to determine what to show on the screen. When it’s ready to go, it fires the on_mount event.

04:50 This is done in two steps because some of the properties of widgets aren’t known until all the widgets have been created. Depending on how you’re laying things out, the size of some widgets can affect the size of others.

05:03 The mount event is fired after all this has been figured out, and gives you a last chance to modify the properties of your widgets before they get displayed to the screen.

05:12 For the core app events, you can register an event handler simply by overloading their on method. In the app here, on_mount is what gets called when the mount event fires.

05:24 And this is why I kept a reference to that Static widget that I built in the .compose(). It means I can access it here and make changes after the .compose() is run.

05:34 The first change I’m applying is by setting the .background property on the .styles property, making our widget blue. The .border property takes a tuple.

05:44 The first value in the tuple describes the style of the border, where solid means a solid line and the second value specifies the color of the border. Setting text_align to center centers the text in the widget.

05:57 If you’ve done CSS before, be careful to note the difference here. Python doesn’t support hyphens in property names, so anywhere where you would use a hyphen in CSS, you need to change it to an underscore.

06:09 This will get a little confusing later and I muck this up all the time, but you have to kind of keep it in mind.

06:16 The .padding property specifies the spacing within the widget. The value of 1 1 here says to put a one-line space above and below the text as well as a one-character space to the left and right of the text.

06:29 Other parts of the layout may cause there to be more padding than this, so consider this a minimum.

06:36 The .margin property specifies the spacing around the widget. 4, 4 means four lines above and below, and four characters to the left and right of the widget.

06:46 Again, the layout could make it bigger than this, so once more, it’s a minimum. Similar to the styling for the Static widget, here I style the label, this time making the background color dark green and using a double-lined border in red.

07:02 When you press a key on your keyboard, Textual fires the key event. You can override the default on_key method to do what you like with those key presses.

07:11 Here I’m using a pattern matching block to react to the key property on the event object passed into the handler. If you haven’t seen these before, they were introduced in Python 3.10 and they serve a similar purpose to if-else blocks.

07:26 The argument to the match statement serves as a value to look for, then the case statement works like an if-equals statement. If the value inside of event.key is q, then this case runs and so the exit() function will get called.

07:41 In the previous lesson, I mentioned, I don’t really like Ctrl-Q to quit, and so now I’ve got it set so pressing q will be enough.

07:49 Below this method, I’ve instantiated the app and called its ,run(), just like before. Let’s try this out.

08:01 And here you have the app. Static widgets fill the container, which in this case is most of the screen. It’s actually all of the screen, but the margin property of the widget is keeping a four character boundary all around it.

08:14 The Label widget, by default, is only the size of its contents. Of course, the padding within it and the border property affect its size, but it doesn’t get any bigger than that.

08:24 It also has a margin around it, which is why it’s offset.

08:28 And since I added the key event handler, I can press q to quit. Next up, more on styling and how to use CSS to control your app’s appearance.

Become a Member to join the conversation.