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.
Files changed (236) hide show
  1. checksums.yaml +4 -4
  2. data/ext/ratatui_ruby/Cargo.lock +1 -1
  3. data/ext/ratatui_ruby/Cargo.toml +1 -1
  4. data/lib/ratatui_ruby/version.rb +1 -1
  5. metadata +1 -232
  6. data/.builds/ruby-3.2.yml +0 -54
  7. data/.builds/ruby-3.3.yml +0 -54
  8. data/.builds/ruby-3.4.yml +0 -54
  9. data/.builds/ruby-4.0.0.yml +0 -54
  10. data/.pre-commit-config.yaml +0 -16
  11. data/.rubocop.yml +0 -10
  12. data/AGENTS.md +0 -146
  13. data/CHANGELOG.md +0 -710
  14. data/README.md +0 -187
  15. data/README.rdoc +0 -302
  16. data/Rakefile +0 -11
  17. data/Steepfile +0 -49
  18. data/doc/concepts/application_architecture.md +0 -321
  19. data/doc/concepts/application_testing.md +0 -193
  20. data/doc/concepts/async.md +0 -190
  21. data/doc/concepts/custom_widgets.md +0 -247
  22. data/doc/concepts/debugging.md +0 -401
  23. data/doc/concepts/event_handling.md +0 -162
  24. data/doc/concepts/interactive_design.md +0 -146
  25. data/doc/contributors/auditing/parity.md +0 -239
  26. data/doc/contributors/design/ruby_frontend.md +0 -420
  27. data/doc/contributors/design/rust_backend.md +0 -422
  28. data/doc/contributors/design.md +0 -11
  29. data/doc/contributors/developing_examples.md +0 -400
  30. data/doc/contributors/documentation_style.md +0 -121
  31. data/doc/contributors/index.md +0 -21
  32. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -375
  33. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -206
  34. data/doc/contributors/todo/align/terminal.md +0 -647
  35. data/doc/contributors/todo/future_work.md +0 -169
  36. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  37. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  38. data/doc/custom.css +0 -22
  39. data/doc/getting_started/quickstart.md +0 -291
  40. data/doc/getting_started/why.md +0 -93
  41. data/doc/images/app_all_events.png +0 -0
  42. data/doc/images/app_cli_rich_moments.gif +0 -0
  43. data/doc/images/app_color_picker.png +0 -0
  44. data/doc/images/app_debugging_showcase.gif +0 -0
  45. data/doc/images/app_debugging_showcase.png +0 -0
  46. data/doc/images/app_login_form.png +0 -0
  47. data/doc/images/app_stateful_interaction.png +0 -0
  48. data/doc/images/verify_quickstart_dsl.png +0 -0
  49. data/doc/images/verify_quickstart_layout.png +0 -0
  50. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  51. data/doc/images/verify_readme_usage.png +0 -0
  52. data/doc/images/widget_barchart.png +0 -0
  53. data/doc/images/widget_block.png +0 -0
  54. data/doc/images/widget_box.png +0 -0
  55. data/doc/images/widget_calendar.png +0 -0
  56. data/doc/images/widget_canvas.png +0 -0
  57. data/doc/images/widget_cell.png +0 -0
  58. data/doc/images/widget_center.png +0 -0
  59. data/doc/images/widget_chart.png +0 -0
  60. data/doc/images/widget_gauge.png +0 -0
  61. data/doc/images/widget_layout_split.png +0 -0
  62. data/doc/images/widget_line_gauge.png +0 -0
  63. data/doc/images/widget_list.png +0 -0
  64. data/doc/images/widget_map.png +0 -0
  65. data/doc/images/widget_overlay.png +0 -0
  66. data/doc/images/widget_popup.png +0 -0
  67. data/doc/images/widget_ratatui_logo.png +0 -0
  68. data/doc/images/widget_ratatui_mascot.png +0 -0
  69. data/doc/images/widget_rect.png +0 -0
  70. data/doc/images/widget_render.png +0 -0
  71. data/doc/images/widget_rich_text.png +0 -0
  72. data/doc/images/widget_scroll_text.png +0 -0
  73. data/doc/images/widget_scrollbar.png +0 -0
  74. data/doc/images/widget_sparkline.png +0 -0
  75. data/doc/images/widget_style_colors.png +0 -0
  76. data/doc/images/widget_table.png +0 -0
  77. data/doc/images/widget_tabs.png +0 -0
  78. data/doc/images/widget_text_width.png +0 -0
  79. data/doc/index.md +0 -39
  80. data/doc/troubleshooting/async.md +0 -4
  81. data/doc/troubleshooting/terminal_limitations.md +0 -131
  82. data/doc/troubleshooting/tui_output.md +0 -197
  83. data/examples/app_all_events/README.md +0 -114
  84. data/examples/app_all_events/app.rb +0 -98
  85. data/examples/app_all_events/model/app_model.rb +0 -159
  86. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  87. data/examples/app_all_events/model/event_entry.rb +0 -94
  88. data/examples/app_all_events/model/msg.rb +0 -39
  89. data/examples/app_all_events/model/timestamp.rb +0 -56
  90. data/examples/app_all_events/update.rb +0 -75
  91. data/examples/app_all_events/view/app_view.rb +0 -80
  92. data/examples/app_all_events/view/controls_view.rb +0 -54
  93. data/examples/app_all_events/view/counts_view.rb +0 -61
  94. data/examples/app_all_events/view/live_view.rb +0 -72
  95. data/examples/app_all_events/view/log_view.rb +0 -57
  96. data/examples/app_all_events/view.rb +0 -9
  97. data/examples/app_cli_rich_moments/README.md +0 -81
  98. data/examples/app_cli_rich_moments/app.rb +0 -189
  99. data/examples/app_color_picker/README.md +0 -156
  100. data/examples/app_color_picker/app.rb +0 -76
  101. data/examples/app_color_picker/clipboard.rb +0 -86
  102. data/examples/app_color_picker/color.rb +0 -193
  103. data/examples/app_color_picker/controls.rb +0 -92
  104. data/examples/app_color_picker/copy_dialog.rb +0 -168
  105. data/examples/app_color_picker/export_pane.rb +0 -128
  106. data/examples/app_color_picker/harmony.rb +0 -58
  107. data/examples/app_color_picker/input.rb +0 -176
  108. data/examples/app_color_picker/main_container.rb +0 -180
  109. data/examples/app_color_picker/palette.rb +0 -111
  110. data/examples/app_debugging_showcase/README.md +0 -119
  111. data/examples/app_debugging_showcase/app.rb +0 -318
  112. data/examples/app_login_form/README.md +0 -58
  113. data/examples/app_login_form/app.rb +0 -109
  114. data/examples/app_stateful_interaction/README.md +0 -35
  115. data/examples/app_stateful_interaction/app.rb +0 -328
  116. data/examples/timeout_demo.rb +0 -45
  117. data/examples/verify_quickstart_dsl/README.md +0 -55
  118. data/examples/verify_quickstart_dsl/app.rb +0 -49
  119. data/examples/verify_quickstart_layout/README.md +0 -77
  120. data/examples/verify_quickstart_layout/app.rb +0 -73
  121. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  122. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  123. data/examples/verify_readme_usage/README.md +0 -49
  124. data/examples/verify_readme_usage/app.rb +0 -42
  125. data/examples/verify_website_managed/README.md +0 -48
  126. data/examples/verify_website_managed/app.rb +0 -36
  127. data/examples/verify_website_menu/README.md +0 -60
  128. data/examples/verify_website_menu/app.rb +0 -84
  129. data/examples/verify_website_spinner/README.md +0 -44
  130. data/examples/verify_website_spinner/app.rb +0 -34
  131. data/examples/widget_barchart/README.md +0 -58
  132. data/examples/widget_barchart/app.rb +0 -240
  133. data/examples/widget_block/README.md +0 -44
  134. data/examples/widget_block/app.rb +0 -258
  135. data/examples/widget_box/README.md +0 -54
  136. data/examples/widget_box/app.rb +0 -255
  137. data/examples/widget_calendar/README.md +0 -48
  138. data/examples/widget_calendar/app.rb +0 -115
  139. data/examples/widget_canvas/README.md +0 -31
  140. data/examples/widget_canvas/app.rb +0 -130
  141. data/examples/widget_cell/README.md +0 -45
  142. data/examples/widget_cell/app.rb +0 -112
  143. data/examples/widget_center/README.md +0 -33
  144. data/examples/widget_center/app.rb +0 -118
  145. data/examples/widget_chart/README.md +0 -50
  146. data/examples/widget_chart/app.rb +0 -220
  147. data/examples/widget_gauge/README.md +0 -50
  148. data/examples/widget_gauge/app.rb +0 -229
  149. data/examples/widget_layout_split/README.md +0 -53
  150. data/examples/widget_layout_split/app.rb +0 -260
  151. data/examples/widget_line_gauge/README.md +0 -50
  152. data/examples/widget_line_gauge/app.rb +0 -219
  153. data/examples/widget_list/README.md +0 -58
  154. data/examples/widget_list/app.rb +0 -384
  155. data/examples/widget_map/README.md +0 -48
  156. data/examples/widget_map/app.rb +0 -95
  157. data/examples/widget_overlay/README.md +0 -45
  158. data/examples/widget_overlay/app.rb +0 -250
  159. data/examples/widget_popup/README.md +0 -45
  160. data/examples/widget_popup/app.rb +0 -106
  161. data/examples/widget_ratatui_logo/README.md +0 -43
  162. data/examples/widget_ratatui_logo/app.rb +0 -104
  163. data/examples/widget_ratatui_mascot/README.md +0 -43
  164. data/examples/widget_ratatui_mascot/app.rb +0 -95
  165. data/examples/widget_rect/README.md +0 -53
  166. data/examples/widget_rect/app.rb +0 -222
  167. data/examples/widget_render/README.md +0 -46
  168. data/examples/widget_render/app.rb +0 -186
  169. data/examples/widget_render/app.rbs +0 -41
  170. data/examples/widget_rich_text/README.md +0 -44
  171. data/examples/widget_rich_text/app.rb +0 -193
  172. data/examples/widget_scroll_text/README.md +0 -46
  173. data/examples/widget_scroll_text/app.rb +0 -109
  174. data/examples/widget_scrollbar/README.md +0 -46
  175. data/examples/widget_scrollbar/app.rb +0 -155
  176. data/examples/widget_sparkline/README.md +0 -51
  177. data/examples/widget_sparkline/app.rb +0 -277
  178. data/examples/widget_style_colors/README.md +0 -43
  179. data/examples/widget_style_colors/app.rb +0 -83
  180. data/examples/widget_table/README.md +0 -57
  181. data/examples/widget_table/app.rb +0 -279
  182. data/examples/widget_tabs/README.md +0 -50
  183. data/examples/widget_tabs/app.rb +0 -183
  184. data/examples/widget_text_width/README.md +0 -44
  185. data/examples/widget_text_width/app.rb +0 -117
  186. data/migrate_to_buffer.rb +0 -145
  187. data/mise.toml +0 -8
  188. data/tasks/autodoc/examples.rb +0 -87
  189. data/tasks/autodoc/member.rb +0 -58
  190. data/tasks/autodoc/name.rb +0 -21
  191. data/tasks/autodoc.rake +0 -21
  192. data/tasks/bump/cargo_lockfile.rb +0 -21
  193. data/tasks/bump/changelog.rb +0 -47
  194. data/tasks/bump/header.rb +0 -32
  195. data/tasks/bump/history.rb +0 -32
  196. data/tasks/bump/links.rb +0 -69
  197. data/tasks/bump/manifest.rb +0 -33
  198. data/tasks/bump/ruby_gem.rb +0 -49
  199. data/tasks/bump/sem_ver.rb +0 -40
  200. data/tasks/bump/unreleased_section.rb +0 -56
  201. data/tasks/bump.rake +0 -51
  202. data/tasks/doc.rake +0 -887
  203. data/tasks/example_viewer.html.erb +0 -172
  204. data/tasks/extension.rake +0 -14
  205. data/tasks/license/headers_md.rb +0 -223
  206. data/tasks/license/headers_rb.rb +0 -210
  207. data/tasks/license/license_utils.rb +0 -130
  208. data/tasks/license/snippets_md.rb +0 -315
  209. data/tasks/license/snippets_rdoc.rb +0 -150
  210. data/tasks/license.rake +0 -91
  211. data/tasks/lint.rake +0 -170
  212. data/tasks/rdoc_config.rb +0 -29
  213. data/tasks/resources/build.yml.erb +0 -60
  214. data/tasks/resources/index.html.erb +0 -141
  215. data/tasks/resources/rubies.yml +0 -7
  216. data/tasks/sourcehut.rake +0 -110
  217. data/tasks/steep.rake +0 -11
  218. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  219. data/tasks/terminal_preview/crash_report.rb +0 -54
  220. data/tasks/terminal_preview/example_app.rb +0 -27
  221. data/tasks/terminal_preview/launcher_script.rb +0 -48
  222. data/tasks/terminal_preview/preview_collection.rb +0 -60
  223. data/tasks/terminal_preview/preview_timing.rb +0 -24
  224. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  225. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  226. data/tasks/terminal_preview/system_appearance.rb +0 -13
  227. data/tasks/terminal_preview/terminal_window.rb +0 -138
  228. data/tasks/terminal_preview/window_id.rb +0 -16
  229. data/tasks/terminal_preview.rake +0 -30
  230. data/tasks/test.rake +0 -33
  231. data/tasks/website/index_page.rb +0 -30
  232. data/tasks/website/version.rb +0 -127
  233. data/tasks/website/version_menu.rb +0 -68
  234. data/tasks/website/versioned_documentation.rb +0 -83
  235. data/tasks/website/website.rb +0 -53
  236. data/tasks/website.rake +0 -28
@@ -1,279 +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
- require "bundler/setup"
10
- require "ratatui_ruby"
11
-
12
- # Sample process data
13
- PROCESSES = [
14
- { pid: 1234, name: "ruby", cpu: 15.2 },
15
- { pid: 5678, name: "postgres", cpu: 8.7 },
16
- { pid: 9012, name: "nginx", cpu: 3.1 },
17
- { pid: 3456, name: "redis", cpu: 12.4 },
18
- { pid: 7890, name: "sidekiq", cpu: 22.8 },
19
- { pid: 2345, name: "webpack", cpu: 45.3 },
20
- { pid: 6789, name: "node", cpu: 18.9 },
21
- ].freeze
22
-
23
- class WidgetTable
24
- attr_reader :selected_index, :selected_col, :current_style_index, :column_spacing, :highlight_spacing, :column_highlight_style, :cell_highlight_style
25
-
26
- HIGHLIGHT_SPACINGS = [
27
- { name: "When Selected", spacing: :when_selected },
28
- { name: "Always", spacing: :always },
29
- { name: "Never", spacing: :never },
30
- ].freeze
31
-
32
- OFFSET_MODES = [
33
- { name: "Auto (No Offset)", offset: nil, allow_selection: true },
34
- { name: "Offset Only (row 3)", offset: 3, allow_selection: false },
35
- { name: "Selection + Offset (Conflict)", offset: 0, allow_selection: true },
36
- ].freeze
37
-
38
- FLEX_MODES = [
39
- { name: "Legacy (Default)", flex: :legacy },
40
- { name: "Start", flex: :start },
41
- { name: "Center", flex: :center },
42
- { name: "End", flex: :end },
43
- { name: "Space Between", flex: :space_between },
44
- { name: "Space Around", flex: :space_around },
45
- { name: "Space Evenly", flex: :space_evenly },
46
- ].freeze
47
-
48
- def initialize
49
- @selected_index = 1
50
- @selected_col = 1
51
- @current_style_index = 0
52
- @column_spacing = 1
53
- @highlight_spacing_index = 0
54
- @show_column_highlight = true
55
- @show_cell_highlight = true
56
- @offset_mode_index = 0
57
- @flex_mode_index = 0
58
- @strikethrough_pids = Set.new # Track which rows have strikethrough
59
- end
60
-
61
- def run
62
- RatatuiRuby.run do |tui|
63
- @tui = tui
64
- setup_styles
65
- loop do
66
- @tui.draw do |frame|
67
- render(frame)
68
- end
69
- break if handle_input == :quit
70
- end
71
- end
72
- end
73
-
74
- private def setup_styles
75
- @styles = [
76
- { name: "Cyan", style: @tui.style(fg: :cyan) },
77
- { name: "Red", style: @tui.style(fg: :red) },
78
- { name: "Green", style: @tui.style(fg: :green) },
79
- { name: "Blue on White", style: @tui.style(fg: :blue, bg: :white) },
80
- { name: "Magenta", style: @tui.style(fg: :magenta, modifiers: [:bold]) },
81
- ]
82
- @column_highlight_style = @tui.style(fg: :red)
83
- @cell_highlight_style = @tui.style(fg: :white, bg: :red, modifiers: [:bold])
84
- @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
85
- end
86
-
87
- private def render(frame)
88
- # v0.7.0: Create table rows using table_row and table_cell for per-cell styling
89
- rows = PROCESSES.each_with_index.map do |p, i|
90
- cpu_style = case p[:cpu]
91
- when 0...10 then @tui.style(fg: :green)
92
- when 10...30 then @tui.style(fg: :yellow)
93
- else @tui.style(fg: :red, modifiers: [:bold])
94
- end
95
- row = @tui.table_row(
96
- cells: [
97
- p[:pid].to_s,
98
- p[:name],
99
- @tui.table_cell(content: "#{p[:cpu]}%", style: cpu_style),
100
- ],
101
- # Apply alternating row backgrounds for readability (using basic ANSI colors for compatibility)
102
- style: i.even? ? @tui.style(bg: :white, fg: :black) : nil
103
- )
104
-
105
- # Row#enable_strikethrough: Apply strikethrough to "tamped" (de-emphasized) processes.
106
- # Note: Strikethrough (SGR 9) is not supported by all terminals. macOS Terminal.app
107
- # notably lacks support, while Kitty, iTerm2, Alacritty, and WezTerm render it.
108
- # We add :dim as a fallback so the effect is visible even without strikethrough.
109
- if @strikethrough_pids.include?(p[:pid])
110
- row.enable_strikethrough.with(style: (row.style || @tui.style).with(modifiers: ((row.style&.modifiers || []) + [:crossed_out, :dim]).uniq))
111
- else
112
- row
113
- end
114
- end
115
-
116
- # Define column widths
117
- widths = [
118
- @tui.constraint_length(8),
119
- @tui.constraint_length(15),
120
- @tui.constraint_length(10),
121
- ]
122
-
123
- # Create highlight style (yellow text)
124
- row_highlight_style = @tui.style(fg: :yellow)
125
-
126
- current_style_entry = @styles[@current_style_index]
127
- current_spacing_entry = HIGHLIGHT_SPACINGS[@highlight_spacing_index]
128
- offset_mode_entry = OFFSET_MODES[@offset_mode_index]
129
- flex_mode_entry = FLEX_MODES[@flex_mode_index]
130
-
131
- # Determine selection/offset based on mode
132
- effective_selection = offset_mode_entry[:allow_selection] ? @selected_index : nil
133
- effective_offset = offset_mode_entry[:offset]
134
- selection_label = effective_selection.nil? ? "none" : effective_selection.to_s
135
- offset_label = effective_offset.nil? ? "auto" : effective_offset.to_s
136
-
137
- # Main table
138
- table = @tui.table(
139
- header: ["PID", "Name", "CPU"],
140
- rows:,
141
- widths:,
142
- selected_row: effective_selection,
143
- selected_column: @selected_col,
144
- offset: effective_offset,
145
- row_highlight_style:,
146
- highlight_symbol: "> ",
147
- highlight_spacing: current_spacing_entry[:spacing],
148
- column_highlight_style: @show_column_highlight ? @column_highlight_style : nil,
149
- cell_highlight_style: @show_cell_highlight ? @cell_highlight_style : nil,
150
- style: current_style_entry[:style],
151
- column_spacing: @column_spacing,
152
- flex: flex_mode_entry[:flex],
153
- block: @tui.block(
154
- title: "Processes | Sel: #{selection_label} | Offset: #{offset_label} | Flex: #{flex_mode_entry[:name]}",
155
- borders: :all
156
- ),
157
- footer: ["Total: #{PROCESSES.length}", "Total CPU: #{PROCESSES.sum { |p| p[:cpu] }}%", ""]
158
- )
159
-
160
- # Bottom control panel
161
- control_panel = @tui.block(
162
- title: "Controls",
163
- borders: [:all],
164
- children: [
165
- @tui.paragraph(
166
- text: [
167
- # Line 1: Navigation
168
- @tui.text_line(spans: [
169
- @tui.text_span(content: "↑/↓", style: @hotkey_style),
170
- @tui.text_span(content: ": Nav Row "),
171
- @tui.text_span(content: "←/β†’", style: @hotkey_style),
172
- @tui.text_span(content: ": Nav Col "),
173
- @tui.text_span(content: "x", style: @hotkey_style),
174
- @tui.text_span(content: ": Toggle Row (#{selection_label}) "),
175
- @tui.text_span(content: "q", style: @hotkey_style),
176
- @tui.text_span(content: ": Quit"),
177
- ]),
178
- # Line 2: Table Controls
179
- @tui.text_line(spans: [
180
- @tui.text_span(content: "s", style: @hotkey_style),
181
- @tui.text_span(content: ": Style (#{current_style_entry[:name]}) "),
182
- @tui.text_span(content: "p", style: @hotkey_style),
183
- @tui.text_span(content: ": Spacing (#{current_spacing_entry[:name]}) "),
184
- @tui.text_span(content: "t", style: @hotkey_style),
185
- @tui.text_span(content: ": Tamp Row"),
186
- ]),
187
- # Line 3: More Controls
188
- @tui.text_line(spans: [
189
- @tui.text_span(content: "+/-", style: @hotkey_style),
190
- @tui.text_span(content: ": Col Space (#{@column_spacing}) "),
191
- @tui.text_span(content: "c", style: @hotkey_style),
192
- @tui.text_span(content: ": Col Highlight (#{@show_column_highlight ? 'On' : 'Off'}) "),
193
- @tui.text_span(content: "f", style: @hotkey_style),
194
- @tui.text_span(content: ": Flex Mode (#{flex_mode_entry[:name]})"),
195
- ]),
196
- # Line 4: Offset Mode
197
- @tui.text_line(spans: [
198
- @tui.text_span(content: "z", style: @hotkey_style),
199
- @tui.text_span(content: ": Cell Highlight (#{@show_cell_highlight ? 'On' : 'Off'}) "),
200
- @tui.text_span(content: "o", style: @hotkey_style),
201
- @tui.text_span(content: ": Offset Mode (#{offset_mode_entry[:name]})"),
202
- ]),
203
- ]
204
- ),
205
- ]
206
- )
207
-
208
- # Layout
209
- layout = @tui.layout_split(
210
- frame.area,
211
- direction: :vertical,
212
- constraints: [
213
- @tui.constraint_fill(1),
214
- @tui.constraint_length(6),
215
- ]
216
- )
217
-
218
- frame.render_widget(table, layout[0])
219
- frame.render_widget(control_panel, layout[1])
220
- end
221
-
222
- private def handle_input
223
- event = @tui.poll_event
224
-
225
- case event
226
- in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
227
- :quit
228
- in type: :key, code: "down" | "j"
229
- @selected_index = ((@selected_index || -1) + 1) % PROCESSES.length
230
- in type: :key, code: "up" | "k"
231
- @selected_index = (@selected_index || 0) - 1
232
- @selected_index = PROCESSES.length - 1 if @selected_index.negative?
233
- in type: :key, code: "right" | "l"
234
- @selected_col = ((@selected_col || -1) + 1) % 3 # 3 columns
235
- in type: :key, code: "left" | "h"
236
- # 'h' is already used for highlight spacing, but let's override it or ignore vim keys for left/right?
237
- # Actually 'h' is used for spacing in this demo. Let's just use arrows for cols.
238
- # Or map 'h' to left if user meant vim keys.
239
- # The demo uses 'h' for "Spacing". Let's change Spacing key to 'p' (property/padding?) or something else.
240
- # Or just stick to arrows for columns to avoid conflict.
241
- @selected_col = (@selected_col || 0) - 1
242
- @selected_col = 2 if @selected_col.negative?
243
- in type: :key, code: "s"
244
- @current_style_index = (@current_style_index + 1) % @styles.length
245
- in type: :key, code: "+"
246
- @column_spacing += 1
247
- in type: :key, code: "-"
248
- @column_spacing = [@column_spacing - 1, 0].max
249
- in type: :key, code: "p"
250
- @highlight_spacing_index = (@highlight_spacing_index + 1) % HIGHLIGHT_SPACINGS.length
251
- in type: :key, code: "x"
252
- @selected_index = @selected_index.nil? ? 0 : nil
253
- in type: :key, code: "t"
254
- # Toggle strikethrough for selected row (demonstrates Row#enable_strikethrough)
255
- if @selected_index
256
- pid = PROCESSES[@selected_index][:pid]
257
- if @strikethrough_pids.include?(pid)
258
- @strikethrough_pids.delete(pid)
259
- else
260
- @strikethrough_pids.add(pid)
261
- end
262
- end
263
- in type: :key, code: "c"
264
- @show_column_highlight = !@show_column_highlight
265
- in type: :key, code: "z"
266
- @show_cell_highlight = !@show_cell_highlight
267
- in type: :key, code: "o"
268
- @offset_mode_index = (@offset_mode_index + 1) % OFFSET_MODES.length
269
- in type: :key, code: "f"
270
- @flex_mode_index = (@flex_mode_index + 1) % FLEX_MODES.length
271
- else
272
- nil
273
- end
274
- end
275
- end
276
-
277
- if __FILE__ == $0
278
- WidgetTable.new.run
279
- end
@@ -1,50 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Tabs Widget Example
7
-
8
- [![widget_tabs](../../doc/images/widget_tabs.png)](app.rb)
9
-
10
- Demonstrates view segregation with interactive navigation.
11
-
12
- Screen real estate is limited. You cannot show everything at once. Tabs segregate content into specialized views (modes), allowing users to switch contexts easily.
13
-
14
- ## Features Demonstrated
15
-
16
- - **Condition Rendering**: Changing the *content* of the screen based on the selected tab (Revenue vs Traffic vs Errors).
17
- - **Styling**: Configurable highlight styles, dividers, and padding.
18
- - **Interaction**: Keyboard navigation to cycle through tabs.
19
-
20
- ## Hotkeys
21
-
22
- - **Left/Right (←/β†’)**: Select Tab (`selected_index`)
23
- - **d**: Cycle Divider Character (`divider`)
24
- - **s**: Cycle Highlight Style (`highlight_style`)
25
- - **b**: Cycle Base Style (`style`)
26
- - **h/l**: Adjust Left Padding (`padding_left`)
27
- - **j/k**: Adjust Right Padding (`padding_right`)
28
- - **q**: Quit
29
-
30
- ## Usage
31
-
32
- <!-- SPDX-SnippetBegin -->
33
- <!--
34
- SPDX-FileCopyrightText: 2026 Kerrick Long
35
- SPDX-License-Identifier: MIT-0
36
- -->
37
- ```bash
38
- ruby examples/widget_tabs/app.rb
39
- ```
40
- <!-- SPDX-SnippetEnd -->
41
-
42
- ## Learning Outcomes
43
-
44
- Use this example if you need to...
45
-
46
- - Build a multi-pane dashboard.
47
- - Create a "Settings" screen with different categories.
48
- - Implement a "wizard" interface with steps.
49
-
50
- [Read the source code β†’](app.rb)
@@ -1,183 +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
- require "ratatui_ruby"
10
- require "faker"
11
-
12
- # Demonstrates view segregation with interactive tab navigation.
13
- #
14
- # Screen real estate is limited. You cannot show everything at once. Segregating content into views is necessary for complex apps.
15
- #
16
- # This demo showcases the <tt>Tabs</tt> widget. It provides an interactive playground where you can select tabs, cycle through dividers and styles, and adjust padding in real-time.
17
- #
18
- # Use it to understand how to build major mode switches or context navigation for your interface.
19
- #
20
- # === Example
21
- #
22
- # Run the demo from the terminal:
23
- #
24
- # ruby examples/widget_tabs/app.rb
25
- #
26
- # rdoc-image:/doc/images/widget_tabs.png
27
- class WidgetTabs
28
- def initialize
29
- @selected_tab = 0
30
- @tabs = ["Revenue", "Traffic", "Errors", "Quarterly"]
31
- @highlight_styles = nil
32
- @highlight_style_index = 0
33
- @divider_index = 0
34
- @dividers = [" | ", " β€’ ", " > ", " / "]
35
- @base_styles = nil
36
- @base_style_index = 0
37
- @padding_left = 0
38
- @padding_right = 0
39
- @width_constraint_index = 0
40
- @hotkey_style = nil
41
-
42
- # Generate the content once, not on every frame
43
- @tab_text = 4.times.map { |it| Faker::Lorem.paragraph(sentence_count: 10 + it) }
44
- end
45
-
46
- def run
47
- RatatuiRuby.run do |tui|
48
- @tui = tui
49
- init_styles
50
-
51
- loop do
52
- render
53
- break if handle_input == :quit
54
- end
55
- end
56
- end
57
-
58
- private def init_styles
59
- @highlight_styles = [
60
- { name: "Yellow Bold", style: @tui.style(fg: :yellow, modifiers: [:bold]) },
61
- { name: "Italic Blue on White", style: @tui.style(fg: :blue, bg: :white, modifiers: [:italic]) },
62
- { name: "Underlined Red", style: @tui.style(fg: :red, modifiers: [:underlined]) },
63
- { name: "Reversed", style: @tui.style(modifiers: [:reversed]) },
64
- ]
65
- @base_styles = [
66
- { name: "Default", style: nil },
67
- { name: "White on Gray", style: @tui.style(fg: :white, bg: :dark_gray) },
68
- { name: "White on Blue", style: @tui.style(fg: :white, bg: :blue) },
69
- { name: "Italic", style: @tui.style(modifiers: [:italic]) },
70
- ]
71
- @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
72
- end
73
-
74
- private def render
75
- @tui.draw do |frame|
76
- main_area, controls_area = @tui.layout_split(
77
- frame.area,
78
- direction: :vertical,
79
- constraints: [
80
- @tui.constraint_fill(1),
81
- @tui.constraint_length(5),
82
- ]
83
- )
84
-
85
- # Center the tabs vertically in the main area
86
- tabs_area, content_area = @tui.layout_split(
87
- main_area,
88
- direction: :vertical,
89
- constraints: [
90
- @tui.constraint_length(3),
91
- @tui.constraint_fill(1),
92
- ]
93
- )
94
-
95
- tabs = @tui.tabs(
96
- titles: @tabs,
97
- selected_index: @selected_tab,
98
- block: @tui.block(title: "Tabs", borders: [:all]),
99
- divider: @dividers[@divider_index],
100
- highlight_style: @highlight_styles[@highlight_style_index][:style],
101
- style: @base_styles[@base_style_index][:style],
102
- padding_left: @padding_left,
103
- padding_right: @padding_right
104
- )
105
- frame.render_widget(tabs, tabs_area)
106
- frame.render_widget(tab_contents, content_area)
107
-
108
- render_controls(frame, controls_area, tabs.width)
109
- end
110
- end
111
-
112
- private def render_controls(frame, area, current_width)
113
- controls = @tui.block(
114
- title: "Controls",
115
- borders: [:all],
116
- children: [
117
- @tui.paragraph(
118
- text: [
119
- @tui.text_line(spans: [
120
- @tui.text_span(content: "←/β†’", style: @hotkey_style),
121
- @tui.text_span(content: ": Select Tab "),
122
- @tui.text_span(content: "h/l", style: @hotkey_style),
123
- @tui.text_span(content: ": Pad Left (#{@padding_left}) "),
124
- @tui.text_span(content: "j/k", style: @hotkey_style),
125
- @tui.text_span(content: ": Pad Right (#{@padding_right}) "),
126
- @tui.text_span(content: "q", style: @hotkey_style),
127
- @tui.text_span(content: ": Quit"),
128
- ]),
129
- @tui.text_line(spans: [
130
- @tui.text_span(content: "d", style: @hotkey_style),
131
- @tui.text_span(content: ": Divider (#{@dividers[@divider_index]}) "),
132
- @tui.text_span(content: "s", style: @hotkey_style),
133
- @tui.text_span(content: ": Highlight (#{@highlight_styles[@highlight_style_index][:name]}) "),
134
- @tui.text_span(content: "b", style: @hotkey_style),
135
- @tui.text_span(content: ": Base Style (#{@base_styles[@base_style_index][:name]}) "),
136
- ]),
137
- @tui.text_line(spans: [
138
- @tui.text_span(content: "Width: #{current_width}"),
139
- ]),
140
- ]
141
- ),
142
- ]
143
- )
144
- frame.render_widget(controls, area)
145
- end
146
-
147
- private def handle_input
148
- case @tui.poll_event
149
- in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
150
- :quit
151
- in type: :key, code: "right"
152
- @selected_tab = (@selected_tab + 1) % @tabs.size
153
- in type: :key, code: "left"
154
- @selected_tab = (@selected_tab - 1) % @tabs.size
155
- in type: :key, code: "d"
156
- @divider_index = (@divider_index + 1) % @dividers.size
157
- in type: :key, code: "s"
158
- @highlight_style_index = (@highlight_style_index + 1) % @highlight_styles.size
159
- in type: :key, code: "b"
160
- @base_style_index = (@base_style_index + 1) % @base_styles.size
161
- in type: :key, code: "h"
162
- @padding_left = [@padding_left - 1, 0].max
163
- in type: :key, code: "l"
164
- @padding_left += 1
165
- in type: :key, code: "j"
166
- @padding_right = [@padding_right - 1, 0].max
167
- in type: :key, code: "k"
168
- @padding_right += 1
169
- else
170
- # Ignore other events
171
- end
172
- end
173
-
174
- private def tab_contents
175
- @tui.paragraph(
176
- text: @tab_text[@selected_tab],
177
- wrap: true,
178
- block: @tui.block(borders: [:all], title: @tabs[@selected_tab])
179
- )
180
- end
181
- end
182
-
183
- WidgetTabs.new.run if __FILE__ == $0
@@ -1,44 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Text Width (Unicode Width) Calculator
7
-
8
- [![widget_text_width](../../doc/images/widget_text_width.png)](app.rb)
9
-
10
- Demonstrates string width calculation in a terminal environment.
11
-
12
- Not all characters are created equal. In a TUI, "Width" means cell count, not string length. Emoji (`πŸ‘`) take 2 cells. Chinese characters (`δ½ `) take 2 cells. The `tui.text_width` helper tells you the visual width of a string.
13
-
14
- ## Features Demonstrated
15
-
16
- - **Unicode Width**: Rendering ASCII (1 cell), CJK (2 cells), and Emoji (2 cells).
17
- - **Calculation**: Comparing `string.length` vs `tui.text_width(string)`.
18
-
19
- ## Hotkeys
20
-
21
- - **Up/Down (↑/↓)**: Cycle Text Sample
22
- - **q**: Quit
23
-
24
- ## Usage
25
-
26
- <!-- SPDX-SnippetBegin -->
27
- <!--
28
- SPDX-FileCopyrightText: 2026 Kerrick Long
29
- SPDX-License-Identifier: MIT-0
30
- -->
31
- ```bash
32
- ruby examples/widget_text_width/app.rb
33
- ```
34
- <!-- SPDX-SnippetEnd -->
35
-
36
- ## Learning Outcomes
37
-
38
- Use this example if you need to...
39
-
40
- - Align text correctly in columns.
41
- - Truncate strings that are too long for a widget.
42
- - Build your own custom layout engine.
43
-
44
- [Read the source code β†’](app.rb)
@@ -1,117 +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
- require "ratatui_ruby"
10
-
11
- class WidgetTextWidth
12
- def initialize
13
- @text_samples = [
14
- { label: "ASCII", text: "Hello, World!", desc: "Simple English text" },
15
- { label: "CJK", text: "δ½ ε₯½δΈ–η•Œ", desc: "Chinese (full-width characters)" },
16
- { label: "Emoji", text: "Hello πŸ‘ World 🌍", desc: "Mixed text with emoji (2 cells each)" },
17
- { label: "Mixed", text: "Hi δ½ ε₯½ πŸ‘", desc: "ASCII + CJK + emoji" },
18
- { label: "Empty", text: "", desc: "Empty string" },
19
- ]
20
- @selected_index = 0
21
- end
22
-
23
- def run
24
- RatatuiRuby.run do |tui|
25
- @tui = tui
26
- loop do
27
- render
28
- break if handle_input == :quit
29
- end
30
- end
31
- end
32
-
33
- private def render
34
- @tui.draw do |frame|
35
- # Layout: main content above, controls below
36
- areas = @tui.layout_split(
37
- frame.area,
38
- direction: :vertical,
39
- constraints: [@tui.constraint_fill(1), @tui.constraint_length(7)]
40
- )
41
-
42
- # Main content area with sample text
43
- render_content(frame, areas[0])
44
-
45
- # Controls footer
46
- render_controls(frame, areas[1])
47
- end
48
- end
49
-
50
- private def render_content(frame, area)
51
- sample = @text_samples[@selected_index]
52
- measured_width = @tui.text_width(sample[:text])
53
-
54
- # v0.7.0: Text::Span#width and Text::Line#width instance methods for rich text measurement
55
- styled_span = @tui.text_span(content: sample[:text], style: @tui.style(fg: :cyan))
56
- span_width = styled_span.width
57
-
58
- styled_line = @tui.text_line(spans: [styled_span])
59
- line_width = styled_line.width
60
-
61
- # Build content text with newlines
62
- content = []
63
- content << "Sample: #{sample[:text]}"
64
- content << ""
65
- content << "Display Width (text_width): #{measured_width} cells"
66
- content << "Display Width (span.width): #{span_width} cells"
67
- content << "Display Width (line.width): #{line_width} cells"
68
- content << "Character Count: #{sample[:text].length}"
69
- content << ""
70
- content << sample[:desc]
71
- text = content.join("\n")
72
-
73
- widget = @tui.paragraph(
74
- text:,
75
- block: @tui.block(
76
- title: "Text Width Calculator",
77
- borders: [:all],
78
- border_style: { fg: "cyan" }
79
- ),
80
- alignment: :left
81
- )
82
-
83
- frame.render_widget(widget, area)
84
- end
85
-
86
- private def render_controls(frame, area)
87
- info = "Sample #{@selected_index + 1}/#{@text_samples.length}: #{@text_samples[@selected_index][:label]}"
88
- controls = "↑/↓ Select q Quit"
89
- text = "#{info}\n#{controls}"
90
-
91
- widget = @tui.paragraph(
92
- text:,
93
- block: @tui.block(borders: [:top], border_style: { fg: "gray" }),
94
- alignment: :center
95
- )
96
-
97
- frame.render_widget(widget, area)
98
- end
99
-
100
- private def handle_input
101
- event = @tui.poll_event
102
- case event
103
- in { type: :key, code: "q" }
104
- :quit
105
- in { type: :key, code: "up" }
106
- @selected_index = (@selected_index - 1) % @text_samples.length
107
- nil
108
- in { type: :key, code: "down" }
109
- @selected_index = (@selected_index + 1) % @text_samples.length
110
- nil
111
- else
112
- nil
113
- end
114
- end
115
- end
116
-
117
- WidgetTextWidth.new.run if __FILE__ == $PROGRAM_NAME