ratatui_ruby 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- metadata +1 -232
- data/.builds/ruby-3.2.yml +0 -54
- data/.builds/ruby-3.3.yml +0 -54
- data/.builds/ruby-3.4.yml +0 -54
- data/.builds/ruby-4.0.0.yml +0 -54
- data/.pre-commit-config.yaml +0 -16
- data/.rubocop.yml +0 -10
- data/AGENTS.md +0 -146
- data/CHANGELOG.md +0 -710
- data/README.md +0 -187
- data/README.rdoc +0 -302
- data/Rakefile +0 -11
- data/Steepfile +0 -49
- data/doc/concepts/application_architecture.md +0 -321
- data/doc/concepts/application_testing.md +0 -193
- data/doc/concepts/async.md +0 -190
- data/doc/concepts/custom_widgets.md +0 -247
- data/doc/concepts/debugging.md +0 -401
- data/doc/concepts/event_handling.md +0 -162
- data/doc/concepts/interactive_design.md +0 -146
- data/doc/contributors/auditing/parity.md +0 -239
- data/doc/contributors/design/ruby_frontend.md +0 -420
- data/doc/contributors/design/rust_backend.md +0 -422
- data/doc/contributors/design.md +0 -11
- data/doc/contributors/developing_examples.md +0 -400
- data/doc/contributors/documentation_style.md +0 -121
- data/doc/contributors/index.md +0 -21
- data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -375
- data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -206
- data/doc/contributors/todo/align/terminal.md +0 -647
- data/doc/contributors/todo/future_work.md +0 -169
- data/doc/contributors/upstream_requests/tab_rects.md +0 -173
- data/doc/contributors/upstream_requests/title_rects.md +0 -132
- data/doc/custom.css +0 -22
- data/doc/getting_started/quickstart.md +0 -291
- data/doc/getting_started/why.md +0 -93
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_cli_rich_moments.gif +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_debugging_showcase.gif +0 -0
- data/doc/images/app_debugging_showcase.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart.png +0 -0
- data/doc/images/widget_block.png +0 -0
- data/doc/images/widget_box.png +0 -0
- data/doc/images/widget_calendar.png +0 -0
- data/doc/images/widget_canvas.png +0 -0
- data/doc/images/widget_cell.png +0 -0
- data/doc/images/widget_center.png +0 -0
- data/doc/images/widget_chart.png +0 -0
- data/doc/images/widget_gauge.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge.png +0 -0
- data/doc/images/widget_list.png +0 -0
- data/doc/images/widget_map.png +0 -0
- data/doc/images/widget_overlay.png +0 -0
- data/doc/images/widget_popup.png +0 -0
- data/doc/images/widget_ratatui_logo.png +0 -0
- data/doc/images/widget_ratatui_mascot.png +0 -0
- data/doc/images/widget_rect.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_scrollbar.png +0 -0
- data/doc/images/widget_sparkline.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table.png +0 -0
- data/doc/images/widget_tabs.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/index.md +0 -39
- data/doc/troubleshooting/async.md +0 -4
- data/doc/troubleshooting/terminal_limitations.md +0 -131
- data/doc/troubleshooting/tui_output.md +0 -197
- data/examples/app_all_events/README.md +0 -114
- data/examples/app_all_events/app.rb +0 -98
- data/examples/app_all_events/model/app_model.rb +0 -159
- data/examples/app_all_events/model/event_color_cycle.rb +0 -43
- data/examples/app_all_events/model/event_entry.rb +0 -94
- data/examples/app_all_events/model/msg.rb +0 -39
- data/examples/app_all_events/model/timestamp.rb +0 -56
- data/examples/app_all_events/update.rb +0 -75
- data/examples/app_all_events/view/app_view.rb +0 -80
- data/examples/app_all_events/view/controls_view.rb +0 -54
- data/examples/app_all_events/view/counts_view.rb +0 -61
- data/examples/app_all_events/view/live_view.rb +0 -72
- data/examples/app_all_events/view/log_view.rb +0 -57
- data/examples/app_all_events/view.rb +0 -9
- data/examples/app_cli_rich_moments/README.md +0 -81
- data/examples/app_cli_rich_moments/app.rb +0 -189
- data/examples/app_color_picker/README.md +0 -156
- data/examples/app_color_picker/app.rb +0 -76
- data/examples/app_color_picker/clipboard.rb +0 -86
- data/examples/app_color_picker/color.rb +0 -193
- data/examples/app_color_picker/controls.rb +0 -92
- data/examples/app_color_picker/copy_dialog.rb +0 -168
- data/examples/app_color_picker/export_pane.rb +0 -128
- data/examples/app_color_picker/harmony.rb +0 -58
- data/examples/app_color_picker/input.rb +0 -176
- data/examples/app_color_picker/main_container.rb +0 -180
- data/examples/app_color_picker/palette.rb +0 -111
- data/examples/app_debugging_showcase/README.md +0 -119
- data/examples/app_debugging_showcase/app.rb +0 -318
- data/examples/app_login_form/README.md +0 -58
- data/examples/app_login_form/app.rb +0 -109
- data/examples/app_stateful_interaction/README.md +0 -35
- data/examples/app_stateful_interaction/app.rb +0 -328
- data/examples/timeout_demo.rb +0 -45
- data/examples/verify_quickstart_dsl/README.md +0 -55
- data/examples/verify_quickstart_dsl/app.rb +0 -49
- data/examples/verify_quickstart_layout/README.md +0 -77
- data/examples/verify_quickstart_layout/app.rb +0 -73
- data/examples/verify_quickstart_lifecycle/README.md +0 -68
- data/examples/verify_quickstart_lifecycle/app.rb +0 -62
- data/examples/verify_readme_usage/README.md +0 -49
- data/examples/verify_readme_usage/app.rb +0 -42
- data/examples/verify_website_managed/README.md +0 -48
- data/examples/verify_website_managed/app.rb +0 -36
- data/examples/verify_website_menu/README.md +0 -60
- data/examples/verify_website_menu/app.rb +0 -84
- data/examples/verify_website_spinner/README.md +0 -44
- data/examples/verify_website_spinner/app.rb +0 -34
- data/examples/widget_barchart/README.md +0 -58
- data/examples/widget_barchart/app.rb +0 -240
- data/examples/widget_block/README.md +0 -44
- data/examples/widget_block/app.rb +0 -258
- data/examples/widget_box/README.md +0 -54
- data/examples/widget_box/app.rb +0 -255
- data/examples/widget_calendar/README.md +0 -48
- data/examples/widget_calendar/app.rb +0 -115
- data/examples/widget_canvas/README.md +0 -31
- data/examples/widget_canvas/app.rb +0 -130
- data/examples/widget_cell/README.md +0 -45
- data/examples/widget_cell/app.rb +0 -112
- data/examples/widget_center/README.md +0 -33
- data/examples/widget_center/app.rb +0 -118
- data/examples/widget_chart/README.md +0 -50
- data/examples/widget_chart/app.rb +0 -220
- data/examples/widget_gauge/README.md +0 -50
- data/examples/widget_gauge/app.rb +0 -229
- data/examples/widget_layout_split/README.md +0 -53
- data/examples/widget_layout_split/app.rb +0 -260
- data/examples/widget_line_gauge/README.md +0 -50
- data/examples/widget_line_gauge/app.rb +0 -219
- data/examples/widget_list/README.md +0 -58
- data/examples/widget_list/app.rb +0 -384
- data/examples/widget_map/README.md +0 -48
- data/examples/widget_map/app.rb +0 -95
- data/examples/widget_overlay/README.md +0 -45
- data/examples/widget_overlay/app.rb +0 -250
- data/examples/widget_popup/README.md +0 -45
- data/examples/widget_popup/app.rb +0 -106
- data/examples/widget_ratatui_logo/README.md +0 -43
- data/examples/widget_ratatui_logo/app.rb +0 -104
- data/examples/widget_ratatui_mascot/README.md +0 -43
- data/examples/widget_ratatui_mascot/app.rb +0 -95
- data/examples/widget_rect/README.md +0 -53
- data/examples/widget_rect/app.rb +0 -222
- data/examples/widget_render/README.md +0 -46
- data/examples/widget_render/app.rb +0 -186
- data/examples/widget_render/app.rbs +0 -41
- data/examples/widget_rich_text/README.md +0 -44
- data/examples/widget_rich_text/app.rb +0 -193
- data/examples/widget_scroll_text/README.md +0 -46
- data/examples/widget_scroll_text/app.rb +0 -109
- data/examples/widget_scrollbar/README.md +0 -46
- data/examples/widget_scrollbar/app.rb +0 -155
- data/examples/widget_sparkline/README.md +0 -51
- data/examples/widget_sparkline/app.rb +0 -277
- data/examples/widget_style_colors/README.md +0 -43
- data/examples/widget_style_colors/app.rb +0 -83
- data/examples/widget_table/README.md +0 -57
- data/examples/widget_table/app.rb +0 -279
- data/examples/widget_tabs/README.md +0 -50
- data/examples/widget_tabs/app.rb +0 -183
- data/examples/widget_text_width/README.md +0 -44
- data/examples/widget_text_width/app.rb +0 -117
- data/migrate_to_buffer.rb +0 -145
- data/mise.toml +0 -8
- data/tasks/autodoc/examples.rb +0 -87
- data/tasks/autodoc/member.rb +0 -58
- data/tasks/autodoc/name.rb +0 -21
- data/tasks/autodoc.rake +0 -21
- data/tasks/bump/cargo_lockfile.rb +0 -21
- data/tasks/bump/changelog.rb +0 -47
- data/tasks/bump/header.rb +0 -32
- data/tasks/bump/history.rb +0 -32
- data/tasks/bump/links.rb +0 -69
- data/tasks/bump/manifest.rb +0 -33
- data/tasks/bump/ruby_gem.rb +0 -49
- data/tasks/bump/sem_ver.rb +0 -40
- data/tasks/bump/unreleased_section.rb +0 -56
- data/tasks/bump.rake +0 -51
- data/tasks/doc.rake +0 -887
- data/tasks/example_viewer.html.erb +0 -172
- data/tasks/extension.rake +0 -14
- data/tasks/license/headers_md.rb +0 -223
- data/tasks/license/headers_rb.rb +0 -210
- data/tasks/license/license_utils.rb +0 -130
- data/tasks/license/snippets_md.rb +0 -315
- data/tasks/license/snippets_rdoc.rb +0 -150
- data/tasks/license.rake +0 -91
- data/tasks/lint.rake +0 -170
- data/tasks/rdoc_config.rb +0 -29
- data/tasks/resources/build.yml.erb +0 -60
- data/tasks/resources/index.html.erb +0 -141
- data/tasks/resources/rubies.yml +0 -7
- data/tasks/sourcehut.rake +0 -110
- data/tasks/steep.rake +0 -11
- data/tasks/terminal_preview/app_screenshot.rb +0 -45
- data/tasks/terminal_preview/crash_report.rb +0 -54
- data/tasks/terminal_preview/example_app.rb +0 -27
- data/tasks/terminal_preview/launcher_script.rb +0 -48
- data/tasks/terminal_preview/preview_collection.rb +0 -60
- data/tasks/terminal_preview/preview_timing.rb +0 -24
- data/tasks/terminal_preview/safety_confirmation.rb +0 -58
- data/tasks/terminal_preview/saved_screenshot.rb +0 -56
- data/tasks/terminal_preview/system_appearance.rb +0 -13
- data/tasks/terminal_preview/terminal_window.rb +0 -138
- data/tasks/terminal_preview/window_id.rb +0 -16
- data/tasks/terminal_preview.rake +0 -30
- data/tasks/test.rake +0 -33
- data/tasks/website/index_page.rb +0 -30
- data/tasks/website/version.rb +0 -127
- data/tasks/website/version_menu.rb +0 -68
- data/tasks/website/versioned_documentation.rb +0 -83
- data/tasks/website/website.rb +0 -53
- data/tasks/website.rake +0 -28
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Ruby Frontend Design (`ratatui_ruby`)
|
|
7
|
-
|
|
8
|
-
This document describes the architectural design and guiding principles of the Ruby layer in `ratatui_ruby`. It is intended for contributors, architects, and AI agents working on the codebase.
|
|
9
|
-
|
|
10
|
-
## Guiding Design Principles
|
|
11
|
-
|
|
12
|
-
### 1. Ratatui Alignment
|
|
13
|
-
|
|
14
|
-
The Ruby namespace structure mirrors Ratatui's Rust module hierarchy exactly. This is a deliberate architectural choice with specific benefits:
|
|
15
|
-
|
|
16
|
-
- **Documentation Mapping**: A contributor reading Ratatui's docs for `ratatui::widgets::Table` immediately knows to look at `RatatuiRuby::Widgets::Table`.
|
|
17
|
-
- **Predictability**: No mental translation required between Rust and Ruby codebases.
|
|
18
|
-
- **Scalability**: As Ratatui adds new types, the Ruby placement is deterministic.
|
|
19
|
-
|
|
20
|
-
**Module Mapping:**
|
|
21
|
-
|
|
22
|
-
| Rust Module | Ruby Module | Purpose |
|
|
23
|
-
|-------------|-------------|---------|
|
|
24
|
-
| `ratatui::layout` | `RatatuiRuby::Layout` | Rect, Constraint, Layout |
|
|
25
|
-
| `ratatui::widgets` | `RatatuiRuby::Widgets` | All widgets (Table, List, Paragraph, Block, etc.) |
|
|
26
|
-
| `ratatui::style` | `RatatuiRuby::Style` | Style, Color |
|
|
27
|
-
| `ratatui::text` | `RatatuiRuby::Text` | Span, Line |
|
|
28
|
-
| `ratatui::buffer` | `RatatuiRuby::Buffer` | Cell (for buffer inspection) |
|
|
29
|
-
|
|
30
|
-
This structure resolves name collisions that would otherwise require arbitrary prefixes. For example, `Buffer::Cell` (terminal cell inspection) vs `Widgets::Cell` (table cell construction) are clearly distinct.
|
|
31
|
-
|
|
32
|
-
### 2. Two-Layer Architecture
|
|
33
|
-
|
|
34
|
-
The Ruby frontend implements a "Mullet Architecture": structured namespaces in the library, flat ergonomic DSL for users.
|
|
35
|
-
|
|
36
|
-
**Layer 1: Schema Classes (The Library)**
|
|
37
|
-
|
|
38
|
-
Located in `lib/ratatui_ruby/widgets/`, `lib/ratatui_ruby/layout/`, etc.
|
|
39
|
-
|
|
40
|
-
These are the actual `Data.define` classes that the Rust backend expects. They have deep, explicit namespaces that match Ratatui:
|
|
41
|
-
|
|
42
|
-
<!-- SPDX-SnippetBegin -->
|
|
43
|
-
<!--
|
|
44
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
45
|
-
SPDX-License-Identifier: MIT-0
|
|
46
|
-
-->
|
|
47
|
-
```ruby
|
|
48
|
-
RatatuiRuby::Widgets::Paragraph.new(text: "Hello")
|
|
49
|
-
RatatuiRuby::Layout::Constraint.length(20)
|
|
50
|
-
RatatuiRuby::Style::Style.new(fg: :red)
|
|
51
|
-
```
|
|
52
|
-
<!-- SPDX-SnippetEnd -->
|
|
53
|
-
|
|
54
|
-
**Layer 2: TUI Facade (The DSL)**
|
|
55
|
-
|
|
56
|
-
Located in `lib/ratatui_ruby/tui.rb` and `lib/ratatui_ruby/tui/*.rb` mixins.
|
|
57
|
-
|
|
58
|
-
The `TUI` class provides shorthand factory methods that hide namespace verbosity:
|
|
59
|
-
|
|
60
|
-
<!-- SPDX-SnippetBegin -->
|
|
61
|
-
<!--
|
|
62
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
63
|
-
SPDX-License-Identifier: MIT-0
|
|
64
|
-
-->
|
|
65
|
-
```ruby
|
|
66
|
-
RatatuiRuby.run do |tui|
|
|
67
|
-
tui.paragraph(text: "Hello")
|
|
68
|
-
tui.constraint_length(20)
|
|
69
|
-
tui.style(fg: :red)
|
|
70
|
-
end
|
|
71
|
-
```
|
|
72
|
-
<!-- SPDX-SnippetEnd -->
|
|
73
|
-
|
|
74
|
-
**Why This Matters:**
|
|
75
|
-
|
|
76
|
-
Users write application code using the TUI API and rarely touch deep namespaces. Contributors maintaining the library work with explicit, documentable, IDE-friendly classes. Both audiences are served without compromise.
|
|
77
|
-
|
|
78
|
-
### 3. Explicit Over Magic
|
|
79
|
-
|
|
80
|
-
The TUI facade uses explicit factory method definitions, not runtime metaprogramming.
|
|
81
|
-
|
|
82
|
-
**What We Do:**
|
|
83
|
-
|
|
84
|
-
<!-- SPDX-SnippetBegin -->
|
|
85
|
-
<!--
|
|
86
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
87
|
-
SPDX-License-Identifier: MIT-0
|
|
88
|
-
-->
|
|
89
|
-
```ruby
|
|
90
|
-
# lib/ratatui_ruby/tui/widget_factories.rb
|
|
91
|
-
module RatatuiRuby
|
|
92
|
-
class TUI
|
|
93
|
-
module WidgetFactories
|
|
94
|
-
def paragraph(**kwargs)
|
|
95
|
-
Widgets::Paragraph.new(**kwargs)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def table(**kwargs)
|
|
99
|
-
Widgets::Table.new(**kwargs)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
```
|
|
105
|
-
<!-- SPDX-SnippetEnd -->
|
|
106
|
-
|
|
107
|
-
**What We Don't Do:**
|
|
108
|
-
|
|
109
|
-
<!-- SPDX-SnippetBegin -->
|
|
110
|
-
<!--
|
|
111
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
112
|
-
SPDX-License-Identifier: MIT-0
|
|
113
|
-
-->
|
|
114
|
-
```ruby
|
|
115
|
-
# NO: Dynamic method generation
|
|
116
|
-
RatatuiRuby.constants.each do |const|
|
|
117
|
-
define_method(const.underscore) { |**kw| RatatuiRuby.const_get(const).new(**kw) }
|
|
118
|
-
end
|
|
119
|
-
```
|
|
120
|
-
<!-- SPDX-SnippetEnd -->
|
|
121
|
-
|
|
122
|
-
**Benefits of Explicit Definitions:**
|
|
123
|
-
|
|
124
|
-
1. **IDE Support**: Solargraph and Ruby LSP provide autocomplete because methods exist at parse time.
|
|
125
|
-
2. **RDoc**: Each method can have its own documentation with examples.
|
|
126
|
-
3. **RBS Types**: Each method has an explicit type signature.
|
|
127
|
-
4. **Debugging**: Stack traces show real method names, not `define_method` closures.
|
|
128
|
-
5. **Decoupling**: Internal class names can change without breaking the public TUI API.
|
|
129
|
-
|
|
130
|
-
### 4. Data-Driven UI (Immediate Mode)
|
|
131
|
-
|
|
132
|
-
All UI components are pure, immutable `Data.define` value objects. They describe *desired appearance* for a single frame, not live stateful objects.
|
|
133
|
-
|
|
134
|
-
**Widgets Are Inputs:**
|
|
135
|
-
|
|
136
|
-
<!-- SPDX-SnippetBegin -->
|
|
137
|
-
<!--
|
|
138
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
139
|
-
SPDX-License-Identifier: MIT-0
|
|
140
|
-
-->
|
|
141
|
-
```ruby
|
|
142
|
-
# This is just data. It has no behavior, no side effects.
|
|
143
|
-
paragraph = RatatuiRuby::Widgets::Paragraph.new(
|
|
144
|
-
text: "Hello",
|
|
145
|
-
style: RatatuiRuby::Style::Style.new(fg: :red)
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
# Pass to renderer as input
|
|
149
|
-
frame.render_widget(paragraph, area)
|
|
150
|
-
```
|
|
151
|
-
<!-- SPDX-SnippetEnd -->
|
|
152
|
-
|
|
153
|
-
**Immediate Mode Loop:**
|
|
154
|
-
|
|
155
|
-
Every frame, the application constructs a fresh view tree and passes it to `draw`. No widget state persists between frames. This is Ratatui's core paradigm.
|
|
156
|
-
|
|
157
|
-
<!-- SPDX-SnippetBegin -->
|
|
158
|
-
<!--
|
|
159
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
160
|
-
SPDX-License-Identifier: MIT-0
|
|
161
|
-
-->
|
|
162
|
-
```ruby
|
|
163
|
-
loop do
|
|
164
|
-
tui.draw do |frame|
|
|
165
|
-
# Fresh tree every frame
|
|
166
|
-
frame.render_widget(tui.paragraph(text: "Time: #{Time.now}"), frame.area)
|
|
167
|
-
end
|
|
168
|
-
break if tui.poll_event.key? && tui.poll_event.code == "q"
|
|
169
|
-
end
|
|
170
|
-
```
|
|
171
|
-
<!-- SPDX-SnippetEnd -->
|
|
172
|
-
|
|
173
|
-
### 5. Separation of Configuration and Status
|
|
174
|
-
|
|
175
|
-
Widgets (Configuration) and State (Status) are strictly separated.
|
|
176
|
-
|
|
177
|
-
**Configuration (Input):**
|
|
178
|
-
|
|
179
|
-
Widgets define *what* to render. They are created, rendered, and discarded.
|
|
180
|
-
|
|
181
|
-
<!-- SPDX-SnippetBegin -->
|
|
182
|
-
<!--
|
|
183
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
184
|
-
SPDX-License-Identifier: MIT-0
|
|
185
|
-
-->
|
|
186
|
-
```ruby
|
|
187
|
-
list = tui.list(items: ["A", "B", "C", "D", "E"])
|
|
188
|
-
```
|
|
189
|
-
<!-- SPDX-SnippetEnd -->
|
|
190
|
-
|
|
191
|
-
**Status (Output):**
|
|
192
|
-
|
|
193
|
-
State objects track *runtime metrics* computed by the Rust backend: scroll offsets, selection positions, etc. They persist across frames.
|
|
194
|
-
|
|
195
|
-
<!-- SPDX-SnippetBegin -->
|
|
196
|
-
<!--
|
|
197
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
198
|
-
SPDX-License-Identifier: MIT-0
|
|
199
|
-
-->
|
|
200
|
-
```ruby
|
|
201
|
-
# Created once
|
|
202
|
-
@list_state = RatatuiRuby::ListState.new
|
|
203
|
-
|
|
204
|
-
# Used every frame
|
|
205
|
-
frame.render_stateful_widget(list, area, @list_state)
|
|
206
|
-
|
|
207
|
-
# Read back computed values
|
|
208
|
-
puts "Scroll offset: #{@list_state.offset}"
|
|
209
|
-
```
|
|
210
|
-
<!-- SPDX-SnippetEnd -->
|
|
211
|
-
|
|
212
|
-
**Precedence Rule:**
|
|
213
|
-
|
|
214
|
-
When using `render_stateful_widget`, the State object is the source of truth. Widget properties like `selected_index` are ignored.
|
|
215
|
-
|
|
216
|
-
### 6. No Render Logic in Ruby
|
|
217
|
-
|
|
218
|
-
Ruby defines data structures. Rust renders them.
|
|
219
|
-
|
|
220
|
-
The classes in `lib/ratatui_ruby/widgets/` contain no rendering code. They are pure structural definitions that the Rust extension walks and converts to Ratatui primitives.
|
|
221
|
-
|
|
222
|
-
**Ruby's Job:**
|
|
223
|
-
- Define `Data.define` classes with attributes
|
|
224
|
-
- Validate inputs (types, ranges)
|
|
225
|
-
- Provide convenience constructors
|
|
226
|
-
|
|
227
|
-
**Rust's Job:**
|
|
228
|
-
- Walk the Ruby object tree
|
|
229
|
-
- Extract attributes via `funcall`
|
|
230
|
-
- Construct Ratatui widgets
|
|
231
|
-
- Render to the terminal buffer
|
|
232
|
-
|
|
233
|
-
This separation ensures rendering performance remains in Rust while Ruby handles the ergonomic API layer.
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## Directory Structure
|
|
238
|
-
|
|
239
|
-
<!-- SPDX-SnippetBegin -->
|
|
240
|
-
<!--
|
|
241
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
242
|
-
SPDX-License-Identifier: MIT-0
|
|
243
|
-
-->
|
|
244
|
-
```
|
|
245
|
-
lib/ratatui_ruby/
|
|
246
|
-
├── tui.rb # TUI class, includes all mixins
|
|
247
|
-
├── tui/ # TUI facade mixins
|
|
248
|
-
│ ├── core.rb # draw, poll_event, get_cell_at
|
|
249
|
-
│ ├── layout_factories.rb # rect, constraint_*, layout_split
|
|
250
|
-
│ ├── style_factories.rb # style
|
|
251
|
-
│ ├── widget_factories.rb # paragraph, block, table, list, etc.
|
|
252
|
-
│ ├── text_factories.rb # span, line, text_width
|
|
253
|
-
│ ├── state_factories.rb # list_state, table_state, scrollbar_state
|
|
254
|
-
│ ├── canvas_factories.rb # shape_map, shape_line, etc.
|
|
255
|
-
│ └── buffer_factories.rb # cell (for buffer inspection)
|
|
256
|
-
├── layout/ # ratatui::layout
|
|
257
|
-
│ ├── rect.rb
|
|
258
|
-
│ ├── constraint.rb
|
|
259
|
-
│ └── layout.rb
|
|
260
|
-
├── widgets/ # ratatui::widgets
|
|
261
|
-
│ ├── paragraph.rb
|
|
262
|
-
│ ├── block.rb
|
|
263
|
-
│ ├── table.rb
|
|
264
|
-
│ ├── list.rb
|
|
265
|
-
│ ├── row.rb # Table row wrapper
|
|
266
|
-
│ ├── cell.rb # Table cell wrapper (NOT buffer cell)
|
|
267
|
-
│ └── ...
|
|
268
|
-
├── style/ # ratatui::style
|
|
269
|
-
│ └── style.rb
|
|
270
|
-
├── text/ # ratatui::text
|
|
271
|
-
│ ├── span.rb
|
|
272
|
-
│ └── line.rb
|
|
273
|
-
├── buffer/ # ratatui::buffer
|
|
274
|
-
│ └── cell.rb # For get_cell_at inspection
|
|
275
|
-
└── schema/ # Legacy location (being migrated)
|
|
276
|
-
```
|
|
277
|
-
<!-- SPDX-SnippetEnd -->
|
|
278
|
-
|
|
279
|
-
---
|
|
280
|
-
|
|
281
|
-
## Adding a New Widget
|
|
282
|
-
|
|
283
|
-
### Step 1: Create the Schema Class
|
|
284
|
-
|
|
285
|
-
Define the Data class in the appropriate namespace directory:
|
|
286
|
-
|
|
287
|
-
<!-- SPDX-SnippetBegin -->
|
|
288
|
-
<!--
|
|
289
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
290
|
-
SPDX-License-Identifier: MIT-0
|
|
291
|
-
-->
|
|
292
|
-
```ruby
|
|
293
|
-
# lib/ratatui_ruby/widgets/my_widget.rb
|
|
294
|
-
module RatatuiRuby
|
|
295
|
-
module Widgets
|
|
296
|
-
# A widget that displays foo with optional styling.
|
|
297
|
-
#
|
|
298
|
-
# [content] The text content to display.
|
|
299
|
-
# [style] Optional styling for the content.
|
|
300
|
-
# [block] Optional block border wrapper.
|
|
301
|
-
class MyWidget < Data.define(:content, :style, :block)
|
|
302
|
-
def initialize(content:, style: nil, block: nil)
|
|
303
|
-
super
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
```
|
|
309
|
-
<!-- SPDX-SnippetEnd -->
|
|
310
|
-
|
|
311
|
-
### Step 2: Add the RBS Type
|
|
312
|
-
|
|
313
|
-
<!-- SPDX-SnippetBegin -->
|
|
314
|
-
<!--
|
|
315
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
316
|
-
SPDX-License-Identifier: MIT-0
|
|
317
|
-
-->
|
|
318
|
-
```rbs
|
|
319
|
-
# sig/ratatui_ruby/widgets/my_widget.rbs
|
|
320
|
-
module RatatuiRuby
|
|
321
|
-
module Widgets
|
|
322
|
-
class MyWidget < Data
|
|
323
|
-
attr_reader content: String
|
|
324
|
-
attr_reader style: Style::Style?
|
|
325
|
-
attr_reader block: Block?
|
|
326
|
-
|
|
327
|
-
def self.new: (content: String, ?style: Style::Style?, ?block: Block?) -> MyWidget
|
|
328
|
-
end
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
```
|
|
332
|
-
<!-- SPDX-SnippetEnd -->
|
|
333
|
-
|
|
334
|
-
### Step 3: Add the TUI Factory Method
|
|
335
|
-
|
|
336
|
-
<!-- SPDX-SnippetBegin -->
|
|
337
|
-
<!--
|
|
338
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
339
|
-
SPDX-License-Identifier: MIT-0
|
|
340
|
-
-->
|
|
341
|
-
```ruby
|
|
342
|
-
# lib/ratatui_ruby/tui/widget_factories.rb
|
|
343
|
-
def my_widget(**kwargs)
|
|
344
|
-
Widgets::MyWidget.new(**kwargs)
|
|
345
|
-
end
|
|
346
|
-
```
|
|
347
|
-
<!-- SPDX-SnippetEnd -->
|
|
348
|
-
|
|
349
|
-
### Step 4: Implement Rust Rendering
|
|
350
|
-
|
|
351
|
-
See `rust_backend.md` for the Rust implementation steps.
|
|
352
|
-
|
|
353
|
-
### Step 5: Register in Requires
|
|
354
|
-
|
|
355
|
-
Add to `lib/ratatui_ruby.rb`:
|
|
356
|
-
|
|
357
|
-
<!-- SPDX-SnippetBegin -->
|
|
358
|
-
<!--
|
|
359
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
360
|
-
SPDX-License-Identifier: MIT-0
|
|
361
|
-
-->
|
|
362
|
-
```ruby
|
|
363
|
-
require_relative "ratatui_ruby/widgets/my_widget"
|
|
364
|
-
```
|
|
365
|
-
<!-- SPDX-SnippetEnd -->
|
|
366
|
-
|
|
367
|
-
---
|
|
368
|
-
|
|
369
|
-
## TUI Mixin Architecture
|
|
370
|
-
|
|
371
|
-
The `TUI` class is composed of 8 focused mixins, each with a single responsibility:
|
|
372
|
-
|
|
373
|
-
| Mixin | Methods | Purpose |
|
|
374
|
-
|-------|---------|---------|
|
|
375
|
-
| `Core` | `draw`, `poll_event`, `get_cell_at`, `draw_cell` | Terminal I/O operations |
|
|
376
|
-
| `LayoutFactories` | `rect`, `constraint_*`, `layout`, `layout_split` | Layout construction |
|
|
377
|
-
| `StyleFactories` | `style` | Style construction |
|
|
378
|
-
| `WidgetFactories` | `paragraph`, `block`, `table`, `list`, etc. | Widget construction |
|
|
379
|
-
| `TextFactories` | `span`, `line`, `text_width` | Text construction |
|
|
380
|
-
| `StateFactories` | `list_state`, `table_state`, `scrollbar_state` | State object construction |
|
|
381
|
-
| `CanvasFactories` | `shape_map`, `shape_line`, `shape_circle`, etc. | Canvas shape construction |
|
|
382
|
-
| `BufferFactories` | `cell` | Buffer cell construction (for testing) |
|
|
383
|
-
|
|
384
|
-
This modular structure keeps each file focused (~20-50 lines) and makes it easy to locate and modify factory methods.
|
|
385
|
-
|
|
386
|
-
---
|
|
387
|
-
|
|
388
|
-
## Thread and Ractor Safety
|
|
389
|
-
|
|
390
|
-
### Shareable (Frozen Data Objects)
|
|
391
|
-
|
|
392
|
-
These are deeply frozen and `Ractor.shareable?`:
|
|
393
|
-
|
|
394
|
-
- `Event::*` objects from `poll_event`
|
|
395
|
-
- `Buffer::Cell` objects from `get_cell_at`
|
|
396
|
-
- `Layout::Rect` objects from `Layout.split`
|
|
397
|
-
|
|
398
|
-
### Not Shareable (I/O Handles)
|
|
399
|
-
|
|
400
|
-
These have side effects and are intentionally not Ractor-safe:
|
|
401
|
-
|
|
402
|
-
- `TUI` — Has terminal I/O methods
|
|
403
|
-
- `Frame` — Valid only during the `draw` block; invalid after
|
|
404
|
-
|
|
405
|
-
<!-- SPDX-SnippetBegin -->
|
|
406
|
-
<!--
|
|
407
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
408
|
-
SPDX-License-Identifier: MIT-0
|
|
409
|
-
-->
|
|
410
|
-
```ruby
|
|
411
|
-
# OK: Cache TUI during run loop
|
|
412
|
-
RatatuiRuby.run do |tui|
|
|
413
|
-
@tui = tui
|
|
414
|
-
loop { render; handle_input }
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
# NOT OK: Include in immutable Model
|
|
418
|
-
Model = Data.define(:tui, :count) # Don't do this
|
|
419
|
-
```
|
|
420
|
-
<!-- SPDX-SnippetEnd -->
|