ratatui_ruby 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +10 -4
  7. data/CHANGELOG.md +79 -7
  8. data/README.md +37 -5
  9. data/REUSE.toml +2 -7
  10. data/doc/application_architecture.md +96 -22
  11. data/doc/application_testing.md +76 -30
  12. data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
  13. data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
  14. data/doc/contributors/architectural_overhaul/task.md +37 -0
  15. data/doc/contributors/design/ruby_frontend.md +288 -56
  16. data/doc/contributors/design/rust_backend.md +349 -54
  17. data/doc/contributors/developing_examples.md +134 -49
  18. data/doc/contributors/index.md +7 -5
  19. data/doc/contributors/v1.0.0_blockers.md +1729 -0
  20. data/doc/event_handling.md +11 -3
  21. data/doc/images/app_all_events.png +0 -0
  22. data/doc/images/app_color_picker.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_stateful_interaction.png +0 -0
  25. data/doc/images/verify_quickstart_dsl.png +0 -0
  26. data/doc/images/verify_quickstart_layout.png +0 -0
  27. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  28. data/doc/images/verify_readme_usage.png +0 -0
  29. data/doc/images/widget_barchart_demo.png +0 -0
  30. data/doc/images/widget_block_demo.png +0 -0
  31. data/doc/images/widget_canvas_demo.png +0 -0
  32. data/doc/images/widget_cell_demo.png +0 -0
  33. data/doc/images/widget_center_demo.png +0 -0
  34. data/doc/images/widget_chart_demo.png +0 -0
  35. data/doc/images/widget_list_demo.png +0 -0
  36. data/doc/images/widget_overlay_demo.png +0 -0
  37. data/doc/images/widget_render.png +0 -0
  38. data/doc/images/widget_rich_text.png +0 -0
  39. data/doc/images/widget_scroll_text.png +0 -0
  40. data/doc/images/widget_sparkline_demo.png +0 -0
  41. data/doc/images/widget_table_demo.png +0 -0
  42. data/doc/images/widget_tabs_demo.png +0 -0
  43. data/doc/images/widget_text_width.png +0 -0
  44. data/doc/index.md +11 -6
  45. data/doc/interactive_design.md +2 -2
  46. data/doc/quickstart.md +127 -165
  47. data/doc/terminal_limitations.md +92 -0
  48. data/doc/v0.7.0_migration.md +236 -0
  49. data/doc/why.md +93 -0
  50. data/examples/app_all_events/README.md +47 -27
  51. data/examples/app_all_events/app.rb +38 -35
  52. data/examples/app_all_events/model/app_model.rb +157 -0
  53. data/examples/app_all_events/model/event_entry.rb +17 -0
  54. data/examples/app_all_events/model/msg.rb +37 -0
  55. data/examples/app_all_events/update.rb +73 -0
  56. data/examples/app_all_events/view/app_view.rb +9 -9
  57. data/examples/app_all_events/view/controls_view.rb +9 -7
  58. data/examples/app_all_events/view/counts_view.rb +13 -9
  59. data/examples/app_all_events/view/live_view.rb +9 -8
  60. data/examples/app_all_events/view/log_view.rb +11 -16
  61. data/examples/app_color_picker/README.md +84 -42
  62. data/examples/app_color_picker/app.rb +24 -62
  63. data/examples/app_color_picker/controls.rb +90 -0
  64. data/examples/app_color_picker/copy_dialog.rb +45 -49
  65. data/examples/app_color_picker/export_pane.rb +126 -0
  66. data/examples/app_color_picker/input.rb +99 -67
  67. data/examples/app_color_picker/main_container.rb +178 -0
  68. data/examples/app_color_picker/palette.rb +55 -26
  69. data/examples/app_login_form/README.md +49 -0
  70. data/examples/app_login_form/app.rb +2 -3
  71. data/examples/app_stateful_interaction/README.md +33 -0
  72. data/examples/app_stateful_interaction/app.rb +272 -0
  73. data/examples/timeout_demo.rb +43 -0
  74. data/examples/verify_quickstart_dsl/README.md +49 -0
  75. data/examples/verify_quickstart_dsl/app.rb +2 -0
  76. data/examples/verify_quickstart_layout/README.md +71 -0
  77. data/examples/verify_quickstart_layout/app.rb +2 -0
  78. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  79. data/examples/verify_quickstart_lifecycle/app.rb +10 -4
  80. data/examples/verify_readme_usage/README.md +43 -0
  81. data/examples/verify_readme_usage/app.rb +8 -2
  82. data/examples/widget_barchart_demo/README.md +50 -0
  83. data/examples/widget_barchart_demo/app.rb +5 -5
  84. data/examples/widget_block_demo/README.md +36 -0
  85. data/examples/widget_block_demo/app.rb +256 -0
  86. data/examples/widget_box_demo/README.md +45 -0
  87. data/examples/widget_calendar_demo/README.md +39 -0
  88. data/examples/widget_calendar_demo/app.rb +5 -1
  89. data/examples/widget_canvas_demo/README.md +27 -0
  90. data/examples/widget_canvas_demo/app.rb +123 -0
  91. data/examples/widget_cell_demo/README.md +36 -0
  92. data/examples/widget_cell_demo/app.rb +31 -24
  93. data/examples/widget_center_demo/README.md +29 -0
  94. data/examples/widget_center_demo/app.rb +116 -0
  95. data/examples/widget_chart_demo/README.md +41 -0
  96. data/examples/widget_chart_demo/app.rb +7 -2
  97. data/examples/widget_gauge_demo/README.md +41 -0
  98. data/examples/widget_layout_split/README.md +44 -0
  99. data/examples/widget_line_gauge_demo/README.md +41 -0
  100. data/examples/widget_list_demo/README.md +49 -0
  101. data/examples/widget_list_demo/app.rb +91 -107
  102. data/examples/widget_map_demo/README.md +39 -0
  103. data/examples/{app_map_demo → widget_map_demo}/app.rb +4 -4
  104. data/examples/widget_overlay_demo/README.md +36 -0
  105. data/examples/widget_overlay_demo/app.rb +248 -0
  106. data/examples/widget_popup_demo/README.md +36 -0
  107. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  108. data/examples/widget_ratatui_logo_demo/app.rb +1 -1
  109. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  110. data/examples/widget_rect/README.md +38 -0
  111. data/examples/widget_render/README.md +37 -0
  112. data/examples/widget_render/app.rb +3 -3
  113. data/examples/widget_rich_text/README.md +35 -0
  114. data/examples/widget_rich_text/app.rb +62 -33
  115. data/examples/widget_scroll_text/README.md +37 -0
  116. data/examples/widget_scroll_text/app.rb +0 -1
  117. data/examples/widget_scrollbar_demo/README.md +37 -0
  118. data/examples/widget_sparkline_demo/README.md +42 -0
  119. data/examples/widget_sparkline_demo/app.rb +4 -3
  120. data/examples/widget_style_colors/README.md +34 -0
  121. data/examples/widget_table_demo/README.md +48 -0
  122. data/examples/{app_table_select → widget_table_demo}/app.rb +65 -12
  123. data/examples/widget_tabs_demo/README.md +41 -0
  124. data/examples/widget_tabs_demo/app.rb +15 -1
  125. data/examples/widget_text_width/README.md +35 -0
  126. data/examples/widget_text_width/app.rb +113 -0
  127. data/exe/.gitkeep +0 -0
  128. data/ext/ratatui_ruby/Cargo.lock +11 -4
  129. data/ext/ratatui_ruby/Cargo.toml +2 -1
  130. data/ext/ratatui_ruby/src/events.rs +238 -26
  131. data/ext/ratatui_ruby/src/frame.rs +116 -3
  132. data/ext/ratatui_ruby/src/lib.rs +37 -6
  133. data/ext/ratatui_ruby/src/rendering.rs +22 -21
  134. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  135. data/ext/ratatui_ruby/src/terminal.rs +39 -15
  136. data/ext/ratatui_ruby/src/text.rs +13 -4
  137. data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
  138. data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
  139. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  140. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  141. data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
  142. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  143. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  144. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
  145. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  146. data/ext/ratatui_ruby/src/widgets/table.rs +191 -34
  147. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  148. data/lib/ratatui_ruby/buffer/cell.rb +168 -0
  149. data/lib/ratatui_ruby/buffer.rb +15 -0
  150. data/lib/ratatui_ruby/cell.rb +4 -4
  151. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  152. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  153. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  154. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  155. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  156. data/lib/ratatui_ruby/event/key.rb +111 -51
  157. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  158. data/lib/ratatui_ruby/event/paste.rb +1 -1
  159. data/lib/ratatui_ruby/frame.rb +100 -4
  160. data/lib/ratatui_ruby/layout/constraint.rb +95 -0
  161. data/lib/ratatui_ruby/layout/layout.rb +106 -0
  162. data/lib/ratatui_ruby/layout/rect.rb +118 -0
  163. data/lib/ratatui_ruby/layout.rb +19 -0
  164. data/lib/ratatui_ruby/list_state.rb +88 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  166. data/lib/ratatui_ruby/schema/cursor.rb +5 -0
  167. data/lib/ratatui_ruby/schema/gauge.rb +3 -1
  168. data/lib/ratatui_ruby/schema/layout.rb +1 -1
  169. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  170. data/lib/ratatui_ruby/schema/list.rb +25 -4
  171. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  172. data/lib/ratatui_ruby/schema/rect.rb +43 -0
  173. data/lib/ratatui_ruby/schema/row.rb +66 -0
  174. data/lib/ratatui_ruby/schema/style.rb +24 -4
  175. data/lib/ratatui_ruby/schema/table.rb +29 -11
  176. data/lib/ratatui_ruby/schema/text.rb +96 -3
  177. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  178. data/lib/ratatui_ruby/style/style.rb +81 -0
  179. data/lib/ratatui_ruby/style.rb +15 -0
  180. data/lib/ratatui_ruby/table_state.rb +90 -0
  181. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  182. data/lib/ratatui_ruby/test_helper/snapshot.rb +414 -0
  183. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  184. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  185. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  186. data/lib/ratatui_ruby/test_helper.rb +65 -358
  187. data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
  188. data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
  189. data/lib/ratatui_ruby/tui/core.rb +38 -0
  190. data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
  191. data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
  192. data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
  193. data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
  194. data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
  195. data/lib/ratatui_ruby/tui.rb +75 -0
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
  198. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
  199. data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
  200. data/lib/ratatui_ruby/widgets/block.rb +192 -0
  201. data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
  202. data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
  203. data/lib/ratatui_ruby/widgets/cell.rb +47 -0
  204. data/lib/ratatui_ruby/widgets/center.rb +59 -0
  205. data/lib/ratatui_ruby/widgets/chart.rb +185 -0
  206. data/lib/ratatui_ruby/widgets/clear.rb +54 -0
  207. data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
  208. data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
  209. data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
  210. data/lib/ratatui_ruby/widgets/list.rb +127 -0
  211. data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
  212. data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
  213. data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
  214. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
  215. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
  216. data/lib/ratatui_ruby/widgets/row.rb +68 -0
  217. data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
  218. data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
  219. data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
  220. data/lib/ratatui_ruby/widgets/table.rb +141 -0
  221. data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
  222. data/lib/ratatui_ruby/widgets.rb +40 -0
  223. data/lib/ratatui_ruby.rb +64 -57
  224. data/sig/examples/app_all_events/view.rbs +1 -1
  225. data/sig/examples/app_all_events/view_state.rbs +1 -1
  226. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  227. data/sig/examples/widget_block_demo/app.rbs +32 -0
  228. data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
  229. data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
  230. data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
  231. data/sig/ratatui_ruby/event.rbs +11 -1
  232. data/sig/ratatui_ruby/frame.rbs +2 -0
  233. data/sig/ratatui_ruby/list_state.rbs +13 -0
  234. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
  235. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  236. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  237. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  238. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  239. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  240. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  241. data/sig/ratatui_ruby/schema/row.rbs +22 -0
  242. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  243. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  244. data/sig/ratatui_ruby/schema/text.rbs +9 -6
  245. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  246. data/sig/ratatui_ruby/session.rbs +41 -48
  247. data/sig/ratatui_ruby/table_state.rbs +15 -0
  248. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  249. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  250. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  251. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  252. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  253. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  254. data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
  255. data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
  256. data/sig/ratatui_ruby/tui/core.rbs +14 -0
  257. data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
  258. data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
  259. data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
  260. data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
  261. data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
  262. data/sig/ratatui_ruby/tui.rbs +19 -0
  263. data/tasks/autodoc/examples.rb +79 -0
  264. data/tasks/autodoc.rake +7 -35
  265. data/tasks/bump/changelog.rb +3 -3
  266. data/tasks/bump/links.rb +67 -0
  267. data/tasks/sourcehut.rake +64 -21
  268. data/tasks/terminal_preview/app_screenshot.rb +13 -3
  269. data/tasks/terminal_preview/saved_screenshot.rb +4 -3
  270. metadata +169 -48
  271. data/doc/contributors/dwim_dx.md +0 -366
  272. data/doc/images/app_analytics.png +0 -0
  273. data/doc/images/app_custom_widget.png +0 -0
  274. data/doc/images/app_mouse_events.png +0 -0
  275. data/doc/images/app_table_select.png +0 -0
  276. data/doc/images/widget_block_padding.png +0 -0
  277. data/doc/images/widget_block_titles.png +0 -0
  278. data/doc/images/widget_list_styles.png +0 -0
  279. data/doc/images/widget_table_flex.png +0 -0
  280. data/examples/app_all_events/model/events.rb +0 -180
  281. data/examples/app_all_events/model/highlight.rb +0 -57
  282. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
  283. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
  284. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
  285. data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
  286. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
  287. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
  288. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
  289. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
  290. data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
  291. data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
  292. data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
  293. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
  294. data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
  295. data/examples/app_all_events/view_state.rb +0 -42
  296. data/examples/app_color_picker/scene.rb +0 -201
  297. data/examples/widget_block_padding/app.rb +0 -67
  298. data/examples/widget_block_titles/app.rb +0 -69
  299. data/examples/widget_list_styles/app.rb +0 -141
  300. data/examples/widget_table_flex/app.rb +0 -95
  301. data/lib/ratatui_ruby/session/autodoc.rb +0 -417
  302. data/lib/ratatui_ruby/session.rb +0 -163
  303. data/sig/examples/widget_block_padding/app.rbs +0 -11
  304. data/sig/examples/widget_block_titles/app.rbs +0 -11
  305. data/sig/examples/widget_list_styles/app.rbs +0 -11
  306. data/tasks/autodoc/inventory.rb +0 -61
  307. data/tasks/autodoc/notice.rb +0 -26
  308. data/tasks/autodoc/rbs.rb +0 -38
  309. data/tasks/autodoc/rdoc.rb +0 -45
  310. data/tasks/bump/comparison_links.rb +0 -41
  311. /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ module Layout
8
+ # Divides an area into smaller chunks.
9
+ #
10
+ # Terminal screens vary in size. Hardcoded positions break when the window resizes. You need a way to organize space dynamically.
11
+ #
12
+ # This class manages geometry. It splits a given area into multiple sections based on a list of constraints.
13
+ #
14
+ # Use layouts to build responsive grids. Stack sections vertically for a sidebar-main structure. Partition them horizontally for headers and footers. Let the layout engine do the math.
15
+ #
16
+ # {rdoc-image:/doc/images/widget_layout_split.png}[link:/examples/widget_layout_split/app_rb.html]
17
+ #
18
+ # === Example
19
+ #
20
+ # Run the interactive demo from the terminal:
21
+ #
22
+ # ruby examples/widget_layout_split/app.rb
23
+ class Layout < Data.define(:direction, :constraints, :children, :flex)
24
+ ##
25
+ # :attr_reader: direction
26
+ # Direction of the split.
27
+ #
28
+ # Either <tt>:vertical</tt> (top to bottom) or <tt>:horizontal</tt> (left to right).
29
+ #
30
+ # layout.direction # => :vertical
31
+
32
+ ##
33
+ # :attr_reader: constraints
34
+ # Array of rules defining section sizes.
35
+ #
36
+ # See RatatuiRuby::Layout::Constraint.
37
+
38
+ ##
39
+ # :attr_reader: children
40
+ # Widgets to render in each section (optional).
41
+ #
42
+ # If provided, `children[i]` is rendered into the area defined by `constraints[i]`.
43
+
44
+ ##
45
+ # :attr_reader: flex
46
+ # Strategy for distributing extra space.
47
+ #
48
+ # One of <tt>:legacy</tt>, <tt>:start</tt>, <tt>:center</tt>, <tt>:end</tt>, <tt>:space_between</tt>, <tt>:space_around</tt>.
49
+
50
+ # :nodoc:
51
+ FLEX_MODES = %i[legacy start center end space_between space_around space_evenly].freeze
52
+
53
+ # Creates a new Layout.
54
+ #
55
+ # [direction]
56
+ # <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
57
+ # [constraints]
58
+ # list of Constraint objects.
59
+ # [children]
60
+ # List of widgets to render (optional).
61
+ # [flex]
62
+ # Flex mode for spacing (default: <tt>:legacy</tt>).
63
+ def initialize(direction: :vertical, constraints: [], children: [], flex: :legacy)
64
+ super
65
+ end
66
+
67
+ # Splits an area into multiple rectangles.
68
+ #
69
+ # This is a pure calculation helper for hit testing. It computes where
70
+ # widgets *would* be placed without actually rendering them.
71
+ #
72
+ # rects = Layout::Layout.split(
73
+ # area,
74
+ # direction: :horizontal,
75
+ # constraints: [Layout::Constraint.percentage(50), Layout::Constraint.percentage(50)]
76
+ # )
77
+ # left, right = rects
78
+ #
79
+ # [area]
80
+ # The area to split. Can be a <tt>Rect</tt> or a <tt>Hash</tt> containing <tt>:x</tt>, <tt>:y</tt>, <tt>:width</tt>, and <tt>:height</tt>.
81
+ # [direction]
82
+ # <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
83
+ # [constraints]
84
+ # Array of <tt>Constraint</tt> objects defining section sizes.
85
+ # [flex]
86
+ # Flex mode for spacing (default: <tt>:legacy</tt>).
87
+ #
88
+ # Returns an Array of <tt>Rect</tt> objects.
89
+ def self.split(area, direction: :vertical, constraints:, flex: :legacy)
90
+ # Duck-typing: If it lacks geometry methods but can be a Hash, convert it.
91
+ if !area.respond_to?(:x) && area.respond_to?(:to_h)
92
+ # Assume it's a Hash-like object with :x, :y, etc.
93
+ hash = area.to_h
94
+ area = Rect.new(
95
+ x: hash.fetch(:x, 0),
96
+ y: hash.fetch(:y, 0),
97
+ width: hash.fetch(:width, 0),
98
+ height: hash.fetch(:height, 0)
99
+ )
100
+ end
101
+ raw_rects = _split(area, direction, constraints, flex)
102
+ raw_rects.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ module Layout
8
+ # Defines a rectangular area in the terminal grid.
9
+ #
10
+ # Geometry management involves passing groups of four integers (`x, y, width, height`) repeatedly.
11
+ # This is verbose and prone to parameter mismatch errors.
12
+ #
13
+ # This class encapsulates the geometry. It provides a standard primitive for passing area definitions
14
+ # between layout engines and rendering functions.
15
+ #
16
+ # Use it when manual positioning is required or when querying layout results.
17
+ #
18
+ # === Examples
19
+ #
20
+ # area = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
21
+ # puts area.width # => 80
22
+ class Rect < Data.define(:x, :y, :width, :height)
23
+ ##
24
+ # :attr_reader: x
25
+ # X coordinate (column) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
26
+
27
+ ##
28
+ # :attr_reader: y
29
+ # Y coordinate (row) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
30
+
31
+ ##
32
+ # :attr_reader: width
33
+ # Width in characters (Integer, coerced via +to_int+ or +to_i+).
34
+
35
+ ##
36
+ # :attr_reader: height
37
+ # Height in characters (Integer, coerced via +to_int+ or +to_i+).
38
+
39
+ # Creates a new Rect.
40
+ #
41
+ # All parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
42
+ #
43
+ # [x] Column index (Numeric).
44
+ # [y] Row index (Numeric).
45
+ # [width] Width in columns (Numeric).
46
+ # [height] Height in rows (Numeric).
47
+ def initialize(x: 0, y: 0, width: 0, height: 0)
48
+ super(
49
+ x: Integer(x),
50
+ y: Integer(y),
51
+ width: Integer(width),
52
+ height: Integer(height)
53
+ )
54
+ end
55
+
56
+ # Tests whether a point is inside this rectangle.
57
+ #
58
+ # Essential for hit testing mouse clicks against layout regions.
59
+ #
60
+ # area = Layout::Rect.new(x: 10, y: 5, width: 20, height: 10)
61
+ # area.contains?(15, 8) # => true
62
+ # area.contains?(5, 8) # => false
63
+ #
64
+ # [px]
65
+ # X coordinate to test (column).
66
+ # [py]
67
+ # Y coordinate to test (row).
68
+ #
69
+ # Returns true if the point (px, py) is within the rectangle bounds.
70
+ def contains?(px, py)
71
+ px >= x && px < x + width && py >= y && py < y + height
72
+ end
73
+
74
+ # Tests whether this rectangle overlaps with another.
75
+ #
76
+ # Essential for determining if a widget is visible within a viewport or clipping area.
77
+ #
78
+ # viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
79
+ # widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
80
+ # viewport.intersects?(widget) # => true (partial overlap)
81
+ #
82
+ # [other]
83
+ # Another Rect to test against.
84
+ #
85
+ # Returns true if the rectangles overlap.
86
+ def intersects?(other)
87
+ x < other.x + other.width &&
88
+ x + width > other.x &&
89
+ y < other.y + other.height &&
90
+ y + height > other.y
91
+ end
92
+
93
+ # Returns the overlapping area between this rectangle and another.
94
+ #
95
+ # Essential for calculating visible portions of widgets inside scroll views.
96
+ #
97
+ # viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
98
+ # widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
99
+ # visible = viewport.intersection(widget)
100
+ # # => Rect(x: 70, y: 20, width: 10, height: 4)
101
+ #
102
+ # [other]
103
+ # Another Rect to intersect with.
104
+ #
105
+ # Returns a new Rect representing the intersection, or +nil+ if no overlap.
106
+ def intersection(other)
107
+ return nil unless intersects?(other)
108
+
109
+ new_x = [x, other.x].max
110
+ new_y = [y, other.y].max
111
+ new_right = [x + width, other.x + other.width].min
112
+ new_bottom = [y + height, other.y + other.height].min
113
+
114
+ Rect.new(x: new_x, y: new_y, width: new_right - new_x, height: new_bottom - new_y)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ # Layout primitives for geometry and space distribution.
8
+ #
9
+ # This module mirrors +ratatui::layout+ and contains:
10
+ # - {Rect} — Rectangle geometry
11
+ # - {Constraint} — Sizing rules
12
+ # - {Layout} — Space distribution
13
+ module Layout
14
+ end
15
+ end
16
+
17
+ require_relative "layout/rect"
18
+ require_relative "layout/constraint"
19
+ require_relative "layout/layout"
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ # Mutable state object for List widgets.
8
+ #
9
+ # When using {Frame#render_stateful_widget}, the State object is the
10
+ # *single source of truth* for selection and scroll offset. Widget
11
+ # properties (+selected_index+, +offset+) are *ignored* in stateful mode.
12
+ #
13
+ # State objects persist across frames, allowing you to:
14
+ # - Track selection without manual index management
15
+ # - Read back the scroll offset calculated by Ratatui
16
+ # - Implement mouse click-to-row hit testing
17
+ #
18
+ # == Thread/Ractor Safety
19
+ #
20
+ # ListState is *not* Ractor-shareable. It contains mutable internal state.
21
+ # Store it in instance variables, not in immutable Models.
22
+ #
23
+ # == Example
24
+ #
25
+ # @list_state = RatatuiRuby::ListState.new
26
+ # @list_state.select(2) # Select third item
27
+ #
28
+ # RatatuiRuby.draw do |frame|
29
+ # list = RatatuiRuby::Widgets::List.new(items: ["A", "B", "C", "D", "E"])
30
+ # frame.render_stateful_widget(list, frame.area, @list_state)
31
+ # end
32
+ #
33
+ # puts @list_state.offset # Scroll position after render
34
+ #
35
+ class ListState
36
+ ##
37
+ # :method: new
38
+ # :call-seq: new(selected = nil) -> ListState
39
+ #
40
+ # Creates a new ListState with optional initial selection.
41
+ #
42
+ # (Native method implemented in Rust)
43
+
44
+ ##
45
+ # :method: select
46
+ # :call-seq: select(index) -> nil
47
+ #
48
+ # Sets the selected index. Pass +nil+ to deselect.
49
+ #
50
+ # (Native method implemented in Rust)
51
+
52
+ ##
53
+ # :method: selected
54
+ # :call-seq: selected() -> Integer or nil
55
+ #
56
+ # Returns the currently selected index, or +nil+ if nothing is selected.
57
+ #
58
+ # (Native method implemented in Rust)
59
+
60
+ ##
61
+ # :method: offset
62
+ # :call-seq: offset() -> Integer
63
+ #
64
+ # Returns the current scroll offset.
65
+ #
66
+ # This is the critical read-back method. After +render_stateful_widget+,
67
+ # this returns the scroll position calculated by Ratatui to keep the
68
+ # selection visible.
69
+ #
70
+ # (Native method implemented in Rust)
71
+
72
+ ##
73
+ # :method: scroll_down_by
74
+ # :call-seq: scroll_down_by(n) -> nil
75
+ #
76
+ # Scrolls down by +n+ items.
77
+ #
78
+ # (Native method implemented in Rust)
79
+
80
+ ##
81
+ # :method: scroll_up_by
82
+ # :call-seq: scroll_up_by(n) -> nil
83
+ #
84
+ # Scrolls up by +n+ items.
85
+ #
86
+ # (Native method implemented in Rust)
87
+ end
88
+ end
@@ -17,7 +17,7 @@ module RatatuiRuby
17
17
 
18
18
  ##
19
19
  # :attr_reader: label
20
- # The label of the bar (optional String).
20
+ # The label of the bar (optional String, Text::Span, or Text::Line for rich styling).
21
21
 
22
22
  ##
23
23
  # :attr_reader: style
@@ -29,7 +29,7 @@ module RatatuiRuby
29
29
 
30
30
  ##
31
31
  # :attr_reader: text_value
32
- # The text to display as the value (optional String).
32
+ # The text to display as the value (optional String, Text::Span, or Text::Line for rich styling).
33
33
 
34
34
  def initialize(value:, label: nil, style: nil, value_style: nil, text_value: nil)
35
35
  super(
@@ -15,6 +15,11 @@ module RatatuiRuby
15
15
  # === Examples
16
16
  #
17
17
  # Cursor.new(x: 10, y: 5)
18
+ #
19
+ # See also:
20
+ # - {Declarative implementation using Tree API}[link:/examples/app_login_form/app_rb.html]
21
+ # - {Component-based implementation using Frame API}[link:/examples/app_color_picker/app_rb.html]
22
+ # - RatatuiRuby::Frame#set_cursor_position (Frame API alternative)
18
23
  class Cursor < Data.define(:x, :y)
19
24
  ##
20
25
  # :attr_reader: x
@@ -28,6 +28,8 @@ module RatatuiRuby
28
28
  # :attr_reader: label
29
29
  # Text label to display (optional).
30
30
  #
31
+ # Accepts String or Text::Span for rich styling.
32
+ #
31
33
  # If nil, it often displays the percentage automatically depending on renderer logic,
32
34
  # but explicit labels are preferred.
33
35
 
@@ -52,7 +54,7 @@ module RatatuiRuby
52
54
  #
53
55
  # [ratio] Float (0.0 - 1.0).
54
56
  # [percent] Integer (0 - 100), alternative to ratio.
55
- # [label] String (optional).
57
+ # [label] String or Text::Span (optional).
56
58
  # [style] Style object for the background (optional).
57
59
  # [gauge_style] Style object for the filled bar (optional).
58
60
  # [block] Block widget (optional).
@@ -32,7 +32,7 @@ module RatatuiRuby
32
32
  # :attr_reader: constraints
33
33
  # Array of rules defining section sizes.
34
34
  #
35
- # See RatatuiRuby::Constraint.
35
+ # See RatatuiRuby::Layout::Constraint.
36
36
 
37
37
  ##
38
38
  # :attr_reader: children
@@ -26,7 +26,7 @@ module RatatuiRuby
26
26
 
27
27
  ##
28
28
  # :attr_reader: label
29
- # Optional label.
29
+ # Optional label (String or Text::Span for rich styling).
30
30
 
31
31
  ##
32
32
  # :attr_reader: style
@@ -55,7 +55,7 @@ module RatatuiRuby
55
55
  # Creates a new LineGauge.
56
56
  #
57
57
  # [ratio] Float (0.0 - 1.0).
58
- # [label] String (optional).
58
+ # [label] String or Text::Span (optional).
59
59
  # [style] Style (optional, base style for the gauge).
60
60
  # [filled_style] Style.
61
61
  # [unfilled_style] Style.
@@ -27,15 +27,34 @@ module RatatuiRuby
27
27
  # highlight_style: Style.new(bg: :blue),
28
28
  # highlight_symbol: ">> "
29
29
  # )
30
- class List < Data.define(:items, :selected_index, :style, :highlight_style, :highlight_symbol, :repeat_highlight_symbol, :highlight_spacing, :direction, :scroll_padding, :block)
30
+ class List < Data.define(:items, :selected_index, :offset, :style, :highlight_style, :highlight_symbol, :repeat_highlight_symbol, :highlight_spacing, :direction, :scroll_padding, :block)
31
31
  ##
32
32
  # :attr_reader: items
33
- # The items to display (Array of Strings).
33
+ # The items to display.
34
+ #
35
+ # Accepts Array of Strings, Text::Spans, Text::Lines, or ListItem objects.
36
+ # For styled individual rows, use ListItem with a style.
34
37
 
35
38
  ##
36
39
  # :attr_reader: selected_index
37
40
  # Index of the active selection (Integer or nil).
38
41
 
42
+ ##
43
+ # :attr_reader: offset
44
+ # Scroll offset (Integer or nil).
45
+ #
46
+ # Controls the viewport's starting position in the list.
47
+ #
48
+ # When +nil+ (default), Ratatui auto-scrolls to keep the selection visible ("natural scrolling").
49
+ #
50
+ # When set, forces the viewport to start at this item index. Use this for:
51
+ # - **Passive scrolling**: Scroll through a log viewer without selecting items.
52
+ # - **Click-to-select math**: Calculate which item index corresponds to a click coordinate.
53
+ #
54
+ # *Important*: When both +offset+ and +selected_index+ are set, Ratatui may still adjust
55
+ # the viewport during rendering to ensure the selection stays visible. Set +selected_index+
56
+ # to +nil+ for fully manual scroll control.
57
+
39
58
  ##
40
59
  # :attr_reader: style
41
60
  # Base style for unselected items.
@@ -76,8 +95,9 @@ module RatatuiRuby
76
95
  #
77
96
  # Integer parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
78
97
  #
79
- # [items] Array of Strings.
98
+ # [items] Array of Strings, Text::Spans, Text::Lines, or ListItem objects.
80
99
  # [selected_index] Numeric (nullable, coerced to Integer).
100
+ # [offset] Numeric (nullable, coerced to Integer). Forces scroll position when set.
81
101
  # [style] Style object.
82
102
  # [highlight_style] Style object.
83
103
  # [highlight_symbol] String (default: <tt>"> "</tt>).
@@ -86,10 +106,11 @@ module RatatuiRuby
86
106
  # [direction] Symbol (default: <tt>:top_to_bottom</tt>).
87
107
  # [scroll_padding] Numeric (nullable, coerced to Integer, default: <tt>nil</tt>).
88
108
  # [block] Block (optional).
89
- def initialize(items: [], selected_index: nil, style: nil, highlight_style: nil, highlight_symbol: "> ", repeat_highlight_symbol: false, highlight_spacing: :when_selected, direction: :top_to_bottom, scroll_padding: nil, block: nil)
109
+ def initialize(items: [], selected_index: nil, offset: nil, style: nil, highlight_style: nil, highlight_symbol: "> ", repeat_highlight_symbol: false, highlight_spacing: :when_selected, direction: :top_to_bottom, scroll_padding: nil, block: nil)
90
110
  super(
91
111
  items:,
92
112
  selected_index: selected_index.nil? ? nil : Integer(selected_index),
113
+ offset: offset.nil? ? nil : Integer(offset),
93
114
  style:,
94
115
  highlight_style:,
95
116
  highlight_symbol:,
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ # A styled list item combining content with optional style.
8
+ #
9
+ # By default, List items are strings. For more control over styling individual rows,
10
+ # wrap the content in a ListItem to apply a style specific to that item.
11
+ #
12
+ # The content can be a String, Text::Span, or Text::Line. The style applies to the
13
+ # entire row background.
14
+ #
15
+ # === Examples
16
+ #
17
+ # # Item with red background
18
+ # ListItem.new(content: "Error", style: Style.new(bg: :red))
19
+ #
20
+ # # Item with styled content
21
+ # ListItem.new(
22
+ # content: Text::Span.new(content: "Status: OK", style: Style.new(fg: :green, modifiers: [:bold]))
23
+ # )
24
+ class ListItem < Data.define(:content, :style)
25
+ ##
26
+ # :attr_reader: content
27
+ # The content to display (String, Text::Span, or Text::Line).
28
+
29
+ ##
30
+ # :attr_reader: style
31
+ # The style to apply to the item (optional Style).
32
+
33
+ # Creates a new ListItem.
34
+ #
35
+ # [content] String, Text::Span, or Text::Line.
36
+ # [style] Style object (optional).
37
+ def initialize(content:, style: nil)
38
+ super
39
+ end
40
+ end
41
+ end
@@ -69,5 +69,48 @@ module RatatuiRuby
69
69
  def contains?(px, py)
70
70
  px >= x && px < x + width && py >= y && py < y + height
71
71
  end
72
+
73
+ # Tests whether this rectangle overlaps with another.
74
+ #
75
+ # Essential for determining if a widget is visible within a viewport or clipping area.
76
+ #
77
+ # viewport = Rect.new(x: 0, y: 0, width: 80, height: 24)
78
+ # widget = Rect.new(x: 70, y: 20, width: 20, height: 10)
79
+ # viewport.intersects?(widget) # => true (partial overlap)
80
+ #
81
+ # [other]
82
+ # Another Rect to test against.
83
+ #
84
+ # Returns true if the rectangles overlap.
85
+ def intersects?(other)
86
+ x < other.x + other.width &&
87
+ x + width > other.x &&
88
+ y < other.y + other.height &&
89
+ y + height > other.y
90
+ end
91
+
92
+ # Returns the overlapping area between this rectangle and another.
93
+ #
94
+ # Essential for calculating visible portions of widgets inside scroll views.
95
+ #
96
+ # viewport = Rect.new(x: 0, y: 0, width: 80, height: 24)
97
+ # widget = Rect.new(x: 70, y: 20, width: 20, height: 10)
98
+ # visible = viewport.intersection(widget)
99
+ # # => Rect(x: 70, y: 20, width: 10, height: 4)
100
+ #
101
+ # [other]
102
+ # Another Rect to intersect with.
103
+ #
104
+ # Returns a new Rect representing the intersection, or +nil+ if no overlap.
105
+ def intersection(other)
106
+ return nil unless intersects?(other)
107
+
108
+ new_x = [x, other.x].max
109
+ new_y = [y, other.y].max
110
+ new_right = [x + width, other.x + other.width].min
111
+ new_bottom = [y + height, other.y + other.height].min
112
+
113
+ Rect.new(x: new_x, y: new_y, width: new_right - new_x, height: new_bottom - new_y)
114
+ end
72
115
  end
73
116
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ # A styled table row combining cells with optional row-level styling.
8
+ #
9
+ # By default, Table rows are arrays of cell content. For more control over styling
10
+ # individual rows, wrap the cells in a Row object to apply row-level style.
11
+ #
12
+ # The cells can be Strings, Text::Spans, Text::Lines, Paragraphs, or Cells.
13
+ # The style applies to the entire row background.
14
+ #
15
+ # === Examples
16
+ #
17
+ # # Row with red background
18
+ # Row.new(cells: ["Error", "Something went wrong"], style: Style.new(bg: :red))
19
+ #
20
+ # # Row with styled cells and custom height
21
+ # Row.new(
22
+ # cells: [
23
+ # Text::Span.new(content: "Status", style: Style.new(modifiers: [:bold])),
24
+ # Text::Span.new(content: "OK", style: Style.new(fg: :green))
25
+ # ],
26
+ # height: 2
27
+ # )
28
+ class Row < Data.define(:cells, :style, :height, :top_margin, :bottom_margin)
29
+ ##
30
+ # :attr_reader: cells
31
+ # The cells to display (Array of Strings, Text::Spans, Text::Lines, Paragraphs, or Cells).
32
+
33
+ ##
34
+ # :attr_reader: style
35
+ # The style to apply to the row (optional Style).
36
+
37
+ ##
38
+ # :attr_reader: height
39
+ # Fixed row height in lines (optional Integer).
40
+
41
+ ##
42
+ # :attr_reader: top_margin
43
+ # Margin above the row in lines (optional Integer).
44
+
45
+ ##
46
+ # :attr_reader: bottom_margin
47
+ # Margin below the row in lines (optional Integer).
48
+
49
+ # Creates a new Row.
50
+ #
51
+ # [cells] Array of Strings, Text::Spans, Text::Lines, Paragraphs, or Cells.
52
+ # [style] Style object (optional).
53
+ # [height] Integer for fixed height (optional).
54
+ # [top_margin] Integer for top margin (optional).
55
+ # [bottom_margin] Integer for bottom margin (optional).
56
+ def initialize(cells:, style: nil, height: nil, top_margin: nil, bottom_margin: nil)
57
+ super(
58
+ cells:,
59
+ style:,
60
+ height: height.nil? ? nil : Integer(height),
61
+ top_margin: top_margin.nil? ? nil : Integer(top_margin),
62
+ bottom_margin: bottom_margin.nil? ? nil : Integer(bottom_margin)
63
+ )
64
+ end
65
+ end
66
+ end
@@ -20,18 +20,38 @@ module RatatuiRuby
20
20
  #
21
21
  # # Hex colors
22
22
  # Style.new(fg: "#ff00ff")
23
+ #
24
+ # === Supported Colors
25
+ #
26
+ # ==== Integer
27
+ # Represents an indexed color from the Xterm 256-color palette (0-255).
28
+ # * <tt>0</tt>–<tt>15</tt>: Standard and bright ANSI colors.
29
+ # * <tt>16</tt>–<tt>231</tt>: {6x6x6 Color Cube}[https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit].
30
+ # * <tt>232</tt>–<tt>255</tt>: Grayscale ramp.
31
+ #
32
+ # ==== Symbol
33
+ # Represents a named color from the standard ANSI palette. Supported values:
34
+ # * <tt>:black</tt>, <tt>:red</tt>, <tt>:green</tt>, <tt>:yellow</tt>,
35
+ # <tt>:blue</tt>, <tt>:magenta</tt>, <tt>:cyan</tt>, <tt>:gray</tt>
36
+ # * <tt>:dark_gray</tt>, <tt>:light_red</tt>, <tt>:light_green</tt>,
37
+ # <tt>:light_yellow</tt>, <tt>:light_blue</tt>, <tt>:light_magenta</tt>,
38
+ # <tt>:light_cyan</tt>, <tt>:white</tt>
39
+ #
40
+ # ==== String
41
+ # Represents a specific RGB color using a Hex code (<tt>"#RRGGBB"</tt>).
42
+ # Requires a terminal emulator with "True Color" (24-bit color) support.
23
43
  class Style < Data.define(:fg, :bg, :modifiers)
24
44
  ##
25
45
  # :attr_reader: fg
26
46
  # Foreground color.
27
47
  #
28
- # Symbol (<tt>:red</tt>) or Hex String (<tt>"#ffffff"</tt>).
48
+ # Symbol (<tt>:red</tt>), Hex String (<tt>"#ffffff"</tt>), or Integer (0-255).
29
49
 
30
50
  ##
31
51
  # :attr_reader: bg
32
52
  # Background color.
33
53
  #
34
- # Symbol (<tt>:black</tt>) or Hex String (<tt>"#000000"</tt>).
54
+ # Symbol (<tt>:black</tt>), Hex String (<tt>"#000000"</tt>), or Integer (0-255).
35
55
 
36
56
  ##
37
57
  # :attr_reader: modifiers
@@ -42,8 +62,8 @@ module RatatuiRuby
42
62
 
43
63
  # Creates a new Style.
44
64
  #
45
- # [fg] Color (Symbol/String).
46
- # [bg] Color (Symbol/String).
65
+ # [fg] Color (Symbol/String/Integer).
66
+ # [bg] Color (Symbol/String/Integer).
47
67
  # [modifiers] Array of Symbols.
48
68
  def initialize(fg: nil, bg: nil, modifiers: [])
49
69
  super