ratatui_ruby 0.9.1 → 0.10.1
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.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +2 -1
- data/CHANGELOG.md +113 -0
- data/README.md +17 -0
- data/REUSE.toml +5 -0
- data/Rakefile +1 -1
- data/Steepfile +49 -0
- data/doc/concepts/debugging.md +401 -0
- data/doc/getting_started/quickstart.md +8 -3
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_debugging_showcase.gif +0 -0
- data/doc/images/app_debugging_showcase.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart.png +0 -0
- data/doc/images/widget_block.png +0 -0
- data/doc/images/widget_box.png +0 -0
- data/doc/images/widget_calendar.png +0 -0
- data/doc/images/widget_canvas.png +0 -0
- data/doc/images/widget_cell.png +0 -0
- data/doc/images/widget_center.png +0 -0
- data/doc/images/widget_chart.png +0 -0
- data/doc/images/widget_gauge.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge.png +0 -0
- data/doc/images/widget_list.png +0 -0
- data/doc/images/widget_map.png +0 -0
- data/doc/images/widget_overlay.png +0 -0
- data/doc/images/widget_popup.png +0 -0
- data/doc/images/widget_ratatui_logo.png +0 -0
- data/doc/images/widget_ratatui_mascot.png +0 -0
- data/doc/images/widget_rect.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_scrollbar.png +0 -0
- data/doc/images/widget_sparkline.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table.png +0 -0
- data/doc/images/widget_tabs.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/troubleshooting/async.md +4 -0
- data/examples/app_debugging_showcase/README.md +119 -0
- data/examples/app_debugging_showcase/app.rb +318 -0
- data/examples/widget_canvas/app.rb +19 -14
- data/examples/widget_gauge/app.rb +18 -3
- data/examples/widget_layout_split/app.rb +10 -4
- data/examples/widget_list/app.rb +22 -6
- data/examples/widget_rect/app.rb +7 -6
- data/examples/widget_rich_text/app.rb +62 -37
- data/examples/widget_style_colors/app.rb +26 -47
- data/examples/widget_table/app.rb +28 -5
- data/examples/widget_text_width/app.rb +6 -4
- data/ext/ratatui_ruby/Cargo.lock +48 -1
- data/ext/ratatui_ruby/Cargo.toml +6 -2
- data/ext/ratatui_ruby/src/color.rs +82 -0
- data/ext/ratatui_ruby/src/errors.rs +28 -0
- data/ext/ratatui_ruby/src/events.rs +15 -14
- data/ext/ratatui_ruby/src/lib.rs +56 -0
- data/ext/ratatui_ruby/src/rendering.rs +3 -1
- data/ext/ratatui_ruby/src/style.rs +48 -21
- data/ext/ratatui_ruby/src/terminal.rs +40 -9
- data/ext/ratatui_ruby/src/text.rs +21 -9
- data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
- data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
- data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
- data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
- data/lib/ratatui_ruby/buffer/cell.rb +25 -15
- data/lib/ratatui_ruby/buffer.rb +134 -2
- data/lib/ratatui_ruby/cell.rb +13 -5
- data/lib/ratatui_ruby/debug.rb +215 -0
- data/lib/ratatui_ruby/event/key.rb +3 -2
- data/lib/ratatui_ruby/event.rb +1 -1
- data/lib/ratatui_ruby/layout/constraint.rb +49 -0
- data/lib/ratatui_ruby/layout/layout.rb +119 -13
- data/lib/ratatui_ruby/layout/position.rb +55 -0
- data/lib/ratatui_ruby/layout/rect.rb +188 -0
- data/lib/ratatui_ruby/layout/size.rb +55 -0
- data/lib/ratatui_ruby/layout.rb +4 -0
- data/lib/ratatui_ruby/style/color.rb +149 -0
- data/lib/ratatui_ruby/style/style.rb +51 -4
- data/lib/ratatui_ruby/style.rb +2 -0
- data/lib/ratatui_ruby/symbols.rb +435 -0
- data/lib/ratatui_ruby/synthetic_events.rb +1 -1
- data/lib/ratatui_ruby/table_state.rb +51 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb +2 -1
- data/lib/ratatui_ruby/test_helper/event_injection.rb +6 -1
- data/lib/ratatui_ruby/test_helper.rb +9 -0
- data/lib/ratatui_ruby/text/line.rb +245 -0
- data/lib/ratatui_ruby/text/span.rb +158 -0
- data/lib/ratatui_ruby/text.rb +99 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +103 -0
- data/lib/ratatui_ruby/tui/core.rb +13 -2
- data/lib/ratatui_ruby/tui/layout_factories.rb +50 -3
- data/lib/ratatui_ruby/tui/state_factories.rb +42 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +40 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +135 -60
- data/lib/ratatui_ruby/tui.rb +22 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +30 -20
- data/lib/ratatui_ruby/widgets/block.rb +14 -6
- data/lib/ratatui_ruby/widgets/calendar.rb +2 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +56 -0
- data/lib/ratatui_ruby/widgets/cell.rb +2 -0
- data/lib/ratatui_ruby/widgets/center.rb +2 -0
- data/lib/ratatui_ruby/widgets/chart.rb +6 -0
- data/lib/ratatui_ruby/widgets/clear.rb +2 -0
- data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +61 -3
- data/lib/ratatui_ruby/widgets/line_gauge.rb +66 -4
- data/lib/ratatui_ruby/widgets/list.rb +87 -3
- data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +4 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -0
- data/lib/ratatui_ruby/widgets/row.rb +45 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +21 -13
- data/lib/ratatui_ruby/widgets/table.rb +13 -3
- data/lib/ratatui_ruby/widgets/tabs.rb +6 -4
- data/lib/ratatui_ruby/widgets.rb +1 -0
- data/lib/ratatui_ruby.rb +42 -11
- data/sig/examples/app_all_events/model/app_model.rbs +23 -0
- data/sig/examples/app_all_events/model/event_entry.rbs +15 -8
- data/sig/examples/app_all_events/model/timestamp.rbs +1 -1
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_stateful_interaction/app.rbs +5 -5
- data/sig/examples/widget_block_demo/app.rbs +6 -6
- data/sig/manifest.yaml +5 -0
- data/sig/patches/data.rbs +26 -0
- data/sig/patches/debugger__.rbs +8 -0
- data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
- data/sig/ratatui_ruby/buffer.rbs +18 -0
- data/sig/ratatui_ruby/cell.rbs +44 -0
- data/sig/ratatui_ruby/clear.rbs +18 -0
- data/sig/ratatui_ruby/constraint.rbs +26 -0
- data/sig/ratatui_ruby/debug.rbs +45 -0
- data/sig/ratatui_ruby/draw.rbs +30 -0
- data/sig/ratatui_ruby/event.rbs +68 -8
- data/sig/ratatui_ruby/frame.rbs +4 -4
- data/sig/ratatui_ruby/interfaces.rbs +25 -0
- data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
- data/sig/ratatui_ruby/layout/layout.rbs +45 -0
- data/sig/ratatui_ruby/layout/position.rbs +18 -0
- data/sig/ratatui_ruby/layout/rect.rbs +64 -0
- data/sig/ratatui_ruby/layout/size.rbs +18 -0
- data/sig/ratatui_ruby/output_guard.rbs +23 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +84 -5
- data/sig/ratatui_ruby/rect.rbs +17 -0
- data/sig/ratatui_ruby/style/color.rbs +22 -0
- data/sig/ratatui_ruby/style/style.rbs +29 -0
- data/sig/ratatui_ruby/symbols.rbs +141 -0
- data/sig/ratatui_ruby/synthetic_events.rbs +21 -0
- data/sig/ratatui_ruby/table_state.rbs +6 -0
- data/sig/ratatui_ruby/terminal_lifecycle.rbs +31 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +2 -2
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +22 -3
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +8 -1
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -3
- data/sig/ratatui_ruby/text/line.rbs +27 -0
- data/sig/ratatui_ruby/text/span.rbs +23 -0
- data/sig/ratatui_ruby/text.rbs +12 -0
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +1 -1
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +23 -5
- data/sig/ratatui_ruby/tui/core.rbs +2 -2
- data/sig/ratatui_ruby/tui/layout_factories.rbs +16 -2
- data/sig/ratatui_ruby/tui/state_factories.rbs +8 -3
- data/sig/ratatui_ruby/tui/style_factories.rbs +3 -1
- data/sig/ratatui_ruby/tui/text_factories.rbs +7 -4
- data/sig/ratatui_ruby/tui/widget_factories.rbs +123 -30
- data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
- data/sig/ratatui_ruby/widgets/block.rbs +51 -0
- data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
- data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
- data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
- data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
- data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
- data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
- data/sig/ratatui_ruby/widgets/list.rbs +63 -0
- data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
- data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
- data/sig/ratatui_ruby/widgets/row.rbs +43 -0
- data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
- data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
- data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
- data/sig/ratatui_ruby/widgets/table.rbs +78 -0
- data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
- data/sig/ratatui_ruby/{schema/list_item.rbs → widgets.rbs} +4 -4
- data/tasks/steep.rake +11 -0
- metadata +80 -63
- data/doc/contributors/v1.0.0_blockers.md +0 -870
- data/doc/troubleshooting/debugging.md +0 -101
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +0 -47
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +0 -25
- data/lib/ratatui_ruby/schema/bar_chart.rb +0 -287
- data/lib/ratatui_ruby/schema/block.rb +0 -198
- data/lib/ratatui_ruby/schema/calendar.rb +0 -84
- data/lib/ratatui_ruby/schema/canvas.rb +0 -239
- data/lib/ratatui_ruby/schema/center.rb +0 -67
- data/lib/ratatui_ruby/schema/chart.rb +0 -159
- data/lib/ratatui_ruby/schema/clear.rb +0 -62
- data/lib/ratatui_ruby/schema/constraint.rb +0 -151
- data/lib/ratatui_ruby/schema/cursor.rb +0 -50
- data/lib/ratatui_ruby/schema/gauge.rb +0 -72
- data/lib/ratatui_ruby/schema/layout.rb +0 -122
- data/lib/ratatui_ruby/schema/line_gauge.rb +0 -80
- data/lib/ratatui_ruby/schema/list.rb +0 -135
- data/lib/ratatui_ruby/schema/list_item.rb +0 -51
- data/lib/ratatui_ruby/schema/overlay.rb +0 -51
- data/lib/ratatui_ruby/schema/paragraph.rb +0 -107
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +0 -31
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +0 -36
- data/lib/ratatui_ruby/schema/rect.rb +0 -174
- data/lib/ratatui_ruby/schema/row.rb +0 -76
- data/lib/ratatui_ruby/schema/scrollbar.rb +0 -143
- data/lib/ratatui_ruby/schema/shape/label.rb +0 -76
- data/lib/ratatui_ruby/schema/sparkline.rb +0 -142
- data/lib/ratatui_ruby/schema/style.rb +0 -97
- data/lib/ratatui_ruby/schema/table.rb +0 -141
- data/lib/ratatui_ruby/schema/tabs.rb +0 -85
- data/lib/ratatui_ruby/schema/text.rb +0 -217
- data/sig/examples/app_all_events/model/events.rbs +0 -15
- data/sig/examples/app_all_events/view_state.rbs +0 -21
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +0 -22
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +0 -19
- data/sig/ratatui_ruby/schema/bar_chart.rbs +0 -38
- data/sig/ratatui_ruby/schema/block.rbs +0 -18
- data/sig/ratatui_ruby/schema/calendar.rbs +0 -23
- data/sig/ratatui_ruby/schema/canvas.rbs +0 -81
- data/sig/ratatui_ruby/schema/center.rbs +0 -17
- data/sig/ratatui_ruby/schema/chart.rbs +0 -39
- data/sig/ratatui_ruby/schema/constraint.rbs +0 -30
- data/sig/ratatui_ruby/schema/cursor.rbs +0 -16
- data/sig/ratatui_ruby/schema/draw.rbs +0 -33
- data/sig/ratatui_ruby/schema/gauge.rbs +0 -23
- data/sig/ratatui_ruby/schema/layout.rbs +0 -27
- data/sig/ratatui_ruby/schema/line_gauge.rbs +0 -24
- data/sig/ratatui_ruby/schema/list.rbs +0 -28
- data/sig/ratatui_ruby/schema/overlay.rbs +0 -15
- data/sig/ratatui_ruby/schema/paragraph.rbs +0 -20
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +0 -14
- data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +0 -17
- data/sig/ratatui_ruby/schema/rect.rbs +0 -48
- data/sig/ratatui_ruby/schema/row.rbs +0 -28
- data/sig/ratatui_ruby/schema/scrollbar.rbs +0 -42
- data/sig/ratatui_ruby/schema/sparkline.rbs +0 -22
- data/sig/ratatui_ruby/schema/style.rbs +0 -19
- data/sig/ratatui_ruby/schema/table.rbs +0 -32
- data/sig/ratatui_ruby/schema/tabs.rbs +0 -21
- data/sig/ratatui_ruby/schema/text.rbs +0 -31
- /data/lib/ratatui_ruby/{schema/draw.rb → draw.rb} +0 -0
|
@@ -41,6 +41,8 @@ module RatatuiRuby
|
|
|
41
41
|
# SPDX-SnippetEnd
|
|
42
42
|
#++
|
|
43
43
|
class BarChart < Data.define(:data, :bar_width, :bar_gap, :group_gap, :max, :style, :block, :direction, :label_style, :value_style, :bar_set)
|
|
44
|
+
include CoerceableWidget
|
|
45
|
+
|
|
44
46
|
##
|
|
45
47
|
##
|
|
46
48
|
##
|
|
@@ -215,19 +217,25 @@ module RatatuiRuby
|
|
|
215
217
|
# SPDX-SnippetEnd
|
|
216
218
|
#++
|
|
217
219
|
def initialize(data:, bar_width: 3, bar_gap: 1, group_gap: 0, max: nil, style: nil, block: nil, direction: :vertical, label_style: nil, value_style: nil, bar_set: nil)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
220
|
+
# Normalize bar_set to Hash[Symbol, String] if provided as Array or Hash
|
|
221
|
+
bar_set = case bar_set
|
|
222
|
+
when Symbol, nil
|
|
223
|
+
bar_set
|
|
224
|
+
when Array
|
|
225
|
+
# Convert Array to Hash using BAR_KEYS order
|
|
226
|
+
BAR_KEYS.zip(bar_set).to_h
|
|
227
|
+
when Hash
|
|
228
|
+
# @type var raw_hash: Hash[untyped, untyped]
|
|
229
|
+
raw_hash = bar_set.dup
|
|
230
|
+
normalized = {} #: Hash[Symbol, String]
|
|
231
|
+
# Normalize numeric keys (0-8) to symbolic keys
|
|
232
|
+
BAR_KEYS.each_with_index do |key, i|
|
|
233
|
+
val = raw_hash.delete(i) || raw_hash.delete(i.to_s) || raw_hash.delete(key)
|
|
234
|
+
normalized[key] = val.to_s if val
|
|
235
|
+
end
|
|
236
|
+
normalized
|
|
237
|
+
else
|
|
238
|
+
bar_set
|
|
231
239
|
end
|
|
232
240
|
|
|
233
241
|
# Normalize data to Array of BarGroup
|
|
@@ -248,12 +256,13 @@ module RatatuiRuby
|
|
|
248
256
|
elsif data.first.is_a?(BarGroup)
|
|
249
257
|
data
|
|
250
258
|
elsif data.first.is_a?(Array)
|
|
251
|
-
# Tuples
|
|
259
|
+
# Tuples - use type assertion for Steep
|
|
252
260
|
if direction == :horizontal
|
|
253
261
|
bars = data.map do |item|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
262
|
+
tuple = item #: Array[untyped]
|
|
263
|
+
label = tuple[0].to_s
|
|
264
|
+
value = tuple[1]
|
|
265
|
+
style = tuple[2]
|
|
257
266
|
|
|
258
267
|
bar = Bar.new(value:, label:)
|
|
259
268
|
bar = bar.with(style:) if style
|
|
@@ -262,9 +271,10 @@ module RatatuiRuby
|
|
|
262
271
|
[BarGroup.new(label: "", bars:)]
|
|
263
272
|
else
|
|
264
273
|
data.map do |item|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
274
|
+
tuple = item #: Array[untyped]
|
|
275
|
+
label = tuple[0].to_s
|
|
276
|
+
value = tuple[1]
|
|
277
|
+
style = tuple[2]
|
|
268
278
|
|
|
269
279
|
bar = Bar.new(value:)
|
|
270
280
|
bar = bar.with(style:) if style
|
|
@@ -23,6 +23,8 @@ module RatatuiRuby
|
|
|
23
23
|
#
|
|
24
24
|
# ruby examples/widget_box/app.rb
|
|
25
25
|
class Block < Data.define(:title, :titles, :title_alignment, :title_style, :borders, :border_style, :border_type, :border_set, :style, :padding, :children)
|
|
26
|
+
include CoerceableWidget
|
|
27
|
+
|
|
26
28
|
##
|
|
27
29
|
# :attr_reader: title
|
|
28
30
|
# The main title displayed on the top border.
|
|
@@ -170,8 +172,8 @@ module RatatuiRuby
|
|
|
170
172
|
if border_set
|
|
171
173
|
border_set = border_set.dup
|
|
172
174
|
%i[top_left top_right bottom_left bottom_right vertical_left vertical_right horizontal_top horizontal_bottom].each do |long_key|
|
|
173
|
-
short_key = long_key.to_s.split("_").map { |s| s[0] }.join
|
|
174
|
-
if (val = border_set.delete(short_key
|
|
175
|
+
short_key = long_key.to_s.split("_").map { |s| s[0] }.join.to_sym
|
|
176
|
+
if (val = border_set.delete(short_key))
|
|
175
177
|
border_set[long_key] = val
|
|
176
178
|
end
|
|
177
179
|
end
|
|
@@ -237,12 +239,18 @@ module RatatuiRuby
|
|
|
237
239
|
top_border = has_border.call(:top) ? 1 : 0
|
|
238
240
|
bottom_border = has_border.call(:bottom) ? 1 : 0
|
|
239
241
|
|
|
240
|
-
# Calculate padding offsets
|
|
241
|
-
if padding.is_a?(Array)
|
|
242
|
+
# Calculate padding offsets - ensure all are Integer
|
|
243
|
+
pad_left, pad_right, pad_top, pad_bottom = if padding.is_a?(Array)
|
|
242
244
|
# [left, right, top, bottom]
|
|
243
|
-
|
|
245
|
+
[
|
|
246
|
+
Integer(padding[0] || 0),
|
|
247
|
+
Integer(padding[1] || 0),
|
|
248
|
+
Integer(padding[2] || 0),
|
|
249
|
+
Integer(padding[3] || 0),
|
|
250
|
+
]
|
|
244
251
|
else
|
|
245
|
-
|
|
252
|
+
p = Integer(padding)
|
|
253
|
+
[p, p, p, p]
|
|
246
254
|
end
|
|
247
255
|
|
|
248
256
|
# Compute inner area
|
|
@@ -24,6 +24,8 @@ module RatatuiRuby
|
|
|
24
24
|
#
|
|
25
25
|
# ruby examples/widget_calendar/app.rb
|
|
26
26
|
class Calendar < Data.define(:year, :month, :events, :default_style, :header_style, :block, :show_weekdays_header, :show_surrounding, :show_month_header)
|
|
27
|
+
include CoerceableWidget
|
|
28
|
+
|
|
27
29
|
##
|
|
28
30
|
# :attr_reader: year
|
|
29
31
|
# The year to display (Integer).
|
|
@@ -190,6 +190,8 @@ module RatatuiRuby
|
|
|
190
190
|
# SPDX-SnippetEnd
|
|
191
191
|
#++
|
|
192
192
|
class Canvas < Data.define(:shapes, :x_bounds, :y_bounds, :marker, :block, :background_color)
|
|
193
|
+
include CoerceableWidget
|
|
194
|
+
|
|
193
195
|
##
|
|
194
196
|
# :attr_reader: shapes
|
|
195
197
|
# Array of shapes to render.
|
|
@@ -236,6 +238,60 @@ module RatatuiRuby
|
|
|
236
238
|
background_color:
|
|
237
239
|
)
|
|
238
240
|
end
|
|
241
|
+
|
|
242
|
+
# Converts canvas coordinates to normalized grid coordinates.
|
|
243
|
+
#
|
|
244
|
+
# Hit testing and layout decisions need to know where a canvas point
|
|
245
|
+
# falls within the drawing surface. This method maps from the canvas
|
|
246
|
+
# coordinate system to normalized [0.0, 1.0] coordinates.
|
|
247
|
+
#
|
|
248
|
+
# Use it to determine if a click or touch event lands within the
|
|
249
|
+
# canvas bounds, and where proportionally.
|
|
250
|
+
#
|
|
251
|
+
# [x] X coordinate in canvas coordinate system.
|
|
252
|
+
# [y] Y coordinate in canvas coordinate system.
|
|
253
|
+
#
|
|
254
|
+
# Returns an Array <tt>[normalized_x, normalized_y]</tt> where each
|
|
255
|
+
# value is between 0.0 and 1.0, or <tt>nil</tt> if the point is
|
|
256
|
+
# outside the canvas bounds.
|
|
257
|
+
#
|
|
258
|
+
# === Example
|
|
259
|
+
#
|
|
260
|
+
#--
|
|
261
|
+
# SPDX-SnippetBegin
|
|
262
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
263
|
+
# SPDX-License-Identifier: MIT-0
|
|
264
|
+
#++
|
|
265
|
+
# canvas = Canvas.new(x_bounds: [0.0, 100.0], y_bounds: [0.0, 50.0])
|
|
266
|
+
# canvas.get_point(50.0, 25.0) # => [0.5, 0.5] (center)
|
|
267
|
+
# canvas.get_point(0.0, 0.0) # => [0.0, 1.0] (bottom-left)
|
|
268
|
+
# canvas.get_point(101.0, 0.0) # => nil (out of bounds)
|
|
269
|
+
#--
|
|
270
|
+
# SPDX-SnippetEnd
|
|
271
|
+
#++
|
|
272
|
+
def get_point(x, y)
|
|
273
|
+
left, right = x_bounds
|
|
274
|
+
bottom, top = y_bounds
|
|
275
|
+
|
|
276
|
+
# Check bounds
|
|
277
|
+
return nil if x < left || x > right || y < bottom || y > top
|
|
278
|
+
|
|
279
|
+
width = right - left
|
|
280
|
+
height = top - bottom
|
|
281
|
+
|
|
282
|
+
# Avoid division by zero
|
|
283
|
+
return nil if width <= 0.0 || height <= 0.0
|
|
284
|
+
|
|
285
|
+
# Normalize to [0.0, 1.0] range
|
|
286
|
+
normalized_x = (x - left) / width
|
|
287
|
+
normalized_y = (top - y) / height # Y inverted: top is 0, bottom is 1
|
|
288
|
+
|
|
289
|
+
[normalized_x, normalized_y]
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Ruby-idiomatic aliases (TIMTOWTDI)
|
|
293
|
+
alias point get_point
|
|
294
|
+
alias [] get_point
|
|
239
295
|
end
|
|
240
296
|
end
|
|
241
297
|
end
|
|
@@ -14,6 +14,8 @@ module RatatuiRuby
|
|
|
14
14
|
# [style] Style
|
|
15
15
|
# [labels_alignment] Symbol (<tt>:left</tt>, <tt>:center</tt>, <tt>:right</tt>)
|
|
16
16
|
class Axis < Data.define(:title, :bounds, :labels, :style, :labels_alignment)
|
|
17
|
+
include CoerceableWidget
|
|
18
|
+
|
|
17
19
|
##
|
|
18
20
|
# :attr_reader: title
|
|
19
21
|
# Label for the axis (String).
|
|
@@ -59,6 +61,8 @@ module RatatuiRuby
|
|
|
59
61
|
# [marker] Symbol (<tt>:dot</tt>, <tt>:braille</tt>, <tt>:block</tt>, <tt>:bar</tt>, <tt>:half_block</tt>)
|
|
60
62
|
# [graph_type] Symbol (<tt>:line</tt>, <tt>:scatter</tt>)
|
|
61
63
|
class Dataset < Data.define(:name, :data, :style, :marker, :graph_type)
|
|
64
|
+
include CoerceableWidget
|
|
65
|
+
|
|
62
66
|
##
|
|
63
67
|
# :attr_reader: name
|
|
64
68
|
# Name for logical identification or legend.
|
|
@@ -118,6 +122,8 @@ module RatatuiRuby
|
|
|
118
122
|
#
|
|
119
123
|
# ruby examples/widget_chart/app.rb
|
|
120
124
|
class Chart < Data.define(:datasets, :x_axis, :y_axis, :block, :style, :legend_position, :hidden_legend_constraints)
|
|
125
|
+
include CoerceableWidget
|
|
126
|
+
|
|
121
127
|
##
|
|
122
128
|
# :attr_reader: datasets
|
|
123
129
|
# Array of Dataset objects to plot.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
module Widgets
|
|
10
|
+
# Mixin that provides DWIM hash coercion for widget classes.
|
|
11
|
+
#
|
|
12
|
+
# When users call `tui.table(hash)` instead of `tui.table(**hash)`,
|
|
13
|
+
# Ruby's `...` forwarding passes the Hash as a positional argument,
|
|
14
|
+
# causing cryptic TypeErrors at the Rust FFI boundary.
|
|
15
|
+
#
|
|
16
|
+
# This mixin provides a `coerce_args` class method that detects
|
|
17
|
+
# this pattern and automatically splats the hash into keyword arguments.
|
|
18
|
+
#
|
|
19
|
+
# === Behavior
|
|
20
|
+
#
|
|
21
|
+
# - **Production mode**: Unknown keys are silently ignored
|
|
22
|
+
# - **Debug mode (RR_DEBUG=1)**: Raises ArgumentError to catch typos early
|
|
23
|
+
#
|
|
24
|
+
# === Usage
|
|
25
|
+
#
|
|
26
|
+
#--
|
|
27
|
+
# SPDX-SnippetBegin
|
|
28
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
29
|
+
# SPDX-License-Identifier: MIT-0
|
|
30
|
+
#++
|
|
31
|
+
# class Table < Data.define(:rows, :widths, ...)
|
|
32
|
+
# include CoerceableWidget
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# # In WidgetFactories:
|
|
36
|
+
# def table(first = nil, **kwargs)
|
|
37
|
+
# Widgets::Table.coerce_args(first, kwargs)
|
|
38
|
+
# end
|
|
39
|
+
#--
|
|
40
|
+
# SPDX-SnippetEnd
|
|
41
|
+
#++
|
|
42
|
+
module CoerceableWidget
|
|
43
|
+
##
|
|
44
|
+
# Hook called when this module is included in a widget class.
|
|
45
|
+
#
|
|
46
|
+
# Extends the class with ClassMethods and defines KNOWN_KEYS constant
|
|
47
|
+
# from the Data.define members for validation.
|
|
48
|
+
#
|
|
49
|
+
# [base] The class including this module.
|
|
50
|
+
def self.included(base)
|
|
51
|
+
base.extend(ClassMethods)
|
|
52
|
+
base.const_set(:KNOWN_KEYS, base.members.freeze) unless base.const_defined?(:KNOWN_KEYS)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Class methods extended onto widget classes.
|
|
56
|
+
module ClassMethods
|
|
57
|
+
# Coerces a bare Hash argument into keyword arguments.
|
|
58
|
+
#
|
|
59
|
+
# @param first [Hash, nil] First positional argument (bare hash case)
|
|
60
|
+
# @param kwargs [Hash] Keyword arguments (normal splatted case)
|
|
61
|
+
# @return [Object] New instance of the widget class
|
|
62
|
+
# @raise [ArgumentError] In debug mode, if unknown keys are present
|
|
63
|
+
def coerce_args(first, kwargs)
|
|
64
|
+
if first.is_a?(Hash) && kwargs.empty?
|
|
65
|
+
unknown = first.keys - self::KNOWN_KEYS
|
|
66
|
+
if unknown.any? && RatatuiRuby::Debug.enabled?
|
|
67
|
+
raise ArgumentError, "#{name}: unknown keys #{unknown.inspect}"
|
|
68
|
+
end
|
|
69
|
+
new(**first.slice(*self::KNOWN_KEYS))
|
|
70
|
+
else
|
|
71
|
+
new(**kwargs)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -32,6 +32,8 @@ module RatatuiRuby
|
|
|
32
32
|
# - {Component-based implementation using Frame API}[link:/examples/app_color_picker/app_rb.html]
|
|
33
33
|
# - RatatuiRuby::Frame#set_cursor_position (Frame API alternative)
|
|
34
34
|
class Cursor < Data.define(:x, :y)
|
|
35
|
+
include CoerceableWidget
|
|
36
|
+
|
|
35
37
|
##
|
|
36
38
|
# :attr_reader: x
|
|
37
39
|
# X coordinate (column).
|
|
@@ -23,6 +23,8 @@ module RatatuiRuby
|
|
|
23
23
|
#
|
|
24
24
|
# ruby examples/widget_gauge/app.rb
|
|
25
25
|
class Gauge < Data.define(:ratio, :label, :style, :gauge_style, :block, :use_unicode)
|
|
26
|
+
include CoerceableWidget
|
|
27
|
+
|
|
26
28
|
##
|
|
27
29
|
# :attr_reader: ratio
|
|
28
30
|
# Progress ratio from 0.0 to 1.0.
|
|
@@ -62,9 +64,16 @@ module RatatuiRuby
|
|
|
62
64
|
# [gauge_style] Style object for the filled bar (optional).
|
|
63
65
|
# [block] Block widget (optional).
|
|
64
66
|
# [use_unicode] Boolean (default: true).
|
|
67
|
+
#
|
|
68
|
+
# Raises ArgumentError if percent is not 0..100.
|
|
65
69
|
def initialize(ratio: nil, percent: nil, label: nil, style: nil, gauge_style: nil, block: nil, use_unicode: true)
|
|
66
70
|
if percent
|
|
67
|
-
|
|
71
|
+
float_percent = Float(percent)
|
|
72
|
+
unless float_percent.between?(0, 100)
|
|
73
|
+
raise ArgumentError, "percent must be between 0 and 100 (got #{percent.inspect})"
|
|
74
|
+
end
|
|
75
|
+
# Float(Numeric) incorrectly returns Float? -- https://github.com/ruby/rbs/issues/2793
|
|
76
|
+
ratio = float_percent / 100.0 #: Float
|
|
68
77
|
end
|
|
69
78
|
ratio = Float(ratio || 0.0)
|
|
70
79
|
super(ratio:, label:, style:, gauge_style:, block:, use_unicode:)
|
|
@@ -72,17 +81,66 @@ module RatatuiRuby
|
|
|
72
81
|
|
|
73
82
|
# Returns true if the gauge has any fill (ratio > 0).
|
|
74
83
|
#
|
|
75
|
-
#
|
|
84
|
+
# === Example
|
|
85
|
+
#
|
|
86
|
+
#--
|
|
87
|
+
# SPDX-SnippetBegin
|
|
88
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
89
|
+
# SPDX-License-Identifier: MIT-0
|
|
90
|
+
#++
|
|
91
|
+
# Widgets::Gauge.new(ratio: 0.0).filled? # => false
|
|
92
|
+
# Widgets::Gauge.new(ratio: 0.5).filled? # => true
|
|
93
|
+
#--
|
|
94
|
+
# SPDX-SnippetEnd
|
|
95
|
+
#++
|
|
76
96
|
def filled?
|
|
77
97
|
ratio > 0
|
|
78
98
|
end
|
|
79
99
|
|
|
80
100
|
# Returns true if the gauge is at 100% or more (ratio >= 1.0).
|
|
81
101
|
#
|
|
82
|
-
#
|
|
102
|
+
# === Example
|
|
103
|
+
#
|
|
104
|
+
#--
|
|
105
|
+
# SPDX-SnippetBegin
|
|
106
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
107
|
+
# SPDX-License-Identifier: MIT-0
|
|
108
|
+
#++
|
|
109
|
+
# Widgets::Gauge.new(ratio: 0.99).complete? # => false
|
|
110
|
+
# Widgets::Gauge.new(ratio: 1.0).complete? # => true
|
|
111
|
+
#--
|
|
112
|
+
# SPDX-SnippetEnd
|
|
113
|
+
#++
|
|
83
114
|
def complete?
|
|
84
115
|
ratio >= 1.0
|
|
85
116
|
end
|
|
117
|
+
|
|
118
|
+
# Returns the progress as an integer percentage (0-100).
|
|
119
|
+
#
|
|
120
|
+
# Gauge stores progress as a ratio (0.0 to 1.0). User-facing code often
|
|
121
|
+
# displays percentages. Converting manually is tedious.
|
|
122
|
+
#
|
|
123
|
+
# This is the inverse of passing <tt>percent:</tt> to the constructor.
|
|
124
|
+
# Rounds down to the nearest integer.
|
|
125
|
+
#
|
|
126
|
+
# === Example
|
|
127
|
+
#
|
|
128
|
+
#--
|
|
129
|
+
# SPDX-SnippetBegin
|
|
130
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
131
|
+
# SPDX-License-Identifier: MIT-0
|
|
132
|
+
#++
|
|
133
|
+
# gauge = Widgets::Gauge.new(percent: 75)
|
|
134
|
+
# gauge.percent # => 75
|
|
135
|
+
#
|
|
136
|
+
# gauge = Widgets::Gauge.new(ratio: 0.456)
|
|
137
|
+
# gauge.percent # => 45
|
|
138
|
+
#--
|
|
139
|
+
# SPDX-SnippetEnd
|
|
140
|
+
#++
|
|
141
|
+
def percent
|
|
142
|
+
(ratio * 100).to_i
|
|
143
|
+
end
|
|
86
144
|
end
|
|
87
145
|
end
|
|
88
146
|
end
|
|
@@ -23,6 +23,8 @@ module RatatuiRuby
|
|
|
23
23
|
#
|
|
24
24
|
# ruby examples/widget_line_gauge/app.rb
|
|
25
25
|
class LineGauge < Data.define(:ratio, :label, :style, :filled_style, :unfilled_style, :block, :filled_symbol, :unfilled_symbol)
|
|
26
|
+
include CoerceableWidget
|
|
27
|
+
|
|
26
28
|
##
|
|
27
29
|
# :attr_reader: ratio
|
|
28
30
|
# Progress ratio from 0.0 to 1.0.
|
|
@@ -58,6 +60,7 @@ module RatatuiRuby
|
|
|
58
60
|
# Creates a new LineGauge.
|
|
59
61
|
#
|
|
60
62
|
# [ratio] Float (0.0 - 1.0).
|
|
63
|
+
# [percent] Integer (0 - 100), alternative to ratio.
|
|
61
64
|
# [label] String or Text::Span (optional).
|
|
62
65
|
# [style] Style (optional, base style for the gauge).
|
|
63
66
|
# [filled_style] Style.
|
|
@@ -65,9 +68,19 @@ module RatatuiRuby
|
|
|
65
68
|
# [block] Block.
|
|
66
69
|
# [filled_symbol] String (default: <tt>"█"</tt>).
|
|
67
70
|
# [unfilled_symbol] String (default: <tt>"░"</tt>).
|
|
68
|
-
|
|
71
|
+
#
|
|
72
|
+
# Raises ArgumentError if percent is not 0..100.
|
|
73
|
+
def initialize(ratio: nil, percent: nil, label: nil, style: nil, filled_style: nil, unfilled_style: nil, block: nil, filled_symbol: "█", unfilled_symbol: "░")
|
|
74
|
+
if percent
|
|
75
|
+
float_percent = Float(percent)
|
|
76
|
+
unless float_percent.between?(0, 100)
|
|
77
|
+
raise ArgumentError, "percent must be between 0 and 100 (got #{percent.inspect})"
|
|
78
|
+
end
|
|
79
|
+
ratio = float_percent / 100.0
|
|
80
|
+
end
|
|
81
|
+
ratio = Float(ratio || 0.0)
|
|
69
82
|
super(
|
|
70
|
-
ratio
|
|
83
|
+
ratio:,
|
|
71
84
|
label:,
|
|
72
85
|
style:,
|
|
73
86
|
filled_style:,
|
|
@@ -80,17 +93,66 @@ module RatatuiRuby
|
|
|
80
93
|
|
|
81
94
|
# Returns true if the gauge has any fill (ratio > 0).
|
|
82
95
|
#
|
|
83
|
-
#
|
|
96
|
+
# === Example
|
|
97
|
+
#
|
|
98
|
+
#--
|
|
99
|
+
# SPDX-SnippetBegin
|
|
100
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
101
|
+
# SPDX-License-Identifier: MIT-0
|
|
102
|
+
#++
|
|
103
|
+
# Widgets::LineGauge.new(ratio: 0.0).filled? # => false
|
|
104
|
+
# Widgets::LineGauge.new(ratio: 0.5).filled? # => true
|
|
105
|
+
#--
|
|
106
|
+
# SPDX-SnippetEnd
|
|
107
|
+
#++
|
|
84
108
|
def filled?
|
|
85
109
|
ratio > 0
|
|
86
110
|
end
|
|
87
111
|
|
|
88
112
|
# Returns true if the gauge is at 100% or more (ratio >= 1.0).
|
|
89
113
|
#
|
|
90
|
-
#
|
|
114
|
+
# === Example
|
|
115
|
+
#
|
|
116
|
+
#--
|
|
117
|
+
# SPDX-SnippetBegin
|
|
118
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
119
|
+
# SPDX-License-Identifier: MIT-0
|
|
120
|
+
#++
|
|
121
|
+
# Widgets::LineGauge.new(ratio: 0.99).complete? # => false
|
|
122
|
+
# Widgets::LineGauge.new(ratio: 1.0).complete? # => true
|
|
123
|
+
#--
|
|
124
|
+
# SPDX-SnippetEnd
|
|
125
|
+
#++
|
|
91
126
|
def complete?
|
|
92
127
|
ratio >= 1.0
|
|
93
128
|
end
|
|
129
|
+
|
|
130
|
+
# Returns the progress as an integer percentage (0-100).
|
|
131
|
+
#
|
|
132
|
+
# LineGauge stores progress as a ratio (0.0 to 1.0). User-facing code often
|
|
133
|
+
# displays percentages. Converting manually is tedious.
|
|
134
|
+
#
|
|
135
|
+
# This is the inverse of passing <tt>percent:</tt> to the constructor.
|
|
136
|
+
# Rounds down to the nearest integer.
|
|
137
|
+
#
|
|
138
|
+
# === Example
|
|
139
|
+
#
|
|
140
|
+
#--
|
|
141
|
+
# SPDX-SnippetBegin
|
|
142
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
143
|
+
# SPDX-License-Identifier: MIT-0
|
|
144
|
+
#++
|
|
145
|
+
# lg = Widgets::LineGauge.new(percent: 75)
|
|
146
|
+
# lg.percent # => 75
|
|
147
|
+
#
|
|
148
|
+
# lg = Widgets::LineGauge.new(ratio: 0.456)
|
|
149
|
+
# lg.percent # => 45
|
|
150
|
+
#--
|
|
151
|
+
# SPDX-SnippetEnd
|
|
152
|
+
#++
|
|
153
|
+
def percent
|
|
154
|
+
(ratio * 100).to_i
|
|
155
|
+
end
|
|
94
156
|
end
|
|
95
157
|
end
|
|
96
158
|
end
|
|
@@ -39,6 +39,8 @@ module RatatuiRuby
|
|
|
39
39
|
# SPDX-SnippetEnd
|
|
40
40
|
#++
|
|
41
41
|
class List < Data.define(:items, :selected_index, :offset, :style, :highlight_style, :highlight_symbol, :repeat_highlight_symbol, :highlight_spacing, :direction, :scroll_padding, :block)
|
|
42
|
+
include CoerceableWidget
|
|
43
|
+
|
|
42
44
|
##
|
|
43
45
|
# Highlight spacing: always show the spacing column.
|
|
44
46
|
HIGHLIGHT_ALWAYS = :always
|
|
@@ -152,17 +154,99 @@ module RatatuiRuby
|
|
|
152
154
|
|
|
153
155
|
# Returns true if an item is selected.
|
|
154
156
|
#
|
|
155
|
-
#
|
|
157
|
+
# === Example
|
|
158
|
+
#
|
|
159
|
+
#--
|
|
160
|
+
# SPDX-SnippetBegin
|
|
161
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
162
|
+
# SPDX-License-Identifier: MIT-0
|
|
163
|
+
#++
|
|
164
|
+
# list = Widgets::List.new(items: %w[a b c])
|
|
165
|
+
# list.selected? # => false
|
|
166
|
+
#
|
|
167
|
+
# list = Widgets::List.new(items: %w[a b c], selected_index: 1)
|
|
168
|
+
# list.selected? # => true
|
|
169
|
+
#
|
|
170
|
+
#--
|
|
171
|
+
# SPDX-SnippetEnd
|
|
172
|
+
#++
|
|
173
|
+
# Returns: Boolean.
|
|
156
174
|
def selected?
|
|
157
175
|
!selected_index.nil?
|
|
158
176
|
end
|
|
159
177
|
|
|
160
|
-
# Returns true if the list
|
|
178
|
+
# Returns true if the list contains no items.
|
|
179
|
+
#
|
|
180
|
+
# === Example
|
|
161
181
|
#
|
|
162
|
-
|
|
182
|
+
#--
|
|
183
|
+
# SPDX-SnippetBegin
|
|
184
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
185
|
+
# SPDX-License-Identifier: MIT-0
|
|
186
|
+
#++
|
|
187
|
+
# list = Widgets::List.new(items: [])
|
|
188
|
+
# list.empty? # => true
|
|
189
|
+
#
|
|
190
|
+
#--
|
|
191
|
+
# SPDX-SnippetEnd
|
|
192
|
+
#++
|
|
193
|
+
# Returns: Boolean.
|
|
163
194
|
def empty?
|
|
164
195
|
items.empty?
|
|
165
196
|
end
|
|
197
|
+
|
|
198
|
+
# Returns the number of items in the list.
|
|
199
|
+
#
|
|
200
|
+
# === Example
|
|
201
|
+
#
|
|
202
|
+
#--
|
|
203
|
+
# SPDX-SnippetBegin
|
|
204
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
205
|
+
# SPDX-License-Identifier: MIT-0
|
|
206
|
+
#++
|
|
207
|
+
# list = Widgets::List.new(items: %w[alpha beta gamma])
|
|
208
|
+
# list.len # => 3
|
|
209
|
+
#
|
|
210
|
+
#--
|
|
211
|
+
# SPDX-SnippetEnd
|
|
212
|
+
#++
|
|
213
|
+
# Returns: Integer.
|
|
214
|
+
def len
|
|
215
|
+
items.length
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
alias length len
|
|
219
|
+
alias size len
|
|
220
|
+
|
|
221
|
+
# NOTE: No 'selection' alias - it's ambiguous whether it returns an item or index.
|
|
222
|
+
# Use selected_index for the index, selected_item for the item.
|
|
223
|
+
|
|
224
|
+
# Returns the currently selected item, or nil if nothing is selected.
|
|
225
|
+
#
|
|
226
|
+
# Accessing the selected item directly requires looking up +items[selected_index]+
|
|
227
|
+
# after checking that +selected_index+ is not nil. This is verbose.
|
|
228
|
+
#
|
|
229
|
+
# This method encapsulates that pattern.
|
|
230
|
+
#
|
|
231
|
+
# === Example
|
|
232
|
+
#
|
|
233
|
+
#--
|
|
234
|
+
# SPDX-SnippetBegin
|
|
235
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
236
|
+
# SPDX-License-Identifier: MIT-0
|
|
237
|
+
#++
|
|
238
|
+
# list = Widgets::List.new(items: %w[alpha beta gamma], selected_index: 1)
|
|
239
|
+
# list.selected_item # => "beta"
|
|
240
|
+
#
|
|
241
|
+
#--
|
|
242
|
+
# SPDX-SnippetEnd
|
|
243
|
+
#++
|
|
244
|
+
# Returns: The item at the selected index, or nil if no selection.
|
|
245
|
+
def selected_item
|
|
246
|
+
return nil if selected_index.nil?
|
|
247
|
+
|
|
248
|
+
items[selected_index]
|
|
249
|
+
end
|
|
166
250
|
end
|
|
167
251
|
end
|
|
168
252
|
end
|