ratatui_ruby 0.6.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +26 -1
- data/doc/application_architecture.md +16 -16
- data/doc/application_testing.md +1 -1
- data/doc/async.md +160 -0
- data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
- data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
- data/doc/contributors/architectural_overhaul/task.md +37 -0
- data/doc/contributors/design/ruby_frontend.md +277 -81
- data/doc/contributors/design/rust_backend.md +349 -55
- data/doc/contributors/developing_examples.md +5 -5
- data/doc/contributors/index.md +7 -5
- data/doc/contributors/v1.0.0_blockers.md +1729 -0
- data/doc/debugging.md +71 -0
- data/doc/index.md +11 -6
- data/doc/interactive_design.md +2 -2
- data/doc/quickstart.md +66 -97
- data/doc/v0.7.0_migration.md +236 -0
- data/doc/why.md +93 -0
- data/examples/app_all_events/README.md +6 -4
- data/examples/app_all_events/app.rb +1 -1
- data/examples/app_all_events/model/app_model.rb +1 -1
- data/examples/app_all_events/model/msg.rb +1 -1
- data/examples/app_all_events/update.rb +1 -1
- data/examples/app_all_events/view/app_view.rb +1 -1
- data/examples/app_all_events/view/controls_view.rb +1 -1
- data/examples/app_all_events/view/counts_view.rb +1 -1
- data/examples/app_all_events/view/live_view.rb +1 -1
- data/examples/app_all_events/view/log_view.rb +1 -1
- data/examples/app_color_picker/README.md +7 -5
- data/examples/app_color_picker/app.rb +1 -1
- data/examples/app_login_form/README.md +2 -0
- data/examples/app_stateful_interaction/README.md +2 -0
- data/examples/app_stateful_interaction/app.rb +1 -1
- data/examples/verify_quickstart_dsl/README.md +4 -3
- data/examples/verify_quickstart_dsl/app.rb +1 -1
- data/examples/verify_quickstart_layout/README.md +1 -1
- data/examples/verify_quickstart_lifecycle/README.md +3 -3
- data/examples/verify_quickstart_lifecycle/app.rb +2 -2
- data/examples/verify_readme_usage/README.md +1 -1
- data/examples/widget_barchart_demo/README.md +2 -1
- data/examples/widget_block_demo/README.md +2 -0
- data/examples/widget_box_demo/README.md +3 -3
- data/examples/widget_calendar_demo/README.md +3 -3
- data/examples/widget_calendar_demo/app.rb +5 -1
- data/examples/widget_canvas_demo/README.md +3 -3
- data/examples/widget_cell_demo/README.md +3 -3
- data/examples/widget_center_demo/README.md +3 -3
- data/examples/widget_chart_demo/README.md +3 -3
- data/examples/widget_gauge_demo/README.md +3 -3
- data/examples/widget_layout_split/README.md +3 -3
- data/examples/widget_line_gauge_demo/README.md +3 -3
- data/examples/widget_list_demo/README.md +3 -3
- data/examples/widget_map_demo/README.md +3 -3
- data/examples/widget_map_demo/app.rb +2 -2
- data/examples/widget_overlay_demo/README.md +36 -0
- data/examples/widget_popup_demo/README.md +3 -3
- data/examples/widget_ratatui_logo_demo/README.md +3 -3
- data/examples/widget_ratatui_logo_demo/app.rb +1 -1
- data/examples/widget_ratatui_mascot_demo/README.md +3 -3
- data/examples/widget_rect/README.md +3 -3
- data/examples/widget_render/README.md +3 -3
- data/examples/widget_render/app.rb +3 -3
- data/examples/widget_rich_text/README.md +3 -3
- data/examples/widget_scroll_text/README.md +3 -3
- data/examples/widget_scrollbar_demo/README.md +3 -3
- data/examples/widget_sparkline_demo/README.md +3 -3
- data/examples/widget_style_colors/README.md +3 -3
- data/examples/widget_table_demo/README.md +3 -3
- data/examples/widget_table_demo/app.rb +19 -4
- data/examples/widget_tabs_demo/README.md +3 -3
- data/examples/widget_text_width/README.md +3 -3
- data/examples/widget_text_width/app.rb +8 -1
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/src/frame.rs +6 -5
- data/ext/ratatui_ruby/src/lib.rs +3 -2
- data/ext/ratatui_ruby/src/rendering.rs +22 -21
- data/ext/ratatui_ruby/src/style.rs +25 -9
- data/ext/ratatui_ruby/src/text.rs +12 -3
- data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
- data/ext/ratatui_ruby/src/widgets/table.rs +81 -36
- data/lib/ratatui_ruby/buffer/cell.rb +168 -0
- data/lib/ratatui_ruby/buffer.rb +15 -0
- data/lib/ratatui_ruby/frame.rb +8 -8
- data/lib/ratatui_ruby/layout/constraint.rb +95 -0
- data/lib/ratatui_ruby/layout/layout.rb +106 -0
- data/lib/ratatui_ruby/layout/rect.rb +118 -0
- data/lib/ratatui_ruby/layout.rb +19 -0
- data/lib/ratatui_ruby/list_state.rb +2 -2
- data/lib/ratatui_ruby/schema/layout.rb +1 -1
- data/lib/ratatui_ruby/schema/row.rb +66 -0
- data/lib/ratatui_ruby/schema/table.rb +10 -10
- data/lib/ratatui_ruby/schema/text.rb +27 -2
- data/lib/ratatui_ruby/style/style.rb +81 -0
- data/lib/ratatui_ruby/style.rb +15 -0
- data/lib/ratatui_ruby/table_state.rb +1 -1
- data/lib/ratatui_ruby/test_helper/snapshot.rb +24 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +1 -1
- data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/core.rb +38 -0
- data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
- data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
- data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
- data/lib/ratatui_ruby/tui.rb +75 -0
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
- data/lib/ratatui_ruby/widgets/block.rb +192 -0
- data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
- data/lib/ratatui_ruby/widgets/cell.rb +47 -0
- data/lib/ratatui_ruby/widgets/center.rb +59 -0
- data/lib/ratatui_ruby/widgets/chart.rb +185 -0
- data/lib/ratatui_ruby/widgets/clear.rb +54 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
- data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
- data/lib/ratatui_ruby/widgets/list.rb +127 -0
- data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
- data/lib/ratatui_ruby/widgets/row.rb +68 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
- data/lib/ratatui_ruby/widgets/table.rb +141 -0
- data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
- data/lib/ratatui_ruby/widgets.rb +40 -0
- data/lib/ratatui_ruby.rb +23 -39
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_all_events/view_state.rbs +1 -1
- data/sig/ratatui_ruby/schema/row.rbs +22 -0
- data/sig/ratatui_ruby/schema/table.rbs +1 -1
- data/sig/ratatui_ruby/schema/text.rbs +1 -0
- data/sig/ratatui_ruby/session.rbs +29 -49
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/core.rbs +14 -0
- data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
- data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
- data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
- data/sig/ratatui_ruby/tui.rbs +19 -0
- data/tasks/autodoc.rake +1 -35
- data/tasks/bump/changelog.rb +8 -0
- data/tasks/bump/ruby_gem.rb +12 -0
- data/tasks/bump/unreleased_section.rb +16 -0
- data/tasks/sourcehut.rake +4 -1
- metadata +64 -15
- data/doc/contributors/dwim_dx.md +0 -366
- data/doc/contributors/examples_audit/p1_high.md +0 -21
- data/doc/contributors/examples_audit/p2_moderate.md +0 -81
- data/doc/contributors/examples_audit.md +0 -41
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/lib/ratatui_ruby/session/autodoc.rb +0 -482
- data/lib/ratatui_ruby/session.rb +0 -178
- data/tasks/autodoc/inventory.rb +0 -63
- data/tasks/autodoc/notice.rb +0 -26
- data/tasks/autodoc/rbs.rb +0 -38
- data/tasks/autodoc/rdoc.rb +0 -45
|
@@ -0,0 +1,118 @@
|
|
|
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 Layout
|
|
8
|
+
# Defines a rectangular area in the terminal grid.
|
|
9
|
+
#
|
|
10
|
+
# Geometry management involves passing groups of four integers (`x, y, width, height`) repeatedly.
|
|
11
|
+
# This is verbose and prone to parameter mismatch errors.
|
|
12
|
+
#
|
|
13
|
+
# This class encapsulates the geometry. It provides a standard primitive for passing area definitions
|
|
14
|
+
# between layout engines and rendering functions.
|
|
15
|
+
#
|
|
16
|
+
# Use it when manual positioning is required or when querying layout results.
|
|
17
|
+
#
|
|
18
|
+
# === Examples
|
|
19
|
+
#
|
|
20
|
+
# area = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
21
|
+
# puts area.width # => 80
|
|
22
|
+
class Rect < Data.define(:x, :y, :width, :height)
|
|
23
|
+
##
|
|
24
|
+
# :attr_reader: x
|
|
25
|
+
# X coordinate (column) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# :attr_reader: y
|
|
29
|
+
# Y coordinate (row) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# :attr_reader: width
|
|
33
|
+
# Width in characters (Integer, coerced via +to_int+ or +to_i+).
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# :attr_reader: height
|
|
37
|
+
# Height in characters (Integer, coerced via +to_int+ or +to_i+).
|
|
38
|
+
|
|
39
|
+
# Creates a new Rect.
|
|
40
|
+
#
|
|
41
|
+
# All parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
|
|
42
|
+
#
|
|
43
|
+
# [x] Column index (Numeric).
|
|
44
|
+
# [y] Row index (Numeric).
|
|
45
|
+
# [width] Width in columns (Numeric).
|
|
46
|
+
# [height] Height in rows (Numeric).
|
|
47
|
+
def initialize(x: 0, y: 0, width: 0, height: 0)
|
|
48
|
+
super(
|
|
49
|
+
x: Integer(x),
|
|
50
|
+
y: Integer(y),
|
|
51
|
+
width: Integer(width),
|
|
52
|
+
height: Integer(height)
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Tests whether a point is inside this rectangle.
|
|
57
|
+
#
|
|
58
|
+
# Essential for hit testing mouse clicks against layout regions.
|
|
59
|
+
#
|
|
60
|
+
# area = Layout::Rect.new(x: 10, y: 5, width: 20, height: 10)
|
|
61
|
+
# area.contains?(15, 8) # => true
|
|
62
|
+
# area.contains?(5, 8) # => false
|
|
63
|
+
#
|
|
64
|
+
# [px]
|
|
65
|
+
# X coordinate to test (column).
|
|
66
|
+
# [py]
|
|
67
|
+
# Y coordinate to test (row).
|
|
68
|
+
#
|
|
69
|
+
# Returns true if the point (px, py) is within the rectangle bounds.
|
|
70
|
+
def contains?(px, py)
|
|
71
|
+
px >= x && px < x + width && py >= y && py < y + height
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Tests whether this rectangle overlaps with another.
|
|
75
|
+
#
|
|
76
|
+
# Essential for determining if a widget is visible within a viewport or clipping area.
|
|
77
|
+
#
|
|
78
|
+
# viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
79
|
+
# widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
80
|
+
# viewport.intersects?(widget) # => true (partial overlap)
|
|
81
|
+
#
|
|
82
|
+
# [other]
|
|
83
|
+
# Another Rect to test against.
|
|
84
|
+
#
|
|
85
|
+
# Returns true if the rectangles overlap.
|
|
86
|
+
def intersects?(other)
|
|
87
|
+
x < other.x + other.width &&
|
|
88
|
+
x + width > other.x &&
|
|
89
|
+
y < other.y + other.height &&
|
|
90
|
+
y + height > other.y
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns the overlapping area between this rectangle and another.
|
|
94
|
+
#
|
|
95
|
+
# Essential for calculating visible portions of widgets inside scroll views.
|
|
96
|
+
#
|
|
97
|
+
# viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
98
|
+
# widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
99
|
+
# visible = viewport.intersection(widget)
|
|
100
|
+
# # => Rect(x: 70, y: 20, width: 10, height: 4)
|
|
101
|
+
#
|
|
102
|
+
# [other]
|
|
103
|
+
# Another Rect to intersect with.
|
|
104
|
+
#
|
|
105
|
+
# Returns a new Rect representing the intersection, or +nil+ if no overlap.
|
|
106
|
+
def intersection(other)
|
|
107
|
+
return nil unless intersects?(other)
|
|
108
|
+
|
|
109
|
+
new_x = [x, other.x].max
|
|
110
|
+
new_y = [y, other.y].max
|
|
111
|
+
new_right = [x + width, other.x + other.width].min
|
|
112
|
+
new_bottom = [y + height, other.y + other.height].min
|
|
113
|
+
|
|
114
|
+
Rect.new(x: new_x, y: new_y, width: new_right - new_x, height: new_bottom - new_y)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
# Layout primitives for geometry and space distribution.
|
|
8
|
+
#
|
|
9
|
+
# This module mirrors +ratatui::layout+ and contains:
|
|
10
|
+
# - {Rect} — Rectangle geometry
|
|
11
|
+
# - {Constraint} — Sizing rules
|
|
12
|
+
# - {Layout} — Space distribution
|
|
13
|
+
module Layout
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
require_relative "layout/rect"
|
|
18
|
+
require_relative "layout/constraint"
|
|
19
|
+
require_relative "layout/layout"
|
|
@@ -18,7 +18,7 @@ module RatatuiRuby
|
|
|
18
18
|
# == Thread/Ractor Safety
|
|
19
19
|
#
|
|
20
20
|
# ListState is *not* Ractor-shareable. It contains mutable internal state.
|
|
21
|
-
# Store it in instance variables, not in immutable
|
|
21
|
+
# Store it in instance variables, not in immutable Models.
|
|
22
22
|
#
|
|
23
23
|
# == Example
|
|
24
24
|
#
|
|
@@ -26,7 +26,7 @@ module RatatuiRuby
|
|
|
26
26
|
# @list_state.select(2) # Select third item
|
|
27
27
|
#
|
|
28
28
|
# RatatuiRuby.draw do |frame|
|
|
29
|
-
# list = RatatuiRuby::List.new(items: ["A", "B", "C", "D", "E"])
|
|
29
|
+
# list = RatatuiRuby::Widgets::List.new(items: ["A", "B", "C", "D", "E"])
|
|
30
30
|
# frame.render_stateful_widget(list, frame.area, @list_state)
|
|
31
31
|
# end
|
|
32
32
|
#
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
# A styled table row combining cells with optional row-level styling.
|
|
8
|
+
#
|
|
9
|
+
# By default, Table rows are arrays of cell content. For more control over styling
|
|
10
|
+
# individual rows, wrap the cells in a Row object to apply row-level style.
|
|
11
|
+
#
|
|
12
|
+
# The cells can be Strings, Text::Spans, Text::Lines, Paragraphs, or Cells.
|
|
13
|
+
# The style applies to the entire row background.
|
|
14
|
+
#
|
|
15
|
+
# === Examples
|
|
16
|
+
#
|
|
17
|
+
# # Row with red background
|
|
18
|
+
# Row.new(cells: ["Error", "Something went wrong"], style: Style.new(bg: :red))
|
|
19
|
+
#
|
|
20
|
+
# # Row with styled cells and custom height
|
|
21
|
+
# Row.new(
|
|
22
|
+
# cells: [
|
|
23
|
+
# Text::Span.new(content: "Status", style: Style.new(modifiers: [:bold])),
|
|
24
|
+
# Text::Span.new(content: "OK", style: Style.new(fg: :green))
|
|
25
|
+
# ],
|
|
26
|
+
# height: 2
|
|
27
|
+
# )
|
|
28
|
+
class Row < Data.define(:cells, :style, :height, :top_margin, :bottom_margin)
|
|
29
|
+
##
|
|
30
|
+
# :attr_reader: cells
|
|
31
|
+
# The cells to display (Array of Strings, Text::Spans, Text::Lines, Paragraphs, or Cells).
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# :attr_reader: style
|
|
35
|
+
# The style to apply to the row (optional Style).
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# :attr_reader: height
|
|
39
|
+
# Fixed row height in lines (optional Integer).
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# :attr_reader: top_margin
|
|
43
|
+
# Margin above the row in lines (optional Integer).
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# :attr_reader: bottom_margin
|
|
47
|
+
# Margin below the row in lines (optional Integer).
|
|
48
|
+
|
|
49
|
+
# Creates a new Row.
|
|
50
|
+
#
|
|
51
|
+
# [cells] Array of Strings, Text::Spans, Text::Lines, Paragraphs, or Cells.
|
|
52
|
+
# [style] Style object (optional).
|
|
53
|
+
# [height] Integer for fixed height (optional).
|
|
54
|
+
# [top_margin] Integer for top margin (optional).
|
|
55
|
+
# [bottom_margin] Integer for bottom margin (optional).
|
|
56
|
+
def initialize(cells:, style: nil, height: nil, top_margin: nil, bottom_margin: nil)
|
|
57
|
+
super(
|
|
58
|
+
cells:,
|
|
59
|
+
style:,
|
|
60
|
+
height: height.nil? ? nil : Integer(height),
|
|
61
|
+
top_margin: top_margin.nil? ? nil : Integer(top_margin),
|
|
62
|
+
bottom_margin: bottom_margin.nil? ? nil : Integer(bottom_margin)
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -20,21 +20,21 @@ module RatatuiRuby
|
|
|
20
20
|
# Run the interactive demo from the terminal:
|
|
21
21
|
#
|
|
22
22
|
# ruby examples/widget_table_flex/app.rb
|
|
23
|
-
class Table < Data.define(:header, :rows, :widths, :
|
|
23
|
+
class Table < Data.define(:header, :rows, :widths, :row_highlight_style, :highlight_symbol, :highlight_spacing, :column_highlight_style, :cell_highlight_style, :selected_row, :selected_column, :offset, :block, :footer, :flex, :style, :column_spacing)
|
|
24
24
|
##
|
|
25
25
|
# :attr_reader: header
|
|
26
|
-
# Header row content (Array of Strings).
|
|
26
|
+
# Header row content (Array of Strings, Text::Spans, Text::Lines, or Paragraphs).
|
|
27
27
|
|
|
28
28
|
##
|
|
29
29
|
# :attr_reader: rows
|
|
30
|
-
# Data rows (Array of Arrays
|
|
30
|
+
# Data rows (Array of Arrays). Each cell can be String, Text::Span, Text::Line, Paragraph, or Cell.
|
|
31
31
|
|
|
32
32
|
##
|
|
33
33
|
# :attr_reader: widths
|
|
34
34
|
# Column width constraints (Array of Constraint).
|
|
35
35
|
|
|
36
36
|
##
|
|
37
|
-
# :attr_reader:
|
|
37
|
+
# :attr_reader: row_highlight_style
|
|
38
38
|
# Style for the selected row.
|
|
39
39
|
|
|
40
40
|
##
|
|
@@ -83,7 +83,7 @@ module RatatuiRuby
|
|
|
83
83
|
|
|
84
84
|
##
|
|
85
85
|
# :attr_reader: footer
|
|
86
|
-
# Footer row content (Array of Strings).
|
|
86
|
+
# Footer row content (Array of Strings, Text::Spans, Text::Lines, or Paragraphs).
|
|
87
87
|
|
|
88
88
|
##
|
|
89
89
|
# :attr_reader: flex
|
|
@@ -99,10 +99,10 @@ module RatatuiRuby
|
|
|
99
99
|
|
|
100
100
|
# Creates a new Table.
|
|
101
101
|
#
|
|
102
|
-
# [header] Array of strings
|
|
103
|
-
# [rows] 2D Array
|
|
102
|
+
# [header] Array of strings, Text::Spans, Text::Lines, or paragraphs.
|
|
103
|
+
# [rows] 2D Array where each cell is String, Text::Span, Text::Line, Paragraph, or Cell.
|
|
104
104
|
# [widths] Array of Constraints.
|
|
105
|
-
# [
|
|
105
|
+
# [row_highlight_style] Style object.
|
|
106
106
|
# [highlight_symbol] String.
|
|
107
107
|
# [highlight_spacing] Symbol (optional, default: <tt>:when_selected</tt>).
|
|
108
108
|
# [column_highlight_style] Style object.
|
|
@@ -115,12 +115,12 @@ module RatatuiRuby
|
|
|
115
115
|
# [flex] Symbol (optional, default: <tt>:legacy</tt>).
|
|
116
116
|
# [style] Style object or Hash (optional).
|
|
117
117
|
# [column_spacing] Integer (optional, default: 1).
|
|
118
|
-
def initialize(header: nil, rows: [], widths: [],
|
|
118
|
+
def initialize(header: nil, rows: [], widths: [], row_highlight_style: nil, highlight_symbol: "> ", highlight_spacing: :when_selected, column_highlight_style: nil, cell_highlight_style: nil, selected_row: nil, selected_column: nil, offset: nil, block: nil, footer: nil, flex: :legacy, style: nil, column_spacing: 1)
|
|
119
119
|
super(
|
|
120
120
|
header:,
|
|
121
121
|
rows:,
|
|
122
122
|
widths:,
|
|
123
|
-
|
|
123
|
+
row_highlight_style:,
|
|
124
124
|
highlight_symbol:,
|
|
125
125
|
highlight_spacing:,
|
|
126
126
|
column_highlight_style:,
|
|
@@ -94,7 +94,7 @@ module RatatuiRuby
|
|
|
94
94
|
# Text::Span.styled("kerrick", Style.new(fg: :blue))
|
|
95
95
|
# ]
|
|
96
96
|
# )
|
|
97
|
-
class Line < Data.define(:spans, :alignment)
|
|
97
|
+
class Line < Data.define(:spans, :alignment, :style)
|
|
98
98
|
##
|
|
99
99
|
# :attr_reader: spans
|
|
100
100
|
# Array of Span objects.
|
|
@@ -105,11 +105,18 @@ module RatatuiRuby
|
|
|
105
105
|
#
|
|
106
106
|
# <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
|
|
107
107
|
|
|
108
|
+
##
|
|
109
|
+
# :attr_reader: style
|
|
110
|
+
# Line-level style applied to all spans.
|
|
111
|
+
#
|
|
112
|
+
# A Style object that sets colors/modifiers for the entire line.
|
|
113
|
+
|
|
108
114
|
# Creates a new Line.
|
|
109
115
|
#
|
|
110
116
|
# [spans] Array of Span objects (or Strings).
|
|
111
117
|
# [alignment] Symbol (optional).
|
|
112
|
-
|
|
118
|
+
# [style] Style object (optional).
|
|
119
|
+
def initialize(spans: [], alignment: nil, style: nil)
|
|
113
120
|
super
|
|
114
121
|
end
|
|
115
122
|
|
|
@@ -119,6 +126,24 @@ module RatatuiRuby
|
|
|
119
126
|
def self.from_string(content, alignment: nil)
|
|
120
127
|
new(spans: [Span.new(content:, style: nil)], alignment:)
|
|
121
128
|
end
|
|
129
|
+
|
|
130
|
+
# Calculates the display width of this line in terminal cells.
|
|
131
|
+
#
|
|
132
|
+
# Sums the widths of all span contents using the same unicode-aware
|
|
133
|
+
# algorithm as Text.width. Useful for layout calculations.
|
|
134
|
+
#
|
|
135
|
+
# === Examples
|
|
136
|
+
#
|
|
137
|
+
# line = Text::Line.new(spans: [
|
|
138
|
+
# Text::Span.new(content: "Hello "),
|
|
139
|
+
# Text::Span.new(content: "世界")
|
|
140
|
+
# ])
|
|
141
|
+
# line.width # => 10 (6 ASCII + 4 CJK)
|
|
142
|
+
#
|
|
143
|
+
# Returns: Integer (number of terminal cells)
|
|
144
|
+
def width
|
|
145
|
+
RatatuiRuby::Text.width(spans.map { |s| s.content.to_s }.join)
|
|
146
|
+
end
|
|
122
147
|
end
|
|
123
148
|
|
|
124
149
|
##
|
|
@@ -0,0 +1,81 @@
|
|
|
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 Style
|
|
8
|
+
# Defines colors and text modifiers.
|
|
9
|
+
#
|
|
10
|
+
# The terminal is traditionally monochrome, but efficient interfaces use color to convey meaning.
|
|
11
|
+
# Red for errors. Green for success. Bold for headers.
|
|
12
|
+
#
|
|
13
|
+
# This value object encapsulates those choices. It applies foreground and background colors. It adds effects like italics or blinking.
|
|
14
|
+
#
|
|
15
|
+
# Use it to theme your application or highlight critical data.
|
|
16
|
+
#
|
|
17
|
+
# === Examples
|
|
18
|
+
#
|
|
19
|
+
# # Standard colors
|
|
20
|
+
# Style::Style.new(fg: :red, bg: :white, modifiers: [:bold])
|
|
21
|
+
#
|
|
22
|
+
# # Hex colors
|
|
23
|
+
# Style::Style.new(fg: "#ff00ff")
|
|
24
|
+
#
|
|
25
|
+
# === Supported Colors
|
|
26
|
+
#
|
|
27
|
+
# ==== Integer
|
|
28
|
+
# Represents an indexed color from the Xterm 256-color palette (0-255).
|
|
29
|
+
# * <tt>0</tt>–<tt>15</tt>: Standard and bright ANSI colors.
|
|
30
|
+
# * <tt>16</tt>–<tt>231</tt>: {6x6x6 Color Cube}[https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit].
|
|
31
|
+
# * <tt>232</tt>–<tt>255</tt>: Grayscale ramp.
|
|
32
|
+
#
|
|
33
|
+
# ==== Symbol
|
|
34
|
+
# Represents a named color from the standard ANSI palette. Supported values:
|
|
35
|
+
# * <tt>:black</tt>, <tt>:red</tt>, <tt>:green</tt>, <tt>:yellow</tt>,
|
|
36
|
+
# <tt>:blue</tt>, <tt>:magenta</tt>, <tt>:cyan</tt>, <tt>:gray</tt>
|
|
37
|
+
# * <tt>:dark_gray</tt>, <tt>:light_red</tt>, <tt>:light_green</tt>,
|
|
38
|
+
# <tt>:light_yellow</tt>, <tt>:light_blue</tt>, <tt>:light_magenta</tt>,
|
|
39
|
+
# <tt>:light_cyan</tt>, <tt>:white</tt>
|
|
40
|
+
#
|
|
41
|
+
# ==== String
|
|
42
|
+
# Represents a specific RGB color using a Hex code (<tt>"#RRGGBB"</tt>).
|
|
43
|
+
# Requires a terminal emulator with "True Color" (24-bit color) support.
|
|
44
|
+
class Style < Data.define(:fg, :bg, :modifiers)
|
|
45
|
+
##
|
|
46
|
+
# :attr_reader: fg
|
|
47
|
+
# Foreground color.
|
|
48
|
+
#
|
|
49
|
+
# Symbol (<tt>:red</tt>), Hex String (<tt>"#ffffff"</tt>), or Integer (0-255).
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# :attr_reader: bg
|
|
53
|
+
# Background color.
|
|
54
|
+
#
|
|
55
|
+
# Symbol (<tt>:black</tt>), Hex String (<tt>"#000000"</tt>), or Integer (0-255).
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# :attr_reader: modifiers
|
|
59
|
+
# Text effects.
|
|
60
|
+
#
|
|
61
|
+
# Array of symbols: <tt>:bold</tt>, <tt>:dim</tt>, <tt>:italic</tt>, <tt>:underlined</tt>,
|
|
62
|
+
# <tt>:slow_blink</tt>, <tt>:rapid_blink</tt>, <tt>:reversed</tt>, <tt>:hidden</tt>, <tt>:crossed_out</tt>.
|
|
63
|
+
|
|
64
|
+
# Creates a new Style.
|
|
65
|
+
#
|
|
66
|
+
# [fg] Color (Symbol/String/Integer).
|
|
67
|
+
# [bg] Color (Symbol/String/Integer).
|
|
68
|
+
# [modifiers] Array of Symbols.
|
|
69
|
+
def initialize(fg: nil, bg: nil, modifiers: [])
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns an empty style.
|
|
74
|
+
#
|
|
75
|
+
# Use this as a baseline to prevent style inheritance issues or when no styling is required.
|
|
76
|
+
def self.default
|
|
77
|
+
new
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
# Styling primitives for colors and text effects.
|
|
8
|
+
#
|
|
9
|
+
# This module mirrors +ratatui::style+ and contains:
|
|
10
|
+
# - {Style} — Colors and modifiers
|
|
11
|
+
module Style
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require_relative "style/style"
|
|
@@ -18,7 +18,7 @@ module RatatuiRuby
|
|
|
18
18
|
# @table_state.select_column(0) # Select first column
|
|
19
19
|
#
|
|
20
20
|
# RatatuiRuby.draw do |frame|
|
|
21
|
-
# table = RatatuiRuby::Table.new(rows: [...], widths: [...])
|
|
21
|
+
# table = RatatuiRuby::Widgets::Table.new(rows: [...], widths: [...])
|
|
22
22
|
# frame.render_stateful_widget(table, frame.area, @table_state)
|
|
23
23
|
# end
|
|
24
24
|
#
|
|
@@ -252,6 +252,30 @@ module RatatuiRuby
|
|
|
252
252
|
end
|
|
253
253
|
end
|
|
254
254
|
|
|
255
|
+
##
|
|
256
|
+
# Returns the current buffer content as an ANSI-encoded string.
|
|
257
|
+
#
|
|
258
|
+
# The rich snapshot assertion captures styled output. Sometimes you need the raw ANSI
|
|
259
|
+
# string for debugging, custom assertions, or programmatic inspection.
|
|
260
|
+
#
|
|
261
|
+
# This method renders the buffer with escape codes for colors and modifiers.
|
|
262
|
+
# You can `cat` the output to see exactly what the terminal would display.
|
|
263
|
+
#
|
|
264
|
+
# === Example
|
|
265
|
+
#
|
|
266
|
+
# with_test_terminal(80, 25) do
|
|
267
|
+
# RatatuiRuby.run do |tui|
|
|
268
|
+
# tui.draw tui.paragraph(text: "Hello", block: tui.block(title: "Test"))
|
|
269
|
+
# break
|
|
270
|
+
# end
|
|
271
|
+
# ansi_output = render_rich_buffer
|
|
272
|
+
# puts ansi_output # Shows styled output with escape codes
|
|
273
|
+
# end
|
|
274
|
+
#
|
|
275
|
+
def render_rich_buffer
|
|
276
|
+
_render_buffer_with_ansi
|
|
277
|
+
end
|
|
278
|
+
|
|
255
279
|
private def _render_buffer_with_ansi
|
|
256
280
|
RatatuiRuby.get_buffer_content # Ensure buffer is fresh if needed
|
|
257
281
|
|
|
@@ -86,7 +86,7 @@ module RatatuiRuby
|
|
|
86
86
|
#
|
|
87
87
|
# === Examples
|
|
88
88
|
#
|
|
89
|
-
# header = RatatuiRuby::Rect.new(x: 0, y: 0, width: 80, height: 1)
|
|
89
|
+
# header = RatatuiRuby::Layout::Rect.new(x: 0, y: 0, width: 80, height: 1)
|
|
90
90
|
# assert_area_style(header, bg: :blue, modifiers: [:bold])
|
|
91
91
|
#
|
|
92
92
|
# assert_area_style({ x: 0, y: 0, w: 10, h: 1 }, fg: :red)
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
class TUI
|
|
8
|
+
# Buffer inspection factory methods for Session.
|
|
9
|
+
#
|
|
10
|
+
# Provides convenient access to Buffer::Cell for testing
|
|
11
|
+
# and buffer inspection purposes.
|
|
12
|
+
module BufferFactories
|
|
13
|
+
# Creates a Buffer::Cell (for testing).
|
|
14
|
+
# @return [Buffer::Cell]
|
|
15
|
+
def cell(...)
|
|
16
|
+
Buffer::Cell.new(...)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
class TUI
|
|
8
|
+
# Canvas shape factory methods for Session.
|
|
9
|
+
#
|
|
10
|
+
# Provides convenient access to Widgets::Shape::* classes
|
|
11
|
+
# for creating custom drawings on Canvas widgets.
|
|
12
|
+
module CanvasFactories
|
|
13
|
+
# Creates a map shape for Canvas.
|
|
14
|
+
# @return [Widgets::Shape::Map]
|
|
15
|
+
def shape_map(...)
|
|
16
|
+
Widgets::Shape::Map.new(...)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Creates a line shape for Canvas.
|
|
20
|
+
# @return [Widgets::Shape::Line]
|
|
21
|
+
def shape_line(...)
|
|
22
|
+
Widgets::Shape::Line.new(...)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Creates a point (single pixel) shape for Canvas.
|
|
26
|
+
# @return [Widgets::Shape::Point]
|
|
27
|
+
def shape_point(...)
|
|
28
|
+
Widgets::Shape::Point.new(...)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Creates a circle shape for Canvas.
|
|
32
|
+
# @return [Widgets::Shape::Circle]
|
|
33
|
+
def shape_circle(...)
|
|
34
|
+
Widgets::Shape::Circle.new(...)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Creates a rectangle shape for Canvas.
|
|
38
|
+
# @return [Widgets::Shape::Rectangle]
|
|
39
|
+
def shape_rectangle(...)
|
|
40
|
+
Widgets::Shape::Rectangle.new(...)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
class TUI
|
|
8
|
+
# Core terminal methods delegated to RatatuiRuby module.
|
|
9
|
+
#
|
|
10
|
+
# These are the fundamental operations for the render loop:
|
|
11
|
+
# drawing UI, polling events, and inspecting the buffer.
|
|
12
|
+
module Core
|
|
13
|
+
# Draws the given UI node tree to the terminal.
|
|
14
|
+
# @see RatatuiRuby.draw
|
|
15
|
+
def draw(tree = nil, &)
|
|
16
|
+
RatatuiRuby.draw(tree, &)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Checks for user input.
|
|
20
|
+
# @see RatatuiRuby.poll_event
|
|
21
|
+
def poll_event(timeout: 0.016)
|
|
22
|
+
RatatuiRuby.poll_event(timeout:)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Inspects the terminal buffer at specific coordinates.
|
|
26
|
+
# @see RatatuiRuby.get_cell_at
|
|
27
|
+
def get_cell_at(x, y)
|
|
28
|
+
RatatuiRuby.get_cell_at(x, y)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Creates a Draw::CellCmd for placing a cell at coordinates.
|
|
32
|
+
# @return [Draw::CellCmd]
|
|
33
|
+
def draw_cell(x, y, cell)
|
|
34
|
+
Draw.cell(x, y, cell)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
class TUI
|
|
8
|
+
# Layout factory methods for Session.
|
|
9
|
+
#
|
|
10
|
+
# Provides convenient access to Layout::Rect, Layout::Constraint,
|
|
11
|
+
# and Layout::Layout without fully qualifying the class names.
|
|
12
|
+
module LayoutFactories
|
|
13
|
+
# Creates a Layout::Rect.
|
|
14
|
+
# @return [Layout::Rect]
|
|
15
|
+
def rect(...)
|
|
16
|
+
Layout::Rect.new(...)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Creates a Layout::Constraint.
|
|
20
|
+
# @return [Layout::Constraint]
|
|
21
|
+
def constraint(...)
|
|
22
|
+
Layout::Constraint.new(...)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Creates a Layout::Constraint.length.
|
|
26
|
+
# @return [Layout::Constraint]
|
|
27
|
+
def constraint_length(n)
|
|
28
|
+
Layout::Constraint.length(n)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Creates a Layout::Constraint.percentage.
|
|
32
|
+
# @return [Layout::Constraint]
|
|
33
|
+
def constraint_percentage(n)
|
|
34
|
+
Layout::Constraint.percentage(n)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Creates a Layout::Constraint.min.
|
|
38
|
+
# @return [Layout::Constraint]
|
|
39
|
+
def constraint_min(n)
|
|
40
|
+
Layout::Constraint.min(n)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Creates a Layout::Constraint.max.
|
|
44
|
+
# @return [Layout::Constraint]
|
|
45
|
+
def constraint_max(n)
|
|
46
|
+
Layout::Constraint.max(n)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Creates a Layout::Constraint.fill.
|
|
50
|
+
# @return [Layout::Constraint]
|
|
51
|
+
def constraint_fill(n = 1)
|
|
52
|
+
Layout::Constraint.fill(n)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Creates a Layout::Constraint.ratio.
|
|
56
|
+
# @return [Layout::Constraint]
|
|
57
|
+
def constraint_ratio(numerator, denominator)
|
|
58
|
+
Layout::Constraint.ratio(numerator, denominator)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Creates a Layout::Layout.
|
|
62
|
+
# @return [Layout::Layout]
|
|
63
|
+
def layout(...)
|
|
64
|
+
Layout::Layout.new(...)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Splits an area using Layout::Layout.split.
|
|
68
|
+
# @return [Array<Layout::Rect>]
|
|
69
|
+
def layout_split(area, direction: :vertical, constraints:, flex: :legacy)
|
|
70
|
+
Layout::Layout.split(area, direction:, constraints:, flex:)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|