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,328 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
9
|
-
require "ratatui_ruby"
|
|
10
|
-
require "faker"
|
|
11
|
-
|
|
12
|
-
# A "Master Class" example demonstrating Stateful Widget Rendering and Interaction.
|
|
13
|
-
#
|
|
14
|
-
# This example shows how to:
|
|
15
|
-
# 1. Use mutable State objects (ListState, TableState) for selection and scrolling
|
|
16
|
-
# 2. Read back the calculated scroll offset from the backend (state.offset)
|
|
17
|
-
# 3. Implement precise mouse-click-to-row interaction using that offset
|
|
18
|
-
class AppStatefulInteraction
|
|
19
|
-
def initialize
|
|
20
|
-
# Data Models
|
|
21
|
-
# Tables are the categories on the left
|
|
22
|
-
@tables = ["Users", "Orders", "Products", "Invoices", "Audit Logs"]
|
|
23
|
-
@headers = {
|
|
24
|
-
"Users" => ["Name", "Email", "Role"],
|
|
25
|
-
"Orders" => ["Order ID", "Status", "Amount"],
|
|
26
|
-
"Products" => ["Product", "SKU", "Status"],
|
|
27
|
-
"Invoices" => ["Invoice #", "Status", "Amount"],
|
|
28
|
-
"Audit Logs" => ["Event", "Action", "IP Address"],
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# Generate dummy data for each table
|
|
32
|
-
# Use fixed seed for deterministic behavior in CI/Tests
|
|
33
|
-
if ENV["CI"] == "true" || ENV["RATA_SEED"]
|
|
34
|
-
seed = (ENV["RATA_SEED"] || 12345).to_i
|
|
35
|
-
Faker::Config.random = Random.new(seed)
|
|
36
|
-
# Also seed Kernel.rand/Array#sample just in case
|
|
37
|
-
srand(seed)
|
|
38
|
-
end
|
|
39
|
-
rand_price = -> { "$#{Faker::Commerce.price(range: 10..500.0)}" }
|
|
40
|
-
|
|
41
|
-
@data = {
|
|
42
|
-
"Users" => Array.new(50) { [Faker::Name.name, Faker::Internet.email, %w[Admin Editor Viewer].sample] },
|
|
43
|
-
"Orders" => Array.new(50) { [Faker::Commerce.promotion_code(digits: 4), ["Completed", "Pending", "Failed"].sample, rand_price.call] },
|
|
44
|
-
"Products" => Array.new(50) { [Faker::Commerce.product_name, "SKU-#{Faker::Number.number(digits: 4)}", ["In Stock", "Low Stock"].sample] },
|
|
45
|
-
"Invoices" => Array.new(50) { ["INV-#{Faker::Number.number(digits: 6)}", ["Paid", "Unpaid"].sample, rand_price.call] },
|
|
46
|
-
"Audit Logs" => Array.new(50) { ["Log #{Faker::Number.unique.number(digits: 3)}", ["Login Success", "Login Failed", "Logout"].sample, Faker::Internet.ip_v4_address] },
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# State Objects - These are mutable and persist across frames!
|
|
50
|
-
@list_state = RatatuiRuby::ListState.new(nil)
|
|
51
|
-
@table_state = RatatuiRuby::TableState.new(nil)
|
|
52
|
-
|
|
53
|
-
# Initialize selection
|
|
54
|
-
@list_state.select(0)
|
|
55
|
-
@table_state.select(0)
|
|
56
|
-
|
|
57
|
-
# Active Pane Focus (:list or :table)
|
|
58
|
-
@active_pane = :list
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def run
|
|
62
|
-
RatatuiRuby.run do |tui|
|
|
63
|
-
@tui = tui
|
|
64
|
-
|
|
65
|
-
# Styles can only be created once TUI is initialized
|
|
66
|
-
@style_active = @tui.style(fg: :yellow, modifiers: [:bold])
|
|
67
|
-
@style_inactive = @tui.style(fg: :dark_gray)
|
|
68
|
-
@style_highlight = @tui.style(bg: :blue, fg: :white, modifiers: [:bold])
|
|
69
|
-
|
|
70
|
-
loop do
|
|
71
|
-
render
|
|
72
|
-
break if handle_input == :quit
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
private def render
|
|
78
|
-
@tui.draw do |frame|
|
|
79
|
-
# 1. Layout
|
|
80
|
-
main_area, help_area = @tui.layout_split(
|
|
81
|
-
frame.area,
|
|
82
|
-
direction: :vertical,
|
|
83
|
-
constraints: [
|
|
84
|
-
@tui.constraint_fill(1),
|
|
85
|
-
@tui.constraint_length(1),
|
|
86
|
-
]
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
list_area, table_area = @tui.layout_split(
|
|
90
|
-
main_area,
|
|
91
|
-
direction: :horizontal,
|
|
92
|
-
constraints: [
|
|
93
|
-
@tui.constraint_percentage(30),
|
|
94
|
-
@tui.constraint_percentage(70),
|
|
95
|
-
]
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
# Save areas for hit testing
|
|
99
|
-
@list_area = list_area
|
|
100
|
-
@table_area = table_area
|
|
101
|
-
|
|
102
|
-
# 2. Render List (Left Pane)
|
|
103
|
-
render_list(frame, list_area)
|
|
104
|
-
|
|
105
|
-
# 3. Render Table (Right Pane)
|
|
106
|
-
render_table(frame, table_area)
|
|
107
|
-
|
|
108
|
-
# 4. Render Help
|
|
109
|
-
help_text = "q: Quit | Tab/Arrows: Nav | Home/End: Jump | Mouse: Click rows"
|
|
110
|
-
frame.render_widget(@tui.paragraph(text: help_text), help_area)
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
private def render_list(frame, area)
|
|
115
|
-
is_active = @active_pane == :list
|
|
116
|
-
|
|
117
|
-
# Render main list
|
|
118
|
-
list = @tui.list(
|
|
119
|
-
items: @tables,
|
|
120
|
-
block: @tui.block(
|
|
121
|
-
title: " Tables ",
|
|
122
|
-
borders: [:all],
|
|
123
|
-
border_style: is_active ? @style_active : @style_inactive
|
|
124
|
-
),
|
|
125
|
-
highlight_style: @style_highlight
|
|
126
|
-
)
|
|
127
|
-
# KEY STEP: Pass the state object!
|
|
128
|
-
frame.render_stateful_widget(list, area, @list_state)
|
|
129
|
-
|
|
130
|
-
# Render Scrollbar
|
|
131
|
-
scrollbar = @tui.scrollbar(
|
|
132
|
-
content_length: 0,
|
|
133
|
-
position: 0,
|
|
134
|
-
orientation: :vertical_right,
|
|
135
|
-
track_symbol: nil,
|
|
136
|
-
thumb_symbol: "▐"
|
|
137
|
-
)
|
|
138
|
-
scrollbar_state = RatatuiRuby::ScrollbarState.new(@tables.size)
|
|
139
|
-
scrollbar_state.position = @list_state.offset
|
|
140
|
-
scrollbar_state.viewport_content_length = area.height - 2
|
|
141
|
-
|
|
142
|
-
frame.render_stateful_widget(scrollbar, area, scrollbar_state)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
private def render_table(frame, area)
|
|
146
|
-
is_active = @active_pane == :table
|
|
147
|
-
|
|
148
|
-
# Get current data based on list selection
|
|
149
|
-
current_table = @tables[@list_state.selected || 0]
|
|
150
|
-
rows = @data[current_table]
|
|
151
|
-
|
|
152
|
-
# Render table
|
|
153
|
-
table = @tui.table(
|
|
154
|
-
rows:,
|
|
155
|
-
header: @headers[current_table],
|
|
156
|
-
widths: [
|
|
157
|
-
@tui.constraint_percentage(30),
|
|
158
|
-
@tui.constraint_percentage(40),
|
|
159
|
-
@tui.constraint_percentage(30),
|
|
160
|
-
],
|
|
161
|
-
block: @tui.block(
|
|
162
|
-
title: " #{current_table} Data ",
|
|
163
|
-
borders: [:all],
|
|
164
|
-
border_style: is_active ? @style_active : @style_inactive
|
|
165
|
-
),
|
|
166
|
-
row_highlight_style: @style_highlight
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
frame.render_stateful_widget(table, area, @table_state)
|
|
170
|
-
|
|
171
|
-
# Render Scrollbar
|
|
172
|
-
scrollbar = @tui.scrollbar(
|
|
173
|
-
content_length: 0,
|
|
174
|
-
position: 0,
|
|
175
|
-
orientation: :vertical_right,
|
|
176
|
-
track_symbol: nil,
|
|
177
|
-
thumb_symbol: "▐"
|
|
178
|
-
)
|
|
179
|
-
scrollbar_state = RatatuiRuby::ScrollbarState.new(rows.size)
|
|
180
|
-
scrollbar_state.position = @table_state.offset
|
|
181
|
-
scrollbar_state.viewport_content_length = area.height - 4 # borders + header + margin
|
|
182
|
-
|
|
183
|
-
frame.render_stateful_widget(scrollbar, area, scrollbar_state)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
private def handle_input
|
|
187
|
-
case @tui.poll_event
|
|
188
|
-
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
189
|
-
:quit
|
|
190
|
-
|
|
191
|
-
# Navigation
|
|
192
|
-
in { type: :key, code: "tab" } | { type: :key, code: "right" } | { type: :key, code: "left" }
|
|
193
|
-
@active_pane = (@active_pane == :list) ? :table : :list
|
|
194
|
-
|
|
195
|
-
in { type: :key, code: "down" }
|
|
196
|
-
move_selection_next
|
|
197
|
-
|
|
198
|
-
in { type: :key, code: "up" }
|
|
199
|
-
move_selection_previous
|
|
200
|
-
|
|
201
|
-
in { type: :key, code: "home" }
|
|
202
|
-
move_selection_first
|
|
203
|
-
|
|
204
|
-
in { type: :key, code: "end" }
|
|
205
|
-
move_selection_last
|
|
206
|
-
|
|
207
|
-
# Mouse Interaction
|
|
208
|
-
in { type: :mouse, kind: "down", x:, y: }
|
|
209
|
-
handle_click(x, y)
|
|
210
|
-
|
|
211
|
-
else
|
|
212
|
-
# no-op
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
private def move_selection_next
|
|
217
|
-
if @active_pane == :list
|
|
218
|
-
current = @list_state.selected || 0
|
|
219
|
-
max_index = @tables.size - 1
|
|
220
|
-
return if current >= max_index # Already at end
|
|
221
|
-
|
|
222
|
-
@list_state.select_next
|
|
223
|
-
reset_table_selection
|
|
224
|
-
else
|
|
225
|
-
current = @table_state.selected || 0
|
|
226
|
-
max_index = current_table_rows.size - 1
|
|
227
|
-
return if current >= max_index
|
|
228
|
-
|
|
229
|
-
@table_state.select_next
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
private def move_selection_previous
|
|
234
|
-
if @active_pane == :list
|
|
235
|
-
current = @list_state.selected || 0
|
|
236
|
-
return if current <= 0 # Already at start
|
|
237
|
-
|
|
238
|
-
@list_state.select_previous
|
|
239
|
-
reset_table_selection
|
|
240
|
-
else
|
|
241
|
-
current = @table_state.selected || 0
|
|
242
|
-
return if current <= 0
|
|
243
|
-
|
|
244
|
-
@table_state.select_previous
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
private def move_selection_first
|
|
249
|
-
if @active_pane == :list
|
|
250
|
-
current = @list_state.selected || 0
|
|
251
|
-
return if current == 0 # Already at first
|
|
252
|
-
|
|
253
|
-
@list_state.select_first
|
|
254
|
-
reset_table_selection
|
|
255
|
-
else
|
|
256
|
-
@table_state.select_first
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
private def move_selection_last
|
|
261
|
-
if @active_pane == :list
|
|
262
|
-
current = @list_state.selected || 0
|
|
263
|
-
max_index = @tables.size - 1
|
|
264
|
-
return if current == max_index # Already at last
|
|
265
|
-
|
|
266
|
-
@list_state.select(max_index)
|
|
267
|
-
reset_table_selection
|
|
268
|
-
else
|
|
269
|
-
@table_state.select(current_table_rows.size - 1)
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
private def reset_table_selection
|
|
274
|
-
@table_state.select(0)
|
|
275
|
-
@table_state.select_column(nil)
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
private def current_table_rows
|
|
279
|
-
@data[@tables[@list_state.selected || 0]]
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
private def handle_click(x, y)
|
|
283
|
-
if @list_area.contains?(x, y)
|
|
284
|
-
handle_list_click(y)
|
|
285
|
-
elsif @table_area.contains?(x, y)
|
|
286
|
-
handle_table_click(y)
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
private def handle_list_click(mouse_y)
|
|
291
|
-
@active_pane = :list
|
|
292
|
-
|
|
293
|
-
# CRITICAL: Read back the offset!
|
|
294
|
-
# Formula: clicked_index = (mouse_y - list_top - border_width) + offset
|
|
295
|
-
offset = @list_state.offset
|
|
296
|
-
list_top = @list_area.y
|
|
297
|
-
border_width = 1 # Top border
|
|
298
|
-
|
|
299
|
-
clicked_row = (mouse_y - list_top - border_width) + offset
|
|
300
|
-
|
|
301
|
-
if clicked_row >= 0 && clicked_row < @tables.size
|
|
302
|
-
@list_state.select(clicked_row)
|
|
303
|
-
@table_state.select(0) # Reset table when category changes
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
private def handle_table_click(mouse_y)
|
|
308
|
-
@active_pane = :table
|
|
309
|
-
|
|
310
|
-
# CRITICAL: Read back the offset!
|
|
311
|
-
# Formula: clicked_index = (mouse_y - table_top - border - header_height - margin) + offset
|
|
312
|
-
offset = @table_state.offset
|
|
313
|
-
table_top = @table_area.y
|
|
314
|
-
border_width = 1
|
|
315
|
-
header_height = 1
|
|
316
|
-
# No header_margin without Row margin
|
|
317
|
-
effective_top = table_top + border_width + header_height
|
|
318
|
-
|
|
319
|
-
clicked_row = (mouse_y - effective_top) + offset
|
|
320
|
-
|
|
321
|
-
current_table_data = @data[@tables[@list_state.selected || 0]]
|
|
322
|
-
if clicked_row >= 0 && clicked_row < current_table_data.size
|
|
323
|
-
@table_state.select(clicked_row)
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
AppStatefulInteraction.new.run if __FILE__ == $PROGRAM_NAME
|
data/examples/timeout_demo.rb
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
# Timeout Demo: Non-Blocking Event Polling
|
|
9
|
-
#
|
|
10
|
-
# This demo shows how to use poll_event with a timeout for game loops
|
|
11
|
-
# and animation systems that need to update at a fixed frame rate
|
|
12
|
-
# regardless of user input.
|
|
13
|
-
#
|
|
14
|
-
# Run: bundle exec ruby examples/timeout_demo.rb
|
|
15
|
-
#
|
|
16
|
-
# Expected behavior:
|
|
17
|
-
# - "Tick..." prints every 100ms continuously
|
|
18
|
-
# - Pressing a key prints "Key Pressed: [key]" immediately
|
|
19
|
-
# - Press 'q' to quit
|
|
20
|
-
|
|
21
|
-
require "bundler/setup"
|
|
22
|
-
require "ratatui_ruby"
|
|
23
|
-
|
|
24
|
-
puts "Timeout Demo - Press 'q' to quit"
|
|
25
|
-
puts "Watch: continuous ticks with responsive key handling"
|
|
26
|
-
puts
|
|
27
|
-
|
|
28
|
-
tick_count = 0
|
|
29
|
-
running = true
|
|
30
|
-
|
|
31
|
-
while running
|
|
32
|
-
# Poll with 100ms timeout (~10 FPS tick rate)
|
|
33
|
-
event = RatatuiRuby.poll_event(timeout: 0.1)
|
|
34
|
-
|
|
35
|
-
if event.none?
|
|
36
|
-
# No input, just tick
|
|
37
|
-
tick_count += 1
|
|
38
|
-
puts "Tick #{tick_count}..."
|
|
39
|
-
elsif event.key?
|
|
40
|
-
puts "Key Pressed: #{event.code}"
|
|
41
|
-
running = false if event.code == "q"
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
puts "\nGoodbye!"
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Quickstart DSL Verification
|
|
7
|
-
|
|
8
|
-
Verifies the "Simplified API" tutorial in the [Quickstart](../../doc/getting_started/quickstart.md#simplified-api).
|
|
9
|
-
|
|
10
|
-
This example exists as a documentation regression test. It ensures the recommended TUI facade and managed lifecycle workflow remains functional.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
<!-- SPDX-SnippetBegin -->
|
|
15
|
-
<!--
|
|
16
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
17
|
-
SPDX-License-Identifier: MIT-0
|
|
18
|
-
-->
|
|
19
|
-
<!-- SYNC:START:app.rb:main -->
|
|
20
|
-
```ruby
|
|
21
|
-
# 1. Initialize the terminal, start the run loop, and ensure the terminal is restored.
|
|
22
|
-
RatatuiRuby.run do |tui|
|
|
23
|
-
loop do
|
|
24
|
-
# 2. Create your UI with methods instead of classes.
|
|
25
|
-
view = tui.paragraph(
|
|
26
|
-
text: "Hello, Ratatui! Press 'q' to quit.",
|
|
27
|
-
alignment: :center,
|
|
28
|
-
block: tui.block(
|
|
29
|
-
title: "My Ruby TUI App",
|
|
30
|
-
title_alignment: :center,
|
|
31
|
-
borders: [:all],
|
|
32
|
-
border_color: "cyan",
|
|
33
|
-
style: { fg: "white" }
|
|
34
|
-
)
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
# 3. Use RatatuiRuby methods, too.
|
|
38
|
-
tui.draw do |frame|
|
|
39
|
-
frame.render_widget(view, frame.area)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# 4. Poll for events with pattern matching
|
|
43
|
-
case tui.poll_event
|
|
44
|
-
in { type: :key, code: "q" }
|
|
45
|
-
break
|
|
46
|
-
else
|
|
47
|
-
# Ignore other events
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
```
|
|
52
|
-
<!-- SYNC:END -->
|
|
53
|
-
<!-- SPDX-SnippetEnd -->
|
|
54
|
-
|
|
55
|
-
[](../../doc/getting_started/quickstart.md#simplified-api)
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: MIT-0
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
9
|
-
|
|
10
|
-
require "ratatui_ruby"
|
|
11
|
-
|
|
12
|
-
class VerifyQuickstartDsl
|
|
13
|
-
def run
|
|
14
|
-
# [SYNC:START:main]
|
|
15
|
-
# 1. Initialize the terminal, start the run loop, and ensure the terminal is restored.
|
|
16
|
-
RatatuiRuby.run do |tui|
|
|
17
|
-
loop do
|
|
18
|
-
# 2. Create your UI with methods instead of classes.
|
|
19
|
-
view = tui.paragraph(
|
|
20
|
-
text: "Hello, Ratatui! Press 'q' to quit.",
|
|
21
|
-
alignment: :center,
|
|
22
|
-
block: tui.block(
|
|
23
|
-
title: "My Ruby TUI App",
|
|
24
|
-
title_alignment: :center,
|
|
25
|
-
borders: [:all],
|
|
26
|
-
border_style: { fg: "cyan" },
|
|
27
|
-
style: { fg: "white" }
|
|
28
|
-
)
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
# 3. Use RatatuiRuby methods, too.
|
|
32
|
-
tui.draw do |frame|
|
|
33
|
-
frame.render_widget(view, frame.area)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# 4. Poll for events with pattern matching
|
|
37
|
-
case tui.poll_event
|
|
38
|
-
in { type: :key, code: "q" }
|
|
39
|
-
break
|
|
40
|
-
else
|
|
41
|
-
# Ignore other events
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
# [SYNC:END:main]
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
VerifyQuickstartDsl.new.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Quickstart Layout Verification
|
|
7
|
-
|
|
8
|
-
Verifies the "Adding Layouts" tutorial in the [Quickstart](../../doc/getting_started/quickstart.md#adding-layouts).
|
|
9
|
-
|
|
10
|
-
This example exists as a documentation regression test. It ensures the layout and constraints examples remain functional.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
<!-- SPDX-SnippetBegin -->
|
|
15
|
-
<!--
|
|
16
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
17
|
-
SPDX-License-Identifier: MIT-0
|
|
18
|
-
-->
|
|
19
|
-
<!-- SYNC:START:app.rb:main -->
|
|
20
|
-
```ruby
|
|
21
|
-
loop do
|
|
22
|
-
tui.draw do |frame|
|
|
23
|
-
# 1. Split the screen
|
|
24
|
-
top, bottom = tui.layout_split(
|
|
25
|
-
frame.area,
|
|
26
|
-
direction: :vertical,
|
|
27
|
-
constraints: [
|
|
28
|
-
tui.constraint_percentage(75),
|
|
29
|
-
tui.constraint_percentage(25),
|
|
30
|
-
]
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
# 2. Render Top Widget
|
|
34
|
-
frame.render_widget(
|
|
35
|
-
tui.paragraph(
|
|
36
|
-
text: "Hello, Ratatui!",
|
|
37
|
-
alignment: :center,
|
|
38
|
-
block: tui.block(title: "Content", borders: [:all], border_color: "cyan")
|
|
39
|
-
),
|
|
40
|
-
top
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
# 3. Render Bottom Widget with Styled Text
|
|
44
|
-
# We use a Line of Spans to style specific characters
|
|
45
|
-
text_line = tui.text_line(
|
|
46
|
-
spans: [
|
|
47
|
-
tui.text_span(content: "Press '"),
|
|
48
|
-
tui.text_span(
|
|
49
|
-
content: "q",
|
|
50
|
-
style: tui.style(modifiers: [:bold, :underlined])
|
|
51
|
-
),
|
|
52
|
-
tui.text_span(content: "' to quit."),
|
|
53
|
-
],
|
|
54
|
-
alignment: :center
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
frame.render_widget(
|
|
58
|
-
tui.paragraph(
|
|
59
|
-
text: text_line,
|
|
60
|
-
block: tui.block(title: "Controls", borders: [:all])
|
|
61
|
-
),
|
|
62
|
-
bottom
|
|
63
|
-
)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
case tui.poll_event
|
|
67
|
-
in { type: :key, code: "q" }
|
|
68
|
-
break
|
|
69
|
-
else
|
|
70
|
-
# Ignore other events
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
```
|
|
74
|
-
<!-- SYNC:END -->
|
|
75
|
-
<!-- SPDX-SnippetEnd -->
|
|
76
|
-
|
|
77
|
-
[](../../doc/getting_started/quickstart.md#adding-layouts)
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: MIT-0
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
9
|
-
|
|
10
|
-
require "ratatui_ruby"
|
|
11
|
-
|
|
12
|
-
class VerifyQuickstartLayout
|
|
13
|
-
def run
|
|
14
|
-
RatatuiRuby.run do |tui|
|
|
15
|
-
# [SYNC:START:main]
|
|
16
|
-
loop do
|
|
17
|
-
tui.draw do |frame|
|
|
18
|
-
# 1. Split the screen
|
|
19
|
-
top, bottom = tui.layout_split(
|
|
20
|
-
frame.area,
|
|
21
|
-
direction: :vertical,
|
|
22
|
-
constraints: [
|
|
23
|
-
tui.constraint_percentage(75),
|
|
24
|
-
tui.constraint_percentage(25),
|
|
25
|
-
]
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# 2. Render Top Widget
|
|
29
|
-
frame.render_widget(
|
|
30
|
-
tui.paragraph(
|
|
31
|
-
text: "Hello, Ratatui!",
|
|
32
|
-
alignment: :center,
|
|
33
|
-
block: tui.block(title: "Content", borders: [:all], border_style: { fg: "cyan" })
|
|
34
|
-
),
|
|
35
|
-
top
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
# 3. Render Bottom Widget with Styled Text
|
|
39
|
-
# We use a Line of Spans to style specific characters
|
|
40
|
-
text_line = tui.text_line(
|
|
41
|
-
spans: [
|
|
42
|
-
tui.text_span(content: "Press '"),
|
|
43
|
-
tui.text_span(
|
|
44
|
-
content: "q",
|
|
45
|
-
style: tui.style(modifiers: [:bold, :underlined])
|
|
46
|
-
),
|
|
47
|
-
tui.text_span(content: "' to quit."),
|
|
48
|
-
],
|
|
49
|
-
alignment: :center
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
frame.render_widget(
|
|
53
|
-
tui.paragraph(
|
|
54
|
-
text: text_line,
|
|
55
|
-
block: tui.block(title: "Controls", borders: [:all])
|
|
56
|
-
),
|
|
57
|
-
bottom
|
|
58
|
-
)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
case tui.poll_event
|
|
62
|
-
in { type: :key, code: "q" }
|
|
63
|
-
break
|
|
64
|
-
else
|
|
65
|
-
# Ignore other events
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
# [SYNC:END:main]
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
VerifyQuickstartLayout.new.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Quickstart Lifecycle Verification
|
|
7
|
-
|
|
8
|
-
Verifies the "Basic Application" tutorial in the [Quickstart](../../doc/getting_started/quickstart.md#basic-application).
|
|
9
|
-
|
|
10
|
-
This example exists as a documentation regression test. It ensures the core lifecycle example presented to new users remains functional.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
<!-- SPDX-SnippetBegin -->
|
|
15
|
-
<!--
|
|
16
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
17
|
-
SPDX-License-Identifier: MIT-0
|
|
18
|
-
-->
|
|
19
|
-
<!-- SYNC:START:app.rb:main -->
|
|
20
|
-
```ruby
|
|
21
|
-
# 1. Initialize the terminal
|
|
22
|
-
RatatuiRuby.init_terminal
|
|
23
|
-
|
|
24
|
-
begin
|
|
25
|
-
# The Main Loop
|
|
26
|
-
loop do
|
|
27
|
-
# 2. Create your UI (Immediate Mode)
|
|
28
|
-
# We define a Paragraph widget inside a Block with a title and borders.
|
|
29
|
-
view = RatatuiRuby::Widgets::Paragraph.new(
|
|
30
|
-
text: "Hello, Ratatui! Press 'q' to quit.",
|
|
31
|
-
alignment: :center,
|
|
32
|
-
block: RatatuiRuby::Widgets::Block.new(
|
|
33
|
-
title: "My Ruby TUI App",
|
|
34
|
-
title_alignment: :center,
|
|
35
|
-
borders: [:all],
|
|
36
|
-
border_color: "cyan",
|
|
37
|
-
style: { fg: "white" }
|
|
38
|
-
)
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
# 3. Draw the UI
|
|
42
|
-
RatatuiRuby.draw do |frame|
|
|
43
|
-
frame.render_widget(view, frame.area)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# 4. Poll for events
|
|
47
|
-
case RatatuiRuby.poll_event
|
|
48
|
-
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
49
|
-
break
|
|
50
|
-
else
|
|
51
|
-
nil
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# 5. Guard against accidental output (optional but recommended)
|
|
55
|
-
# Wrap any code that might puts/warn to prevent screen corruption.
|
|
56
|
-
RatatuiRuby.guard_io do
|
|
57
|
-
# SomeChattyGem.do_something
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
ensure
|
|
61
|
-
# 6. Restore the terminal to its original state
|
|
62
|
-
RatatuiRuby.restore_terminal
|
|
63
|
-
end
|
|
64
|
-
```
|
|
65
|
-
<!-- SYNC:END -->
|
|
66
|
-
<!-- SPDX-SnippetEnd -->
|
|
67
|
-
|
|
68
|
-
[](../../doc/getting_started/quickstart.md#basic-application)
|