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.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +4 -4
  7. data/CHANGELOG.md +48 -0
  8. data/README.md +26 -1
  9. data/doc/application_architecture.md +16 -16
  10. data/doc/application_testing.md +1 -1
  11. data/doc/async.md +160 -0
  12. data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
  13. data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
  14. data/doc/contributors/architectural_overhaul/task.md +37 -0
  15. data/doc/contributors/design/ruby_frontend.md +277 -81
  16. data/doc/contributors/design/rust_backend.md +349 -55
  17. data/doc/contributors/developing_examples.md +5 -5
  18. data/doc/contributors/index.md +7 -5
  19. data/doc/contributors/v1.0.0_blockers.md +1729 -0
  20. data/doc/debugging.md +71 -0
  21. data/doc/index.md +11 -6
  22. data/doc/interactive_design.md +2 -2
  23. data/doc/quickstart.md +66 -97
  24. data/doc/v0.7.0_migration.md +236 -0
  25. data/doc/why.md +93 -0
  26. data/examples/app_all_events/README.md +6 -4
  27. data/examples/app_all_events/app.rb +1 -1
  28. data/examples/app_all_events/model/app_model.rb +1 -1
  29. data/examples/app_all_events/model/msg.rb +1 -1
  30. data/examples/app_all_events/update.rb +1 -1
  31. data/examples/app_all_events/view/app_view.rb +1 -1
  32. data/examples/app_all_events/view/controls_view.rb +1 -1
  33. data/examples/app_all_events/view/counts_view.rb +1 -1
  34. data/examples/app_all_events/view/live_view.rb +1 -1
  35. data/examples/app_all_events/view/log_view.rb +1 -1
  36. data/examples/app_color_picker/README.md +7 -5
  37. data/examples/app_color_picker/app.rb +1 -1
  38. data/examples/app_login_form/README.md +2 -0
  39. data/examples/app_stateful_interaction/README.md +2 -0
  40. data/examples/app_stateful_interaction/app.rb +1 -1
  41. data/examples/verify_quickstart_dsl/README.md +4 -3
  42. data/examples/verify_quickstart_dsl/app.rb +1 -1
  43. data/examples/verify_quickstart_layout/README.md +1 -1
  44. data/examples/verify_quickstart_lifecycle/README.md +3 -3
  45. data/examples/verify_quickstart_lifecycle/app.rb +2 -2
  46. data/examples/verify_readme_usage/README.md +1 -1
  47. data/examples/widget_barchart_demo/README.md +2 -1
  48. data/examples/widget_block_demo/README.md +2 -0
  49. data/examples/widget_box_demo/README.md +3 -3
  50. data/examples/widget_calendar_demo/README.md +3 -3
  51. data/examples/widget_calendar_demo/app.rb +5 -1
  52. data/examples/widget_canvas_demo/README.md +3 -3
  53. data/examples/widget_cell_demo/README.md +3 -3
  54. data/examples/widget_center_demo/README.md +3 -3
  55. data/examples/widget_chart_demo/README.md +3 -3
  56. data/examples/widget_gauge_demo/README.md +3 -3
  57. data/examples/widget_layout_split/README.md +3 -3
  58. data/examples/widget_line_gauge_demo/README.md +3 -3
  59. data/examples/widget_list_demo/README.md +3 -3
  60. data/examples/widget_map_demo/README.md +3 -3
  61. data/examples/widget_map_demo/app.rb +2 -2
  62. data/examples/widget_overlay_demo/README.md +36 -0
  63. data/examples/widget_popup_demo/README.md +3 -3
  64. data/examples/widget_ratatui_logo_demo/README.md +3 -3
  65. data/examples/widget_ratatui_logo_demo/app.rb +1 -1
  66. data/examples/widget_ratatui_mascot_demo/README.md +3 -3
  67. data/examples/widget_rect/README.md +3 -3
  68. data/examples/widget_render/README.md +3 -3
  69. data/examples/widget_render/app.rb +3 -3
  70. data/examples/widget_rich_text/README.md +3 -3
  71. data/examples/widget_scroll_text/README.md +3 -3
  72. data/examples/widget_scrollbar_demo/README.md +3 -3
  73. data/examples/widget_sparkline_demo/README.md +3 -3
  74. data/examples/widget_style_colors/README.md +3 -3
  75. data/examples/widget_table_demo/README.md +3 -3
  76. data/examples/widget_table_demo/app.rb +19 -4
  77. data/examples/widget_tabs_demo/README.md +3 -3
  78. data/examples/widget_text_width/README.md +3 -3
  79. data/examples/widget_text_width/app.rb +8 -1
  80. data/ext/ratatui_ruby/Cargo.lock +1 -1
  81. data/ext/ratatui_ruby/Cargo.toml +1 -1
  82. data/ext/ratatui_ruby/src/frame.rs +6 -5
  83. data/ext/ratatui_ruby/src/lib.rs +3 -2
  84. data/ext/ratatui_ruby/src/rendering.rs +22 -21
  85. data/ext/ratatui_ruby/src/style.rs +25 -9
  86. data/ext/ratatui_ruby/src/text.rs +12 -3
  87. data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
  88. data/ext/ratatui_ruby/src/widgets/table.rs +81 -36
  89. data/lib/ratatui_ruby/buffer/cell.rb +168 -0
  90. data/lib/ratatui_ruby/buffer.rb +15 -0
  91. data/lib/ratatui_ruby/frame.rb +8 -8
  92. data/lib/ratatui_ruby/layout/constraint.rb +95 -0
  93. data/lib/ratatui_ruby/layout/layout.rb +106 -0
  94. data/lib/ratatui_ruby/layout/rect.rb +118 -0
  95. data/lib/ratatui_ruby/layout.rb +19 -0
  96. data/lib/ratatui_ruby/list_state.rb +2 -2
  97. data/lib/ratatui_ruby/schema/layout.rb +1 -1
  98. data/lib/ratatui_ruby/schema/row.rb +66 -0
  99. data/lib/ratatui_ruby/schema/table.rb +10 -10
  100. data/lib/ratatui_ruby/schema/text.rb +27 -2
  101. data/lib/ratatui_ruby/style/style.rb +81 -0
  102. data/lib/ratatui_ruby/style.rb +15 -0
  103. data/lib/ratatui_ruby/table_state.rb +1 -1
  104. data/lib/ratatui_ruby/test_helper/snapshot.rb +24 -0
  105. data/lib/ratatui_ruby/test_helper/style_assertions.rb +1 -1
  106. data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
  107. data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
  108. data/lib/ratatui_ruby/tui/core.rb +38 -0
  109. data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
  110. data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
  111. data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
  112. data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
  113. data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
  114. data/lib/ratatui_ruby/tui.rb +75 -0
  115. data/lib/ratatui_ruby/version.rb +1 -1
  116. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
  117. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
  118. data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
  119. data/lib/ratatui_ruby/widgets/block.rb +192 -0
  120. data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
  121. data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
  122. data/lib/ratatui_ruby/widgets/cell.rb +47 -0
  123. data/lib/ratatui_ruby/widgets/center.rb +59 -0
  124. data/lib/ratatui_ruby/widgets/chart.rb +185 -0
  125. data/lib/ratatui_ruby/widgets/clear.rb +54 -0
  126. data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
  127. data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
  128. data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
  129. data/lib/ratatui_ruby/widgets/list.rb +127 -0
  130. data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
  131. data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
  132. data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
  133. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
  134. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
  135. data/lib/ratatui_ruby/widgets/row.rb +68 -0
  136. data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
  137. data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
  138. data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
  139. data/lib/ratatui_ruby/widgets/table.rb +141 -0
  140. data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
  141. data/lib/ratatui_ruby/widgets.rb +40 -0
  142. data/lib/ratatui_ruby.rb +23 -39
  143. data/sig/examples/app_all_events/view.rbs +1 -1
  144. data/sig/examples/app_all_events/view_state.rbs +1 -1
  145. data/sig/ratatui_ruby/schema/row.rbs +22 -0
  146. data/sig/ratatui_ruby/schema/table.rbs +1 -1
  147. data/sig/ratatui_ruby/schema/text.rbs +1 -0
  148. data/sig/ratatui_ruby/session.rbs +29 -49
  149. data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
  150. data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
  151. data/sig/ratatui_ruby/tui/core.rbs +14 -0
  152. data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
  153. data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
  154. data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
  155. data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
  156. data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
  157. data/sig/ratatui_ruby/tui.rbs +19 -0
  158. data/tasks/autodoc.rake +1 -35
  159. data/tasks/bump/changelog.rb +8 -0
  160. data/tasks/bump/ruby_gem.rb +12 -0
  161. data/tasks/bump/unreleased_section.rb +16 -0
  162. data/tasks/sourcehut.rake +4 -1
  163. metadata +64 -15
  164. data/doc/contributors/dwim_dx.md +0 -366
  165. data/doc/contributors/examples_audit/p1_high.md +0 -21
  166. data/doc/contributors/examples_audit/p2_moderate.md +0 -81
  167. data/doc/contributors/examples_audit.md +0 -41
  168. data/doc/images/app_analytics.png +0 -0
  169. data/doc/images/app_custom_widget.png +0 -0
  170. data/doc/images/app_mouse_events.png +0 -0
  171. data/doc/images/widget_table_flex.png +0 -0
  172. data/lib/ratatui_ruby/session/autodoc.rb +0 -482
  173. data/lib/ratatui_ruby/session.rb +0 -178
  174. data/tasks/autodoc/inventory.rb +0 -63
  175. data/tasks/autodoc/notice.rb +0 -26
  176. data/tasks/autodoc/rbs.rb +0 -38
  177. 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 TEA Models.
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
  #
@@ -32,7 +32,7 @@ module RatatuiRuby
32
32
  # :attr_reader: constraints
33
33
  # Array of rules defining section sizes.
34
34
  #
35
- # See RatatuiRuby::Constraint.
35
+ # See RatatuiRuby::Layout::Constraint.
36
36
 
37
37
  ##
38
38
  # :attr_reader: children
@@ -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, :highlight_style, :highlight_symbol, :highlight_spacing, :column_highlight_style, :cell_highlight_style, :selected_row, :selected_column, :offset, :block, :footer, :flex, :style, :column_spacing)
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 of Strings).
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: highlight_style
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/paragraphs.
103
- # [rows] 2D Array of strings/paragraphs.
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
- # [highlight_style] Style object.
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: [], 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)
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
- highlight_style:,
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
- def initialize(spans: [], alignment: nil)
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