Ruby Frontend Design (ratatui_ruby)
This document describes the design philosophy and structure of the Ruby layer in ratatui_ruby.
Core Philosophy: Data-Driven UI
The Ruby frontend is designed as a thin, declarative layer over the Rust backend. It uses an Immediate Mode paradigm where the user constructs a tree of pure data objects every frame to represent the desired UI state.
1. View Tree as Data
Unlike traditional OO GUI toolkits (like Qt or Swing) where widgets are retained objects with internal state, ratatui_ruby widgets are immutable value objects.
-
Implemented using Ruby 3.2+
Dataclasses. -
Located in
lib/ratatui_ruby/schema/. -
These objects act as a Schema or Interface Definition Language (IDL) between Ruby and Rust.
Example:
# This is just a piece of data, not a "live" widget paragraph = RatatuiRuby::Paragraph.new( text: "Hello World", style: RatatuiRuby::Style.new(fg: :red), block: nil )
2. Immediate Mode Rendering
The application loop typically looks like this:
-
Poll Event: Ruby asks Rust for the next event.
-
Update State: Ruby application code updates its own domain state (e.g.,
counter += 1). -
Render: Ruby constructs a fresh View Tree based on the current domain state and passes the root node to
RatatuiRuby.draw.
loop do # 1. & 2. Handle events and update state event = RatatuiRuby.poll_event break if event[:type] == :key && event[:code] == "esc" # 3. Construct View Tree ui = RatatuiRuby::Paragraph.new(text: "Time: #{Time.now}") # 4. Draw RatatuiRuby.draw(ui) end
3. No render logic in Ruby
The Ruby classes in lib/ratatui_ruby/schema/ should not contain rendering logic. They are strictly for structural definition and validation. All rendering logic resides in the Rust extension (ext/ratatui_ruby/), which walks this Ruby object tree and produces Ratatui primitives.
Adding a New Widget
To add a new widget to the Ruby frontend:
-
Define the class in
lib/ratatui_ruby/schema/. -
Use
Data.define. -
Ensure attribute names match what the Rust rendering logic expects (see
ext/ratatui_ruby/src/widgets/).
# lib/ratatui_ruby/schema/my_widget.rb module RatatuiRuby # A widget that does something specific. # # [some_property] The description of the property. # [style] The style to apply. # [block] Optional block widget. class MyWidget < Data.define(:some_property, :style, :block) # Creates a new MyWidget. # # [some_property] The description of the property. # [style] The style to apply. # [block] Optional block widget. def initialize(some_property:, style: nil, block: nil) super end end end
And define the types in the corresponding .rbs file:
# sig/ratatui_ruby/schema/my_widget.rbs
module RatatuiRuby
class MyWidget < Data
attr_reader some_property: String
attr_reader style: Style?
attr_reader block: Block?
def self.new: (some_property: String, ?style: Style?, ?block: Block?) -> MyWidget
end
end