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