ratatui_ruby 1.4.0-x86_64-linux

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 (292) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +15 -0
  3. data/LICENSES/AGPL-3.0-or-later.txt +661 -0
  4. data/LICENSES/CC-BY-SA-4.0.txt +427 -0
  5. data/LICENSES/CC0-1.0.txt +121 -0
  6. data/LICENSES/LGPL-3.0-or-later.txt +304 -0
  7. data/LICENSES/MIT-0.txt +16 -0
  8. data/LICENSES/MIT.txt +21 -0
  9. data/REUSE.toml +42 -0
  10. data/exe/.gitkeep +0 -0
  11. data/ext/ratatui_ruby/.cargo/config.toml +13 -0
  12. data/ext/ratatui_ruby/.gitignore +4 -0
  13. data/ext/ratatui_ruby/Cargo.lock +1737 -0
  14. data/ext/ratatui_ruby/Cargo.toml +24 -0
  15. data/ext/ratatui_ruby/clippy.toml +7 -0
  16. data/ext/ratatui_ruby/extconf.rb +21 -0
  17. data/ext/ratatui_ruby/src/color.rs +82 -0
  18. data/ext/ratatui_ruby/src/errors.rs +28 -0
  19. data/ext/ratatui_ruby/src/events.rs +700 -0
  20. data/ext/ratatui_ruby/src/frame.rs +241 -0
  21. data/ext/ratatui_ruby/src/lib.rs +343 -0
  22. data/ext/ratatui_ruby/src/lib_header.rs +11 -0
  23. data/ext/ratatui_ruby/src/rendering.rs +158 -0
  24. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  25. data/ext/ratatui_ruby/src/style.rs +469 -0
  26. data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
  27. data/ext/ratatui_ruby/src/terminal/init.rs +233 -0
  28. data/ext/ratatui_ruby/src/terminal/mod.rs +42 -0
  29. data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
  30. data/ext/ratatui_ruby/src/terminal/queries.rs +231 -0
  31. data/ext/ratatui_ruby/src/terminal/query.rs +400 -0
  32. data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
  33. data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
  34. data/ext/ratatui_ruby/src/text.rs +225 -0
  35. data/ext/ratatui_ruby/src/widgets/barchart.rs +169 -0
  36. data/ext/ratatui_ruby/src/widgets/block.rs +41 -0
  37. data/ext/ratatui_ruby/src/widgets/calendar.rs +84 -0
  38. data/ext/ratatui_ruby/src/widgets/canvas.rs +183 -0
  39. data/ext/ratatui_ruby/src/widgets/center.rs +79 -0
  40. data/ext/ratatui_ruby/src/widgets/chart.rs +222 -0
  41. data/ext/ratatui_ruby/src/widgets/clear.rs +39 -0
  42. data/ext/ratatui_ruby/src/widgets/cursor.rs +32 -0
  43. data/ext/ratatui_ruby/src/widgets/gauge.rs +65 -0
  44. data/ext/ratatui_ruby/src/widgets/layout.rs +379 -0
  45. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +100 -0
  46. data/ext/ratatui_ruby/src/widgets/list.rs +378 -0
  47. data/ext/ratatui_ruby/src/widgets/list_state.rs +173 -0
  48. data/ext/ratatui_ruby/src/widgets/mod.rs +26 -0
  49. data/ext/ratatui_ruby/src/widgets/overlay.rs +24 -0
  50. data/ext/ratatui_ruby/src/widgets/paragraph.rs +87 -0
  51. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  52. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +55 -0
  53. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +214 -0
  54. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  55. data/ext/ratatui_ruby/src/widgets/sparkline.rs +127 -0
  56. data/ext/ratatui_ruby/src/widgets/table.rs +415 -0
  57. data/ext/ratatui_ruby/src/widgets/table_state.rs +203 -0
  58. data/ext/ratatui_ruby/src/widgets/tabs.rs +194 -0
  59. data/lib/ratatui_ruby/backend/window_size.rb +50 -0
  60. data/lib/ratatui_ruby/backend.rb +59 -0
  61. data/lib/ratatui_ruby/buffer/cell.rb +212 -0
  62. data/lib/ratatui_ruby/buffer.rb +149 -0
  63. data/lib/ratatui_ruby/cell.rb +208 -0
  64. data/lib/ratatui_ruby/debug.rb +215 -0
  65. data/lib/ratatui_ruby/draw.rb +63 -0
  66. data/lib/ratatui_ruby/event/focus_gained.rb +125 -0
  67. data/lib/ratatui_ruby/event/focus_lost.rb +127 -0
  68. data/lib/ratatui_ruby/event/key/character.rb +53 -0
  69. data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
  70. data/lib/ratatui_ruby/event/key/media.rb +46 -0
  71. data/lib/ratatui_ruby/event/key/modifier.rb +107 -0
  72. data/lib/ratatui_ruby/event/key/navigation.rb +72 -0
  73. data/lib/ratatui_ruby/event/key/system.rb +47 -0
  74. data/lib/ratatui_ruby/event/key.rb +479 -0
  75. data/lib/ratatui_ruby/event/mouse.rb +291 -0
  76. data/lib/ratatui_ruby/event/none.rb +53 -0
  77. data/lib/ratatui_ruby/event/paste.rb +130 -0
  78. data/lib/ratatui_ruby/event/resize.rb +221 -0
  79. data/lib/ratatui_ruby/event/sync.rb +52 -0
  80. data/lib/ratatui_ruby/event.rb +163 -0
  81. data/lib/ratatui_ruby/frame.rb +257 -0
  82. data/lib/ratatui_ruby/labs/a11y.rb +182 -0
  83. data/lib/ratatui_ruby/labs/frame_a11y_capture.rb +50 -0
  84. data/lib/ratatui_ruby/labs.rb +47 -0
  85. data/lib/ratatui_ruby/layout/alignment.rb +91 -0
  86. data/lib/ratatui_ruby/layout/constraint.rb +337 -0
  87. data/lib/ratatui_ruby/layout/layout.rb +258 -0
  88. data/lib/ratatui_ruby/layout/position.rb +81 -0
  89. data/lib/ratatui_ruby/layout/rect.rb +733 -0
  90. data/lib/ratatui_ruby/layout/size.rb +62 -0
  91. data/lib/ratatui_ruby/layout.rb +29 -0
  92. data/lib/ratatui_ruby/list_state.rb +201 -0
  93. data/lib/ratatui_ruby/output_guard.rb +171 -0
  94. data/lib/ratatui_ruby/ratatui_ruby.so +0 -0
  95. data/lib/ratatui_ruby/scrollbar_state.rb +122 -0
  96. data/lib/ratatui_ruby/style/color.rb +149 -0
  97. data/lib/ratatui_ruby/style/style.rb +147 -0
  98. data/lib/ratatui_ruby/style.rb +19 -0
  99. data/lib/ratatui_ruby/symbols.rb +435 -0
  100. data/lib/ratatui_ruby/synthetic_events.rb +106 -0
  101. data/lib/ratatui_ruby/table_state.rb +251 -0
  102. data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
  103. data/lib/ratatui_ruby/terminal/viewport.rb +80 -0
  104. data/lib/ratatui_ruby/terminal.rb +66 -0
  105. data/lib/ratatui_ruby/terminal_lifecycle.rb +303 -0
  106. data/lib/ratatui_ruby/terminal_lifecycle.rb.bak +197 -0
  107. data/lib/ratatui_ruby/test_helper/event_injection.rb +241 -0
  108. data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
  109. data/lib/ratatui_ruby/test_helper/snapshot.rb +568 -0
  110. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
  111. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
  112. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
  113. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
  114. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
  115. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
  116. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
  117. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
  118. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
  119. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
  120. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
  121. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
  122. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
  123. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
  124. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
  125. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
  126. data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
  127. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
  128. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
  129. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
  130. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
  131. data/lib/ratatui_ruby/test_helper/style_assertions.rb +449 -0
  132. data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
  133. data/lib/ratatui_ruby/test_helper/terminal.rb +187 -0
  134. data/lib/ratatui_ruby/test_helper/test_doubles.rb +86 -0
  135. data/lib/ratatui_ruby/test_helper.rb +115 -0
  136. data/lib/ratatui_ruby/text/line.rb +245 -0
  137. data/lib/ratatui_ruby/text/span.rb +158 -0
  138. data/lib/ratatui_ruby/text.rb +99 -0
  139. data/lib/ratatui_ruby/tui/buffer_factories.rb +22 -0
  140. data/lib/ratatui_ruby/tui/canvas_factories.rb +149 -0
  141. data/lib/ratatui_ruby/tui/core.rb +67 -0
  142. data/lib/ratatui_ruby/tui/layout_factories.rb +153 -0
  143. data/lib/ratatui_ruby/tui/state_factories.rb +77 -0
  144. data/lib/ratatui_ruby/tui/style_factories.rb +22 -0
  145. data/lib/ratatui_ruby/tui/text_factories.rb +86 -0
  146. data/lib/ratatui_ruby/tui/widget_factories.rb +272 -0
  147. data/lib/ratatui_ruby/tui.rb +106 -0
  148. data/lib/ratatui_ruby/version.rb +12 -0
  149. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +51 -0
  150. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +29 -0
  151. data/lib/ratatui_ruby/widgets/bar_chart.rb +308 -0
  152. data/lib/ratatui_ruby/widgets/block.rb +266 -0
  153. data/lib/ratatui_ruby/widgets/calendar.rb +88 -0
  154. data/lib/ratatui_ruby/widgets/canvas.rb +297 -0
  155. data/lib/ratatui_ruby/widgets/cell.rb +59 -0
  156. data/lib/ratatui_ruby/widgets/center.rb +71 -0
  157. data/lib/ratatui_ruby/widgets/chart.rb +172 -0
  158. data/lib/ratatui_ruby/widgets/clear.rb +66 -0
  159. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  160. data/lib/ratatui_ruby/widgets/cursor.rb +54 -0
  161. data/lib/ratatui_ruby/widgets/gauge.rb +146 -0
  162. data/lib/ratatui_ruby/widgets/line_gauge.rb +158 -0
  163. data/lib/ratatui_ruby/widgets/list.rb +252 -0
  164. data/lib/ratatui_ruby/widgets/list_item.rb +55 -0
  165. data/lib/ratatui_ruby/widgets/overlay.rb +55 -0
  166. data/lib/ratatui_ruby/widgets/paragraph.rb +113 -0
  167. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +35 -0
  168. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +40 -0
  169. data/lib/ratatui_ruby/widgets/row.rb +123 -0
  170. data/lib/ratatui_ruby/widgets/scrollbar.rb +147 -0
  171. data/lib/ratatui_ruby/widgets/shape/label.rb +80 -0
  172. data/lib/ratatui_ruby/widgets/sparkline.rb +153 -0
  173. data/lib/ratatui_ruby/widgets/table.rb +213 -0
  174. data/lib/ratatui_ruby/widgets/tabs.rb +91 -0
  175. data/lib/ratatui_ruby/widgets.rb +43 -0
  176. data/lib/ratatui_ruby.rb +555 -0
  177. data/sig/examples/app_all_events/app.rbs +11 -0
  178. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  179. data/sig/examples/app_all_events/model/event_entry.rbs +23 -0
  180. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  181. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  182. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  183. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  184. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  185. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  186. data/sig/examples/app_all_events/view.rbs +14 -0
  187. data/sig/examples/app_cli_rich_moments/app.rbs +12 -0
  188. data/sig/examples/app_color_picker/app.rbs +17 -0
  189. data/sig/examples/app_external_editor/app.rbs +12 -0
  190. data/sig/examples/app_login_form/app.rbs +11 -0
  191. data/sig/examples/app_stateful_interaction/app.rbs +39 -0
  192. data/sig/examples/verify_quickstart_dsl/app.rbs +17 -0
  193. data/sig/examples/verify_quickstart_lifecycle/app.rbs +17 -0
  194. data/sig/examples/verify_readme_usage/app.rbs +17 -0
  195. data/sig/examples/widget_block_demo/app.rbs +38 -0
  196. data/sig/examples/widget_box_demo/app.rbs +17 -0
  197. data/sig/examples/widget_calendar_demo/app.rbs +17 -0
  198. data/sig/examples/widget_cell_demo/app.rbs +17 -0
  199. data/sig/examples/widget_chart_demo/app.rbs +17 -0
  200. data/sig/examples/widget_gauge_demo/app.rbs +17 -0
  201. data/sig/examples/widget_layout_split/app.rbs +16 -0
  202. data/sig/examples/widget_line_gauge_demo/app.rbs +17 -0
  203. data/sig/examples/widget_list_demo/app.rbs +17 -0
  204. data/sig/examples/widget_map_demo/app.rbs +17 -0
  205. data/sig/examples/widget_popup_demo/app.rbs +17 -0
  206. data/sig/examples/widget_ratatui_logo_demo/app.rbs +17 -0
  207. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +17 -0
  208. data/sig/examples/widget_rect/app.rbs +18 -0
  209. data/sig/examples/widget_render/app.rbs +16 -0
  210. data/sig/examples/widget_rich_text/app.rbs +17 -0
  211. data/sig/examples/widget_scroll_text/app.rbs +17 -0
  212. data/sig/examples/widget_scrollbar_demo/app.rbs +17 -0
  213. data/sig/examples/widget_sparkline_demo/app.rbs +16 -0
  214. data/sig/examples/widget_style_colors/app.rbs +20 -0
  215. data/sig/examples/widget_table_demo/app.rbs +17 -0
  216. data/sig/examples/widget_text_width/app.rbs +16 -0
  217. data/sig/generated/event_key_predicates.rbs +1348 -0
  218. data/sig/manifest.yaml +5 -0
  219. data/sig/patches/data.rbs +26 -0
  220. data/sig/patches/debugger__.rbs +8 -0
  221. data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
  222. data/sig/ratatui_ruby/backend.rbs +12 -0
  223. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  224. data/sig/ratatui_ruby/buffer.rbs +18 -0
  225. data/sig/ratatui_ruby/cell.rbs +44 -0
  226. data/sig/ratatui_ruby/clear.rbs +18 -0
  227. data/sig/ratatui_ruby/constraint.rbs +26 -0
  228. data/sig/ratatui_ruby/debug.rbs +45 -0
  229. data/sig/ratatui_ruby/draw.rbs +30 -0
  230. data/sig/ratatui_ruby/event.rbs +249 -0
  231. data/sig/ratatui_ruby/frame.rbs +23 -0
  232. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  233. data/sig/ratatui_ruby/labs.rbs +90 -0
  234. data/sig/ratatui_ruby/layout/alignment.rbs +26 -0
  235. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  236. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  237. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  238. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  239. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  240. data/sig/ratatui_ruby/list_state.rbs +23 -0
  241. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  242. data/sig/ratatui_ruby/ratatui_ruby.rbs +113 -0
  243. data/sig/ratatui_ruby/rect.rbs +17 -0
  244. data/sig/ratatui_ruby/scrollbar_state.rbs +24 -0
  245. data/sig/ratatui_ruby/session.rbs +93 -0
  246. data/sig/ratatui_ruby/style/color.rbs +22 -0
  247. data/sig/ratatui_ruby/style/style.rbs +29 -0
  248. data/sig/ratatui_ruby/symbols.rbs +141 -0
  249. data/sig/ratatui_ruby/synthetic_events.rbs +24 -0
  250. data/sig/ratatui_ruby/table_state.rbs +27 -0
  251. data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
  252. data/sig/ratatui_ruby/terminal/viewport.rbs +33 -0
  253. data/sig/ratatui_ruby/terminal_lifecycle.rbs +39 -0
  254. data/sig/ratatui_ruby/test_helper/event_injection.rbs +22 -0
  255. data/sig/ratatui_ruby/test_helper/snapshot.rbs +37 -0
  256. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +77 -0
  257. data/sig/ratatui_ruby/test_helper/terminal.rbs +20 -0
  258. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +32 -0
  259. data/sig/ratatui_ruby/test_helper.rbs +18 -0
  260. data/sig/ratatui_ruby/text/line.rbs +27 -0
  261. data/sig/ratatui_ruby/text/span.rbs +23 -0
  262. data/sig/ratatui_ruby/text.rbs +12 -0
  263. data/sig/ratatui_ruby/tui/buffer_factories.rbs +16 -0
  264. data/sig/ratatui_ruby/tui/canvas_factories.rbs +38 -0
  265. data/sig/ratatui_ruby/tui/core.rbs +23 -0
  266. data/sig/ratatui_ruby/tui/layout_factories.rbs +39 -0
  267. data/sig/ratatui_ruby/tui/state_factories.rbs +23 -0
  268. data/sig/ratatui_ruby/tui/style_factories.rbs +18 -0
  269. data/sig/ratatui_ruby/tui/text_factories.rbs +23 -0
  270. data/sig/ratatui_ruby/tui/widget_factories.rbs +138 -0
  271. data/sig/ratatui_ruby/tui.rbs +25 -0
  272. data/sig/ratatui_ruby/version.rbs +12 -0
  273. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  274. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  275. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  276. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  277. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  278. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  279. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  280. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  281. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  282. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  283. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  284. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  285. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  286. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  287. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  288. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  289. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  290. data/sig/ratatui_ruby/widgets.rbs +16 -0
  291. data/vendor/goodcop/base.yml +1047 -0
  292. metadata +729 -0
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ module RatatuiRuby
9
+ module Widgets
10
+ # Displays categorical data as bars.
11
+ #
12
+ # Raw tables of numbers are hard to scan. Comparing magnitudes requires mental arithmetic, which slows down decision-making.
13
+ #
14
+ # This widget visualizes the data. It renders vertical bars proportional to their value.
15
+ #
16
+ # Use it to compare server loads, sales figures, or any discrete datasets.
17
+ #
18
+ # {rdoc-image:/doc/images/widget_barchart.png}[link:/examples/widget_barchart/app_rb.html]
19
+ #
20
+ # === Example
21
+ #
22
+ # Run the interactive demo from the terminal:
23
+ #
24
+ #--
25
+ # SPDX-SnippetBegin
26
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
27
+ # SPDX-License-Identifier: MIT-0
28
+ #++
29
+ # ruby examples/widget_barchart/app.rb
30
+ #
31
+ # # Grouped Bar Chart
32
+ # BarChart.new(
33
+ # data: [
34
+ # BarGroup.new(label: "Q1", bars: [Bar.new(value: 40), Bar.new(value: 45)]),
35
+ # BarGroup.new(label: "Q2", bars: [Bar.new(value: 50), Bar.new(value: 55)])
36
+ # ],
37
+ # bar_width: 5,
38
+ # group_gap: 3
39
+ # )
40
+ #--
41
+ # SPDX-SnippetEnd
42
+ #++
43
+ class BarChart < Data.define(:data, :bar_width, :bar_gap, :group_gap, :max, :style, :block, :direction, :label_style, :value_style, :bar_set)
44
+ include CoerceableWidget
45
+
46
+ ##
47
+ ##
48
+ ##
49
+ ##
50
+ # :attr_reader: data
51
+ # The data to display.
52
+ #
53
+ # Supports multiple formats:
54
+ # [<tt>Hash</tt>]
55
+ # Mapping labels (<tt>String</tt> or <tt>Symbol</tt>) to values (<tt>Integer</tt>).
56
+ # [<tt>Array</tt> of tuples]
57
+ # Ordered list of <tt>["Label", Value]</tt> or <tt>["Label", Value, Style]</tt> pairs.
58
+ # [<tt>Array</tt> of <tt>BarChart::BarGroup</tt>]
59
+ #--
60
+ # SPDX-SnippetBegin
61
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
62
+ # SPDX-License-Identifier: MIT-0
63
+ #++
64
+ # List of <tt>BarChart::BarGroup</tt> objects for grouped charts.
65
+ #
66
+ #--
67
+ # SPDX-SnippetEnd
68
+ #++
69
+ # === Examples
70
+ #
71
+ # Hash (Simple):
72
+ #--
73
+ # SPDX-SnippetBegin
74
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
75
+ # SPDX-License-Identifier: MIT-0
76
+ #++
77
+ # { "Apples" => 10, :Oranges => 15 }
78
+ #
79
+ #--
80
+ # SPDX-SnippetEnd
81
+ #++
82
+ # Array of Tuples (Ordered):
83
+ #--
84
+ # SPDX-SnippetBegin
85
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
86
+ # SPDX-License-Identifier: MIT-0
87
+ #++
88
+ # [["Mon", 20], ["Tue", 30], ["Wed", 25]]
89
+ #
90
+ #--
91
+ # SPDX-SnippetEnd
92
+ #++
93
+ # BarGroup (Grouped):
94
+ #--
95
+ # SPDX-SnippetBegin
96
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
97
+ # SPDX-License-Identifier: MIT-0
98
+ #++
99
+ # [
100
+ # RatatuiRuby::BarChart::BarGroup.new(label: "Q1", bars: [
101
+ # RatatuiRuby::BarChart::Bar.new(value: 50, label: "Rev"),
102
+ # RatatuiRuby::BarChart::Bar.new(value: 30, label: "Cost")
103
+ # ])
104
+ # ]
105
+ #--
106
+ # SPDX-SnippetEnd
107
+ #++
108
+
109
+ ##
110
+ # :attr_reader: bar_width
111
+ # Width of each bar in characters.
112
+
113
+ ##
114
+ # :attr_reader: bar_gap
115
+ # Spaces between bars.
116
+
117
+ ##
118
+ # :attr_reader: group_gap
119
+ # Spaces between groups (for grouped bar charts).
120
+
121
+ ##
122
+ # :attr_reader: max
123
+ # Maximum value for the Y-axis (optional).
124
+ #
125
+ # If nil, it is calculated from the data.
126
+
127
+ ##
128
+ # :attr_reader: style
129
+ # Style for the bars.
130
+
131
+ ##
132
+ # :attr_reader: block
133
+ # Optional wrapping block.
134
+
135
+ ##
136
+ # :attr_reader: label_style
137
+ # Style for the bar labels (optional).
138
+
139
+ ##
140
+ # :attr_reader: value_style
141
+ # Style for the bar values (optional).
142
+
143
+ ##
144
+ # :attr_reader: bar_set
145
+ # Custom characters for the bars (optional).
146
+ #
147
+ # A Hash with keys defining the characters for the bars.
148
+ # Keys: <tt>:empty</tt>, <tt>:one_eighth</tt>, <tt>:one_quarter</tt>, <tt>:three_eighths</tt>, <tt>:half</tt>, <tt>:five_eighths</tt>, <tt>:three_quarters</tt>, <tt>:seven_eighths</tt>, <tt>:full</tt>.
149
+ #
150
+ # You can also use integers (0-8) as keys, where 0 is empty, 4 is half, and 8 is full.
151
+ #
152
+ # Alternatively, you can pass an Array of 9 strings, where index 0 is empty and index 8 is full.
153
+ #
154
+ # === Examples
155
+ #
156
+ #--
157
+ # SPDX-SnippetBegin
158
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
159
+ # SPDX-License-Identifier: MIT-0
160
+ #++
161
+ # bar_set: {
162
+ # empty: " ",
163
+ # one_eighth: " ",
164
+ # one_quarter: "▂",
165
+ # three_eighths: "▃",
166
+ # half: "▄",
167
+ # five_eighths: "▅",
168
+ # three_quarters: "▆",
169
+ # seven_eighths: "▇",
170
+ # full: "█"
171
+ # }
172
+ #
173
+ # # Numeric keys (0-8)
174
+ # bar_set: {
175
+ # 0 => " ", 1 => " ", 2 => "▂", 3 => "▃", 4 => "▄", 5 => "▅", 6 => "▆", 7 => "▇", 8 => "█"
176
+ # }
177
+ #
178
+ # # Array (9 items)
179
+ # bar_set: [" ", " ", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
180
+ #--
181
+ # SPDX-SnippetEnd
182
+ #++
183
+
184
+ BAR_KEYS = %i[empty one_eighth one_quarter three_eighths half five_eighths three_quarters seven_eighths full].freeze
185
+
186
+ # Creates a new BarChart widget.
187
+ #
188
+ # [data]
189
+ # Data to display. Hash, Array of arrays, or Array of BarGroup.
190
+ # [bar_width]
191
+ # Width of each bar (Integer).
192
+ # [bar_gap]
193
+ # Gap between bars (Integer).
194
+ # [group_gap]
195
+ # Gap between groups (Integer).
196
+ # [max]
197
+ # Maximum value of the bar chart (Integer).
198
+ # [style]
199
+ # Base style for the widget (Style).
200
+ # [block]
201
+ # Block to render around the chart (Block).
202
+ # [direction]
203
+ # Direction of the bars (:vertical or :horizontal).
204
+ # [label_style]
205
+ # Style object for labels (optional).
206
+ # [value_style]
207
+ # Style object for values (optional).
208
+ # [bar_set]
209
+ #--
210
+ # SPDX-SnippetBegin
211
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
212
+ # SPDX-License-Identifier: MIT-0
213
+ #++
214
+ # Symbol, Hash, or Array: Custom characters for bars.
215
+ # Symbols: <tt>:nine_levels</tt> (default gradient), <tt>:three_levels</tt> (simplified).
216
+ #--
217
+ # SPDX-SnippetEnd
218
+ #++
219
+ def initialize(data:, bar_width: 3, bar_gap: 1, group_gap: 0, max: nil, style: nil, block: nil, direction: :vertical, label_style: nil, value_style: nil, bar_set: nil)
220
+ # Normalize bar_set to Hash[Symbol, String] if provided as Array or Hash
221
+ bar_set = case bar_set
222
+ when Symbol, nil
223
+ bar_set
224
+ when Array
225
+ # Convert Array to Hash using BAR_KEYS order
226
+ BAR_KEYS.zip(bar_set).to_h
227
+ when Hash
228
+ # @type var raw_hash: Hash[untyped, untyped]
229
+ raw_hash = bar_set.dup
230
+ normalized = {} #: Hash[Symbol, String]
231
+ # Normalize numeric keys (0-8) to symbolic keys
232
+ BAR_KEYS.each_with_index do |key, i|
233
+ val = raw_hash.delete(i) || raw_hash.delete(i.to_s) || raw_hash.delete(key)
234
+ normalized[key] = val.to_s if val
235
+ end
236
+ normalized
237
+ else
238
+ bar_set
239
+ end
240
+
241
+ # Normalize data to Array of BarGroup
242
+ data = if data.is_a?(Hash)
243
+ if direction == :horizontal
244
+ bars = data.map do |label, value|
245
+ Bar.new(value:, label: label.to_s)
246
+ end
247
+ [BarGroup.new(label: "", bars:)]
248
+ else
249
+ data.map do |label, value|
250
+ BarGroup.new(label: label.to_s, bars: [Bar.new(value:)])
251
+ end
252
+ end
253
+ elsif data.is_a?(Array)
254
+ if data.empty?
255
+ []
256
+ elsif data.first.is_a?(BarGroup)
257
+ data
258
+ elsif data.first.is_a?(Array)
259
+ # Tuples - use type assertion for Steep
260
+ if direction == :horizontal
261
+ bars = data.map do |item|
262
+ tuple = item #: Array[untyped]
263
+ label = tuple[0].to_s
264
+ value = tuple[1]
265
+ style = tuple[2]
266
+
267
+ bar = Bar.new(value:, label:)
268
+ bar = bar.with(style:) if style
269
+ bar
270
+ end
271
+ [BarGroup.new(label: "", bars:)]
272
+ else
273
+ data.map do |item|
274
+ tuple = item #: Array[untyped]
275
+ label = tuple[0].to_s
276
+ value = tuple[1]
277
+ style = tuple[2]
278
+
279
+ bar = Bar.new(value:)
280
+ bar = bar.with(style:) if style
281
+ BarGroup.new(label:, bars: [bar])
282
+ end
283
+ end
284
+ else
285
+ # Fallback
286
+ data
287
+ end
288
+ else
289
+ data
290
+ end
291
+
292
+ super(
293
+ data:,
294
+ bar_width: Integer(bar_width),
295
+ bar_gap: Integer(bar_gap),
296
+ group_gap: Integer(group_gap),
297
+ max: max.nil? ? nil : Integer(max),
298
+ style:,
299
+ block:,
300
+ direction:,
301
+ label_style:,
302
+ value_style:,
303
+ bar_set:
304
+ )
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ module RatatuiRuby
9
+ module Widgets
10
+ # Defines the visual container for a widget.
11
+ #
12
+ # Widgets often float in void. Without boundaries, interfaces become a chaotic mess of text. Users need structure to parse information.
13
+ #
14
+ # This widget creates that structure. It wraps content in borders. It labels sections with titles. It paints the background.
15
+ #
16
+ # Use blocks to define distinct areas. Group related information. Create a visual hierarchy that guides the user's eye.
17
+ #
18
+ # {rdoc-image:/doc/images/widget_box.png}[link:/examples/widget_box/app_rb.html]
19
+ #
20
+ # === Example
21
+ #
22
+ # Run the interactive demo from the terminal:
23
+ #
24
+ # ruby examples/widget_box/app.rb
25
+ class Block < Data.define(:title, :titles, :title_alignment, :title_style, :borders, :border_style, :border_type, :border_set, :style, :padding, :children)
26
+ include CoerceableWidget
27
+
28
+ ##
29
+ # :attr_reader: title
30
+ # The main title displayed on the top border.
31
+ #
32
+ # === Example
33
+ #
34
+ # Block.new(title: "Main").title # => "Main"
35
+
36
+ ##
37
+ # :attr_reader: titles
38
+ # Additional titles for complex labeling.
39
+ #
40
+ # Each title can be a <tt>String</tt> or a <tt>Hash</tt> with keys <tt>:content</tt>, <tt>:alignment</tt>, <tt>:position</tt> (<tt>:top</tt> or <tt>:bottom</tt>), and <tt>:style</tt>.
41
+ #
42
+ # === Example
43
+ #
44
+ # Block.new(titles: ["Top", { content: "Bottom", position: :bottom }]).titles
45
+
46
+ ##
47
+ # :attr_reader: title_alignment
48
+ # Alignment of the main title.
49
+ #
50
+ # One of <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
51
+ #
52
+ # === Example
53
+ #
54
+ # Block.new(title_alignment: :center).title_alignment # => :center
55
+
56
+ ##
57
+ # :attr_reader: title_style
58
+ # Style applied to all titles if not overridden.
59
+ #
60
+ # === Example
61
+ #
62
+ # Block.new(title_style: Style.new(fg: :red)).title_style
63
+
64
+ ##
65
+ # :attr_reader: borders
66
+ # Visible borders.
67
+ #
68
+ # An array containing any of <tt>:top</tt>, <tt>:bottom</tt>, <tt>:left</tt>, <tt>:right</tt>, or <tt>:all</tt>.
69
+ #
70
+ # === Example
71
+ #
72
+ # Block.new(borders: [:left, :right]).borders # => [:left, :right]
73
+
74
+ ##
75
+ # :attr_reader: border_style
76
+ # Full style (colors/modifiers) for the border lines.
77
+ #
78
+ # A Style object or Hash with <tt>:fg</tt>, <tt>:bg</tt>, and <tt>:modifiers</tt>.
79
+ # This allows borders to be bold, italic, colored, etc.
80
+
81
+ ##
82
+ # :attr_reader: border_type
83
+ # Visual style of the border lines.
84
+ #
85
+ # One of <tt>:plain</tt>, <tt>:rounded</tt>, <tt>:double</tt>, <tt>:thick</tt>, etc.
86
+
87
+ ##
88
+ # :attr_reader: border_set
89
+ # Custom characters for the border lines.
90
+ #
91
+ # A Hash with keys defining the characters for the borders.
92
+ # Keys: <tt>:top_left</tt>, <tt>:top_right</tt>, <tt>:bottom_left</tt>, <tt>:bottom_right</tt>,
93
+ # <tt>:vertical_left</tt>, <tt>:vertical_right</tt>, <tt>:horizontal_top</tt>, <tt>:horizontal_bottom</tt>.
94
+ #
95
+ # Providing this overrides <tt>border_type</tt>.
96
+ #
97
+ #
98
+ # === Example
99
+ #
100
+ # Block.new(border_set: { top_left: "1", top_right: "2", bottom_left: "3", bottom_right: "4", vertical_left: "5", vertical_right: "6", horizontal_top: "7", horizontal_bottom: "8" })
101
+
102
+ ##
103
+ # :attr_reader: style
104
+ # Base style (colors/modifiers) for the block content.
105
+
106
+ ##
107
+ # :attr_reader: padding
108
+ # Inner padding.
109
+ #
110
+ # Can be a single <tt>Integer</tt> (uniform) or a 4-element <tt>Array</tt> (left, right, top, bottom).
111
+ #
112
+ # === Example
113
+ #
114
+ #--
115
+ # SPDX-SnippetBegin
116
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
117
+ # SPDX-License-Identifier: MIT-0
118
+ #++
119
+ # Block.new(padding: 2).padding # => 2
120
+ # Block.new(padding: [1, 1, 0, 0]).padding # => [1, 1, 0, 0]
121
+ #--
122
+ # SPDX-SnippetEnd
123
+ #++
124
+
125
+ ##
126
+ # :attr_reader: children
127
+ # Widgets to render inside the block (optional).
128
+ #
129
+ # When provided, each child widget is rendered within the block's area.
130
+ #
131
+ # === Example
132
+ #
133
+ #--
134
+ # SPDX-SnippetBegin
135
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
136
+ # SPDX-License-Identifier: MIT-0
137
+ #++
138
+ # Block.new(
139
+ # title: "Content",
140
+ # borders: [:all],
141
+ # children: [Paragraph.new(text: "Hello")]
142
+ # )
143
+ #--
144
+ # SPDX-SnippetEnd
145
+ #++
146
+
147
+ # Creates a new Block.
148
+ #
149
+ # [title]
150
+ # Main title string (optional).
151
+ # [titles]
152
+ # Array of additional titles (optional).
153
+ # [title_alignment]
154
+ # Alignment symbol: <tt>:left</tt> (default), <tt>:center</tt>, <tt>:right</tt>.
155
+ # [title_style]
156
+ # Base style for all titles (optional).
157
+ # [borders]
158
+ # Array of borders to show: <tt>:top</tt>, <tt>:bottom</tt>, <tt>:left</tt>, <tt>:right</tt>, or <tt>:all</tt> (default).
159
+ # [border_style]
160
+ # Style object or Hash for the border lines.
161
+ # [border_type]
162
+ # Symbol: <tt>:plain</tt> (default), <tt>:rounded</tt>, <tt>:double</tt>, <tt>:thick</tt>, <tt>:hidden</tt>, <tt>:quadrant_inside</tt>, <tt>:quadrant_outside</tt>.
163
+ # [border_set]
164
+ # Hash: Custom characters for the border lines. Unique characters are interned (leaked) permanently, so avoid infinite dynamic variations.
165
+ # [style]
166
+ # Style object or Hash for the block's content area.
167
+ # [padding]
168
+ # Integer (uniform) or Array[4] (left, right, top, bottom).
169
+ # [children]
170
+ # Array of widgets to render inside the block (optional).
171
+ def initialize(title: nil, titles: [], title_alignment: nil, title_style: nil, borders: [:all], border_style: nil, border_type: nil, border_set: nil, style: nil, padding: 0, children: [])
172
+ if border_set
173
+ border_set = border_set.dup
174
+ %i[top_left top_right bottom_left bottom_right vertical_left vertical_right horizontal_top horizontal_bottom].each do |long_key|
175
+ short_key = long_key.to_s.split("_").map { |s| s[0] }.join.to_sym
176
+ if (val = border_set.delete(short_key))
177
+ border_set[long_key] = val
178
+ end
179
+ end
180
+ end
181
+ coerced_padding = if padding.is_a?(Array)
182
+ padding.map { |v| Integer(v) }
183
+ else
184
+ Integer(padding)
185
+ end
186
+ super(
187
+ title:,
188
+ titles:,
189
+ title_alignment:,
190
+ title_style:,
191
+ borders:,
192
+ border_style:,
193
+ border_type:,
194
+ border_set:,
195
+ style:,
196
+ padding: coerced_padding,
197
+ children:
198
+ )
199
+ end
200
+
201
+ # Computes the inner content area given an outer area.
202
+ #
203
+ # This method calculates where content should be placed within a block,
204
+ # accounting for borders and padding. Essential for layout calculations
205
+ # when you need to know the usable space inside a block.
206
+ #
207
+ # === Example
208
+ #
209
+ #--
210
+ # SPDX-SnippetBegin
211
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
212
+ # SPDX-License-Identifier: MIT-0
213
+ #++
214
+ # block = Block.new(borders: [:all], padding: 1)
215
+ # outer = Layout::Rect.new(x: 0, y: 0, width: 20, height: 10)
216
+ # inner = block.inner(outer)
217
+ # # => Rect(x: 2, y: 2, width: 16, height: 6)
218
+ #
219
+ #--
220
+ # SPDX-SnippetEnd
221
+ #++
222
+ # [area]
223
+ #--
224
+ # SPDX-SnippetBegin
225
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
226
+ # SPDX-License-Identifier: MIT-0
227
+ #++
228
+ # The outer Rect to compute the inner area for.
229
+ #
230
+ #--
231
+ # SPDX-SnippetEnd
232
+ #++
233
+ # Returns a new Rect representing the inner content area.
234
+ def inner(area)
235
+ # Calculate border offsets
236
+ has_border = -> (side) { borders.include?(:all) || borders.include?(side) }
237
+ left_border = has_border.call(:left) ? 1 : 0
238
+ right_border = has_border.call(:right) ? 1 : 0
239
+ top_border = has_border.call(:top) ? 1 : 0
240
+ bottom_border = has_border.call(:bottom) ? 1 : 0
241
+
242
+ # Calculate padding offsets - ensure all are Integer
243
+ pad_left, pad_right, pad_top, pad_bottom = if padding.is_a?(Array)
244
+ # [left, right, top, bottom]
245
+ [
246
+ Integer(padding[0] || 0),
247
+ Integer(padding[1] || 0),
248
+ Integer(padding[2] || 0),
249
+ Integer(padding[3] || 0),
250
+ ]
251
+ else
252
+ p = Integer(padding)
253
+ [p, p, p, p]
254
+ end
255
+
256
+ # Compute inner area
257
+ new_x = area.x + left_border + pad_left
258
+ new_y = area.y + top_border + pad_top
259
+ new_width = [area.width - left_border - right_border - pad_left - pad_right, 0].max
260
+ new_height = [area.height - top_border - bottom_border - pad_top - pad_bottom, 0].max
261
+
262
+ Layout::Rect.new(x: new_x, y: new_y, width: new_width, height: new_height)
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ module RatatuiRuby
9
+ module Widgets
10
+ # Displays a monthly calendar grid.
11
+ #
12
+ # Dates are complex. Rendering them in a grid requires calculation of leap years, month lengths, and day-of-week offsets.
13
+ # Use this widget to skip the boilerplate.
14
+ #
15
+ # This widget renders a standard monthly view. It highlights the current date. It structures time.
16
+ #
17
+ # Use it for date pickers, schedulers, or logs.
18
+ #
19
+ # {rdoc-image:/doc/images/widget_calendar.png}[link:/examples/widget_calendar/app_rb.html]
20
+ #
21
+ # === Example
22
+ #
23
+ # Run the interactive demo from the terminal:
24
+ #
25
+ # ruby examples/widget_calendar/app.rb
26
+ class Calendar < Data.define(:year, :month, :events, :default_style, :header_style, :block, :show_weekdays_header, :show_surrounding, :show_month_header)
27
+ include CoerceableWidget
28
+
29
+ ##
30
+ # :attr_reader: year
31
+ # The year to display (Integer).
32
+
33
+ ##
34
+ # :attr_reader: month
35
+ # The month to display (1–12).
36
+
37
+ ##
38
+ # :attr_reader: events
39
+ # A Hash mapping Dates to Styles for event highlighting.
40
+ # Keys must be `Date` objects (or objects responding to `day`, `month`, `year`).
41
+ # Values must be `Style` objects.
42
+
43
+ ##
44
+ # :attr_reader: default_style
45
+ # Style for the days.
46
+
47
+ ##
48
+ # :attr_reader: header_style
49
+ # Style for the month name header.
50
+
51
+ ##
52
+ # :attr_reader: block
53
+ # Optional wrapping block.
54
+
55
+ ##
56
+ # :attr_reader: show_weekdays_header
57
+ # Whether to show the weekday header (Mon, Tue, etc.).
58
+
59
+ ##
60
+ # :attr_reader: show_surrounding
61
+ # Style for dates from surrounding months. If <tt>nil</tt>, surrounding dates are hidden.
62
+
63
+ # Creates a new Calendar.
64
+ #
65
+ # [year] Integer.
66
+ # [month] Integer.
67
+ # [events] Hash<Date, Style>. Optional.
68
+ # [default_style] Style.
69
+ # [header_style] Style.
70
+ # [block] Block.
71
+ # [show_weekdays_header] Boolean. Whether to show the weekday header.
72
+ # [show_surrounding] <tt>Style</tt> or <tt>nil</tt>. Style for surrounding month dates.
73
+ def initialize(year:, month:, events: {}, default_style: nil, header_style: nil, block: nil, show_weekdays_header: true, show_surrounding: nil, show_month_header: false)
74
+ super(
75
+ year: Integer(year),
76
+ month: Integer(month),
77
+ events:,
78
+ default_style:,
79
+ header_style:,
80
+ block:,
81
+ show_weekdays_header:,
82
+ show_surrounding:,
83
+ show_month_header:
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end