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,34 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+ # Block Widget Demo
6
+
7
+ This example demonstrates the versatile `Block` widget, which provides the visual container, borders, and titles for almost every other widget in `ratatui_ruby`.
8
+
9
+ ## Key Concepts
10
+
11
+ - **Borders:** Choose which sides to show and apply different border types (plain, rounded, double, thick, etc.).
12
+ - **Titles:** Add a main title with alignment or multiple titles at the top and bottom with independent styles and positions.
13
+ - **Padding:** Define inner spacing using uniform or directional (L/R/T/B) values.
14
+ - **Styling:** Individually style the block's content area, its borders, and its titles.
15
+ - **Custom Border Sets:** Create entirely custom border appearances by defining each character in the border set.
16
+
17
+ ## Hotkeys
18
+
19
+ - `t`: Cycle **Title** (None, Main Title)
20
+ - `a`: Cycle **Title Alignment** (Left, Center, Right)
21
+ - `s`: Cycle **Title Style** (None, Cyan Bold, Yellow Italic)
22
+ - `e`: Cycle **Additional Titles** (None, Top+Bottom, Complex)
23
+ - `b`: Cycle **Borders** (All, Top/Bottom, Left/Right, None)
24
+ - `y`: Cycle **Border Type** (Rounded, Plain, Double, Thick, Quadrant, Custom)
25
+ - `c`: Cycle **Border Style** (Magenta Bold, None, Green, Blue on White)
26
+ - `p`: Cycle **Padding** (Uniform, None, Directional, Narrow)
27
+ - `f`: Cycle **Base Style** (Dark Gray, None, White on Black)
28
+ - `q`: **Quit**
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ ruby examples/widget_block_demo/app.rb
34
+ ```
@@ -0,0 +1,256 @@
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
+ # Demonstrates the Block widget with interactive attribute cycling.
10
+ #
11
+ # Blocks are the foundation of terminal layouts, providing structure, borders, and titles.
12
+ # This demo showcases all available parameters, including advanced title positioning,
13
+ # directional padding, and custom border sets.
14
+ #
15
+ # === Examples
16
+ #
17
+ # Run the demo from the terminal:
18
+ #
19
+ # ruby examples/widget_block_demo/app.rb
20
+ #
21
+ # rdoc-image:/doc/images/widget_block_demo.png
22
+ class WidgetBlockDemo
23
+ def initialize
24
+ @title_configs = [
25
+ { name: "None", title: nil },
26
+ { name: "Main Title", title: "Main Title" },
27
+ ]
28
+ @title_index = 1
29
+
30
+ @titles_configs = [
31
+ { name: "None", titles: [] },
32
+ {
33
+ name: "Top + Bottom",
34
+ titles: [
35
+ { content: "Top Right", alignment: :right, position: :top },
36
+ { content: "Bottom Left", alignment: :left, position: :bottom },
37
+ ],
38
+ },
39
+ {
40
+ name: "Complex",
41
+ titles: [
42
+ { content: "★ Left ★", alignment: :left, position: :top },
43
+ { content: "Center", alignment: :center, position: :top },
44
+ { content: "Right", alignment: :right, position: :top },
45
+ { content: "Bottom Center", alignment: :center, position: :bottom },
46
+ ],
47
+ },
48
+ ]
49
+ @titles_index = 1
50
+
51
+ @alignment_configs = [
52
+ { name: "Left", alignment: :left },
53
+ { name: "Center", alignment: :center },
54
+ { name: "Right", alignment: :right },
55
+ ]
56
+ @alignment_index = 1 # Center
57
+
58
+ @border_configs = [
59
+ { name: "All", borders: [:all] },
60
+ { name: "Top/Bottom", borders: [:top, :bottom] },
61
+ { name: "Left/Right", borders: [:left, :right] },
62
+ { name: "None", borders: [] },
63
+ ]
64
+ @border_index = 0
65
+
66
+ @border_type_configs = [
67
+ { name: "Rounded", type: :rounded },
68
+ { name: "Plain", type: :plain },
69
+ { name: "Double", type: :double },
70
+ { name: "Thick", type: :thick },
71
+ { name: "Quadrant Inside", type: :quadrant_inside },
72
+ { name: "Quadrant Outside", type: :quadrant_outside },
73
+ {
74
+ name: "Custom Set",
75
+ type: nil,
76
+ set: {
77
+ top_left: "1",
78
+ top_right: "2",
79
+ bottom_left: "3",
80
+ bottom_right: "4",
81
+ vertical_left: "5",
82
+ vertical_right: "6",
83
+ horizontal_top: "7",
84
+ horizontal_bottom: "8",
85
+ },
86
+ },
87
+ ]
88
+ @border_type_index = 0
89
+
90
+ @padding_configs = [
91
+ { name: "Uniform (2)", padding: 2 },
92
+ { name: "None (0)", padding: 0 },
93
+ { name: "Directional (L:4, T:2)", padding: [4, 0, 2, 0] },
94
+ { name: "Narrow (H:1, V:0)", padding: [1, 1, 0, 0] },
95
+ ]
96
+ @padding_index = 0
97
+ end
98
+
99
+ def run
100
+ RatatuiRuby.run do |tui|
101
+ @tui = tui
102
+
103
+ @title_styles = [
104
+ { name: "None", style: nil },
105
+ { name: "Magenta Bold", style: @tui.style(fg: :magenta, modifiers: [:bold]) },
106
+ { name: "Cyan Bold", style: @tui.style(fg: :cyan, modifiers: [:bold]) },
107
+ { name: "Yellow Italic", style: @tui.style(fg: :yellow, modifiers: [:italic]) },
108
+ ]
109
+ @title_style_index = 1 # Magenta Bold
110
+
111
+ @border_styles = [
112
+ { name: "Cyan", style: @tui.style(fg: :cyan) },
113
+ { name: "Magenta Bold", style: @tui.style(fg: :magenta, modifiers: [:bold]) },
114
+ { name: "None", style: nil },
115
+ { name: "Blue on White", style: @tui.style(fg: :blue, bg: :white) },
116
+ ]
117
+ @border_style_index = 0
118
+
119
+ @base_styles = [
120
+ { name: "Dark Gray", style: @tui.style(fg: :dark_gray) },
121
+ { name: "None", style: nil },
122
+ { name: "White on Black", style: @tui.style(fg: :white, bg: :black) },
123
+ ]
124
+ @base_style_index = 1
125
+
126
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
127
+
128
+ loop do
129
+ render
130
+ break if handle_input == :quit
131
+ sleep 0.05
132
+ end
133
+ end
134
+ end
135
+
136
+ private def render
137
+ title_config = @title_configs[@title_index]
138
+ titles_config = @titles_configs[@titles_index]
139
+ alignment_config = @alignment_configs[@alignment_index]
140
+ title_style_config = @title_styles[@title_style_index]
141
+ border_config = @border_configs[@border_index]
142
+ border_type_config = @border_type_configs[@border_type_index]
143
+ border_style_config = @border_styles[@border_style_index]
144
+ base_style_config = @base_styles[@base_style_index]
145
+ padding_config = @padding_configs[@padding_index]
146
+
147
+ @tui.draw do |frame|
148
+ main_area, control_area = @tui.layout_split(
149
+ frame.area,
150
+ direction: :vertical,
151
+ constraints: [
152
+ @tui.constraint_fill(1),
153
+ @tui.constraint_length(10),
154
+ ]
155
+ )
156
+
157
+ # Render the demo block
158
+ demo_block = @tui.block(
159
+ title: title_config[:title],
160
+ titles: titles_config[:titles],
161
+ title_alignment: alignment_config[:alignment],
162
+ title_style: title_style_config[:style],
163
+ borders: border_config[:borders],
164
+ border_type: border_type_config[:type],
165
+ border_set: border_type_config[:set],
166
+ border_style: border_style_config[:style],
167
+ style: base_style_config[:style],
168
+ padding: padding_config[:padding]
169
+ )
170
+
171
+ # Paragraph inside the block to show padding/content interaction
172
+ content = @tui.paragraph(
173
+ text: "This paragraph is rendered inside the Block widget.\n" \
174
+ "You can see how padding and base style affect this content.\n\n" \
175
+ "Current State:\n" \
176
+ "• Padding: #{padding_config[:name]}\n" \
177
+ "• Borders: #{border_config[:name]}\n" \
178
+ "• Type: #{border_type_config[:name]}",
179
+ block: demo_block
180
+ )
181
+ frame.render_widget(content, main_area)
182
+
183
+ # Render control panel
184
+ control_panel = @tui.block(
185
+ title: "Controls",
186
+ borders: [:all],
187
+ children: [
188
+ @tui.paragraph(
189
+ text: [
190
+ @tui.text_line(spans: [
191
+ @tui.text_span(content: "t", style: @hotkey_style),
192
+ @tui.text_span(content: ": Title (#{title_config[:name]}) "),
193
+ @tui.text_span(content: "a", style: @hotkey_style),
194
+ @tui.text_span(content: ": Alignment (#{alignment_config[:name]}) "),
195
+ @tui.text_span(content: "s", style: @hotkey_style),
196
+ @tui.text_span(content: ": Title Style (#{title_style_config[:name]})"),
197
+ ]),
198
+ @tui.text_line(spans: [
199
+ @tui.text_span(content: "e", style: @hotkey_style),
200
+ @tui.text_span(content: ": Additional Titles (#{titles_config[:name]})"),
201
+ ]),
202
+ @tui.text_line(spans: [
203
+ @tui.text_span(content: "b", style: @hotkey_style),
204
+ @tui.text_span(content: ": Borders (#{border_config[:name]}) "),
205
+ @tui.text_span(content: "y", style: @hotkey_style),
206
+ @tui.text_span(content: ": Border Type (#{border_type_config[:name]})"),
207
+ ]),
208
+ @tui.text_line(spans: [
209
+ @tui.text_span(content: "c", style: @hotkey_style),
210
+ @tui.text_span(content: ": Border Style (#{border_style_config[:name]}) "),
211
+ @tui.text_span(content: "p", style: @hotkey_style),
212
+ @tui.text_span(content: ": Padding (#{padding_config[:name]})"),
213
+ ]),
214
+ @tui.text_line(spans: [
215
+ @tui.text_span(content: "f", style: @hotkey_style),
216
+ @tui.text_span(content: ": Base Style (#{base_style_config[:name]}) "),
217
+ @tui.text_span(content: "q", style: @hotkey_style),
218
+ @tui.text_span(content: ": Quit"),
219
+ ]),
220
+ ]
221
+ ),
222
+ ]
223
+ )
224
+ frame.render_widget(control_panel, control_area)
225
+ end
226
+ end
227
+
228
+ private 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: "t"
233
+ @title_index = (@title_index + 1) % @title_configs.size
234
+ in type: :key, code: "e"
235
+ @titles_index = (@titles_index + 1) % @titles_configs.size
236
+ in type: :key, code: "a"
237
+ @alignment_index = (@alignment_index + 1) % @alignment_configs.size
238
+ in type: :key, code: "s"
239
+ @title_style_index = (@title_style_index + 1) % @title_styles.size
240
+ in type: :key, code: "b"
241
+ @border_index = (@border_index + 1) % @border_configs.size
242
+ in type: :key, code: "y"
243
+ @border_type_index = (@border_type_index + 1) % @border_type_configs.size
244
+ in type: :key, code: "c"
245
+ @border_style_index = (@border_style_index + 1) % @border_styles.size
246
+ in type: :key, code: "p"
247
+ @padding_index = (@padding_index + 1) % @padding_configs.size
248
+ in type: :key, code: "f"
249
+ @base_style_index = (@base_style_index + 1) % @base_styles.size
250
+ else
251
+ nil
252
+ end
253
+ end
254
+ end
255
+
256
+ WidgetBlockDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,45 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Box (Block) Widget Example
7
+
8
+ Demonstrates visual container attributes with interactive cycling.
9
+
10
+ Widgets often float in a void. Without boundaries, interfaces become a chaotic mess of text. `Block` (often called a Box) provides the structure and visual hierarchy needed to organize information.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Border Types**: Native styles like `:plain`, `:rounded`, `:double`, `:thick`.
15
+ - **Custom Borders**: defining completely custom character sets (e.g. using numbers or letters for borders).
16
+ - **Styling**: Independent control over border color, background style, and title style.
17
+ - **Positioning**: Placing titles on the `:top` or `:bottom` border (`position`).
18
+ - **Alignment**: Aligning titles to `:left`, `:center`, or `:right` (`alignment`).
19
+
20
+ ## Hotkeys
21
+
22
+ - **Arrow Keys**: Change Border/Content Color
23
+ - **Space**: Cycle Border Type (`border_type`)
24
+ - **c**: Cycle Custom Border Set (`border_set`)
25
+ - **Enter**: Cycle Title Alignment (`title_alignment`)
26
+ - **s**: Cycle Content Style (`style`)
27
+ - **t**: Cycle Title Style (`title_style`)
28
+ - **b**: Cycle Border Style (`border_style`)
29
+ - **q**: Quit
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ ruby examples/widget_box_demo/app.rb
35
+ ```
36
+
37
+ ## Learning Outcomes
38
+
39
+ Use this example if you need to...
40
+ - Group related widgets together.
41
+ - Create distinct "panels" or "cards" in your UI.
42
+ - Style borders to indicate state (e.g., Red border for error state).
43
+ - Understand the difference between `style` (content) and `border_style` (frame).
44
+
45
+ ![Demo](/doc/images/widget_box_demo.png)
@@ -0,0 +1,39 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Calendar Widget Example
7
+
8
+ Demonstrates a monthly calendar with customizable headers and event highlighting.
9
+
10
+ Rendering dates in a grid involves complex calculations for leap years and weekday offsets. This widget handles that logic, letting you focus on displaying dates.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Headers**: Toggling the Month name and Weekday labels.
15
+ - **Surrounding Days**: displaying (or hiding/dimming) days from the previous and next months to fill the grid.
16
+ - **Event Styling**: applying specific styles to individual `Time` or `Date` objects to highlight events.
17
+
18
+ ## Hotkeys
19
+
20
+ - **h**: Toggle Month Header (`show_month_header`)
21
+ - **w**: Toggle Weekday Labels (`show_weekdays_header`)
22
+ - **s**: Toggle Surrounding Days (`show_surrounding`)
23
+ - **e**: Toggle Example Events (`events`)
24
+ - **q**: Quit
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ ruby examples/widget_calendar_demo/app.rb
30
+ ```
31
+
32
+ ## Learning Outcomes
33
+
34
+ Use this example if you need to...
35
+ - Display a date picker.
36
+ - Show a schedule or timeline view.
37
+ - Highlight specific dates (deadlines, holidays) on a calendar grid.
38
+
39
+ ![Demo](/doc/images/widget_calendar_demo.png)
@@ -0,0 +1,27 @@
1
+ # Canvas 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 `Canvas` widget, which provides a high-resolution drawing surface using Braille characters.
8
+
9
+ ## Key Concepts
10
+
11
+ - **Shapes**: Supports `Point`, `Line`, `Rectangle`, `Circle`, and `Map`.
12
+ - **Coordinates**: Uses a float-based coordinate system independent of terminal cells.
13
+ - **Markers**: Can use Braille (high res) or block characters (lower res) for rendering.
14
+
15
+ ## Controls
16
+
17
+ | Key | Action |
18
+ | --- | --- |
19
+ | `q` | Quit |
20
+
21
+ ## Screenshot
22
+
23
+ ![Screenshot of Canvas Widget Demo](../../doc/images/widget_canvas_demo.png)
24
+
25
+ ## Source Code
26
+
27
+ - [app.rb](app.rb)
@@ -0,0 +1,123 @@
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: Canvas Widget
10
+ # Demonstrates how to draw geometric shapes (Points, Lines, Rects, Circles)
11
+ # on a high-resolution canvas.
12
+ class WidgetCanvasDemo
13
+ def initialize
14
+ @x_offset = 0.0
15
+ @y_offset = 0.0
16
+ @time = 0.0
17
+ end
18
+
19
+ def run
20
+ RatatuiRuby.run do |tui|
21
+ @tui = tui
22
+ loop do
23
+ # Animate
24
+ @time += 0.1
25
+ @x_offset = Math.sin(@time) * 20.0
26
+ @y_offset = Math.cos(@time) * 20.0
27
+
28
+ render
29
+ break if handle_input == :quit
30
+
31
+ sleep 0.05
32
+ end
33
+ end
34
+ end
35
+
36
+ private def render
37
+ @tui.draw do |frame|
38
+ # Define shapes
39
+ shapes = []
40
+
41
+ # 1. Static Grid (Lines)
42
+ (-100..100).step(20) do |i|
43
+ shapes << @tui.shape_line(x1: i.to_f, y1: -100.0, x2: i.to_f, y2: 100.0, color: :gray)
44
+ shapes << @tui.shape_line(x1: -100.0, y1: i.to_f, x2: 100.0, y2: i.to_f, color: :gray)
45
+ end
46
+
47
+ # 2. Moving Circle (The "Player")
48
+ shapes << @tui.shape_circle(
49
+ x: @x_offset,
50
+ y: @y_offset,
51
+ radius: 10.0,
52
+ color: :green
53
+ )
54
+
55
+ # 3. Static Rectangle (Target)
56
+ shapes << @tui.shape_rectangle(
57
+ x: 30.0,
58
+ y: 30.0,
59
+ width: 20.0,
60
+ height: 20.0,
61
+ color: :red
62
+ )
63
+
64
+ # 4. Points (Starfield)
65
+ # Deterministic "random" points
66
+ 10.times do |i|
67
+ shapes << @tui.shape_point(
68
+ x: ((i * 37) % 200) - 100.0,
69
+ y: ((i * 19) % 200) - 100.0
70
+ )
71
+ end
72
+
73
+ # 5. Label
74
+ shapes << @tui.shape_line(x1: 0.0, y1: 0.0, x2: @x_offset, y2: @y_offset, color: :yellow)
75
+
76
+ canvas = @tui.canvas(
77
+ shapes:,
78
+ x_bounds: [-100.0, 100.0],
79
+ y_bounds: [-100.0, 100.0],
80
+ marker: :braille,
81
+ block: @tui.block(title: "Canvas Demo", borders: [:all])
82
+ )
83
+
84
+ # Main area for canvas
85
+ layout = @tui.layout_split(
86
+ frame.area,
87
+ direction: :vertical,
88
+ constraints: [
89
+ @tui.constraint_fill(1),
90
+ @tui.constraint_length(3),
91
+ ]
92
+ )
93
+
94
+ frame.render_widget(canvas, layout[0])
95
+
96
+ # Controls
97
+ controls = @tui.paragraph(
98
+ text: [
99
+ @tui.text_line(spans: [
100
+ @tui.text_span(content: "Canvas auto-animates.", style: @tui.style(fg: :yellow)),
101
+ ]),
102
+ @tui.text_line(spans: [
103
+ @tui.text_span(content: "q", style: @tui.style(modifiers: [:bold, :underlined])),
104
+ @tui.text_span(content: ": Quit"),
105
+ ]),
106
+ ],
107
+ block: @tui.block(borders: [:top])
108
+ )
109
+ frame.render_widget(controls, layout[1])
110
+ end
111
+ end
112
+
113
+ def handle_input
114
+ case @tui.poll_event
115
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
116
+ :quit
117
+ else
118
+ # Ignore other events
119
+ end
120
+ end
121
+ end
122
+
123
+ WidgetCanvasDemo.new.run if __FILE__ == $0
@@ -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
+ # Cell Widget Example
7
+
8
+ Demonstrates using `Cell` objects for granular control over individual character grid units.
9
+
10
+ Sometimes you need to render specific characters with unique styles outside of standard widgets. The `Cell` primitive allows you to build custom widgets or inject styled content into Tables.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Custom Widgets**: A `CheckeredBackground` widget built by rendering `Cell`s in a loop.
15
+ - **Table Integration**: Mixing simple Strings and rich `Cell` objects in a Table row.
16
+ - **Overlays**: Using `RatatuiRuby::Overlay` to stack widgets on top of each other.
17
+ - **Modifiers**: Using `rapid_blink`, `bold`, and `dim` on individual cells.
18
+
19
+ ## Hotkeys
20
+
21
+ - **q** / **Ctrl+c**: Quit
22
+
23
+ ## Usage
24
+
25
+ ```bash
26
+ ruby examples/widget_cell_demo/app.rb
27
+ ```
28
+
29
+ ## Learning Outcomes
30
+
31
+ Use this example if you need to...
32
+ - Create a custom widget (like a game board or specialized graph).
33
+ - Style specific cells in a Table (e.g., Green "OK", Red "FAIL").
34
+ - Understand how to position content precisely with `Cell`.
35
+
36
+ ![Demo](/doc/images/widget_cell_demo.png)
@@ -9,15 +9,19 @@ require "ratatui_ruby"
9
9
 
10
10
  # A custom widget that fills its area with a checkered pattern using Cell objects.
11
11
  class CheckeredBackground
12
+ def initialize(tui)
13
+ @tui = tui
14
+ end
15
+
12
16
  def render(area)
13
- cell = RatatuiRuby::Cell.new(char: "░", fg: :dark_gray)
17
+ cell = @tui.cell(char: "░", fg: :dark_gray)
14
18
  commands = []
15
19
  area.height.times do |y|
16
20
  area.width.times do |x|
17
21
  # Checkerboard logic
18
22
  if (x + y).even?
19
23
  # Use a dim cell for the background pattern
20
- commands << RatatuiRuby::Draw.cell(area.x + x, area.y + y, cell)
24
+ commands << @tui.draw_cell(area.x + x, area.y + y, cell)
21
25
  end
22
26
  end
23
27
  end
@@ -26,12 +30,13 @@ class CheckeredBackground
26
30
  end
27
31
 
28
32
  class WidgetCellDemo
29
- def main
33
+ def run
30
34
  RatatuiRuby.run do |tui|
35
+ @tui = tui
31
36
  # Define some reusable cells for our table
32
- ok_cell = RatatuiRuby::Cell.new(char: "OK", fg: :green)
33
- fail_cell = RatatuiRuby::Cell.new(char: "FAIL", fg: :red, modifiers: ["bold"])
34
- pending_cell = RatatuiRuby::Cell.new(char: "...", fg: :yellow, modifiers: ["dim"])
37
+ ok_cell = @tui.cell(char: "OK", fg: :green)
38
+ fail_cell = @tui.cell(char: "FAIL", fg: :red, modifiers: ["bold"])
39
+ pending_cell = @tui.cell(char: "...", fg: :yellow, modifiers: ["dim"])
35
40
 
36
41
  # A mix of Strings and Cells in rows
37
42
  rows = [
@@ -39,47 +44,47 @@ class WidgetCellDemo
39
44
  ["Cache", ok_cell],
40
45
  ["Worker", fail_cell],
41
46
  ["Analytics", pending_cell],
42
- ["Web Server", RatatuiRuby::Cell.new(char: "RESTARTING", fg: :blue, modifiers: ["rapid_blink"])],
47
+ ["Web Server", @tui.cell(char: "RESTARTING", fg: :blue, modifiers: ["rapid_blink"])],
43
48
  ]
44
49
 
45
- table = RatatuiRuby::Table.new(
46
- header: ["Service", RatatuiRuby::Cell.new(char: "Status", modifiers: ["underlined"])],
50
+ table = @tui.table(
51
+ header: ["Service", @tui.cell(char: "Status", modifiers: ["underlined"])],
47
52
  rows:,
48
53
  widths: [
49
- RatatuiRuby::Constraint.percentage(70),
50
- RatatuiRuby::Constraint.percentage(30),
54
+ @tui.constraint_percentage(70),
55
+ @tui.constraint_percentage(30),
51
56
  ],
52
- block: RatatuiRuby::Block.new(title: "System Status", borders: :all),
57
+ block: @tui.block(title: "System Status", borders: :all),
53
58
  column_spacing: 1
54
59
  )
55
60
 
56
61
  # Main loop
57
62
  loop do
58
- tui.draw do |frame|
63
+ @tui.draw do |frame|
59
64
  # Create a layout that holds both widgets
60
65
  # We use a vertical layout:
61
66
  # Top: Custom CheckeredBackground with specific height
62
67
  # Bottom: Table using remaining space
63
- top_area, bottom_area = RatatuiRuby::Layout.split(
68
+ top_area, bottom_area = @tui.layout_split(
64
69
  frame.area,
65
70
  direction: :vertical,
66
71
  constraints: [
67
- RatatuiRuby::Constraint.length(10), # Top section
68
- RatatuiRuby::Constraint.min(0), # Bottom section
72
+ @tui.constraint_length(10), # Top section
73
+ @tui.constraint_min(0), # Bottom section
69
74
  ]
70
75
  )
71
76
 
72
77
  # Top Child: An Overlay of Paragraph on top of CheckeredBackground
73
- overlay = RatatuiRuby::Overlay.new(
78
+ overlay = @tui.overlay(
74
79
  layers: [
75
- CheckeredBackground.new,
76
- RatatuiRuby::Center.new(
80
+ CheckeredBackground.new(@tui),
81
+ @tui.center(
77
82
  width_percent: 50,
78
83
  height_percent: 50,
79
- child: RatatuiRuby::Paragraph.new(
84
+ child: @tui.paragraph(
80
85
  text: "Custom Widget Demo\n(CheckeredBackground)",
81
86
  alignment: :center,
82
- block: RatatuiRuby::Block.new(borders: :all, title: "Overlay")
87
+ block: @tui.block(borders: :all, title: "Overlay")
83
88
  )
84
89
  ),
85
90
  ]
@@ -90,9 +95,11 @@ class WidgetCellDemo
90
95
  frame.render_widget(table, bottom_area)
91
96
  end
92
97
 
93
- event = RatatuiRuby.poll_event
94
- if event.is_a?(RatatuiRuby::Event::Key) && (event.code == "q" || (event.code == "c" && event.modifiers.include?("ctrl")))
98
+ case @tui.poll_event
99
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
95
100
  break
101
+ else
102
+ nil
96
103
  end
97
104
  end
98
105
  end
@@ -100,5 +107,5 @@ class WidgetCellDemo
100
107
  end
101
108
 
102
109
  if __FILE__ == $0
103
- WidgetCellDemo.new.main
110
+ WidgetCellDemo.new.run
104
111
  end