ratatui_ruby 0.4.0 → 0.6.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 (441) 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 +98 -176
  7. data/CHANGELOG.md +80 -6
  8. data/README.md +19 -7
  9. data/REUSE.toml +15 -0
  10. data/doc/application_architecture.md +179 -45
  11. data/doc/application_testing.md +80 -32
  12. data/doc/contributors/design/ruby_frontend.md +48 -8
  13. data/doc/contributors/design/rust_backend.md +1 -0
  14. data/doc/contributors/developing_examples.md +191 -48
  15. data/doc/contributors/documentation_style.md +7 -0
  16. data/doc/contributors/examples_audit/p1_high.md +21 -0
  17. data/doc/contributors/examples_audit/p2_moderate.md +81 -0
  18. data/doc/contributors/examples_audit.md +41 -0
  19. data/doc/contributors/index.md +2 -0
  20. data/doc/event_handling.md +21 -7
  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_box_demo.png +0 -0
  32. data/doc/images/widget_calendar_demo.png +0 -0
  33. data/doc/images/widget_canvas_demo.png +0 -0
  34. data/doc/images/widget_cell_demo.png +0 -0
  35. data/doc/images/widget_center_demo.png +0 -0
  36. data/doc/images/widget_chart_demo.png +0 -0
  37. data/doc/images/widget_gauge_demo.png +0 -0
  38. data/doc/images/widget_layout_split.png +0 -0
  39. data/doc/images/widget_line_gauge_demo.png +0 -0
  40. data/doc/images/widget_list_demo.png +0 -0
  41. data/doc/images/widget_overlay_demo.png +0 -0
  42. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  43. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  44. data/doc/images/widget_render.png +0 -0
  45. data/doc/images/widget_rich_text.png +0 -0
  46. data/doc/images/widget_scroll_text.png +0 -0
  47. data/doc/images/widget_scrollbar_demo.png +0 -0
  48. data/doc/images/widget_sparkline_demo.png +0 -0
  49. data/doc/images/widget_style_colors.png +0 -0
  50. data/doc/images/widget_table_demo.png +0 -0
  51. data/doc/images/widget_table_flex.png +0 -0
  52. data/doc/images/widget_tabs_demo.png +0 -0
  53. data/doc/images/widget_text_width.png +0 -0
  54. data/doc/interactive_design.md +25 -30
  55. data/doc/quickstart.md +150 -130
  56. data/doc/terminal_limitations.md +92 -0
  57. data/examples/app_all_events/README.md +99 -0
  58. data/examples/app_all_events/app.rb +96 -0
  59. data/examples/app_all_events/model/app_model.rb +157 -0
  60. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  61. data/examples/app_all_events/model/event_entry.rb +92 -0
  62. data/examples/app_all_events/model/msg.rb +37 -0
  63. data/examples/app_all_events/model/timestamp.rb +54 -0
  64. data/examples/app_all_events/update.rb +73 -0
  65. data/examples/app_all_events/view/app_view.rb +78 -0
  66. data/examples/app_all_events/view/controls_view.rb +52 -0
  67. data/examples/app_all_events/view/counts_view.rb +59 -0
  68. data/examples/app_all_events/view/live_view.rb +70 -0
  69. data/examples/app_all_events/view/log_view.rb +55 -0
  70. data/examples/app_all_events/view.rb +7 -0
  71. data/examples/app_color_picker/README.md +134 -0
  72. data/examples/app_color_picker/app.rb +74 -0
  73. data/examples/app_color_picker/clipboard.rb +84 -0
  74. data/examples/app_color_picker/color.rb +191 -0
  75. data/examples/app_color_picker/controls.rb +90 -0
  76. data/examples/app_color_picker/copy_dialog.rb +166 -0
  77. data/examples/app_color_picker/export_pane.rb +126 -0
  78. data/examples/app_color_picker/harmony.rb +56 -0
  79. data/examples/app_color_picker/input.rb +174 -0
  80. data/examples/app_color_picker/main_container.rb +178 -0
  81. data/examples/app_color_picker/palette.rb +109 -0
  82. data/examples/app_login_form/README.md +47 -0
  83. data/examples/{login_form → app_login_form}/app.rb +38 -42
  84. data/examples/app_stateful_interaction/README.md +31 -0
  85. data/examples/app_stateful_interaction/app.rb +272 -0
  86. data/examples/timeout_demo.rb +43 -0
  87. data/examples/verify_quickstart_dsl/README.md +48 -0
  88. data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +17 -6
  89. data/examples/verify_quickstart_layout/README.md +71 -0
  90. data/examples/verify_quickstart_layout/app.rb +71 -0
  91. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  92. data/examples/verify_quickstart_lifecycle/app.rb +54 -0
  93. data/examples/verify_readme_usage/README.md +43 -0
  94. data/examples/verify_readme_usage/app.rb +40 -0
  95. data/examples/widget_barchart_demo/README.md +49 -0
  96. data/examples/widget_barchart_demo/app.rb +238 -0
  97. data/examples/widget_block_demo/README.md +34 -0
  98. data/examples/widget_block_demo/app.rb +256 -0
  99. data/examples/widget_box_demo/README.md +45 -0
  100. data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
  101. data/examples/widget_calendar_demo/README.md +39 -0
  102. data/examples/widget_calendar_demo/app.rb +109 -0
  103. data/examples/widget_canvas_demo/README.md +27 -0
  104. data/examples/widget_canvas_demo/app.rb +123 -0
  105. data/examples/widget_cell_demo/README.md +36 -0
  106. data/examples/widget_cell_demo/app.rb +111 -0
  107. data/examples/widget_center_demo/README.md +29 -0
  108. data/examples/widget_center_demo/app.rb +116 -0
  109. data/examples/widget_chart_demo/README.md +41 -0
  110. data/examples/widget_chart_demo/app.rb +218 -0
  111. data/examples/widget_gauge_demo/README.md +41 -0
  112. data/examples/widget_gauge_demo/app.rb +212 -0
  113. data/examples/widget_layout_split/README.md +44 -0
  114. data/examples/widget_layout_split/app.rb +246 -0
  115. data/examples/widget_line_gauge_demo/README.md +41 -0
  116. data/examples/widget_line_gauge_demo/app.rb +217 -0
  117. data/examples/widget_list_demo/README.md +49 -0
  118. data/examples/widget_list_demo/app.rb +366 -0
  119. data/examples/widget_map_demo/README.md +39 -0
  120. data/examples/{map_demo → widget_map_demo}/app.rb +24 -21
  121. data/examples/widget_overlay_demo/app.rb +248 -0
  122. data/examples/widget_popup_demo/README.md +36 -0
  123. data/examples/widget_popup_demo/app.rb +104 -0
  124. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  125. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  126. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  127. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  128. data/examples/widget_rect/README.md +38 -0
  129. data/examples/widget_rect/app.rb +205 -0
  130. data/examples/widget_render/README.md +37 -0
  131. data/examples/widget_render/app.rb +184 -0
  132. data/examples/widget_rich_text/README.md +35 -0
  133. data/examples/widget_rich_text/app.rb +166 -0
  134. data/examples/widget_scroll_text/README.md +37 -0
  135. data/examples/widget_scroll_text/app.rb +107 -0
  136. data/examples/widget_scrollbar_demo/README.md +37 -0
  137. data/examples/widget_scrollbar_demo/app.rb +153 -0
  138. data/examples/widget_sparkline_demo/README.md +42 -0
  139. data/examples/widget_sparkline_demo/app.rb +275 -0
  140. data/examples/widget_style_colors/README.md +34 -0
  141. data/examples/widget_style_colors/app.rb +19 -21
  142. data/examples/widget_table_demo/README.md +48 -0
  143. data/examples/widget_table_demo/app.rb +239 -0
  144. data/examples/widget_tabs_demo/README.md +41 -0
  145. data/examples/widget_tabs_demo/app.rb +181 -0
  146. data/examples/widget_text_width/README.md +35 -0
  147. data/examples/widget_text_width/app.rb +106 -0
  148. data/ext/ratatui_ruby/Cargo.lock +11 -4
  149. data/ext/ratatui_ruby/Cargo.toml +2 -1
  150. data/ext/ratatui_ruby/src/events.rs +359 -62
  151. data/ext/ratatui_ruby/src/frame.rs +227 -0
  152. data/ext/ratatui_ruby/src/lib.rs +110 -27
  153. data/ext/ratatui_ruby/src/rendering.rs +8 -4
  154. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  155. data/ext/ratatui_ruby/src/style.rs +138 -57
  156. data/ext/ratatui_ruby/src/terminal.rs +42 -22
  157. data/ext/ratatui_ruby/src/text.rs +14 -7
  158. data/ext/ratatui_ruby/src/widgets/barchart.rs +74 -54
  159. data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
  160. data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
  161. data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
  162. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  163. data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
  164. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  165. data/ext/ratatui_ruby/src/widgets/list.rs +211 -12
  166. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  167. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  168. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  169. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  170. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
  171. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
  172. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +97 -3
  173. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  174. data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
  175. data/ext/ratatui_ruby/src/widgets/table.rs +121 -5
  176. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  177. data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
  178. data/lib/ratatui_ruby/cell.rb +7 -7
  179. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  180. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  181. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  182. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  183. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  184. data/lib/ratatui_ruby/event/key.rb +112 -52
  185. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  186. data/lib/ratatui_ruby/event/none.rb +43 -0
  187. data/lib/ratatui_ruby/event/paste.rb +1 -1
  188. data/lib/ratatui_ruby/event.rb +56 -4
  189. data/lib/ratatui_ruby/frame.rb +183 -0
  190. data/lib/ratatui_ruby/list_state.rb +88 -0
  191. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +13 -13
  192. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
  193. data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
  194. data/lib/ratatui_ruby/schema/block.rb +163 -168
  195. data/lib/ratatui_ruby/schema/calendar.rb +66 -67
  196. data/lib/ratatui_ruby/schema/canvas.rb +63 -63
  197. data/lib/ratatui_ruby/schema/center.rb +46 -46
  198. data/lib/ratatui_ruby/schema/chart.rb +135 -143
  199. data/lib/ratatui_ruby/schema/clear.rb +42 -42
  200. data/lib/ratatui_ruby/schema/constraint.rb +76 -76
  201. data/lib/ratatui_ruby/schema/cursor.rb +30 -25
  202. data/lib/ratatui_ruby/schema/gauge.rb +54 -52
  203. data/lib/ratatui_ruby/schema/layout.rb +87 -87
  204. data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
  205. data/lib/ratatui_ruby/schema/list.rb +103 -80
  206. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  207. data/lib/ratatui_ruby/schema/overlay.rb +31 -31
  208. data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
  209. data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
  210. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
  211. data/lib/ratatui_ruby/schema/rect.rb +99 -56
  212. data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
  213. data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
  214. data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
  215. data/lib/ratatui_ruby/schema/style.rb +66 -46
  216. data/lib/ratatui_ruby/schema/table.rb +126 -115
  217. data/lib/ratatui_ruby/schema/tabs.rb +66 -67
  218. data/lib/ratatui_ruby/schema/text.rb +69 -1
  219. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  220. data/lib/ratatui_ruby/session/autodoc.rb +482 -0
  221. data/lib/ratatui_ruby/session.rb +55 -23
  222. data/lib/ratatui_ruby/table_state.rb +90 -0
  223. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  224. data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
  225. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  226. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  227. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  228. data/lib/ratatui_ruby/test_helper.rb +66 -193
  229. data/lib/ratatui_ruby/version.rb +1 -1
  230. data/lib/ratatui_ruby.rb +100 -51
  231. data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
  232. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  233. data/sig/examples/app_all_events/model/events.rbs +15 -0
  234. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  235. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  236. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  237. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  238. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  239. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  240. data/sig/examples/app_all_events/view.rbs +8 -0
  241. data/sig/examples/app_all_events/view_state.rbs +15 -0
  242. data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
  243. data/sig/examples/app_login_form/app.rbs +11 -0
  244. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  245. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  246. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  247. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  248. data/sig/examples/widget_block_demo/app.rbs +32 -0
  249. data/sig/examples/widget_box_demo/app.rbs +11 -0
  250. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  251. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  252. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  253. data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
  254. data/sig/examples/widget_layout_split/app.rbs +10 -0
  255. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  256. data/sig/examples/widget_list_demo/app.rbs +12 -0
  257. data/sig/examples/widget_map_demo/app.rbs +11 -0
  258. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  259. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  260. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  261. data/sig/examples/widget_rect/app.rbs +12 -0
  262. data/sig/examples/widget_render/app.rbs +10 -0
  263. data/sig/examples/widget_rich_text/app.rbs +11 -0
  264. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  265. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  266. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  267. data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
  268. data/sig/examples/widget_table_demo/app.rbs +11 -0
  269. data/sig/examples/widget_text_width/app.rbs +10 -0
  270. data/sig/ratatui_ruby/event.rbs +11 -1
  271. data/sig/ratatui_ruby/frame.rbs +11 -0
  272. data/sig/ratatui_ruby/list_state.rbs +13 -0
  273. data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -4
  274. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  275. data/sig/ratatui_ruby/schema/draw.rbs +4 -0
  276. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  277. data/sig/ratatui_ruby/schema/layout.rbs +1 -1
  278. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  279. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  280. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  281. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  282. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  283. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  284. data/sig/ratatui_ruby/schema/text.rbs +8 -6
  285. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  286. data/sig/ratatui_ruby/session.rbs +107 -0
  287. data/sig/ratatui_ruby/table_state.rbs +15 -0
  288. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  289. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  290. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  291. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  292. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  293. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  294. data/tasks/autodoc/examples.rb +79 -0
  295. data/tasks/autodoc/inventory.rb +63 -0
  296. data/tasks/autodoc/member.rb +56 -0
  297. data/tasks/autodoc/name.rb +19 -0
  298. data/tasks/autodoc/notice.rb +26 -0
  299. data/tasks/autodoc/rbs.rb +38 -0
  300. data/tasks/autodoc/rdoc.rb +45 -0
  301. data/tasks/autodoc.rake +53 -0
  302. data/tasks/bump/changelog.rb +3 -3
  303. data/tasks/bump/history.rb +2 -2
  304. data/tasks/bump/links.rb +67 -0
  305. data/tasks/doc.rake +600 -6
  306. data/tasks/example_viewer.html.erb +172 -0
  307. data/tasks/lint.rake +8 -4
  308. data/tasks/resources/index.html.erb +6 -0
  309. data/tasks/sourcehut.rake +70 -30
  310. data/tasks/terminal_preview/app_screenshot.rb +14 -6
  311. data/tasks/terminal_preview/crash_report.rb +7 -9
  312. data/tasks/terminal_preview/launcher_script.rb +4 -6
  313. data/tasks/terminal_preview/preview_collection.rb +4 -6
  314. data/tasks/terminal_preview/safety_confirmation.rb +3 -5
  315. data/tasks/terminal_preview/saved_screenshot.rb +10 -11
  316. data/tasks/terminal_preview/terminal_window.rb +7 -9
  317. data/tasks/test.rake +1 -1
  318. data/tasks/website/index_page.rb +3 -3
  319. data/tasks/website/version.rb +10 -10
  320. data/tasks/website/version_menu.rb +10 -12
  321. data/tasks/website/versioned_documentation.rb +49 -17
  322. data/tasks/website/website.rb +6 -8
  323. data/tasks/website.rake +4 -4
  324. metadata +232 -127
  325. data/LICENSES/BSD-2-Clause.txt +0 -9
  326. data/doc/contributors/better_dx.md +0 -543
  327. data/doc/contributors/example_analysis.md +0 -82
  328. data/doc/images/all_events.png +0 -0
  329. data/doc/images/block_padding.png +0 -0
  330. data/doc/images/block_titles.png +0 -0
  331. data/doc/images/box_demo.png +0 -0
  332. data/doc/images/calendar_demo.png +0 -0
  333. data/doc/images/cell_demo.png +0 -0
  334. data/doc/images/chart_demo.png +0 -0
  335. data/doc/images/flex_layout.png +0 -0
  336. data/doc/images/gauge_demo.png +0 -0
  337. data/doc/images/line_gauge_demo.png +0 -0
  338. data/doc/images/list_demo.png +0 -0
  339. data/doc/images/list_styles.png +0 -0
  340. data/doc/images/login_form.png +0 -0
  341. data/doc/images/quickstart_dsl.png +0 -0
  342. data/doc/images/quickstart_lifecycle.png +0 -0
  343. data/doc/images/readme_usage.png +0 -0
  344. data/doc/images/rich_text.png +0 -0
  345. data/doc/images/scroll_text.png +0 -0
  346. data/doc/images/scrollbar_demo.png +0 -0
  347. data/doc/images/sparkline_demo.png +0 -0
  348. data/doc/images/table_flex.png +0 -0
  349. data/doc/images/table_select.png +0 -0
  350. data/examples/all_events/app.rb +0 -169
  351. data/examples/all_events/app.rbs +0 -7
  352. data/examples/all_events/test_app.rb +0 -139
  353. data/examples/analytics/app.rb +0 -258
  354. data/examples/analytics/app.rbs +0 -7
  355. data/examples/analytics/test_app.rb +0 -132
  356. data/examples/block_padding/app.rb +0 -63
  357. data/examples/block_padding/app.rbs +0 -7
  358. data/examples/block_padding/test_app.rb +0 -31
  359. data/examples/block_titles/app.rb +0 -61
  360. data/examples/block_titles/app.rbs +0 -7
  361. data/examples/block_titles/test_app.rb +0 -34
  362. data/examples/box_demo/app.rbs +0 -7
  363. data/examples/box_demo/test_app.rb +0 -88
  364. data/examples/calendar_demo/app.rb +0 -101
  365. data/examples/calendar_demo/app.rbs +0 -7
  366. data/examples/calendar_demo/test_app.rb +0 -108
  367. data/examples/cell_demo/app.rb +0 -108
  368. data/examples/cell_demo/app.rbs +0 -7
  369. data/examples/cell_demo/test_app.rb +0 -36
  370. data/examples/chart_demo/app.rb +0 -203
  371. data/examples/chart_demo/app.rbs +0 -7
  372. data/examples/chart_demo/test_app.rb +0 -102
  373. data/examples/custom_widget/app.rb +0 -51
  374. data/examples/custom_widget/app.rbs +0 -7
  375. data/examples/custom_widget/test_app.rb +0 -30
  376. data/examples/flex_layout/app.rb +0 -156
  377. data/examples/flex_layout/app.rbs +0 -7
  378. data/examples/flex_layout/test_app.rb +0 -65
  379. data/examples/gauge_demo/app.rb +0 -182
  380. data/examples/gauge_demo/test_app.rb +0 -120
  381. data/examples/hit_test/app.rb +0 -175
  382. data/examples/hit_test/app.rbs +0 -7
  383. data/examples/hit_test/test_app.rb +0 -102
  384. data/examples/line_gauge_demo/app.rb +0 -190
  385. data/examples/line_gauge_demo/app.rbs +0 -7
  386. data/examples/line_gauge_demo/test_app.rb +0 -129
  387. data/examples/list_demo/app.rb +0 -253
  388. data/examples/list_demo/test_app.rb +0 -237
  389. data/examples/list_styles/app.rb +0 -140
  390. data/examples/list_styles/app.rbs +0 -7
  391. data/examples/list_styles/test_app.rb +0 -157
  392. data/examples/login_form/app.rbs +0 -7
  393. data/examples/login_form/test_app.rb +0 -51
  394. data/examples/map_demo/app.rbs +0 -7
  395. data/examples/map_demo/test_app.rb +0 -149
  396. data/examples/mouse_events/app.rb +0 -97
  397. data/examples/mouse_events/app.rbs +0 -7
  398. data/examples/mouse_events/test_app.rb +0 -53
  399. data/examples/popup_demo/app.rb +0 -103
  400. data/examples/popup_demo/app.rbs +0 -7
  401. data/examples/popup_demo/test_app.rb +0 -54
  402. data/examples/quickstart_dsl/app.rbs +0 -7
  403. data/examples/quickstart_dsl/test_app.rb +0 -29
  404. data/examples/quickstart_lifecycle/app.rb +0 -39
  405. data/examples/quickstart_lifecycle/app.rbs +0 -7
  406. data/examples/quickstart_lifecycle/test_app.rb +0 -29
  407. data/examples/ratatui_logo_demo/app.rb +0 -79
  408. data/examples/ratatui_logo_demo/app.rbs +0 -7
  409. data/examples/ratatui_logo_demo/test_app.rb +0 -51
  410. data/examples/ratatui_mascot_demo/app.rb +0 -84
  411. data/examples/ratatui_mascot_demo/app.rbs +0 -7
  412. data/examples/ratatui_mascot_demo/test_app.rb +0 -47
  413. data/examples/readme_usage/app.rb +0 -29
  414. data/examples/readme_usage/app.rbs +0 -7
  415. data/examples/readme_usage/test_app.rb +0 -29
  416. data/examples/rich_text/app.rb +0 -141
  417. data/examples/rich_text/app.rbs +0 -7
  418. data/examples/rich_text/test_app.rb +0 -166
  419. data/examples/scroll_text/app.rb +0 -103
  420. data/examples/scroll_text/app.rbs +0 -7
  421. data/examples/scroll_text/test_app.rb +0 -110
  422. data/examples/scrollbar_demo/app.rb +0 -143
  423. data/examples/scrollbar_demo/app.rbs +0 -7
  424. data/examples/scrollbar_demo/test_app.rb +0 -77
  425. data/examples/sparkline_demo/app.rb +0 -240
  426. data/examples/sparkline_demo/test_app.rb +0 -107
  427. data/examples/table_flex/app.rb +0 -65
  428. data/examples/table_flex/app.rbs +0 -7
  429. data/examples/table_flex/test_app.rb +0 -36
  430. data/examples/table_select/app.rb +0 -198
  431. data/examples/table_select/app.rbs +0 -7
  432. data/examples/table_select/test_app.rb +0 -180
  433. data/examples/widget_style_colors/test_app.rb +0 -48
  434. data/tasks/bump/comparison_links.rb +0 -41
  435. /data/doc/images/{analytics.png → app_analytics.png} +0 -0
  436. /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
  437. /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
  438. /data/doc/images/{map_demo.png → widget_map_demo.png} +0 -0
  439. /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
  440. /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
  441. /data/{doc/images/ratatui_logo_demo.png → exe/.gitkeep} +0 -0
@@ -28,22 +28,120 @@ pub fn inject_test_event(event_type: String, data: magnus::RHash) -> Result<(),
28
28
  Ok(())
29
29
  }
30
30
 
31
- fn parse_key_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::crossterm::event::Event, Error> {
32
- let code_val: Value = data.get(ruby.to_symbol("code")).ok_or_else(|| {
33
- Error::new(ruby.exception_arg_error(), "Missing 'code' in key event")
34
- })?;
31
+ /// Parses a `snake_case` string to `MediaKeyCode`.
32
+ ///
33
+ /// Accepts both the new `media_`-prefixed codes (canonical) and the legacy
34
+ /// unprefixed codes for backward compatibility with existing tests.
35
+ fn parse_media_key(s: &str) -> Option<ratatui::crossterm::event::MediaKeyCode> {
36
+ use ratatui::crossterm::event::MediaKeyCode;
37
+ match s {
38
+ // New canonical codes (media_ prefix)
39
+ "media_play" | "play" => Some(MediaKeyCode::Play),
40
+ "media_pause" => Some(MediaKeyCode::Pause),
41
+ "media_play_pause" | "play_pause" => Some(MediaKeyCode::PlayPause),
42
+ "media_reverse" | "reverse" => Some(MediaKeyCode::Reverse),
43
+ "media_stop" | "stop" => Some(MediaKeyCode::Stop),
44
+ "media_fast_forward" | "fast_forward" => Some(MediaKeyCode::FastForward),
45
+ "media_rewind" | "rewind" => Some(MediaKeyCode::Rewind),
46
+ "media_track_next" | "track_next" => Some(MediaKeyCode::TrackNext),
47
+ "media_track_previous" | "track_previous" => Some(MediaKeyCode::TrackPrevious),
48
+ "media_record" | "record" => Some(MediaKeyCode::Record),
49
+ "media_lower_volume" | "lower_volume" => Some(MediaKeyCode::LowerVolume),
50
+ "media_raise_volume" | "raise_volume" => Some(MediaKeyCode::RaiseVolume),
51
+ "media_mute_volume" | "mute_volume" => Some(MediaKeyCode::MuteVolume),
52
+ _ => None,
53
+ }
54
+ }
55
+
56
+ /// Parses a `snake_case` string to `ModifierKeyCode`.
57
+ fn parse_modifier_key(s: &str) -> Option<ratatui::crossterm::event::ModifierKeyCode> {
58
+ use ratatui::crossterm::event::ModifierKeyCode;
59
+ match s {
60
+ "left_shift" => Some(ModifierKeyCode::LeftShift),
61
+ "left_control" => Some(ModifierKeyCode::LeftControl),
62
+ "left_alt" => Some(ModifierKeyCode::LeftAlt),
63
+ "left_super" => Some(ModifierKeyCode::LeftSuper),
64
+ "left_hyper" => Some(ModifierKeyCode::LeftHyper),
65
+ "left_meta" => Some(ModifierKeyCode::LeftMeta),
66
+ "right_shift" => Some(ModifierKeyCode::RightShift),
67
+ "right_control" => Some(ModifierKeyCode::RightControl),
68
+ "right_alt" => Some(ModifierKeyCode::RightAlt),
69
+ "right_super" => Some(ModifierKeyCode::RightSuper),
70
+ "right_hyper" => Some(ModifierKeyCode::RightHyper),
71
+ "right_meta" => Some(ModifierKeyCode::RightMeta),
72
+ "iso_level3_shift" => Some(ModifierKeyCode::IsoLevel3Shift),
73
+ "iso_level5_shift" => Some(ModifierKeyCode::IsoLevel5Shift),
74
+ _ => None,
75
+ }
76
+ }
77
+
78
+ fn parse_key_event(
79
+ data: magnus::RHash,
80
+ ruby: &magnus::Ruby,
81
+ ) -> Result<ratatui::crossterm::event::Event, Error> {
82
+ use ratatui::crossterm::event::KeyCode;
83
+
84
+ let code_val: Value = data
85
+ .get(ruby.to_symbol("code"))
86
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "Missing 'code' in key event"))?;
35
87
  let code_str: String = String::try_convert(code_val)?;
36
88
  let code = match code_str.as_str() {
37
- "up" => ratatui::crossterm::event::KeyCode::Up,
38
- "down" => ratatui::crossterm::event::KeyCode::Down,
39
- "left" => ratatui::crossterm::event::KeyCode::Left,
40
- "right" => ratatui::crossterm::event::KeyCode::Right,
41
- "enter" => ratatui::crossterm::event::KeyCode::Enter,
42
- "esc" => ratatui::crossterm::event::KeyCode::Esc,
43
- "backspace" => ratatui::crossterm::event::KeyCode::Backspace,
44
- "tab" => ratatui::crossterm::event::KeyCode::Tab,
45
- c if c.len() == 1 => ratatui::crossterm::event::KeyCode::Char(c.chars().next().unwrap()),
46
- _ => ratatui::crossterm::event::KeyCode::Null,
89
+ // Arrow keys
90
+ "up" => KeyCode::Up,
91
+ "down" => KeyCode::Down,
92
+ "left" => KeyCode::Left,
93
+ "right" => KeyCode::Right,
94
+ // Common keys
95
+ "enter" => KeyCode::Enter,
96
+ "esc" => KeyCode::Esc,
97
+ "backspace" => KeyCode::Backspace,
98
+ "tab" => KeyCode::Tab,
99
+ "back_tab" => KeyCode::BackTab,
100
+ // Navigation keys
101
+ "home" => KeyCode::Home,
102
+ "end" => KeyCode::End,
103
+ "page_up" => KeyCode::PageUp,
104
+ "page_down" => KeyCode::PageDown,
105
+ "insert" => KeyCode::Insert,
106
+ "delete" => KeyCode::Delete,
107
+ // Lock keys
108
+ "caps_lock" => KeyCode::CapsLock,
109
+ "scroll_lock" => KeyCode::ScrollLock,
110
+ "num_lock" => KeyCode::NumLock,
111
+ // System keys
112
+ "print_screen" => KeyCode::PrintScreen,
113
+ "pause" => KeyCode::Pause,
114
+ "menu" => KeyCode::Menu,
115
+ "keypad_begin" => KeyCode::KeypadBegin,
116
+ "null" => KeyCode::Null,
117
+ // Dynamic parsing for media, modifiers, function keys, and characters
118
+ s => {
119
+ // Media keys (check first - "fast_forward" starts with 'f' but isn't F-key)
120
+ if let Some(m) = parse_media_key(s) {
121
+ KeyCode::Media(m)
122
+ }
123
+ // Modifier keys
124
+ else if let Some(m) = parse_modifier_key(s) {
125
+ KeyCode::Modifier(m)
126
+ }
127
+ // Function keys: f1, f2, ..., f12, etc.
128
+ else if let Some(num_str) = s.strip_prefix('f') {
129
+ if let Ok(n) = num_str.parse::<u8>() {
130
+ KeyCode::F(n)
131
+ } else {
132
+ // "f" alone or invalid suffix - treat as character
133
+ KeyCode::Char(s.chars().next().unwrap_or('\0'))
134
+ }
135
+ }
136
+ // Single character
137
+ else if s.len() == 1 {
138
+ KeyCode::Char(s.chars().next().unwrap())
139
+ }
140
+ // Unknown - default to Null
141
+ else {
142
+ KeyCode::Null
143
+ }
144
+ }
47
145
  };
48
146
 
49
147
  let mut modifiers = ratatui::crossterm::event::KeyModifiers::empty();
@@ -59,13 +157,18 @@ fn parse_key_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::
59
157
  }
60
158
  }
61
159
 
62
- Ok(ratatui::crossterm::event::Event::Key(ratatui::crossterm::event::KeyEvent::new(code, modifiers)))
160
+ Ok(ratatui::crossterm::event::Event::Key(
161
+ ratatui::crossterm::event::KeyEvent::new(code, modifiers),
162
+ ))
63
163
  }
64
164
 
65
- fn parse_mouse_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::crossterm::event::Event, Error> {
66
- let kind_val: Value = data.get(ruby.to_symbol("kind")).ok_or_else(|| {
67
- Error::new(ruby.exception_arg_error(), "Missing 'kind' in mouse event")
68
- })?;
165
+ fn parse_mouse_event(
166
+ data: magnus::RHash,
167
+ ruby: &magnus::Ruby,
168
+ ) -> Result<ratatui::crossterm::event::Event, Error> {
169
+ let kind_val: Value = data
170
+ .get(ruby.to_symbol("kind"))
171
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "Missing 'kind' in mouse event"))?;
69
172
  let kind_str: String = String::try_convert(kind_val)?;
70
173
 
71
174
  let button = if let Some(btn_val) = data.get(ruby.to_symbol("button")) {
@@ -79,14 +182,14 @@ fn parse_mouse_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui
79
182
  ratatui::crossterm::event::MouseButton::Left
80
183
  };
81
184
 
82
- let x_val: Value = data.get(ruby.to_symbol("x")).ok_or_else(|| {
83
- Error::new(ruby.exception_arg_error(), "Missing 'x' in mouse event")
84
- })?;
185
+ let x_val: Value = data
186
+ .get(ruby.to_symbol("x"))
187
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "Missing 'x' in mouse event"))?;
85
188
  let x: u16 = u16::try_convert(x_val)?;
86
189
 
87
- let y_val: Value = data.get(ruby.to_symbol("y")).ok_or_else(|| {
88
- Error::new(ruby.exception_arg_error(), "Missing 'y' in mouse event")
89
- })?;
190
+ let y_val: Value = data
191
+ .get(ruby.to_symbol("y"))
192
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "Missing 'y' in mouse event"))?;
90
193
  let y: u16 = u16::try_convert(y_val)?;
91
194
 
92
195
  let kind = match kind_str.as_str() {
@@ -98,7 +201,12 @@ fn parse_mouse_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui
98
201
  "scroll_up" => ratatui::crossterm::event::MouseEventKind::ScrollUp,
99
202
  "scroll_left" => ratatui::crossterm::event::MouseEventKind::ScrollLeft,
100
203
  "scroll_right" => ratatui::crossterm::event::MouseEventKind::ScrollRight,
101
- _ => return Err(Error::new(ruby.exception_arg_error(), format!("Unknown mouse kind: {kind_str}"))),
204
+ _ => {
205
+ return Err(Error::new(
206
+ ruby.exception_arg_error(),
207
+ format!("Unknown mouse kind: {kind_str}"),
208
+ ))
209
+ }
102
210
  };
103
211
 
104
212
  let mut modifiers = ratatui::crossterm::event::KeyModifiers::empty();
@@ -114,26 +222,48 @@ fn parse_mouse_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui
114
222
  }
115
223
  }
116
224
 
117
- Ok(ratatui::crossterm::event::Event::Mouse(ratatui::crossterm::event::MouseEvent { kind, column: x, row: y, modifiers }))
225
+ Ok(ratatui::crossterm::event::Event::Mouse(
226
+ ratatui::crossterm::event::MouseEvent {
227
+ kind,
228
+ column: x,
229
+ row: y,
230
+ modifiers,
231
+ },
232
+ ))
118
233
  }
119
234
 
120
- fn parse_resize_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::crossterm::event::Event, Error> {
235
+ fn parse_resize_event(
236
+ data: magnus::RHash,
237
+ ruby: &magnus::Ruby,
238
+ ) -> Result<ratatui::crossterm::event::Event, Error> {
121
239
  let width_val: Value = data.get(ruby.to_symbol("width")).ok_or_else(|| {
122
- Error::new(ruby.exception_arg_error(), "Missing 'width' in resize event")
240
+ Error::new(
241
+ ruby.exception_arg_error(),
242
+ "Missing 'width' in resize event",
243
+ )
123
244
  })?;
124
245
  let width: u16 = u16::try_convert(width_val)?;
125
246
 
126
247
  let height_val: Value = data.get(ruby.to_symbol("height")).ok_or_else(|| {
127
- Error::new(ruby.exception_arg_error(), "Missing 'height' in resize event")
248
+ Error::new(
249
+ ruby.exception_arg_error(),
250
+ "Missing 'height' in resize event",
251
+ )
128
252
  })?;
129
253
  let height: u16 = u16::try_convert(height_val)?;
130
254
 
131
255
  Ok(ratatui::crossterm::event::Event::Resize(width, height))
132
256
  }
133
257
 
134
- fn parse_paste_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::crossterm::event::Event, Error> {
258
+ fn parse_paste_event(
259
+ data: magnus::RHash,
260
+ ruby: &magnus::Ruby,
261
+ ) -> Result<ratatui::crossterm::event::Event, Error> {
135
262
  let content_val: Value = data.get(ruby.to_symbol("content")).ok_or_else(|| {
136
- Error::new(ruby.exception_arg_error(), "Missing 'content' in paste event")
263
+ Error::new(
264
+ ruby.exception_arg_error(),
265
+ "Missing 'content' in paste event",
266
+ )
137
267
  })?;
138
268
  let content: String = String::try_convert(content_val)?;
139
269
  Ok(ratatui::crossterm::event::Event::Paste(content))
@@ -143,7 +273,7 @@ pub fn clear_events() {
143
273
  EVENT_QUEUE.lock().unwrap().clear();
144
274
  }
145
275
 
146
- pub fn poll_event(ruby: &magnus::Ruby) -> Result<Value, Error> {
276
+ pub fn poll_event(ruby: &magnus::Ruby, timeout_val: Option<f64>) -> Result<Value, Error> {
147
277
  let event = {
148
278
  let mut queue = EVENT_QUEUE.lock().unwrap();
149
279
  if queue.is_empty() {
@@ -159,21 +289,33 @@ pub fn poll_event(ruby: &magnus::Ruby) -> Result<Value, Error> {
159
289
 
160
290
  let is_test_mode = {
161
291
  let term_lock = crate::terminal::TERMINAL.lock().unwrap();
162
- matches!(term_lock.as_ref(), Some(crate::terminal::TerminalWrapper::Test(_)))
292
+ matches!(
293
+ term_lock.as_ref(),
294
+ Some(crate::terminal::TerminalWrapper::Test(_))
295
+ )
163
296
  };
164
297
 
165
298
  if is_test_mode {
166
299
  return Ok(ruby.qnil().into_value_with(ruby));
167
300
  }
168
301
 
169
- if ratatui::crossterm::event::poll(std::time::Duration::from_millis(16))
170
- .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?
171
- {
302
+ if let Some(secs) = timeout_val {
303
+ // Timed poll: wait up to the specified duration
304
+ let duration = std::time::Duration::from_secs_f64(secs);
305
+ if ratatui::crossterm::event::poll(duration)
306
+ .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?
307
+ {
308
+ let event = ratatui::crossterm::event::read()
309
+ .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
310
+ handle_event(event)
311
+ } else {
312
+ Ok(ruby.qnil().into_value_with(ruby))
313
+ }
314
+ } else {
315
+ // Blocking: wait indefinitely for an event
172
316
  let event = ratatui::crossterm::event::read()
173
317
  .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
174
318
  handle_event(event)
175
- } else {
176
- Ok(ruby.qnil().into_value_with(ruby))
177
319
  }
178
320
  }
179
321
 
@@ -188,31 +330,156 @@ fn handle_event(event: ratatui::crossterm::event::Event) -> Result<Value, Error>
188
330
  }
189
331
  }
190
332
 
333
+ /// Converts `MediaKeyCode` to `snake_case` string.
334
+ ///
335
+ /// All media keys are consistently prefixed with `media_` to reflect that they
336
+ /// belong to the `KeyCode::Media(_)` variant in Crossterm. This allows Ruby's
337
+ /// "Smart Predicates" to provide DWIM behavior (e.g., `pause?` matching both
338
+ /// system and media pause).
339
+ fn media_key_to_string(m: ratatui::crossterm::event::MediaKeyCode) -> &'static str {
340
+ use ratatui::crossterm::event::MediaKeyCode;
341
+ match m {
342
+ MediaKeyCode::Play => "media_play",
343
+ MediaKeyCode::Pause => "media_pause",
344
+ MediaKeyCode::PlayPause => "media_play_pause",
345
+ MediaKeyCode::Reverse => "media_reverse",
346
+ MediaKeyCode::Stop => "media_stop",
347
+ MediaKeyCode::FastForward => "media_fast_forward",
348
+ MediaKeyCode::Rewind => "media_rewind",
349
+ MediaKeyCode::TrackNext => "media_track_next",
350
+ MediaKeyCode::TrackPrevious => "media_track_previous",
351
+ MediaKeyCode::Record => "media_record",
352
+ MediaKeyCode::LowerVolume => "media_lower_volume",
353
+ MediaKeyCode::RaiseVolume => "media_raise_volume",
354
+ MediaKeyCode::MuteVolume => "media_mute_volume",
355
+ }
356
+ }
357
+
358
+ /// Converts `ModifierKeyCode` to `snake_case` string.
359
+ fn modifier_key_to_string(m: ratatui::crossterm::event::ModifierKeyCode) -> &'static str {
360
+ use ratatui::crossterm::event::ModifierKeyCode;
361
+ match m {
362
+ ModifierKeyCode::LeftShift => "left_shift",
363
+ ModifierKeyCode::LeftControl => "left_control",
364
+ ModifierKeyCode::LeftAlt => "left_alt",
365
+ ModifierKeyCode::LeftSuper => "left_super",
366
+ ModifierKeyCode::LeftHyper => "left_hyper",
367
+ ModifierKeyCode::LeftMeta => "left_meta",
368
+ ModifierKeyCode::RightShift => "right_shift",
369
+ ModifierKeyCode::RightControl => "right_control",
370
+ ModifierKeyCode::RightAlt => "right_alt",
371
+ ModifierKeyCode::RightSuper => "right_super",
372
+ ModifierKeyCode::RightHyper => "right_hyper",
373
+ ModifierKeyCode::RightMeta => "right_meta",
374
+ ModifierKeyCode::IsoLevel3Shift => "iso_level3_shift",
375
+ ModifierKeyCode::IsoLevel5Shift => "iso_level5_shift",
376
+ }
377
+ }
378
+
191
379
  fn handle_key_event(key: ratatui::crossterm::event::KeyEvent) -> Result<Value, Error> {
380
+ use ratatui::crossterm::event::KeyCode;
381
+
192
382
  let ruby = magnus::Ruby::get().unwrap();
193
383
  if key.kind != ratatui::crossterm::event::KeyEventKind::Press {
194
384
  return Ok(ruby.qnil().into_value_with(&ruby));
195
385
  }
196
386
  let hash = ruby.hash_new();
197
387
  hash.aset(ruby.to_symbol("type"), ruby.to_symbol("key"))?;
388
+
389
+ // Determine the kind (category) of the key
390
+ let kind = match key.code {
391
+ KeyCode::Char(_)
392
+ | KeyCode::Enter
393
+ | KeyCode::Tab
394
+ | KeyCode::Backspace
395
+ | KeyCode::BackTab
396
+ | KeyCode::Up
397
+ | KeyCode::Down
398
+ | KeyCode::Left
399
+ | KeyCode::Right
400
+ | KeyCode::Home
401
+ | KeyCode::End
402
+ | KeyCode::PageUp
403
+ | KeyCode::PageDown
404
+ | KeyCode::Insert
405
+ | KeyCode::Delete
406
+ | KeyCode::Null => "standard",
407
+ KeyCode::F(_) => "function",
408
+ KeyCode::Media(_) => "media",
409
+ KeyCode::Modifier(_) => "modifier",
410
+ KeyCode::Esc
411
+ | KeyCode::CapsLock
412
+ | KeyCode::ScrollLock
413
+ | KeyCode::NumLock
414
+ | KeyCode::PrintScreen
415
+ | KeyCode::Pause
416
+ | KeyCode::Menu
417
+ | KeyCode::KeypadBegin => "system",
418
+ };
419
+
198
420
  let code = match key.code {
199
- ratatui::crossterm::event::KeyCode::Char(c) => c.to_string(),
200
- ratatui::crossterm::event::KeyCode::Up => "up".to_string(),
201
- ratatui::crossterm::event::KeyCode::Down => "down".to_string(),
202
- ratatui::crossterm::event::KeyCode::Left => "left".to_string(),
203
- ratatui::crossterm::event::KeyCode::Right => "right".to_string(),
204
- ratatui::crossterm::event::KeyCode::Enter => "enter".to_string(),
205
- ratatui::crossterm::event::KeyCode::Esc => "esc".to_string(),
206
- ratatui::crossterm::event::KeyCode::Backspace => "backspace".to_string(),
207
- ratatui::crossterm::event::KeyCode::Tab => "tab".to_string(),
208
- _ => "unknown".to_string(),
421
+ // Characters
422
+ KeyCode::Char(c) => c.to_string(),
423
+ // Arrow keys
424
+ KeyCode::Up => "up".to_string(),
425
+ KeyCode::Down => "down".to_string(),
426
+ KeyCode::Left => "left".to_string(),
427
+ KeyCode::Right => "right".to_string(),
428
+ // Common keys
429
+ KeyCode::Enter => "enter".to_string(),
430
+ KeyCode::Esc => "esc".to_string(),
431
+ KeyCode::Backspace => "backspace".to_string(),
432
+ KeyCode::Tab => "tab".to_string(),
433
+ KeyCode::BackTab => "back_tab".to_string(),
434
+ // Navigation keys
435
+ KeyCode::Home => "home".to_string(),
436
+ KeyCode::End => "end".to_string(),
437
+ KeyCode::PageUp => "page_up".to_string(),
438
+ KeyCode::PageDown => "page_down".to_string(),
439
+ KeyCode::Insert => "insert".to_string(),
440
+ KeyCode::Delete => "delete".to_string(),
441
+ // Function keys
442
+ KeyCode::F(n) => format!("f{n}"),
443
+ // Lock keys
444
+ KeyCode::CapsLock => "caps_lock".to_string(),
445
+ KeyCode::ScrollLock => "scroll_lock".to_string(),
446
+ KeyCode::NumLock => "num_lock".to_string(),
447
+ // System keys
448
+ KeyCode::PrintScreen => "print_screen".to_string(),
449
+ KeyCode::Pause => "pause".to_string(),
450
+ KeyCode::Menu => "menu".to_string(),
451
+ KeyCode::KeypadBegin => "keypad_begin".to_string(),
452
+ KeyCode::Null => "null".to_string(),
453
+ // Compound variants
454
+ KeyCode::Media(m) => media_key_to_string(m).to_string(),
455
+ KeyCode::Modifier(m) => modifier_key_to_string(m).to_string(),
209
456
  };
457
+
210
458
  hash.aset(ruby.to_symbol("code"), code)?;
459
+ hash.aset(ruby.to_symbol("kind"), ruby.to_symbol(kind))?;
460
+
211
461
  let mut modifiers = Vec::new();
212
- if key.modifiers.contains(ratatui::crossterm::event::KeyModifiers::CONTROL) { modifiers.push("ctrl"); }
213
- if key.modifiers.contains(ratatui::crossterm::event::KeyModifiers::ALT) { modifiers.push("alt"); }
214
- if key.modifiers.contains(ratatui::crossterm::event::KeyModifiers::SHIFT) { modifiers.push("shift"); }
215
- if !modifiers.is_empty() { hash.aset(ruby.to_symbol("modifiers"), modifiers)?; }
462
+ if key
463
+ .modifiers
464
+ .contains(ratatui::crossterm::event::KeyModifiers::CONTROL)
465
+ {
466
+ modifiers.push("ctrl");
467
+ }
468
+ if key
469
+ .modifiers
470
+ .contains(ratatui::crossterm::event::KeyModifiers::ALT)
471
+ {
472
+ modifiers.push("alt");
473
+ }
474
+ if key
475
+ .modifiers
476
+ .contains(ratatui::crossterm::event::KeyModifiers::SHIFT)
477
+ {
478
+ modifiers.push("shift");
479
+ }
480
+ if !modifiers.is_empty() {
481
+ hash.aset(ruby.to_symbol("modifiers"), modifiers)?;
482
+ }
216
483
  Ok(hash.into_value_with(&ruby))
217
484
  }
218
485
 
@@ -224,14 +491,29 @@ fn handle_mouse_event(event: ratatui::crossterm::event::MouseEvent) -> Result<Va
224
491
  ratatui::crossterm::event::MouseEventKind::Down(btn) => ("down", btn),
225
492
  ratatui::crossterm::event::MouseEventKind::Up(btn) => ("up", btn),
226
493
  ratatui::crossterm::event::MouseEventKind::Drag(btn) => ("drag", btn),
227
- ratatui::crossterm::event::MouseEventKind::Moved => ("moved", ratatui::crossterm::event::MouseButton::Left),
228
- ratatui::crossterm::event::MouseEventKind::ScrollDown => ("scroll_down", ratatui::crossterm::event::MouseButton::Left),
229
- ratatui::crossterm::event::MouseEventKind::ScrollUp => ("scroll_up", ratatui::crossterm::event::MouseButton::Left),
230
- ratatui::crossterm::event::MouseEventKind::ScrollLeft => ("scroll_left", ratatui::crossterm::event::MouseButton::Left),
231
- ratatui::crossterm::event::MouseEventKind::ScrollRight => ("scroll_right", ratatui::crossterm::event::MouseButton::Left),
494
+ ratatui::crossterm::event::MouseEventKind::Moved => {
495
+ ("moved", ratatui::crossterm::event::MouseButton::Left)
496
+ }
497
+ ratatui::crossterm::event::MouseEventKind::ScrollDown => {
498
+ ("scroll_down", ratatui::crossterm::event::MouseButton::Left)
499
+ }
500
+ ratatui::crossterm::event::MouseEventKind::ScrollUp => {
501
+ ("scroll_up", ratatui::crossterm::event::MouseButton::Left)
502
+ }
503
+ ratatui::crossterm::event::MouseEventKind::ScrollLeft => {
504
+ ("scroll_left", ratatui::crossterm::event::MouseButton::Left)
505
+ }
506
+ ratatui::crossterm::event::MouseEventKind::ScrollRight => {
507
+ ("scroll_right", ratatui::crossterm::event::MouseButton::Left)
508
+ }
232
509
  };
233
510
  hash.aset(ruby.to_symbol("kind"), ruby.to_symbol(kind))?;
234
- if matches!(event.kind, ratatui::crossterm::event::MouseEventKind::Down(_) | ratatui::crossterm::event::MouseEventKind::Up(_) | ratatui::crossterm::event::MouseEventKind::Drag(_)) {
511
+ if matches!(
512
+ event.kind,
513
+ ratatui::crossterm::event::MouseEventKind::Down(_)
514
+ | ratatui::crossterm::event::MouseEventKind::Up(_)
515
+ | ratatui::crossterm::event::MouseEventKind::Drag(_)
516
+ ) {
235
517
  let btn_sym = match button {
236
518
  ratatui::crossterm::event::MouseButton::Left => "left",
237
519
  ratatui::crossterm::event::MouseButton::Right => "right",
@@ -244,9 +526,24 @@ fn handle_mouse_event(event: ratatui::crossterm::event::MouseEvent) -> Result<Va
244
526
  hash.aset(ruby.to_symbol("x"), event.column)?;
245
527
  hash.aset(ruby.to_symbol("y"), event.row)?;
246
528
  let mut modifiers = Vec::new();
247
- if event.modifiers.contains(ratatui::crossterm::event::KeyModifiers::CONTROL) { modifiers.push("ctrl"); }
248
- if event.modifiers.contains(ratatui::crossterm::event::KeyModifiers::ALT) { modifiers.push("alt"); }
249
- if event.modifiers.contains(ratatui::crossterm::event::KeyModifiers::SHIFT) { modifiers.push("shift"); }
529
+ if event
530
+ .modifiers
531
+ .contains(ratatui::crossterm::event::KeyModifiers::CONTROL)
532
+ {
533
+ modifiers.push("ctrl");
534
+ }
535
+ if event
536
+ .modifiers
537
+ .contains(ratatui::crossterm::event::KeyModifiers::ALT)
538
+ {
539
+ modifiers.push("alt");
540
+ }
541
+ if event
542
+ .modifiers
543
+ .contains(ratatui::crossterm::event::KeyModifiers::SHIFT)
544
+ {
545
+ modifiers.push("shift");
546
+ }
250
547
  hash.aset(ruby.to_symbol("modifiers"), modifiers)?;
251
548
  Ok(hash.into_value_with(&ruby))
252
549
  }