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
data/tasks/doc.rake CHANGED
@@ -8,18 +8,612 @@ require "rdoc/task"
8
8
  require_relative "rdoc_config"
9
9
 
10
10
  RDoc::Task.new do |rdoc|
11
- rdoc.rdoc_dir = "tmp/rdoc"
11
+ rdoc.rdoc_dir = ENV["RDOC_OUTPUT"] || "tmp/rdoc"
12
12
  rdoc.main = RDocConfig::MAIN
13
+ rdoc.title = ENV["RDOC_TITLE"] if ENV["RDOC_TITLE"]
13
14
  rdoc.rdoc_files.include(RDocConfig::RDOC_FILES)
14
- rdoc.options << "--template-stylesheets=doc/custom.css"
15
+ custom_css = ENV["RDOC_CUSTOM_CSS"] || "doc/custom.css"
16
+ rdoc.options << "--template-stylesheets=#{custom_css}"
17
+ end
18
+
19
+ # Custom RDoc HTML generator that captures headings for TOC
20
+ class CapturingToHtml < RDoc::Markup::ToHtml
21
+ attr_reader :captured_headings
22
+
23
+ def initialize(options, markup = nil)
24
+ super
25
+ @captured_headings = []
26
+ end
27
+
28
+ def accept_heading(heading)
29
+ start_pos = @res.length
30
+ super
31
+ added = @res[start_pos..-1].join
32
+ if added =~ /id="([^"]+)"/
33
+ @captured_headings << { level: heading.level, text: heading.text, id: $1 }
34
+ end
35
+ end
15
36
  end
16
37
 
17
38
  task :copy_doc_images do
39
+ rdoc_dir = ENV["RDOC_OUTPUT"] || "tmp/rdoc"
18
40
  if Dir.exist?("doc/images")
19
- FileUtils.mkdir_p "tmp/rdoc/doc/images"
20
- FileUtils.cp_r Dir["doc/images/*.png"], "tmp/rdoc/doc/images"
41
+ FileUtils.mkdir_p "#{rdoc_dir}/doc/images"
42
+ FileUtils.cp_r Dir["doc/images/*.png"], "#{rdoc_dir}/doc/images"
43
+ FileUtils.cp_r Dir["doc/images/*.gif"], "#{rdoc_dir}/doc/images"
44
+ end
45
+ end
46
+
47
+ def build_tree(all_files, root_dir, max_depth = nil, current_depth = 1)
48
+ return {} if max_depth && current_depth > max_depth
49
+
50
+ files_by_dir = all_files.group_by { |f| File.dirname(f) }
51
+ dirs_at_level = files_by_dir.keys.select { |d| d.start_with?(root_dir) && d.count("/") == current_depth }
52
+
53
+ tree = {}
54
+
55
+ dirs_at_level.each do |dir|
56
+ dir_name = dir.split("/").last
57
+ files = files_by_dir[dir] || []
58
+ subdirs = files_by_dir.keys.select { |d| d.start_with?("#{dir}/") && d.count("/") == current_depth + 1 }
59
+
60
+ tree[dir_name] = {
61
+ path: dir.sub("examples/", ""),
62
+ files: files.map { |f|
63
+ {
64
+ name: File.basename(f),
65
+ path: "#{File.basename(f).gsub('.', '_')}.html",
66
+ full_path: f,
67
+ }
68
+ }.sort_by { |f| f[:name] },
69
+ subdirs: build_tree(all_files, dir, max_depth, current_depth + 1),
70
+ }
71
+ end
72
+
73
+ tree
74
+ end
75
+
76
+ def extract_rdoc_info(content, filename)
77
+ require "rdoc/comment"
78
+ require "rdoc/markup/to_html"
79
+
80
+ options = RDoc::Options.new
81
+ store = RDoc::Store.new(options)
82
+ top_level = store.add_file filename
83
+ stats = RDoc::Stats.new(store, 1)
84
+
85
+ parser = RDoc::Parser::Ruby.new(top_level, content, options, stats)
86
+ parser.scan
87
+
88
+ lines = content.lines
89
+
90
+ # Find the first class/module defined in the file
91
+ # We want the one that appears earliest in the file
92
+ # Filter out items with nil lines
93
+ target_class = top_level.classes_or_modules.select(&:line).min_by(&:line)
94
+
95
+ if target_class
96
+ # Use the class definition line as the anchor
97
+ # RDoc line numbers are 1-based
98
+ anchor_index = target_class.line - 1
99
+ title = target_class.name
100
+ search_snippet = target_class.respond_to?(:search_snippet) ? target_class.search_snippet : ""
101
+ else
102
+ # Fallback to first line of code if no class defined
103
+ first_code_index = lines.find_index { |l| !l.strip.empty? && !l.strip.start_with?("#") }
104
+ anchor_index = first_code_index
105
+ title = nil
106
+ search_snippet = ""
107
+ end
108
+
109
+ # Walk upwards from the line before the anchor to find immediate comments
110
+ comment_lines = []
111
+ if anchor_index && anchor_index > 0
112
+ idx = anchor_index - 1
113
+ while idx >= 0
114
+ line = lines[idx].strip
115
+
116
+ # Stop at blank lines
117
+ break if line.empty?
118
+
119
+ # Stop if we hit something that isn't a comment (shouldn't happen if we are above code, but safety check)
120
+ break unless line.start_with?("#")
121
+
122
+ comment_lines.unshift(lines[idx])
123
+ idx -= 1
124
+ end
21
125
  end
126
+
127
+ raw_comment = nil
128
+ unless comment_lines.empty?
129
+ # Create RDoc comment from extracted lines
130
+ # Strip leading # and optional space
131
+ cleaned_lines = comment_lines.map do |line|
132
+ line.strip.sub(/^#\s?/, "")
133
+ end
134
+ raw_comment = cleaned_lines.join("\n")
135
+ # Use first line of comment as snippet if RDoc didn't provide one
136
+ search_snippet = cleaned_lines.first if search_snippet.empty?
137
+ end
138
+
139
+ {
140
+ title:,
141
+ raw_comment:,
142
+ search_snippet:,
143
+ }
144
+ rescue => e
145
+ puts "Warning: Failed to extract RDoc info for #{filename}: #{e.message}"
146
+ { title: nil, raw_comment: nil, search_snippet: "" }
22
147
  end
23
148
 
24
- Rake::Task[:rdoc].enhance [:copy_doc_images]
25
- Rake::Task[:rerdoc].enhance [:copy_doc_images]
149
+ def render_tree_html(tree_data, current_path, current_file_html, depth = 0)
150
+ html_parts = []
151
+
152
+ # Calculate prefix to return to examples root from current file
153
+ current_depth = current_path.split("/").size - 1
154
+ root_prefix = "../" * current_depth
155
+
156
+ tree_data.keys.sort.each do |dir_name|
157
+ item = tree_data[dir_name]
158
+ has_children = item[:files].any? || item[:subdirs].any?
159
+
160
+ if has_children
161
+ # Check if this directory is in the path of the current file
162
+ # item[:path] is relative to examples root (e.g., "app_all_events/model")
163
+ # current_path is also relative to examples root (e.g., "app_all_events/model/event_color_cycle")
164
+ # We want to open if current_path starts with this directory's path
165
+ is_in_path = current_path == item[:path] || current_path.start_with?("#{item[:path]}/")
166
+ open_attr = is_in_path ? "open" : ""
167
+
168
+ html_parts << "<li>"
169
+ html_parts << " <details #{open_attr}>"
170
+ html_parts << " <summary>#{ERB::Util.html_escape(dir_name)}</summary>"
171
+ html_parts << " <ul class=\"link-list nav-list\">"
172
+
173
+ # Add files
174
+ item[:files].each do |file|
175
+ # Check specific file match
176
+ # file[:path] is the HTML filename (e.g., "event_color_cycle.html")
177
+ # current_file_html is the current file's HTML name
178
+ is_active = is_in_path && file[:path] == current_file_html
179
+
180
+ active_class = is_active ? ' class="active"' : ""
181
+ file_name_display = ERB::Util.html_escape(file[:name])
182
+ file_name_display = "<strong>#{file_name_display}</strong>" if is_active
183
+
184
+ # Link relative to examples root
185
+ file_path = "#{root_prefix}#{item[:path]}/#{file[:path]}"
186
+ html_parts << " <li><a href=\"#{file_path}\"#{active_class}><span class=\"file\"></span>#{file_name_display}</a></li>"
187
+ end
188
+
189
+ # Add subdirectories recursively
190
+ if item[:subdirs].any?
191
+ html_parts << render_tree_html(item[:subdirs], current_path, current_file_html, depth + 1)
192
+ end
193
+
194
+ html_parts << " </ul>"
195
+ html_parts << " </details>"
196
+ html_parts << "</li>"
197
+ end
198
+ end
199
+
200
+ html_parts.join("\n")
201
+ end
202
+
203
+ task :copy_examples do
204
+ puts "Copying examples..."
205
+ require "erb"
206
+
207
+ require "rdoc"
208
+ require "rdoc/markdown"
209
+ require "rdoc/markup/to_html"
210
+
211
+ rdoc_dir = ENV["RDOC_OUTPUT"] || "tmp/rdoc"
212
+
213
+ if Dir.exist?("examples")
214
+ FileUtils.rm_rf "#{rdoc_dir}/examples"
215
+ FileUtils.mkdir_p "#{rdoc_dir}/examples"
216
+
217
+ all_files = Dir.glob("examples/**/*.{rb,md}")
218
+
219
+ template = File.read("tasks/example_viewer.html.erb")
220
+ erb = ERB.new(template)
221
+
222
+ # Find the RDoc icons template
223
+ icons_path = Gem.find_files("rdoc/generator/template/aliki/_icons.rhtml").first
224
+ icons_svg = icons_path ? File.read(icons_path) : ""
225
+
226
+ # Group files by directory
227
+ files_by_dir = all_files.group_by { |f| File.dirname(f) }
228
+
229
+ # Create a binding context for ERB
230
+ class ExampleViewerContext
231
+ attr_reader :breadcrumb_path, :page_title, :file_content_html, :file_header_html
232
+ attr_reader :current_file_html, :tree_data, :doc_root_link, :icons_svg, :relative_path
233
+ attr_reader :toc_items
234
+ attr_accessor :render_tree_helper
235
+
236
+ def initialize(breadcrumb_path, page_title, file_content_html, file_header_html,
237
+ current_file_html, tree_data, doc_root_link, icons_svg, relative_path, toc_items
238
+ )
239
+ @breadcrumb_path = breadcrumb_path
240
+ @page_title = page_title
241
+ @file_content_html = file_content_html
242
+ @file_header_html = file_header_html
243
+
244
+ @current_file_html = current_file_html
245
+ @tree_data = tree_data
246
+ @doc_root_link = doc_root_link
247
+ @icons_svg = icons_svg
248
+ @relative_path = relative_path
249
+ @toc_items = toc_items
250
+ end
251
+
252
+ def render_tree(tree_data, current_path, current_file_html)
253
+ render_tree_helper.call(tree_data, current_path, current_file_html)
254
+ # Output directly to preserve HTML tags
255
+ end
256
+
257
+ def render_toc(items)
258
+ return "" if items.empty?
259
+
260
+ html = []
261
+ html << "<ul>"
262
+ base_level = items.first[:level]
263
+
264
+ items.each_with_index do |item, i|
265
+ level = item[:level]
266
+ text = item[:text]
267
+ id = item[:id]
268
+
269
+ html << "<li><a href=\"##{id}\">#{text}</a>"
270
+
271
+ next_item = items[i + 1]
272
+
273
+ if next_item
274
+ next_level = next_item[:level]
275
+ if next_level > level
276
+ (next_level - level).times { html << "<ul>" }
277
+ elsif next_level < level
278
+ html << "</li>"
279
+ (level - next_level).times { html << "</ul></li>" }
280
+ else # same level
281
+ html << "</li>"
282
+ end
283
+ else
284
+ # Last item. Close everything back to start.
285
+ html << "</li>"
286
+ (level - base_level).times { html << "</ul></li>" }
287
+ end
288
+ end
289
+
290
+ html << "</ul>"
291
+ html.join("\n")
292
+ end
293
+
294
+ def get_binding
295
+ binding
296
+ end
297
+ end
298
+
299
+ # Collect search index entries
300
+ search_entries = []
301
+
302
+ # Generate HTML files for each file
303
+ all_files.each do |file_path|
304
+ relative_path = file_path.sub("examples/", "")
305
+ target_dir = "#{rdoc_dir}/examples/#{File.dirname(relative_path)}"
306
+ FileUtils.mkdir_p target_dir
307
+
308
+ content = File.read(file_path)
309
+ ext = File.extname(file_path)
310
+ filename = File.basename(file_path)
311
+ toc_items = []
312
+
313
+ if ext == ".md"
314
+ # Markdown files usually have their own H1, so no header needed
315
+ page_title = filename
316
+ breadcrumb_path = relative_path
317
+ file_header_html = ""
318
+
319
+ # Parse markdown
320
+ doc = RDoc::Markdown.parse(content)
321
+
322
+ # Render and capture headings
323
+ options = RDoc::Options.new
324
+ renderer = CapturingToHtml.new(options)
325
+ file_content_html = doc.accept(renderer)
326
+ toc_items = renderer.captured_headings
327
+
328
+ # For Markdown, if we assume the first header is the title and is already captured,
329
+ # we might not need to prepend anything.
330
+ # But if file_header_html is empty, the H1 is in file_content_html.
331
+ # CapturingToHtml should have captured it.
332
+ else
333
+ info = extract_rdoc_info(content, filename)
334
+
335
+ if info[:title]
336
+ page_title = info[:title]
337
+ breadcrumb_path = relative_path
338
+ else
339
+ page_title = filename
340
+ breadcrumb_path = "#{File.dirname(relative_path)}/"
341
+ end
342
+
343
+ # Add to search index
344
+ html_path = "#{File.dirname(relative_path)}/#{File.basename(file_path).gsub('.', '_')}.html"
345
+ search_entries << {
346
+ name: page_title,
347
+ full_name: relative_path,
348
+ type: info[:title] ? "class" : "file",
349
+ path: html_path,
350
+ snippet: info[:search_snippet],
351
+ }
352
+
353
+ file_header_html = "<h1 id=\"top\">#{ERB::Util.html_escape(page_title)}</h1>"
354
+
355
+ # Concatenate comment and Source Code section
356
+ parts = []
357
+ if info[:raw_comment] && !info[:raw_comment].strip.empty?
358
+ parts << info[:raw_comment]
359
+ end
360
+
361
+ indented_code = content.gsub(/^/, " ")
362
+ parts << "= Source Code\n\n#{indented_code}"
363
+
364
+ combined_doc = parts.join("\n\n")
365
+
366
+ # Parse and render
367
+ doc = RDoc::Markup.parse(combined_doc)
368
+ options = RDoc::Options.new
369
+ renderer = CapturingToHtml.new(options)
370
+ file_content_html = doc.accept(renderer)
371
+ toc_items = renderer.captured_headings
372
+
373
+ # Add Page Title to TOC
374
+ toc_items.unshift({ level: 1, text: page_title, id: "top" })
375
+ end
376
+
377
+ # Calculate link to doc root
378
+
379
+ # Calculate link to doc root
380
+ depth = relative_path.split("/").size - 1
381
+ doc_root_link = "#{'../' * (depth + 1)}index.html"
382
+
383
+ # Build tree structure for sidebar
384
+ current_file_html = "#{File.basename(file_path).gsub('.', '_')}.html"
385
+ tree_data = build_tree(all_files, "examples", nil)
386
+
387
+ context = ExampleViewerContext.new(breadcrumb_path, page_title, file_content_html, file_header_html,
388
+ current_file_html, tree_data, doc_root_link, icons_svg, relative_path, toc_items)
389
+ context.render_tree_helper = lambda { |tree, path, file|
390
+ render_tree_html(tree, path, file)
391
+ }
392
+ html = erb.result(context.get_binding)
393
+
394
+ html_file = "#{target_dir}/#{File.basename(file_path).gsub('.', '_')}.html"
395
+ File.write(html_file, html)
396
+ end
397
+
398
+ # Write search index for examples
399
+ FileUtils.mkdir_p "#{rdoc_dir}/examples/js"
400
+ search_data = { index: search_entries }
401
+ File.write("#{rdoc_dir}/examples/js/search_data.js", "var search_data = #{JSON.generate(search_data)};")
402
+
403
+ # Copy RDoc search JS files to examples
404
+ rdoc_js_dir = Gem.find_files("rdoc/generator/template/aliki/js").first
405
+ if rdoc_js_dir && Dir.exist?(rdoc_js_dir)
406
+ %w[search_navigation.js search_ranker.js search_controller.js aliki.js].each do |js_file|
407
+ src = File.join(rdoc_js_dir, js_file)
408
+ FileUtils.cp(src, "#{rdoc_dir}/examples/js/#{js_file}") if File.exist?(src)
409
+ end
410
+ end
411
+
412
+ # Generate index.html files for each directory
413
+ files_by_dir.each do |dir, files|
414
+ target_dir = "#{rdoc_dir}/examples/#{dir}".sub("examples/", "")
415
+ FileUtils.mkdir_p target_dir
416
+
417
+ # Get parent directory
418
+ if dir == "examples"
419
+ parent_link = nil
420
+ doc_root_link = "../index.html"
421
+ else
422
+ parent_dir = File.dirname(dir).sub("examples/", "")
423
+ parent_link = (parent_dir == ".") ? "../index.html" : "../index.html"
424
+ depth = dir.sub("examples/", "").split("/").size
425
+ doc_root_link = "#{'../' * (depth + 1)}index.html"
426
+ end
427
+
428
+ # Find subdirectories
429
+ subdirs = files_by_dir.keys.select { |d| File.dirname(d) == dir && d != dir }
430
+
431
+ # Build combined list of folders and files with icons
432
+ items = []
433
+ subdirs.each { |d| items << { type: :dir, name: File.basename(d), path: "#{File.basename(d)}/index.html", icon: "📁" } }
434
+ files.each { |f| items << { type: :file, name: File.basename(f), path: "#{File.basename(f).gsub('.', '_')}.html", icon: "📄" } }
435
+
436
+ # Sort alphabetically
437
+ sorted_items = items.sort_by { |i| i[:name].downcase }
438
+
439
+ index_html = <<~HTML
440
+ <!DOCTYPE html>
441
+ <html lang="en">
442
+ <head>
443
+ <meta charset="UTF-8">
444
+ <meta name="viewport" content="width=device-width, initial-scale=1">
445
+ <title>Examples</title>
446
+ <link href="#{doc_root_link.sub('index.html', '')}css/rdoc.css" rel="stylesheet">
447
+ <link href="#{doc_root_link.sub('index.html', '')}custom.css" rel="stylesheet">
448
+ <script>
449
+ var rdoc_rel_prefix = "#{doc_root_link.sub('index.html', '')}";
450
+ </script>
451
+ </head>
452
+ <body class="file">
453
+ <header class="top-navbar">
454
+ <div class="navbar-brand">Examples</div>
455
+ <div class="navbar-search navbar-search-desktop"></div>
456
+ <button id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" type="button" onclick="cycleColorMode()">
457
+ <span class="theme-toggle-icon" aria-hidden="true">🌙</span>
458
+ </button>
459
+ </header>
460
+
461
+ <nav id="navigation" role="navigation">
462
+ <div id="fileindex-section" class="nav-section">
463
+ <h3>Navigation</h3>
464
+ <ul class="nav-list">
465
+ <li><a href="#{doc_root_link}">← Back to Docs</a></li>
466
+ #{parent_link ? "<li><a href=\"#{parent_link}\">↑ Up to parent directory</a></li>" : ''}
467
+ </ul>
468
+ </div>
469
+ </nav>
470
+
471
+ <main role="main">
472
+ <div class="content">
473
+ <ul class="file-list">
474
+ #{sorted_items.map { |item| "<li><a href=\"#{item[:path]}\"><span class=\"icon\">#{item[:icon]}</span>#{item[:name]}#{(item[:type] == :dir) ? '/' : ''}</a></li>" }.join("\n ")}
475
+ </ul>
476
+ </div>
477
+ <div class="footer"><a href="#{doc_root_link}">← Back to docs</a></div>
478
+ </main>
479
+
480
+ <script>
481
+ const modes = ['auto', 'light', 'dark'];
482
+ const icons = { auto: '🌓', light: '☀️', dark: '🌙' };
483
+
484
+ function setColorMode(mode) {
485
+ if (mode === 'auto') {
486
+ document.documentElement.removeAttribute('data-theme');
487
+ const systemTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
488
+ document.documentElement.setAttribute('data-theme', systemTheme);
489
+ } else {
490
+ document.documentElement.setAttribute('data-theme', mode);
491
+ }
492
+
493
+ const icon = icons[mode];
494
+ const toggle = document.getElementById('theme-toggle');
495
+ if (toggle) {
496
+ toggle.querySelector('.theme-toggle-icon').textContent = icon;
497
+ }
498
+
499
+ localStorage.setItem('rdoc-theme', mode);
500
+ }
501
+
502
+ function cycleColorMode() {
503
+ const current = localStorage.getItem('rdoc-theme') || 'auto';
504
+ const currentIndex = modes.indexOf(current);
505
+ const nextMode = modes[(currentIndex + 1) % modes.length];
506
+ setColorMode(nextMode);
507
+ }
508
+
509
+ const savedMode = localStorage.getItem('rdoc-theme') || 'auto';
510
+ setColorMode(savedMode);
511
+ </script>
512
+ </body>
513
+ </html>
514
+ HTML
515
+
516
+ File.write("#{target_dir}/index.html", index_html)
517
+ end
518
+
519
+ # Generate root index.html
520
+ root_files = all_files.select { |f| File.dirname(f) == "examples" }
521
+ root_subdirs = files_by_dir.keys.select { |d| File.dirname(d) == "examples" && d != "examples" }
522
+
523
+ # Build combined list of root folders and files with icons
524
+ root_items = []
525
+ root_subdirs.each { |d| root_items << { type: :dir, name: File.basename(d), path: "#{File.basename(d)}/index.html", icon: "📁" } }
526
+ root_files.each { |f| root_items << { type: :file, name: File.basename(f), path: "#{File.basename(f).gsub('.', '_')}.html", icon: "📄" } }
527
+
528
+ # Sort alphabetically
529
+ sorted_root_items = root_items.sort_by { |i| i[:name].downcase }
530
+
531
+ root_index_html = <<~HTML
532
+ <!DOCTYPE html>
533
+ <html lang="en">
534
+ <head>
535
+ <meta charset="UTF-8">
536
+ <meta name="viewport" content="width=device-width, initial-scale=1">
537
+ <title>Examples</title>
538
+ <link href="../css/rdoc.css" rel="stylesheet">
539
+ <link href="../custom.css" rel="stylesheet">
540
+ <script>
541
+ var rdoc_rel_prefix = "../";
542
+ </script>
543
+ </head>
544
+ <body class="file">
545
+ <header class="top-navbar">
546
+ <div class="navbar-brand">Examples</div>
547
+ <div class="navbar-search navbar-search-desktop"></div>
548
+ <button id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" type="button" onclick="cycleColorMode()">
549
+ <span class="theme-toggle-icon" aria-hidden="true">🌙</span>
550
+ </button>
551
+ </header>
552
+
553
+ <nav id="navigation" role="navigation">
554
+ <div id="fileindex-section" class="nav-section">
555
+ <h3>Navigation</h3>
556
+ <ul class="nav-list">
557
+ <li><a href="../index.html">← Back to Docs</a></li>
558
+ </ul>
559
+ </div>
560
+ </nav>
561
+
562
+ <main role="main">
563
+ <div class="content">
564
+ <ul class="file-list">
565
+ #{sorted_root_items.map { |item| "<li><a href=\"#{item[:path]}\"><span class=\"icon\">#{item[:icon]}</span>#{item[:name]}#{(item[:type] == :dir) ? '/' : ''}</a></li>" }.join("\n ")}
566
+ </ul>
567
+ </div>
568
+ <div class="footer"><a href="../index.html">← Back to docs</a></div>
569
+ </main>
570
+
571
+ <script>
572
+ const modes = ['auto', 'light', 'dark'];
573
+ const icons = { auto: '🌓', light: '☀️', dark: '🌙' };
574
+
575
+ function setColorMode(mode) {
576
+ if (mode === 'auto') {
577
+ document.documentElement.removeAttribute('data-theme');
578
+ const systemTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
579
+ document.documentElement.setAttribute('data-theme', systemTheme);
580
+ } else {
581
+ document.documentElement.setAttribute('data-theme', mode);
582
+ }
583
+
584
+ const icon = icons[mode];
585
+ const toggle = document.getElementById('theme-toggle');
586
+ if (toggle) {
587
+ toggle.querySelector('.theme-toggle-icon').textContent = icon;
588
+ }
589
+
590
+ localStorage.setItem('rdoc-theme', mode);
591
+ }
592
+
593
+ function cycleColorMode() {
594
+ const current = localStorage.getItem('rdoc-theme') || 'auto';
595
+ const currentIndex = modes.indexOf(current);
596
+ const nextMode = modes[(currentIndex + 1) % modes.length];
597
+ setColorMode(nextMode);
598
+ }
599
+
600
+ const savedMode = localStorage.getItem('rdoc-theme') || 'auto';
601
+ setColorMode(savedMode);
602
+ </script>
603
+ </body>
604
+ </html>
605
+ HTML
606
+
607
+ File.write("#{rdoc_dir}/examples/index.html", root_index_html)
608
+ end
609
+ end
610
+
611
+ Rake::Task[:rdoc].enhance do
612
+ Rake::Task[:copy_doc_images].invoke
613
+ Rake::Task[:copy_examples].invoke
614
+ end
615
+
616
+ Rake::Task[:rerdoc].enhance do
617
+ Rake::Task[:copy_doc_images].invoke
618
+ Rake::Task[:copy_examples].invoke
619
+ end