Rust Terminal Novel Reader Gets Bell-Ringing Mouse Support: Hit Testing, Pointer Capture, and the Art of Not Swallowing Events
Terminal applications no longer need to feel keyboard-only. A reusable Rust component now gives TUI apps the same click/drag/hover semantics as GUI toolkits, without porting a full DOM event model, and it exposes these behaviors through simple hooks that any ratatui-based app can adopt.
Mouse support in a terminal app is more than reading coordinates. TRNovel, a Rust-based novel reader, now lets users click books, scroll chapters, and drag scrollbars with the mouse, thanks to the underlying ratatui-kit framework. The article walks through the raw escape sequences crossterm parses, the "Stamp" records that track each component's visible region per draw frame, and the priority rules that decide which nested component gets the event first.
Two problems get particular attention: a popup button that was drawn but unclickable because a parent keyboard handler swallowed all events with `Consumed`, and the need for pointer capture so a drag continues even when the cursor leaves the scrollbar track. TRNovel also makes reader-specific decisions—such as capping wheel scroll at chapter boundaries rather than auto-turning pages, and reusing the Enter key's business logic for mouse clicks on list items.
The framework exposes four hooks—`use_click`, `use_hover`, `use_drag`, and `use_pointer`—so component authors don't reimplement common interactions. A key design choice is using draw order to decide event priority, making event dispatch mirror what the user sees.
Using draw order as the primary signal for event priority is a clever mapping between visual stacking and event dispatch, which avoids maintaining a separate event-routing graph.
The decision to stop wheel scroll at chapter boundaries respects the reader's intent, acknowledging that a continuous stream of scroll events shouldn't be conflated with a deliberate page turn.
The framework's separation of input layers (which level is active) from in-layer priority (who within that level handles first) is a clean decomposition that scales to complex TUI layouts.
The bug where a keyboard handler swallowed mouse events shows how easy it is to conflate different event types in a unified event system; the fix is a principle: consume only what you own.
The emphasis on visible-region hit testing addresses a TUI-specific trap where internal coordinates don't map to screen space after scrolling or resize.