ratatui_ruby 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +4 -4
  7. data/CHANGELOG.md +35 -0
  8. data/README.md +26 -1
  9. data/doc/application_architecture.md +16 -16
  10. data/doc/application_testing.md +1 -1
  11. data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
  12. data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
  13. data/doc/contributors/architectural_overhaul/task.md +37 -0
  14. data/doc/contributors/design/ruby_frontend.md +277 -81
  15. data/doc/contributors/design/rust_backend.md +349 -55
  16. data/doc/contributors/developing_examples.md +5 -5
  17. data/doc/contributors/index.md +7 -5
  18. data/doc/contributors/v1.0.0_blockers.md +1729 -0
  19. data/doc/index.md +11 -6
  20. data/doc/interactive_design.md +2 -2
  21. data/doc/quickstart.md +66 -97
  22. data/doc/v0.7.0_migration.md +236 -0
  23. data/doc/why.md +93 -0
  24. data/examples/app_all_events/README.md +6 -4
  25. data/examples/app_all_events/app.rb +1 -1
  26. data/examples/app_all_events/model/app_model.rb +1 -1
  27. data/examples/app_all_events/model/msg.rb +1 -1
  28. data/examples/app_all_events/update.rb +1 -1
  29. data/examples/app_all_events/view/app_view.rb +1 -1
  30. data/examples/app_all_events/view/controls_view.rb +1 -1
  31. data/examples/app_all_events/view/counts_view.rb +1 -1
  32. data/examples/app_all_events/view/live_view.rb +1 -1
  33. data/examples/app_all_events/view/log_view.rb +1 -1
  34. data/examples/app_color_picker/README.md +7 -5
  35. data/examples/app_color_picker/app.rb +1 -1
  36. data/examples/app_login_form/README.md +2 -0
  37. data/examples/app_stateful_interaction/README.md +2 -0
  38. data/examples/app_stateful_interaction/app.rb +1 -1
  39. data/examples/verify_quickstart_dsl/README.md +4 -3
  40. data/examples/verify_quickstart_dsl/app.rb +1 -1
  41. data/examples/verify_quickstart_layout/README.md +1 -1
  42. data/examples/verify_quickstart_lifecycle/README.md +3 -3
  43. data/examples/verify_quickstart_lifecycle/app.rb +2 -2
  44. data/examples/verify_readme_usage/README.md +1 -1
  45. data/examples/widget_barchart_demo/README.md +2 -1
  46. data/examples/widget_block_demo/README.md +2 -0
  47. data/examples/widget_box_demo/README.md +3 -3
  48. data/examples/widget_calendar_demo/README.md +3 -3
  49. data/examples/widget_calendar_demo/app.rb +5 -1
  50. data/examples/widget_canvas_demo/README.md +3 -3
  51. data/examples/widget_cell_demo/README.md +3 -3
  52. data/examples/widget_center_demo/README.md +3 -3
  53. data/examples/widget_chart_demo/README.md +3 -3
  54. data/examples/widget_gauge_demo/README.md +3 -3
  55. data/examples/widget_layout_split/README.md +3 -3
  56. data/examples/widget_line_gauge_demo/README.md +3 -3
  57. data/examples/widget_list_demo/README.md +3 -3
  58. data/examples/widget_map_demo/README.md +3 -3
  59. data/examples/widget_map_demo/app.rb +2 -2
  60. data/examples/widget_overlay_demo/README.md +36 -0
  61. data/examples/widget_popup_demo/README.md +3 -3
  62. data/examples/widget_ratatui_logo_demo/README.md +3 -3
  63. data/examples/widget_ratatui_logo_demo/app.rb +1 -1
  64. data/examples/widget_ratatui_mascot_demo/README.md +3 -3
  65. data/examples/widget_rect/README.md +3 -3
  66. data/examples/widget_render/README.md +3 -3
  67. data/examples/widget_render/app.rb +3 -3
  68. data/examples/widget_rich_text/README.md +3 -3
  69. data/examples/widget_scroll_text/README.md +3 -3
  70. data/examples/widget_scrollbar_demo/README.md +3 -3
  71. data/examples/widget_sparkline_demo/README.md +3 -3
  72. data/examples/widget_style_colors/README.md +3 -3
  73. data/examples/widget_table_demo/README.md +3 -3
  74. data/examples/widget_table_demo/app.rb +19 -4
  75. data/examples/widget_tabs_demo/README.md +3 -3
  76. data/examples/widget_text_width/README.md +3 -3
  77. data/examples/widget_text_width/app.rb +8 -1
  78. data/ext/ratatui_ruby/Cargo.lock +1 -1
  79. data/ext/ratatui_ruby/Cargo.toml +1 -1
  80. data/ext/ratatui_ruby/src/frame.rs +6 -5
  81. data/ext/ratatui_ruby/src/lib.rs +3 -2
  82. data/ext/ratatui_ruby/src/rendering.rs +22 -21
  83. data/ext/ratatui_ruby/src/text.rs +12 -3
  84. data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
  85. data/ext/ratatui_ruby/src/widgets/table.rs +81 -36
  86. data/lib/ratatui_ruby/buffer/cell.rb +168 -0
  87. data/lib/ratatui_ruby/buffer.rb +15 -0
  88. data/lib/ratatui_ruby/frame.rb +8 -8
  89. data/lib/ratatui_ruby/layout/constraint.rb +95 -0
  90. data/lib/ratatui_ruby/layout/layout.rb +106 -0
  91. data/lib/ratatui_ruby/layout/rect.rb +118 -0
  92. data/lib/ratatui_ruby/layout.rb +19 -0
  93. data/lib/ratatui_ruby/list_state.rb +2 -2
  94. data/lib/ratatui_ruby/schema/layout.rb +1 -1
  95. data/lib/ratatui_ruby/schema/row.rb +66 -0
  96. data/lib/ratatui_ruby/schema/table.rb +10 -10
  97. data/lib/ratatui_ruby/schema/text.rb +27 -2
  98. data/lib/ratatui_ruby/style/style.rb +81 -0
  99. data/lib/ratatui_ruby/style.rb +15 -0
  100. data/lib/ratatui_ruby/table_state.rb +1 -1
  101. data/lib/ratatui_ruby/test_helper/snapshot.rb +24 -0
  102. data/lib/ratatui_ruby/test_helper/style_assertions.rb +1 -1
  103. data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
  104. data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
  105. data/lib/ratatui_ruby/tui/core.rb +38 -0
  106. data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
  107. data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
  108. data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
  109. data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
  110. data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
  111. data/lib/ratatui_ruby/tui.rb +75 -0
  112. data/lib/ratatui_ruby/version.rb +1 -1
  113. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
  114. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
  115. data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
  116. data/lib/ratatui_ruby/widgets/block.rb +192 -0
  117. data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
  118. data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
  119. data/lib/ratatui_ruby/widgets/cell.rb +47 -0
  120. data/lib/ratatui_ruby/widgets/center.rb +59 -0
  121. data/lib/ratatui_ruby/widgets/chart.rb +185 -0
  122. data/lib/ratatui_ruby/widgets/clear.rb +54 -0
  123. data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
  124. data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
  125. data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
  126. data/lib/ratatui_ruby/widgets/list.rb +127 -0
  127. data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
  128. data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
  129. data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
  130. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
  131. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
  132. data/lib/ratatui_ruby/widgets/row.rb +68 -0
  133. data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
  134. data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
  135. data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
  136. data/lib/ratatui_ruby/widgets/table.rb +141 -0
  137. data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
  138. data/lib/ratatui_ruby/widgets.rb +40 -0
  139. data/lib/ratatui_ruby.rb +23 -39
  140. data/sig/examples/app_all_events/view.rbs +1 -1
  141. data/sig/examples/app_all_events/view_state.rbs +1 -1
  142. data/sig/ratatui_ruby/schema/row.rbs +22 -0
  143. data/sig/ratatui_ruby/schema/table.rbs +1 -1
  144. data/sig/ratatui_ruby/schema/text.rbs +1 -0
  145. data/sig/ratatui_ruby/session.rbs +29 -49
  146. data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
  147. data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
  148. data/sig/ratatui_ruby/tui/core.rbs +14 -0
  149. data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
  150. data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
  151. data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
  152. data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
  153. data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
  154. data/sig/ratatui_ruby/tui.rbs +19 -0
  155. data/tasks/autodoc.rake +1 -35
  156. data/tasks/sourcehut.rake +4 -1
  157. metadata +62 -15
  158. data/doc/contributors/dwim_dx.md +0 -366
  159. data/doc/contributors/examples_audit/p1_high.md +0 -21
  160. data/doc/contributors/examples_audit/p2_moderate.md +0 -81
  161. data/doc/contributors/examples_audit.md +0 -41
  162. data/doc/images/app_analytics.png +0 -0
  163. data/doc/images/app_custom_widget.png +0 -0
  164. data/doc/images/app_mouse_events.png +0 -0
  165. data/doc/images/widget_table_flex.png +0 -0
  166. data/lib/ratatui_ruby/session/autodoc.rb +0 -482
  167. data/lib/ratatui_ruby/session.rb +0 -178
  168. data/tasks/autodoc/inventory.rb +0 -63
  169. data/tasks/autodoc/notice.rb +0 -26
  170. data/tasks/autodoc/rbs.rb +0 -38
  171. data/tasks/autodoc/rdoc.rb +0 -45
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ module Widgets
8
+ # Defines the visual container for a widget.
9
+ #
10
+ # Widgets often float in void. Without boundaries, interfaces become a chaotic mess of text. Users need structure to parse information.
11
+ #
12
+ # This widget creates that structure. It wraps content in borders. It labels sections with titles. It paints the background.
13
+ #
14
+ # Use blocks to define distinct areas. Group related information. Create a visual hierarchy that guides the user's eye.
15
+ #
16
+ # {rdoc-image:/doc/images/widget_box_demo.png}[link:/examples/widget_box_demo/app_rb.html]
17
+ #
18
+ # === Example
19
+ #
20
+ # Run the interactive demo from the terminal:
21
+ #
22
+ # ruby examples/widget_box_demo/app.rb
23
+ class Block < Data.define(:title, :titles, :title_alignment, :title_style, :borders, :border_color, :border_style, :border_type, :border_set, :style, :padding, :children)
24
+ ##
25
+ # :attr_reader: title
26
+ # The main title displayed on the top border.
27
+ #
28
+ # === Example
29
+ #
30
+ # Block.new(title: "Main").title # => "Main"
31
+
32
+ ##
33
+ # :attr_reader: titles
34
+ # Additional titles for complex labeling.
35
+ #
36
+ # Each title can be a <tt>String</tt> or a <tt>Hash</tt> with keys <tt>:content</tt>, <tt>:alignment</tt>, <tt>:position</tt> (<tt>:top</tt> or <tt>:bottom</tt>), and <tt>:style</tt>.
37
+ #
38
+ # === Example
39
+ #
40
+ # Block.new(titles: ["Top", { content: "Bottom", position: :bottom }]).titles
41
+
42
+ ##
43
+ # :attr_reader: title_alignment
44
+ # Alignment of the main title.
45
+ #
46
+ # One of <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
47
+ #
48
+ # === Example
49
+ #
50
+ # Block.new(title_alignment: :center).title_alignment # => :center
51
+
52
+ ##
53
+ # :attr_reader: title_style
54
+ # Style applied to all titles if not overridden.
55
+ #
56
+ # === Example
57
+ #
58
+ # Block.new(title_style: Style.new(fg: :red)).title_style
59
+
60
+ ##
61
+ # :attr_reader: borders
62
+ # Visible borders.
63
+ #
64
+ # An array containing any of <tt>:top</tt>, <tt>:bottom</tt>, <tt>:left</tt>, <tt>:right</tt>, or <tt>:all</tt>.
65
+ #
66
+ # === Example
67
+ #
68
+ # Block.new(borders: [:left, :right]).borders # => [:left, :right]
69
+
70
+ ##
71
+ # :attr_reader: border_color
72
+ # Color of the border lines.
73
+ #
74
+ # Deprecated: Use <tt>border_style:</tt> instead for full style support.
75
+
76
+ ##
77
+ # :attr_reader: border_style
78
+ # Full style (colors/modifiers) for the border lines.
79
+ #
80
+ # A Style object or Hash with <tt>:fg</tt>, <tt>:bg</tt>, and <tt>:modifiers</tt>.
81
+ # This allows borders to be bold, italic, colored, etc. If both <tt>border_color</tt>
82
+ # and <tt>border_style</tt> are provided, <tt>border_style</tt> takes precedence.
83
+
84
+ ##
85
+ # :attr_reader: border_type
86
+ # Visual style of the border lines.
87
+ #
88
+ # One of <tt>:plain</tt>, <tt>:rounded</tt>, <tt>:double</tt>, <tt>:thick</tt>, etc.
89
+
90
+ ##
91
+ # :attr_reader: border_set
92
+ # Custom characters for the border lines.
93
+ #
94
+ # A Hash with keys defining the characters for the borders.
95
+ # Keys: <tt>:top_left</tt>, <tt>:top_right</tt>, <tt>:bottom_left</tt>, <tt>:bottom_right</tt>,
96
+ # <tt>:vertical_left</tt>, <tt>:vertical_right</tt>, <tt>:horizontal_top</tt>, <tt>:horizontal_bottom</tt>.
97
+ #
98
+ # Providing this overrides <tt>border_type</tt>.
99
+ #
100
+ #
101
+ # === Example
102
+ #
103
+ # Block.new(border_set: { top_left: "1", top_right: "2", bottom_left: "3", bottom_right: "4", vertical_left: "5", vertical_right: "6", horizontal_top: "7", horizontal_bottom: "8" })
104
+
105
+ ##
106
+ # :attr_reader: style
107
+ # Base style (colors/modifiers) for the block content.
108
+
109
+ ##
110
+ # :attr_reader: padding
111
+ # Inner padding.
112
+ #
113
+ # Can be a single <tt>Integer</tt> (uniform) or a 4-element <tt>Array</tt> (left, right, top, bottom).
114
+ #
115
+ # === Example
116
+ #
117
+ # Block.new(padding: 2).padding # => 2
118
+ # Block.new(padding: [1, 1, 0, 0]).padding # => [1, 1, 0, 0]
119
+
120
+ ##
121
+ # :attr_reader: children
122
+ # Widgets to render inside the block (optional).
123
+ #
124
+ # When provided, each child widget is rendered within the block's area.
125
+ #
126
+ # === Example
127
+ #
128
+ # Block.new(
129
+ # title: "Content",
130
+ # borders: [:all],
131
+ # children: [Paragraph.new(text: "Hello")]
132
+ # )
133
+
134
+ # Creates a new Block.
135
+ #
136
+ # [title]
137
+ # Main title string (optional).
138
+ # [titles]
139
+ # Array of additional titles (optional).
140
+ # [title_alignment]
141
+ # Alignment symbol: <tt>:left</tt> (default), <tt>:center</tt>, <tt>:right</tt>.
142
+ # [title_style]
143
+ # Base style for all titles (optional).
144
+ # [borders]
145
+ # Array of borders to show: <tt>:top</tt>, <tt>:bottom</tt>, <tt>:left</tt>, <tt>:right</tt>, or <tt>:all</tt> (default).
146
+ # [border_color]
147
+ # Color string or symbol (e.g., <tt>:red</tt>). Deprecated: use <tt>border_style</tt> instead.
148
+ # [border_style]
149
+ # Style object or Hash for the border lines.
150
+ # [border_type]
151
+ # Symbol: <tt>:plain</tt> (default), <tt>:rounded</tt>, <tt>:double</tt>, <tt>:thick</tt>, <tt>:hidden</tt>, <tt>:quadrant_inside</tt>, <tt>:quadrant_outside</tt>.
152
+ # [border_set]
153
+ # Hash: Custom characters for the border lines. Unique characters are interned (leaked) permanently, so avoid infinite dynamic variations.
154
+ # [style]
155
+ # Style object or Hash for the block's content area.
156
+ # [padding]
157
+ # Integer (uniform) or Array[4] (left, right, top, bottom).
158
+ # [children]
159
+ # Array of widgets to render inside the block (optional).
160
+ def initialize(title: nil, titles: [], title_alignment: nil, title_style: nil, borders: [:all], border_color: nil, border_style: nil, border_type: nil, border_set: nil, style: nil, padding: 0, children: [])
161
+ if border_set
162
+ border_set = border_set.dup
163
+ %i[top_left top_right bottom_left bottom_right vertical_left vertical_right horizontal_top horizontal_bottom].each do |long_key|
164
+ short_key = long_key.to_s.split("_").map { |s| s[0] }.join
165
+ if (val = border_set.delete(short_key.to_sym) || border_set.delete(short_key))
166
+ border_set[long_key] = val
167
+ end
168
+ end
169
+ end
170
+ coerced_padding = if padding.is_a?(Array)
171
+ padding.map { |v| Integer(v) }
172
+ else
173
+ Integer(padding)
174
+ end
175
+ super(
176
+ title:,
177
+ titles:,
178
+ title_alignment:,
179
+ title_style:,
180
+ borders:,
181
+ border_color:,
182
+ border_style:,
183
+ border_type:,
184
+ border_set:,
185
+ style:,
186
+ padding: coerced_padding,
187
+ children:
188
+ )
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ module Widgets
8
+ # Displays a monthly calendar grid.
9
+ #
10
+ # Dates are complex. Rendering them in a grid requires calculation of leap years, month lengths, and day-of-week offsets.
11
+ # Use this widget to skip the boilerplate.
12
+ #
13
+ # This widget renders a standard monthly view. It highlights the current date. It structures time.
14
+ #
15
+ # Use it for date pickers, schedulers, or logs.
16
+ #
17
+ # {rdoc-image:/doc/images/widget_calendar_demo.png}[link:/examples/widget_calendar_demo/app_rb.html]
18
+ #
19
+ # === Example
20
+ #
21
+ # Run the interactive demo from the terminal:
22
+ #
23
+ # ruby examples/widget_calendar_demo/app.rb
24
+ class Calendar < Data.define(:year, :month, :events, :default_style, :header_style, :block, :show_weekdays_header, :show_surrounding, :show_month_header)
25
+ ##
26
+ # :attr_reader: year
27
+ # The year to display (Integer).
28
+
29
+ ##
30
+ # :attr_reader: month
31
+ # The month to display (1–12).
32
+
33
+ ##
34
+ # :attr_reader: events
35
+ # A Hash mapping Dates to Styles for event highlighting.
36
+ # Keys must be `Date` objects (or objects responding to `day`, `month`, `year`).
37
+ # Values must be `Style` objects.
38
+
39
+ ##
40
+ # :attr_reader: default_style
41
+ # Style for the days.
42
+
43
+ ##
44
+ # :attr_reader: header_style
45
+ # Style for the month name header.
46
+
47
+ ##
48
+ # :attr_reader: block
49
+ # Optional wrapping block.
50
+
51
+ ##
52
+ # :attr_reader: show_weekdays_header
53
+ # Whether to show the weekday header (Mon, Tue, etc.).
54
+
55
+ ##
56
+ # :attr_reader: show_surrounding
57
+ # Style for dates from surrounding months. If <tt>nil</tt>, surrounding dates are hidden.
58
+
59
+ # Creates a new Calendar.
60
+ #
61
+ # [year] Integer.
62
+ # [month] Integer.
63
+ # [events] Hash<Date, Style>. Optional.
64
+ # [default_style] Style.
65
+ # [header_style] Style.
66
+ # [block] Block.
67
+ # [show_weekdays_header] Boolean. Whether to show the weekday header.
68
+ # [show_surrounding] <tt>Style</tt> or <tt>nil</tt>. Style for surrounding month dates.
69
+ def initialize(year:, month:, events: {}, default_style: nil, header_style: nil, block: nil, show_weekdays_header: true, show_surrounding: nil, show_month_header: false)
70
+ super(
71
+ year: Integer(year),
72
+ month: Integer(month),
73
+ events:,
74
+ default_style:,
75
+ header_style:,
76
+ block:,
77
+ show_weekdays_header:,
78
+ show_surrounding:,
79
+ show_month_header:
80
+ )
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ module Widgets
8
+ # Namespace for canvas shape primitives (Point, Line, Rectangle, Circle, Map, Label).
9
+ # Distinct from text components and other Line usages.
10
+ module Shape
11
+ # A point in the canvas coordinate system.
12
+ #
13
+ # [x] The x-coordinate.
14
+ # [y] The y-coordinate.
15
+ class Point < Data.define(:x, :y)
16
+ ##
17
+ # :attr_reader: x
18
+ # X coordinate (Float, duck-typed via +to_f+).
19
+
20
+ ##
21
+ # :attr_reader: y
22
+ # Y coordinate (Float, duck-typed via +to_f+).
23
+
24
+ # Creates a new Point.
25
+ #
26
+ # [x] X coordinate (Numeric).
27
+ # [y] Y coordinate (Numeric).
28
+ def initialize(x:, y:)
29
+ super(x: Float(x), y: Float(y))
30
+ end
31
+ end
32
+
33
+ # A line shape on a canvas.
34
+ #
35
+ # [x1] The starting x-coordinate.
36
+ # [y1] The starting y-coordinate.
37
+ # [x2] The ending x-coordinate.
38
+ # [y2] The ending y-coordinate.
39
+ # [color] The color of the line.
40
+ class Line < Data.define(:x1, :y1, :x2, :y2, :color)
41
+ ##
42
+ # :attr_reader: x1
43
+ # Start X (Float, duck-typed via +to_f+).
44
+
45
+ ##
46
+ # :attr_reader: y1
47
+ # Start Y (Float, duck-typed via +to_f+).
48
+
49
+ ##
50
+ # :attr_reader: x2
51
+ # End X (Float, duck-typed via +to_f+).
52
+
53
+ ##
54
+ # :attr_reader: y2
55
+ # End Y (Float, duck-typed via +to_f+).
56
+
57
+ ##
58
+ # :attr_reader: color
59
+ # Line color.
60
+
61
+ # Creates a new Line.
62
+ #
63
+ # [x1] Start X (Numeric).
64
+ # [y1] Start Y (Numeric).
65
+ # [x2] End X (Numeric).
66
+ # [y2] End Y (Numeric).
67
+ # [color] Line color (Symbol).
68
+ def initialize(x1:, y1:, x2:, y2:, color:)
69
+ super(x1: Float(x1), y1: Float(y1), x2: Float(x2), y2: Float(y2), color:)
70
+ end
71
+ end
72
+
73
+ # A rectangle shape on a canvas.
74
+ #
75
+ # [x] The x-coordinate of the bottom-left corner.
76
+ # [y] The y-coordinate of the bottom-left corner.
77
+ # [width] The width of the rectangle.
78
+ # [height] The height of the rectangle.
79
+ # [color] The color of the rectangle.
80
+ class Rectangle < Data.define(:x, :y, :width, :height, :color)
81
+ ##
82
+ # :attr_reader: x
83
+ # Bottom-left X (Float, duck-typed via +to_f+).
84
+
85
+ ##
86
+ # :attr_reader: y
87
+ # Bottom-left Y (Float, duck-typed via +to_f+).
88
+
89
+ ##
90
+ # :attr_reader: width
91
+ # Width (Float, duck-typed via +to_f+).
92
+
93
+ ##
94
+ # :attr_reader: height
95
+ # Height (Float, duck-typed via +to_f+).
96
+
97
+ ##
98
+ # :attr_reader: color
99
+ # Color.
100
+
101
+ # Creates a new Rectangle.
102
+ #
103
+ # [x] Bottom-left X (Numeric).
104
+ # [y] Bottom-left Y (Numeric).
105
+ # [width] Width (Numeric).
106
+ # [height] Height (Numeric).
107
+ # [color] Color (Symbol).
108
+ def initialize(x:, y:, width:, height:, color:)
109
+ super(x: Float(x), y: Float(y), width: Float(width), height: Float(height), color:)
110
+ end
111
+ end
112
+
113
+ # A circle shape on a canvas.
114
+ #
115
+ # [x] The x-coordinate of the center.
116
+ # [y] The y-coordinate of the center.
117
+ # [radius] The radius of the circle.
118
+ # [color] The color of the circle.
119
+ class Circle < Data.define(:x, :y, :radius, :color)
120
+ ##
121
+ # :attr_reader: x
122
+ # Center X (Float, duck-typed via +to_f+).
123
+
124
+ ##
125
+ # :attr_reader: y
126
+ # Center Y (Float, duck-typed via +to_f+).
127
+
128
+ ##
129
+ # :attr_reader: radius
130
+ # Radius (Float, duck-typed via +to_f+).
131
+
132
+ ##
133
+ # :attr_reader: color
134
+ # Color.
135
+
136
+ # Creates a new Circle.
137
+ #
138
+ # [x] Center X (Numeric).
139
+ # [y] Center Y (Numeric).
140
+ # [radius] Radius (Numeric).
141
+ # [color] Color (Symbol).
142
+ def initialize(x:, y:, radius:, color:)
143
+ super(x: Float(x), y: Float(y), radius: Float(radius), color:)
144
+ end
145
+ end
146
+
147
+ # A world map shape on a canvas.
148
+ #
149
+ # [color] The color of the map.
150
+ # [resolution] The resolution of the map (<tt>:low</tt>, <tt>:high</tt>).
151
+ class Map < Data.define(:color, :resolution)
152
+ ##
153
+ # :attr_reader: color
154
+ # Map color.
155
+
156
+ ##
157
+ # :attr_reader: resolution
158
+ # Resolution (<tt>:low</tt> or <tt>:high</tt>).
159
+ end
160
+ end
161
+
162
+ # Provides a drawing surface for custom shapes.
163
+ #
164
+ # Standard widgets cover standard cases. Sometimes you need to draw a map, a custom diagram, or a game.
165
+ # Character grids are too coarse for fine detail.
166
+ #
167
+ # This widget increases the resolution. It uses Braille patterns or block characters to create a "sub-pixel" drawing surface.
168
+ #
169
+ # Use it to implement free-form graphics, high-resolution plots, or geographic maps.
170
+ #
171
+ # === Examples
172
+ #
173
+ # Canvas.new(
174
+ # x_bounds: [-180, 180],
175
+ # y_bounds: [-90, 90],
176
+ # shapes: [
177
+ # Shape::Map.new(color: :green, resolution: :high),
178
+ # Shape::Circle.new(x: 0, y: 0, radius: 10, color: :red),
179
+ # Shape::Label.new(x: -122.4, y: 37.8, text: "San Francisco")
180
+ # ]
181
+ # )
182
+ class Canvas < Data.define(:shapes, :x_bounds, :y_bounds, :marker, :block, :background_color)
183
+ ##
184
+ # :attr_reader: shapes
185
+ # Array of shapes to render.
186
+ #
187
+ # Includes {Shape::Line}, {Shape::Circle}, {Shape::Map}, etc.
188
+
189
+ ##
190
+ # :attr_reader: x_bounds
191
+ # [min, max] range for the x-axis.
192
+
193
+ ##
194
+ # :attr_reader: y_bounds
195
+ # [min, max] range for the y-axis.
196
+
197
+ ##
198
+ # :attr_reader: marker
199
+ # The marker type used for drawing.
200
+ #
201
+ # <tt>:braille</tt> (high res), <tt>:half_block</tt>, <tt>:dot</tt>, <tt>:block</tt>, <tt>:bar</tt>.
202
+
203
+ ##
204
+ # :attr_reader: block
205
+ # Optional wrapping block.
206
+
207
+ ##
208
+ # :attr_reader: background_color
209
+ # The background color of the canvas.
210
+
211
+ # Creates a new Canvas.
212
+ #
213
+ # [shapes] Array of Shapes.
214
+ # [x_bounds] Array of [min, max] (Numeric, duck-typed via +to_f+).
215
+ # [y_bounds] Array of [min, max] (Numeric, duck-typed via +to_f+).
216
+ # [marker] Symbol (default: <tt>:braille</tt>).
217
+ # [block] Block (optional).
218
+ # [background_color] Color (optional).
219
+ def initialize(shapes: [], x_bounds: [0.0, 100.0], y_bounds: [0.0, 100.0], marker: :braille, block: nil, background_color: nil)
220
+ super(
221
+ shapes:,
222
+ x_bounds: [Float(x_bounds[0]), Float(x_bounds[1])],
223
+ y_bounds: [Float(y_bounds[0]), Float(y_bounds[1])],
224
+ marker:,
225
+ block:,
226
+ background_color:
227
+ )
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ module Widgets
8
+ # A styled table cell combining content with optional styling.
9
+ #
10
+ # By default, Table cells are plain strings. For more control over cell styling,
11
+ # wrap the content in a Cell object to apply cell-level background style.
12
+ #
13
+ # The content can be a String, Text::Span, or Text::Line.
14
+ # The style applies to the entire cell area (background).
15
+ #
16
+ # === Examples
17
+ #
18
+ # # Cell with yellow background
19
+ # Widgets::Cell.new(content: "Warning", style: Style::Style.new(bg: :yellow))
20
+ #
21
+ # # Cell with rich text content
22
+ # Widgets::Cell.new(
23
+ # content: Text::Line.new(spans: [
24
+ # Text::Span.new(content: "Error: ", style: Style::Style.new(fg: :red)),
25
+ # Text::Span.new(content: "Details here")
26
+ # ]),
27
+ # style: Style::Style.new(bg: :dark_gray)
28
+ # )
29
+ class Cell < Data.define(:content, :style)
30
+ ##
31
+ # :attr_reader: content
32
+ # The content to display (String, Text::Span, or Text::Line).
33
+
34
+ ##
35
+ # :attr_reader: style
36
+ # The style to apply to the cell area (optional Style::Style).
37
+
38
+ # Creates a new Cell.
39
+ #
40
+ # [content] String, Text::Span, or Text::Line.
41
+ # [style] Style::Style object (optional).
42
+ def initialize(content:, style: nil)
43
+ super
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ module Widgets
8
+ # Centers content within available space.
9
+ #
10
+ # Layouts often require alignment. Manually calculating offsets for centering is error-prone and brittle.
11
+ #
12
+ # This widget handles the math. It centers a child widget within the current area, resizing the child
13
+ # according to optional percentage modifiers.
14
+ #
15
+ # Use it to position modals, splash screens, or floating dialogue boxes.
16
+ #
17
+ # === Examples
18
+ #
19
+ # # Center a paragraph using 50% of width and height
20
+ # Center.new(
21
+ # child: Paragraph.new(text: "Hello"),
22
+ # width_percent: 50,
23
+ # height_percent: 50
24
+ # )
25
+ class Center < Data.define(:child, :width_percent, :height_percent)
26
+ ##
27
+ # :attr_reader: child
28
+ # The widget to be centered.
29
+
30
+ ##
31
+ # :attr_reader: width_percent
32
+ # Width of the centered area as a percentage (0-100).
33
+ #
34
+ # If 50, the child occupies half the available width.
35
+
36
+ ##
37
+ # :attr_reader: height_percent
38
+ # Height of the centered area as a percentage (0-100).
39
+ #
40
+ # If 50, the child occupies half the available height.
41
+
42
+ # Creates a new Center widget.
43
+ #
44
+ # [child]
45
+ # Widget to render.
46
+ # [width_percent]
47
+ # Target width percentage (Integer, default: 100).
48
+ # [height_percent]
49
+ # Target height percentage (Integer, default: 100).
50
+ def initialize(child:, width_percent: 100, height_percent: 100)
51
+ super(
52
+ child:,
53
+ width_percent: Float(width_percent),
54
+ height_percent: Float(height_percent)
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end