Last week, I finally started exploring textual. The main motivation was to start implementing a few project ideas I've had in my todo list for years. I don't particularly have a preference between TUI (terminal user interface) and GUI (graphical user interface) for these projects. Seeing a few Textual demos on twitter (courtesy Will McGugan) over the past few months, I felt like exploring this framework first.
For my first app, I picked a 4x4 board game — like Tic Tac Toe but form a square instead of a line. I came up with this variation in high school and been fond of coding it since college days.
Installation and Tutorials🔗
The Getting started page of the documentation will give you all the relevant installation instructions. I used
pip install 'textual[dev]' since the development mode has nice features like live editing. As I looked up the Devtools page to link here in this blog post, I found that there's a
console command for
print() based debugging! That would've been handy while I was working on the game — sigh, I should've been more proactive in exploring the documentation site.
In the Getting started page, you'll also be informed about
python -m textual (builtin demo) and other examples in the GitHub repo.
After playing with the demo a bit, I went through the tutorial — shows how to build a Stopwatch app step-by-step.
I should note that while I got introduced to programming in school about 20 years ago, I don't have much experience with projects that need more than a few hundred lines. I'm good with command-line tools and text processing with scripting languages like Python. I had a horrible experience writing an Android app a few years back, mainly due to object-oriented programming and the complexity of the project. I've improved a bit since then, but still feel like a newbie when it comes to working with classes.
Building Square Tic Tac Toe board game🔗
Similar to the step-by-step Textual tutorial, I built the game by adding features incrementally. I tweeted my progress along with screenshots and recordings. Here's a summary:
- Managed to place 16 buttons in a grid layout
- Buttons now respond to clicking! And in response, the computer plays a random move
- Recording below shows 3 games: User wins, AI wins, Tie
- Added Easy/Hard modes — it is impossible to beat the AI in hard mode
- Almost done! Layout is better now and starting new game is now a button instead of a shortcut keybinding
- Cleaned up code a bit and posted on GitHub
- Next step: write a blog post (this post!)
Visit my GitHub repo for the code, game rules and other details.
I had made a GUI version of this game using tkinter last year. I copied most of the game logic from there, so I didn't much struggle with object-oriented programming in this case. Here's a sample screenshot from the finished code:
What I liked🔗
As mentioned before, Textual supports live editing mode. The command is
textual run --dev script.py and this helps you experiment with CSS. I found this very helpful while trying out layout combinations, margin, padding, etc.
The default colors were great too. I didn't have to think about choosing colors (except for setting background color for header and game status). The framework even provides an easy way to allow users to switch between dark and light themes! Though, I haven't yet figured out how to set light theme as the default (I worked around by explicitly adding a call to the theme toggle method).
Overall, the code was significantly shorter compared to the
tkinter version I did last year. That version had a few more features, but I'd say Textual felt much easier to reason about. I remember having to spend days shifting through stackoverflow threads and tkdocs to get the GUI version working.
What gave me trouble🔗
Struggling with layout isn't new for me. I started with 4x4 grid for the board, which was fairly straightforward. Problems arose when I wanted to add status text area to the left and control buttons to the right. Placing them left/right was easy to do with dock in CSS. But, I couldn't get them to align well — too much spacing around the 4x4 board. I was trying to give 50% to 60% for the board and the remaining evenly divided for the other two elements. After some experimentation, what worked was giving 20% to status, 25% to control and not assigning a width value for the board.
I initially used a button for the status because I couldn't find a textbox widget (edit: Textual now has a
Label widget). I knew that Static widget can display text, but I didn't find how to dynamically change the text from that documentation page. I thought I'll have to make a custom widget, but when I went to Widgets guide, I found that
Static already has an
I probably missed something (or perhaps part of the roadmap), but I found it strange to have a single
on_button_pressed() method to handle on click event for every
Button widget. I'd prefer a way to bind a method to the buttons, like
As mentioned before, I have several projects in my todo list. The next one I want to try is an app for interactive exercises for my ebooks. Last year, I made one for Python regular expressions using