ratatui_ruby 0.5.0 → 0.7.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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +10 -4
  7. data/CHANGELOG.md +79 -7
  8. data/README.md +37 -5
  9. data/REUSE.toml +2 -7
  10. data/doc/application_architecture.md +96 -22
  11. data/doc/application_testing.md +76 -30
  12. data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
  13. data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
  14. data/doc/contributors/architectural_overhaul/task.md +37 -0
  15. data/doc/contributors/design/ruby_frontend.md +288 -56
  16. data/doc/contributors/design/rust_backend.md +349 -54
  17. data/doc/contributors/developing_examples.md +134 -49
  18. data/doc/contributors/index.md +7 -5
  19. data/doc/contributors/v1.0.0_blockers.md +1729 -0
  20. data/doc/event_handling.md +11 -3
  21. data/doc/images/app_all_events.png +0 -0
  22. data/doc/images/app_color_picker.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_stateful_interaction.png +0 -0
  25. data/doc/images/verify_quickstart_dsl.png +0 -0
  26. data/doc/images/verify_quickstart_layout.png +0 -0
  27. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  28. data/doc/images/verify_readme_usage.png +0 -0
  29. data/doc/images/widget_barchart_demo.png +0 -0
  30. data/doc/images/widget_block_demo.png +0 -0
  31. data/doc/images/widget_canvas_demo.png +0 -0
  32. data/doc/images/widget_cell_demo.png +0 -0
  33. data/doc/images/widget_center_demo.png +0 -0
  34. data/doc/images/widget_chart_demo.png +0 -0
  35. data/doc/images/widget_list_demo.png +0 -0
  36. data/doc/images/widget_overlay_demo.png +0 -0
  37. data/doc/images/widget_render.png +0 -0
  38. data/doc/images/widget_rich_text.png +0 -0
  39. data/doc/images/widget_scroll_text.png +0 -0
  40. data/doc/images/widget_sparkline_demo.png +0 -0
  41. data/doc/images/widget_table_demo.png +0 -0
  42. data/doc/images/widget_tabs_demo.png +0 -0
  43. data/doc/images/widget_text_width.png +0 -0
  44. data/doc/index.md +11 -6
  45. data/doc/interactive_design.md +2 -2
  46. data/doc/quickstart.md +127 -165
  47. data/doc/terminal_limitations.md +92 -0
  48. data/doc/v0.7.0_migration.md +236 -0
  49. data/doc/why.md +93 -0
  50. data/examples/app_all_events/README.md +47 -27
  51. data/examples/app_all_events/app.rb +38 -35
  52. data/examples/app_all_events/model/app_model.rb +157 -0
  53. data/examples/app_all_events/model/event_entry.rb +17 -0
  54. data/examples/app_all_events/model/msg.rb +37 -0
  55. data/examples/app_all_events/update.rb +73 -0
  56. data/examples/app_all_events/view/app_view.rb +9 -9
  57. data/examples/app_all_events/view/controls_view.rb +9 -7
  58. data/examples/app_all_events/view/counts_view.rb +13 -9
  59. data/examples/app_all_events/view/live_view.rb +9 -8
  60. data/examples/app_all_events/view/log_view.rb +11 -16
  61. data/examples/app_color_picker/README.md +84 -42
  62. data/examples/app_color_picker/app.rb +24 -62
  63. data/examples/app_color_picker/controls.rb +90 -0
  64. data/examples/app_color_picker/copy_dialog.rb +45 -49
  65. data/examples/app_color_picker/export_pane.rb +126 -0
  66. data/examples/app_color_picker/input.rb +99 -67
  67. data/examples/app_color_picker/main_container.rb +178 -0
  68. data/examples/app_color_picker/palette.rb +55 -26
  69. data/examples/app_login_form/README.md +49 -0
  70. data/examples/app_login_form/app.rb +2 -3
  71. data/examples/app_stateful_interaction/README.md +33 -0
  72. data/examples/app_stateful_interaction/app.rb +272 -0
  73. data/examples/timeout_demo.rb +43 -0
  74. data/examples/verify_quickstart_dsl/README.md +49 -0
  75. data/examples/verify_quickstart_dsl/app.rb +2 -0
  76. data/examples/verify_quickstart_layout/README.md +71 -0
  77. data/examples/verify_quickstart_layout/app.rb +2 -0
  78. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  79. data/examples/verify_quickstart_lifecycle/app.rb +10 -4
  80. data/examples/verify_readme_usage/README.md +43 -0
  81. data/examples/verify_readme_usage/app.rb +8 -2
  82. data/examples/widget_barchart_demo/README.md +50 -0
  83. data/examples/widget_barchart_demo/app.rb +5 -5
  84. data/examples/widget_block_demo/README.md +36 -0
  85. data/examples/widget_block_demo/app.rb +256 -0
  86. data/examples/widget_box_demo/README.md +45 -0
  87. data/examples/widget_calendar_demo/README.md +39 -0
  88. data/examples/widget_calendar_demo/app.rb +5 -1
  89. data/examples/widget_canvas_demo/README.md +27 -0
  90. data/examples/widget_canvas_demo/app.rb +123 -0
  91. data/examples/widget_cell_demo/README.md +36 -0
  92. data/examples/widget_cell_demo/app.rb +31 -24
  93. data/examples/widget_center_demo/README.md +29 -0
  94. data/examples/widget_center_demo/app.rb +116 -0
  95. data/examples/widget_chart_demo/README.md +41 -0
  96. data/examples/widget_chart_demo/app.rb +7 -2
  97. data/examples/widget_gauge_demo/README.md +41 -0
  98. data/examples/widget_layout_split/README.md +44 -0
  99. data/examples/widget_line_gauge_demo/README.md +41 -0
  100. data/examples/widget_list_demo/README.md +49 -0
  101. data/examples/widget_list_demo/app.rb +91 -107
  102. data/examples/widget_map_demo/README.md +39 -0
  103. data/examples/{app_map_demo → widget_map_demo}/app.rb +4 -4
  104. data/examples/widget_overlay_demo/README.md +36 -0
  105. data/examples/widget_overlay_demo/app.rb +248 -0
  106. data/examples/widget_popup_demo/README.md +36 -0
  107. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  108. data/examples/widget_ratatui_logo_demo/app.rb +1 -1
  109. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  110. data/examples/widget_rect/README.md +38 -0
  111. data/examples/widget_render/README.md +37 -0
  112. data/examples/widget_render/app.rb +3 -3
  113. data/examples/widget_rich_text/README.md +35 -0
  114. data/examples/widget_rich_text/app.rb +62 -33
  115. data/examples/widget_scroll_text/README.md +37 -0
  116. data/examples/widget_scroll_text/app.rb +0 -1
  117. data/examples/widget_scrollbar_demo/README.md +37 -0
  118. data/examples/widget_sparkline_demo/README.md +42 -0
  119. data/examples/widget_sparkline_demo/app.rb +4 -3
  120. data/examples/widget_style_colors/README.md +34 -0
  121. data/examples/widget_table_demo/README.md +48 -0
  122. data/examples/{app_table_select → widget_table_demo}/app.rb +65 -12
  123. data/examples/widget_tabs_demo/README.md +41 -0
  124. data/examples/widget_tabs_demo/app.rb +15 -1
  125. data/examples/widget_text_width/README.md +35 -0
  126. data/examples/widget_text_width/app.rb +113 -0
  127. data/exe/.gitkeep +0 -0
  128. data/ext/ratatui_ruby/Cargo.lock +11 -4
  129. data/ext/ratatui_ruby/Cargo.toml +2 -1
  130. data/ext/ratatui_ruby/src/events.rs +238 -26
  131. data/ext/ratatui_ruby/src/frame.rs +116 -3
  132. data/ext/ratatui_ruby/src/lib.rs +37 -6
  133. data/ext/ratatui_ruby/src/rendering.rs +22 -21
  134. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  135. data/ext/ratatui_ruby/src/terminal.rs +39 -15
  136. data/ext/ratatui_ruby/src/text.rs +13 -4
  137. data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
  138. data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
  139. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  140. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  141. data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
  142. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  143. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  144. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
  145. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  146. data/ext/ratatui_ruby/src/widgets/table.rs +191 -34
  147. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  148. data/lib/ratatui_ruby/buffer/cell.rb +168 -0
  149. data/lib/ratatui_ruby/buffer.rb +15 -0
  150. data/lib/ratatui_ruby/cell.rb +4 -4
  151. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  152. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  153. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  154. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  155. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  156. data/lib/ratatui_ruby/event/key.rb +111 -51
  157. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  158. data/lib/ratatui_ruby/event/paste.rb +1 -1
  159. data/lib/ratatui_ruby/frame.rb +100 -4
  160. data/lib/ratatui_ruby/layout/constraint.rb +95 -0
  161. data/lib/ratatui_ruby/layout/layout.rb +106 -0
  162. data/lib/ratatui_ruby/layout/rect.rb +118 -0
  163. data/lib/ratatui_ruby/layout.rb +19 -0
  164. data/lib/ratatui_ruby/list_state.rb +88 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  166. data/lib/ratatui_ruby/schema/cursor.rb +5 -0
  167. data/lib/ratatui_ruby/schema/gauge.rb +3 -1
  168. data/lib/ratatui_ruby/schema/layout.rb +1 -1
  169. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  170. data/lib/ratatui_ruby/schema/list.rb +25 -4
  171. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  172. data/lib/ratatui_ruby/schema/rect.rb +43 -0
  173. data/lib/ratatui_ruby/schema/row.rb +66 -0
  174. data/lib/ratatui_ruby/schema/style.rb +24 -4
  175. data/lib/ratatui_ruby/schema/table.rb +29 -11
  176. data/lib/ratatui_ruby/schema/text.rb +96 -3
  177. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  178. data/lib/ratatui_ruby/style/style.rb +81 -0
  179. data/lib/ratatui_ruby/style.rb +15 -0
  180. data/lib/ratatui_ruby/table_state.rb +90 -0
  181. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  182. data/lib/ratatui_ruby/test_helper/snapshot.rb +414 -0
  183. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  184. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  185. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  186. data/lib/ratatui_ruby/test_helper.rb +65 -358
  187. data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
  188. data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
  189. data/lib/ratatui_ruby/tui/core.rb +38 -0
  190. data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
  191. data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
  192. data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
  193. data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
  194. data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
  195. data/lib/ratatui_ruby/tui.rb +75 -0
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
  198. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
  199. data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
  200. data/lib/ratatui_ruby/widgets/block.rb +192 -0
  201. data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
  202. data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
  203. data/lib/ratatui_ruby/widgets/cell.rb +47 -0
  204. data/lib/ratatui_ruby/widgets/center.rb +59 -0
  205. data/lib/ratatui_ruby/widgets/chart.rb +185 -0
  206. data/lib/ratatui_ruby/widgets/clear.rb +54 -0
  207. data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
  208. data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
  209. data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
  210. data/lib/ratatui_ruby/widgets/list.rb +127 -0
  211. data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
  212. data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
  213. data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
  214. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
  215. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
  216. data/lib/ratatui_ruby/widgets/row.rb +68 -0
  217. data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
  218. data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
  219. data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
  220. data/lib/ratatui_ruby/widgets/table.rb +141 -0
  221. data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
  222. data/lib/ratatui_ruby/widgets.rb +40 -0
  223. data/lib/ratatui_ruby.rb +64 -57
  224. data/sig/examples/app_all_events/view.rbs +1 -1
  225. data/sig/examples/app_all_events/view_state.rbs +1 -1
  226. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  227. data/sig/examples/widget_block_demo/app.rbs +32 -0
  228. data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
  229. data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
  230. data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
  231. data/sig/ratatui_ruby/event.rbs +11 -1
  232. data/sig/ratatui_ruby/frame.rbs +2 -0
  233. data/sig/ratatui_ruby/list_state.rbs +13 -0
  234. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
  235. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  236. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  237. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  238. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  239. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  240. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  241. data/sig/ratatui_ruby/schema/row.rbs +22 -0
  242. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  243. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  244. data/sig/ratatui_ruby/schema/text.rbs +9 -6
  245. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  246. data/sig/ratatui_ruby/session.rbs +41 -48
  247. data/sig/ratatui_ruby/table_state.rbs +15 -0
  248. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  249. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  250. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  251. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  252. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  253. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  254. data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
  255. data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
  256. data/sig/ratatui_ruby/tui/core.rbs +14 -0
  257. data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
  258. data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
  259. data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
  260. data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
  261. data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
  262. data/sig/ratatui_ruby/tui.rbs +19 -0
  263. data/tasks/autodoc/examples.rb +79 -0
  264. data/tasks/autodoc.rake +7 -35
  265. data/tasks/bump/changelog.rb +3 -3
  266. data/tasks/bump/links.rb +67 -0
  267. data/tasks/sourcehut.rake +64 -21
  268. data/tasks/terminal_preview/app_screenshot.rb +13 -3
  269. data/tasks/terminal_preview/saved_screenshot.rb +4 -3
  270. metadata +169 -48
  271. data/doc/contributors/dwim_dx.md +0 -366
  272. data/doc/images/app_analytics.png +0 -0
  273. data/doc/images/app_custom_widget.png +0 -0
  274. data/doc/images/app_mouse_events.png +0 -0
  275. data/doc/images/app_table_select.png +0 -0
  276. data/doc/images/widget_block_padding.png +0 -0
  277. data/doc/images/widget_block_titles.png +0 -0
  278. data/doc/images/widget_list_styles.png +0 -0
  279. data/doc/images/widget_table_flex.png +0 -0
  280. data/examples/app_all_events/model/events.rb +0 -180
  281. data/examples/app_all_events/model/highlight.rb +0 -57
  282. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
  283. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
  284. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
  285. data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
  286. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
  287. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
  288. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
  289. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
  290. data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
  291. data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
  292. data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
  293. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
  294. data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
  295. data/examples/app_all_events/view_state.rb +0 -42
  296. data/examples/app_color_picker/scene.rb +0 -201
  297. data/examples/widget_block_padding/app.rb +0 -67
  298. data/examples/widget_block_titles/app.rb +0 -69
  299. data/examples/widget_list_styles/app.rb +0 -141
  300. data/examples/widget_table_flex/app.rb +0 -95
  301. data/lib/ratatui_ruby/session/autodoc.rb +0 -417
  302. data/lib/ratatui_ruby/session.rb +0 -163
  303. data/sig/examples/widget_block_padding/app.rbs +0 -11
  304. data/sig/examples/widget_block_titles/app.rbs +0 -11
  305. data/sig/examples/widget_list_styles/app.rbs +0 -11
  306. data/tasks/autodoc/inventory.rb +0 -61
  307. data/tasks/autodoc/notice.rb +0 -26
  308. data/tasks/autodoc/rbs.rb +0 -38
  309. data/tasks/autodoc/rdoc.rb +0 -45
  310. data/tasks/bump/comparison_links.rb +0 -41
  311. /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
@@ -20,21 +20,21 @@ module RatatuiRuby
20
20
  # Run the interactive demo from the terminal:
21
21
  #
22
22
  # ruby examples/widget_table_flex/app.rb
23
- class Table < Data.define(:header, :rows, :widths, :highlight_style, :highlight_symbol, :highlight_spacing, :column_highlight_style, :cell_highlight_style, :selected_row, :selected_column, :block, :footer, :flex, :style, :column_spacing)
23
+ class Table < Data.define(:header, :rows, :widths, :row_highlight_style, :highlight_symbol, :highlight_spacing, :column_highlight_style, :cell_highlight_style, :selected_row, :selected_column, :offset, :block, :footer, :flex, :style, :column_spacing)
24
24
  ##
25
25
  # :attr_reader: header
26
- # Header row content (Array of Strings).
26
+ # Header row content (Array of Strings, Text::Spans, Text::Lines, or Paragraphs).
27
27
 
28
28
  ##
29
29
  # :attr_reader: rows
30
- # Data rows (Array of Arrays of Strings).
30
+ # Data rows (Array of Arrays). Each cell can be String, Text::Span, Text::Line, Paragraph, or Cell.
31
31
 
32
32
  ##
33
33
  # :attr_reader: widths
34
34
  # Column width constraints (Array of Constraint).
35
35
 
36
36
  ##
37
- # :attr_reader: highlight_style
37
+ # :attr_reader: row_highlight_style
38
38
  # Style for the selected row.
39
39
 
40
40
  ##
@@ -43,7 +43,7 @@ module RatatuiRuby
43
43
 
44
44
  ##
45
45
  # :attr_reader: highlight_spacing
46
- # When to show the highlight symbol column (:always, :when_selected, :never).
46
+ # When to show the highlight symbol column (<tt>:always</tt>, <tt>:when_selected</tt>, <tt>:never</tt>).
47
47
 
48
48
  ##
49
49
  # :attr_reader: column_highlight_style
@@ -61,13 +61,29 @@ module RatatuiRuby
61
61
  # :attr_reader: selected_column
62
62
  # Index of the selected column (Integer or nil).
63
63
 
64
+ ##
65
+ # :attr_reader: offset
66
+ # Scroll offset (Integer or nil).
67
+ #
68
+ # Controls the viewport's starting row position in the table.
69
+ #
70
+ # When +nil+ (default), Ratatui auto-scrolls to keep the selection visible ("natural scrolling").
71
+ #
72
+ # When set, forces the viewport to start at this row index. Use this for:
73
+ # - **Passive scrolling**: Scroll through a log table without selecting rows.
74
+ # - **Click-to-select math**: Calculate which row index corresponds to a click coordinate.
75
+ #
76
+ # *Important*: When both +offset+ and +selected_row+ are set, Ratatui may still adjust
77
+ # the viewport during rendering to ensure the selection stays visible. Set +selected_row+
78
+ # to +nil+ for fully manual scroll control.
79
+
64
80
  ##
65
81
  # :attr_reader: block
66
82
  # Optional wrapping block.
67
83
 
68
84
  ##
69
85
  # :attr_reader: footer
70
- # Footer row content (Array of Strings).
86
+ # Footer row content (Array of Strings, Text::Spans, Text::Lines, or Paragraphs).
71
87
 
72
88
  ##
73
89
  # :attr_reader: flex
@@ -83,33 +99,35 @@ module RatatuiRuby
83
99
 
84
100
  # Creates a new Table.
85
101
  #
86
- # [header] Array of strings/paragraphs.
87
- # [rows] 2D Array of strings/paragraphs.
102
+ # [header] Array of strings, Text::Spans, Text::Lines, or paragraphs.
103
+ # [rows] 2D Array where each cell is String, Text::Span, Text::Line, Paragraph, or Cell.
88
104
  # [widths] Array of Constraints.
89
- # [highlight_style] Style object.
105
+ # [row_highlight_style] Style object.
90
106
  # [highlight_symbol] String.
91
107
  # [highlight_spacing] Symbol (optional, default: <tt>:when_selected</tt>).
92
108
  # [column_highlight_style] Style object.
93
109
  # [cell_highlight_style] Style object.
94
110
  # [selected_row] Integer (nullable).
95
111
  # [selected_column] Integer (nullable).
112
+ # [offset] Numeric (nullable, coerced to Integer). Forces scroll position when set.
96
113
  # [block] Block (optional).
97
114
  # [footer] Array of strings/paragraphs (optional).
98
115
  # [flex] Symbol (optional, default: <tt>:legacy</tt>).
99
116
  # [style] Style object or Hash (optional).
100
117
  # [column_spacing] Integer (optional, default: 1).
101
- def initialize(header: nil, rows: [], widths: [], highlight_style: nil, highlight_symbol: "> ", highlight_spacing: :when_selected, column_highlight_style: nil, cell_highlight_style: nil, selected_row: nil, selected_column: nil, block: nil, footer: nil, flex: :legacy, style: nil, column_spacing: 1)
118
+ def initialize(header: nil, rows: [], widths: [], row_highlight_style: nil, highlight_symbol: "> ", highlight_spacing: :when_selected, column_highlight_style: nil, cell_highlight_style: nil, selected_row: nil, selected_column: nil, offset: nil, block: nil, footer: nil, flex: :legacy, style: nil, column_spacing: 1)
102
119
  super(
103
120
  header:,
104
121
  rows:,
105
122
  widths:,
106
- highlight_style:,
123
+ row_highlight_style:,
107
124
  highlight_symbol:,
108
125
  highlight_spacing:,
109
126
  column_highlight_style:,
110
127
  cell_highlight_style:,
111
128
  selected_row: selected_row.nil? ? nil : Integer(selected_row),
112
129
  selected_column: selected_column.nil? ? nil : Integer(selected_column),
130
+ offset: offset.nil? ? nil : Integer(offset),
113
131
  block:,
114
132
  footer:,
115
133
  flex:,
@@ -4,8 +4,42 @@
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
6
  module RatatuiRuby
7
- # Namespace for rich text components (Span and Line).
7
+ # Namespace for rich text components (Span, Line) and text utilities.
8
8
  # Distinct from canvas shapes and other Line usages.
9
+ #
10
+ # == Text Measurement
11
+ #
12
+ # The Text module provides a utility method for calculating the display width
13
+ # of strings in terminal cells. This accounts for unicode complexity:
14
+ #
15
+ # - ASCII characters: 1 cell each
16
+ # - CJK (Chinese, Japanese, Korean) characters: 2 cells each (full-width)
17
+ # - Emoji: typically 2 cells each (varies by terminal)
18
+ # - Combining marks: 0 cells (zero-width)
19
+ #
20
+ # This is essential for layout calculations in TUI applications, where you need to know
21
+ # how much space a string will occupy on the screen, not just its byte or character length.
22
+ #
23
+ # === Use Cases
24
+ #
25
+ # - Auto-sizing widgets (Button, Badge) that fit their content
26
+ # - Calculating padding or centering for text alignment
27
+ # - Building responsive layouts that adapt to content width
28
+ # - Measuring text for scrolling or truncation logic
29
+ #
30
+ # === Examples
31
+ #
32
+ # # Simple ASCII text
33
+ # RatatuiRuby::Text.width("Hello") # => 5
34
+ #
35
+ # # With emoji
36
+ # RatatuiRuby::Text.width("Hello 👍") # => 8 (5 + space + 2-width emoji)
37
+ #
38
+ # # With CJK characters
39
+ # RatatuiRuby::Text.width("你好") # => 4 (each CJK char is 2 cells)
40
+ #
41
+ # # Mixed content
42
+ # RatatuiRuby::Text.width("Hi 你好 👍") # => 11 (2 + space + 4 + space + 2)
9
43
  module Text
10
44
  # A styled string fragment.
11
45
  #
@@ -60,7 +94,7 @@ module RatatuiRuby
60
94
  # Text::Span.styled("kerrick", Style.new(fg: :blue))
61
95
  # ]
62
96
  # )
63
- class Line < Data.define(:spans, :alignment)
97
+ class Line < Data.define(:spans, :alignment, :style)
64
98
  ##
65
99
  # :attr_reader: spans
66
100
  # Array of Span objects.
@@ -71,11 +105,18 @@ module RatatuiRuby
71
105
  #
72
106
  # <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
73
107
 
108
+ ##
109
+ # :attr_reader: style
110
+ # Line-level style applied to all spans.
111
+ #
112
+ # A Style object that sets colors/modifiers for the entire line.
113
+
74
114
  # Creates a new Line.
75
115
  #
76
116
  # [spans] Array of Span objects (or Strings).
77
117
  # [alignment] Symbol (optional).
78
- def initialize(spans: [], alignment: nil)
118
+ # [style] Style object (optional).
119
+ def initialize(spans: [], alignment: nil, style: nil)
79
120
  super
80
121
  end
81
122
 
@@ -85,6 +126,58 @@ module RatatuiRuby
85
126
  def self.from_string(content, alignment: nil)
86
127
  new(spans: [Span.new(content:, style: nil)], alignment:)
87
128
  end
129
+
130
+ # Calculates the display width of this line in terminal cells.
131
+ #
132
+ # Sums the widths of all span contents using the same unicode-aware
133
+ # algorithm as Text.width. Useful for layout calculations.
134
+ #
135
+ # === Examples
136
+ #
137
+ # line = Text::Line.new(spans: [
138
+ # Text::Span.new(content: "Hello "),
139
+ # Text::Span.new(content: "世界")
140
+ # ])
141
+ # line.width # => 10 (6 ASCII + 4 CJK)
142
+ #
143
+ # Returns: Integer (number of terminal cells)
144
+ def width
145
+ RatatuiRuby::Text.width(spans.map { |s| s.content.to_s }.join)
146
+ end
147
+ end
148
+
149
+ ##
150
+ # :method: width
151
+ # :call-seq: width(string) -> Integer
152
+ #
153
+ # Calculates the display width of a string in terminal cells.
154
+ #
155
+ # Layout demands precision. Terminals measure space in cells, not characters. An ASCII letter occupies one cell. A Chinese character occupies two. An emoji occupies two. Combining marks occupy zero.
156
+ #
157
+ # Measuring width manually is error-prone. You can count <tt>string.length</tt>, but that counts characters, not cells. A string with one emoji counts as 1 character but occupies 2 cells.
158
+ #
159
+ # This method returns the true display width. Use it to auto-size widgets, calculate padding, center text, or build responsive layouts.
160
+ #
161
+ # === Examples
162
+ #
163
+ # RatatuiRuby::Text.width("Hello") # => 5 (5 ASCII chars × 1 cell)
164
+ #
165
+ # RatatuiRuby::Text.width("你好") # => 4 (2 CJK chars × 2 cells)
166
+ #
167
+ # RatatuiRuby::Text.width("Hello 👍") # => 8 (5 ASCII + 1 space + 1 emoji × 2)
168
+ #
169
+ # # In the Session DSL (easier)
170
+ # RatatuiRuby.run do |tui|
171
+ # width = tui.text_width("Hello 👍")
172
+ # end
173
+ #
174
+ # [string] String to measure (String or object convertible to String)
175
+ # Returns: Integer (number of terminal cells the string occupies)
176
+ # Raises: TypeError if the argument is not a String
177
+ #
178
+ # (Native method implemented in Rust)
179
+ def self.width(string)
180
+ RatatuiRuby._text_width(string)
88
181
  end
89
182
  end
90
183
  end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ # Mutable state object for Scrollbar widgets.
8
+ #
9
+ # When using {Frame#render_stateful_widget}, the State object is the
10
+ # *single source of truth* for position and content length. Widget
11
+ # properties (+position+, +content_length+) are *ignored* in stateful mode.
12
+ #
13
+ # == Example
14
+ #
15
+ # @scrollbar_state = RatatuiRuby::ScrollbarState.new(100)
16
+ # @scrollbar_state.position = 25
17
+ #
18
+ # RatatuiRuby.draw do |frame|
19
+ # scrollbar = RatatuiRuby::Scrollbar.new(orientation: :vertical_right)
20
+ # frame.render_stateful_widget(scrollbar, frame.area, @scrollbar_state)
21
+ # end
22
+ #
23
+ class ScrollbarState
24
+ ##
25
+ # :method: new
26
+ # :call-seq: new(content_length) -> ScrollbarState
27
+ #
28
+ # Creates a new ScrollbarState with the given content length.
29
+ #
30
+ # (Native method implemented in Rust)
31
+
32
+ ##
33
+ # :method: position
34
+ # :call-seq: position() -> Integer
35
+ #
36
+ # Returns the current scroll position.
37
+ #
38
+ # (Native method implemented in Rust)
39
+
40
+ ##
41
+ # :method: position=
42
+ # :call-seq: position=(value) -> Integer
43
+ #
44
+ # Sets the current scroll position.
45
+ #
46
+ # (Native method implemented in Rust)
47
+
48
+ ##
49
+ # :method: content_length
50
+ # :call-seq: content_length() -> Integer
51
+ #
52
+ # Returns the total content length.
53
+ #
54
+ # (Native method implemented in Rust)
55
+
56
+ ##
57
+ # :method: content_length=
58
+ # :call-seq: content_length=(value) -> Integer
59
+ #
60
+ # Sets the total content length.
61
+ #
62
+ # (Native method implemented in Rust)
63
+
64
+ ##
65
+ # :method: viewport_content_length
66
+ # :call-seq: viewport_content_length() -> Integer
67
+ #
68
+ # Returns the viewport content length.
69
+ #
70
+ # (Native method implemented in Rust)
71
+
72
+ ##
73
+ # :method: viewport_content_length=
74
+ # :call-seq: viewport_content_length=(value) -> Integer
75
+ #
76
+ # Sets the viewport content length.
77
+ #
78
+ # (Native method implemented in Rust)
79
+
80
+ ##
81
+ # :method: first
82
+ # :call-seq: first() -> nil
83
+ #
84
+ # Scrolls to the first position.
85
+ #
86
+ # (Native method implemented in Rust)
87
+
88
+ ##
89
+ # :method: last
90
+ # :call-seq: last() -> nil
91
+ #
92
+ # Scrolls to the last position.
93
+ #
94
+ # (Native method implemented in Rust)
95
+
96
+ ##
97
+ # :method: next
98
+ # :call-seq: next() -> nil
99
+ #
100
+ # Scrolls to the next position.
101
+ #
102
+ # (Native method implemented in Rust)
103
+
104
+ ##
105
+ # :method: prev
106
+ # :call-seq: prev() -> nil
107
+ #
108
+ # Scrolls to the previous position.
109
+ #
110
+ # (Native method implemented in Rust)
111
+ end
112
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ module Style
8
+ # Defines colors and text modifiers.
9
+ #
10
+ # The terminal is traditionally monochrome, but efficient interfaces use color to convey meaning.
11
+ # Red for errors. Green for success. Bold for headers.
12
+ #
13
+ # This value object encapsulates those choices. It applies foreground and background colors. It adds effects like italics or blinking.
14
+ #
15
+ # Use it to theme your application or highlight critical data.
16
+ #
17
+ # === Examples
18
+ #
19
+ # # Standard colors
20
+ # Style::Style.new(fg: :red, bg: :white, modifiers: [:bold])
21
+ #
22
+ # # Hex colors
23
+ # Style::Style.new(fg: "#ff00ff")
24
+ #
25
+ # === Supported Colors
26
+ #
27
+ # ==== Integer
28
+ # Represents an indexed color from the Xterm 256-color palette (0-255).
29
+ # * <tt>0</tt>–<tt>15</tt>: Standard and bright ANSI colors.
30
+ # * <tt>16</tt>–<tt>231</tt>: {6x6x6 Color Cube}[https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit].
31
+ # * <tt>232</tt>–<tt>255</tt>: Grayscale ramp.
32
+ #
33
+ # ==== Symbol
34
+ # Represents a named color from the standard ANSI palette. Supported values:
35
+ # * <tt>:black</tt>, <tt>:red</tt>, <tt>:green</tt>, <tt>:yellow</tt>,
36
+ # <tt>:blue</tt>, <tt>:magenta</tt>, <tt>:cyan</tt>, <tt>:gray</tt>
37
+ # * <tt>:dark_gray</tt>, <tt>:light_red</tt>, <tt>:light_green</tt>,
38
+ # <tt>:light_yellow</tt>, <tt>:light_blue</tt>, <tt>:light_magenta</tt>,
39
+ # <tt>:light_cyan</tt>, <tt>:white</tt>
40
+ #
41
+ # ==== String
42
+ # Represents a specific RGB color using a Hex code (<tt>"#RRGGBB"</tt>).
43
+ # Requires a terminal emulator with "True Color" (24-bit color) support.
44
+ class Style < Data.define(:fg, :bg, :modifiers)
45
+ ##
46
+ # :attr_reader: fg
47
+ # Foreground color.
48
+ #
49
+ # Symbol (<tt>:red</tt>), Hex String (<tt>"#ffffff"</tt>), or Integer (0-255).
50
+
51
+ ##
52
+ # :attr_reader: bg
53
+ # Background color.
54
+ #
55
+ # Symbol (<tt>:black</tt>), Hex String (<tt>"#000000"</tt>), or Integer (0-255).
56
+
57
+ ##
58
+ # :attr_reader: modifiers
59
+ # Text effects.
60
+ #
61
+ # Array of symbols: <tt>:bold</tt>, <tt>:dim</tt>, <tt>:italic</tt>, <tt>:underlined</tt>,
62
+ # <tt>:slow_blink</tt>, <tt>:rapid_blink</tt>, <tt>:reversed</tt>, <tt>:hidden</tt>, <tt>:crossed_out</tt>.
63
+
64
+ # Creates a new Style.
65
+ #
66
+ # [fg] Color (Symbol/String/Integer).
67
+ # [bg] Color (Symbol/String/Integer).
68
+ # [modifiers] Array of Symbols.
69
+ def initialize(fg: nil, bg: nil, modifiers: [])
70
+ super
71
+ end
72
+
73
+ # Returns an empty style.
74
+ #
75
+ # Use this as a baseline to prevent style inheritance issues or when no styling is required.
76
+ def self.default
77
+ new
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ # Styling primitives for colors and text effects.
8
+ #
9
+ # This module mirrors +ratatui::style+ and contains:
10
+ # - {Style} — Colors and modifiers
11
+ module Style
12
+ end
13
+ end
14
+
15
+ require_relative "style/style"
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ # Mutable state object for Table widgets.
8
+ #
9
+ # When using {Frame#render_stateful_widget}, the State object is the
10
+ # *single source of truth* for selection and scroll offset. Widget
11
+ # properties (+selected_row+, +selected_column+, +offset+) are *ignored*
12
+ # in stateful mode.
13
+ #
14
+ # == Example
15
+ #
16
+ # @table_state = RatatuiRuby::TableState.new
17
+ # @table_state.select(1) # Select second row
18
+ # @table_state.select_column(0) # Select first column
19
+ #
20
+ # RatatuiRuby.draw do |frame|
21
+ # table = RatatuiRuby::Widgets::Table.new(rows: [...], widths: [...])
22
+ # frame.render_stateful_widget(table, frame.area, @table_state)
23
+ # end
24
+ #
25
+ class TableState
26
+ ##
27
+ # :method: new
28
+ # :call-seq: new(selected = nil) -> TableState
29
+ #
30
+ # Creates a new TableState with optional initial row selection.
31
+ #
32
+ # (Native method implemented in Rust)
33
+
34
+ ##
35
+ # :method: select
36
+ # :call-seq: select(index) -> nil
37
+ #
38
+ # Sets the selected row index. Pass +nil+ to deselect.
39
+ #
40
+ # (Native method implemented in Rust)
41
+
42
+ ##
43
+ # :method: selected
44
+ # :call-seq: selected() -> Integer or nil
45
+ #
46
+ # Returns the currently selected row index.
47
+ #
48
+ # (Native method implemented in Rust)
49
+
50
+ ##
51
+ # :method: select_column
52
+ # :call-seq: select_column(index) -> nil
53
+ #
54
+ # Sets the selected column index. Pass +nil+ to deselect.
55
+ #
56
+ # (Native method implemented in Rust)
57
+
58
+ ##
59
+ # :method: selected_column
60
+ # :call-seq: selected_column() -> Integer or nil
61
+ #
62
+ # Returns the currently selected column index.
63
+ #
64
+ # (Native method implemented in Rust)
65
+
66
+ ##
67
+ # :method: offset
68
+ # :call-seq: offset() -> Integer
69
+ #
70
+ # Returns the current scroll offset.
71
+ #
72
+ # (Native method implemented in Rust)
73
+
74
+ ##
75
+ # :method: scroll_down_by
76
+ # :call-seq: scroll_down_by(n) -> nil
77
+ #
78
+ # Scrolls down by +n+ rows.
79
+ #
80
+ # (Native method implemented in Rust)
81
+
82
+ ##
83
+ # :method: scroll_up_by
84
+ # :call-seq: scroll_up_by(n) -> nil
85
+ #
86
+ # Scrolls up by +n+ rows.
87
+ #
88
+ # (Native method implemented in Rust)
89
+ end
90
+ end