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,39 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Canvas Widget Example
7
+
8
+ Demonstrates drawing custom graphics and maps using the standard Braille and Block patterns.
9
+
10
+ Standard widgets are great for text, but sometimes you need to draw. The `Canvas` widget gives you a high-resolution coordinate system (x, y) to render shapes, lines, and data visualizations that go beyond the grid.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **High-Resolution Drawing**: Using Braille patterns (`⣿`) to effectively double the vertical and horizontal resolution of the terminal.
15
+ - **Layers**: Drawing multiple shapes (Map, Circles, Lines) in a specific order.
16
+ - **Animation**: Updating coordinates in a loop to create smooth motion.
17
+ - **World Map**: Using the built-in `Map` shape for geographic data.
18
+
19
+ ## Hotkeys
20
+
21
+ - **b**: Cycle Background Color (`background_color`)
22
+ - **m**: Cycle Marker Type (`marker`)
23
+ - **l**: Toggle Labels (modifies `shapes`)
24
+ - **q**: Quit
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ ruby examples/widget_map_demo/app.rb
30
+ ```
31
+
32
+ ## Learning Outcomes
33
+
34
+ Use this example if you need to...
35
+ - Render geographic data (World, USA, Europe).
36
+ - Overlay custom labels and markers on a map.
37
+ - Animate visual elements on top of a static background.
38
+
39
+ ![Demo](/doc/images/widget_map_demo.png)
@@ -7,7 +7,7 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
  require "ratatui_ruby"
8
8
 
9
9
  # An example of the Canvas widget showing a world map and animated shapes.
10
- class AppMapDemo
10
+ class WidgetMapDemo
11
11
  include RatatuiRuby
12
12
 
13
13
  COLORS = [:black, :blue, :white, nil].freeze
@@ -90,4 +90,4 @@ class AppMapDemo
90
90
  end
91
91
  end
92
92
 
93
- AppMapDemo.new.run if __FILE__ == $PROGRAM_NAME
93
+ WidgetMapDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,248 @@
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
+ HEADLINES = [
10
+ "Scientists Discover New Species of Deep-Sea Octopus Near Hawaii",
11
+ "Global Climate Summit Reaches Historic Agreement on Emissions",
12
+ "Tech Giant Announces Breakthrough in Quantum Computing Research",
13
+ "Local Community Garden Initiative Expands to Ten More Cities",
14
+ "Astronomers Detect Unusual Radio Signals from Distant Galaxy",
15
+ "New Study Links Mediterranean Diet to Improved Heart Health",
16
+ "Electric Vehicle Sales Surge as Battery Technology Improves",
17
+ "Ancient Manuscripts Reveal Previously Unknown Trading Routes",
18
+ "Renewable Energy Now Powers 40% of National Grid",
19
+ "Robotics Team Develops AI System for Disaster Response",
20
+ "Archaeological Dig Uncovers Evidence of Early Human Settlement",
21
+ "Major Airline Commits to Carbon-Neutral Flights by 2035",
22
+ "Breakthrough Treatment Shows Promise for Rare Genetic Disease",
23
+ "City Council Approves Expanded Public Transportation Network",
24
+ "Marine Biologists Track Migration Patterns of Endangered Whales",
25
+ "New App Helps Farmers Optimize Water Usage During Drought",
26
+ "International Space Station Extends Mission Timeline to 2030",
27
+ "Local Schools Implement Innovative STEM Education Program",
28
+ "Wildlife Conservation Efforts Lead to Species Population Recovery",
29
+ "Research Team Creates Biodegradable Alternative to Plastic Packaging",
30
+ "Historic Theater Restoration Project Nears Completion",
31
+ "Cybersecurity Experts Warn of Emerging Online Threats",
32
+ "Community Food Bank Serves Record Number of Families This Year",
33
+ "Innovative Urban Planning Reduces Traffic Congestion by 30%",
34
+ ].freeze
35
+
36
+ # Overlay Demo Example
37
+ # Demonstrates the Overlay widget for layering widgets with depth.
38
+ class WidgetOverlayDemo
39
+ def initialize
40
+ @layer_count = 2 # Start with 2 layers visible
41
+ @swapped = false
42
+ @clear = true
43
+ end
44
+
45
+ def run
46
+ RatatuiRuby.run do |tui|
47
+ @tui = tui
48
+ loop do
49
+ tui.draw do |frame|
50
+ render(frame)
51
+ end
52
+ break if handle_input == :quit
53
+ sleep 0.05
54
+ end
55
+ end
56
+ end
57
+
58
+ private def render(frame)
59
+ area = frame.area
60
+
61
+ # Split into main area and control panel
62
+ layout = @tui.layout_split(
63
+ area,
64
+ direction: :vertical,
65
+ constraints: [
66
+ @tui.constraint_fill(1),
67
+ @tui.constraint_length(5),
68
+ ]
69
+ )
70
+
71
+ main_area = layout[0]
72
+ control_area = layout[1]
73
+
74
+ # Render background layer - RSS reader
75
+ frame.render_widget(background_layer, main_area)
76
+
77
+ # Render upper layers based on layer_count and swap state
78
+ if @swapped
79
+ render_beta_layer(frame, main_area) if @layer_count >= 2
80
+ render_notification_layer(frame, main_area) if @layer_count >= 1
81
+ else
82
+ render_notification_layer(frame, main_area) if @layer_count >= 1
83
+ render_beta_layer(frame, main_area) if @layer_count >= 2
84
+ end
85
+
86
+ # Render control panel
87
+ frame.render_widget(control_panel, control_area)
88
+ end
89
+
90
+ def background_layer
91
+ @background_layer ||= @tui.list(
92
+ items: HEADLINES,
93
+ block: @tui.block(
94
+ title: "RSS Reader",
95
+ borders: [:all]
96
+ )
97
+ )
98
+ end
99
+
100
+ def render_notification_layer(frame, area)
101
+ # Position modal: 20% from top, 60% height, 15% from left, 70% width
102
+
103
+ vertical_sections = @tui.layout_split(
104
+ area,
105
+ direction: :vertical,
106
+ constraints: [
107
+ @tui.constraint_fill(2),
108
+ @tui.constraint_fill(5),
109
+ @tui.constraint_fill(3),
110
+ ]
111
+ )
112
+
113
+ horizontal_sections = @tui.layout_split(
114
+ vertical_sections[1],
115
+ direction: :horizontal,
116
+ constraints: [
117
+ @tui.constraint_fill(1),
118
+ @tui.constraint_fill(5),
119
+ @tui.constraint_fill(1),
120
+ ]
121
+ )
122
+
123
+ modal_rect = horizontal_sections[1]
124
+
125
+ frame.render_widget(@tui.clear, modal_rect) if @clear
126
+
127
+ # Render the modal content
128
+ frame.render_widget(
129
+ @tui.paragraph(
130
+ text: "Your feeds have been updated",
131
+ wrap: true,
132
+ alignment: :center,
133
+ block: @tui.block(
134
+ title: "Notification",
135
+ borders: [:all],
136
+ border_style: @tui.style(fg: :black),
137
+ style: @tui.style(bg: :red, fg: :black)
138
+ )
139
+ ),
140
+ modal_rect
141
+ )
142
+ end
143
+
144
+ def render_beta_layer(frame, area)
145
+ # Position modal: 30% from top, 40% height, 25% from left, 50% width
146
+
147
+ vertical_sections = @tui.layout_split(
148
+ area,
149
+ direction: :vertical,
150
+ constraints: [
151
+ @tui.constraint_fill(3),
152
+ @tui.constraint_fill(4),
153
+ @tui.constraint_fill(2),
154
+ ]
155
+ )
156
+
157
+ horizontal_sections = @tui.layout_split(
158
+ vertical_sections[1],
159
+ direction: :horizontal,
160
+ constraints: [
161
+ @tui.constraint_fill(2),
162
+ @tui.constraint_fill(3),
163
+ @tui.constraint_fill(2),
164
+ ]
165
+ )
166
+
167
+ modal_rect = horizontal_sections[1]
168
+
169
+ frame.render_widget(@tui.clear, modal_rect) if @clear
170
+
171
+ # Render the modal content
172
+ frame.render_widget(
173
+ beta_paragraph,
174
+ modal_rect
175
+ )
176
+ end
177
+
178
+ def beta_paragraph
179
+ @beta_paragraph ||= @tui.paragraph(
180
+ text: "Thank you for being a beta tester. To give feedback, shout very loudly and we will hear you. Be careful not to scare the llamas.",
181
+ wrap: true,
182
+ alignment: :left,
183
+ block: @tui.block(
184
+ title: "Beta Program",
185
+ borders: [:all],
186
+ border_style: @tui.style(fg: :black),
187
+ style: @tui.style(bg: :blue, fg: :black)
188
+ )
189
+ )
190
+ end
191
+
192
+ def control_panel
193
+ bold_underline = @tui.style(modifiers: [:bold, :underlined])
194
+
195
+ first_controls = [
196
+ @tui.text_span(content: "0", style: bold_underline),
197
+ @tui.text_span(content: "/"),
198
+ @tui.text_span(content: "1", style: bold_underline),
199
+ @tui.text_span(content: "/"),
200
+ @tui.text_span(content: "2", style: bold_underline),
201
+ @tui.text_span(content: ": Change number of overlays | "),
202
+ @tui.text_span(content: "space", style: bold_underline),
203
+ @tui.text_span(content: ": Swap overlay order"),
204
+ ]
205
+ second_controls = [
206
+ @tui.text_span(content: "c", style: bold_underline),
207
+ @tui.text_span(content: ": Toggle clear (currently #{@clear ? 'on' : 'off'})"),
208
+ ]
209
+ third_controls = [
210
+ @tui.text_span(content: "q", style: bold_underline),
211
+ @tui.text_span(content: ": Quit"),
212
+ ]
213
+
214
+ first = @tui.text_line(spans: first_controls)
215
+ second = @tui.text_line(spans: second_controls)
216
+ third = @tui.text_line(spans: third_controls)
217
+
218
+ @tui.paragraph(
219
+ text: [first, second, third],
220
+ alignment: :center,
221
+ block: @tui.block(
222
+ title: "Controls",
223
+ borders: [:all]
224
+ )
225
+ )
226
+ end
227
+
228
+ def handle_input
229
+ case @tui.poll_event
230
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
231
+ :quit
232
+ in { type: :key, code: "0" }
233
+ @layer_count = 0
234
+ in { type: :key, code: "1" }
235
+ @layer_count = 1
236
+ in { type: :key, code: "2" }
237
+ @layer_count = 2
238
+ in { type: :key, code: " " }
239
+ @swapped = !@swapped
240
+ in { type: :key, code: "c" }
241
+ @clear = !@clear
242
+ else
243
+ nil
244
+ end
245
+ end
246
+ end
247
+
248
+ WidgetOverlayDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,36 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Popup (Clear) Widget Example
7
+
8
+ Demonstrates how to render opaque overlays on top of content.
9
+
10
+ Terminal renders are additive. If you draw a new widget over an old one, the background colors might mix if not handled correctly. The `Clear` widget resets the area to default (usually transparent/black) to ensure a clean canvas for popups.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **The `Clear` Widget**: Printing spaces over an area to "erase" what was underneath.
15
+ - **Centering**: Using `Layout` constraints to perfectly center a block on screen.
16
+ - **Style Bleed**: showing what happens when you *don't* use `Clear` (background colors leak through).
17
+
18
+ ## Hotkeys
19
+
20
+ - **Space**: Toggle Clear Widget (Observe the red background effect when disabled)
21
+ - **q**: Quit
22
+
23
+ ## Usage
24
+
25
+ ```bash
26
+ ruby examples/widget_popup_demo/app.rb
27
+ ```
28
+
29
+ ## Learning Outcomes
30
+
31
+ Use this example if you need to...
32
+ - Create a modal dialog (Confirm, Alert, Form).
33
+ - Implement a dropdown menu that overlays other content.
34
+ - Fix visual artifacts where old text shows through new widgets.
35
+
36
+ ![Demo](/doc/images/widget_popup_demo.png)
@@ -0,0 +1,34 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Ratatui Logo Example
7
+
8
+ Demonstrates branding with the official logo widget.
9
+
10
+ A polished application often needs an "About" screen or a splash screen. This widget provides the standardized project branding.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **RatatuiLogo Widget**: Renders the Ratatui ASCII art logo.
15
+ - **Centering**: Techniques for centering fixed-size content in a fluid layout.
16
+
17
+ ## Hotkeys
18
+
19
+ - **q**: Quit
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ ruby examples/widget_ratatui_logo_demo/app.rb
25
+ ```
26
+
27
+ ## Learning Outcomes
28
+
29
+ Use this example if you need to...
30
+ - Create a splash screen.
31
+ - Add an "About" modal to your application.
32
+ - See how to center a widget both vertically and horizontally.
33
+
34
+ ![Demo](/doc/images/widget_ratatui_logo_demo.png)
@@ -0,0 +1,34 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Ratatui Mascot Example
7
+
8
+ Demonstrates the project mascot widget for adding personality.
9
+
10
+ Interfaces can feel clinical. A friendly mascot adds charm and brand identity to your terminal application.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **RatatuiMascot Widget**: Renders the ASCII art mascot.
15
+ - **Block Integration**: Wrapping the mascot in a bordered block title.
16
+
17
+ ## Hotkeys
18
+
19
+ - **b**: Toggle Block Border (`block`)
20
+ - **q**: Quit
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ ruby examples/widget_ratatui_mascot_demo/app.rb
26
+ ```
27
+
28
+ ## Learning Outcomes
29
+
30
+ Use this example if you need to...
31
+ - Add visual flair to your UI.
32
+ - Create a friendly empty state or success screen.
33
+
34
+ ![Demo](/doc/images/widget_ratatui_mascot_demo.png)
@@ -0,0 +1,38 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Rect (Geometry) Widget Example
7
+
8
+ Demonstrates the Rect geometry primitive and hit-testing patterns.
9
+
10
+ TUI layouts are composed of rectangles. Understanding how to manipulate `Rect` objects, reuse them from the layout phase, and use them for mouse interaction is critical for building interactive apps.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Rect Attributes**: Investigating x, y, width, and height.
15
+ - **Cached Layout Pattern**: Computing constraints in the render loop and reusing the resulting `Rect`s in the event loop for logic.
16
+ - **Hit Testing**: Using `Rect#contains?(x, y)` to determine if a mouse click happened inside a specific panel.
17
+
18
+ ## Hotkeys
19
+
20
+ - **Arrows (←/→)**: Expand/Shrink Sidebar Width (Layout Constraint)
21
+ - **Arrows (↑/↓)**: Navigate Menu Selection (`selected_index`)
22
+ - **Mouse Click**: Click anywhere to see which Rect detects the hit (`contains?`)
23
+ - **q**: Quit
24
+
25
+ ## Usage
26
+
27
+ ```bash
28
+ ruby examples/widget_rect/app.rb
29
+ ```
30
+
31
+ ## Learning Outcomes
32
+
33
+ Use this example if you need to...
34
+ - Handle mouse clicks on specific buttons or areas.
35
+ - Create resizable panes (like a split pane in an IDE).
36
+ - Debug layout issues by inspecting Rect coordinates.
37
+
38
+ ![Demo](/doc/images/widget_rect.png)
@@ -0,0 +1,37 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Render (Custom Widget) Example
7
+
8
+ Demonstrates how to build Custom Widgets using absolute coordinates.
9
+
10
+ Sometimes standard widgets aren't enough. You need to draw custom shapes, games, or graphs. This example shows how to implement the `render(area)` contract to draw anything you want while respecting layout boundaries.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Custom Widget Contract**: Implementing a class with `render(area)`.
15
+ - **Coordinate Offsets**: Creating drawing logic that works regardless of where the widget is placed on screen (using `area.x + offset`).
16
+ - **Composability**: Wrapping custom widgets in standard `Block`s with borders.
17
+
18
+ ## Hotkeys
19
+
20
+ - **n**: Next Widget (Diagonal -> Checkerboard -> Border)
21
+ - **p**: Previous Widget
22
+ - **q**: Quit
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ ruby examples/widget_render/app.rb
28
+ ```
29
+
30
+ ## Learning Outcomes
31
+
32
+ Use this example if you need to...
33
+ - Build a game (Snake, Tetris) inside the terminal.
34
+ - Create a specialized visualization (Network topology graph).
35
+ - Draw custom UI elements not provided by the library.
36
+
37
+ ![Demo](/doc/images/widget_render.png)
@@ -0,0 +1,35 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Rich Text Example
7
+
8
+ Demonstrates styling individual words and characters.
9
+
10
+ Standard strings are monochromatic. "Rich Text" is composed of `Lines` containing multiple `Spans`, where each Span has its own style. This allows for multi-colored, multi-styled text blocks.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Spans**: Chunks of text with a specific style (e.g., "Bold Red Word").
15
+ - **Lines**: ordered collections of Spans that form a single row of text.
16
+ - **Paragraphs**: Rendering lines of rich text.
17
+
18
+ ## Hotkeys
19
+
20
+ - **q**: Quit
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ ruby examples/widget_rich_text/app.rb
26
+ ```
27
+
28
+ ## Learning Outcomes
29
+
30
+ Use this example if you need to...
31
+ - Highlight keywords in code (Syntax highlighting).
32
+ - Create status lines with icons (e.g., "✔ Success" where the checkmark is green).
33
+ - Emphasize specific data points in a paragraph.
34
+
35
+ ![Demo](/doc/images/widget_rich_text.png)
@@ -12,6 +12,7 @@ require "ratatui_ruby"
12
12
  class WidgetRichText
13
13
  def initialize
14
14
  @scroll_pos = 0
15
+ @color_index = 0
15
16
  end
16
17
 
17
18
  def run
@@ -44,38 +45,52 @@ class WidgetRichText
44
45
  private def simple_text_line_example
45
46
  # Example 1: A line with mixed styles
46
47
  @tui.paragraph(
47
- text: @tui.text_line(
48
- spans: [
49
- @tui.text_span(
50
- content: "Normal text, ",
51
- style: nil
52
- ),
53
- @tui.text_span(
54
- content: "Bold Text",
55
- style: @tui.style(modifiers: [:bold])
56
- ),
57
- @tui.text_span(
58
- content: ", ",
59
- style: nil
60
- ),
61
- @tui.text_span(
62
- content: "Italic Text",
63
- style: @tui.style(modifiers: [:italic])
64
- ),
65
- @tui.text_span(
66
- content: ", ",
67
- style: nil
68
- ),
69
- @tui.text_span(
70
- content: "Red Text",
71
- style: @tui.style(fg: :red)
72
- ),
73
- @tui.text_span(
74
- content: ".",
75
- style: nil
76
- ),
77
- ]
78
- ),
48
+ text: [
49
+ @tui.text_line(
50
+ spans: [
51
+ @tui.text_span(
52
+ content: "Normal text, ",
53
+ style: nil
54
+ ),
55
+ @tui.text_span(
56
+ content: "Bold Text",
57
+ style: @tui.style(modifiers: [:bold])
58
+ ),
59
+ @tui.text_span(
60
+ content: ", ",
61
+ style: nil
62
+ ),
63
+ @tui.text_span(
64
+ content: "Italic Text",
65
+ style: @tui.style(modifiers: [:italic])
66
+ ),
67
+ @tui.text_span(
68
+ content: ", ",
69
+ style: nil
70
+ ),
71
+ @tui.text_span(
72
+ content: "Red Text",
73
+ style: @tui.style(fg: :red)
74
+ ),
75
+ @tui.text_span(
76
+ content: ".",
77
+ style: nil
78
+ ),
79
+ ]
80
+ ),
81
+ @tui.text_line(spans: []),
82
+ @tui.text_line(
83
+ spans: [
84
+ @tui.text_span(content: "Integer Color Test: "),
85
+ @tui.text_span(content: "Color #{@color_index}", style: @tui.style(fg: @color_index)),
86
+ @tui.text_span(content: " (Use "),
87
+ @tui.text_span(content: "↑ ↓", style: @tui.style(modifiers: [:bold])),
88
+ @tui.text_span(content: " for +/- 1,", style: nil),
89
+ @tui.text_span(content: "→ ←", style: @tui.style(modifiers: [:bold])),
90
+ @tui.text_span(content: " for +/- 10)", style: nil),
91
+ ]
92
+ ),
93
+ ],
79
94
  block: @tui.block(
80
95
  title: "Simple Rich Text",
81
96
  borders: [:all]
@@ -113,7 +128,11 @@ class WidgetRichText
113
128
  spans: [
114
129
  @tui.text_span(content: "Press ", style: nil),
115
130
  @tui.text_span(content: "Q", style: @tui.style(modifiers: [:bold])),
116
- @tui.text_span(content: " to quit", style: nil),
131
+ @tui.text_span(content: " to quit, ", style: nil),
132
+ @tui.text_span(content: "↑ ↓", style: @tui.style(modifiers: [:bold])),
133
+ @tui.text_span(content: " to adjust color by 1, ", style: nil),
134
+ @tui.text_span(content: "← →", style: @tui.style(modifiers: [:bold])),
135
+ @tui.text_span(content: " to adjust color by 10.", style: nil),
117
136
  ]
118
137
  ),
119
138
  ],
@@ -128,6 +147,16 @@ class WidgetRichText
128
147
  event = @tui.poll_event
129
148
  return :quit if event == "q" || event == :esc || event == :ctrl_c
130
149
 
150
+ if event.left?
151
+ @color_index = (@color_index - 10) % 256
152
+ elsif event.right?
153
+ @color_index = (@color_index + 10) % 256
154
+ elsif event.up?
155
+ @color_index = (@color_index + 1) % 256
156
+ elsif event.down?
157
+ @color_index = (@color_index - 1) % 256
158
+ end
159
+
131
160
  nil
132
161
  end
133
162
  end
@@ -0,0 +1,37 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Scroll Text Example
7
+
8
+ Demonstrates scrolling long text content within a fixed viewport.
9
+
10
+ Sometimes text exceeds the available space. The `Paragraph` widget supports a `scroll` parameter to simulate a viewport, allowing users to pan vertically and horizontally.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Vertical Scrolling**: Moving through lines of text.
15
+ - **Horizontal Scrolling**: Panning across long, unwrapped lines.
16
+ - **State Management**: tracking `scroll_x` and `scroll_y` offsets in the application state.
17
+
18
+ ## Hotkeys
19
+
20
+ - **Arrows (↑/↓)**: Scroll Vertically (`scroll`)
21
+ - **Arrows (←/→)**: Scroll Horizontally (`scroll`)
22
+ - **q**: Quit
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ ruby examples/widget_scroll_text/app.rb
28
+ ```
29
+
30
+ ## Learning Outcomes
31
+
32
+ Use this example if you need to...
33
+ - Build a log viewer.
34
+ - Create a "terms and conditions" scrollbox.
35
+ - Display code snippets that might be wider than the terminal.
36
+
37
+ ![Demo](/doc/images/widget_scroll_text.png)
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env ruby
2
1
  # frozen_string_literal: true
3
2
 
4
3
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>