ratatui_ruby 0.6.0 → 0.7.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 (171) 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 +4 -4
  7. data/CHANGELOG.md +35 -0
  8. data/README.md +26 -1
  9. data/doc/application_architecture.md +16 -16
  10. data/doc/application_testing.md +1 -1
  11. data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
  12. data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
  13. data/doc/contributors/architectural_overhaul/task.md +37 -0
  14. data/doc/contributors/design/ruby_frontend.md +277 -81
  15. data/doc/contributors/design/rust_backend.md +349 -55
  16. data/doc/contributors/developing_examples.md +5 -5
  17. data/doc/contributors/index.md +7 -5
  18. data/doc/contributors/v1.0.0_blockers.md +1729 -0
  19. data/doc/index.md +11 -6
  20. data/doc/interactive_design.md +2 -2
  21. data/doc/quickstart.md +66 -97
  22. data/doc/v0.7.0_migration.md +236 -0
  23. data/doc/why.md +93 -0
  24. data/examples/app_all_events/README.md +6 -4
  25. data/examples/app_all_events/app.rb +1 -1
  26. data/examples/app_all_events/model/app_model.rb +1 -1
  27. data/examples/app_all_events/model/msg.rb +1 -1
  28. data/examples/app_all_events/update.rb +1 -1
  29. data/examples/app_all_events/view/app_view.rb +1 -1
  30. data/examples/app_all_events/view/controls_view.rb +1 -1
  31. data/examples/app_all_events/view/counts_view.rb +1 -1
  32. data/examples/app_all_events/view/live_view.rb +1 -1
  33. data/examples/app_all_events/view/log_view.rb +1 -1
  34. data/examples/app_color_picker/README.md +7 -5
  35. data/examples/app_color_picker/app.rb +1 -1
  36. data/examples/app_login_form/README.md +2 -0
  37. data/examples/app_stateful_interaction/README.md +2 -0
  38. data/examples/app_stateful_interaction/app.rb +1 -1
  39. data/examples/verify_quickstart_dsl/README.md +4 -3
  40. data/examples/verify_quickstart_dsl/app.rb +1 -1
  41. data/examples/verify_quickstart_layout/README.md +1 -1
  42. data/examples/verify_quickstart_lifecycle/README.md +3 -3
  43. data/examples/verify_quickstart_lifecycle/app.rb +2 -2
  44. data/examples/verify_readme_usage/README.md +1 -1
  45. data/examples/widget_barchart_demo/README.md +2 -1
  46. data/examples/widget_block_demo/README.md +2 -0
  47. data/examples/widget_box_demo/README.md +3 -3
  48. data/examples/widget_calendar_demo/README.md +3 -3
  49. data/examples/widget_calendar_demo/app.rb +5 -1
  50. data/examples/widget_canvas_demo/README.md +3 -3
  51. data/examples/widget_cell_demo/README.md +3 -3
  52. data/examples/widget_center_demo/README.md +3 -3
  53. data/examples/widget_chart_demo/README.md +3 -3
  54. data/examples/widget_gauge_demo/README.md +3 -3
  55. data/examples/widget_layout_split/README.md +3 -3
  56. data/examples/widget_line_gauge_demo/README.md +3 -3
  57. data/examples/widget_list_demo/README.md +3 -3
  58. data/examples/widget_map_demo/README.md +3 -3
  59. data/examples/widget_map_demo/app.rb +2 -2
  60. data/examples/widget_overlay_demo/README.md +36 -0
  61. data/examples/widget_popup_demo/README.md +3 -3
  62. data/examples/widget_ratatui_logo_demo/README.md +3 -3
  63. data/examples/widget_ratatui_logo_demo/app.rb +1 -1
  64. data/examples/widget_ratatui_mascot_demo/README.md +3 -3
  65. data/examples/widget_rect/README.md +3 -3
  66. data/examples/widget_render/README.md +3 -3
  67. data/examples/widget_render/app.rb +3 -3
  68. data/examples/widget_rich_text/README.md +3 -3
  69. data/examples/widget_scroll_text/README.md +3 -3
  70. data/examples/widget_scrollbar_demo/README.md +3 -3
  71. data/examples/widget_sparkline_demo/README.md +3 -3
  72. data/examples/widget_style_colors/README.md +3 -3
  73. data/examples/widget_table_demo/README.md +3 -3
  74. data/examples/widget_table_demo/app.rb +19 -4
  75. data/examples/widget_tabs_demo/README.md +3 -3
  76. data/examples/widget_text_width/README.md +3 -3
  77. data/examples/widget_text_width/app.rb +8 -1
  78. data/ext/ratatui_ruby/Cargo.lock +1 -1
  79. data/ext/ratatui_ruby/Cargo.toml +1 -1
  80. data/ext/ratatui_ruby/src/frame.rs +6 -5
  81. data/ext/ratatui_ruby/src/lib.rs +3 -2
  82. data/ext/ratatui_ruby/src/rendering.rs +22 -21
  83. data/ext/ratatui_ruby/src/text.rs +12 -3
  84. data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
  85. data/ext/ratatui_ruby/src/widgets/table.rs +81 -36
  86. data/lib/ratatui_ruby/buffer/cell.rb +168 -0
  87. data/lib/ratatui_ruby/buffer.rb +15 -0
  88. data/lib/ratatui_ruby/frame.rb +8 -8
  89. data/lib/ratatui_ruby/layout/constraint.rb +95 -0
  90. data/lib/ratatui_ruby/layout/layout.rb +106 -0
  91. data/lib/ratatui_ruby/layout/rect.rb +118 -0
  92. data/lib/ratatui_ruby/layout.rb +19 -0
  93. data/lib/ratatui_ruby/list_state.rb +2 -2
  94. data/lib/ratatui_ruby/schema/layout.rb +1 -1
  95. data/lib/ratatui_ruby/schema/row.rb +66 -0
  96. data/lib/ratatui_ruby/schema/table.rb +10 -10
  97. data/lib/ratatui_ruby/schema/text.rb +27 -2
  98. data/lib/ratatui_ruby/style/style.rb +81 -0
  99. data/lib/ratatui_ruby/style.rb +15 -0
  100. data/lib/ratatui_ruby/table_state.rb +1 -1
  101. data/lib/ratatui_ruby/test_helper/snapshot.rb +24 -0
  102. data/lib/ratatui_ruby/test_helper/style_assertions.rb +1 -1
  103. data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
  104. data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
  105. data/lib/ratatui_ruby/tui/core.rb +38 -0
  106. data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
  107. data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
  108. data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
  109. data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
  110. data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
  111. data/lib/ratatui_ruby/tui.rb +75 -0
  112. data/lib/ratatui_ruby/version.rb +1 -1
  113. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
  114. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
  115. data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
  116. data/lib/ratatui_ruby/widgets/block.rb +192 -0
  117. data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
  118. data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
  119. data/lib/ratatui_ruby/widgets/cell.rb +47 -0
  120. data/lib/ratatui_ruby/widgets/center.rb +59 -0
  121. data/lib/ratatui_ruby/widgets/chart.rb +185 -0
  122. data/lib/ratatui_ruby/widgets/clear.rb +54 -0
  123. data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
  124. data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
  125. data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
  126. data/lib/ratatui_ruby/widgets/list.rb +127 -0
  127. data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
  128. data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
  129. data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
  130. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
  131. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
  132. data/lib/ratatui_ruby/widgets/row.rb +68 -0
  133. data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
  134. data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
  135. data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
  136. data/lib/ratatui_ruby/widgets/table.rb +141 -0
  137. data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
  138. data/lib/ratatui_ruby/widgets.rb +40 -0
  139. data/lib/ratatui_ruby.rb +23 -39
  140. data/sig/examples/app_all_events/view.rbs +1 -1
  141. data/sig/examples/app_all_events/view_state.rbs +1 -1
  142. data/sig/ratatui_ruby/schema/row.rbs +22 -0
  143. data/sig/ratatui_ruby/schema/table.rbs +1 -1
  144. data/sig/ratatui_ruby/schema/text.rbs +1 -0
  145. data/sig/ratatui_ruby/session.rbs +29 -49
  146. data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
  147. data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
  148. data/sig/ratatui_ruby/tui/core.rbs +14 -0
  149. data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
  150. data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
  151. data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
  152. data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
  153. data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
  154. data/sig/ratatui_ruby/tui.rbs +19 -0
  155. data/tasks/autodoc.rake +1 -35
  156. data/tasks/sourcehut.rake +4 -1
  157. metadata +62 -15
  158. data/doc/contributors/dwim_dx.md +0 -366
  159. data/doc/contributors/examples_audit/p1_high.md +0 -21
  160. data/doc/contributors/examples_audit/p2_moderate.md +0 -81
  161. data/doc/contributors/examples_audit.md +0 -41
  162. data/doc/images/app_analytics.png +0 -0
  163. data/doc/images/app_custom_widget.png +0 -0
  164. data/doc/images/app_mouse_events.png +0 -0
  165. data/doc/images/widget_table_flex.png +0 -0
  166. data/lib/ratatui_ruby/session/autodoc.rb +0 -482
  167. data/lib/ratatui_ruby/session.rb +0 -178
  168. data/tasks/autodoc/inventory.rb +0 -63
  169. data/tasks/autodoc/notice.rb +0 -26
  170. data/tasks/autodoc/rbs.rb +0 -38
  171. data/tasks/autodoc/rdoc.rb +0 -45
@@ -0,0 +1,185 @@
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
+ module RatatuiRuby
7
+ module Widgets
8
+ # Defines an Axis for a Chart
9
+ # [title] String
10
+ # [bounds] Array<Float> [min, max]
11
+ # [labels] Array<String>
12
+ # [style] Style
13
+ # [labels_alignment] Symbol (<tt>:left</tt>, <tt>:center</tt>, <tt>:right</tt>)
14
+ class Axis < Data.define(:title, :bounds, :labels, :style, :labels_alignment)
15
+ ##
16
+ # :attr_reader: title
17
+ # Label for the axis (String).
18
+
19
+ ##
20
+ # :attr_reader: bounds
21
+ # Range [min, max] (Array of Floats).
22
+
23
+ ##
24
+ # :attr_reader: labels
25
+ # Explicit labels for ticks (Array of Strings).
26
+
27
+ ##
28
+ # :attr_reader: style
29
+ # Style for axis lines/text.
30
+
31
+ ##
32
+ # :attr_reader: labels_alignment
33
+ # Alignment of axis labels (:left, :center, :right).
34
+
35
+ # Creates a new Axis.
36
+ #
37
+ # [title] String.
38
+ # [bounds] Array [min, max].
39
+ # [labels] Array of Strings.
40
+ # [style] Style.
41
+ # [labels_alignment] Symbol (:left, :center, :right).
42
+ def initialize(title: "", bounds: [0.0, 10.0], labels: [], style: nil, labels_alignment: nil)
43
+ super(
44
+ title:,
45
+ bounds: [Float(bounds[0]), Float(bounds[1])],
46
+ labels:,
47
+ style:,
48
+ labels_alignment:
49
+ )
50
+ end
51
+ end
52
+
53
+ # Defines a Dataset for a Chart.
54
+ # [name] The name of the dataset.
55
+ # [data] Array of arrays [[x, y], [x, y]] (Floats).
56
+ # [style] The style of the line.
57
+ # [marker] Symbol (<tt>:dot</tt>, <tt>:braille</tt>, <tt>:block</tt>, <tt>:bar</tt>)
58
+ # [graph_type] Symbol (<tt>:line</tt>, <tt>:scatter</tt>)
59
+ class Dataset < Data.define(:name, :data, :style, :marker, :graph_type)
60
+ ##
61
+ # :attr_reader: name
62
+ # Name for logical identification or legend.
63
+
64
+ ##
65
+ # :attr_reader: data
66
+ # list of [x, y] coordinates.
67
+
68
+ ##
69
+ # :attr_reader: style
70
+ # Style applied to the dataset (Style).
71
+ #
72
+ # **Note**: Due to Ratatui's Chart widget design, only the foreground color (<tt>fg</tt>) is applied to markers in the chart area.
73
+ # The full style (including <tt>bg</tt> and <tt>modifiers</tt>) is displayed in the legend.
74
+ #
75
+ # Supports:
76
+ # - +fg+: Foreground color of markers (Symbol/Hex) - _applied to chart_
77
+ # - +bg+: Background color (Symbol/Hex) - _legend only_
78
+ # - +modifiers+: Array of effects (<tt>:bold</tt>, <tt>:dim</tt>, <tt>:italic</tt>, <tt>:underlined</tt>, <tt>:slow_blink</tt>, <tt>:rapid_blink</tt>, <tt>:reversed</tt>, <tt>:hidden</tt>, <tt>:crossed_out</tt>) - _legend only_
79
+
80
+ ##
81
+ # :attr_reader: marker
82
+ # Marker type (<tt>:dot</tt>, <tt>:braille</tt>).
83
+
84
+ ##
85
+ # :attr_reader: graph_type
86
+ # Type of graph (<tt>:line</tt>, <tt>:scatter</tt>).
87
+
88
+ # Creates a new Dataset.
89
+ #
90
+ # [name] String.
91
+ # [data] Array of [x, y] (Numeric, duck-typed via +to_f+).
92
+ # [style] Style.
93
+ # [marker] Symbol.
94
+ # [graph_type] Symbol.
95
+ def initialize(name:, data:, style: nil, marker: :dot, graph_type: :line)
96
+ coerced_data = data.map { |point| [Float(point[0]), Float(point[1])] }
97
+ super(name:, data: coerced_data, style:, marker:, graph_type:)
98
+ end
99
+ end
100
+
101
+ # Plots data points on a Cartesian coordinate system.
102
+ #
103
+ # Trends and patterns are invisible in raw logs. You need to see the shape of the data to understand the story it tells.
104
+ #
105
+ # This widget plots X/Y coordinates. It supports multiple datasets, custom axes, and different marker types.
106
+ #
107
+ # Use it for analytics, scientific data, or monitoring metrics over time.
108
+ #
109
+ # {rdoc-image:/doc/images/widget_chart_demo.png}[link:/examples/widget_chart_demo/app_rb.html]
110
+ #
111
+ # === Example
112
+ #
113
+ # Run the interactive demo from the terminal:
114
+ #
115
+ # ruby examples/widget_chart_demo/app.rb
116
+ class Chart < Data.define(:datasets, :x_axis, :y_axis, :block, :style, :legend_position, :hidden_legend_constraints)
117
+ ##
118
+ # :attr_reader: datasets
119
+ # Array of Dataset objects to plot.
120
+
121
+ ##
122
+ # :attr_reader: x_axis
123
+ # Configuration for the X Axis.
124
+
125
+ ##
126
+ # :attr_reader: y_axis
127
+ # Configuration for the Y Axis.
128
+
129
+ ##
130
+ # :attr_reader: block
131
+ # Optional wrapping block.
132
+
133
+ ##
134
+ # :attr_reader: style
135
+ # Base style for the chart area.
136
+
137
+ ##
138
+ # :attr_reader: legend_position
139
+ # Position of the legend (<tt>:top_left</tt>, <tt>:top_right</tt>, <tt>:bottom_left</tt>, <tt>:bottom_right</tt>).
140
+
141
+ ##
142
+ # :attr_reader: hidden_legend_constraints
143
+ # Constraints for hiding the legend when the chart is too small (Array of [width, height]).
144
+
145
+ # Creates a new Chart widget.
146
+ #
147
+ # [datasets] Array of Datasets.
148
+ # [x_axis] X Axis config.
149
+ # [y_axis] Y Axis config.
150
+ # [block] Wrapper (optional).
151
+ # [style] Base style (optional).
152
+ # [legend_position] Symbol (<tt>:top_left</tt>, <tt>:top_right</tt>, <tt>:bottom_left</tt>, <tt>:bottom_right</tt>).
153
+ # [hidden_legend_constraints] Array of two Constraints [width, height] (optional).
154
+ def initialize(datasets:, x_axis:, y_axis:, block: nil, style: nil, legend_position: nil, hidden_legend_constraints: [])
155
+ super
156
+ end
157
+ end
158
+
159
+ # A complex chart widget. (Legacy/Alias for Chart)
160
+ #
161
+ # [datasets] Array of Dataset objects.
162
+ # [x_labels] Array of Strings for the X-axis labels.
163
+ # [y_labels] Array of Strings for the Y-axis labels.
164
+ # [y_bounds] Array of two Floats [min, max] for the Y-axis.
165
+ # [block] Optional block widget to wrap the chart.
166
+ class LineChart < Data.define(:datasets, :x_labels, :y_labels, :y_bounds, :block)
167
+ # Creates a new LineChart widget.
168
+ #
169
+ # [datasets] Array of Dataset objects.
170
+ # [x_labels] Array of Strings for the X-axis labels.
171
+ # [y_labels] Array of Strings for the Y-axis labels.
172
+ # [y_bounds] Array of two Floats [min, max] for the Y-axis.
173
+ # [block] Optional block widget to wrap the chart.
174
+ def initialize(datasets:, x_labels: [], y_labels: [], y_bounds: [0.0, 100.0], block: nil)
175
+ super(
176
+ datasets:,
177
+ x_labels:,
178
+ y_labels:,
179
+ y_bounds: [Float(y_bounds[0]), Float(y_bounds[1])],
180
+ block:
181
+ )
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,54 @@
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
+ module RatatuiRuby
7
+ module Widgets
8
+ # Resets the terminal buffer for a specific area.
9
+ #
10
+ # Painting in a terminal is additive. New content draws over old content. If the new content has transparency
11
+ # or empty spaces, the old content "bleeds" through. This ruins popups and modals.
12
+ #
13
+ # This widget wipes the slate clean. It resets all cells in its area to their default state (spaces with default background).
14
+ #
15
+ # Use it as the first layer in an Overlay stack when building popups. Ensure your floating windows are truly opaque.
16
+ #
17
+ # === Examples
18
+ #
19
+ # # Opaque Popup Construction
20
+ # Overlay.new(
21
+ # layers: [
22
+ # MainUI.new,
23
+ # Center.new(
24
+ # child: Overlay.new(
25
+ # layers: [
26
+ # Clear.new, # Wipe the area first
27
+ # Block.new(title: "Modal", borders: [:all])
28
+ # ]
29
+ # ),
30
+ # width_percent: 50,
31
+ # height_percent: 50
32
+ # )
33
+ # ]
34
+ # )
35
+ #
36
+ # # Shortcut: rendering a block directly
37
+ # Clear.new(block: Block.new(title: "Cleared area", borders: [:all]))
38
+ class Clear < Data.define(:block)
39
+ ##
40
+ # :attr_reader: block
41
+ # Optional Block to render after clearing.
42
+ #
43
+ # If provided, the borders/title of this block are drawn on top of the cleared area.
44
+
45
+ # Creates a new Clear widget.
46
+ #
47
+ # [block]
48
+ # Block widget to render (optional).
49
+ def initialize(block: nil)
50
+ super
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,42 @@
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
+ module RatatuiRuby
7
+ module Widgets
8
+ # Controls the terminal cursor position.
9
+ #
10
+ # Interfaces are not just output; they require input. Users need a visual cue—a blinking block or line—to know where their keystrokes will appear.
11
+ #
12
+ # This widget renders a ghost. It does not draw a character but instructs the terminal to place the hardware cursor at specific coordinates.
13
+ #
14
+ # Use it for text editors, input fields, or command prompts.
15
+ #
16
+ # === Examples
17
+ #
18
+ # Cursor.new(x: 10, y: 5)
19
+ #
20
+ # See also:
21
+ # - {Declarative implementation using Tree API}[link:/examples/app_login_form/app_rb.html]
22
+ # - {Component-based implementation using Frame API}[link:/examples/app_color_picker/app_rb.html]
23
+ # - RatatuiRuby::Frame#set_cursor_position (Frame API alternative)
24
+ class Cursor < Data.define(:x, :y)
25
+ ##
26
+ # :attr_reader: x
27
+ # X coordinate (column).
28
+
29
+ ##
30
+ # :attr_reader: y
31
+ # Y coordinate (row).
32
+
33
+ # Creates a new Cursor.
34
+ #
35
+ # [x] Integer.
36
+ # [y] Integer.
37
+ def initialize(x:, y:)
38
+ super(x: Integer(x), y: Integer(y))
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,72 @@
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
+ module RatatuiRuby
7
+ module Widgets
8
+ # Displays a standard progress bar.
9
+ #
10
+ # Long-running tasks create anxiety. Users need to know that the system is working and how much is left to do.
11
+ #
12
+ # This widget visualizes completion. It fills a bar based on a percentage.
13
+ #
14
+ # Use it for downloads, installations, or processing jobs.
15
+ #
16
+ # {rdoc-image:/doc/images/widget_gauge_demo.png}[link:/examples/widget_gauge_demo/app_rb.html]
17
+ #
18
+ # === Example
19
+ #
20
+ # Run the interactive demo from the terminal:
21
+ #
22
+ # ruby examples/widget_gauge_demo/app.rb
23
+ class Gauge < Data.define(:ratio, :label, :style, :gauge_style, :block, :use_unicode)
24
+ ##
25
+ # :attr_reader: ratio
26
+ # Progress ratio from 0.0 to 1.0.
27
+
28
+ ##
29
+ # :attr_reader: label
30
+ # Text label to display (optional).
31
+ #
32
+ # Accepts String or Text::Span for rich styling.
33
+ #
34
+ # If nil, it often displays the percentage automatically depending on renderer logic,
35
+ # but explicit labels are preferred.
36
+
37
+ ##
38
+ # :attr_reader: style
39
+ # Base style applied to the entire gauge background (optional).
40
+
41
+ ##
42
+ # :attr_reader: gauge_style
43
+ # Style applied specifically to the filled bar portion (optional).
44
+
45
+ ##
46
+ # :attr_reader: block
47
+ # Optional wrapping block.
48
+
49
+ ##
50
+ # :attr_reader: use_unicode
51
+ # Whether to use Unicode block characters (true) or ASCII characters (false).
52
+ # Default is false (ASCII) to be conservative, though Ratatui defaults to true.
53
+
54
+ # Creates a new Gauge.
55
+ #
56
+ # [ratio] Float (0.0 - 1.0).
57
+ # [percent] Integer (0 - 100), alternative to ratio.
58
+ # [label] String or Text::Span (optional).
59
+ # [style] Style object for the background (optional).
60
+ # [gauge_style] Style object for the filled bar (optional).
61
+ # [block] Block widget (optional).
62
+ # [use_unicode] Boolean (default: true).
63
+ def initialize(ratio: nil, percent: nil, label: nil, style: nil, gauge_style: nil, block: nil, use_unicode: true)
64
+ if percent
65
+ ratio = Float(percent) / 100.0
66
+ end
67
+ ratio = Float(ratio || 0.0)
68
+ super(ratio:, label:, style:, gauge_style:, block:, use_unicode:)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,80 @@
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
+ module RatatuiRuby
7
+ module Widgets
8
+ # Displays a compact, single-line progress bar.
9
+ #
10
+ # Screen space is precious. Standard block gauges are bulky and consume multiple rows.
11
+ #
12
+ # This widget compresses the feedback. It draws a progress bar using line characters, fitting perfectly into tight layouts or lists.
13
+ #
14
+ # Use it when you need to show status without stealing focus or space.
15
+ #
16
+ # {rdoc-image:/doc/images/widget_line_gauge_demo.png}[link:/examples/widget_line_gauge_demo/app_rb.html]
17
+ #
18
+ # === Example
19
+ #
20
+ # Run the interactive demo from the terminal:
21
+ #
22
+ # ruby examples/widget_line_gauge_demo/app.rb
23
+ class LineGauge < Data.define(:ratio, :label, :style, :filled_style, :unfilled_style, :block, :filled_symbol, :unfilled_symbol)
24
+ ##
25
+ # :attr_reader: ratio
26
+ # Progress ratio from 0.0 to 1.0.
27
+
28
+ ##
29
+ # :attr_reader: label
30
+ # Optional label (String or Text::Span for rich styling).
31
+
32
+ ##
33
+ # :attr_reader: style
34
+ # Base style applied to the entire gauge.
35
+
36
+ ##
37
+ # :attr_reader: filled_style
38
+ # Style for the completed portion.
39
+
40
+ ##
41
+ # :attr_reader: unfilled_style
42
+ # Style for the remainder.
43
+
44
+ ##
45
+ # :attr_reader: block
46
+ # Optional wrapping block.
47
+
48
+ ##
49
+ # :attr_reader: filled_symbol
50
+ # Character for filled segments.
51
+
52
+ ##
53
+ # :attr_reader: unfilled_symbol
54
+ # Character for empty segments.
55
+
56
+ # Creates a new LineGauge.
57
+ #
58
+ # [ratio] Float (0.0 - 1.0).
59
+ # [label] String or Text::Span (optional).
60
+ # [style] Style (optional, base style for the gauge).
61
+ # [filled_style] Style.
62
+ # [unfilled_style] Style.
63
+ # [block] Block.
64
+ # [filled_symbol] String (default: <tt>"█"</tt>).
65
+ # [unfilled_symbol] String (default: <tt>"░"</tt>).
66
+ def initialize(ratio: 0.0, label: nil, style: nil, filled_style: nil, unfilled_style: nil, block: nil, filled_symbol: "█", unfilled_symbol: "░")
67
+ super(
68
+ ratio: Float(ratio),
69
+ label:,
70
+ style:,
71
+ filled_style:,
72
+ unfilled_style:,
73
+ block:,
74
+ filled_symbol:,
75
+ unfilled_symbol:
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,127 @@
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
+ module RatatuiRuby
7
+ module Widgets
8
+ # Displays a selectable list of items.
9
+ #
10
+ # Users need to choose from options. Menus, file explorers, and selectors are everywhere.
11
+ # Implementing navigation, highlighting, and scrolling state from scratch is tedious.
12
+ #
13
+ # This widget manages the list. It renders the items. It highlights the selection. It handles the scrolling window.
14
+ #
15
+ # Use it to build main menus, navigation sidebars, or logs.
16
+ #
17
+ # {rdoc-image:/doc/images/widget_list_demo.png}[link:/examples/widget_list_demo/app_rb.html]
18
+ #
19
+ # === Examples
20
+ #
21
+ # # Basic List
22
+ # List.new(items: ["Item 1", "Item 2"])
23
+ #
24
+ # # Navigation Menu
25
+ # List.new(
26
+ # items: ["New Game", "Load Game", "Options", "Quit"],
27
+ # selected_index: 0,
28
+ # highlight_style: Style.new(bg: :blue),
29
+ # highlight_symbol: ">> "
30
+ # )
31
+ class List < Data.define(:items, :selected_index, :offset, :style, :highlight_style, :highlight_symbol, :repeat_highlight_symbol, :highlight_spacing, :direction, :scroll_padding, :block)
32
+ ##
33
+ # :attr_reader: items
34
+ # The items to display.
35
+ #
36
+ # Accepts Array of Strings, Text::Spans, Text::Lines, or ListItem objects.
37
+ # For styled individual rows, use ListItem with a style.
38
+
39
+ ##
40
+ # :attr_reader: selected_index
41
+ # Index of the active selection (Integer or nil).
42
+
43
+ ##
44
+ # :attr_reader: offset
45
+ # Scroll offset (Integer or nil).
46
+ #
47
+ # Controls the viewport's starting position in the list.
48
+ #
49
+ # When +nil+ (default), Ratatui auto-scrolls to keep the selection visible ("natural scrolling").
50
+ #
51
+ # When set, forces the viewport to start at this item index. Use this for:
52
+ # - **Passive scrolling**: Scroll through a log viewer without selecting items.
53
+ # - **Click-to-select math**: Calculate which item index corresponds to a click coordinate.
54
+ #
55
+ # *Important*: When both +offset+ and +selected_index+ are set, Ratatui may still adjust
56
+ # the viewport during rendering to ensure the selection stays visible. Set +selected_index+
57
+ # to +nil+ for fully manual scroll control.
58
+
59
+ ##
60
+ # :attr_reader: style
61
+ # Base style for unselected items.
62
+
63
+ ##
64
+ # :attr_reader: highlight_style
65
+ # Style for the selected item.
66
+
67
+ ##
68
+ # :attr_reader: highlight_symbol
69
+ # Symbol drawn before the selected item.
70
+
71
+ ##
72
+ # :attr_reader: repeat_highlight_symbol
73
+ # Whether to repeat the highlight symbol for each line of the selected item.
74
+
75
+ ##
76
+ # :attr_reader: highlight_spacing
77
+ # When to show the highlight symbol column.
78
+ #
79
+ # <tt>:always</tt>, <tt>:when_selected</tt>, or <tt>:never</tt>.
80
+
81
+ ##
82
+ # :attr_reader: direction
83
+ # Render direction.
84
+ #
85
+ # <tt>:top_to_bottom</tt> or <tt>:bottom_to_top</tt>.
86
+
87
+ ##
88
+ # :attr_reader: scroll_padding
89
+ # Number of items to keep visible above/below the selected item when scrolling (Integer or nil).
90
+
91
+ ##
92
+ # :attr_reader: block
93
+ # Optional wrapping block.
94
+
95
+ # Creates a new List.
96
+ #
97
+ # Integer parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
98
+ #
99
+ # [items] Array of Strings, Text::Spans, Text::Lines, or ListItem objects.
100
+ # [selected_index] Numeric (nullable, coerced to Integer).
101
+ # [offset] Numeric (nullable, coerced to Integer). Forces scroll position when set.
102
+ # [style] Style object.
103
+ # [highlight_style] Style object.
104
+ # [highlight_symbol] String (default: <tt>"> "</tt>).
105
+ # [repeat_highlight_symbol] Boolean (default: <tt>false</tt>).
106
+ # [highlight_spacing] Symbol (default: <tt>:when_selected</tt>).
107
+ # [direction] Symbol (default: <tt>:top_to_bottom</tt>).
108
+ # [scroll_padding] Numeric (nullable, coerced to Integer, default: <tt>nil</tt>).
109
+ # [block] Block (optional).
110
+ def initialize(items: [], selected_index: nil, offset: nil, style: nil, highlight_style: nil, highlight_symbol: "> ", repeat_highlight_symbol: false, highlight_spacing: :when_selected, direction: :top_to_bottom, scroll_padding: nil, block: nil)
111
+ super(
112
+ items:,
113
+ selected_index: selected_index.nil? ? nil : Integer(selected_index),
114
+ offset: offset.nil? ? nil : Integer(offset),
115
+ style:,
116
+ highlight_style:,
117
+ highlight_symbol:,
118
+ repeat_highlight_symbol:,
119
+ highlight_spacing:,
120
+ direction:,
121
+ scroll_padding: scroll_padding.nil? ? nil : Integer(scroll_padding),
122
+ block:
123
+ )
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,43 @@
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
+ module RatatuiRuby
7
+ module Widgets
8
+ # A styled list item combining content with optional style.
9
+ #
10
+ # By default, List items are strings. For more control over styling individual rows,
11
+ # wrap the content in a ListItem to apply a style specific to that item.
12
+ #
13
+ # The content can be a String, Text::Span, or Text::Line. The style applies to the
14
+ # entire row background.
15
+ #
16
+ # === Examples
17
+ #
18
+ # # Item with red background
19
+ # ListItem.new(content: "Error", style: Style.new(bg: :red))
20
+ #
21
+ # # Item with styled content
22
+ # ListItem.new(
23
+ # content: Text::Span.new(content: "Status: OK", style: Style.new(fg: :green, modifiers: [:bold]))
24
+ # )
25
+ class ListItem < Data.define(:content, :style)
26
+ ##
27
+ # :attr_reader: content
28
+ # The content to display (String, Text::Span, or Text::Line).
29
+
30
+ ##
31
+ # :attr_reader: style
32
+ # The style to apply to the item (optional Style).
33
+
34
+ # Creates a new ListItem.
35
+ #
36
+ # [content] String, Text::Span, or Text::Line.
37
+ # [style] Style object (optional).
38
+ def initialize(content:, style: nil)
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
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
+ module RatatuiRuby
7
+ module Widgets
8
+ # Stacks widgets on top of each other.
9
+ #
10
+ # Terminal interfaces are 2D grids, but complex UIs require depth. You need to float modals over text,
11
+ # or place a status bar on top of a map.
12
+ #
13
+ # This widget manages the Z-axis. It renders a list of widgets sequentially into the same area.
14
+ # Later widgets draw over earlier ones (Painter's Algorithm).
15
+ #
16
+ # Use overlays to compose complex scenes. Combine backgrounds, main content, and floating elements.
17
+ #
18
+ # === Examples
19
+ #
20
+ # Overlay.new(
21
+ # layers: [
22
+ # BackgroundMap.new,
23
+ # StatusBar.new, # Draws over map
24
+ # ModalDialog.new # Draws over everything
25
+ # ]
26
+ # )
27
+ class Overlay < Data.define(:layers)
28
+ ##
29
+ # :attr_reader: layers
30
+ # The stack of widgets to render.
31
+ #
32
+ # Rendered from index 0 to N. Index N is the top-most layer.
33
+
34
+ # Creates a new Overlay.
35
+ #
36
+ # [layers]
37
+ # Array of widgets.
38
+ def initialize(layers: [])
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end