ratatui_ruby 0.3.1 → 0.5.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 (350) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +14 -12
  3. data/.builds/ruby-3.3.yml +14 -12
  4. data/.builds/ruby-3.4.yml +14 -12
  5. data/.builds/ruby-4.0.0.yml +14 -12
  6. data/AGENTS.md +89 -132
  7. data/CHANGELOG.md +223 -1
  8. data/README.md +23 -16
  9. data/REUSE.toml +20 -0
  10. data/doc/application_architecture.md +176 -0
  11. data/doc/application_testing.md +17 -10
  12. data/doc/contributors/design/ruby_frontend.md +11 -7
  13. data/doc/contributors/developing_examples.md +261 -0
  14. data/doc/contributors/documentation_style.md +104 -0
  15. data/doc/contributors/dwim_dx.md +366 -0
  16. data/doc/contributors/index.md +2 -0
  17. data/doc/custom.css +14 -0
  18. data/doc/event_handling.md +125 -0
  19. data/doc/images/app_all_events.png +0 -0
  20. data/doc/images/app_analytics.png +0 -0
  21. data/doc/images/app_color_picker.png +0 -0
  22. data/doc/images/app_custom_widget.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_map_demo.png +0 -0
  25. data/doc/images/app_mouse_events.png +0 -0
  26. data/doc/images/app_table_select.png +0 -0
  27. data/doc/images/verify_quickstart_dsl.png +0 -0
  28. data/doc/images/verify_quickstart_layout.png +0 -0
  29. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  30. data/doc/images/verify_readme_usage.png +0 -0
  31. data/doc/images/widget_barchart_demo.png +0 -0
  32. data/doc/images/widget_block_padding.png +0 -0
  33. data/doc/images/widget_block_titles.png +0 -0
  34. data/doc/images/widget_box_demo.png +0 -0
  35. data/doc/images/widget_calendar_demo.png +0 -0
  36. data/doc/images/widget_cell_demo.png +0 -0
  37. data/doc/images/widget_chart_demo.png +0 -0
  38. data/doc/images/widget_gauge_demo.png +0 -0
  39. data/doc/images/widget_layout_split.png +0 -0
  40. data/doc/images/widget_line_gauge_demo.png +0 -0
  41. data/doc/images/widget_list_demo.png +0 -0
  42. data/doc/images/widget_list_styles.png +0 -0
  43. data/doc/images/widget_popup_demo.png +0 -0
  44. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  45. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  46. data/doc/images/widget_rect.png +0 -0
  47. data/doc/images/widget_render.png +0 -0
  48. data/doc/images/widget_rich_text.png +0 -0
  49. data/doc/images/widget_scroll_text.png +0 -0
  50. data/doc/images/widget_scrollbar_demo.png +0 -0
  51. data/doc/images/widget_sparkline_demo.png +0 -0
  52. data/doc/images/widget_style_colors.png +0 -0
  53. data/doc/images/widget_table_flex.png +0 -0
  54. data/doc/images/widget_tabs_demo.png +0 -0
  55. data/doc/index.md +1 -0
  56. data/doc/interactive_design.md +116 -0
  57. data/doc/quickstart.md +186 -84
  58. data/examples/app_all_events/README.md +81 -0
  59. data/examples/app_all_events/app.rb +93 -0
  60. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  61. data/examples/app_all_events/model/event_entry.rb +75 -0
  62. data/examples/app_all_events/model/events.rb +180 -0
  63. data/examples/app_all_events/model/highlight.rb +57 -0
  64. data/examples/app_all_events/model/timestamp.rb +54 -0
  65. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
  66. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
  67. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
  68. data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
  69. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
  70. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
  71. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
  72. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
  73. data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
  74. data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
  75. data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
  76. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
  77. data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
  78. data/examples/app_all_events/view/app_view.rb +78 -0
  79. data/examples/app_all_events/view/controls_view.rb +50 -0
  80. data/examples/app_all_events/view/counts_view.rb +55 -0
  81. data/examples/app_all_events/view/live_view.rb +69 -0
  82. data/examples/app_all_events/view/log_view.rb +60 -0
  83. data/{lib/ratatui_ruby/output.rb → examples/app_all_events/view.rb} +1 -1
  84. data/examples/app_all_events/view_state.rb +42 -0
  85. data/examples/app_color_picker/README.md +94 -0
  86. data/examples/app_color_picker/app.rb +112 -0
  87. data/examples/app_color_picker/clipboard.rb +84 -0
  88. data/examples/app_color_picker/color.rb +191 -0
  89. data/examples/app_color_picker/copy_dialog.rb +170 -0
  90. data/examples/app_color_picker/harmony.rb +56 -0
  91. data/examples/app_color_picker/input.rb +142 -0
  92. data/examples/app_color_picker/palette.rb +80 -0
  93. data/examples/app_color_picker/scene.rb +201 -0
  94. data/examples/app_login_form/app.rb +108 -0
  95. data/examples/app_map_demo/app.rb +93 -0
  96. data/examples/app_table_select/app.rb +201 -0
  97. data/examples/verify_quickstart_dsl/app.rb +45 -0
  98. data/examples/verify_quickstart_layout/app.rb +69 -0
  99. data/examples/verify_quickstart_lifecycle/app.rb +48 -0
  100. data/examples/verify_readme_usage/app.rb +34 -0
  101. data/examples/widget_barchart_demo/app.rb +238 -0
  102. data/examples/widget_block_padding/app.rb +67 -0
  103. data/examples/widget_block_titles/app.rb +69 -0
  104. data/examples/widget_box_demo/app.rb +250 -0
  105. data/examples/widget_calendar_demo/app.rb +109 -0
  106. data/examples/widget_cell_demo/app.rb +104 -0
  107. data/examples/widget_chart_demo/app.rb +213 -0
  108. data/examples/widget_gauge_demo/app.rb +212 -0
  109. data/examples/widget_layout_split/app.rb +246 -0
  110. data/examples/widget_line_gauge_demo/app.rb +217 -0
  111. data/examples/widget_list_demo/app.rb +382 -0
  112. data/examples/widget_list_styles/app.rb +141 -0
  113. data/examples/widget_popup_demo/app.rb +104 -0
  114. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  115. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  116. data/examples/widget_rect/app.rb +205 -0
  117. data/examples/widget_render/app.rb +184 -0
  118. data/examples/widget_rich_text/app.rb +137 -0
  119. data/examples/widget_scroll_text/app.rb +108 -0
  120. data/examples/widget_scrollbar_demo/app.rb +153 -0
  121. data/examples/widget_sparkline_demo/app.rb +274 -0
  122. data/examples/widget_style_colors/app.rb +102 -0
  123. data/examples/widget_table_flex/app.rb +95 -0
  124. data/examples/widget_tabs_demo/app.rb +167 -0
  125. data/ext/ratatui_ruby/Cargo.lock +889 -115
  126. data/ext/ratatui_ruby/Cargo.toml +4 -3
  127. data/ext/ratatui_ruby/clippy.toml +7 -0
  128. data/ext/ratatui_ruby/extconf.rb +7 -0
  129. data/ext/ratatui_ruby/src/events.rs +293 -219
  130. data/ext/ratatui_ruby/src/frame.rs +115 -0
  131. data/ext/ratatui_ruby/src/lib.rs +105 -24
  132. data/ext/ratatui_ruby/src/rendering.rs +94 -10
  133. data/ext/ratatui_ruby/src/style.rs +357 -93
  134. data/ext/ratatui_ruby/src/terminal.rs +121 -31
  135. data/ext/ratatui_ruby/src/text.rs +178 -0
  136. data/ext/ratatui_ruby/src/widgets/barchart.rs +99 -24
  137. data/ext/ratatui_ruby/src/widgets/block.rs +32 -3
  138. data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
  139. data/ext/ratatui_ruby/src/widgets/canvas.rs +44 -9
  140. data/ext/ratatui_ruby/src/widgets/chart.rs +79 -27
  141. data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
  142. data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
  143. data/ext/ratatui_ruby/src/widgets/layout.rs +223 -15
  144. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
  145. data/ext/ratatui_ruby/src/widgets/list.rs +114 -11
  146. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  147. data/ext/ratatui_ruby/src/widgets/overlay.rs +4 -2
  148. data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
  149. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  150. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +51 -0
  151. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +61 -7
  152. data/ext/ratatui_ruby/src/widgets/sparkline.rs +73 -6
  153. data/ext/ratatui_ruby/src/widgets/table.rs +177 -64
  154. data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
  155. data/lib/ratatui_ruby/cell.rb +166 -0
  156. data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
  157. data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
  158. data/lib/ratatui_ruby/event/key.rb +211 -0
  159. data/lib/ratatui_ruby/event/mouse.rb +124 -0
  160. data/lib/ratatui_ruby/event/none.rb +43 -0
  161. data/lib/ratatui_ruby/event/paste.rb +71 -0
  162. data/lib/ratatui_ruby/event/resize.rb +80 -0
  163. data/lib/ratatui_ruby/event.rb +131 -0
  164. data/lib/ratatui_ruby/frame.rb +87 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
  166. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +23 -0
  167. data/lib/ratatui_ruby/schema/bar_chart.rb +226 -17
  168. data/lib/ratatui_ruby/schema/block.rb +178 -11
  169. data/lib/ratatui_ruby/schema/calendar.rb +70 -14
  170. data/lib/ratatui_ruby/schema/canvas.rb +213 -46
  171. data/lib/ratatui_ruby/schema/center.rb +46 -8
  172. data/lib/ratatui_ruby/schema/chart.rb +134 -32
  173. data/lib/ratatui_ruby/schema/clear.rb +22 -53
  174. data/lib/ratatui_ruby/schema/constraint.rb +72 -12
  175. data/lib/ratatui_ruby/schema/cursor.rb +23 -5
  176. data/lib/ratatui_ruby/schema/draw.rb +53 -0
  177. data/lib/ratatui_ruby/schema/gauge.rb +56 -12
  178. data/lib/ratatui_ruby/schema/layout.rb +91 -9
  179. data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
  180. data/lib/ratatui_ruby/schema/list.rb +92 -16
  181. data/lib/ratatui_ruby/schema/overlay.rb +29 -3
  182. data/lib/ratatui_ruby/schema/paragraph.rb +82 -25
  183. data/lib/ratatui_ruby/schema/ratatui_logo.rb +29 -0
  184. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +34 -0
  185. data/lib/ratatui_ruby/schema/rect.rb +59 -10
  186. data/lib/ratatui_ruby/schema/scrollbar.rb +127 -19
  187. data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
  188. data/lib/ratatui_ruby/schema/sparkline.rb +120 -12
  189. data/lib/ratatui_ruby/schema/style.rb +39 -11
  190. data/lib/ratatui_ruby/schema/table.rb +109 -18
  191. data/lib/ratatui_ruby/schema/tabs.rb +71 -10
  192. data/lib/ratatui_ruby/schema/text.rb +90 -0
  193. data/lib/ratatui_ruby/session/autodoc.rb +417 -0
  194. data/lib/ratatui_ruby/session.rb +163 -0
  195. data/lib/ratatui_ruby/test_helper.rb +322 -13
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby.rb +184 -38
  198. data/sig/examples/app_all_events/app.rbs +11 -0
  199. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  200. data/sig/examples/app_all_events/model/events.rbs +15 -0
  201. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  202. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  203. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  204. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  205. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  206. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  207. data/sig/examples/app_all_events/view.rbs +8 -0
  208. data/sig/examples/app_all_events/view_state.rbs +15 -0
  209. data/sig/examples/app_color_picker/app.rbs +12 -0
  210. data/sig/examples/app_login_form/app.rbs +11 -0
  211. data/sig/examples/app_map_demo/app.rbs +11 -0
  212. data/sig/examples/app_table_select/app.rbs +11 -0
  213. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  214. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  215. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  216. data/sig/examples/widget_block_padding/app.rbs +11 -0
  217. data/sig/examples/widget_block_titles/app.rbs +11 -0
  218. data/sig/examples/widget_box_demo/app.rbs +11 -0
  219. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  220. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  221. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  222. data/sig/examples/widget_gauge_demo/app.rbs +11 -0
  223. data/sig/examples/widget_layout_split/app.rbs +10 -0
  224. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  225. data/sig/examples/widget_list_demo/app.rbs +12 -0
  226. data/sig/examples/widget_list_styles/app.rbs +11 -0
  227. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  228. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  229. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  230. data/sig/examples/widget_rect/app.rbs +12 -0
  231. data/sig/examples/widget_render/app.rbs +10 -0
  232. data/sig/examples/widget_rich_text/app.rbs +11 -0
  233. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  234. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  235. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  236. data/sig/examples/widget_style_colors/app.rbs +14 -0
  237. data/sig/examples/widget_table_flex/app.rbs +11 -0
  238. data/sig/ratatui_ruby/event.rbs +69 -0
  239. data/sig/ratatui_ruby/frame.rbs +9 -0
  240. data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -3
  241. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
  242. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
  243. data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
  244. data/sig/ratatui_ruby/schema/block.rbs +5 -4
  245. data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
  246. data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
  247. data/sig/ratatui_ruby/schema/center.rbs +3 -3
  248. data/sig/ratatui_ruby/schema/chart.rbs +8 -5
  249. data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
  250. data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
  251. data/sig/ratatui_ruby/schema/draw.rbs +27 -0
  252. data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
  253. data/sig/ratatui_ruby/schema/layout.rbs +11 -1
  254. data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
  255. data/sig/ratatui_ruby/schema/list.rbs +5 -1
  256. data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
  257. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +8 -0
  258. data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
  259. data/sig/ratatui_ruby/schema/rect.rbs +2 -1
  260. data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
  261. data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
  262. data/sig/ratatui_ruby/schema/table.rbs +8 -1
  263. data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
  264. data/sig/ratatui_ruby/schema/text.rbs +22 -0
  265. data/sig/ratatui_ruby/session.rbs +94 -0
  266. data/tasks/autodoc/inventory.rb +61 -0
  267. data/tasks/autodoc/member.rb +56 -0
  268. data/tasks/autodoc/name.rb +19 -0
  269. data/tasks/autodoc/notice.rb +26 -0
  270. data/tasks/autodoc/rbs.rb +38 -0
  271. data/tasks/autodoc/rdoc.rb +45 -0
  272. data/tasks/autodoc.rake +47 -0
  273. data/tasks/bump/history.rb +2 -2
  274. data/tasks/doc.rake +600 -6
  275. data/tasks/example_viewer.html.erb +172 -0
  276. data/tasks/lint.rake +8 -4
  277. data/tasks/resources/build.yml.erb +13 -11
  278. data/tasks/resources/index.html.erb +6 -0
  279. data/tasks/sourcehut.rake +4 -4
  280. data/tasks/terminal_preview/app_screenshot.rb +33 -0
  281. data/tasks/terminal_preview/crash_report.rb +52 -0
  282. data/tasks/terminal_preview/example_app.rb +25 -0
  283. data/tasks/terminal_preview/launcher_script.rb +46 -0
  284. data/tasks/terminal_preview/preview_collection.rb +58 -0
  285. data/tasks/terminal_preview/preview_timing.rb +22 -0
  286. data/tasks/terminal_preview/safety_confirmation.rb +56 -0
  287. data/tasks/terminal_preview/saved_screenshot.rb +53 -0
  288. data/tasks/terminal_preview/system_appearance.rb +11 -0
  289. data/tasks/terminal_preview/terminal_window.rb +136 -0
  290. data/tasks/terminal_preview/window_id.rb +14 -0
  291. data/tasks/terminal_preview.rake +28 -0
  292. data/tasks/test.rake +2 -2
  293. data/tasks/website/index_page.rb +3 -3
  294. data/tasks/website/version.rb +10 -10
  295. data/tasks/website/version_menu.rb +10 -12
  296. data/tasks/website/versioned_documentation.rb +49 -17
  297. data/tasks/website/website.rb +6 -8
  298. data/tasks/website.rake +4 -4
  299. metadata +206 -54
  300. data/LICENSES/BSD-2-Clause.txt +0 -9
  301. data/doc/images/examples-analytics.rb.png +0 -0
  302. data/doc/images/examples-box_demo.rb.png +0 -0
  303. data/doc/images/examples-calendar_demo.rb.png +0 -0
  304. data/doc/images/examples-chart_demo.rb.png +0 -0
  305. data/doc/images/examples-custom_widget.rb.png +0 -0
  306. data/doc/images/examples-dashboard.rb.png +0 -0
  307. data/doc/images/examples-list_styles.rb.png +0 -0
  308. data/doc/images/examples-login_form.rb.png +0 -0
  309. data/doc/images/examples-map_demo.rb.png +0 -0
  310. data/doc/images/examples-mouse_events.rb.png +0 -0
  311. data/doc/images/examples-popup_demo.rb.gif +0 -0
  312. data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
  313. data/doc/images/examples-scroll_text.rb.png +0 -0
  314. data/doc/images/examples-scrollbar_demo.rb.png +0 -0
  315. data/doc/images/examples-stock_ticker.rb.png +0 -0
  316. data/doc/images/examples-system_monitor.rb.png +0 -0
  317. data/doc/images/examples-table_select.rb.png +0 -0
  318. data/examples/analytics.rb +0 -88
  319. data/examples/box_demo.rb +0 -71
  320. data/examples/calendar_demo.rb +0 -55
  321. data/examples/chart_demo.rb +0 -84
  322. data/examples/custom_widget.rb +0 -43
  323. data/examples/dashboard.rb +0 -72
  324. data/examples/list_styles.rb +0 -66
  325. data/examples/login_form.rb +0 -115
  326. data/examples/map_demo.rb +0 -58
  327. data/examples/mouse_events.rb +0 -95
  328. data/examples/popup_demo.rb +0 -105
  329. data/examples/quickstart_dsl.rb +0 -30
  330. data/examples/quickstart_lifecycle.rb +0 -40
  331. data/examples/readme_usage.rb +0 -21
  332. data/examples/scroll_text.rb +0 -74
  333. data/examples/scrollbar_demo.rb +0 -75
  334. data/examples/stock_ticker.rb +0 -93
  335. data/examples/system_monitor.rb +0 -94
  336. data/examples/table_select.rb +0 -70
  337. data/examples/test_analytics.rb +0 -65
  338. data/examples/test_box_demo.rb +0 -38
  339. data/examples/test_calendar_demo.rb +0 -66
  340. data/examples/test_dashboard.rb +0 -38
  341. data/examples/test_list_styles.rb +0 -61
  342. data/examples/test_login_form.rb +0 -63
  343. data/examples/test_map_demo.rb +0 -100
  344. data/examples/test_popup_demo.rb +0 -62
  345. data/examples/test_scroll_text.rb +0 -130
  346. data/examples/test_stock_ticker.rb +0 -39
  347. data/examples/test_system_monitor.rb +0 -40
  348. data/examples/test_table_select.rb +0 -37
  349. data/ext/ratatui_ruby/src/buffer.rs +0 -54
  350. data/lib/ratatui_ruby/dsl.rb +0 -64
@@ -0,0 +1,40 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ //
3
+ // SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ use magnus::Value;
6
+ use ratatui::{
7
+ layout::Rect,
8
+ widgets::{RatatuiLogo, RatatuiLogoSize},
9
+ Frame,
10
+ };
11
+
12
+ pub fn render(frame: &mut Frame, area: Rect, _node: Value) {
13
+ // RatatuiLogo does not support custom styling (it has fixed colors).
14
+ // It requires a size argument.
15
+ let widget = RatatuiLogo::new(RatatuiLogoSize::Small);
16
+ frame.render_widget(widget, area);
17
+ }
18
+
19
+ #[cfg(test)]
20
+ mod tests {
21
+ use super::*;
22
+ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
23
+
24
+ #[test]
25
+ fn test_render() {
26
+ let mut buffer = Buffer::empty(Rect::new(0, 0, 50, 20));
27
+ let widget = RatatuiLogo::new(RatatuiLogoSize::Small);
28
+ widget.render(Rect::new(0, 0, 50, 20), &mut buffer);
29
+
30
+ let content = buffer
31
+ .content()
32
+ .iter()
33
+ .map(|c| c.symbol())
34
+ .collect::<String>();
35
+
36
+ // The logo uses block characters for rendering
37
+ assert!(content.contains('█'));
38
+ assert!(!content.trim().is_empty());
39
+ }
40
+ }
@@ -0,0 +1,51 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ //
3
+ // SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ use crate::style::parse_block;
6
+ use bumpalo::Bump;
7
+ use magnus::{prelude::*, Error, Value};
8
+ use ratatui::{layout::Rect, widgets::RatatuiMascot, Frame};
9
+
10
+ pub fn render_ratatui_mascot(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
11
+ let block_val: Value = node.funcall("block", ())?;
12
+
13
+ let mut inner_area = area;
14
+
15
+ if !block_val.is_nil() {
16
+ let bump = Bump::new();
17
+ let block = parse_block(block_val, &bump)?;
18
+ inner_area = block.inner(area);
19
+ frame.render_widget(block, area);
20
+ }
21
+
22
+ let widget = RatatuiMascot::new();
23
+ frame.render_widget(widget, inner_area);
24
+ Ok(())
25
+ }
26
+
27
+ #[cfg(test)]
28
+ mod tests {
29
+ use super::*;
30
+ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
31
+
32
+ #[test]
33
+ fn test_render() {
34
+ let mut buffer = Buffer::empty(Rect::new(0, 0, 50, 20));
35
+ let widget = RatatuiMascot::new();
36
+ widget.render(Rect::new(0, 0, 50, 20), &mut buffer);
37
+
38
+ let content = buffer
39
+ .content()
40
+ .iter()
41
+ .map(|c| c.symbol())
42
+ .collect::<String>();
43
+
44
+ // The mascot uses block drawing characters
45
+ assert!(
46
+ content.contains("█"),
47
+ "Mascot rendering should contain block characters"
48
+ );
49
+ assert!(!content.trim().is_empty());
50
+ }
51
+ }
@@ -2,6 +2,7 @@
2
2
  // SPDX-License-Identifier: AGPL-3.0-or-later
3
3
 
4
4
  use crate::style::parse_block;
5
+ use bumpalo::Bump;
5
6
  use magnus::{prelude::*, Error, Symbol, Value};
6
7
  use ratatui::{
7
8
  layout::Rect,
@@ -13,24 +14,77 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
13
14
  let content_length: usize = node.funcall("content_length", ())?;
14
15
  let position: usize = node.funcall("position", ())?;
15
16
  let orientation_sym: Symbol = node.funcall("orientation", ())?;
16
- let thumb_symbol: String = node.funcall("thumb_symbol", ())?;
17
+
18
+ let thumb_symbol_val: Value = node.funcall("thumb_symbol", ())?;
19
+ let thumb_style_val: Value = node.funcall("thumb_style", ())?;
20
+ let track_symbol_val: Value = node.funcall("track_symbol", ())?;
21
+ let track_style_val: Value = node.funcall("track_style", ())?;
22
+ let begin_symbol_val: Value = node.funcall("begin_symbol", ())?;
23
+ let begin_style_val: Value = node.funcall("begin_style", ())?;
24
+ let end_symbol_val: Value = node.funcall("end_symbol", ())?;
25
+ let end_style_val: Value = node.funcall("end_style", ())?;
26
+ let style_val: Value = node.funcall("style", ())?;
27
+
17
28
  let block_val: Value = node.funcall("block", ())?;
18
29
 
19
30
  let mut state = ScrollbarState::new(content_length).position(position);
20
- let mut scrollbar = Scrollbar::default().thumb_symbol(&thumb_symbol);
31
+ let mut scrollbar = Scrollbar::default();
21
32
 
22
33
  scrollbar = match orientation_sym.to_string().as_str() {
23
- "horizontal" => scrollbar.orientation(ScrollbarOrientation::HorizontalBottom),
34
+ "vertical_left" => scrollbar.orientation(ScrollbarOrientation::VerticalLeft),
35
+ "horizontal_bottom" | "horizontal" => {
36
+ scrollbar.orientation(ScrollbarOrientation::HorizontalBottom)
37
+ }
38
+ "horizontal_top" => scrollbar.orientation(ScrollbarOrientation::HorizontalTop),
24
39
  _ => scrollbar.orientation(ScrollbarOrientation::VerticalRight),
25
40
  };
26
41
 
27
- if !block_val.is_nil() {
28
- let block = parse_block(block_val)?;
42
+ // Hoisted strings to extend lifetime
43
+ let thumb_str: String;
44
+ let track_str: String;
45
+ let begin_str: String;
46
+ let end_str: String;
47
+
48
+ if !thumb_symbol_val.is_nil() {
49
+ thumb_str = thumb_symbol_val.funcall("to_s", ())?;
50
+ scrollbar = scrollbar.thumb_symbol(&thumb_str);
51
+ }
52
+ if !thumb_style_val.is_nil() {
53
+ scrollbar = scrollbar.thumb_style(crate::style::parse_style(thumb_style_val)?);
54
+ }
55
+ if !track_symbol_val.is_nil() {
56
+ track_str = track_symbol_val.funcall("to_s", ())?;
57
+ scrollbar = scrollbar.track_symbol(Some(&track_str));
58
+ }
59
+ if !track_style_val.is_nil() {
60
+ scrollbar = scrollbar.track_style(crate::style::parse_style(track_style_val)?);
61
+ }
62
+ if !begin_symbol_val.is_nil() {
63
+ begin_str = begin_symbol_val.funcall("to_s", ())?;
64
+ scrollbar = scrollbar.begin_symbol(Some(&begin_str));
65
+ }
66
+ if !begin_style_val.is_nil() {
67
+ scrollbar = scrollbar.begin_style(crate::style::parse_style(begin_style_val)?);
68
+ }
69
+ if !end_symbol_val.is_nil() {
70
+ end_str = end_symbol_val.funcall("to_s", ())?;
71
+ scrollbar = scrollbar.end_symbol(Some(&end_str));
72
+ }
73
+ if !end_style_val.is_nil() {
74
+ scrollbar = scrollbar.end_style(crate::style::parse_style(end_style_val)?);
75
+ }
76
+ if !style_val.is_nil() {
77
+ scrollbar = scrollbar.style(crate::style::parse_style(style_val)?);
78
+ }
79
+
80
+ if block_val.is_nil() {
81
+ frame.render_stateful_widget(scrollbar, area, &mut state);
82
+ } else {
83
+ let bump = Bump::new();
84
+ let block = parse_block(block_val, &bump)?;
29
85
  let inner_area = block.inner(area);
30
86
  frame.render_widget(block, area);
31
87
  frame.render_stateful_widget(scrollbar, inner_area, &mut state);
32
- } else {
33
- frame.render_stateful_widget(scrollbar, area, &mut state);
34
88
  }
35
89
  Ok(())
36
90
  }
@@ -1,20 +1,34 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
2
  // SPDX-License-Identifier: AGPL-3.0-or-later
3
3
 
4
- use crate::style::{parse_block, parse_style};
5
- use magnus::{prelude::*, Error, Value};
6
- use ratatui::{layout::Rect, widgets::Sparkline, Frame};
4
+ use crate::style::{parse_bar_set, parse_block, parse_style};
5
+ use bumpalo::Bump;
6
+ use magnus::{prelude::*, Error, RString, Value};
7
+ use ratatui::{layout::Rect, widgets::RenderDirection, widgets::Sparkline, Frame};
7
8
 
8
9
  pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
10
+ let bump = Bump::new();
11
+ let ruby = magnus::Ruby::get().unwrap();
9
12
  let data_val: magnus::RArray = node.funcall("data", ())?;
10
13
  let max_val: Value = node.funcall("max", ())?;
11
14
  let style_val: Value = node.funcall("style", ())?;
12
15
  let block_val: Value = node.funcall("block", ())?;
16
+ let direction_val: Value = node.funcall("direction", ())?;
17
+ let absent_value_symbol_val: Value = node.funcall("absent_value_symbol", ())?;
18
+ let absent_value_style_val: Value = node.funcall("absent_value_style", ())?;
19
+ let bar_set_val: Value = node.funcall("bar_set", ())?;
13
20
 
14
21
  let mut data_vec = Vec::new();
15
22
  for i in 0..data_val.len() {
16
- let val: u64 = data_val.entry(i as isize)?;
17
- data_vec.push(val);
23
+ let index = isize::try_from(i)
24
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
25
+ let val: Value = data_val.entry(index)?;
26
+ if val.is_nil() {
27
+ data_vec.push(None);
28
+ } else {
29
+ let num: u64 = u64::try_convert(val)?;
30
+ data_vec.push(Some(num));
31
+ }
18
32
  }
19
33
 
20
34
  let mut sparkline = Sparkline::default().data(&data_vec);
@@ -29,7 +43,33 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
29
43
  }
30
44
 
31
45
  if !block_val.is_nil() {
32
- sparkline = sparkline.block(parse_block(block_val)?);
46
+ sparkline = sparkline.block(parse_block(block_val, &bump)?);
47
+ }
48
+
49
+ if !direction_val.is_nil() {
50
+ let direction_sym: RString = direction_val.funcall("to_s", ())?;
51
+ let direction_str = direction_sym.to_string()?;
52
+ let direction = match direction_str.as_str() {
53
+ "right_to_left" => RenderDirection::RightToLeft,
54
+ _ => RenderDirection::LeftToRight,
55
+ };
56
+ sparkline = sparkline.direction(direction);
57
+ }
58
+
59
+ if !absent_value_symbol_val.is_nil() {
60
+ let symbol_str: String = String::try_convert(absent_value_symbol_val)?;
61
+ // Only use the first character if multiple are provided
62
+ if let Some(first_char) = symbol_str.chars().next() {
63
+ sparkline = sparkline.absent_value_symbol(first_char);
64
+ }
65
+ }
66
+
67
+ if !absent_value_style_val.is_nil() {
68
+ sparkline = sparkline.absent_value_style(parse_style(absent_value_style_val)?);
69
+ }
70
+
71
+ if !bar_set_val.is_nil() {
72
+ sparkline = sparkline.bar_set(parse_bar_set(bar_set_val, &bump)?);
33
73
  }
34
74
 
35
75
  frame.render_widget(sparkline, area);
@@ -56,4 +96,31 @@ mod tests {
56
96
  let bars = buf.content().iter().filter(|c| c.symbol() != " ").count();
57
97
  assert_eq!(bars, 4);
58
98
  }
99
+
100
+ #[test]
101
+ fn test_sparkline_absent_value_symbol() {
102
+ // Data with absent (None) and present values: [Some(5), None, Some(8), None]
103
+ let data = vec![Some(5), None, Some(8), None];
104
+ let sparkline = Sparkline::default().data(&data).absent_value_symbol("-");
105
+ let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
106
+ sparkline.render(Rect::new(0, 0, 4, 1), &mut buf);
107
+
108
+ // Collect all rendered symbols
109
+ let symbols: Vec<&str> = buf.content().iter().map(|c| c.symbol()).collect();
110
+
111
+ // Check that we have 4 cells rendered
112
+ assert_eq!(
113
+ symbols.len(),
114
+ 4,
115
+ "Should have 4 cells rendered for 4 data points"
116
+ );
117
+
118
+ // Absent values (None) should render as "-"
119
+ assert_eq!(symbols[1], "-", "Second value (None) should render as dash");
120
+ assert_eq!(symbols[3], "-", "Fourth value (None) should render as dash");
121
+
122
+ // Present values should not be dashes
123
+ assert_ne!(symbols[0], "-", "First value (Some(5)) should not be dash");
124
+ assert_ne!(symbols[2], "-", "Third value (Some(8)) should not be dash");
125
+ }
59
126
  }
@@ -2,116 +2,226 @@
2
2
  // SPDX-License-Identifier: AGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_block, parse_style};
5
+ use bumpalo::Bump;
5
6
  use magnus::{prelude::*, Error, Symbol, Value};
6
7
  use ratatui::{
7
- layout::{Constraint, Rect},
8
- widgets::{Cell, Row, Table, TableState},
8
+ layout::{Constraint, Flex, Rect},
9
+ widgets::{Cell, HighlightSpacing, Row, Table, TableState},
9
10
  Frame,
10
11
  };
11
12
 
12
13
  pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
14
+ let bump = Bump::new();
13
15
  let ruby = magnus::Ruby::get().unwrap();
14
16
  let header_val: Value = node.funcall("header", ())?;
15
- let rows_val: Value = node.funcall("rows", ())?;
16
- let rows_array = magnus::RArray::from_value(rows_val)
17
+ let footer_val: Value = node.funcall("footer", ())?;
18
+ let rows_value: Value = node.funcall("rows", ())?;
19
+ let rows_array = magnus::RArray::from_value(rows_value)
17
20
  .ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for rows"))?;
18
21
  let widths_val: Value = node.funcall("widths", ())?;
19
22
  let widths_array = magnus::RArray::from_value(widths_val)
20
23
  .ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for widths"))?;
21
24
  let highlight_style_val: Value = node.funcall("highlight_style", ())?;
25
+ let column_highlight_style_val: Value = node.funcall("column_highlight_style", ())?;
26
+ let cell_highlight_style_val: Value = node.funcall("cell_highlight_style", ())?;
22
27
  let highlight_symbol_val: Value = node.funcall("highlight_symbol", ())?;
23
28
  let selected_row_val: Value = node.funcall("selected_row", ())?;
29
+ let selected_column_val: Value = node.funcall("selected_column", ())?;
24
30
  let block_val: Value = node.funcall("block", ())?;
31
+ let flex_sym: Symbol = node.funcall("flex", ())?;
32
+ let highlight_spacing_sym: Symbol = node.funcall("highlight_spacing", ())?;
25
33
 
26
34
  let mut rows = Vec::new();
27
35
  for i in 0..rows_array.len() {
28
- let row_val: Value = rows_array.entry(i as isize)?;
29
- let row_array = magnus::RArray::from_value(row_val)
30
- .ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for row"))?;
31
-
32
- let mut cells = Vec::new();
33
- for j in 0..row_array.len() {
34
- let cell_val: Value = row_array.entry(j as isize)?;
35
- let class = cell_val.class();
36
- let class_name = unsafe { class.name() };
37
-
38
- if class_name.as_ref() == "RatatuiRuby::Paragraph" {
39
- let text: String = cell_val.funcall("text", ())?;
40
- let style_val: Value = cell_val.funcall("style", ())?;
41
- let cell_style = parse_style(style_val)?;
42
- cells.push(Cell::from(text).style(cell_style));
43
- } else if class_name.as_ref() == "RatatuiRuby::Style" {
44
- cells.push(Cell::from("").style(parse_style(cell_val)?));
45
- } else {
46
- let cell_str: String = cell_val.funcall("to_s", ())?;
47
- cells.push(Cell::from(cell_str));
48
- }
49
- }
50
- rows.push(Row::new(cells));
36
+ let index = isize::try_from(i)
37
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
38
+ let row_val: Value = rows_array.entry(index)?;
39
+ rows.push(parse_row(row_val)?);
51
40
  }
52
41
 
53
- let mut constraints = Vec::new();
54
- for i in 0..widths_array.len() {
55
- let constraint_obj: Value = widths_array.entry(i as isize)?;
56
- let type_sym: Symbol = constraint_obj.funcall("type", ())?;
57
- let value: u16 = constraint_obj.funcall("value", ())?;
42
+ let constraints = parse_constraints(widths_array)?;
58
43
 
59
- match type_sym.to_string().as_str() {
60
- "length" => constraints.push(Constraint::Length(value)),
61
- "percentage" => constraints.push(Constraint::Percentage(value)),
62
- "min" => constraints.push(Constraint::Min(value)),
63
- _ => {}
64
- }
65
- }
44
+ let flex = match flex_sym.to_string().as_str() {
45
+ "start" => Flex::Start,
46
+ "center" => Flex::Center,
47
+ "end" => Flex::End,
48
+ "space_between" => Flex::SpaceBetween,
49
+ "space_around" => Flex::SpaceAround,
50
+ "space_evenly" => Flex::SpaceEvenly,
51
+ _ => Flex::Legacy,
52
+ };
66
53
 
67
- let mut table = Table::new(rows, constraints);
54
+ let mut table = Table::new(rows, constraints).flex(flex);
55
+
56
+ let highlight_spacing = match highlight_spacing_sym.to_string().as_str() {
57
+ "always" => HighlightSpacing::Always,
58
+ "never" => HighlightSpacing::Never,
59
+ _ => HighlightSpacing::WhenSelected,
60
+ };
61
+ table = table.highlight_spacing(highlight_spacing);
68
62
 
69
63
  if !header_val.is_nil() {
70
- let header_array = magnus::RArray::from_value(header_val).ok_or_else(|| {
71
- Error::new(ruby.exception_type_error(), "expected array for header")
72
- })?;
73
- let mut header_cells = Vec::new();
74
- for i in 0..header_array.len() {
75
- let cell_val: Value = header_array.entry(i as isize)?;
76
- let class = cell_val.class();
77
- let class_name = unsafe { class.name() };
78
-
79
- if class_name.as_ref() == "RatatuiRuby::Paragraph" {
80
- let text: String = cell_val.funcall("text", ())?;
81
- let style_val: Value = cell_val.funcall("style", ())?;
82
- let cell_style = parse_style(style_val)?;
83
- header_cells.push(Cell::from(text).style(cell_style));
84
- } else {
85
- let cell_str: String = cell_val.funcall("to_s", ())?;
86
- header_cells.push(Cell::from(cell_str));
87
- }
88
- }
89
- table = table.header(Row::new(header_cells));
64
+ table = table.header(parse_row(header_val)?);
65
+ }
66
+
67
+ if !footer_val.is_nil() {
68
+ table = table.footer(parse_row(footer_val)?);
90
69
  }
91
70
 
92
71
  if !block_val.is_nil() {
93
- table = table.block(parse_block(block_val)?);
72
+ table = table.block(parse_block(block_val, &bump)?);
94
73
  }
95
74
 
96
75
  if !highlight_style_val.is_nil() {
97
76
  table = table.row_highlight_style(parse_style(highlight_style_val)?);
98
77
  }
99
78
 
79
+ if !column_highlight_style_val.is_nil() {
80
+ table = table.column_highlight_style(parse_style(column_highlight_style_val)?);
81
+ }
82
+
83
+ if !cell_highlight_style_val.is_nil() {
84
+ table = table.cell_highlight_style(parse_style(cell_highlight_style_val)?);
85
+ }
86
+
100
87
  if !highlight_symbol_val.is_nil() {
101
88
  let symbol: String = highlight_symbol_val.funcall("to_s", ())?;
102
89
  table = table.highlight_symbol(symbol);
103
90
  }
104
91
 
92
+ let style_val: Value = node.funcall("style", ())?;
93
+ if !style_val.is_nil() {
94
+ table = table.style(parse_style(style_val)?);
95
+ }
96
+
97
+ let column_spacing_val: Value = node.funcall("column_spacing", ())?;
98
+ if !column_spacing_val.is_nil() {
99
+ let spacing: u16 = column_spacing_val.funcall("to_int", ())?;
100
+ table = table.column_spacing(spacing);
101
+ }
102
+
105
103
  let mut state = TableState::default();
106
104
  if !selected_row_val.is_nil() {
107
105
  let index: usize = selected_row_val.funcall("to_int", ())?;
108
106
  state.select(Some(index));
109
107
  }
108
+ if !selected_column_val.is_nil() {
109
+ let index: usize = selected_column_val.funcall("to_int", ())?;
110
+ state.select_column(Some(index));
111
+ }
110
112
 
111
113
  frame.render_stateful_widget(table, area, &mut state);
112
114
  Ok(())
113
115
  }
114
116
 
117
+ fn parse_row(row_val: Value) -> Result<Row<'static>, Error> {
118
+ let ruby = magnus::Ruby::get().unwrap();
119
+ let row_array = magnus::RArray::from_value(row_val)
120
+ .ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for row"))?;
121
+
122
+ let mut cells = Vec::new();
123
+ for i in 0..row_array.len() {
124
+ let index = isize::try_from(i)
125
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
126
+ let cell_val: Value = row_array.entry(index)?;
127
+ cells.push(parse_cell(cell_val)?);
128
+ }
129
+ Ok(Row::new(cells))
130
+ }
131
+
132
+ fn parse_cell(cell_val: Value) -> Result<Cell<'static>, Error> {
133
+ let class = cell_val.class();
134
+ // SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
135
+ let class_name = unsafe { class.name() }.into_owned();
136
+
137
+ if class_name == "RatatuiRuby::Paragraph" {
138
+ let text: String = cell_val.funcall("text", ())?;
139
+ let style_val: Value = cell_val.funcall("style", ())?;
140
+ let cell_style = parse_style(style_val)?;
141
+ Ok(Cell::from(text).style(cell_style))
142
+ } else if class_name == "RatatuiRuby::Style" {
143
+ Ok(Cell::from("").style(parse_style(cell_val)?))
144
+ } else if class_name == "RatatuiRuby::Cell" {
145
+ let symbol: String = cell_val.funcall("char", ())?;
146
+ let fg_val: Value = cell_val.funcall("fg", ())?;
147
+ let bg_val: Value = cell_val.funcall("bg", ())?;
148
+ let modifiers_val: Value = cell_val.funcall("modifiers", ())?;
149
+
150
+ let mut style = ratatui::style::Style::default();
151
+ if !fg_val.is_nil() {
152
+ if let Some(color) = crate::style::parse_color_value(fg_val)? {
153
+ style = style.fg(color);
154
+ }
155
+ }
156
+ if !bg_val.is_nil() {
157
+ if let Some(color) = crate::style::parse_color_value(bg_val)? {
158
+ style = style.bg(color);
159
+ }
160
+ }
161
+ if let Some(mods_array) = magnus::RArray::from_value(modifiers_val) {
162
+ let ruby = magnus::Ruby::get().unwrap();
163
+ for i in 0..mods_array.len() {
164
+ let index = isize::try_from(i)
165
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
166
+ let mod_str: String = mods_array.entry::<String>(index)?;
167
+ if let Some(modifier) = crate::style::parse_modifier_str(&mod_str) {
168
+ style = style.add_modifier(modifier);
169
+ }
170
+ }
171
+ }
172
+ Ok(Cell::from(symbol).style(style))
173
+ } else {
174
+ let cell_str: String = cell_val.funcall("to_s", ())?;
175
+ Ok(Cell::from(cell_str))
176
+ }
177
+ }
178
+
179
+ fn parse_constraints(widths_array: magnus::RArray) -> Result<Vec<Constraint>, Error> {
180
+ let ruby = magnus::Ruby::get().unwrap();
181
+ let mut constraints = Vec::new();
182
+ for i in 0..widths_array.len() {
183
+ let index = isize::try_from(i)
184
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
185
+ let constraint_obj: Value = widths_array.entry(index)?;
186
+ let type_sym: Symbol = constraint_obj.funcall("type", ())?;
187
+ let value_obj: Value = constraint_obj.funcall("value", ())?;
188
+
189
+ match type_sym.to_string().as_str() {
190
+ "length" => {
191
+ let val = u16::try_convert(value_obj)?;
192
+ constraints.push(Constraint::Length(val));
193
+ }
194
+ "percentage" => {
195
+ let val = u16::try_convert(value_obj)?;
196
+ constraints.push(Constraint::Percentage(val));
197
+ }
198
+ "min" => {
199
+ let val = u16::try_convert(value_obj)?;
200
+ constraints.push(Constraint::Min(val));
201
+ }
202
+ "max" => {
203
+ let val = u16::try_convert(value_obj)?;
204
+ constraints.push(Constraint::Max(val));
205
+ }
206
+ "fill" => {
207
+ let val = u16::try_convert(value_obj)?;
208
+ constraints.push(Constraint::Fill(val));
209
+ }
210
+ "ratio" => {
211
+ if let Some(arr) = magnus::RArray::from_value(value_obj) {
212
+ if arr.len() == 2 {
213
+ let n = u32::try_convert(arr.entry(0)?)?;
214
+ let d = u32::try_convert(arr.entry(1)?)?;
215
+ constraints.push(Constraint::Ratio(n, d));
216
+ }
217
+ }
218
+ }
219
+ _ => {}
220
+ }
221
+ }
222
+ Ok(constraints)
223
+ }
224
+
115
225
  #[cfg(test)]
116
226
  mod tests {
117
227
  use super::*;
@@ -122,14 +232,17 @@ mod tests {
122
232
  fn test_table_rendering() {
123
233
  let rows = vec![Row::new(vec!["C1", "C2"])];
124
234
  let table = Table::new(rows, [Constraint::Length(3), Constraint::Length(3)])
125
- .header(Row::new(vec!["H1", "H2"]));
126
- let mut buf = Buffer::empty(Rect::new(0, 0, 10, 2));
127
- Widget::render(table, Rect::new(0, 0, 10, 2), &mut buf);
235
+ .header(Row::new(vec!["H1", "H2"]))
236
+ .footer(Row::new(vec!["F1", "F2"]));
237
+ let mut buf = Buffer::empty(Rect::new(0, 0, 10, 3));
238
+ Widget::render(table, Rect::new(0, 0, 10, 3), &mut buf);
128
239
 
129
240
  let content = buf.content().iter().map(|c| c.symbol()).collect::<String>();
130
241
  // Check for presence of header and row content
131
242
  assert!(content.contains("H1"));
132
243
  assert!(content.contains("H2"));
244
+ assert!(content.contains("F1"));
245
+ assert!(content.contains("F2"));
133
246
  assert!(content.contains("C1"));
134
247
  assert!(content.contains("C2"));
135
248
  }