Turbo Vision for Rust 1.0: Difference between revisions
Created page with "500px In an era dominated by web technologies and GPU-accelerated interfaces, there’s something deeply satisfying about building text-mode user interfaces that run directly in the terminal. While modern Rust developers typically reach for Ratatui when building terminal UIs, there’s another approach that offers a different philosophy: Turbo Vision for Rust, a faithful port of Borland’s legendary 1990s text-mode UI framework. Th..." |
No edit summary |
||
| Line 1: | Line 1: | ||
[[Turbo_Vision_for_Rust_1.0.jpg|500px]] | [[file:Turbo_Vision_for_Rust_1.0.jpg|500px]] | ||
In an era dominated by web technologies and GPU-accelerated interfaces, there’s something deeply satisfying about building text-mode user interfaces that run directly in the terminal. While modern Rust developers typically reach for Ratatui when building terminal UIs, there’s another approach that offers a different philosophy: Turbo Vision for Rust, a faithful port of Borland’s legendary 1990s text-mode UI framework. | In an era dominated by web technologies and GPU-accelerated interfaces, there’s something deeply satisfying about building text-mode user interfaces that run directly in the terminal. While modern Rust developers typically reach for Ratatui when building terminal UIs, there’s another approach that offers a different philosophy: Turbo Vision for Rust, a faithful port of Borland’s legendary 1990s text-mode UI framework. | ||
Latest revision as of 16:32, 22 November 2025
In an era dominated by web technologies and GPU-accelerated interfaces, there’s something deeply satisfying about building text-mode user interfaces that run directly in the terminal. While modern Rust developers typically reach for Ratatui when building terminal UIs, there’s another approach that offers a different philosophy: Turbo Vision for Rust, a faithful port of Borland’s legendary 1990s text-mode UI framework.
This is the story of porting 30-year-old C++ code to Rust, preserving decades of UI/UX wisdom while adapting to Rust’s ownership model, and the architectural decisions that made it possible.
Why Port a 1990s Framework in 2025?
The question is reasonable: why spend time porting a framework from the MS-DOS era when we have modern alternatives? The answer lies in what Turbo Vision represents:
- Battle-tested UI patterns: Borland’s engineers solved complex problems like modal dialogs, z-order window management, keyboard navigation, and event broadcasting in the early 1990s. These solutions still work beautifully today.
- Complete widget system: Turbo Vision shipped with everything: windows, dialogs, menus, status lines, scrollbars, text editors, file browsers, and more. It was a complete application framework, not just a rendering library.
- Retro aesthetic: The classic “3D” frame style with shadows, the color palette system, and the overall look evoke the golden age of DOS applications. For certain types of applications, this aesthetic is not just nostalgic, it’s functional and focused.
- Different philosophy: While Ratatui follows React’s immediate-mode rendering approach, Turbo Vision uses retained-mode widgets with event-driven updates. This can be more intuitive for certain application architectures.
- A fun vibe-coding experiment: got to try Anthropic Claude as the main developer handling such a complex porting exercise with very few manual editions.
The Challenge: C++ to Rust
The original Borland Turbo Vision codebase consists of approximately 50,000 lines of C++ code spread across multiple implementations. The most complete is the kloczek port, which served as our primary reference.
Ownership and Borrowing vs. Raw Pointers
The biggest challenge in porting C++ to Rust is reconciling two fundamentally different memory models. Turbo Vision’s C++ code relies heavily on:
- Raw pointers for parent-child relationships
- Manual memory management with new and delete
- Inheritance hierarchies (TView → TGroup → TWindow → TDialog)
- Virtual method dispatch
Rust doesn’t have inheritance. It has traits, composition, and a borrow checker that prevents the casual pointer manipulation C++ allows.
Our solution:
- Composition over inheritance: A Dialog contains a Window, which contains a Group
- Trait objects: Box<dyn View> for polymorphic collections
- Safe owner pointers: Raw *const dyn View pointers are used only for reading palette data during drawing, never for mutation
- Interior mutability: Rc<RefCell<>> for the editor widget, where multiple scrollbars need to update the same editor state
The Event Loop Location Problem
One of the most subtle but important architectural decisions was where to place the event loop. Modern Rust TUI applications typically structure their event loop like this:
// Typical modern approach (Ratatui style)
fn main() {
let mut app = App::new();
loop {
terminal.draw(&mut app)?;
if handle_event(&mut app)? {
break;
}
}
}
But Borland Turbo Vision puts the event loop inside TGroup::execute(), not in the main function:
This pattern enables proper modal dialogs that can “nest” event loops. When you open a modal dialog, it runs its own event loop and blocks the caller until dismissed. This is fundamentally different from the single-loop approach used by most modern frameworks. We preserved this pattern because it’s essential for Borland-compatible behavior. Modal dialogs in Turbo Vision truly pause execution of the calling code, just like native OS dialog boxes. Key Architectural Patterns Preserved The Three-Phase Event System Turbo Vision uses a sophisticated three-phase event processing system:
- PreProcess: Views with OF_PRE_PROCESS flag handle the event first (e.g., dialogs checking for global shortcuts)
- Focused: The currently focused child gets the event
- PostProcess: Views with OF_POST_PROCESS flag handle the event last (e.g., status line updating help text)
This allows the status line to show context-sensitive help even when an input field is focused, and allows dialogs to handle Escape key globally even if a button is focused.
The Palette System
Borland’s three-level palette system is one of the framework’s most elegant features:
Views don’t specify RGB colors directly. Instead:
- A button asks for “button normal color” (palette index 0)
- Its owner (dialog) translates: “button normal = my index 13”
- The dialog’s owner (application) says: “dialog index 13 = bright cyan on blue”
This allows complete theme customization by changing just the application’s palette. All widgets automatically adapt.
Drawing with DrawBuffer
Rather than drawing directly to the terminal, views draw to a DrawBuffer, a fixed-size array representing one line of text:
// Borland pattern: write to buffer, then blit to screen let mut buf = DrawBuffer::new(width); buf.move_str(0, "Hello", color); buf.move_str(6, "World", color); terminal.write_line_to_terminal(x, y, &buf);
This enables efficient rendering with minimal terminal updates. The framework tracks dirty regions and only redraws what changed.
Community Contributions
While the core architecture was ported following the C++ reference implementations, reaching production quality required extensive testing and refinement. The journey from initial implementation to production-ready involved discovering and fixing over 40 issues through real-world testing and usage. Special recognition goes to Philippe BAUCOUR (@40tude), who contributed numerous pull requests that improved the framework significantly:
- Keyboard navigation improvements and bug fixes
- Widget usability enhancements
- Testing across different terminal environments
- Documentation clarifications
- Example application refinements
Open source projects thrive on community involvement, and Philippe’s contributions exemplify the kind of detailed, incremental work that transforms “it compiles” into “it’s production-ready.” His testing and feedback helped identify edge cases that only emerge through real-world usage. The issue tracker became a valuable tool for capturing these discoveries (from subtle rendering glitches to focus management edge cases) and systematically addressing them.
Turbo Vision vs. Ratatui: Two Philosophies
It’s worth comparing Turbo Vision to Ratatui, the dominant Rust TUI framework, because they represent fundamentally different approaches:
Ratatui (Immediate Mode)
// Ratatui: describe what to render each frame
fn ui(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(0)])
.split(f.size());
f.render_widget(Paragraph::new(app.text.clone()), chunks[0]);
f.render_widget(List::new(app.items.clone()), chunks[1]);
}
Every frame, you describe the entire UI. The library figures out what changed and updates the terminal accordingly. This is similar to React or SwiftUI: declarative and functional.
Turbo Vision (Retained Mode)
// Turbo Vision: build widget tree once, update as needed
let mut dialog = Dialog::new(bounds, "Settings");
dialog.add(Box::new(InputLine::new(bounds, 50)));
dialog.add(Box::new(Button::new(bounds, "OK", CM_OK, true)));
// Widgets persist and handle their own events
let result = dialog.execute(&mut app);
Widgets are objects that maintain state and respond to events. You build the UI once, and it stays in memory. This is similar to classic GUI frameworks like Qt or WinForms: imperative and event-driven.
Neither approach is better, they serve different use cases: Use CaseBetter Choice Complex forms with many inputsTurbo Vision (widget state management) Real-time data visualizationRatatui (easy full-screen redraws) Modal dialog-heavy applicationsTurbo Vision (native modal support) Game-like interfacesRatatui (flexible layout) Traditional desktop-app feelTurbo Vision (window manager, menus) Modern dashboard styleRatatui (responsive layouts)
Turbo Vision excels at building applications that feel like traditional desktop software, complete with overlapping windows, drag-and-drop, and deep menu hierarchies. Ratatui excels at modern, streamlined interfaces where the entire screen is one coherent view.
Composition Challenges in Rust
One of the ongoing challenges when porting object-oriented code to Rust is handling composition properly. Unlike inheritance, where methods automatically propagate down the class hierarchy, Rust’s composition requires explicit delegation. For example, consider wrapper types like EditWindow (which wraps a Window) or FileEditor (which wraps an EditWindow). Each wrapper must explicitly delegate every trait method:
impl View for EditWindow {
fn bounds(&self) -> Rect {
self.window.bounds() // Delegate to inner window
}
fn set_bounds(&mut self, bounds: Rect) {
self.window.set_bounds(bounds) // Delegate to inner window
}
fn set_owner(&mut self, owner: *const dyn View) {
self.window.set_owner(owner); // Must explicitly delegate!
}
// ... and so on for every method
}
Missing even a single delegation can cause subtle bugs. If set_owner() isn’t delegated, the inner window never learns about its parent, which affects palette resolution and drag constraints. There’s no compiler error (the code compiles fine using the default trait implementation) but the runtime behavior is wrong. This highlights a fundamental trade-off: Rust’s explicitness prevents many bugs through the type system, but it also means you can’t rely on automatic behavior inheritance. Every layer must be wired up manually.
Code Statistics and Completeness
The final Turbo Vision for Rust implementation consists of: Language Files Lines Code Comments Blanks =============================================================================== Rust 115 33,955 25,389 3,304 5,262 |- Markdown 90 3,846 255 3,010 581 =============================================================================== Total 37,801 25,644 6,314 5,843 ===============================================================================
210 unit tests covering all major subsystems, all passing. The port achieves 100% API parity with the kloczek C++ implementation. Every widget, every flag, every event processing path from the original has a Rust equivalent.
What Turbo Vision Offers Today
If you’re building a terminal application in Rust and considering your UI framework options, Turbo Vision offers: ✅ Complete Widget Library
- Windows with frames, shadows, and drag/resize
- Dialogs (modal and non-modal)
- Buttons, checkboxes, radio buttons
- Input fields with validation
- Multi-line text editor with syntax highlighting
- Menus (pull-down and horizontal)
- Status line with context-sensitive help
- List boxes and sorted lists
- File and directory browsers
- Scrollbars (horizontal and vertical)
- Progress indicators
- Static text and parameterized text
✅ Window Management
- Z-order window stacking
- Drag and drop
- Window tiling and cascading
- Window cycling (Next/Previous)
- Proper modal dialog blocking
✅ Event System
- Mouse support (click, drag, double-click)
- Full keyboard navigation
- Command broadcasting
- Three-phase event processing
- Event re-queuing
✅ Theming
- Complete palette system
- Runtime theme switching
- Borland-compatible color schemes
✅ Professional Polish
- Proper focus management
- Tab order control
- Keyboard shortcuts
- Undo/redo in editors
- History lists for input fields
- Validation framework
When to Choose Turbo Vision Consider Turbo Vision if you’re building:
- Traditional desktop-style applications: Email clients, database tools, system administration utilities
- Modal workflow applications: Setup wizards, configuration tools, installers
- Multi-window editors: Log viewers, data browsers with multiple panels
- Retro-aesthetic projects: Games, demos, or apps where the 1990s look is part of the brand
- Applications ported from DOS/Unix curses: Turbo Vision concepts map directly to classic TUI patterns
Consider Ratatui if you’re building:
- Dashboards and monitoring tools: Where real-time data visualization is primary
- Single-view applications: Where modal dialogs aren’t central to the workflow
- Modern, minimalist interfaces: Where a clean, uncluttered look is important
- Rapid prototyping: Where immediate-mode’s simplicity speeds development
Known Limitations While the framework is production-ready with 100% API parity to the C++ implementation, there are a few areas that need further work: Help System: The original Borland Turbo Vision included a comprehensive context-sensitive help system that could display help topics in modal windows. While the basic infrastructure exists in the Rust port, the help system is not fully functional in the current release. This includes:
- Help file parsing and indexing
- Context-sensitive help triggered by F1
- Help navigation (hyperlinks, search)
The help system is a substantial subsystem on its own and may be completed in a future release. For now, applications can implement their own help dialogs using the standard Dialog and TextView widgets.
Looking Forward
With the core framework complete and production-ready, there’s always room for evolution:
- Help System: Complete implementation of Borland’s context-sensitive help system
- Accessibility improvements: Better screen reader support, high-contrast themes
- Unicode handling: Full support for international text and emoji
- Performance optimization: Profiling and optimizing hot paths
- Extended widgets: Date pickers, color pickers, tree views
- Platform-specific features: Leverage modern terminal capabilities (true color, etc.)
The core architecture is solid, battle-tested across decades of use in the original C++ framework. The Rust port preserves that foundation while adding memory safety and modern tooling.
Conclusion
Porting Turbo Vision from C++ to Rust has been an exercise in bridging eras: taking a framework designed when 640KB was enough for anyone and bringing it to a world of terabyte RAM and multi-core processors. The surprising discovery is how well the original design holds up. Borland’s engineers made smart architectural decisions (modal event loops, three-phase event processing, palette indirection, composition-based widget assembly) that remain relevant today. The port didn’t require “modernizing” these patterns; it required respecting them and finding Rust-idiomatic ways to express them. The result is a framework that lets you build sophisticated terminal applications with a proven, polished widget set and a distinct aesthetic. In a world of web apps and Electron bloat, there’s something refreshing about a 2MB binary that runs in any terminal and looks like it stepped out of 1992, in the best possible way. For developers building terminal applications, the ecosystem is richer for having both Ratatui’s modern immediate-mode approach and Turbo Vision’s classic retained-mode approach. Different tools for different jobs, both excellent at what they do.
Project: github.com/aovestdipaperino/turbo-vision-4-rust Documentation: See repository README and examples License: MIT Status: Production Ready ✅
Read the full article here: https://medium.com/rustaceans/turbo-vision-for-rust-1-0-06c253faac64


