ratatui_ruby 0.4.0 → 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 (351) 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 +87 -171
  7. data/CHANGELOG.md +38 -1
  8. data/README.md +8 -3
  9. data/REUSE.toml +20 -0
  10. data/doc/application_architecture.md +105 -45
  11. data/doc/application_testing.md +5 -3
  12. data/doc/contributors/design/ruby_frontend.md +9 -5
  13. data/doc/contributors/developing_examples.md +76 -18
  14. data/doc/contributors/documentation_style.md +7 -0
  15. data/doc/contributors/index.md +2 -0
  16. data/doc/event_handling.md +10 -4
  17. data/doc/images/app_all_events.png +0 -0
  18. data/doc/images/app_color_picker.png +0 -0
  19. data/doc/images/verify_readme_usage.png +0 -0
  20. data/doc/images/widget_barchart_demo.png +0 -0
  21. data/doc/images/widget_block_padding.png +0 -0
  22. data/doc/images/widget_block_titles.png +0 -0
  23. data/doc/images/widget_box_demo.png +0 -0
  24. data/doc/images/widget_calendar_demo.png +0 -0
  25. data/doc/images/widget_cell_demo.png +0 -0
  26. data/doc/images/widget_chart_demo.png +0 -0
  27. data/doc/images/widget_gauge_demo.png +0 -0
  28. data/doc/images/widget_layout_split.png +0 -0
  29. data/doc/images/widget_line_gauge_demo.png +0 -0
  30. data/doc/images/widget_list_demo.png +0 -0
  31. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  32. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  33. data/doc/images/widget_render.png +0 -0
  34. data/doc/images/widget_scrollbar_demo.png +0 -0
  35. data/doc/images/widget_sparkline_demo.png +0 -0
  36. data/doc/images/widget_style_colors.png +0 -0
  37. data/doc/images/widget_table_flex.png +0 -0
  38. data/doc/images/widget_tabs_demo.png +0 -0
  39. data/doc/interactive_design.md +25 -30
  40. data/doc/quickstart.md +147 -120
  41. data/examples/app_all_events/README.md +81 -0
  42. data/examples/app_all_events/app.rb +93 -0
  43. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  44. data/examples/app_all_events/model/event_entry.rb +75 -0
  45. data/examples/app_all_events/model/events.rb +180 -0
  46. data/examples/app_all_events/model/highlight.rb +57 -0
  47. data/examples/app_all_events/model/timestamp.rb +54 -0
  48. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
  49. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
  50. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
  51. data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
  52. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
  53. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
  54. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
  55. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
  56. data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
  57. data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
  58. data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
  59. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
  60. data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
  61. data/examples/app_all_events/view/app_view.rb +78 -0
  62. data/examples/app_all_events/view/controls_view.rb +50 -0
  63. data/examples/app_all_events/view/counts_view.rb +55 -0
  64. data/examples/app_all_events/view/live_view.rb +69 -0
  65. data/examples/app_all_events/view/log_view.rb +60 -0
  66. data/examples/app_all_events/view.rb +7 -0
  67. data/examples/app_all_events/view_state.rb +42 -0
  68. data/examples/app_color_picker/README.md +94 -0
  69. data/examples/app_color_picker/app.rb +112 -0
  70. data/examples/app_color_picker/clipboard.rb +84 -0
  71. data/examples/app_color_picker/color.rb +191 -0
  72. data/examples/app_color_picker/copy_dialog.rb +170 -0
  73. data/examples/app_color_picker/harmony.rb +56 -0
  74. data/examples/app_color_picker/input.rb +142 -0
  75. data/examples/app_color_picker/palette.rb +80 -0
  76. data/examples/app_color_picker/scene.rb +201 -0
  77. data/examples/{login_form → app_login_form}/app.rb +39 -42
  78. data/examples/{map_demo → app_map_demo}/app.rb +24 -21
  79. data/examples/{table_select → app_table_select}/app.rb +68 -65
  80. data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +15 -6
  81. data/examples/verify_quickstart_layout/app.rb +69 -0
  82. data/examples/{quickstart_lifecycle → verify_quickstart_lifecycle}/app.rb +19 -10
  83. data/examples/verify_readme_usage/app.rb +34 -0
  84. data/examples/widget_barchart_demo/app.rb +238 -0
  85. data/examples/{block_padding → widget_block_padding}/app.rb +17 -13
  86. data/examples/{block_titles → widget_block_titles}/app.rb +25 -17
  87. data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
  88. data/examples/widget_calendar_demo/app.rb +109 -0
  89. data/examples/widget_cell_demo/app.rb +104 -0
  90. data/examples/widget_chart_demo/app.rb +213 -0
  91. data/examples/widget_gauge_demo/app.rb +212 -0
  92. data/examples/widget_layout_split/app.rb +246 -0
  93. data/examples/widget_line_gauge_demo/app.rb +217 -0
  94. data/examples/widget_list_demo/app.rb +382 -0
  95. data/examples/widget_list_styles/app.rb +141 -0
  96. data/examples/widget_popup_demo/app.rb +104 -0
  97. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  98. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  99. data/examples/widget_rect/app.rb +205 -0
  100. data/examples/widget_render/app.rb +184 -0
  101. data/examples/widget_rich_text/app.rb +137 -0
  102. data/examples/widget_scroll_text/app.rb +108 -0
  103. data/examples/widget_scrollbar_demo/app.rb +153 -0
  104. data/examples/widget_sparkline_demo/app.rb +274 -0
  105. data/examples/widget_style_colors/app.rb +19 -21
  106. data/examples/widget_table_flex/app.rb +95 -0
  107. data/examples/widget_tabs_demo/app.rb +167 -0
  108. data/ext/ratatui_ruby/Cargo.lock +1 -1
  109. data/ext/ratatui_ruby/Cargo.toml +1 -1
  110. data/ext/ratatui_ruby/src/events.rs +121 -36
  111. data/ext/ratatui_ruby/src/frame.rs +115 -0
  112. data/ext/ratatui_ruby/src/lib.rs +79 -26
  113. data/ext/ratatui_ruby/src/rendering.rs +8 -4
  114. data/ext/ratatui_ruby/src/style.rs +138 -57
  115. data/ext/ratatui_ruby/src/terminal.rs +5 -9
  116. data/ext/ratatui_ruby/src/text.rs +13 -6
  117. data/ext/ratatui_ruby/src/widgets/barchart.rs +56 -54
  118. data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
  119. data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
  120. data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
  121. data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
  122. data/ext/ratatui_ruby/src/widgets/list.rs +32 -9
  123. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  124. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  125. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
  126. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
  127. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +4 -2
  128. data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
  129. data/ext/ratatui_ruby/src/widgets/table.rs +8 -4
  130. data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
  131. data/lib/ratatui_ruby/cell.rb +3 -3
  132. data/lib/ratatui_ruby/event/key.rb +1 -1
  133. data/lib/ratatui_ruby/event/none.rb +43 -0
  134. data/lib/ratatui_ruby/event.rb +56 -4
  135. data/lib/ratatui_ruby/frame.rb +87 -0
  136. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +11 -11
  137. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
  138. data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
  139. data/lib/ratatui_ruby/schema/block.rb +163 -168
  140. data/lib/ratatui_ruby/schema/calendar.rb +66 -67
  141. data/lib/ratatui_ruby/schema/canvas.rb +63 -63
  142. data/lib/ratatui_ruby/schema/center.rb +46 -46
  143. data/lib/ratatui_ruby/schema/chart.rb +135 -143
  144. data/lib/ratatui_ruby/schema/clear.rb +42 -42
  145. data/lib/ratatui_ruby/schema/constraint.rb +76 -76
  146. data/lib/ratatui_ruby/schema/cursor.rb +25 -25
  147. data/lib/ratatui_ruby/schema/gauge.rb +53 -53
  148. data/lib/ratatui_ruby/schema/layout.rb +87 -87
  149. data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
  150. data/lib/ratatui_ruby/schema/list.rb +86 -84
  151. data/lib/ratatui_ruby/schema/overlay.rb +31 -31
  152. data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
  153. data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
  154. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
  155. data/lib/ratatui_ruby/schema/rect.rb +60 -60
  156. data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
  157. data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
  158. data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
  159. data/lib/ratatui_ruby/schema/style.rb +46 -46
  160. data/lib/ratatui_ruby/schema/table.rb +112 -119
  161. data/lib/ratatui_ruby/schema/tabs.rb +66 -67
  162. data/lib/ratatui_ruby/session/autodoc.rb +417 -0
  163. data/lib/ratatui_ruby/session.rb +40 -23
  164. data/lib/ratatui_ruby/test_helper.rb +185 -19
  165. data/lib/ratatui_ruby/version.rb +1 -1
  166. data/lib/ratatui_ruby.rb +65 -39
  167. data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
  168. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  169. data/sig/examples/app_all_events/model/events.rbs +15 -0
  170. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  171. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  172. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  173. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  174. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  175. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  176. data/sig/examples/app_all_events/view.rbs +8 -0
  177. data/sig/examples/app_all_events/view_state.rbs +15 -0
  178. data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
  179. data/sig/examples/app_login_form/app.rbs +11 -0
  180. data/sig/examples/app_map_demo/app.rbs +11 -0
  181. data/sig/examples/app_table_select/app.rbs +11 -0
  182. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  183. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  184. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  185. data/sig/examples/widget_block_padding/app.rbs +11 -0
  186. data/sig/examples/widget_block_titles/app.rbs +11 -0
  187. data/sig/examples/widget_box_demo/app.rbs +11 -0
  188. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  189. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  190. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  191. data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
  192. data/sig/examples/widget_layout_split/app.rbs +10 -0
  193. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  194. data/sig/examples/widget_list_demo/app.rbs +12 -0
  195. data/sig/examples/widget_list_styles/app.rbs +11 -0
  196. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  197. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  198. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  199. data/sig/examples/widget_rect/app.rbs +12 -0
  200. data/sig/examples/widget_render/app.rbs +10 -0
  201. data/sig/examples/widget_rich_text/app.rbs +11 -0
  202. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  203. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  204. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  205. data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
  206. data/sig/examples/widget_table_flex/app.rbs +11 -0
  207. data/sig/ratatui_ruby/frame.rbs +9 -0
  208. data/sig/ratatui_ruby/ratatui_ruby.rbs +3 -2
  209. data/sig/ratatui_ruby/schema/draw.rbs +4 -0
  210. data/sig/ratatui_ruby/schema/layout.rbs +1 -1
  211. data/sig/ratatui_ruby/session.rbs +94 -0
  212. data/tasks/autodoc/inventory.rb +61 -0
  213. data/tasks/autodoc/member.rb +56 -0
  214. data/tasks/autodoc/name.rb +19 -0
  215. data/tasks/autodoc/notice.rb +26 -0
  216. data/tasks/autodoc/rbs.rb +38 -0
  217. data/tasks/autodoc/rdoc.rb +45 -0
  218. data/tasks/autodoc.rake +47 -0
  219. data/tasks/bump/history.rb +2 -2
  220. data/tasks/doc.rake +600 -6
  221. data/tasks/example_viewer.html.erb +172 -0
  222. data/tasks/lint.rake +8 -4
  223. data/tasks/resources/index.html.erb +6 -0
  224. data/tasks/sourcehut.rake +4 -4
  225. data/tasks/terminal_preview/app_screenshot.rb +1 -3
  226. data/tasks/terminal_preview/crash_report.rb +7 -9
  227. data/tasks/terminal_preview/launcher_script.rb +4 -6
  228. data/tasks/terminal_preview/preview_collection.rb +4 -6
  229. data/tasks/terminal_preview/safety_confirmation.rb +3 -5
  230. data/tasks/terminal_preview/saved_screenshot.rb +7 -9
  231. data/tasks/terminal_preview/terminal_window.rb +7 -9
  232. data/tasks/test.rake +1 -1
  233. data/tasks/website/index_page.rb +3 -3
  234. data/tasks/website/version.rb +10 -10
  235. data/tasks/website/version_menu.rb +10 -12
  236. data/tasks/website/versioned_documentation.rb +49 -17
  237. data/tasks/website/website.rb +6 -8
  238. data/tasks/website.rake +4 -4
  239. metadata +156 -125
  240. data/LICENSES/BSD-2-Clause.txt +0 -9
  241. data/doc/contributors/better_dx.md +0 -543
  242. data/doc/contributors/example_analysis.md +0 -82
  243. data/doc/images/all_events.png +0 -0
  244. data/doc/images/block_padding.png +0 -0
  245. data/doc/images/block_titles.png +0 -0
  246. data/doc/images/box_demo.png +0 -0
  247. data/doc/images/calendar_demo.png +0 -0
  248. data/doc/images/cell_demo.png +0 -0
  249. data/doc/images/chart_demo.png +0 -0
  250. data/doc/images/flex_layout.png +0 -0
  251. data/doc/images/gauge_demo.png +0 -0
  252. data/doc/images/line_gauge_demo.png +0 -0
  253. data/doc/images/list_demo.png +0 -0
  254. data/doc/images/readme_usage.png +0 -0
  255. data/doc/images/scrollbar_demo.png +0 -0
  256. data/doc/images/sparkline_demo.png +0 -0
  257. data/doc/images/table_flex.png +0 -0
  258. data/examples/all_events/app.rb +0 -169
  259. data/examples/all_events/app.rbs +0 -7
  260. data/examples/all_events/test_app.rb +0 -139
  261. data/examples/analytics/app.rb +0 -258
  262. data/examples/analytics/app.rbs +0 -7
  263. data/examples/analytics/test_app.rb +0 -132
  264. data/examples/block_padding/app.rbs +0 -7
  265. data/examples/block_padding/test_app.rb +0 -31
  266. data/examples/block_titles/app.rbs +0 -7
  267. data/examples/block_titles/test_app.rb +0 -34
  268. data/examples/box_demo/app.rbs +0 -7
  269. data/examples/box_demo/test_app.rb +0 -88
  270. data/examples/calendar_demo/app.rb +0 -101
  271. data/examples/calendar_demo/app.rbs +0 -7
  272. data/examples/calendar_demo/test_app.rb +0 -108
  273. data/examples/cell_demo/app.rb +0 -108
  274. data/examples/cell_demo/app.rbs +0 -7
  275. data/examples/cell_demo/test_app.rb +0 -36
  276. data/examples/chart_demo/app.rb +0 -203
  277. data/examples/chart_demo/app.rbs +0 -7
  278. data/examples/chart_demo/test_app.rb +0 -102
  279. data/examples/custom_widget/app.rb +0 -51
  280. data/examples/custom_widget/app.rbs +0 -7
  281. data/examples/custom_widget/test_app.rb +0 -30
  282. data/examples/flex_layout/app.rb +0 -156
  283. data/examples/flex_layout/app.rbs +0 -7
  284. data/examples/flex_layout/test_app.rb +0 -65
  285. data/examples/gauge_demo/app.rb +0 -182
  286. data/examples/gauge_demo/test_app.rb +0 -120
  287. data/examples/hit_test/app.rb +0 -175
  288. data/examples/hit_test/app.rbs +0 -7
  289. data/examples/hit_test/test_app.rb +0 -102
  290. data/examples/line_gauge_demo/app.rb +0 -190
  291. data/examples/line_gauge_demo/app.rbs +0 -7
  292. data/examples/line_gauge_demo/test_app.rb +0 -129
  293. data/examples/list_demo/app.rb +0 -253
  294. data/examples/list_demo/test_app.rb +0 -237
  295. data/examples/list_styles/app.rb +0 -140
  296. data/examples/list_styles/app.rbs +0 -7
  297. data/examples/list_styles/test_app.rb +0 -157
  298. data/examples/login_form/app.rbs +0 -7
  299. data/examples/login_form/test_app.rb +0 -51
  300. data/examples/map_demo/app.rbs +0 -7
  301. data/examples/map_demo/test_app.rb +0 -149
  302. data/examples/mouse_events/app.rb +0 -97
  303. data/examples/mouse_events/app.rbs +0 -7
  304. data/examples/mouse_events/test_app.rb +0 -53
  305. data/examples/popup_demo/app.rb +0 -103
  306. data/examples/popup_demo/app.rbs +0 -7
  307. data/examples/popup_demo/test_app.rb +0 -54
  308. data/examples/quickstart_dsl/app.rbs +0 -7
  309. data/examples/quickstart_dsl/test_app.rb +0 -29
  310. data/examples/quickstart_lifecycle/app.rbs +0 -7
  311. data/examples/quickstart_lifecycle/test_app.rb +0 -29
  312. data/examples/ratatui_logo_demo/app.rb +0 -79
  313. data/examples/ratatui_logo_demo/app.rbs +0 -7
  314. data/examples/ratatui_logo_demo/test_app.rb +0 -51
  315. data/examples/ratatui_mascot_demo/app.rb +0 -84
  316. data/examples/ratatui_mascot_demo/app.rbs +0 -7
  317. data/examples/ratatui_mascot_demo/test_app.rb +0 -47
  318. data/examples/readme_usage/app.rb +0 -29
  319. data/examples/readme_usage/app.rbs +0 -7
  320. data/examples/readme_usage/test_app.rb +0 -29
  321. data/examples/rich_text/app.rb +0 -141
  322. data/examples/rich_text/app.rbs +0 -7
  323. data/examples/rich_text/test_app.rb +0 -166
  324. data/examples/scroll_text/app.rb +0 -103
  325. data/examples/scroll_text/app.rbs +0 -7
  326. data/examples/scroll_text/test_app.rb +0 -110
  327. data/examples/scrollbar_demo/app.rb +0 -143
  328. data/examples/scrollbar_demo/app.rbs +0 -7
  329. data/examples/scrollbar_demo/test_app.rb +0 -77
  330. data/examples/sparkline_demo/app.rb +0 -240
  331. data/examples/sparkline_demo/test_app.rb +0 -107
  332. data/examples/table_flex/app.rb +0 -65
  333. data/examples/table_flex/app.rbs +0 -7
  334. data/examples/table_flex/test_app.rb +0 -36
  335. data/examples/table_select/app.rbs +0 -7
  336. data/examples/table_select/test_app.rb +0 -180
  337. data/examples/widget_style_colors/test_app.rb +0 -48
  338. /data/doc/images/{analytics.png → app_analytics.png} +0 -0
  339. /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
  340. /data/doc/images/{login_form.png → app_login_form.png} +0 -0
  341. /data/doc/images/{map_demo.png → app_map_demo.png} +0 -0
  342. /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
  343. /data/doc/images/{table_select.png → app_table_select.png} +0 -0
  344. /data/doc/images/{quickstart_dsl.png → verify_quickstart_dsl.png} +0 -0
  345. /data/doc/images/{ratatui_logo_demo.png → verify_quickstart_layout.png} +0 -0
  346. /data/doc/images/{quickstart_lifecycle.png → verify_quickstart_lifecycle.png} +0 -0
  347. /data/doc/images/{list_styles.png → widget_list_styles.png} +0 -0
  348. /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
  349. /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
  350. /data/doc/images/{rich_text.png → widget_rich_text.png} +0 -0
  351. /data/doc/images/{scroll_text.png → widget_scroll_text.png} +0 -0
@@ -1,543 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Improving DX for Layout & Hit Testing
7
-
8
- ## Problem Statement
9
-
10
- Interactive TUI applications require hit testing: determining which UI region the user clicked. Current RatatuiRuby practice duplicates layout logic between rendering and input handling.
11
-
12
- ### Current Pattern (Duplication)
13
-
14
- ```ruby
15
- def run
16
- loop do
17
- calculate_layout # Phase 1: Manually calculate rects
18
- render # Phase 2: Build UI tree (repeating the same layout logic)
19
- handle_input # Phase 3: Use cached rects from Phase 1
20
- end
21
- end
22
-
23
- def calculate_layout
24
- full_area = RatatuiRuby::Rect.new(x: 0, y: 0, width: 80, height: 24)
25
-
26
- @main_area, @control_area = RatatuiRuby::Layout.split(
27
- full_area,
28
- direction: :vertical,
29
- constraints: [
30
- RatatuiRuby::Constraint.fill(1),
31
- RatatuiRuby::Constraint.length(7)
32
- ]
33
- )
34
-
35
- @left_rect, @right_rect = RatatuiRuby::Layout.split(
36
- @main_area,
37
- direction: :horizontal,
38
- constraints: [
39
- RatatuiRuby::Constraint.percentage(50),
40
- RatatuiRuby::Constraint.percentage(50)
41
- ]
42
- )
43
- end
44
-
45
- def render
46
- # Rebuilds the SAME layout internally, but we can't access those rects
47
- layout = RatatuiRuby::Layout.new(
48
- direction: :vertical,
49
- constraints: [
50
- RatatuiRuby::Constraint.fill(1),
51
- RatatuiRuby::Constraint.length(7)
52
- ],
53
- children: [...]
54
- )
55
- RatatuiRuby.draw(layout)
56
- end
57
-
58
- def handle_input
59
- event = RatatuiRuby.poll_event
60
- if @left_rect&.contains?(event.x, event.y)
61
- # hit test using cached rect
62
- end
63
- end
64
- ```
65
-
66
- **Problems:**
67
- 1. **Duplication**: Layout constraints are written twice—once in `calculate_layout`, once in the UI tree.
68
- 2. **Fragility**: If layout changes in `render`, the cached rects in `@left_rect` become stale. The user must remember to update both places.
69
- 3. **Maintainability**: Adding new UI regions requires changes in two places and explicit rect caching.
70
- 4. **Performance**: Layout is calculated twice per frame (once manually, once internally during render).
71
-
72
- ### Ideal Pattern (No Duplication)
73
-
74
- ```ruby
75
- def run
76
- loop do
77
- render # Single source of truth
78
- break if handle_input == :quit
79
- end
80
- end
81
-
82
- def render
83
- layout = RatatuiRuby::Layout.new(
84
- direction: :vertical,
85
- constraints: [
86
- RatatuiRuby::Constraint.fill(1),
87
- RatatuiRuby::Constraint.length(7)
88
- ],
89
- children: [...]
90
- )
91
-
92
- @layout_info = RatatuiRuby.draw(layout) # Returns layout metadata
93
- end
94
-
95
- def handle_input
96
- event = RatatuiRuby.poll_event
97
- # Query the layout info returned from draw()
98
- if @layout_info[:left_panel]&.contains?(event.x, event.y)
99
- # No manual caching needed; rects come from the same render pass
100
- end
101
- end
102
- ```
103
-
104
- ## Proposed Solution
105
-
106
- Extend `RatatuiRuby.draw()` to return a `LayoutInfo` object containing the rectangles where widgets were rendered.
107
-
108
- ### API Changes
109
-
110
- #### Option A: Return a Plain Hash (Simpler)
111
-
112
- ```ruby
113
- # Layout.rb with optional layout_id parameter
114
- class Layout < Data
115
- def self.new(
116
- direction: :vertical,
117
- constraints: [],
118
- children: [],
119
- flex: :legacy,
120
- layout_id: nil # NEW: Optional semantic identifier
121
- )
122
- # ...
123
- end
124
- end
125
-
126
- # Block.rb with optional layout_id parameter
127
- class Block < Data
128
- def self.new(
129
- title: nil,
130
- borders: [],
131
- border_type: :rounded,
132
- style: nil,
133
- layout_id: nil # NEW: Optional semantic identifier
134
- )
135
- # ...
136
- end
137
- end
138
-
139
- # Usage in app:
140
- layout = RatatuiRuby::Layout.new(
141
- direction: :vertical,
142
- constraints: [...],
143
- layout_id: :main, # Tag this layout
144
- children: [
145
- RatatuiRuby::Block.new(
146
- title: "Left",
147
- layout_id: :left_panel, # Tag this block
148
- ...
149
- ),
150
- RatatuiRuby::Block.new(
151
- title: "Right",
152
- layout_id: :right_panel,
153
- ...
154
- )
155
- ]
156
- )
157
-
158
- layout_info = RatatuiRuby.draw(layout)
159
-
160
- # layout_info is a Hash:
161
- # {
162
- # left_panel: #<Rect x=0 y=0 width=40 height=24>,
163
- # right_panel: #<Rect x=40 y=0 width=40 height=24>,
164
- # main: #<Rect x=0 y=0 width=80 height=24>
165
- # }
166
-
167
- if layout_info[:left_panel]&.contains?(event.x, event.y)
168
- handle_left_click
169
- end
170
- ```
171
-
172
- #### Option B: Return a LayoutInfo Class (More Structured)
173
-
174
- ```ruby
175
- class LayoutInfo
176
- attr_reader :rects # Hash of layout_id => Rect
177
-
178
- def [](key)
179
- rects[key]
180
- end
181
-
182
- def get(key)
183
- rects[key]
184
- end
185
-
186
- def contains?(key, x, y)
187
- rects[key]&.contains?(x, y)
188
- end
189
- end
190
-
191
- # Usage:
192
- layout_info = RatatuiRuby.draw(layout)
193
- if layout_info.contains?(:left_panel, event.x, event.y)
194
- handle_left_click
195
- end
196
- ```
197
-
198
- **Recommendation:** Start with Option A (Plain Hash). It's simpler, aligns with RatatuiRuby's minimal design, and can evolve to Option B if needed.
199
-
200
- ### Implementation Sketch
201
-
202
- #### Ruby Side
203
-
204
- 1. **Add `layout_id` parameter** to `Layout` and `Block` (and optionally other container widgets like `Center`, `Overlay`).
205
- 2. **Update `.rbs` type signatures** to document the new optional parameter.
206
- 3. **Update `RatatuiRuby.draw()` signature** to return `Hash[Symbol | String, Rect] | nil` (or return both render status and layout info as needed).
207
-
208
- ```ruby
209
- # sig/ratatui_ruby/ratatui_ruby.rbs
210
- def self.draw: (widget, ?return_layout: bool) -> (nil | Hash[Symbol | String, Rect])
211
- ```
212
-
213
- #### Rust Side
214
-
215
- 1. **Track layout IDs during render:** When the Rust renderer encounters a widget with a `layout_id`, record its rendered rectangle.
216
- 2. **Return layout info as a Ruby Hash:** Construct a Ruby Hash mapping `layout_id` (String or Symbol) to `Rect` objects.
217
- 3. **Wire into `lib.rs`:** Modify the `draw` function to return this Hash instead of `nil`.
218
-
219
- **Pseudo-code for `rendering.rs`:**
220
-
221
- ```rust
222
- pub fn render_node(
223
- frame: &mut Frame,
224
- area: Rect,
225
- node: Value,
226
- layout_map: &mut HashMap<Value, Rect>, // Collect rects as we go
227
- ) -> Result<(), Error> {
228
- // Extract layout_id if present
229
- let layout_id: Option<Value> = node.funcall("layout_id", ()).ok();
230
-
231
- if let Some(id) = layout_id {
232
- layout_map.insert(id.clone(), area);
233
- }
234
-
235
- // ... render the widget ...
236
- }
237
-
238
- // In lib.rs, wrap the result:
239
- pub fn draw(node: Value) -> Result<Value, Error> {
240
- let mut layout_map = HashMap::new();
241
- render_node(&mut frame, full_area, node, &mut layout_map)?;
242
-
243
- // Convert HashMap to Ruby Hash
244
- let result_hash = RHash::new();
245
- for (key, rect) in layout_map {
246
- result_hash.aset(key, rect)?;
247
- }
248
-
249
- Ok(result_hash.into())
250
- }
251
- ```
252
-
253
- ### Backward Compatibility
254
-
255
- **No breaking changes:**
256
- - `layout_id` is optional on all widgets.
257
- - `RatatuiRuby.draw()` continues to render correctly.
258
- - **Behavior**: If `layout_id` is omitted, that region is simply not included in the returned Hash.
259
- - **Return value**: If no widgets have `layout_id`, returns an empty Hash (or `nil` if we want to preserve existing return type).
260
-
261
- **Recommendation**: Return `nil` if `layout_id` is not used anywhere in the tree (preserves current behavior of returning nothing). Return a Hash if any widget has a `layout_id`.
262
-
263
- ## Example: Before and After
264
-
265
- ### Before (Current)
266
-
267
- ```ruby
268
- class ColorPickerApp
269
- def initialize
270
- @input = "#F96302"
271
- @current_color = parse_color(@input)
272
- @error_message = ""
273
- end
274
-
275
- def run
276
- RatatuiRuby.run do
277
- loop do
278
- calculate_layout # Manual layout calculation
279
- render
280
- result = handle_input
281
- break if result == :quit
282
- end
283
- end
284
- end
285
-
286
- def calculate_layout
287
- terminal_size = RatatuiRuby.terminal_size
288
- width, height = terminal_size
289
-
290
- full_area = RatatuiRuby::Rect.new(x: 0, y: 0, width: width, height: height)
291
-
292
- input_area, rest = RatatuiRuby::Layout.split(full_area,
293
- direction: :vertical,
294
- constraints: [
295
- RatatuiRuby::Constraint.length(3),
296
- RatatuiRuby::Constraint.fill(1)
297
- ]
298
- )
299
-
300
- color_area, control_area = RatatuiRuby::Layout.split(rest,
301
- direction: :vertical,
302
- constraints: [
303
- RatatuiRuby::Constraint.length(14),
304
- RatatuiRuby::Constraint.fill(1)
305
- ]
306
- )
307
-
308
- harmony_area, @export_area_rect = RatatuiRuby::Layout.split(color_area,
309
- direction: :vertical,
310
- constraints: [
311
- RatatuiRuby::Constraint.length(7),
312
- RatatuiRuby::Constraint.fill(1)
313
- ]
314
- )
315
- end
316
-
317
- def render
318
- main_ui = RatatuiRuby::Layout.new(
319
- direction: :vertical,
320
- constraints: [
321
- RatatuiRuby::Constraint.length(3),
322
- RatatuiRuby::Constraint.length(14),
323
- RatatuiRuby::Constraint.fill(1)
324
- ],
325
- children: [
326
- build_input_section,
327
- build_color_section,
328
- build_controls_section
329
- ]
330
- )
331
- RatatuiRuby.draw(main_ui)
332
- end
333
-
334
- def handle_input
335
- event = RatatuiRuby.poll_event
336
- case event
337
- in {type: :mouse, kind: "down", button: "left", x:, y:}
338
- if @export_area_rect&.contains?(x, y) # Using cached rect
339
- @copy_dialog_text = @current_color.to_hex.upcase
340
- @copy_dialog_active = true
341
- end
342
- # ...
343
- end
344
- end
345
- end
346
- ```
347
-
348
- **Problems:**
349
- - `calculate_layout` duplicates the exact same layout structure as `render`.
350
- - Changes to layout in `render` require manual updates to `calculate_layout`.
351
- - Fragile: rect caching is manual and easy to forget.
352
-
353
- ### After (With `layout_id`)
354
-
355
- ```ruby
356
- class ColorPickerApp
357
- def initialize
358
- @input = "#F96302"
359
- @current_color = parse_color(@input)
360
- @error_message = ""
361
- @layout_info = {} # Will be populated by draw()
362
- end
363
-
364
- def run
365
- RatatuiRuby.run do
366
- loop do
367
- render
368
- result = handle_input
369
- break if result == :quit
370
- end
371
- end
372
- end
373
-
374
- def render
375
- main_ui = RatatuiRuby::Layout.new(
376
- direction: :vertical,
377
- layout_id: :main, # Tag the main layout
378
- constraints: [
379
- RatatuiRuby::Constraint.length(3),
380
- RatatuiRuby::Constraint.length(14),
381
- RatatuiRuby::Constraint.fill(1)
382
- ],
383
- children: [
384
- build_input_section,
385
- build_color_section(layout_id: :color_section), # Tag child layouts
386
- build_controls_section
387
- ]
388
- )
389
- @layout_info = RatatuiRuby.draw(main_ui) || {} # Capture layout info
390
- end
391
-
392
- def build_color_section(layout_id: nil)
393
- RatatuiRuby::Layout.new(
394
- direction: :vertical,
395
- layout_id: layout_id,
396
- constraints: [
397
- RatatuiRuby::Constraint.length(7),
398
- RatatuiRuby::Constraint.fill(1)
399
- ],
400
- children: [
401
- build_harmonies,
402
- RatatuiRuby::Block.new(
403
- title: "Export Formats",
404
- layout_id: :export_formats, # Tag the export block
405
- borders: [:all],
406
- children: [
407
- build_export_content
408
- ]
409
- )
410
- ]
411
- )
412
- end
413
-
414
- def handle_input
415
- event = RatatuiRuby.poll_event
416
- case event
417
- in {type: :mouse, kind: "down", button: "left", x:, y:}
418
- if @layout_info[:export_formats]&.contains?(x, y) # No manual caching!
419
- @copy_dialog_text = @current_color.to_hex.upcase
420
- @copy_dialog_active = true
421
- end
422
- # ...
423
- end
424
- end
425
- end
426
- ```
427
-
428
- **Benefits:**
429
- - **Single source of truth**: Layout is defined once in `render`, not duplicated in `calculate_layout`.
430
- - **Automatic tracking**: As you modify the UI tree, rects are automatically updated by the same render pass.
431
- - **No manual caching**: Use `@layout_info` directly from `draw()`.
432
- - **Declarative**: Tag regions with semantic IDs, making hit testing code self-documenting.
433
-
434
- ## Design Alignment
435
-
436
- ### Immediate-Mode Rendering
437
-
438
- This proposal **preserves** immediate-mode principles:
439
-
440
- - **Every frame**, the app constructs a fresh UI tree from current state.
441
- - **Every frame**, `draw()` consumes that tree and renders it.
442
- - **Returns**: Layout metadata computed during the same render pass.
443
-
444
- The key insight: Returning layout info is **not** retained state; it's a **by-product** of the render, computed fresh each frame.
445
-
446
- ### Data-Driven UI
447
-
448
- Widgets remain immutable data objects; adding `layout_id` is just an optional annotation:
449
-
450
- ```ruby
451
- # Still pure data:
452
- widget = RatatuiRuby::Block.new(
453
- title: "Foo",
454
- layout_id: :my_widget, # Just metadata, not behavior
455
- borders: [:all]
456
- )
457
- ```
458
-
459
- No rendering logic moves to Ruby.
460
-
461
- ### Rust Backend Alignment
462
-
463
- In Rust Ratatui, the `Frame` tracks where widgets are rendered:
464
-
465
- ```rust
466
- let mut frame = Terminal::new(backend)?;
467
- frame.render_widget(widget, area); // Frame knows where this widget is now
468
- ```
469
-
470
- Returning layout info from `draw()` mirrors this: the Rust backend knows where things ended up, and returns that information to Ruby.
471
-
472
- ## Alternatives Considered
473
-
474
- ### Alternative 1: Widgets Maintain Their Own State
475
-
476
- Store rects on mutable widget objects. **Rejected** because:
477
- - Violates immediate-mode and data-driven design.
478
- - Requires mutable state tracking in Rust.
479
- - Complicates the simplicity of immutable data objects.
480
-
481
- ### Alternative 2: Full Frame Object
482
-
483
- Return a `Frame` object (similar to Ratatui's Frame) that tracks all rendering details. **Rejected** because:
484
- - Over-engineered for the current need.
485
- - RatatuiRuby is intentionally minimal.
486
- - Overkill if the app only cares about hit testing a few regions.
487
-
488
- ### Alternative 3: Callback-Based Rendering
489
-
490
- Allow widgets to register callbacks when rendered. **Rejected** because:
491
- - Adds complexity and statefulness.
492
- - Less idiomatic for Ruby.
493
- - Harder to reason about in immediate-mode loop.
494
-
495
- ### Alternative 4: Hit Testing DSL
496
-
497
- Provide a declarative hit testing layer separate from rendering. **Rejected** because:
498
- - Duplicates layout info (still two sources of truth).
499
- - Unnecessary indirection.
500
-
501
- ## Impact Assessment
502
-
503
- ### User-Facing Changes
504
-
505
- - **New optional parameter**: `layout_id` on `Layout`, `Block`, and similar container widgets.
506
- - **New return value**: `RatatuiRuby.draw()` optionally returns a Hash of rects.
507
- - **Zero breaking changes**: Existing apps without `layout_id` work unchanged.
508
-
509
- ### Documentation Updates
510
-
511
- - **Update RDoc** for `Layout` and `Block` to document `layout_id`.
512
- - **Add example**: `examples/hit_test/app.rb` (or new example) showing the pattern.
513
- - **Update `doc/interactive_design.md`** with the new approach.
514
-
515
- ### Testing
516
-
517
- - **Unit tests (Rust)**: Verify that rects are collected and returned correctly.
518
- - **Integration tests (Ruby)**: Verify hit testing works with returned layout info.
519
- - **Example app**: Ensure the color picker and hit test examples demonstrate the pattern.
520
-
521
- ## Timeline & Scope
522
-
523
- **Scope**: Pre-1.0 feature. Fits RatatuiRuby's design philosophy and solves a real pain point.
524
-
525
- **Estimated effort**:
526
- - Rust backend: 4–6 hours (add `layout_id` extraction, rect collection, Hash construction)
527
- - Ruby side: 2–3 hours (add parameter to widget classes, update `.rbs`, docs)
528
- - Testing & examples: 2–3 hours
529
- - **Total**: ~10 hours
530
-
531
- **Risk**: Low. The change is additive (optional parameter, new return value). Backward compatible.
532
-
533
- ## Recommendation
534
-
535
- **Approve**. This proposal:
536
-
537
- 1. Eliminates a real pain point (layout duplication).
538
- 2. Aligns with immediate-mode and data-driven design.
539
- 3. Mirrors how Rust Ratatui works (Frame tracks layout).
540
- 4. Requires no breaking changes.
541
- 5. Is low-risk and achievable pre-1.0.
542
-
543
- Implement as **Option A (Plain Hash)** first. It's simpler and sufficient for hit testing. Evolve to `LayoutInfo` class if more complex queries are needed later.
@@ -1,82 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Example Analysis
7
-
8
- The `examples/` directory contains examples in three distinct categories, each serving a different purpose.
9
-
10
- ## Category 1: Widget Showcases
11
-
12
- Single-widget (or widget-focused) examples that exhaustively demonstrate a widget's configuration options through interactive attribute cycling.
13
-
14
- These examples follow the pattern described in `developing_examples.md`: they expose all major widget parameters as hotkey-controllable options so users can interactively explore the behavior. They render at most two widgets side-by-side or vertically stacked for comparison purposes, all in service of the primary widget.
15
-
16
- **Examples:**
17
- - `box_demo`: Demonstrates Block widget variations (border types, styles, padding, titles).
18
- - `gauge_demo`: Demonstrates Gauge with adjustable ratio, color, Unicode flag, and label modes.
19
- - `list_demo`: Demonstrates List with items, highlight styles, symbols, spacing, direction, and scroll padding.
20
- - `line_gauge_demo`: Demonstrates LineGauge widget attributes.
21
- - `sparkline_demo`: Demonstrates Sparkline with direction, style, absent value markers, and bar sets.
22
- - `scrollbar_demo`: Demonstrates Scrollbar with orientation and theme cycling.
23
- - `chart_demo`: Demonstrates Chart widget attributes.
24
- - `calendar_demo`: Demonstrates Calendar widget.
25
- - `cell_demo`: Demonstrates Cell widget.
26
- - `block_titles`: Demonstrates Block title positioning and alignment.
27
- - `block_padding`: Demonstrates Block padding (uniform and directional).
28
- - `ratatui_logo_demo`: Demonstrates RatatuiLogo with style cycling.
29
- - `ratatui_mascot_demo`: Demonstrates RatatuiMascot with style cycling.
30
- - `list_styles`: Demonstrates List styling variations.
31
- - `popup_demo`: Demonstrates Popup widget positioning and behavior.
32
- - `scroll_text`: Demonstrates text scrolling behavior.
33
-
34
- ## Category 2: Real-Application Showcases
35
-
36
- Examples that function as proof-of-concept TUI applications, demonstrating how to build moderately complex, interactive programs.
37
-
38
- These are not API documentation—they do not systematically cycle through all widget parameters. Instead, they showcase composing multiple widgets to solve realistic problems (dashboards, forms, data views). They serve as inspiration for developers building their own applications and do not strictly follow the single-focus pattern.
39
-
40
- **Examples:**
41
- - `analytics`: Multi-widget analytics dashboard with Tabs and BarChart, demonstrating tab navigation and multi-chart layouts.
42
- - `login_form`: Form UI with text input, cursor positioning, and popup feedback using Paragraph, Overlay, Center, and Cursor.
43
- - `table_select`: Interactive table viewer with row/column selection, simulating a process monitor application.
44
- - `hit_test`: Demonstrates layout caching pattern for hit testing with split panels and mouse interaction.
45
- - `map_demo`: Canvas-based world map visualization with animated shapes and interactive marker cycling.
46
- - `mouse_events`: Multi-panel event display app showcasing all mouse event types.
47
- - `all_events`: Multi-panel dashboard displaying all event types (key, mouse, resize, paste, focus).
48
- - `flex_layout`: Layout demo showcasing Layout flex modes (space_between, space_evenly, fill, ratio).
49
- - `custom_widget`: Demonstrates custom widget implementation with a diagonal line widget.
50
-
51
- ## Category 3: Documentation-Verification Examples
52
-
53
- Examples that are verbatim copies (or near copies) of code snippets from documentation files, added to the `examples/` directory to ensure they remain executable and don't rot.
54
-
55
- These serve as automated documentation tests: if the example code changes but the documentation does not, tests will fail and reveal the inconsistency.
56
-
57
- **Examples:**
58
- - `quickstart_lifecycle`: Copy of the lifecycle example from `README.md` or quickstart documentation.
59
- - `quickstart_dsl`: Copy of the DSL-style example from quickstart documentation.
60
- - `readme_usage`: Copy of the simple "Hello, Ratatui!" example from `README.md`.
61
-
62
- ## TODO
63
-
64
- - [ ] **Establish a naming prefix convention** to disambiguate categories alphabetically without requiring subdirectories. Suggested prefixes:
65
- - `app_` (application): `app_analytics`, `app_login_form`, etc.
66
- - `verify_` (doc/documentation): `verify_quickstart_lifecycle`, `verify_readme_usage`, etc.
67
- - `widget_` (widget showcase): `widget_gauge_demo`, `widget_list_demo`, etc.
68
- - Apply this retroactively to all examples via directory renames (includes renaming screenshots in `doc/images/`, updating markdown image references in documentation, updating links in markdown files, and ensuring the `ExampleApp.all` list reflects the new names).
69
-
70
- - [ ] **Split `analytics`** (demonstrates both Tabs and BarChart interactively). Create `widget_tabs_demo` and move BarChart demo to it, or extract BarChart into its own single-widget showcase.
71
-
72
- - [ ] **Split `all_events`** (demonstrates multiple event types in a 2×2 grid). Consider extracting each event type into its own single-widget panel, or clarify whether this remains a "showcase of everything" vs. a focused event-demo.
73
-
74
- - [ ] **Split `flex_layout`** (demonstrates Layout flex modes with multiple examples). This is borderline—it's quasi-documentation-verification of Layout behavior. Consider whether it belongs as `verify_flex_layout` or if it should remain as a showcase.
75
-
76
- - [ ] **Reassign `mouse_events`**: Currently straddles Category 2 (real app) and Category 3 (doc verification). Clarify its purpose: is it an app showcase or documenting mouse event structure? If doc-verification, move to Category 3 and rename to `verify_mouse_events`.
77
-
78
- - [ ] **Reassign `hit_test`**: Currently categorized as Category 2 but serves partly to document the "Cached Layout Pattern". Consider renaming to `verify_hit_test` if it should be documentation-verification, or ensure it's purely a showcase of an application pattern.
79
-
80
- - [ ] **Verify `custom_widget`**: Currently Category 2 (real app), but is it actually a documented pattern or example code? If it's meant to verify custom widget documentation, rename to `verify_custom_widget`.
81
-
82
- - [ ] **Update documentation** (developing_examples.md) to reflect the new naming convention and clarify which category each example should belong to.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file