ratatui_ruby 0.9.1 → 0.10.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.
- 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 +98 -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 +40 -9
- 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 +83 -4
- 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
|
@@ -379,6 +379,37 @@ module RatatuiRuby
|
|
|
379
379
|
)
|
|
380
380
|
end
|
|
381
381
|
|
|
382
|
+
# Expands the rect by a uniform margin on all sides.
|
|
383
|
+
#
|
|
384
|
+
# Containers wrap content with decorations. Adding margin to all four edges inline is verbose.
|
|
385
|
+
# Off-by-one errors happen when you forget to double the margin.
|
|
386
|
+
#
|
|
387
|
+
# This method computes the outer area. Saturates x/y at 0 when margin exceeds position.
|
|
388
|
+
# Use Rect#clamp to constrain the result if it may exceed screen bounds.
|
|
389
|
+
#
|
|
390
|
+
# [margin] Integer expansion on all sides.
|
|
391
|
+
#
|
|
392
|
+
# === Example
|
|
393
|
+
#
|
|
394
|
+
#--
|
|
395
|
+
# SPDX-SnippetBegin
|
|
396
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
397
|
+
# SPDX-License-Identifier: MIT-0
|
|
398
|
+
#++
|
|
399
|
+
# rect = Layout::Rect.new(x: 10, y: 10, width: 20, height: 10)
|
|
400
|
+
# rect.outer(5) # => Rect(x: 5, y: 5, width: 30, height: 20)
|
|
401
|
+
#--
|
|
402
|
+
# SPDX-SnippetEnd
|
|
403
|
+
#++
|
|
404
|
+
def outer(margin)
|
|
405
|
+
new_x = [x - margin, 0].max
|
|
406
|
+
new_y = [y - margin, 0].max
|
|
407
|
+
new_width = right + margin - new_x
|
|
408
|
+
new_height = bottom + margin - new_y
|
|
409
|
+
|
|
410
|
+
Rect.new(x: new_x, y: new_y, width: new_width, height: new_height)
|
|
411
|
+
end
|
|
412
|
+
|
|
382
413
|
# Moves the rect without changing size.
|
|
383
414
|
#
|
|
384
415
|
# Animations and drag-and-drop shift widgets.
|
|
@@ -405,6 +436,32 @@ module RatatuiRuby
|
|
|
405
436
|
Rect.new(x: x + dx, y: y + dy, width:, height:)
|
|
406
437
|
end
|
|
407
438
|
|
|
439
|
+
# Changes dimensions while preserving position.
|
|
440
|
+
#
|
|
441
|
+
# Window resizing and responsive layouts adjust size mid-session.
|
|
442
|
+
# Creating a new rect with the same position but different size is common.
|
|
443
|
+
#
|
|
444
|
+
# This method returns a resized copy. Position unchanged.
|
|
445
|
+
#
|
|
446
|
+
# [new_size] Size object with new dimensions.
|
|
447
|
+
#
|
|
448
|
+
# === Example
|
|
449
|
+
#
|
|
450
|
+
#--
|
|
451
|
+
# SPDX-SnippetBegin
|
|
452
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
453
|
+
# SPDX-License-Identifier: MIT-0
|
|
454
|
+
#++
|
|
455
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 20, height: 10)
|
|
456
|
+
# rect.resize(Size.new(width: 40, height: 20))
|
|
457
|
+
# # => Rect(x: 10, y: 5, width: 40, height: 20)
|
|
458
|
+
#--
|
|
459
|
+
# SPDX-SnippetEnd
|
|
460
|
+
#++
|
|
461
|
+
def resize(new_size)
|
|
462
|
+
Rect.new(x:, y:, width: new_size.width, height: new_size.height)
|
|
463
|
+
end
|
|
464
|
+
|
|
408
465
|
# Constrains the rect to fit inside bounds.
|
|
409
466
|
#
|
|
410
467
|
# Popups and tooltips may extend beyond screen edges.
|
|
@@ -516,6 +573,137 @@ module RatatuiRuby
|
|
|
516
573
|
end
|
|
517
574
|
end
|
|
518
575
|
end
|
|
576
|
+
|
|
577
|
+
# Extracts the position (x, y) from this rect.
|
|
578
|
+
#
|
|
579
|
+
# Layout code sometimes separates position from size.
|
|
580
|
+
# Extracting x and y into multiple variables is verbose.
|
|
581
|
+
#
|
|
582
|
+
# This method returns a Position object containing just the coordinates.
|
|
583
|
+
#
|
|
584
|
+
# === Example
|
|
585
|
+
#
|
|
586
|
+
#--
|
|
587
|
+
# SPDX-SnippetBegin
|
|
588
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
589
|
+
# SPDX-License-Identifier: MIT-0
|
|
590
|
+
#++
|
|
591
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
592
|
+
# rect.as_position # => Position(x: 10, y: 5)
|
|
593
|
+
#--
|
|
594
|
+
# SPDX-SnippetEnd
|
|
595
|
+
#++
|
|
596
|
+
def as_position
|
|
597
|
+
Position.new(x:, y:)
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
# Extracts the size (width, height) from this rect.
|
|
601
|
+
#
|
|
602
|
+
# Layout code sometimes separates size from position.
|
|
603
|
+
# Extracting width and height into multiple variables is verbose.
|
|
604
|
+
#
|
|
605
|
+
# This method returns a Size object containing just the dimensions.
|
|
606
|
+
#
|
|
607
|
+
# === Example
|
|
608
|
+
#
|
|
609
|
+
#--
|
|
610
|
+
# SPDX-SnippetBegin
|
|
611
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
612
|
+
# SPDX-License-Identifier: MIT-0
|
|
613
|
+
#++
|
|
614
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
615
|
+
# rect.as_size # => Size(width: 80, height: 24)
|
|
616
|
+
#--
|
|
617
|
+
# SPDX-SnippetEnd
|
|
618
|
+
#++
|
|
619
|
+
def as_size
|
|
620
|
+
Size.new(width:, height:)
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# Returns a new Rect, centered horizontally within this rect based on the constraint.
|
|
624
|
+
#
|
|
625
|
+
# Modal dialogs and centered content need horizontal centering.
|
|
626
|
+
# Computing the left offset manually is error-prone.
|
|
627
|
+
#
|
|
628
|
+
# This method uses Layout to compute the centered position.
|
|
629
|
+
#
|
|
630
|
+
# [constraint] Constraint defining the width of the centered area.
|
|
631
|
+
#
|
|
632
|
+
# === Example
|
|
633
|
+
#
|
|
634
|
+
#--
|
|
635
|
+
# SPDX-SnippetBegin
|
|
636
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
637
|
+
# SPDX-License-Identifier: MIT-0
|
|
638
|
+
#++
|
|
639
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 100, height: 24)
|
|
640
|
+
# rect.centered_horizontally(Constraint.length(40))
|
|
641
|
+
# # => Rect(x: 30, y: 0, width: 40, height: 24)
|
|
642
|
+
#--
|
|
643
|
+
# SPDX-SnippetEnd
|
|
644
|
+
#++
|
|
645
|
+
def centered_horizontally(constraint)
|
|
646
|
+
areas = Layout.split(self, direction: :horizontal, constraints: [constraint], flex: :center)
|
|
647
|
+
areas.first
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# Returns a new Rect, centered vertically within this rect based on the constraint.
|
|
651
|
+
#
|
|
652
|
+
# Modal dialogs and centered content need vertical centering.
|
|
653
|
+
# Computing the top offset manually is error-prone.
|
|
654
|
+
#
|
|
655
|
+
# This method uses Layout to compute the centered position.
|
|
656
|
+
#
|
|
657
|
+
# [constraint] Constraint defining the height of the centered area.
|
|
658
|
+
#
|
|
659
|
+
# === Example
|
|
660
|
+
#
|
|
661
|
+
#--
|
|
662
|
+
# SPDX-SnippetBegin
|
|
663
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
664
|
+
# SPDX-License-Identifier: MIT-0
|
|
665
|
+
#++
|
|
666
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 80, height: 100)
|
|
667
|
+
# rect.centered_vertically(Constraint.length(20))
|
|
668
|
+
# # => Rect(x: 0, y: 40, width: 80, height: 20)
|
|
669
|
+
#--
|
|
670
|
+
# SPDX-SnippetEnd
|
|
671
|
+
#++
|
|
672
|
+
def centered_vertically(constraint)
|
|
673
|
+
areas = Layout.split(self, direction: :vertical, constraints: [constraint], flex: :center)
|
|
674
|
+
areas.first
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Returns a new Rect, centered both horizontally and vertically within this rect.
|
|
678
|
+
#
|
|
679
|
+
# Modal dialogs often need exact centering on both axes.
|
|
680
|
+
# Computing both offsets manually is tedious.
|
|
681
|
+
#
|
|
682
|
+
# This method chains centered_horizontally and centered_vertically.
|
|
683
|
+
#
|
|
684
|
+
# [horizontal_constraint] Constraint defining the width of the centered area.
|
|
685
|
+
# [vertical_constraint] Constraint defining the height of the centered area.
|
|
686
|
+
#
|
|
687
|
+
# === Example
|
|
688
|
+
#
|
|
689
|
+
#--
|
|
690
|
+
# SPDX-SnippetBegin
|
|
691
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
692
|
+
# SPDX-License-Identifier: MIT-0
|
|
693
|
+
#++
|
|
694
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 100, height: 100)
|
|
695
|
+
# rect.centered(Constraint.length(40), Constraint.length(20))
|
|
696
|
+
# # => Rect(x: 30, y: 40, width: 40, height: 20)
|
|
697
|
+
#--
|
|
698
|
+
# SPDX-SnippetEnd
|
|
699
|
+
#++
|
|
700
|
+
def centered(horizontal_constraint, vertical_constraint)
|
|
701
|
+
centered_horizontally(horizontal_constraint).centered_vertically(vertical_constraint)
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# Ruby-idiomatic aliases (TIMTOWTDI)
|
|
705
|
+
alias position as_position
|
|
706
|
+
alias size as_size
|
|
519
707
|
end
|
|
520
708
|
end
|
|
521
709
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
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 Layout
|
|
10
|
+
# Terminal dimensions as width and height.
|
|
11
|
+
#
|
|
12
|
+
# Layout calculations need sizes. Passing width and height
|
|
13
|
+
# as separate arguments is verbose and easy to swap by mistake.
|
|
14
|
+
#
|
|
15
|
+
# This class bundles dimensions into a single immutable object.
|
|
16
|
+
# Extract it from a Rect or create it directly for sizing operations.
|
|
17
|
+
#
|
|
18
|
+
# Use it for terminal dimensions, widget sizing constraints,
|
|
19
|
+
# or anywhere you need width/height without position.
|
|
20
|
+
#
|
|
21
|
+
# === Example
|
|
22
|
+
#
|
|
23
|
+
#--
|
|
24
|
+
# SPDX-SnippetBegin
|
|
25
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
26
|
+
# SPDX-License-Identifier: MIT-0
|
|
27
|
+
#++
|
|
28
|
+
# size = Layout::Size.new(width: 80, height: 24)
|
|
29
|
+
# puts "Terminal is #{size.width} columns by #{size.height} rows"
|
|
30
|
+
#
|
|
31
|
+
# # Extract from a Rect
|
|
32
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
33
|
+
# size = rect.as_size # => Size(width: 80, height: 24)
|
|
34
|
+
#--
|
|
35
|
+
# SPDX-SnippetEnd
|
|
36
|
+
#++
|
|
37
|
+
class Size < Data.define(:width, :height)
|
|
38
|
+
##
|
|
39
|
+
# :attr_reader: width
|
|
40
|
+
# Width in terminal columns.
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# :attr_reader: height
|
|
44
|
+
# Height in terminal rows.
|
|
45
|
+
|
|
46
|
+
# Creates a new Size.
|
|
47
|
+
#
|
|
48
|
+
# [width] Width in columns (Integer, coerced via +Integer()+).
|
|
49
|
+
# [height] Height in rows (Integer, coerced via +Integer()+).
|
|
50
|
+
def initialize(width: 0, height: 0)
|
|
51
|
+
super(width: Integer(width), height: Integer(height))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/ratatui_ruby/layout.rb
CHANGED
|
@@ -10,6 +10,8 @@ module RatatuiRuby
|
|
|
10
10
|
#
|
|
11
11
|
# This module mirrors +ratatui::layout+ and contains:
|
|
12
12
|
# - {Rect} — Rectangle geometry
|
|
13
|
+
# - {Position} — Terminal coordinates
|
|
14
|
+
# - {Size} — Terminal dimensions
|
|
13
15
|
# - {Constraint} — Sizing rules
|
|
14
16
|
# - {Layout} — Space distribution
|
|
15
17
|
module Layout
|
|
@@ -17,5 +19,7 @@ module RatatuiRuby
|
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
require_relative "layout/rect"
|
|
22
|
+
require_relative "layout/position"
|
|
23
|
+
require_relative "layout/size"
|
|
20
24
|
require_relative "layout/constraint"
|
|
21
25
|
require_relative "layout/layout"
|
|
@@ -0,0 +1,149 @@
|
|
|
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 Style
|
|
10
|
+
# Color constructors for creating RGB color values.
|
|
11
|
+
#
|
|
12
|
+
# Styles accept colors in multiple formats: symbols (<tt>:red</tt>),
|
|
13
|
+
# indexed integers (0-255), or hex strings (<tt>"#FF0000"</tt>).
|
|
14
|
+
# Converting from other color representations manually is tedious.
|
|
15
|
+
#
|
|
16
|
+
# This module provides factory methods matching Ratatui's Color API.
|
|
17
|
+
# Convert from hex integers, HSL, or other formats in a single call.
|
|
18
|
+
#
|
|
19
|
+
# Use these constructors when you have color data in non-standard formats.
|
|
20
|
+
#
|
|
21
|
+
# === Example
|
|
22
|
+
#
|
|
23
|
+
#--
|
|
24
|
+
# SPDX-SnippetBegin
|
|
25
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
26
|
+
# SPDX-License-Identifier: MIT-0
|
|
27
|
+
#++
|
|
28
|
+
# # From a hex integer (common in design tools)
|
|
29
|
+
# red = Style::Color.from_u32(0xFF0000)
|
|
30
|
+
# style = Style::Style.new(fg: red)
|
|
31
|
+
#
|
|
32
|
+
# # From HSL (common in color pickers)
|
|
33
|
+
# blue = Style::Color.from_hsl(240, 100, 50)
|
|
34
|
+
# style = Style::Style.new(bg: blue)
|
|
35
|
+
#--
|
|
36
|
+
# SPDX-SnippetEnd
|
|
37
|
+
#++
|
|
38
|
+
module Color
|
|
39
|
+
class << self
|
|
40
|
+
# Creates a color from a 32-bit unsigned integer.
|
|
41
|
+
#
|
|
42
|
+
# Design tools and APIs often represent colors as hex integers.
|
|
43
|
+
# Manually extracting RGB components and formatting is error-prone.
|
|
44
|
+
#
|
|
45
|
+
# This method parses the integer and returns a hex string
|
|
46
|
+
# ready for use with Style.
|
|
47
|
+
#
|
|
48
|
+
# === Example
|
|
49
|
+
#
|
|
50
|
+
#--
|
|
51
|
+
# SPDX-SnippetBegin
|
|
52
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
53
|
+
# SPDX-License-Identifier: MIT-0
|
|
54
|
+
#++
|
|
55
|
+
# Color.from_u32(0xFF0000) # => "#ff0000" (red)
|
|
56
|
+
# Color.from_u32(0x00FF00) # => "#00ff00" (green)
|
|
57
|
+
# Color.from_u32(0x0000FF) # => "#0000ff" (blue)
|
|
58
|
+
#--
|
|
59
|
+
# SPDX-SnippetEnd
|
|
60
|
+
#++
|
|
61
|
+
#
|
|
62
|
+
# [value] Integer in <tt>0xRRGGBB</tt> format.
|
|
63
|
+
#
|
|
64
|
+
# Returns a hex string like <tt>"#rrggbb"</tt>.
|
|
65
|
+
def from_u32(value)
|
|
66
|
+
RatatuiRuby._color_from_u32(value)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Creates a color from HSL (Hue, Saturation, Lightness) values.
|
|
70
|
+
#
|
|
71
|
+
# Color pickers often use HSL because it matches human perception
|
|
72
|
+
# better than RGB. Converting HSL to RGB manually requires math.
|
|
73
|
+
#
|
|
74
|
+
# This method handles the conversion by delegating to Ratatui's
|
|
75
|
+
# palette integration.
|
|
76
|
+
#
|
|
77
|
+
# Note: This implementation uses degrees (0-360) for hue and
|
|
78
|
+
# percentages (0-100) for saturation and lightness, matching
|
|
79
|
+
# common UI conventions.
|
|
80
|
+
#
|
|
81
|
+
# === Example
|
|
82
|
+
#
|
|
83
|
+
#--
|
|
84
|
+
# SPDX-SnippetBegin
|
|
85
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
86
|
+
# SPDX-License-Identifier: MIT-0
|
|
87
|
+
#++
|
|
88
|
+
# Color.from_hsl(0, 100, 50) # => "#ff0000" (red)
|
|
89
|
+
# Color.from_hsl(120, 100, 50) # => "#00ff00" (green)
|
|
90
|
+
# Color.from_hsl(240, 100, 50) # => "#0000ff" (blue)
|
|
91
|
+
# Color.from_hsl(0, 0, 50) # => "#808080" (gray)
|
|
92
|
+
#--
|
|
93
|
+
# SPDX-SnippetEnd
|
|
94
|
+
#++
|
|
95
|
+
#
|
|
96
|
+
# [h] Hue in degrees (0-360). Values wrap automatically.
|
|
97
|
+
# [s] Saturation as percentage (0-100).
|
|
98
|
+
# [l] Lightness as percentage (0-100).
|
|
99
|
+
#
|
|
100
|
+
# Returns a hex string like <tt>"#rrggbb"</tt>.
|
|
101
|
+
def from_hsl(h, s, l)
|
|
102
|
+
RatatuiRuby._color_from_hsl(h.to_f, s.to_f, l.to_f)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Ruby-idiomatic aliases (TIMTOWTDI)
|
|
106
|
+
alias hex from_u32
|
|
107
|
+
alias hsl from_hsl
|
|
108
|
+
|
|
109
|
+
# Creates a color from HSLuv (Human-friendly Hue, Saturation, Lightness) values.
|
|
110
|
+
#
|
|
111
|
+
# HSLuv is a perceptually uniform color space. Unlike standard HSL,
|
|
112
|
+
# colors at the same lightness appear equally bright regardless of hue.
|
|
113
|
+
# This makes it ideal for generating color palettes with consistent
|
|
114
|
+
# perceived brightness.
|
|
115
|
+
#
|
|
116
|
+
# This method delegates to Ratatui's palette integration for the
|
|
117
|
+
# complex HSLuv to RGB conversion.
|
|
118
|
+
#
|
|
119
|
+
# Note: Ratatui uses the range [-180, 180] for hue and [0, 100] for
|
|
120
|
+
# saturation and lightness. This implementation matches those conventions.
|
|
121
|
+
#
|
|
122
|
+
# === Example
|
|
123
|
+
#
|
|
124
|
+
#--
|
|
125
|
+
# SPDX-SnippetBegin
|
|
126
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
127
|
+
# SPDX-License-Identifier: MIT-0
|
|
128
|
+
#++
|
|
129
|
+
# Color.from_hsluv(12.18, 100, 53.2) # => "#ff0000" (bright red)
|
|
130
|
+
# Color.from_hsluv(-94.13, 100, 32.3) # => "#0000ff" (bright blue)
|
|
131
|
+
# Color.from_hsluv(0, 0, 50) # => gray
|
|
132
|
+
#--
|
|
133
|
+
# SPDX-SnippetEnd
|
|
134
|
+
#++
|
|
135
|
+
#
|
|
136
|
+
# [h] Hue in degrees (-180 to 360). Values wrap automatically.
|
|
137
|
+
# [s] Saturation as percentage (0-100). Values are clamped.
|
|
138
|
+
# [l] Lightness as percentage (0-100). Values are clamped.
|
|
139
|
+
#
|
|
140
|
+
# Returns a hex string like <tt>"#rrggbb"</tt>.
|
|
141
|
+
def from_hsluv(h, s, l)
|
|
142
|
+
RatatuiRuby._color_from_hsluv(h.to_f, s.to_f, l.to_f)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
alias hsluv from_hsluv
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -60,7 +60,7 @@ module RatatuiRuby
|
|
|
60
60
|
# ==== String
|
|
61
61
|
# Represents a specific RGB color using a Hex code (<tt>"#RRGGBB"</tt>).
|
|
62
62
|
# Requires a terminal emulator with "True Color" (24-bit color) support.
|
|
63
|
-
class Style < Data.define(:fg, :bg, :modifiers)
|
|
63
|
+
class Style < Data.define(:fg, :bg, :underline_color, :modifiers, :remove_modifiers)
|
|
64
64
|
##
|
|
65
65
|
# :attr_reader: fg
|
|
66
66
|
# Foreground color.
|
|
@@ -73,19 +73,36 @@ module RatatuiRuby
|
|
|
73
73
|
#
|
|
74
74
|
# Symbol (<tt>:black</tt>), Hex String (<tt>"#000000"</tt>), or Integer (0-255).
|
|
75
75
|
|
|
76
|
+
##
|
|
77
|
+
# :attr_reader: underline_color
|
|
78
|
+
# Color of the underline.
|
|
79
|
+
#
|
|
80
|
+
# Symbol (<tt>:red</tt>), Hex String (<tt>"#ff0000"</tt>), or Integer (0-255).
|
|
81
|
+
# Distinct from foreground color. Terminals supporting this feature render
|
|
82
|
+
# the underline in this color while text remains in the foreground color.
|
|
83
|
+
|
|
76
84
|
##
|
|
77
85
|
# :attr_reader: modifiers
|
|
78
|
-
# Text effects.
|
|
86
|
+
# Text effects to add.
|
|
79
87
|
#
|
|
80
88
|
# Array of symbols: <tt>:bold</tt>, <tt>:dim</tt>, <tt>:italic</tt>, <tt>:underlined</tt>,
|
|
81
89
|
# <tt>:slow_blink</tt>, <tt>:rapid_blink</tt>, <tt>:reversed</tt>, <tt>:hidden</tt>, <tt>:crossed_out</tt>.
|
|
82
90
|
|
|
91
|
+
##
|
|
92
|
+
# :attr_reader: remove_modifiers
|
|
93
|
+
# Text effects to remove.
|
|
94
|
+
#
|
|
95
|
+
# Array of symbols. When this style is applied, these modifiers are removed
|
|
96
|
+
# from any inherited/patched styles. Corresponds to Ratatui's sub_modifier.
|
|
97
|
+
|
|
83
98
|
# Creates a new Style.
|
|
84
99
|
#
|
|
85
100
|
# [fg] Color (Symbol/String/Integer).
|
|
86
101
|
# [bg] Color (Symbol/String/Integer).
|
|
87
|
-
# [
|
|
88
|
-
|
|
102
|
+
# [underline_color] Color for underline (Symbol/String/Integer).
|
|
103
|
+
# [modifiers] Array of Symbols to add.
|
|
104
|
+
# [remove_modifiers] Array of Symbols to remove (Ratatui: sub_modifier).
|
|
105
|
+
def initialize(fg: nil, bg: nil, underline_color: nil, modifiers: [], remove_modifiers: [])
|
|
89
106
|
super
|
|
90
107
|
end
|
|
91
108
|
|
|
@@ -95,6 +112,36 @@ module RatatuiRuby
|
|
|
95
112
|
def self.default
|
|
96
113
|
new
|
|
97
114
|
end
|
|
115
|
+
|
|
116
|
+
# Creates a new Style (convenience alias for {#initialize}).
|
|
117
|
+
#
|
|
118
|
+
# Constructor keyword arguments require typing out the full <tt>Style.new</tt> form.
|
|
119
|
+
# This gets verbose in tight layout code or one-liners.
|
|
120
|
+
#
|
|
121
|
+
# <tt>Style.with</tt> reads more naturally and enables method chaining.
|
|
122
|
+
# It shows intent: "use this style with these properties."
|
|
123
|
+
#
|
|
124
|
+
# Use it for inline styling where conciseness matters.
|
|
125
|
+
#
|
|
126
|
+
# === Examples
|
|
127
|
+
#
|
|
128
|
+
#--
|
|
129
|
+
# SPDX-SnippetBegin
|
|
130
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
131
|
+
# SPDX-License-Identifier: MIT-0
|
|
132
|
+
#++
|
|
133
|
+
# Style.with(fg: :red, bg: :black, modifiers: [:bold])
|
|
134
|
+
# Style.with(fg: :white, modifiers: [:underlined], underline_color: :red)
|
|
135
|
+
# Style.with(modifiers: [:bold], remove_modifiers: [:italic]) # Add bold, remove italic
|
|
136
|
+
# paragraph = Paragraph.new(text: "Alert!", style: Style.with(fg: :red))
|
|
137
|
+
#--
|
|
138
|
+
# SPDX-SnippetEnd
|
|
139
|
+
#++
|
|
140
|
+
#
|
|
141
|
+
# @return [Style]
|
|
142
|
+
def self.with(fg: nil, bg: nil, underline_color: nil, modifiers: [], remove_modifiers: [])
|
|
143
|
+
new(fg:, bg:, underline_color:, modifiers:, remove_modifiers:)
|
|
144
|
+
end
|
|
98
145
|
end
|
|
99
146
|
end
|
|
100
147
|
end
|
data/lib/ratatui_ruby/style.rb
CHANGED
|
@@ -10,8 +10,10 @@ module RatatuiRuby
|
|
|
10
10
|
#
|
|
11
11
|
# This module mirrors +ratatui::style+ and contains:
|
|
12
12
|
# - {Style} — Colors and modifiers
|
|
13
|
+
# - {Color} — Color constructors
|
|
13
14
|
module Style
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
require_relative "style/style"
|
|
19
|
+
require_relative "style/color"
|