Welcome to Part 1 of TypeGym: a terminal based typing trainer we’re building from scratch in Rust using Ratatui (and Crossterm under the hood).
This episode is all about getting the boring but critical stuff in place: a solid main loop, a clean terminal UI lifecycle and the first pass at input handling.
What We Covered
- Created a minimal
Config(placeholder for later CLI parsing) - Implemented the main loop that runs typing sessions until the user quits
- Built a
run_ui()function that:- initializes the terminal
- runs an event loop
- draws every frame
- polls keyboard input
- restores the terminal on exit
- Added an
Appwrapper that:- draws the current screen (for now: just the target text)
- handles key events
- tracks a
should_quitflag
At the end of Part 1 we have a real interactive terminal program that can react to keys and exit cleanly, which is exactly what we need before we start adding “typing trainer” features.
Design Insights
Start With the Loop, Not the Features
It is tempting to jump straight into WPM counters, colored text and accuracy tracking. But terminal apps are unforgiving: if your setup/teardown is messy, you’ll constantly fight broken terminal states.
So the goal here is simple: a reliable foundation.
Separate “UI App” from “Typing State”
We keep:
App→ UI/event-loop concerns (draw, input dispatch, quit flags)State→ typing-session data (target, input, timing, stats)
Right now State is mostly scaffolding (some methods are todo!()), but the boundary is already in place.
Implementation Highlights
The Main Practice Loop
The program is structured around a session loop: fetch text → create state → run UI → decide whether to repeat.
loop
This makes it easy to support “restart with new text” later, without restarting the whole binary.
Terminal UI Event Loop (Ratatui + Crossterm)
Inside run_ui() we:
- init terminal
- enable mouse capture (even if unused for now)
- draw repeatedly
- poll for key events
- restore terminal on exit
let mut terminal = init;
execute!?;
let mut app = new;
loop
restore;
execute!?;
First Keybindings (Early Version)
We already sketch the behavior we want:
- When complete:
Enter→ quitEsc→ restart
- During typing:
Ctrl+C→ quitCtrl+W→ delete word (hooked, logic comes later)Backspace→ delete char (hooked)Char(c)/Enter→ add input + start/stop timer
This is the skeleton that later episodes will “fill in” with actual typing logic.
What’s Next?
In Part 2 we will focus on rendering the typing text properly: turning the raw target/input into something that feels great to use in a terminal UI:
- Introduce the
Characterenum (hit / miss / empty) to represent per-cell state - Build the layout helpers like
build_lineandbuild_pageto split the text into renderable chunks - Render those chunks with Ratatui widgets (styling, spans, lines) instead of a plain paragraph
- Center the typing area so the text sits nicely in the middle of the terminal
Once that is in place, we will have a solid UI foundation to layer in typing logic and real-time stats in the following episodes.
Project Code
You will find the complete source code here: typegym