ratatui_ruby 1.3.0 β†’ 1.4.0

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 (263) hide show
  1. checksums.yaml +4 -4
  2. data/ext/ratatui_ruby/Cargo.lock +2 -1
  3. data/ext/ratatui_ruby/Cargo.toml +2 -1
  4. data/ext/ratatui_ruby/src/events.rs +157 -18
  5. data/lib/ratatui_ruby/test_helper/snapshot.rb +58 -10
  6. data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
  7. data/lib/ratatui_ruby/test_helper.rb +2 -0
  8. data/lib/ratatui_ruby/version.rb +1 -1
  9. metadata +17 -270
  10. data/.builds/ruby-3.2.yml +0 -54
  11. data/.builds/ruby-3.3.yml +0 -54
  12. data/.builds/ruby-3.4.yml +0 -54
  13. data/.builds/ruby-4.0.0.yml +0 -54
  14. data/.pre-commit-config.yaml +0 -16
  15. data/.rubocop.yml +0 -10
  16. data/AGENTS.md +0 -147
  17. data/CHANGELOG.md +0 -771
  18. data/README.md +0 -187
  19. data/README.rdoc +0 -302
  20. data/Rakefile +0 -11
  21. data/Steepfile +0 -50
  22. data/doc/concepts/application_architecture.md +0 -321
  23. data/doc/concepts/application_testing.md +0 -193
  24. data/doc/concepts/async.md +0 -190
  25. data/doc/concepts/custom_widgets.md +0 -247
  26. data/doc/concepts/debugging.md +0 -401
  27. data/doc/concepts/event_handling.md +0 -162
  28. data/doc/concepts/interactive_design.md +0 -146
  29. data/doc/contributors/auditing/parity.md +0 -239
  30. data/doc/contributors/design/ruby_frontend.md +0 -448
  31. data/doc/contributors/design/rust_backend.md +0 -434
  32. data/doc/contributors/design.md +0 -11
  33. data/doc/contributors/developing_examples.md +0 -400
  34. data/doc/contributors/documentation_style.md +0 -121
  35. data/doc/contributors/index.md +0 -21
  36. data/doc/contributors/releasing.md +0 -215
  37. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  38. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  39. data/doc/contributors/todo/align/term.md +0 -351
  40. data/doc/contributors/todo/align/terminal.md +0 -647
  41. data/doc/contributors/todo/future_work.md +0 -169
  42. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  43. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  44. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  45. data/doc/custom.css +0 -22
  46. data/doc/getting_started/quickstart.md +0 -291
  47. data/doc/getting_started/why.md +0 -93
  48. data/doc/images/app_all_events.png +0 -0
  49. data/doc/images/app_cli_rich_moments.gif +0 -0
  50. data/doc/images/app_color_picker.png +0 -0
  51. data/doc/images/app_debugging_showcase.gif +0 -0
  52. data/doc/images/app_debugging_showcase.png +0 -0
  53. data/doc/images/app_external_editor.gif +0 -0
  54. data/doc/images/app_login_form.png +0 -0
  55. data/doc/images/app_stateful_interaction.png +0 -0
  56. data/doc/images/verify_quickstart_dsl.png +0 -0
  57. data/doc/images/verify_quickstart_layout.png +0 -0
  58. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  59. data/doc/images/verify_readme_usage.png +0 -0
  60. data/doc/images/widget_barchart.png +0 -0
  61. data/doc/images/widget_block.png +0 -0
  62. data/doc/images/widget_box.png +0 -0
  63. data/doc/images/widget_calendar.png +0 -0
  64. data/doc/images/widget_canvas.png +0 -0
  65. data/doc/images/widget_cell.png +0 -0
  66. data/doc/images/widget_center.png +0 -0
  67. data/doc/images/widget_chart.png +0 -0
  68. data/doc/images/widget_gauge.png +0 -0
  69. data/doc/images/widget_layout_split.png +0 -0
  70. data/doc/images/widget_line_gauge.png +0 -0
  71. data/doc/images/widget_list.png +0 -0
  72. data/doc/images/widget_map.png +0 -0
  73. data/doc/images/widget_overlay.png +0 -0
  74. data/doc/images/widget_popup.png +0 -0
  75. data/doc/images/widget_ratatui_logo.png +0 -0
  76. data/doc/images/widget_ratatui_mascot.png +0 -0
  77. data/doc/images/widget_rect.png +0 -0
  78. data/doc/images/widget_render.png +0 -0
  79. data/doc/images/widget_rich_text.png +0 -0
  80. data/doc/images/widget_scroll_text.png +0 -0
  81. data/doc/images/widget_scrollbar.png +0 -0
  82. data/doc/images/widget_sparkline.png +0 -0
  83. data/doc/images/widget_style_colors.png +0 -0
  84. data/doc/images/widget_table.png +0 -0
  85. data/doc/images/widget_tabs.png +0 -0
  86. data/doc/images/widget_text_width.png +0 -0
  87. data/doc/index.md +0 -34
  88. data/doc/troubleshooting/async.md +0 -4
  89. data/doc/troubleshooting/terminal_limitations.md +0 -131
  90. data/doc/troubleshooting/tui_output.md +0 -197
  91. data/examples/app_all_events/README.md +0 -114
  92. data/examples/app_all_events/app.rb +0 -98
  93. data/examples/app_all_events/model/app_model.rb +0 -159
  94. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  95. data/examples/app_all_events/model/event_entry.rb +0 -94
  96. data/examples/app_all_events/model/msg.rb +0 -39
  97. data/examples/app_all_events/model/timestamp.rb +0 -56
  98. data/examples/app_all_events/update.rb +0 -75
  99. data/examples/app_all_events/view/app_view.rb +0 -80
  100. data/examples/app_all_events/view/controls_view.rb +0 -54
  101. data/examples/app_all_events/view/counts_view.rb +0 -61
  102. data/examples/app_all_events/view/live_view.rb +0 -72
  103. data/examples/app_all_events/view/log_view.rb +0 -57
  104. data/examples/app_all_events/view.rb +0 -9
  105. data/examples/app_cli_rich_moments/README.md +0 -81
  106. data/examples/app_cli_rich_moments/app.rb +0 -189
  107. data/examples/app_color_picker/README.md +0 -156
  108. data/examples/app_color_picker/app.rb +0 -76
  109. data/examples/app_color_picker/clipboard.rb +0 -86
  110. data/examples/app_color_picker/color.rb +0 -193
  111. data/examples/app_color_picker/controls.rb +0 -92
  112. data/examples/app_color_picker/copy_dialog.rb +0 -168
  113. data/examples/app_color_picker/export_pane.rb +0 -128
  114. data/examples/app_color_picker/harmony.rb +0 -58
  115. data/examples/app_color_picker/input.rb +0 -176
  116. data/examples/app_color_picker/main_container.rb +0 -180
  117. data/examples/app_color_picker/palette.rb +0 -111
  118. data/examples/app_debugging_showcase/README.md +0 -119
  119. data/examples/app_debugging_showcase/app.rb +0 -318
  120. data/examples/app_external_editor/README.md +0 -62
  121. data/examples/app_external_editor/app.rb +0 -344
  122. data/examples/app_login_form/README.md +0 -58
  123. data/examples/app_login_form/app.rb +0 -109
  124. data/examples/app_stateful_interaction/README.md +0 -35
  125. data/examples/app_stateful_interaction/app.rb +0 -328
  126. data/examples/timeout_demo.rb +0 -45
  127. data/examples/verify_quickstart_dsl/README.md +0 -55
  128. data/examples/verify_quickstart_dsl/app.rb +0 -49
  129. data/examples/verify_quickstart_layout/README.md +0 -77
  130. data/examples/verify_quickstart_layout/app.rb +0 -73
  131. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  132. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  133. data/examples/verify_readme_usage/README.md +0 -49
  134. data/examples/verify_readme_usage/app.rb +0 -42
  135. data/examples/verify_website_managed/README.md +0 -48
  136. data/examples/verify_website_managed/app.rb +0 -36
  137. data/examples/verify_website_menu/README.md +0 -60
  138. data/examples/verify_website_menu/app.rb +0 -84
  139. data/examples/verify_website_spinner/README.md +0 -44
  140. data/examples/verify_website_spinner/app.rb +0 -34
  141. data/examples/widget_barchart/README.md +0 -58
  142. data/examples/widget_barchart/app.rb +0 -240
  143. data/examples/widget_block/README.md +0 -44
  144. data/examples/widget_block/app.rb +0 -258
  145. data/examples/widget_box/README.md +0 -54
  146. data/examples/widget_box/app.rb +0 -255
  147. data/examples/widget_calendar/README.md +0 -48
  148. data/examples/widget_calendar/app.rb +0 -115
  149. data/examples/widget_canvas/README.md +0 -31
  150. data/examples/widget_canvas/app.rb +0 -130
  151. data/examples/widget_cell/README.md +0 -45
  152. data/examples/widget_cell/app.rb +0 -112
  153. data/examples/widget_center/README.md +0 -33
  154. data/examples/widget_center/app.rb +0 -118
  155. data/examples/widget_chart/README.md +0 -50
  156. data/examples/widget_chart/app.rb +0 -220
  157. data/examples/widget_gauge/README.md +0 -50
  158. data/examples/widget_gauge/app.rb +0 -229
  159. data/examples/widget_layout_split/README.md +0 -53
  160. data/examples/widget_layout_split/app.rb +0 -260
  161. data/examples/widget_line_gauge/README.md +0 -50
  162. data/examples/widget_line_gauge/app.rb +0 -219
  163. data/examples/widget_list/README.md +0 -58
  164. data/examples/widget_list/app.rb +0 -382
  165. data/examples/widget_map/README.md +0 -48
  166. data/examples/widget_map/app.rb +0 -95
  167. data/examples/widget_overlay/README.md +0 -45
  168. data/examples/widget_overlay/app.rb +0 -250
  169. data/examples/widget_popup/README.md +0 -45
  170. data/examples/widget_popup/app.rb +0 -106
  171. data/examples/widget_ratatui_logo/README.md +0 -43
  172. data/examples/widget_ratatui_logo/app.rb +0 -104
  173. data/examples/widget_ratatui_mascot/README.md +0 -43
  174. data/examples/widget_ratatui_mascot/app.rb +0 -95
  175. data/examples/widget_rect/README.md +0 -53
  176. data/examples/widget_rect/app.rb +0 -222
  177. data/examples/widget_render/README.md +0 -46
  178. data/examples/widget_render/app.rb +0 -186
  179. data/examples/widget_render/app.rbs +0 -41
  180. data/examples/widget_rich_text/README.md +0 -44
  181. data/examples/widget_rich_text/app.rb +0 -193
  182. data/examples/widget_scroll_text/README.md +0 -46
  183. data/examples/widget_scroll_text/app.rb +0 -109
  184. data/examples/widget_scrollbar/README.md +0 -46
  185. data/examples/widget_scrollbar/app.rb +0 -155
  186. data/examples/widget_sparkline/README.md +0 -51
  187. data/examples/widget_sparkline/app.rb +0 -277
  188. data/examples/widget_style_colors/README.md +0 -43
  189. data/examples/widget_style_colors/app.rb +0 -83
  190. data/examples/widget_table/README.md +0 -57
  191. data/examples/widget_table/app.rb +0 -285
  192. data/examples/widget_tabs/README.md +0 -50
  193. data/examples/widget_tabs/app.rb +0 -183
  194. data/examples/widget_text_width/README.md +0 -44
  195. data/examples/widget_text_width/app.rb +0 -117
  196. data/migrate_to_buffer.rb +0 -145
  197. data/mise.toml +0 -8
  198. data/tasks/autodoc/examples.rb +0 -87
  199. data/tasks/autodoc/member.rb +0 -58
  200. data/tasks/autodoc/name.rb +0 -21
  201. data/tasks/autodoc.rake +0 -21
  202. data/tasks/bump/bump_workflow.rb +0 -49
  203. data/tasks/bump/cargo_lockfile.rb +0 -21
  204. data/tasks/bump/changelog.rb +0 -104
  205. data/tasks/bump/header.rb +0 -32
  206. data/tasks/bump/history.rb +0 -32
  207. data/tasks/bump/links.rb +0 -69
  208. data/tasks/bump/manifest.rb +0 -33
  209. data/tasks/bump/patch_release.rb +0 -19
  210. data/tasks/bump/release_branch.rb +0 -17
  211. data/tasks/bump/release_from_trunk.rb +0 -49
  212. data/tasks/bump/repository.rb +0 -54
  213. data/tasks/bump/ruby_gem.rb +0 -29
  214. data/tasks/bump/sem_ver.rb +0 -44
  215. data/tasks/bump/unreleased_section.rb +0 -73
  216. data/tasks/bump.rake +0 -61
  217. data/tasks/doc/documentation.rb +0 -59
  218. data/tasks/doc/link/file_url.rb +0 -30
  219. data/tasks/doc/link/relative_path.rb +0 -61
  220. data/tasks/doc/link/web_url.rb +0 -55
  221. data/tasks/doc/link.rb +0 -52
  222. data/tasks/doc/link_audit.rb +0 -116
  223. data/tasks/doc/problem.rb +0 -40
  224. data/tasks/doc/source_file.rb +0 -93
  225. data/tasks/doc.rake +0 -905
  226. data/tasks/example_viewer.html.erb +0 -172
  227. data/tasks/extension.rake +0 -14
  228. data/tasks/license/headers_md.rb +0 -223
  229. data/tasks/license/headers_rb.rb +0 -210
  230. data/tasks/license/license_utils.rb +0 -130
  231. data/tasks/license/snippets_md.rb +0 -315
  232. data/tasks/license/snippets_rdoc.rb +0 -150
  233. data/tasks/license.rake +0 -91
  234. data/tasks/lint.rake +0 -170
  235. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  236. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  237. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  238. data/tasks/rbs_predicates.rake +0 -31
  239. data/tasks/rdoc_config.rb +0 -29
  240. data/tasks/resources/build.yml.erb +0 -60
  241. data/tasks/resources/index.html.erb +0 -141
  242. data/tasks/resources/rubies.yml +0 -7
  243. data/tasks/sourcehut.rake +0 -122
  244. data/tasks/steep.rake +0 -11
  245. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  246. data/tasks/terminal_preview/crash_report.rb +0 -54
  247. data/tasks/terminal_preview/example_app.rb +0 -27
  248. data/tasks/terminal_preview/launcher_script.rb +0 -48
  249. data/tasks/terminal_preview/preview_collection.rb +0 -60
  250. data/tasks/terminal_preview/preview_timing.rb +0 -24
  251. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  252. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  253. data/tasks/terminal_preview/system_appearance.rb +0 -13
  254. data/tasks/terminal_preview/terminal_window.rb +0 -138
  255. data/tasks/terminal_preview/window_id.rb +0 -16
  256. data/tasks/terminal_preview.rake +0 -30
  257. data/tasks/test.rake +0 -36
  258. data/tasks/website/index_page.rb +0 -30
  259. data/tasks/website/version.rb +0 -122
  260. data/tasks/website/version_menu.rb +0 -68
  261. data/tasks/website/versioned_documentation.rb +0 -83
  262. data/tasks/website/website.rb +0 -53
  263. data/tasks/website.rake +0 -28
@@ -1,285 +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
- @show_header = true
57
- @offset_mode_index = 0
58
- @flex_mode_index = 0
59
- @strikethrough_pids = Set.new # Track which rows have strikethrough
60
- end
61
-
62
- def run
63
- RatatuiRuby.run do |tui|
64
- @tui = tui
65
- setup_styles
66
- loop do
67
- @tui.draw do |frame|
68
- render(frame)
69
- end
70
- break if handle_input == :quit
71
- end
72
- end
73
- end
74
-
75
- private def setup_styles
76
- @styles = [
77
- { name: "Cyan", style: @tui.style(fg: :cyan) },
78
- { name: "Red", style: @tui.style(fg: :red) },
79
- { name: "Green", style: @tui.style(fg: :green) },
80
- { name: "Blue on White", style: @tui.style(fg: :blue, bg: :white) },
81
- { name: "Magenta", style: @tui.style(fg: :magenta, modifiers: [:bold]) },
82
- ]
83
- @column_highlight_style = @tui.style(fg: :red)
84
- @cell_highlight_style = @tui.style(fg: :white, bg: :red, modifiers: [:bold])
85
- @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
86
- end
87
-
88
- private def render(frame)
89
- # v0.7.0: Create table rows using table_row and table_cell for per-cell styling
90
- rows = PROCESSES.each_with_index.map do |p, i|
91
- cpu_style = case p[:cpu]
92
- when 0...10 then @tui.style(fg: :green)
93
- when 10...30 then @tui.style(fg: :yellow)
94
- else @tui.style(fg: :red, modifiers: [:bold])
95
- end
96
- row = @tui.table_row(
97
- cells: [
98
- p[:pid].to_s,
99
- p[:name],
100
- @tui.table_cell(content: "#{p[:cpu]}%", style: cpu_style),
101
- ],
102
- # Apply alternating row backgrounds for readability (using basic ANSI colors for compatibility)
103
- style: i.even? ? @tui.style(bg: :white, fg: :black) : nil
104
- )
105
-
106
- # Row#enable_strikethrough: Apply strikethrough to "tamped" (de-emphasized) processes.
107
- # Note: Strikethrough (SGR 9) is not supported by all terminals. macOS Terminal.app
108
- # notably lacks support, while Kitty, iTerm2, Alacritty, and WezTerm render it.
109
- # We add :dim as a fallback so the effect is visible even without strikethrough.
110
- if @strikethrough_pids.include?(p[:pid])
111
- row.enable_strikethrough.with(style: (row.style || @tui.style).with(modifiers: ((row.style&.modifiers || []) + [:crossed_out, :dim]).uniq))
112
- else
113
- row
114
- end
115
- end
116
-
117
- # Define column widths
118
- widths = [
119
- @tui.constraint_length(8),
120
- @tui.constraint_length(15),
121
- @tui.constraint_length(10),
122
- ]
123
-
124
- # Create highlight style (yellow text)
125
- row_highlight_style = @tui.style(fg: :yellow)
126
-
127
- current_style_entry = @styles[@current_style_index]
128
- current_spacing_entry = HIGHLIGHT_SPACINGS[@highlight_spacing_index]
129
- offset_mode_entry = OFFSET_MODES[@offset_mode_index]
130
- flex_mode_entry = FLEX_MODES[@flex_mode_index]
131
-
132
- # Determine selection/offset based on mode
133
- effective_selection = offset_mode_entry[:allow_selection] ? @selected_index : nil
134
- effective_offset = offset_mode_entry[:offset]
135
- selection_label = effective_selection.nil? ? "none" : effective_selection.to_s
136
- offset_label = effective_offset.nil? ? "auto" : effective_offset.to_s
137
-
138
- # Main table
139
- header_label = @show_header ? "On" : "Off"
140
- table = @tui.table(
141
- header: @show_header ? ["PID", "Name", "CPU"] : nil,
142
- rows:,
143
- widths:,
144
- selected_row: effective_selection,
145
- selected_column: @selected_col,
146
- offset: effective_offset,
147
- row_highlight_style:,
148
- highlight_symbol: "> ",
149
- highlight_spacing: current_spacing_entry[:spacing],
150
- column_highlight_style: @show_column_highlight ? @column_highlight_style : nil,
151
- cell_highlight_style: @show_cell_highlight ? @cell_highlight_style : nil,
152
- style: current_style_entry[:style],
153
- column_spacing: @column_spacing,
154
- flex: flex_mode_entry[:flex],
155
- block: @tui.block(
156
- title: "Processes | Sel: #{selection_label} | Offset: #{offset_label} | Flex: #{flex_mode_entry[:name]}",
157
- borders: :all
158
- ),
159
- footer: ["Total: #{PROCESSES.length}", "Total CPU: #{PROCESSES.sum { |p| p[:cpu] }}%", ""]
160
- )
161
-
162
- # Bottom control panel
163
- control_panel = @tui.block(
164
- title: "Controls",
165
- borders: [:all],
166
- children: [
167
- @tui.paragraph(
168
- text: [
169
- # Line 1: Navigation
170
- @tui.text_line(spans: [
171
- @tui.text_span(content: "↑/↓", style: @hotkey_style),
172
- @tui.text_span(content: ": Nav Row "),
173
- @tui.text_span(content: "←/β†’", style: @hotkey_style),
174
- @tui.text_span(content: ": Nav Col "),
175
- @tui.text_span(content: "x", style: @hotkey_style),
176
- @tui.text_span(content: ": Toggle Row (#{selection_label}) "),
177
- @tui.text_span(content: "q", style: @hotkey_style),
178
- @tui.text_span(content: ": Quit"),
179
- ]),
180
- # Line 2: Table Controls
181
- @tui.text_line(spans: [
182
- @tui.text_span(content: "s", style: @hotkey_style),
183
- @tui.text_span(content: ": Style (#{current_style_entry[:name]}) "),
184
- @tui.text_span(content: "p", style: @hotkey_style),
185
- @tui.text_span(content: ": Spacing (#{current_spacing_entry[:name]}) "),
186
- @tui.text_span(content: "t", style: @hotkey_style),
187
- @tui.text_span(content: ": Tamp Row"),
188
- ]),
189
- # Line 3: More Controls
190
- @tui.text_line(spans: [
191
- @tui.text_span(content: "+/-", style: @hotkey_style),
192
- @tui.text_span(content: ": Col Space (#{@column_spacing}) "),
193
- @tui.text_span(content: "c", style: @hotkey_style),
194
- @tui.text_span(content: ": Col Highlight (#{@show_column_highlight ? 'On' : 'Off'}) "),
195
- @tui.text_span(content: "f", style: @hotkey_style),
196
- @tui.text_span(content: ": Flex Mode (#{flex_mode_entry[:name]})"),
197
- ]),
198
- # Line 4: Offset Mode
199
- @tui.text_line(spans: [
200
- @tui.text_span(content: "z", style: @hotkey_style),
201
- @tui.text_span(content: ": Cell Highlight (#{@show_cell_highlight ? 'On' : 'Off'}) "),
202
- @tui.text_span(content: "o", style: @hotkey_style),
203
- @tui.text_span(content: ": Offset Mode (#{offset_mode_entry[:name]}) "),
204
- @tui.text_span(content: "d", style: @hotkey_style),
205
- @tui.text_span(content: ": Header (#{header_label})"),
206
- ]),
207
- ]
208
- ),
209
- ]
210
- )
211
-
212
- # Layout
213
- layout = @tui.layout_split(
214
- frame.area,
215
- direction: :vertical,
216
- constraints: [
217
- @tui.constraint_fill(1),
218
- @tui.constraint_length(6),
219
- ]
220
- )
221
-
222
- frame.render_widget(table, layout[0])
223
- frame.render_widget(control_panel, layout[1])
224
- end
225
-
226
- private def handle_input
227
- event = @tui.poll_event
228
-
229
- case event
230
- in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
231
- :quit
232
- in type: :key, code: "down" | "j"
233
- @selected_index = ((@selected_index || -1) + 1) % PROCESSES.length
234
- in type: :key, code: "up" | "k"
235
- @selected_index = (@selected_index || 0) - 1
236
- @selected_index = PROCESSES.length - 1 if @selected_index.negative?
237
- in type: :key, code: "right" | "l"
238
- @selected_col = ((@selected_col || -1) + 1) % 3 # 3 columns
239
- in type: :key, code: "left" | "h"
240
- # 'h' is already used for highlight spacing, but let's override it or ignore vim keys for left/right?
241
- # Actually 'h' is used for spacing in this demo. Let's just use arrows for cols.
242
- # Or map 'h' to left if user meant vim keys.
243
- # The demo uses 'h' for "Spacing". Let's change Spacing key to 'p' (property/padding?) or something else.
244
- # Or just stick to arrows for columns to avoid conflict.
245
- @selected_col = (@selected_col || 0) - 1
246
- @selected_col = 2 if @selected_col.negative?
247
- in type: :key, code: "s"
248
- @current_style_index = (@current_style_index + 1) % @styles.length
249
- in type: :key, code: "+"
250
- @column_spacing += 1
251
- in type: :key, code: "-"
252
- @column_spacing = [@column_spacing - 1, 0].max
253
- in type: :key, code: "p"
254
- @highlight_spacing_index = (@highlight_spacing_index + 1) % HIGHLIGHT_SPACINGS.length
255
- in type: :key, code: "x"
256
- @selected_index = @selected_index.nil? ? 0 : nil
257
- in type: :key, code: "t"
258
- # Toggle strikethrough for selected row (demonstrates Row#enable_strikethrough)
259
- if @selected_index
260
- pid = PROCESSES[@selected_index][:pid]
261
- if @strikethrough_pids.include?(pid)
262
- @strikethrough_pids.delete(pid)
263
- else
264
- @strikethrough_pids.add(pid)
265
- end
266
- end
267
- in type: :key, code: "c"
268
- @show_column_highlight = !@show_column_highlight
269
- in type: :key, code: "z"
270
- @show_cell_highlight = !@show_cell_highlight
271
- in type: :key, code: "o"
272
- @offset_mode_index = (@offset_mode_index + 1) % OFFSET_MODES.length
273
- in type: :key, code: "f"
274
- @flex_mode_index = (@flex_mode_index + 1) % FLEX_MODES.length
275
- in type: :key, code: "d"
276
- @show_header = !@show_header
277
- else
278
- nil
279
- end
280
- end
281
- end
282
-
283
- if __FILE__ == $0
284
- WidgetTable.new.run
285
- 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