ratatui_ruby 0.3.1 → 0.4.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 (300) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +14 -12
  3. data/.builds/ruby-3.3.yml +14 -12
  4. data/.builds/ruby-3.4.yml +14 -12
  5. data/.builds/ruby-4.0.0.yml +14 -12
  6. data/AGENTS.md +54 -13
  7. data/CHANGELOG.md +186 -1
  8. data/README.md +17 -15
  9. data/doc/application_architecture.md +116 -0
  10. data/doc/application_testing.md +12 -7
  11. data/doc/contributors/better_dx.md +543 -0
  12. data/doc/contributors/design/ruby_frontend.md +1 -1
  13. data/doc/contributors/developing_examples.md +203 -0
  14. data/doc/contributors/documentation_style.md +97 -0
  15. data/doc/contributors/dwim_dx.md +366 -0
  16. data/doc/contributors/example_analysis.md +82 -0
  17. data/doc/custom.css +14 -0
  18. data/doc/event_handling.md +119 -0
  19. data/doc/images/all_events.png +0 -0
  20. data/doc/images/analytics.png +0 -0
  21. data/doc/images/block_padding.png +0 -0
  22. data/doc/images/block_titles.png +0 -0
  23. data/doc/images/box_demo.png +0 -0
  24. data/doc/images/calendar_demo.png +0 -0
  25. data/doc/images/cell_demo.png +0 -0
  26. data/doc/images/chart_demo.png +0 -0
  27. data/doc/images/custom_widget.png +0 -0
  28. data/doc/images/flex_layout.png +0 -0
  29. data/doc/images/gauge_demo.png +0 -0
  30. data/doc/images/hit_test.png +0 -0
  31. data/doc/images/line_gauge_demo.png +0 -0
  32. data/doc/images/list_demo.png +0 -0
  33. data/doc/images/list_styles.png +0 -0
  34. data/doc/images/login_form.png +0 -0
  35. data/doc/images/map_demo.png +0 -0
  36. data/doc/images/mouse_events.png +0 -0
  37. data/doc/images/popup_demo.png +0 -0
  38. data/doc/images/quickstart_dsl.png +0 -0
  39. data/doc/images/quickstart_lifecycle.png +0 -0
  40. data/doc/images/ratatui_logo_demo.png +0 -0
  41. data/doc/images/readme_usage.png +0 -0
  42. data/doc/images/rich_text.png +0 -0
  43. data/doc/images/scroll_text.png +0 -0
  44. data/doc/images/scrollbar_demo.png +0 -0
  45. data/doc/images/sparkline_demo.png +0 -0
  46. data/doc/images/table_flex.png +0 -0
  47. data/doc/images/table_select.png +0 -0
  48. data/doc/images/widget_style_colors.png +0 -0
  49. data/doc/index.md +1 -0
  50. data/doc/interactive_design.md +121 -0
  51. data/doc/quickstart.md +147 -72
  52. data/examples/all_events/app.rb +169 -0
  53. data/examples/all_events/app.rbs +7 -0
  54. data/examples/all_events/test_app.rb +139 -0
  55. data/examples/analytics/app.rb +258 -0
  56. data/examples/analytics/app.rbs +7 -0
  57. data/examples/analytics/test_app.rb +132 -0
  58. data/examples/block_padding/app.rb +63 -0
  59. data/examples/block_padding/app.rbs +7 -0
  60. data/examples/block_padding/test_app.rb +31 -0
  61. data/examples/block_titles/app.rb +61 -0
  62. data/examples/block_titles/app.rbs +7 -0
  63. data/examples/block_titles/test_app.rb +34 -0
  64. data/examples/box_demo/app.rb +216 -0
  65. data/examples/box_demo/app.rbs +7 -0
  66. data/examples/box_demo/test_app.rb +88 -0
  67. data/examples/calendar_demo/app.rb +101 -0
  68. data/examples/calendar_demo/app.rbs +7 -0
  69. data/examples/calendar_demo/test_app.rb +108 -0
  70. data/examples/cell_demo/app.rb +108 -0
  71. data/examples/cell_demo/app.rbs +7 -0
  72. data/examples/cell_demo/test_app.rb +36 -0
  73. data/examples/chart_demo/app.rb +203 -0
  74. data/examples/chart_demo/app.rbs +7 -0
  75. data/examples/chart_demo/test_app.rb +102 -0
  76. data/examples/custom_widget/app.rb +51 -0
  77. data/examples/custom_widget/app.rbs +7 -0
  78. data/examples/custom_widget/test_app.rb +30 -0
  79. data/examples/flex_layout/app.rb +156 -0
  80. data/examples/flex_layout/app.rbs +7 -0
  81. data/examples/flex_layout/test_app.rb +65 -0
  82. data/examples/gauge_demo/app.rb +182 -0
  83. data/examples/gauge_demo/app.rbs +7 -0
  84. data/examples/gauge_demo/test_app.rb +120 -0
  85. data/examples/hit_test/app.rb +175 -0
  86. data/examples/hit_test/app.rbs +7 -0
  87. data/examples/hit_test/test_app.rb +102 -0
  88. data/examples/line_gauge_demo/app.rb +190 -0
  89. data/examples/line_gauge_demo/app.rbs +7 -0
  90. data/examples/line_gauge_demo/test_app.rb +129 -0
  91. data/examples/list_demo/app.rb +253 -0
  92. data/examples/list_demo/app.rbs +12 -0
  93. data/examples/list_demo/test_app.rb +237 -0
  94. data/examples/list_styles/app.rb +140 -0
  95. data/examples/list_styles/app.rbs +7 -0
  96. data/examples/list_styles/test_app.rb +157 -0
  97. data/examples/{login_form.rb → login_form/app.rb} +12 -16
  98. data/examples/login_form/app.rbs +7 -0
  99. data/examples/login_form/test_app.rb +51 -0
  100. data/examples/map_demo/app.rb +90 -0
  101. data/examples/map_demo/app.rbs +7 -0
  102. data/examples/map_demo/test_app.rb +149 -0
  103. data/examples/{mouse_events.rb → mouse_events/app.rb} +29 -27
  104. data/examples/mouse_events/app.rbs +7 -0
  105. data/examples/mouse_events/test_app.rb +53 -0
  106. data/examples/{popup_demo.rb → popup_demo/app.rb} +15 -17
  107. data/examples/popup_demo/app.rbs +7 -0
  108. data/examples/{test_popup_demo.rb → popup_demo/test_app.rb} +18 -26
  109. data/examples/quickstart_dsl/app.rb +36 -0
  110. data/examples/quickstart_dsl/app.rbs +7 -0
  111. data/examples/quickstart_dsl/test_app.rb +29 -0
  112. data/examples/quickstart_lifecycle/app.rb +39 -0
  113. data/examples/quickstart_lifecycle/app.rbs +7 -0
  114. data/examples/quickstart_lifecycle/test_app.rb +29 -0
  115. data/examples/ratatui_logo_demo/app.rb +79 -0
  116. data/examples/ratatui_logo_demo/app.rbs +7 -0
  117. data/examples/ratatui_logo_demo/test_app.rb +51 -0
  118. data/examples/ratatui_mascot_demo/app.rb +84 -0
  119. data/examples/ratatui_mascot_demo/app.rbs +7 -0
  120. data/examples/ratatui_mascot_demo/test_app.rb +47 -0
  121. data/examples/readme_usage/app.rb +29 -0
  122. data/examples/readme_usage/app.rbs +7 -0
  123. data/examples/readme_usage/test_app.rb +29 -0
  124. data/examples/rich_text/app.rb +141 -0
  125. data/examples/rich_text/app.rbs +7 -0
  126. data/examples/rich_text/test_app.rb +166 -0
  127. data/examples/scroll_text/app.rb +103 -0
  128. data/examples/scroll_text/app.rbs +7 -0
  129. data/examples/scroll_text/test_app.rb +110 -0
  130. data/examples/scrollbar_demo/app.rb +143 -0
  131. data/examples/scrollbar_demo/app.rbs +7 -0
  132. data/examples/scrollbar_demo/test_app.rb +77 -0
  133. data/examples/sparkline_demo/app.rb +240 -0
  134. data/examples/sparkline_demo/app.rbs +10 -0
  135. data/examples/sparkline_demo/test_app.rb +107 -0
  136. data/examples/table_flex/app.rb +65 -0
  137. data/examples/table_flex/app.rbs +7 -0
  138. data/examples/table_flex/test_app.rb +36 -0
  139. data/examples/table_select/app.rb +198 -0
  140. data/examples/table_select/app.rbs +7 -0
  141. data/examples/table_select/test_app.rb +180 -0
  142. data/examples/widget_style_colors/app.rb +104 -0
  143. data/examples/widget_style_colors/app.rbs +14 -0
  144. data/examples/widget_style_colors/test_app.rb +48 -0
  145. data/ext/ratatui_ruby/Cargo.lock +889 -115
  146. data/ext/ratatui_ruby/Cargo.toml +4 -3
  147. data/ext/ratatui_ruby/clippy.toml +7 -0
  148. data/ext/ratatui_ruby/extconf.rb +7 -0
  149. data/ext/ratatui_ruby/src/events.rs +218 -229
  150. data/ext/ratatui_ruby/src/lib.rs +38 -10
  151. data/ext/ratatui_ruby/src/rendering.rs +90 -10
  152. data/ext/ratatui_ruby/src/style.rs +281 -98
  153. data/ext/ratatui_ruby/src/terminal.rs +119 -25
  154. data/ext/ratatui_ruby/src/text.rs +171 -0
  155. data/ext/ratatui_ruby/src/widgets/barchart.rs +97 -24
  156. data/ext/ratatui_ruby/src/widgets/block.rs +31 -3
  157. data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
  158. data/ext/ratatui_ruby/src/widgets/canvas.rs +46 -29
  159. data/ext/ratatui_ruby/src/widgets/chart.rs +69 -27
  160. data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
  161. data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
  162. data/ext/ratatui_ruby/src/widgets/layout.rs +218 -15
  163. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
  164. data/ext/ratatui_ruby/src/widgets/list.rs +91 -11
  165. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  166. data/ext/ratatui_ruby/src/widgets/overlay.rs +3 -2
  167. data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
  168. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +29 -0
  169. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +44 -0
  170. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +59 -7
  171. data/ext/ratatui_ruby/src/widgets/sparkline.rs +70 -6
  172. data/ext/ratatui_ruby/src/widgets/table.rs +173 -64
  173. data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
  174. data/lib/ratatui_ruby/cell.rb +166 -0
  175. data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
  176. data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
  177. data/lib/ratatui_ruby/event/key.rb +211 -0
  178. data/lib/ratatui_ruby/event/mouse.rb +124 -0
  179. data/lib/ratatui_ruby/event/paste.rb +71 -0
  180. data/lib/ratatui_ruby/event/resize.rb +80 -0
  181. data/lib/ratatui_ruby/event.rb +79 -0
  182. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
  183. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +27 -0
  184. data/lib/ratatui_ruby/schema/bar_chart.rb +228 -19
  185. data/lib/ratatui_ruby/schema/block.rb +186 -14
  186. data/lib/ratatui_ruby/schema/calendar.rb +74 -17
  187. data/lib/ratatui_ruby/schema/canvas.rb +215 -48
  188. data/lib/ratatui_ruby/schema/center.rb +49 -11
  189. data/lib/ratatui_ruby/schema/chart.rb +151 -41
  190. data/lib/ratatui_ruby/schema/clear.rb +41 -72
  191. data/lib/ratatui_ruby/schema/constraint.rb +82 -22
  192. data/lib/ratatui_ruby/schema/cursor.rb +27 -9
  193. data/lib/ratatui_ruby/schema/draw.rb +53 -0
  194. data/lib/ratatui_ruby/schema/gauge.rb +59 -15
  195. data/lib/ratatui_ruby/schema/layout.rb +95 -13
  196. data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
  197. data/lib/ratatui_ruby/schema/list.rb +93 -19
  198. data/lib/ratatui_ruby/schema/overlay.rb +34 -8
  199. data/lib/ratatui_ruby/schema/paragraph.rb +87 -30
  200. data/lib/ratatui_ruby/schema/ratatui_logo.rb +25 -0
  201. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +29 -0
  202. data/lib/ratatui_ruby/schema/rect.rb +64 -15
  203. data/lib/ratatui_ruby/schema/scrollbar.rb +132 -24
  204. data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
  205. data/lib/ratatui_ruby/schema/sparkline.rb +122 -15
  206. data/lib/ratatui_ruby/schema/style.rb +49 -21
  207. data/lib/ratatui_ruby/schema/table.rb +119 -21
  208. data/lib/ratatui_ruby/schema/tabs.rb +75 -13
  209. data/lib/ratatui_ruby/schema/text.rb +90 -0
  210. data/lib/ratatui_ruby/session.rb +146 -0
  211. data/lib/ratatui_ruby/test_helper.rb +156 -13
  212. data/lib/ratatui_ruby/version.rb +1 -1
  213. data/lib/ratatui_ruby.rb +143 -23
  214. data/sig/ratatui_ruby/event.rbs +69 -0
  215. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -1
  216. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
  217. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
  218. data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
  219. data/sig/ratatui_ruby/schema/block.rbs +5 -4
  220. data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
  221. data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
  222. data/sig/ratatui_ruby/schema/center.rbs +3 -3
  223. data/sig/ratatui_ruby/schema/chart.rbs +8 -5
  224. data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
  225. data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
  226. data/sig/ratatui_ruby/schema/draw.rbs +23 -0
  227. data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
  228. data/sig/ratatui_ruby/schema/layout.rbs +11 -1
  229. data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
  230. data/sig/ratatui_ruby/schema/list.rbs +5 -1
  231. data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
  232. data/{lib/ratatui_ruby/output.rb → sig/ratatui_ruby/schema/ratatui_logo.rbs} +3 -2
  233. data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
  234. data/sig/ratatui_ruby/schema/rect.rbs +2 -1
  235. data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
  236. data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
  237. data/sig/ratatui_ruby/schema/table.rbs +8 -1
  238. data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
  239. data/sig/ratatui_ruby/schema/text.rbs +22 -0
  240. data/tasks/resources/build.yml.erb +13 -11
  241. data/tasks/terminal_preview/app_screenshot.rb +35 -0
  242. data/tasks/terminal_preview/crash_report.rb +54 -0
  243. data/tasks/terminal_preview/example_app.rb +25 -0
  244. data/tasks/terminal_preview/launcher_script.rb +48 -0
  245. data/tasks/terminal_preview/preview_collection.rb +60 -0
  246. data/tasks/terminal_preview/preview_timing.rb +22 -0
  247. data/tasks/terminal_preview/safety_confirmation.rb +58 -0
  248. data/tasks/terminal_preview/saved_screenshot.rb +55 -0
  249. data/tasks/terminal_preview/system_appearance.rb +11 -0
  250. data/tasks/terminal_preview/terminal_window.rb +138 -0
  251. data/tasks/terminal_preview/window_id.rb +14 -0
  252. data/tasks/terminal_preview.rake +28 -0
  253. data/tasks/test.rake +1 -1
  254. metadata +174 -53
  255. data/doc/images/examples-analytics.rb.png +0 -0
  256. data/doc/images/examples-box_demo.rb.png +0 -0
  257. data/doc/images/examples-calendar_demo.rb.png +0 -0
  258. data/doc/images/examples-chart_demo.rb.png +0 -0
  259. data/doc/images/examples-custom_widget.rb.png +0 -0
  260. data/doc/images/examples-dashboard.rb.png +0 -0
  261. data/doc/images/examples-list_styles.rb.png +0 -0
  262. data/doc/images/examples-login_form.rb.png +0 -0
  263. data/doc/images/examples-map_demo.rb.png +0 -0
  264. data/doc/images/examples-mouse_events.rb.png +0 -0
  265. data/doc/images/examples-popup_demo.rb.gif +0 -0
  266. data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
  267. data/doc/images/examples-scroll_text.rb.png +0 -0
  268. data/doc/images/examples-scrollbar_demo.rb.png +0 -0
  269. data/doc/images/examples-stock_ticker.rb.png +0 -0
  270. data/doc/images/examples-system_monitor.rb.png +0 -0
  271. data/doc/images/examples-table_select.rb.png +0 -0
  272. data/examples/analytics.rb +0 -88
  273. data/examples/box_demo.rb +0 -71
  274. data/examples/calendar_demo.rb +0 -55
  275. data/examples/chart_demo.rb +0 -84
  276. data/examples/custom_widget.rb +0 -43
  277. data/examples/dashboard.rb +0 -72
  278. data/examples/list_styles.rb +0 -66
  279. data/examples/map_demo.rb +0 -58
  280. data/examples/quickstart_dsl.rb +0 -30
  281. data/examples/quickstart_lifecycle.rb +0 -40
  282. data/examples/readme_usage.rb +0 -21
  283. data/examples/scroll_text.rb +0 -74
  284. data/examples/scrollbar_demo.rb +0 -75
  285. data/examples/stock_ticker.rb +0 -93
  286. data/examples/system_monitor.rb +0 -94
  287. data/examples/table_select.rb +0 -70
  288. data/examples/test_analytics.rb +0 -65
  289. data/examples/test_box_demo.rb +0 -38
  290. data/examples/test_calendar_demo.rb +0 -66
  291. data/examples/test_dashboard.rb +0 -38
  292. data/examples/test_list_styles.rb +0 -61
  293. data/examples/test_login_form.rb +0 -63
  294. data/examples/test_map_demo.rb +0 -100
  295. data/examples/test_scroll_text.rb +0 -130
  296. data/examples/test_stock_ticker.rb +0 -39
  297. data/examples/test_system_monitor.rb +0 -40
  298. data/examples/test_table_select.rb +0 -37
  299. data/ext/ratatui_ruby/src/buffer.rs +0 -54
  300. data/lib/ratatui_ruby/dsl.rb +0 -64
@@ -0,0 +1,90 @@
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
+ # An example of the Canvas widget showing a world map and animated shapes.
10
+ class MapDemoApp
11
+ include RatatuiRuby
12
+
13
+ COLORS = [:black, :blue, :white, nil].freeze
14
+ MARKERS = [:braille, :half_block, :dot, :block, :bar, :quadrant, :sextant, :octant].freeze
15
+
16
+ # Returns a Canvas view for the map demo with the given circle radius.
17
+ #
18
+ # +radius+:: The radius of the animated circle.
19
+ # +marker+:: The marker type.
20
+ # +background_color+:: The background color of the canvas.
21
+ # +show_labels+:: Whether to show city labels.
22
+ def view(radius, marker = :braille, background_color = nil, show_labels: true)
23
+ shapes = [
24
+ Shape::Map.new(color: :green, resolution: :high),
25
+ Shape::Circle.new(x: 0.0, y: 0.0, radius:, color: :red),
26
+ Shape::Line.new(x1: 0.0, y1: 0.0, x2: 50.0, y2: 25.0, color: :yellow),
27
+ ]
28
+
29
+ if show_labels
30
+ shapes += [
31
+ Shape::Label.new(x: -0.1, y: 51.5, text: "London", style: Style.new(fg: :cyan)),
32
+ Shape::Label.new(x: 139.7, y: 35.7, text: "Tokyo", style: Style.new(fg: :magenta)),
33
+ Shape::Label.new(x: -74.0, y: 40.7, text: "New York", style: Style.new(fg: :yellow)),
34
+ Shape::Label.new(x: -122.4, y: 37.8, text: "San Francisco", style: Style.new(fg: :blue)),
35
+ Shape::Label.new(x: 151.2, y: -33.9, text: "Sydney", style: Style.new(fg: :green)),
36
+ ]
37
+ end
38
+
39
+ Canvas.new(
40
+ shapes: shapes,
41
+ x_bounds: [-180.0, 180.0],
42
+ y_bounds: [-90.0, 90.0],
43
+ marker: marker,
44
+ block: Block.new(title: "World Map ['b' bg, 'm' marker: #{marker}, 'l' labels: #{show_labels ? 'on' : 'off'}]", borders: :all),
45
+ background_color: background_color
46
+ )
47
+ end
48
+
49
+ # Runs the map demo loop.
50
+ def run
51
+ RatatuiRuby.run do
52
+ radius = 0.0
53
+ direction = 1
54
+ bg_index = 0
55
+ marker_index = 0
56
+ show_labels = true
57
+
58
+ loop do
59
+ # Animate the circle radius
60
+ radius += 0.5 * direction
61
+ if radius > 10.0 || radius < 0.0
62
+ direction *= -1
63
+ end
64
+
65
+ # Define the view
66
+ v = view(radius, MARKERS[marker_index], COLORS[bg_index], show_labels:)
67
+
68
+ RatatuiRuby.draw(v)
69
+
70
+ event = RatatuiRuby.poll_event
71
+ case event
72
+ in {type: :key, code: "q"} | {type: :key, code: :ctrl_c}
73
+ break
74
+ in type: :key, code: "b"
75
+ bg_index = (bg_index + 1) % COLORS.size
76
+ in type: :key, code: "m"
77
+ marker_index = (marker_index + 1) % MARKERS.size
78
+ in type: :key, code: "l"
79
+ show_labels = !show_labels
80
+ else
81
+ # Ignore other events
82
+ end
83
+
84
+ sleep 0.05
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ MapDemoApp.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,7 @@
1
+ class MapDemoApp
2
+ # @public
3
+ def self.new: () -> MapDemoApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,149 @@
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
+ require "ratatui_ruby/test_helper"
9
+ require "minitest/autorun"
10
+ require "minitest/mock"
11
+ require_relative "app"
12
+
13
+ class TestMapDemoApp < Minitest::Test
14
+ include RatatuiRuby::TestHelper
15
+
16
+ def setup
17
+ @app = MapDemoApp.new
18
+ end
19
+
20
+ def test_map_demo_renders
21
+ with_test_terminal do
22
+ # Queue events: turn off labels so buffer matches original, then quit
23
+ inject_keys("l", "q")
24
+
25
+ # Stub sleep to speed up test
26
+ @app.stub :sleep, nil do
27
+ @app.run
28
+ end
29
+
30
+ # Verify the buffer content reflects the rendered map (with labels off)
31
+ expected_buffer = [
32
+ "┌World Map ['b' bg, 'm' marker: braille, 'l' labels: off]──────────────────────┐",
33
+ "│ ⣀⢀⣀⣀⡀ ⢀⣀⡀ ⡀ │",
34
+ "│ ⢠⣤⣰⣦⣶⣶⣿⣭⣿⣣⠶⠶⣿⣉⣉⠉⠉⠁ ⠁⠉⢽⠎⠁ ⠲⠶⠶⠖ ⠐⠛⢃⡀⣀⢤ ⢀⣐⣋⣷⠶⡤⣀ ⣀⡀⣀⡀ │",
35
+ "│⢖⡀⡀⣤⡖⠒⠒⠤⠤⠤⠤⠶⠿⠿⣿⣹⢿⣿⣿⢿⣭⣽⣿⡒⣦⣀⠘⣷⠄ ⢀⡀⢼⣏⢀ ⡠⠤⠒⠶⡤⢀⣄⡀⢻⠯⠤⢾⣻⡗⠛⠉ ⠈⠉⠈⠈⠙⠒⠚⠋ ⠒⠐⠦⠤⠤⠤⠶│",
36
+ "│⠉⠉⠻⣿⣃⣀⣤⡤⣄⣀⡀ ⢠⠒⠛⠳⡟⠳⣿⣏ ⠙⠦⠴⠋⠁ ⠈⠛⠋⠁⢀⡀ ⣖⡉⢰⢾⣥⡄⠙⠉⠁ ⠈ ⢀⣀⣀⡠⣤⣦⢤⠤⠛│",
37
+ "│ ⠒⠛⠉⠁ ⠿⣄⡀ ⠉⠓⢖⠎ ⢀⣘⣲⡄ ⠰⣾⣟⣄⠼⠛⠟⠚⠁ ⠺⣶ ⢪⠜⠁ │",
38
+ "│ ⠈⡟ ⢸⠭⠿⠛⠛ ⢀⡤⣿⢀⣠⣴⣆⡀⢀⣴⣶⢆ ⡶⣆ ⢀⣀⡔⣽⡅ │",
39
+ "│ ⠳⣀ ⢀⣺⠉ ⠘⣧⢴⠧⠼⡿⠟⠻⣿⢭⣬⠉ ⠸⠿ ⢺⠟⣿⣠⣶⡟ │",
40
+ "│ ⠘⢿⣄ ⡔⠒⠒⣿⡄ ⢀⡼ ⠈⠙⠋⠉⠛⣿⡀ ⢶⣤⣄⣀⡀ ⢠⡇⠈⠋ │",
41
+ "│ ⠐⠦ ⠈⠉⢧⣘⢆⣴⡞⠛⣷⣤⣄ ⣯ ⣸⣷⡊⠉⣉⡽ ⠙⢲ ⢀⡴⠻⣄ ⢰⣶⠚⢙⡁ │",
42
+ "│ ⠈⠉⠛⢯⣇⣠⣴⢤⣤⡀ ⢧⡀ ⡠⠔⠊ ⠙⠷⢮⠅ ⠈⣇⣏ ⠉⣷⢄⠵ ⣸⣷⡄ │",
43
+ "│ ⢉⡇ ⠙⠲⡄ ⠙⠒⢒⣊⡲⡔⠉ ⣀⠎ ⠈⠋ ⠰⡿⡆⣠⠔⣗⣙⣣ │",
44
+ "│ ⢼ ⠉⠓⠲⣄ ⠈⠁⠈⢢ ⡼⠁ ⠘⢽⣝⣒⣻⡯⡛⢻⣮⢒⣦⡶⣄⡀ │",
45
+ "│⡀ ⠈⢢⡀ ⢰⠃ ⢸ ⢹⢀⣴ ⠉⠉⣙⣥⠖⣎⣽⡌⠃ ⠙⢀⡀ ⢀│",
46
+ "│⠁ ⢸ ⢀⣀⠏ ⠸⡄ ⣲⠁⡝⡞ ⣤⠖⠋ ⠈⠁⠑⣆ ⠶⠁ ⠉│",
47
+ "│ ⢸ ⢀⠞ ⢳ ⡰⠃ ⠉⠁ ⢹ ⢀⣀⣀⢀ ⢸⠆ │",
48
+ "│ ⡎⢀⣠⠽⠉ ⠈⠉⠉ ⠈⠉⠉ ⠈⠛⠦⣤⠎ ⣷⡄│",
49
+ "│ ⢸⠇⢴⠁ ⢀⡀ ⠛ ⠰⠾⠋ │",
50
+ "│ ⠘⠶⠧⠐⠛ ⠈⠁ │",
51
+ "│ ⢀⣠⡤ ⣀⡀ ⡀ ⣀ ⡀ │",
52
+ "│ ⢀⣀⣀⣀⣀⣀⡀⢠⣤⣄⣀⣀⣀⣠⣴⡿⢳ ⣀⡤⠤⠤⠤⠔⠶⠖⠲⠤⠒⠒⠒⠋⠉⠁⠉⠉⠹⠶⠒⠋⠉⠉⠉⠉⠈⠉⠈⠉⠉⠉⠉⠉⠉⠉⠒⠒⠲⢤⣤ │",
53
+ "│ ⠰⠶⣶⣶⠏⠉⠉ ⠉ ⠈⠉⠉⠁ ⠶⢞⣩⣥⡄⣤⣶⣶⣀⣶⡶⠉⠉⠉ ⣠⣿ │",
54
+ "│⠉⠉⠉⠉⠒⠘⠙⠓⠃ ⠉ ⠈⠁⠉⠉│",
55
+ "└──────────────────────────────────────────────────────────────────────────────┘",
56
+ ]
57
+
58
+ expected_buffer.each_with_index do |line, i|
59
+ assert_equal line, buffer_content[i], "Line #{i} should match"
60
+ end
61
+
62
+ # Verify the background color is set on the view (Unit test of the view method)
63
+ view = @app.view(0.0, :braille, nil)
64
+ assert_nil view.background_color
65
+
66
+ # Verify labels are included in the shapes (default show_labels: true)
67
+ label_shapes = view.shapes.select { |s| s.is_a?(RatatuiRuby::Shape::Label) }
68
+ assert_equal 5, label_shapes.size, "Should have 5 city labels"
69
+ assert label_shapes.any? { |l| l.text == "London" }, "Should have London label"
70
+ assert label_shapes.any? { |l| l.text == "Tokyo" }, "Should have Tokyo label"
71
+ end
72
+ end
73
+
74
+ def test_labels_visible
75
+ with_test_terminal do
76
+ # Don't toggle labels - they're on by default
77
+ inject_keys("q")
78
+ @app.stub :sleep, nil do
79
+ @app.run
80
+ end
81
+ # Labels should be visible - check for a city name in the buffer
82
+ buffer_text = buffer_content.join
83
+ assert_includes buffer_text, "London", "London label should be visible"
84
+ end
85
+ end
86
+
87
+ def test_labels_toggle
88
+ with_test_terminal do
89
+ inject_keys("l", "q")
90
+ @app.stub :sleep, nil do
91
+ @app.run
92
+ end
93
+ # After pressing 'l', labels should be off - check title
94
+ assert_includes buffer_content[0], "labels: off"
95
+ end
96
+ end
97
+
98
+ def test_background_default
99
+ with_test_terminal(timeout: 5) do
100
+ inject_keys("q")
101
+ @app.stub :sleep, nil do
102
+ @app.run
103
+ end
104
+ # View is roughly at (1,1) to (18,8) inside borders. (10, 5) is safely inside.
105
+ assert_cell_style(10, 5, bg: :black)
106
+ end
107
+ end
108
+
109
+ def test_background_blue
110
+ with_test_terminal(timeout: 5) do
111
+ inject_keys("b", "q")
112
+ @app.stub :sleep, nil do
113
+ @app.run
114
+ end
115
+ assert_cell_style(10, 5, bg: :blue)
116
+ end
117
+ end
118
+
119
+ def test_background_white
120
+ with_test_terminal(timeout: 5) do
121
+ inject_keys("b", "b", "q")
122
+ @app.stub :sleep, nil do
123
+ @app.run
124
+ end
125
+ assert_cell_style(10, 5, bg: :white)
126
+ end
127
+ end
128
+
129
+ def test_background_transparent
130
+ with_test_terminal(timeout: 5) do
131
+ inject_keys("b", "b", "b", "q")
132
+ @app.stub :sleep, nil do
133
+ @app.run
134
+ end
135
+ # Transparent typically means no bg color set on the cell
136
+ assert_cell_style(10, 5, bg: nil)
137
+ end
138
+ end
139
+
140
+ def test_quit
141
+ with_test_terminal do
142
+ inject_key(:q)
143
+
144
+ @app.stub :sleep, nil do
145
+ @app.run
146
+ end
147
+ end
148
+ end
149
+ end
@@ -3,7 +3,7 @@
3
3
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
- $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
  require "ratatui_ruby"
8
8
 
9
9
  class MouseEventsApp
@@ -14,17 +14,16 @@ class MouseEventsApp
14
14
  end
15
15
 
16
16
  def run
17
- RatatuiRuby.init_terminal
18
- begin
17
+ RatatuiRuby.run do
19
18
  loop do
20
19
  render
21
20
  break if handle_input == :quit
22
21
  end
23
- ensure
24
- RatatuiRuby.restore_terminal
25
22
  end
26
23
  end
27
24
 
25
+ private
26
+
28
27
  def render
29
28
  # Create a centered block with the mouse event details
30
29
  block = RatatuiRuby::Block.new(
@@ -35,7 +34,7 @@ class MouseEventsApp
35
34
 
36
35
  content = RatatuiRuby::Paragraph.new(
37
36
  text: "#{@message}\n#{@details}",
38
- align: :center,
37
+ alignment: :center,
39
38
  block:
40
39
  )
41
40
 
@@ -61,32 +60,35 @@ class MouseEventsApp
61
60
  event = RatatuiRuby.poll_event
62
61
  return unless event
63
62
 
64
- if event[:type] == :key && event[:code] == "q"
63
+ case event
64
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
65
65
  return :quit
66
- end
67
-
68
- if event[:type] == :mouse
66
+ in type: :mouse, kind:, button:, x:, y:, modifiers:
69
67
  @color = "green"
70
- case event[:kind]
71
- when :down
72
- @message = "#{event[:button].to_s.capitalize} Click at [#{event[:x]}, #{event[:y]}]"
73
- @details = "Modifiers: #{event[:modifiers].join(', ')}"
74
- when :up
75
- @message = "#{event[:button].to_s.capitalize} Release at [#{event[:x]}, #{event[:y]}]"
76
- @details = "Modifiers: #{event[:modifiers].join(', ')}"
77
- when :drag
78
- @message = "Dragging #{event[:button].to_s.capitalize} Button at [#{event[:x]}, #{event[:y]}]"
79
- @details = "Modifiers: #{event[:modifiers].join(', ')}"
80
- when :moved
81
- @message = "Mouse Moved to [#{event[:x]}, #{event[:y]}]"
82
- @details = "Modifiers: #{event[:modifiers].join(', ')}"
83
- when :scroll_down
68
+ case kind
69
+ when "down"
70
+ @message = "#{button.capitalize} Click at [#{x}, #{y}]"
71
+ @details = "Modifiers: #{modifiers.join(', ')}"
72
+ when "up"
73
+ @message = "#{button.capitalize} Release at [#{x}, #{y}]"
74
+ @details = "Modifiers: #{modifiers.join(', ')}"
75
+ when "drag"
76
+ @message = "Dragging #{button.capitalize} Button at [#{x}, #{y}]"
77
+ @details = "Modifiers: #{modifiers.join(', ')}"
78
+ when "moved"
79
+ @message = "Mouse Moved to [#{x}, #{y}]"
80
+ @details = "Modifiers: #{modifiers.join(', ')}"
81
+ when "scroll_down"
84
82
  @message = "Scrolled Down"
85
- @details = "Position: [#{event[:x]}, #{event[:y]}]"
86
- when :scroll_up
83
+ @details = "Position: [#{x}, #{y}]"
84
+ when "scroll_up"
87
85
  @message = "Scrolled Up"
88
- @details = "Position: [#{event[:x]}, #{event[:y]}]"
86
+ @details = "Position: [#{x}, #{y}]"
87
+ else
88
+ nil
89
89
  end
90
+ else
91
+ nil
90
92
  end
91
93
  nil
92
94
  end
@@ -0,0 +1,7 @@
1
+ class MouseEventsApp
2
+ # @public
3
+ def self.new: () -> MouseEventsApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,53 @@
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
+ require "ratatui_ruby/test_helper"
9
+ require "minitest/autorun"
10
+ require_relative "app"
11
+
12
+ class TestMouseEvents < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = MouseEventsApp.new
17
+ end
18
+
19
+ def test_initial_render
20
+ with_test_terminal do
21
+ inject_key(:q)
22
+ @app.run
23
+
24
+ content = buffer_content.join("\n")
25
+ assert_includes content, "Mouse Event Plumbing"
26
+ assert_includes content, "Waiting for Mouse..."
27
+ end
28
+ end
29
+
30
+ def test_mouse_click
31
+ with_test_terminal do
32
+ # Click left button at 10, 5
33
+ inject_event(RatatuiRuby::Event::Mouse.new(kind: "down", button: "left", x: 10, y: 5))
34
+ inject_key(:q)
35
+ @app.run
36
+
37
+ content = buffer_content.join("\n")
38
+ assert_includes content, "Left Click at [10, 5]"
39
+ end
40
+ end
41
+
42
+ def test_mouse_scroll
43
+ with_test_terminal do
44
+ inject_event(RatatuiRuby::Event::Mouse.new(kind: "scroll_down", button: "none", x: 20, y: 15))
45
+ inject_key(:q)
46
+ @app.run
47
+
48
+ content = buffer_content.join("\n")
49
+ assert_includes content, "Scrolled Down"
50
+ assert_includes content, "Position: [20, 15]"
51
+ end
52
+ end
53
+ end
@@ -3,30 +3,29 @@
3
3
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
- $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
  require "ratatui_ruby"
8
8
 
9
9
  # Popup Demo Example
10
10
  # Demonstrates the Clear widget for creating opaque popups.
11
11
 
12
- class PopupDemo
12
+ class PopupDemoApp
13
13
  def initialize
14
14
  @clear_enabled = false
15
15
  end
16
16
 
17
17
  def run
18
- RatatuiRuby.init_terminal
19
- begin
18
+ RatatuiRuby.run do
20
19
  loop do
21
20
  render
22
21
  break if handle_input == :quit
23
22
  sleep 0.05
24
23
  end
25
- ensure
26
- RatatuiRuby.restore_terminal
27
24
  end
28
25
  end
29
26
 
27
+ private
28
+
30
29
  def render
31
30
  # 1. Background: Loud Red Background
32
31
  # This demonstrates "Style Bleed" where the background color persists
@@ -47,9 +46,9 @@ class PopupDemo
47
46
 
48
47
  popup_content = RatatuiRuby::Paragraph.new(
49
48
  text: popup_text,
50
- align: :center,
49
+ alignment: :center,
51
50
  block: RatatuiRuby::Block.new(
52
- title: "Popup Demo (Press 'q' to quit)",
51
+ title: "Popup Demo (q to quit, space to toggle)",
53
52
  borders: [:all]
54
53
  )
55
54
  )
@@ -90,16 +89,15 @@ class PopupDemo
90
89
  end
91
90
 
92
91
  def handle_input
93
- event = RatatuiRuby.poll_event
94
- if event
95
- case event[:code]
96
- when "q"
97
- :quit
98
- when " "
99
- @clear_enabled = !@clear_enabled
100
- end
92
+ case RatatuiRuby.poll_event
93
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
94
+ :quit
95
+ in type: :key, code: " "
96
+ @clear_enabled = !@clear_enabled
97
+ else
98
+ nil
101
99
  end
102
100
  end
103
101
  end
104
102
 
105
- PopupDemo.new.run if __FILE__ == $0
103
+ PopupDemoApp.new.run if __FILE__ == $0
@@ -0,0 +1,7 @@
1
+ class PopupDemoApp
2
+ # @public
3
+ def self.new: () -> PopupDemoApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -3,24 +3,25 @@
3
3
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
- $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
  require "ratatui_ruby"
8
8
  require "ratatui_ruby/test_helper"
9
9
  require "minitest/autorun"
10
- require_relative "popup_demo"
10
+ require_relative "app"
11
11
 
12
- class Minitest::Test
12
+ class TestPopupDemoApp < Minitest::Test
13
13
  include RatatuiRuby::TestHelper
14
- end
15
14
 
16
- class TestPopupDemo < Minitest::Test
17
15
  def setup
18
- @app = PopupDemo.new
16
+ @app = PopupDemoApp.new
19
17
  end
20
18
 
21
19
  def test_render_initial_state
22
- with_test_terminal(60, 20) do
23
- @app.render
20
+ with_test_terminal do
21
+ # Queue quit
22
+ inject_key(:q)
23
+
24
+ @app.run
24
25
 
25
26
  # Should have background text
26
27
  assert buffer_content.any? { |line| line.include?("BACKGROUND RED") }
@@ -32,31 +33,22 @@ class TestPopupDemo < Minitest::Test
32
33
  end
33
34
 
34
35
  def test_toggle_clear
35
- with_test_terminal(60, 20) do
36
- # Initial state: Clear disabled
37
- @app.render
38
- assert buffer_content.any? { |line| line.include?("Clear is DISABLED") }
36
+ with_test_terminal do
37
+ # Toggle Clear on then quit
38
+ inject_keys(" ", :q)
39
39
 
40
- # Toggle Clear on
41
- inject_event("key", { code: " " })
42
- @app.handle_input
40
+ @app.run
43
41
 
44
- @app.render
45
42
  assert buffer_content.any? { |line| line.include?("Clear is ENABLED") }
46
43
  assert buffer_content.any? { |line| line.include?("Resets background to default") }
47
-
48
- # Toggle Clear off
49
- inject_event("key", { code: " " })
50
- @app.handle_input
51
-
52
- @app.render
53
- assert buffer_content.any? { |line| line.include?("Clear is DISABLED") }
54
44
  end
55
45
  end
56
46
 
57
47
  def test_quit
58
- inject_event("key", { code: "q" })
59
- status = @app.handle_input
60
- assert_equal :quit, status
48
+ with_test_terminal do
49
+ inject_key(:q)
50
+ @app.run
51
+ # Success
52
+ end
61
53
  end
62
54
  end
@@ -0,0 +1,36 @@
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
+
8
+ require "ratatui_ruby"
9
+
10
+ class QuickstartDslApp
11
+ def run
12
+ # 1. Initialize the terminal, start the run loop, and ensure the terminal is restored.
13
+ RatatuiRuby.run do |tui|
14
+ loop do
15
+ # 2. Create your UI with methods instead of classes.
16
+ view = tui.paragraph(
17
+ text: "Hello, Ratatui! Press 'q' to quit.",
18
+ alignment: :center,
19
+ block: tui.block(
20
+ title: "My Ruby TUI App",
21
+ borders: [:all],
22
+ border_color: "cyan"
23
+ )
24
+ )
25
+
26
+ # 3. Use RatatuiRuby methods, too.
27
+ tui.draw(view)
28
+ event = tui.poll_event
29
+
30
+ break if event == "q" || event == :ctrl_c
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ QuickstartDslApp.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,7 @@
1
+ class QuickstartDslApp
2
+ # @public
3
+ def self.new: () -> QuickstartDslApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,29 @@
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
+ require "ratatui_ruby/test_helper"
9
+ require "minitest/autorun"
10
+ require_relative "app"
11
+
12
+ class TestQuickstartDsl < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = QuickstartDslApp.new
17
+ end
18
+
19
+ def test_render
20
+ with_test_terminal do
21
+ inject_key(:q)
22
+ @app.run
23
+
24
+ content = buffer_content.join("\n")
25
+ assert_includes content, "Hello, Ratatui!"
26
+ assert_includes content, "My Ruby TUI App"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
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
+
8
+ require "ratatui_ruby"
9
+
10
+ class QuickstartLifecycleApp
11
+ def run
12
+ # Using the RatatuiRuby.run block for automatic terminal setup/teardown
13
+ RatatuiRuby.run do |tui|
14
+ # The Main Loop
15
+ loop do
16
+ # 1. Create your UI (Immediate Mode)
17
+ # We define a Paragraph widget inside a Block with a title and borders.
18
+ view = RatatuiRuby::Paragraph.new(
19
+ text: "Hello, Ratatui! Press 'q' to quit.",
20
+ alignment: :center,
21
+ block: RatatuiRuby::Block.new(
22
+ title: "My Ruby TUI App",
23
+ borders: [:all],
24
+ border_color: "cyan"
25
+ )
26
+ )
27
+
28
+ # 2. Draw the UI
29
+ tui.draw(view)
30
+
31
+ # 3. Poll for events
32
+ event = RatatuiRuby.poll_event
33
+ break if event == "q" || event == :ctrl_c
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ QuickstartLifecycleApp.new.run if __FILE__ == $PROGRAM_NAME