ratatui_ruby 1.2.0 β†’ 1.2.2

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 (260) 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/version.rb +1 -1
  6. metadata +1 -255
  7. data/.builds/ruby-3.2.yml +0 -54
  8. data/.builds/ruby-3.3.yml +0 -54
  9. data/.builds/ruby-3.4.yml +0 -54
  10. data/.builds/ruby-4.0.0.yml +0 -54
  11. data/.pre-commit-config.yaml +0 -16
  12. data/.rubocop.yml +0 -10
  13. data/AGENTS.md +0 -147
  14. data/CHANGELOG.md +0 -751
  15. data/README.md +0 -187
  16. data/README.rdoc +0 -302
  17. data/Rakefile +0 -11
  18. data/Steepfile +0 -50
  19. data/doc/concepts/application_architecture.md +0 -321
  20. data/doc/concepts/application_testing.md +0 -193
  21. data/doc/concepts/async.md +0 -190
  22. data/doc/concepts/custom_widgets.md +0 -247
  23. data/doc/concepts/debugging.md +0 -401
  24. data/doc/concepts/event_handling.md +0 -162
  25. data/doc/concepts/interactive_design.md +0 -146
  26. data/doc/contributors/auditing/parity.md +0 -239
  27. data/doc/contributors/design/ruby_frontend.md +0 -448
  28. data/doc/contributors/design/rust_backend.md +0 -434
  29. data/doc/contributors/design.md +0 -11
  30. data/doc/contributors/developing_examples.md +0 -400
  31. data/doc/contributors/documentation_style.md +0 -121
  32. data/doc/contributors/index.md +0 -21
  33. data/doc/contributors/releasing.md +0 -215
  34. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  35. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  36. data/doc/contributors/todo/align/term.md +0 -351
  37. data/doc/contributors/todo/align/terminal.md +0 -647
  38. data/doc/contributors/todo/future_work.md +0 -169
  39. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  40. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  41. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  42. data/doc/custom.css +0 -22
  43. data/doc/getting_started/quickstart.md +0 -291
  44. data/doc/getting_started/why.md +0 -93
  45. data/doc/images/app_all_events.png +0 -0
  46. data/doc/images/app_cli_rich_moments.gif +0 -0
  47. data/doc/images/app_color_picker.png +0 -0
  48. data/doc/images/app_debugging_showcase.gif +0 -0
  49. data/doc/images/app_debugging_showcase.png +0 -0
  50. data/doc/images/app_external_editor.gif +0 -0
  51. data/doc/images/app_login_form.png +0 -0
  52. data/doc/images/app_stateful_interaction.png +0 -0
  53. data/doc/images/verify_quickstart_dsl.png +0 -0
  54. data/doc/images/verify_quickstart_layout.png +0 -0
  55. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  56. data/doc/images/verify_readme_usage.png +0 -0
  57. data/doc/images/widget_barchart.png +0 -0
  58. data/doc/images/widget_block.png +0 -0
  59. data/doc/images/widget_box.png +0 -0
  60. data/doc/images/widget_calendar.png +0 -0
  61. data/doc/images/widget_canvas.png +0 -0
  62. data/doc/images/widget_cell.png +0 -0
  63. data/doc/images/widget_center.png +0 -0
  64. data/doc/images/widget_chart.png +0 -0
  65. data/doc/images/widget_gauge.png +0 -0
  66. data/doc/images/widget_layout_split.png +0 -0
  67. data/doc/images/widget_line_gauge.png +0 -0
  68. data/doc/images/widget_list.png +0 -0
  69. data/doc/images/widget_map.png +0 -0
  70. data/doc/images/widget_overlay.png +0 -0
  71. data/doc/images/widget_popup.png +0 -0
  72. data/doc/images/widget_ratatui_logo.png +0 -0
  73. data/doc/images/widget_ratatui_mascot.png +0 -0
  74. data/doc/images/widget_rect.png +0 -0
  75. data/doc/images/widget_render.png +0 -0
  76. data/doc/images/widget_rich_text.png +0 -0
  77. data/doc/images/widget_scroll_text.png +0 -0
  78. data/doc/images/widget_scrollbar.png +0 -0
  79. data/doc/images/widget_sparkline.png +0 -0
  80. data/doc/images/widget_style_colors.png +0 -0
  81. data/doc/images/widget_table.png +0 -0
  82. data/doc/images/widget_tabs.png +0 -0
  83. data/doc/images/widget_text_width.png +0 -0
  84. data/doc/index.md +0 -34
  85. data/doc/troubleshooting/async.md +0 -4
  86. data/doc/troubleshooting/terminal_limitations.md +0 -131
  87. data/doc/troubleshooting/tui_output.md +0 -197
  88. data/examples/app_all_events/README.md +0 -114
  89. data/examples/app_all_events/app.rb +0 -98
  90. data/examples/app_all_events/model/app_model.rb +0 -159
  91. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  92. data/examples/app_all_events/model/event_entry.rb +0 -94
  93. data/examples/app_all_events/model/msg.rb +0 -39
  94. data/examples/app_all_events/model/timestamp.rb +0 -56
  95. data/examples/app_all_events/update.rb +0 -75
  96. data/examples/app_all_events/view/app_view.rb +0 -80
  97. data/examples/app_all_events/view/controls_view.rb +0 -54
  98. data/examples/app_all_events/view/counts_view.rb +0 -61
  99. data/examples/app_all_events/view/live_view.rb +0 -72
  100. data/examples/app_all_events/view/log_view.rb +0 -57
  101. data/examples/app_all_events/view.rb +0 -9
  102. data/examples/app_cli_rich_moments/README.md +0 -81
  103. data/examples/app_cli_rich_moments/app.rb +0 -189
  104. data/examples/app_color_picker/README.md +0 -156
  105. data/examples/app_color_picker/app.rb +0 -76
  106. data/examples/app_color_picker/clipboard.rb +0 -86
  107. data/examples/app_color_picker/color.rb +0 -193
  108. data/examples/app_color_picker/controls.rb +0 -92
  109. data/examples/app_color_picker/copy_dialog.rb +0 -168
  110. data/examples/app_color_picker/export_pane.rb +0 -128
  111. data/examples/app_color_picker/harmony.rb +0 -58
  112. data/examples/app_color_picker/input.rb +0 -176
  113. data/examples/app_color_picker/main_container.rb +0 -180
  114. data/examples/app_color_picker/palette.rb +0 -111
  115. data/examples/app_debugging_showcase/README.md +0 -119
  116. data/examples/app_debugging_showcase/app.rb +0 -318
  117. data/examples/app_external_editor/README.md +0 -62
  118. data/examples/app_external_editor/app.rb +0 -344
  119. data/examples/app_login_form/README.md +0 -58
  120. data/examples/app_login_form/app.rb +0 -109
  121. data/examples/app_stateful_interaction/README.md +0 -35
  122. data/examples/app_stateful_interaction/app.rb +0 -328
  123. data/examples/timeout_demo.rb +0 -45
  124. data/examples/verify_quickstart_dsl/README.md +0 -55
  125. data/examples/verify_quickstart_dsl/app.rb +0 -49
  126. data/examples/verify_quickstart_layout/README.md +0 -77
  127. data/examples/verify_quickstart_layout/app.rb +0 -73
  128. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  129. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  130. data/examples/verify_readme_usage/README.md +0 -49
  131. data/examples/verify_readme_usage/app.rb +0 -42
  132. data/examples/verify_website_managed/README.md +0 -48
  133. data/examples/verify_website_managed/app.rb +0 -36
  134. data/examples/verify_website_menu/README.md +0 -60
  135. data/examples/verify_website_menu/app.rb +0 -84
  136. data/examples/verify_website_spinner/README.md +0 -44
  137. data/examples/verify_website_spinner/app.rb +0 -34
  138. data/examples/widget_barchart/README.md +0 -58
  139. data/examples/widget_barchart/app.rb +0 -240
  140. data/examples/widget_block/README.md +0 -44
  141. data/examples/widget_block/app.rb +0 -258
  142. data/examples/widget_box/README.md +0 -54
  143. data/examples/widget_box/app.rb +0 -255
  144. data/examples/widget_calendar/README.md +0 -48
  145. data/examples/widget_calendar/app.rb +0 -115
  146. data/examples/widget_canvas/README.md +0 -31
  147. data/examples/widget_canvas/app.rb +0 -130
  148. data/examples/widget_cell/README.md +0 -45
  149. data/examples/widget_cell/app.rb +0 -112
  150. data/examples/widget_center/README.md +0 -33
  151. data/examples/widget_center/app.rb +0 -118
  152. data/examples/widget_chart/README.md +0 -50
  153. data/examples/widget_chart/app.rb +0 -220
  154. data/examples/widget_gauge/README.md +0 -50
  155. data/examples/widget_gauge/app.rb +0 -229
  156. data/examples/widget_layout_split/README.md +0 -53
  157. data/examples/widget_layout_split/app.rb +0 -260
  158. data/examples/widget_line_gauge/README.md +0 -50
  159. data/examples/widget_line_gauge/app.rb +0 -219
  160. data/examples/widget_list/README.md +0 -58
  161. data/examples/widget_list/app.rb +0 -382
  162. data/examples/widget_map/README.md +0 -48
  163. data/examples/widget_map/app.rb +0 -95
  164. data/examples/widget_overlay/README.md +0 -45
  165. data/examples/widget_overlay/app.rb +0 -250
  166. data/examples/widget_popup/README.md +0 -45
  167. data/examples/widget_popup/app.rb +0 -106
  168. data/examples/widget_ratatui_logo/README.md +0 -43
  169. data/examples/widget_ratatui_logo/app.rb +0 -104
  170. data/examples/widget_ratatui_mascot/README.md +0 -43
  171. data/examples/widget_ratatui_mascot/app.rb +0 -95
  172. data/examples/widget_rect/README.md +0 -53
  173. data/examples/widget_rect/app.rb +0 -222
  174. data/examples/widget_render/README.md +0 -46
  175. data/examples/widget_render/app.rb +0 -186
  176. data/examples/widget_render/app.rbs +0 -41
  177. data/examples/widget_rich_text/README.md +0 -44
  178. data/examples/widget_rich_text/app.rb +0 -193
  179. data/examples/widget_scroll_text/README.md +0 -46
  180. data/examples/widget_scroll_text/app.rb +0 -109
  181. data/examples/widget_scrollbar/README.md +0 -46
  182. data/examples/widget_scrollbar/app.rb +0 -155
  183. data/examples/widget_sparkline/README.md +0 -51
  184. data/examples/widget_sparkline/app.rb +0 -277
  185. data/examples/widget_style_colors/README.md +0 -43
  186. data/examples/widget_style_colors/app.rb +0 -83
  187. data/examples/widget_table/README.md +0 -57
  188. data/examples/widget_table/app.rb +0 -285
  189. data/examples/widget_tabs/README.md +0 -50
  190. data/examples/widget_tabs/app.rb +0 -183
  191. data/examples/widget_text_width/README.md +0 -44
  192. data/examples/widget_text_width/app.rb +0 -117
  193. data/migrate_to_buffer.rb +0 -145
  194. data/mise.toml +0 -8
  195. data/tasks/autodoc/examples.rb +0 -87
  196. data/tasks/autodoc/member.rb +0 -58
  197. data/tasks/autodoc/name.rb +0 -21
  198. data/tasks/autodoc.rake +0 -21
  199. data/tasks/bump/bump_workflow.rb +0 -49
  200. data/tasks/bump/cargo_lockfile.rb +0 -21
  201. data/tasks/bump/changelog.rb +0 -104
  202. data/tasks/bump/header.rb +0 -32
  203. data/tasks/bump/history.rb +0 -32
  204. data/tasks/bump/links.rb +0 -69
  205. data/tasks/bump/manifest.rb +0 -33
  206. data/tasks/bump/patch_release.rb +0 -19
  207. data/tasks/bump/release_branch.rb +0 -17
  208. data/tasks/bump/release_from_trunk.rb +0 -49
  209. data/tasks/bump/repository.rb +0 -54
  210. data/tasks/bump/ruby_gem.rb +0 -29
  211. data/tasks/bump/sem_ver.rb +0 -44
  212. data/tasks/bump/unreleased_section.rb +0 -73
  213. data/tasks/bump.rake +0 -61
  214. data/tasks/doc/documentation.rb +0 -59
  215. data/tasks/doc/link/file_url.rb +0 -30
  216. data/tasks/doc/link/relative_path.rb +0 -61
  217. data/tasks/doc/link/web_url.rb +0 -55
  218. data/tasks/doc/link.rb +0 -52
  219. data/tasks/doc/link_audit.rb +0 -116
  220. data/tasks/doc/problem.rb +0 -40
  221. data/tasks/doc/source_file.rb +0 -93
  222. data/tasks/doc.rake +0 -905
  223. data/tasks/example_viewer.html.erb +0 -172
  224. data/tasks/extension.rake +0 -14
  225. data/tasks/license/headers_md.rb +0 -223
  226. data/tasks/license/headers_rb.rb +0 -210
  227. data/tasks/license/license_utils.rb +0 -130
  228. data/tasks/license/snippets_md.rb +0 -315
  229. data/tasks/license/snippets_rdoc.rb +0 -150
  230. data/tasks/license.rake +0 -91
  231. data/tasks/lint.rake +0 -170
  232. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  233. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  234. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  235. data/tasks/rbs_predicates.rake +0 -31
  236. data/tasks/rdoc_config.rb +0 -29
  237. data/tasks/resources/build.yml.erb +0 -60
  238. data/tasks/resources/index.html.erb +0 -141
  239. data/tasks/resources/rubies.yml +0 -7
  240. data/tasks/sourcehut.rake +0 -122
  241. data/tasks/steep.rake +0 -11
  242. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  243. data/tasks/terminal_preview/crash_report.rb +0 -54
  244. data/tasks/terminal_preview/example_app.rb +0 -27
  245. data/tasks/terminal_preview/launcher_script.rb +0 -48
  246. data/tasks/terminal_preview/preview_collection.rb +0 -60
  247. data/tasks/terminal_preview/preview_timing.rb +0 -24
  248. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  249. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  250. data/tasks/terminal_preview/system_appearance.rb +0 -13
  251. data/tasks/terminal_preview/terminal_window.rb +0 -138
  252. data/tasks/terminal_preview/window_id.rb +0 -16
  253. data/tasks/terminal_preview.rake +0 -30
  254. data/tasks/test.rake +0 -36
  255. data/tasks/website/index_page.rb +0 -30
  256. data/tasks/website/version.rb +0 -122
  257. data/tasks/website/version_menu.rb +0 -68
  258. data/tasks/website/versioned_documentation.rb +0 -83
  259. data/tasks/website/website.rb +0 -53
  260. 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