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
@@ -1,201 +0,0 @@
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
- require_relative "input"
7
- require_relative "palette"
8
- require_relative "clipboard"
9
- require_relative "copy_dialog"
10
-
11
- # Orchestrates layout and rendering of the color picker UI.
12
- #
13
- # Building a complete color picker UI involves layout calculation, widget
14
- # composition, and coordinate tracking for hit testing. Keeping this logic
15
- # scattered across the main app makes the app harder to read and test.
16
- #
17
- # This object owns the layout logic. It orchestrates all sections. It calculates
18
- # and caches rects for hit testing.
19
- #
20
- # Use it to encapsulate complex UI composition.
21
- #
22
- # === Example
23
- #
24
- # scene = Scene.new(tui)
25
- # scene.render(frame, input:, palette:, clipboard:, dialog:)
26
- #
27
- # # For hit testing:
28
- # rect = scene.export_rect
29
- # if rect.contains?(x, y)
30
- # # Handle click
31
- # end
32
- class Scene
33
- def initialize(tui)
34
- @tui = tui
35
- @export_area_rect = nil
36
- @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
37
- end
38
-
39
- # Renders the complete UI given all model objects.
40
- #
41
- # Calculates layout once per frame. Renders input, palette, export, controls,
42
- # and dialog sections. Caches the export area rect for hit testing.
43
- #
44
- # [frame] Frame object from RatatuiRuby.draw block
45
- # [input] Input object for text input display
46
- # [palette] Palette object for color display
47
- # [clipboard] Clipboard object for feedback message
48
- # [dialog] CopyDialog object for confirmation dialog
49
- #
50
- # === Example
51
- #
52
- # scene.render(frame, input: @input, palette: @palette, clipboard: @clipboard, dialog: @dialog)
53
- def render(frame, input:, palette:, clipboard:, dialog:)
54
- input_area, rest = @tui.layout_split(
55
- frame.area,
56
- direction: :vertical,
57
- constraints: [
58
- @tui.constraint_length(3),
59
- @tui.constraint_fill(1),
60
- ]
61
- )
62
-
63
- color_area, control_area = @tui.layout_split(
64
- rest,
65
- direction: :vertical,
66
- constraints: [
67
- @tui.constraint_length(14),
68
- @tui.constraint_fill(1),
69
- ]
70
- )
71
-
72
- harmony_area, @export_area_rect = @tui.layout_split(
73
- color_area,
74
- direction: :vertical,
75
- constraints: [
76
- @tui.constraint_length(7),
77
- @tui.constraint_fill(1),
78
- ]
79
- )
80
-
81
- frame.render_widget(input.render(@tui), input_area)
82
- frame.render_widget(build_palette_section(palette, harmony_area), harmony_area)
83
- frame.render_widget(build_export_section(palette), @export_area_rect)
84
- frame.render_widget(build_controls_section(clipboard), control_area)
85
-
86
- if dialog.active?
87
- dialog_center = calculate_center_area(frame.area, 40, 8)
88
- frame.render_widget(@tui.clear, frame.area)
89
- frame.render_widget(dialog.render(@tui, dialog_center), dialog_center)
90
- end
91
- end
92
-
93
- # The cached rectangle of the export formats section, used for hit testing.
94
- #
95
- # Populated during #render. Use this to detect clicks on the export section.
96
- #
97
- # === Example
98
- #
99
- # scene.render(frame, ...)
100
- # if scene.export_rect.contains?(x, y)
101
- # # Click on export section
102
- # end
103
- def export_rect
104
- @export_area_rect
105
- end
106
-
107
- private def build_palette_section(palette, _harmony_area)
108
- if palette.main.nil?
109
- @tui.paragraph(text: "No color selected")
110
- else
111
- blocks = palette.as_blocks(@tui)
112
- @tui.layout(
113
- direction: :horizontal,
114
- constraints: Array.new(blocks.size) { @tui.constraint_fill(1) },
115
- children: blocks
116
- )
117
- end
118
- end
119
-
120
- private def build_export_section(palette)
121
- if palette.main.nil?
122
- @tui.block(
123
- title: "Export Formats",
124
- borders: [:all],
125
- children: [
126
- @tui.paragraph(
127
- text: @tui.text_line(spans: [
128
- @tui.text_span(content: "Enter a color to see formats"),
129
- ])
130
- ),
131
- ]
132
- )
133
- else
134
- color = palette.main
135
- hex = color.hex
136
- rgb = color.rgb
137
- hsl = color.hsl_string
138
- text_color = color.contrasting_text_color
139
- bg_style = @tui.style(bg: hex, fg: text_color)
140
-
141
- @tui.block(
142
- title: "Export Formats",
143
- borders: [:all],
144
- style: bg_style,
145
- children: [
146
- @tui.paragraph(
147
- text: [
148
- @tui.text_line(spans: [
149
- @tui.text_span(content: "HEX: ", style: bg_style),
150
- @tui.text_span(content: hex, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
151
- ]),
152
- @tui.text_line(spans: [
153
- @tui.text_span(content: "RGB: ", style: bg_style),
154
- @tui.text_span(content: rgb, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
155
- ]),
156
- @tui.text_line(spans: [
157
- @tui.text_span(content: "HSL: ", style: bg_style),
158
- @tui.text_span(content: hsl, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
159
- ]),
160
- ]
161
- ),
162
- ]
163
- )
164
- end
165
- end
166
-
167
- private def build_controls_section(clipboard)
168
- control_lines = [
169
- @tui.text_line(spans: [
170
- @tui.text_span(content: "a-z/0-9", style: @hotkey_style),
171
- @tui.text_span(content: ": Type "),
172
- @tui.text_span(content: "enter", style: @hotkey_style),
173
- @tui.text_span(content: ": Parse "),
174
- @tui.text_span(content: "bksp", style: @hotkey_style),
175
- @tui.text_span(content: ": Erase "),
176
- @tui.text_span(content: "esc", style: @hotkey_style),
177
- @tui.text_span(content: ": Quit"),
178
- ]),
179
- ]
180
-
181
- unless clipboard.message.empty?
182
- control_lines << @tui.text_line(spans: [
183
- @tui.text_span(content: clipboard.message, style: @tui.style(fg: :green, modifiers: [:bold])),
184
- ])
185
- end
186
-
187
- @tui.block(
188
- title: "Controls",
189
- borders: [:all],
190
- children: [
191
- @tui.paragraph(text: control_lines),
192
- ]
193
- )
194
- end
195
-
196
- private def calculate_center_area(parent_area, width, height)
197
- x = (parent_area.width - width) / 2
198
- y = (parent_area.height - height) / 2
199
- @tui.rect(x:, y:, width:, height:)
200
- end
201
- end
@@ -1,67 +0,0 @@
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
- # A demo showing Block padding capabilities
10
- class WidgetBlockPadding
11
- def run
12
- RatatuiRuby.run do |tui|
13
- loop do
14
- # 1. Uniform Padding
15
- block1 = RatatuiRuby::Block.new(
16
- title: "Uniform Padding (2)",
17
- borders: [:all],
18
- padding: 2
19
- )
20
- para1 = RatatuiRuby::Paragraph.new(
21
- text: "This text is padded by 2 on all sides.\nNotice the space between the border and this text.",
22
- block: block1
23
- )
24
-
25
- # 2. Directional Padding
26
- block2 = RatatuiRuby::Block.new(
27
- title: "Directional Padding [Left: 4, Right: 0, Top: 2, Bottom: 0]",
28
- borders: [:all],
29
- padding: [4, 0, 2, 0]
30
- )
31
- para2 = RatatuiRuby::Paragraph.new(
32
- text: "This text has different padding per side.\nLeft: 4, Top: 2.",
33
- block: block2
34
- )
35
-
36
- # Instructions
37
- para3 = RatatuiRuby::Paragraph.new(
38
- text: "Press 'q' to quit."
39
- )
40
-
41
- tui.draw do |frame|
42
- # Layout
43
- areas = RatatuiRuby::Layout.split(
44
- frame.area,
45
- direction: :vertical,
46
- constraints: [
47
- RatatuiRuby::Constraint.length(10), # Uniform Padding
48
- RatatuiRuby::Constraint.length(10), # Directional Padding
49
- RatatuiRuby::Constraint.min(0),
50
- ]
51
- )
52
-
53
- frame.render_widget(para1, areas[0])
54
- frame.render_widget(para2, areas[1])
55
- frame.render_widget(para3, areas[2])
56
- end
57
-
58
- event = tui.poll_event
59
- break if event == "q" || event == :ctrl_c
60
- end
61
- end
62
- end
63
- end
64
-
65
- if __FILE__ == $0
66
- WidgetBlockPadding.new.run
67
- end
@@ -1,69 +0,0 @@
1
- # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- #
3
- # SPDX-License-Identifier: AGPL-3.0-or-later
4
-
5
- # frozen_string_literal: true
6
-
7
- $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
8
- require "ratatui_ruby"
9
-
10
- # Initialize the terminal
11
- class WidgetBlockTitles
12
- def run
13
- RatatuiRuby.run do |tui|
14
- loop do
15
- # Create a layout with multiple blocks demonstrating titles
16
- blocks = [
17
- tui.block(
18
- titles: [
19
- { content: "Top Left", alignment: :left, position: :top },
20
- { content: "Top Right", alignment: :right, position: :top },
21
- ],
22
- borders: [:all],
23
- border_color: "cyan"
24
- ),
25
- tui.block(
26
- titles: [
27
- { content: "Bottom Left", alignment: :left, position: :bottom },
28
- { content: "Bottom Center", alignment: :center, position: :bottom },
29
- { content: "Bottom Right", alignment: :right, position: :bottom },
30
- ],
31
- borders: [:all],
32
- border_color: "magenta"
33
- ),
34
- tui.block(
35
- titles: [
36
- "Simple String Title (Top Left Default)",
37
- { content: "Mixed Title", alignment: :center, position: :bottom },
38
- ],
39
- borders: [:all],
40
- border_color: "green"
41
- ),
42
- ]
43
-
44
- tui.draw do |frame|
45
- layout = tui.layout_split(
46
- frame.area,
47
- direction: :vertical,
48
- constraints: [
49
- tui.constraint(:length, 10),
50
- tui.constraint(:length, 10),
51
- tui.constraint(:length, 10),
52
- ]
53
- )
54
-
55
- layout.each_with_index do |area, i|
56
- frame.render_widget(blocks[i], area) if blocks[i]
57
- end
58
- end
59
-
60
- event = tui.poll_event
61
- break if event == "q" || event == :ctrl_c
62
- end
63
- end
64
- end
65
- end
66
-
67
- if __FILE__ == $0
68
- WidgetBlockTitles.new.run
69
- end
@@ -1,141 +0,0 @@
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
- # Styled List Example
10
- # Demonstrates advanced styling options for the List widget.
11
- class WidgetListStyles
12
- attr_reader :selected_index, :highlight_spacing
13
-
14
- def initialize
15
- @items = ["Item 1", "Item 2", "Item 3"]
16
- @selected_index = nil
17
-
18
- @directions = [
19
- { name: "Top to Bottom", direction: :top_to_bottom },
20
- { name: "Bottom to Top", direction: :bottom_to_top },
21
- ]
22
- @direction_index = 0
23
-
24
- @highlight_spacings = [
25
- { name: "When Selected", spacing: :when_selected },
26
- { name: "Always", spacing: :always },
27
- { name: "Never", spacing: :never },
28
- ]
29
- @highlight_spacing_index = 0
30
-
31
- @repeat_modes = [
32
- { name: "Off", repeat: false },
33
- { name: "On", repeat: true },
34
- ]
35
- @repeat_index = 0
36
- end
37
-
38
- def run
39
- RatatuiRuby.run do |tui|
40
- @tui = tui
41
- @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
42
-
43
- loop do
44
- render
45
- break if handle_input == :quit
46
- sleep 0.05
47
- end
48
- end
49
- end
50
-
51
- private def render
52
- selection_label = @selected_index.nil? ? "none" : @selected_index.to_s
53
- direction_config = @directions[@direction_index]
54
- spacing_config = @highlight_spacings[@highlight_spacing_index]
55
- repeat_config = @repeat_modes[@repeat_index]
56
-
57
- @tui.draw do |frame|
58
- # Split into main content and control panel
59
- main_area, control_area = @tui.layout_split(
60
- frame.area,
61
- direction: :vertical,
62
- constraints: [
63
- @tui.constraint_fill(1),
64
- @tui.constraint_length(5),
65
- ]
66
- )
67
-
68
- # Render list
69
- main_list = @tui.list(
70
- items: @items,
71
- selected_index: @selected_index,
72
- style: @tui.style(fg: :white, bg: :black),
73
- highlight_style: @tui.style(fg: :blue, bg: :white, modifiers: [:bold]),
74
- highlight_symbol: ">> ",
75
- repeat_highlight_symbol: repeat_config[:repeat],
76
- highlight_spacing: spacing_config[:spacing],
77
- direction: direction_config[:direction],
78
- block: @tui.block(
79
- title: "Items",
80
- borders: [:all]
81
- )
82
- )
83
- frame.render_widget(main_list, main_area)
84
-
85
- # Render control panel
86
- control_panel = @tui.block(
87
- title: "Controls",
88
- borders: [:all],
89
- children: [
90
- @tui.paragraph(
91
- text: [
92
- @tui.text_line(spans: [
93
- @tui.text_span(content: "↑/↓", style: @hotkey_style),
94
- @tui.text_span(content: ": Select (#{selection_label}) "),
95
- @tui.text_span(content: "x", style: @hotkey_style),
96
- @tui.text_span(content: ": Toggle Selection "),
97
- @tui.text_span(content: "q", style: @hotkey_style),
98
- @tui.text_span(content: ": Quit"),
99
- ]),
100
- @tui.text_line(spans: [
101
- @tui.text_span(content: "d", style: @hotkey_style),
102
- @tui.text_span(content: ": Direction (#{direction_config[:name]}) "),
103
- @tui.text_span(content: "s", style: @hotkey_style),
104
- @tui.text_span(content: ": Spacing (#{spacing_config[:name]})"),
105
- ]),
106
- @tui.text_line(spans: [
107
- @tui.text_span(content: "r", style: @hotkey_style),
108
- @tui.text_span(content: ": Repeat Symbol (#{repeat_config[:name]})"),
109
- ]),
110
- ]
111
- ),
112
- ]
113
- )
114
- frame.render_widget(control_panel, control_area)
115
- end
116
- end
117
-
118
- private def handle_input
119
- case @tui.poll_event
120
- in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
121
- :quit
122
- in type: :key, code: "up"
123
- @selected_index = (@selected_index || 0) - 1
124
- @selected_index = @items.size - 1 if @selected_index.negative?
125
- in type: :key, code: "down"
126
- @selected_index = ((@selected_index || -1) + 1) % @items.size
127
- in type: :key, code: "d"
128
- @direction_index = (@direction_index + 1) % @directions.size
129
- in type: :key, code: "s"
130
- @highlight_spacing_index = (@highlight_spacing_index + 1) % @highlight_spacings.size
131
- in type: :key, code: "r"
132
- @repeat_index = (@repeat_index + 1) % @repeat_modes.size
133
- in type: :key, code: "x"
134
- @selected_index = @selected_index.nil? ? 0 : nil
135
- else
136
- nil
137
- end
138
- end
139
- end
140
-
141
- WidgetListStyles.new.run if __FILE__ == $PROGRAM_NAME
@@ -1,95 +0,0 @@
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 structured data layout with flexible column distribution.
10
- #
11
- # Data is often multidimensional. You need to show relationships between fields (Name, Age, ID). Aligning columns manually in a monospaced environment is painful and error-prone.
12
- #
13
- # This demo showcases the <tt>Table</tt> widget's flex modes. It renders multiple tables demonstrating how data is distributed across the available space using <tt>:legacy</tt>, <tt>:space_between</tt>, and <tt>:space_around</tt> settings.
14
- #
15
- # Use it to understand how to build perfectly aligned grids for database records, logs, or file lists.
16
- #
17
- # === Example
18
- #
19
- # Run the demo from the terminal:
20
- #
21
- # ruby examples/widget_table_flex/app.rb
22
- #
23
- # rdoc-image:/doc/images/widget_table_flex.png
24
- class WidgetTableFlex
25
- def run
26
- RatatuiRuby.run do |tui|
27
- loop do
28
- render(tui)
29
- break if handle_input(tui) == :quit
30
- end
31
- end
32
- end
33
-
34
- def render(tui)
35
- tui.draw do |frame|
36
- chunks = tui.layout_split(
37
- frame.area,
38
- direction: :vertical,
39
- constraints: [
40
- tui.constraint_length(3),
41
- tui.constraint_fill(1),
42
- tui.constraint_fill(1),
43
- tui.constraint_fill(1),
44
- ]
45
- )
46
-
47
- frame.render_widget(
48
- tui.paragraph(
49
- text: "Table Flex Layout (press 'q' to quit)",
50
- block: tui.block(title: "Header", borders: [:all])
51
- ),
52
- chunks[0]
53
- )
54
-
55
- frame.render_widget(
56
- tui.table(
57
- header: ["Legacy (Default)", "Table"],
58
- rows: [["Item 1", "Item 2"], ["Item 3", "Item 4"]],
59
- widths: [tui.constraint_length(20), tui.constraint_length(20)],
60
- block: tui.block(title: "Flex: :legacy (Default)", borders: [:all])
61
- ),
62
- chunks[1]
63
- )
64
-
65
- frame.render_widget(
66
- tui.table(
67
- header: ["Space", "Between"],
68
- rows: [["A", "B"], ["C", "D"]],
69
- widths: [tui.constraint_length(20), tui.constraint_length(20)],
70
- block: tui.block(title: "Flex: :space_between", borders: [:all]),
71
- flex: :space_between
72
- ),
73
- chunks[2]
74
- )
75
-
76
- frame.render_widget(
77
- tui.table(
78
- header: ["Space", "Around"],
79
- rows: [["E", "F"], ["G", "H"]],
80
- widths: [tui.constraint_length(20), tui.constraint_length(20)],
81
- block: tui.block(title: "Flex: :space_around", borders: [:all]),
82
- flex: :space_around
83
- ),
84
- chunks[3]
85
- )
86
- end
87
- end
88
-
89
- def handle_input(tui)
90
- event = tui.poll_event
91
- :quit if event == "q" || event == :ctrl_c
92
- end
93
- end
94
-
95
- WidgetTableFlex.new.run if __FILE__ == $0
@@ -1,11 +0,0 @@
1
- # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- #
3
- # SPDX-License-Identifier: AGPL-3.0-or-later
4
-
5
- class WidgetBlockPadding
6
- # @public
7
- def self.new: () -> WidgetBlockPadding
8
-
9
- # @public
10
- def run: () -> void
11
- end
@@ -1,11 +0,0 @@
1
- # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- #
3
- # SPDX-License-Identifier: AGPL-3.0-or-later
4
-
5
- class WidgetBlockTitles
6
- # @public
7
- def self.new: () -> WidgetBlockTitles
8
-
9
- # @public
10
- def run: () -> void
11
- end
@@ -1,11 +0,0 @@
1
- # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- #
3
- # SPDX-License-Identifier: AGPL-3.0-or-later
4
-
5
- class WidgetListStyles
6
- # @public
7
- def self.new: () -> WidgetListStyles
8
-
9
- # @public
10
- def run: () -> void
11
- end
@@ -1,41 +0,0 @@
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
- # ComparisonLinks manages the git comparison links at the bottom of the changelog.
7
- class ComparisonLinks
8
- PATTERN = /^(\[Unreleased\]: .*)$/m
9
-
10
- # Extracts the comparison links from the given content.
11
- def self.parse(content)
12
- match = content.match(PATTERN)
13
- new(match[1].strip) if match
14
- end
15
-
16
- # Creates a new ComparisonLinks from the given links text.
17
- def initialize(links)
18
- @links = links.dup
19
- end
20
-
21
- # Updates the comparison links for the new version.
22
- def update(new_version)
23
- pattern = %r{^\[Unreleased\]: (.*?/compare/)v(.*)\.\.\.HEAD$}
24
- match = @links.match(pattern)
25
- return unless match
26
-
27
- base_url = match[1]
28
- prev_version = match[2]
29
-
30
- new_unreleased = "[Unreleased]: #{base_url}v#{new_version}...HEAD"
31
- new_version_link = "[#{new_version}]: #{base_url}v#{prev_version}...v#{new_version}"
32
-
33
- @links.sub!(pattern, "#{new_unreleased}\n#{new_version_link}")
34
- nil
35
- end
36
-
37
- # Returns the current state of the links as a string.
38
- def to_s
39
- @links
40
- end
41
- end