ratatui_ruby 0.5.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 (234) 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 +6 -0
  7. data/CHANGELOG.md +44 -7
  8. data/README.md +11 -4
  9. data/REUSE.toml +2 -7
  10. data/doc/application_architecture.md +84 -10
  11. data/doc/application_testing.md +75 -29
  12. data/doc/contributors/design/ruby_frontend.md +39 -3
  13. data/doc/contributors/design/rust_backend.md +1 -0
  14. data/doc/contributors/developing_examples.md +129 -44
  15. data/doc/contributors/examples_audit/p1_high.md +21 -0
  16. data/doc/contributors/examples_audit/p2_moderate.md +81 -0
  17. data/doc/contributors/examples_audit.md +41 -0
  18. data/doc/event_handling.md +11 -3
  19. data/doc/images/app_all_events.png +0 -0
  20. data/doc/images/app_color_picker.png +0 -0
  21. data/doc/images/app_login_form.png +0 -0
  22. data/doc/images/app_stateful_interaction.png +0 -0
  23. data/doc/images/verify_quickstart_dsl.png +0 -0
  24. data/doc/images/verify_quickstart_layout.png +0 -0
  25. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  26. data/doc/images/verify_readme_usage.png +0 -0
  27. data/doc/images/widget_barchart_demo.png +0 -0
  28. data/doc/images/widget_block_demo.png +0 -0
  29. data/doc/images/widget_canvas_demo.png +0 -0
  30. data/doc/images/widget_cell_demo.png +0 -0
  31. data/doc/images/widget_center_demo.png +0 -0
  32. data/doc/images/widget_chart_demo.png +0 -0
  33. data/doc/images/widget_list_demo.png +0 -0
  34. data/doc/images/widget_overlay_demo.png +0 -0
  35. data/doc/images/widget_render.png +0 -0
  36. data/doc/images/widget_rich_text.png +0 -0
  37. data/doc/images/widget_scroll_text.png +0 -0
  38. data/doc/images/widget_sparkline_demo.png +0 -0
  39. data/doc/images/widget_table_demo.png +0 -0
  40. data/doc/images/widget_tabs_demo.png +0 -0
  41. data/doc/images/widget_text_width.png +0 -0
  42. data/doc/quickstart.md +69 -76
  43. data/doc/terminal_limitations.md +92 -0
  44. data/examples/app_all_events/README.md +45 -27
  45. data/examples/app_all_events/app.rb +38 -35
  46. data/examples/app_all_events/model/app_model.rb +157 -0
  47. data/examples/app_all_events/model/event_entry.rb +17 -0
  48. data/examples/app_all_events/model/msg.rb +37 -0
  49. data/examples/app_all_events/update.rb +73 -0
  50. data/examples/app_all_events/view/app_view.rb +8 -8
  51. data/examples/app_all_events/view/controls_view.rb +8 -6
  52. data/examples/app_all_events/view/counts_view.rb +12 -8
  53. data/examples/app_all_events/view/live_view.rb +8 -7
  54. data/examples/app_all_events/view/log_view.rb +10 -15
  55. data/examples/app_color_picker/README.md +84 -44
  56. data/examples/app_color_picker/app.rb +24 -62
  57. data/examples/app_color_picker/controls.rb +90 -0
  58. data/examples/app_color_picker/copy_dialog.rb +45 -49
  59. data/examples/app_color_picker/export_pane.rb +126 -0
  60. data/examples/app_color_picker/input.rb +99 -67
  61. data/examples/app_color_picker/main_container.rb +178 -0
  62. data/examples/app_color_picker/palette.rb +55 -26
  63. data/examples/app_login_form/README.md +47 -0
  64. data/examples/app_login_form/app.rb +2 -3
  65. data/examples/app_stateful_interaction/README.md +31 -0
  66. data/examples/app_stateful_interaction/app.rb +272 -0
  67. data/examples/timeout_demo.rb +43 -0
  68. data/examples/verify_quickstart_dsl/README.md +48 -0
  69. data/examples/verify_quickstart_dsl/app.rb +2 -0
  70. data/examples/verify_quickstart_layout/README.md +71 -0
  71. data/examples/verify_quickstart_layout/app.rb +2 -0
  72. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  73. data/examples/verify_quickstart_lifecycle/app.rb +8 -2
  74. data/examples/verify_readme_usage/README.md +43 -0
  75. data/examples/verify_readme_usage/app.rb +8 -2
  76. data/examples/widget_barchart_demo/README.md +49 -0
  77. data/examples/widget_barchart_demo/app.rb +5 -5
  78. data/examples/widget_block_demo/README.md +34 -0
  79. data/examples/widget_block_demo/app.rb +256 -0
  80. data/examples/widget_box_demo/README.md +45 -0
  81. data/examples/widget_calendar_demo/README.md +39 -0
  82. data/examples/widget_canvas_demo/README.md +27 -0
  83. data/examples/widget_canvas_demo/app.rb +123 -0
  84. data/examples/widget_cell_demo/README.md +36 -0
  85. data/examples/widget_cell_demo/app.rb +31 -24
  86. data/examples/widget_center_demo/README.md +29 -0
  87. data/examples/widget_center_demo/app.rb +116 -0
  88. data/examples/widget_chart_demo/README.md +41 -0
  89. data/examples/widget_chart_demo/app.rb +7 -2
  90. data/examples/widget_gauge_demo/README.md +41 -0
  91. data/examples/widget_layout_split/README.md +44 -0
  92. data/examples/widget_line_gauge_demo/README.md +41 -0
  93. data/examples/widget_list_demo/README.md +49 -0
  94. data/examples/widget_list_demo/app.rb +91 -107
  95. data/examples/widget_map_demo/README.md +39 -0
  96. data/examples/{app_map_demo → widget_map_demo}/app.rb +2 -2
  97. data/examples/widget_overlay_demo/app.rb +248 -0
  98. data/examples/widget_popup_demo/README.md +36 -0
  99. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  100. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  101. data/examples/widget_rect/README.md +38 -0
  102. data/examples/widget_render/README.md +37 -0
  103. data/examples/widget_rich_text/README.md +35 -0
  104. data/examples/widget_rich_text/app.rb +62 -33
  105. data/examples/widget_scroll_text/README.md +37 -0
  106. data/examples/widget_scroll_text/app.rb +0 -1
  107. data/examples/widget_scrollbar_demo/README.md +37 -0
  108. data/examples/widget_sparkline_demo/README.md +42 -0
  109. data/examples/widget_sparkline_demo/app.rb +4 -3
  110. data/examples/widget_style_colors/README.md +34 -0
  111. data/examples/widget_table_demo/README.md +48 -0
  112. data/examples/{app_table_select → widget_table_demo}/app.rb +46 -8
  113. data/examples/widget_tabs_demo/README.md +41 -0
  114. data/examples/widget_tabs_demo/app.rb +15 -1
  115. data/examples/widget_text_width/README.md +35 -0
  116. data/examples/widget_text_width/app.rb +106 -0
  117. data/exe/.gitkeep +0 -0
  118. data/ext/ratatui_ruby/Cargo.lock +11 -4
  119. data/ext/ratatui_ruby/Cargo.toml +2 -1
  120. data/ext/ratatui_ruby/src/events.rs +238 -26
  121. data/ext/ratatui_ruby/src/frame.rs +113 -1
  122. data/ext/ratatui_ruby/src/lib.rs +34 -4
  123. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  124. data/ext/ratatui_ruby/src/terminal.rs +39 -15
  125. data/ext/ratatui_ruby/src/text.rs +1 -1
  126. data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
  127. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  128. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  129. data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
  130. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  131. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  132. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
  133. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  134. data/ext/ratatui_ruby/src/widgets/table.rs +113 -1
  135. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  136. data/lib/ratatui_ruby/cell.rb +4 -4
  137. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  138. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  139. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  140. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  141. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  142. data/lib/ratatui_ruby/event/key.rb +111 -51
  143. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  144. data/lib/ratatui_ruby/event/paste.rb +1 -1
  145. data/lib/ratatui_ruby/frame.rb +96 -0
  146. data/lib/ratatui_ruby/list_state.rb +88 -0
  147. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  148. data/lib/ratatui_ruby/schema/cursor.rb +5 -0
  149. data/lib/ratatui_ruby/schema/gauge.rb +3 -1
  150. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  151. data/lib/ratatui_ruby/schema/list.rb +25 -4
  152. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  153. data/lib/ratatui_ruby/schema/rect.rb +43 -0
  154. data/lib/ratatui_ruby/schema/style.rb +24 -4
  155. data/lib/ratatui_ruby/schema/table.rb +21 -3
  156. data/lib/ratatui_ruby/schema/text.rb +69 -1
  157. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  158. data/lib/ratatui_ruby/session/autodoc.rb +65 -0
  159. data/lib/ratatui_ruby/session.rb +22 -7
  160. data/lib/ratatui_ruby/table_state.rb +90 -0
  161. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  162. data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
  163. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  164. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  165. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  166. data/lib/ratatui_ruby/test_helper.rb +65 -358
  167. data/lib/ratatui_ruby/version.rb +1 -1
  168. data/lib/ratatui_ruby.rb +42 -19
  169. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  170. data/sig/examples/widget_block_demo/app.rbs +32 -0
  171. data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
  172. data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
  173. data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
  174. data/sig/ratatui_ruby/event.rbs +11 -1
  175. data/sig/ratatui_ruby/frame.rbs +2 -0
  176. data/sig/ratatui_ruby/list_state.rbs +13 -0
  177. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
  178. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  179. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  180. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  181. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  182. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  183. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  184. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  185. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  186. data/sig/ratatui_ruby/schema/text.rbs +8 -6
  187. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  188. data/sig/ratatui_ruby/session.rbs +13 -0
  189. data/sig/ratatui_ruby/table_state.rbs +15 -0
  190. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  191. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  192. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  193. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  194. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  195. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  196. data/tasks/autodoc/examples.rb +79 -0
  197. data/tasks/autodoc/inventory.rb +9 -7
  198. data/tasks/autodoc.rake +11 -5
  199. data/tasks/bump/changelog.rb +3 -3
  200. data/tasks/bump/links.rb +67 -0
  201. data/tasks/sourcehut.rake +61 -21
  202. data/tasks/terminal_preview/app_screenshot.rb +13 -3
  203. data/tasks/terminal_preview/saved_screenshot.rb +4 -3
  204. metadata +111 -37
  205. data/doc/images/app_table_select.png +0 -0
  206. data/doc/images/widget_block_padding.png +0 -0
  207. data/doc/images/widget_block_titles.png +0 -0
  208. data/doc/images/widget_list_styles.png +0 -0
  209. data/examples/app_all_events/model/events.rb +0 -180
  210. data/examples/app_all_events/model/highlight.rb +0 -57
  211. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
  212. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
  213. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
  214. data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
  215. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
  216. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
  217. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
  218. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
  219. data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
  220. data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
  221. data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
  222. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
  223. data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
  224. data/examples/app_all_events/view_state.rb +0 -42
  225. data/examples/app_color_picker/scene.rb +0 -201
  226. data/examples/widget_block_padding/app.rb +0 -67
  227. data/examples/widget_block_titles/app.rb +0 -69
  228. data/examples/widget_list_styles/app.rb +0 -141
  229. data/examples/widget_table_flex/app.rb +0 -95
  230. data/sig/examples/widget_block_padding/app.rbs +0 -11
  231. data/sig/examples/widget_block_titles/app.rbs +0 -11
  232. data/sig/examples/widget_list_styles/app.rbs +0 -11
  233. data/tasks/bump/comparison_links.rb +0 -41
  234. /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
@@ -0,0 +1,29 @@
1
+ # Center Widget Demo
2
+ <!--
3
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
+ -->
6
+
7
+ This example demonstrates the `Center` widget, which positions a child widget in the center of the available area.
8
+
9
+ ## Key Concepts
10
+
11
+ - **Centering**: The widget automatically calculates the necessary padding to center its child.
12
+ - **Sizing**: You can control the size of the centered area using `width_percent` and `height_percent`.
13
+ - **Composition**: The `Center` widget wraps another widget (the child), making it easy to compose layouts.
14
+
15
+ ## Controls
16
+
17
+ | Key | Action |
18
+ | --- | --- |
19
+ | `←` / `→` | Decrease / Increase width percentage |
20
+ | `↑` / `↓` | Increase / Decrease height percentage |
21
+ | `q` | Quit |
22
+
23
+ ## Screenshot
24
+
25
+ ![Screenshot of Center Widget Demo](../../doc/images/widget_center_demo.png)
26
+
27
+ ## Source Code
28
+
29
+ - [app.rb](app.rb)
@@ -0,0 +1,116 @@
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
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+ require "ratatui_ruby"
8
+
9
+ # Demo: Center Widget
10
+ # Demonstrates how to center content horizontally and vertically
11
+ # with adjustable width/height percentages.
12
+ class WidgetCenterDemo
13
+ def initialize
14
+ @width_percent = 50
15
+ @height_percent = 50
16
+ end
17
+
18
+ def run
19
+ RatatuiRuby.run do |tui|
20
+ @tui = tui
21
+ loop do
22
+ render
23
+ break if handle_input == :quit
24
+ end
25
+ end
26
+ end
27
+
28
+ private def render
29
+ @tui.draw do |frame|
30
+ layout = @tui.layout_split(
31
+ frame.area,
32
+ direction: :vertical,
33
+ constraints: [
34
+ @tui.constraint_fill(1),
35
+ @tui.constraint_length(3),
36
+ ]
37
+ )
38
+
39
+ # 1. Main Area
40
+ # Background block frames the centered content
41
+ bg_block = @tui.block(
42
+ title: "Center Widget Demo",
43
+ borders: [:all],
44
+ style: @tui.style(fg: :gray)
45
+ )
46
+ frame.render_widget(bg_block, layout[0])
47
+
48
+ # 2. Centered Content
49
+ # The content itself is just a block with some text
50
+ content = @tui.paragraph(
51
+ text: [
52
+ @tui.text_line(
53
+ spans: [
54
+ @tui.text_span(content: "Centered Area", style: @tui.style(modifiers: [:bold])),
55
+ ],
56
+ alignment: :center
57
+ ),
58
+ @tui.text_line(spans: []),
59
+ @tui.text_line(spans: [@tui.text_span(content: "Width: #{@width_percent}%", style: @tui.style(fg: :cyan))], alignment: :center),
60
+ @tui.text_line(spans: [@tui.text_span(content: "Height: #{@height_percent}%", style: @tui.style(fg: :magenta))], alignment: :center),
61
+ ],
62
+ block: @tui.block(
63
+ title: "Child Widget",
64
+ borders: [:all],
65
+ style: @tui.style(fg: :white)
66
+ ),
67
+ alignment: :center
68
+ )
69
+
70
+ # Create the Center widget
71
+ center_widget = @tui.center(
72
+ child: content,
73
+ width_percent: @width_percent,
74
+ height_percent: @height_percent
75
+ )
76
+
77
+ # Render center widget into the main layout area
78
+ frame.render_widget(center_widget, layout[0])
79
+
80
+ # 3. Controls
81
+ control_text = @tui.paragraph(
82
+ text: [
83
+ @tui.text_line(spans: [
84
+ @tui.text_span(content: "←/→", style: @tui.style(modifiers: [:bold, :underlined])),
85
+ @tui.text_span(content: ": Width "),
86
+ @tui.text_span(content: "↑/↓", style: @tui.style(modifiers: [:bold, :underlined])),
87
+ @tui.text_span(content: ": Height "),
88
+ @tui.text_span(content: "q", style: @tui.style(modifiers: [:bold, :underlined])),
89
+ @tui.text_span(content: ": Quit"),
90
+ ]),
91
+ ],
92
+ block: @tui.block(borders: [:top], style: @tui.style(bg: :black))
93
+ )
94
+ frame.render_widget(control_text, layout[1])
95
+ end
96
+ end
97
+
98
+ def handle_input
99
+ case @tui.poll_event
100
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
101
+ :quit
102
+ in { type: :key, code: "left" }
103
+ @width_percent = [@width_percent - 5, 5].max
104
+ in { type: :key, code: "right" }
105
+ @width_percent = [@width_percent + 5, 100].min
106
+ in { type: :key, code: "up" }
107
+ @height_percent = [@height_percent + 5, 100].min
108
+ in { type: :key, code: "down" }
109
+ @height_percent = [@height_percent - 5, 5].max
110
+ else
111
+ # Ignore other events
112
+ end
113
+ end
114
+ end
115
+
116
+ WidgetCenterDemo.new.run if __FILE__ == $0
@@ -0,0 +1,41 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Chart Widget Example
7
+
8
+ Demonstrates Cartesian plotting with interactive styling and configuration.
9
+
10
+ Trends and patterns are invisible in raw logs. Charts visualize X/Y datasets to reveal the story behind the data.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Dataset Types**: Line charts and Scatter plots.
15
+ - **Markers**: Braille patterns, dots, blocks, and bars.
16
+ - **Axis Configuration**: Controlling labels, bounds, and alignment (Left/Center/Right).
17
+ - **Legend**: Positioning the legend in any of the four corners or hiding it based on constraints.
18
+
19
+ ## Hotkeys
20
+
21
+ - **m**: Cycle Marker Type (`marker`)
22
+ - **s**: Cycle Dataset Style (`style`)
23
+ - **x**: Cycle X-Axis Alignment (`labels_alignment`)
24
+ - **y**: Cycle Y-Axis Alignment (`labels_alignment`)
25
+ - **l**: Cycle Legend Position (`legend_position`)
26
+ - **q**: Quit
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ ruby examples/widget_chart_demo/app.rb
32
+ ```
33
+
34
+ ## Learning Outcomes
35
+
36
+ Use this example if you need to...
37
+ - Plot real-time data monitoring (CPU history, request latency).
38
+ - Visualize mathematical functions.
39
+ - Compare multiple datasets on the same axis.
40
+
41
+ ![Demo](/doc/images/widget_chart_demo.png)
@@ -53,6 +53,11 @@ class WidgetChartDemo
53
53
  @tui = tui
54
54
  init_styles
55
55
 
56
+ # Support seeded random for deterministic testing
57
+ # Set RATA_SEED=42 for reproducible scatter plot data
58
+ seed = ENV.fetch("RATA_SEED", nil)
59
+ @rng = seed ? Random.new(seed.to_i) : Random.new
60
+
56
61
  @marker_index = 0
57
62
  @dataset_style_index = 0
58
63
  @x_alignment_index = 1
@@ -89,9 +94,9 @@ class WidgetChartDemo
89
94
  [x, Math.sin(x)]
90
95
  end
91
96
 
92
- # Scatter: Random points
97
+ # Scatter: Random points (deterministic when RATA_SEED is set)
93
98
  scatter_data = (0..20).map do |_|
94
- [rand(0.0..10.0), rand(-1.0..1.0)]
99
+ [@rng.rand(0.0..10.0), @rng.rand(-1.0..1.0)]
95
100
  end
96
101
 
97
102
  style = @dataset_styles[@dataset_style_index][:style]
@@ -0,0 +1,41 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Gauge Widget Example
7
+
8
+ Demonstrates progress bars with interactive configuration.
9
+
10
+ Long-running tasks create anxiety. Users need to know the system is working. Gauges provide visual feedback on completion status.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Progress styles**: standard block characters or Unicode bars.
15
+ - **Labels**: Customizing the text overlay (Percentage, Ratio, etc.).
16
+ - **Styling**: Independent control of the filled gauge color and the background track.
17
+ - **Thresholds**: Implementing multi-colored gauges based on values.
18
+
19
+ ## Hotkeys
20
+
21
+ - **Arrows (←/→)**: Adjust Ratio (`ratio`)
22
+ - **g**: Cycle Gauge Color (`gauge_style`)
23
+ - **b**: Cycle Background Style (`style`)
24
+ - **u**: Toggle Unicode Mode (`use_unicode`)
25
+ - **l**: Cycle Label Mode (`label`)
26
+ - **q**: Quit
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ ruby examples/widget_gauge_demo/app.rb
32
+ ```
33
+
34
+ ## Learning Outcomes
35
+
36
+ Use this example if you need to...
37
+ - Show download or upload progress.
38
+ - Visualize resource quotas (disk space, memory usage).
39
+ - Create "health bars" or status indicators.
40
+
41
+ ![Demo](/doc/images/widget_gauge_demo.png)
@@ -0,0 +1,44 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Layout Split Example
7
+
8
+ Demonstrates dynamic geometry management with constraints and flex modes.
9
+
10
+ Terminal screens vary in size. Hardcoded layouts break. `Layout.split` manages space dynamically, ensuring your interface adapts to any window dimension.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Constraints**:
15
+ - `Fill(n)`: Takes available space proportional to `n`.
16
+ - `Length(n)`: Fixed number of cells.
17
+ - `Percentage(n)`: Percentage of the parent area.
18
+ - `Min(n)`: At least `n` cells.
19
+ - `Ratio(x, y)`: `x/y` of the parent area.
20
+ - **Flex Modes**: Controlling how extra space is distributed (`Start`, `End`, `Center`, `SpaceBetween`, etc.).
21
+ - **Direction**: Splitting Vertically vs Horizontally.
22
+
23
+ ## Hotkeys
24
+
25
+ - **d**: Toggle Direction (`direction`)
26
+ - **f**: Cycle Flex Mode (`flex`)
27
+ - **c**: Cycle Constraint Set (`constraints`)
28
+ - **q**: Quit
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ ruby examples/widget_layout_split/app.rb
34
+ ```
35
+
36
+ ## Learning Outcomes
37
+
38
+ Use this example if you need to...
39
+ - Build responsive dashboards.
40
+ - Create 3-column layouts where the middle content fills remaining space.
41
+ - Center a modal dialog on the screen.
42
+ - Distribute buttons evenly across a control bar.
43
+
44
+ ![Demo](/doc/images/widget_layout_split.png)
@@ -0,0 +1,41 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Line Gauge Widget Example
7
+
8
+ Demonstrates compact progress bars for constrained spaces.
9
+
10
+ Standard block gauges take up vertical space. Sometimes you only have one line to show status. The `LineGauge` provides a compact, high-density progress indicator.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Compact Rendering**: Visualizing progress in a single character height.
15
+ - **Custom Symbols**: Replacing the standard line with Blocks, Shades, Dashes, or ASCII characters.
16
+ - **Styling**: Independent styling for the filled (progress) and unfilled (track) portions.
17
+
18
+ ## Hotkeys
19
+
20
+ - **Arrows (←/→)**: Adjust Ratio (`ratio`)
21
+ - **f**: Cycle Filled Symbol (`filled_symbol`)
22
+ - **u**: Cycle Unfilled Symbol (`unfilled_symbol`)
23
+ - **c**: Cycle Filled Color (`filled_style`)
24
+ - **x**: Cycle Unfilled Color (`unfilled_style`)
25
+ - **b**: Cycle Base Style (`style`)
26
+ - **q**: Cycle Quit
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ ruby examples/widget_line_gauge_demo/app.rb
32
+ ```
33
+
34
+ ## Learning Outcomes
35
+
36
+ Use this example if you need to...
37
+ - Add a progress bar to a list item or table row.
38
+ - Create a status line at the bottom of the screen.
39
+ - Show multiple metrics (CPU, RAM, Net) in a compact list.
40
+
41
+ ![Demo](/doc/images/widget_line_gauge_demo.png)
@@ -0,0 +1,49 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # List Widget Example
7
+
8
+ Demonstrates a selectable list with extensive configuration options.
9
+
10
+ Lists are the workhorse of terminal interfaces. Managing selection state, scrolling windows, and highlight styles logic is complex. The `List` widget handles all of this.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Scrolling**: Automatically handles lists larger than the view area.
15
+ - **Selection**: Maintains selected index and supports "no selection" state.
16
+ - **Highlighting**: Custom styles and symbols (e.g., `>>`) for the selected item.
17
+ - **Offset Modes**: Manual control over the scroll offset vs automatic "scroll to selection" behavior.
18
+ - **Scroll Padding**: Keeping a margin of items visible above/below the selection.
19
+
20
+ ## Hotkeys
21
+
22
+ - **i**: Cycle Item Data (`items`)
23
+ - **Arrow Keys (↑/↓)**: Navigate (`selected_index`)
24
+ - **x**: Toggle Selection (`selected_index`)
25
+ - **h**: Cycle Highlight Style (`highlight_style`)
26
+ - **y**: Cycle Highlight Symbol (`highlight_symbol`)
27
+ - **d**: Toggle Direction (`direction`)
28
+ - **s**: Cycle Highlight Spacing (`highlight_spacing`)
29
+ - **p**: Cycle Scroll Padding (`scroll_padding`)
30
+ - **b**: Cycle Base Style (`style`)
31
+ - **r**: Toggle Repeat Highlight Symbol (`repeat_highlight_symbol`)
32
+ - **o**: Cycle Offset Mode (`offset`)
33
+ - **q**: Quit
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ ruby examples/widget_list_demo/app.rb
39
+ ```
40
+
41
+ ## Learning Outcomes
42
+
43
+ Use this example if you need to...
44
+ - Create a file explorer.
45
+ - Build a navigation menu.
46
+ - Display a log where users can scroll back to read history.
47
+ - Implement "infinite select" behaviors.
48
+
49
+ ![Demo](/doc/images/widget_list_demo.png)
@@ -5,6 +5,7 @@
5
5
 
6
6
  $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
  require "ratatui_ruby"
8
+ require "faker" # Use Faker for large, realistic datasets
8
9
 
9
10
  # Demonstrates a selectable list of items with interactive attribute cycling.
10
11
  #
@@ -24,111 +25,18 @@ require "ratatui_ruby"
24
25
  class WidgetListDemo
25
26
  # Initializes the demo with example data and default configuration.
26
27
  def initialize
27
- @selected_index = nil
28
+ Faker::Config.random = Random.new(12345)
29
+ @selected_index = 6 # Start at C# to avoid highlighting the rich text examples
30
+ @tui_for_setup = nil
28
31
 
29
32
  @item_sets = [
30
- {
31
- name: "Large List",
32
- items: (1..200).map { |i| "Item #{i}" },
33
- },
34
- {
35
- name: "Colors",
36
- items: [
37
- "Red",
38
- "Orange",
39
- "Yellow",
40
- "Green",
41
- "Cyan",
42
- "Blue",
43
- "Indigo",
44
- "Violet",
45
- "Scarlet",
46
- "Crimson",
47
- "Maroon",
48
- "Brown",
49
- "Tan",
50
- "Beige",
51
- "Khaki",
52
- "Gold",
53
- "Silver",
54
- "White",
55
- "Gray",
56
- "Black",
57
- "Pink",
58
- "Magenta",
59
- "Turquoise",
60
- "Teal",
61
- "Coral",
62
- "Salmon",
63
- "Peach",
64
- "Lavender",
65
- "Lilac",
66
- "Olive",
67
- "Lime",
68
- "Navy",
69
- "Charcoal",
70
- "Ivory",
71
- "Azure",
72
- ],
73
- },
74
- {
75
- name: "Fruits",
76
- items: [
77
- "Apple",
78
- "Apricot",
79
- "Avocado",
80
- "Banana",
81
- "Blueberry",
82
- "Blackberry",
83
- "Cherry",
84
- "Cranberry",
85
- "Cucumber",
86
- "Date",
87
- "Dragonfruit",
88
- "Elderberry",
89
- "Fig",
90
- "Grape",
91
- "Grapefruit",
92
- "Guava",
93
- "Honeydew",
94
- "Huckleberry",
95
- "Jackfruit",
96
- "Kiwi",
97
- "Kumquat",
98
- "Lemon",
99
- "Lime",
100
- "Lychee",
101
- "Mango",
102
- "Melon",
103
- "Mulberry",
104
- "Nectarine",
105
- "Olive",
106
- "Orange",
107
- "Papaya",
108
- "Passion Fruit",
109
- "Peach",
110
- "Pear",
111
- "Persimmon",
112
- "Pineapple",
113
- "Plum",
114
- "Pomegranate",
115
- "Prune",
116
- "Rambutan",
117
- "Raspberry",
118
- "Starfruit",
119
- "Strawberry",
120
- "Tangerine",
121
- "Watermelon",
122
- "Ugli Fruit",
123
- ],
124
- },
125
33
  {
126
34
  name: "Programming",
127
35
  items: [
128
- "Ruby",
129
- "Rust",
130
- "Python",
131
- "JavaScript",
36
+ :ruby_styled, # Will be replaced with rich text in run()
37
+ :rust_styled, # Will be replaced with rich text in run()
38
+ :python_styled, # Will be replaced with rich text in run()
39
+ :javascript_styled, # Will be replaced with rich text in run()
132
40
  "Go",
133
41
  "C++",
134
42
  "C#",
@@ -175,6 +83,24 @@ class WidgetListDemo
175
83
  "BASIC",
176
84
  ],
177
85
  },
86
+ {
87
+ name: "Large List",
88
+ items: (1..200).map { |i| "Item #{i}" },
89
+ },
90
+ {
91
+ name: "Colors",
92
+ items: begin
93
+ Faker::Color.unique.clear
94
+ Array.new(100) { Faker::Color.color_name }
95
+ end,
96
+ },
97
+ {
98
+ name: "Fruits",
99
+ items: begin
100
+ Faker::Food.unique.clear
101
+ Array.new(100) { Faker::Food.fruits }
102
+ end,
103
+ },
178
104
  ]
179
105
  @item_set_index = 0
180
106
 
@@ -192,7 +118,7 @@ class WidgetListDemo
192
118
  { name: "Always", spacing: :always },
193
119
  { name: "Never", spacing: :never },
194
120
  ]
195
- @highlight_spacing_index = 0
121
+ @highlight_spacing_index = 1
196
122
 
197
123
  @repeat_modes = [
198
124
  { name: "Off", repeat: false },
@@ -205,7 +131,15 @@ class WidgetListDemo
205
131
  { name: "1 item", padding: 1 },
206
132
  { name: "2 items", padding: 2 },
207
133
  ]
208
- @scroll_padding_index = 0
134
+ @scroll_padding_index = 1
135
+
136
+ # Offset mode configurations to demonstrate offset + selection interaction
137
+ @offset_modes = [
138
+ { name: "Auto (No Offset)", offset: nil, allow_selection: true },
139
+ { name: "Offset Only", offset: 10, allow_selection: false },
140
+ { name: "Selection + Offset (Conflict)", offset: 0, allow_selection: true },
141
+ ]
142
+ @offset_mode_index = 0
209
143
  end
210
144
 
211
145
  # Runs the demo application.
@@ -214,8 +148,45 @@ class WidgetListDemo
214
148
  def run
215
149
  RatatuiRuby.run do |tui|
216
150
  @tui = tui
151
+
152
+ # Create rich text for "Ruby" - each letter with a different red style
153
+ ruby_line = @tui.text_line(spans: [
154
+ @tui.text_span(content: "R", style: @tui.style(fg: :red, modifiers: [:underlined])),
155
+ @tui.text_span(content: "u", style: @tui.style(fg: :light_red, modifiers: [:bold])),
156
+ @tui.text_span(content: "b", style: @tui.style(fg: :red, modifiers: [:italic])),
157
+ @tui.text_span(content: "y", style: @tui.style(fg: :light_red, modifiers: [:reversed])),
158
+ ])
159
+
160
+ # Create rich text for "Rust" - single styled Span
161
+ rust_span = @tui.text_span(
162
+ content: "Rust",
163
+ style: @tui.style(fg: :magenta, modifiers: [:bold, :underlined])
164
+ )
165
+
166
+ # Create ListItem for "Python" - demonstrates content + row background
167
+ python_item = @tui.list_item(
168
+ content: @tui.text_span(content: "Python", style: @tui.style(fg: :yellow)),
169
+ style: @tui.style(bg: :dark_gray)
170
+ )
171
+
172
+ # Create ListItem for "JavaScript" - demonstrates styled text with row background
173
+ javascript_item = @tui.list_item(
174
+ content: @tui.text_line(spans: [
175
+ @tui.text_span(content: "Java", style: @tui.style(fg: :yellow, modifiers: [:bold])),
176
+ @tui.text_span(content: "Script", style: @tui.style(fg: :light_yellow, modifiers: [:italic])),
177
+ ]),
178
+ style: @tui.style(bg: :blue)
179
+ )
180
+
181
+ # Replace the styled placeholders
182
+ @item_sets[0][:items][0] = ruby_line
183
+ @item_sets[0][:items][1] = rust_span
184
+ @item_sets[0][:items][2] = python_item
185
+ @item_sets[0][:items][3] = javascript_item
186
+
217
187
  # Initialize styles that require @tui
218
188
  @highlight_styles = [
189
+ { name: "Blue on White Bold", style: @tui.style(fg: :blue, bg: :white, modifiers: [:bold]) },
219
190
  { name: "Blue Bold", style: @tui.style(fg: :blue, modifiers: [:bold]) },
220
191
  { name: "Yellow on Black", style: @tui.style(fg: :yellow, bg: :black) },
221
192
  { name: "Green Italic", style: @tui.style(fg: :green, modifiers: [:italic]) },
@@ -245,7 +216,6 @@ class WidgetListDemo
245
216
  # :nodoc:
246
217
  private def render
247
218
  items = @item_sets[@item_set_index][:items]
248
- selection_label = @selected_index.nil? ? "none" : @selected_index.to_s
249
219
  direction_config = @direction_configs[@direction_index]
250
220
  spacing_config = @highlight_spacing_configs[@highlight_spacing_index]
251
221
  repeat_config = @repeat_modes[@repeat_index]
@@ -253,6 +223,13 @@ class WidgetListDemo
253
223
  highlight_symbol = @highlight_symbol_names[@highlight_symbol_index]
254
224
  base_style_config = @base_styles[@base_style_index]
255
225
  scroll_padding_config = @scroll_padding_configs[@scroll_padding_index]
226
+ offset_mode_config = @offset_modes[@offset_mode_index]
227
+
228
+ # Determine selection/offset based on mode
229
+ effective_selection = offset_mode_config[:allow_selection] ? @selected_index : nil
230
+ effective_offset = offset_mode_config[:offset]
231
+ selection_label = effective_selection.nil? ? "none" : effective_selection.to_s
232
+ offset_label = effective_offset.nil? ? "auto" : effective_offset.to_s
256
233
 
257
234
  @tui.draw do |frame|
258
235
  # Split into main content and control panel
@@ -261,7 +238,7 @@ class WidgetListDemo
261
238
  direction: :vertical,
262
239
  constraints: [
263
240
  @tui.constraint_fill(1),
264
- @tui.constraint_length(7),
241
+ @tui.constraint_length(8),
265
242
  ]
266
243
  )
267
244
 
@@ -282,7 +259,8 @@ class WidgetListDemo
282
259
  # Render list
283
260
  list = @tui.list(
284
261
  items:,
285
- selected_index: @selected_index,
262
+ selected_index: effective_selection,
263
+ offset: effective_offset,
286
264
  style: base_style_config[:style],
287
265
  highlight_style: highlight_style_config[:style],
288
266
  highlight_symbol:,
@@ -291,7 +269,7 @@ class WidgetListDemo
291
269
  direction: direction_config[:direction],
292
270
  scroll_padding: scroll_padding_config[:padding],
293
271
  block: @tui.block(
294
- title: "#{@item_sets[@item_set_index][:name]} (Selection: #{selection_label})",
272
+ title: "#{@item_sets[@item_set_index][:name]} | Sel: #{selection_label} | Offset: #{offset_label}",
295
273
  borders: [:all]
296
274
  )
297
275
  )
@@ -330,7 +308,11 @@ class WidgetListDemo
330
308
  @tui.text_span(content: "b", style: @hotkey_style),
331
309
  @tui.text_span(content: ": Base (#{base_style_config[:name]}) "),
332
310
  @tui.text_span(content: "r", style: @hotkey_style),
333
- @tui.text_span(content: ": Repeat (#{repeat_config[:name]}) "),
311
+ @tui.text_span(content: ": Repeat (#{repeat_config[:name]})"),
312
+ ]),
313
+ @tui.text_line(spans: [
314
+ @tui.text_span(content: "o", style: @hotkey_style),
315
+ @tui.text_span(content: ": Offset Mode (#{offset_mode_config[:name]}) "),
334
316
  @tui.text_span(content: "q", style: @hotkey_style),
335
317
  @tui.text_span(content: ": Quit"),
336
318
  ]),
@@ -373,6 +355,8 @@ class WidgetListDemo
373
355
  @repeat_index = (@repeat_index + 1) % @repeat_modes.size
374
356
  in type: :key, code: "p"
375
357
  @scroll_padding_index = (@scroll_padding_index + 1) % @scroll_padding_configs.size
358
+ in type: :key, code: "o"
359
+ @offset_mode_index = (@offset_mode_index + 1) % @offset_modes.size
376
360
  else
377
361
  nil
378
362
  end