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,99 @@
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 block of text.
9
+ #
10
+ # Raw strings are insufficient for UIs. They overflow constraints. They don't respect alignment (left, center, right).
11
+ #
12
+ # This widget creates a smart text container. It wraps content to fit the area. It aligns text as requested. It supports scrolling.
13
+ #
14
+ # Use it for everything from simple labels to complex, multi-paragraph documents.
15
+ #
16
+ # === Examples
17
+ #
18
+ # # Basic Text
19
+ # Paragraph.new(text: "Hello, World!")
20
+ #
21
+ # # Styled container with wrapping
22
+ # Paragraph.new(
23
+ # text: "This is a long line that will wrap automatically.",
24
+ # style: Style.new(fg: :green),
25
+ # wrap: true,
26
+ # block: Block.new(title: "Output", borders: [:all])
27
+ # )
28
+ #
29
+ # # Scrolling mechanism
30
+ # Paragraph.new(text: large_text, scroll: [scroll_y, 0])
31
+ class Paragraph < Data.define(:text, :style, :block, :wrap, :alignment, :scroll)
32
+ ##
33
+ # :attr_reader: text
34
+ # The content to display.
35
+
36
+ ##
37
+ # :attr_reader: style
38
+ # Base style for the text.
39
+
40
+ ##
41
+ # :attr_reader: block
42
+ # Optional wrapping block.
43
+
44
+ ##
45
+ # :attr_reader: wrap
46
+ # Whether to wrap text at the edge of the container (Boolean).
47
+
48
+ ##
49
+ # :attr_reader: alignment
50
+ # Text alignment.
51
+ #
52
+ # <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
53
+
54
+ ##
55
+ # :attr_reader: scroll
56
+ # Scroll offset [y, x].
57
+
58
+ # Creates a new Paragraph.
59
+ #
60
+ # [text] String or Text::Line array.
61
+ # [style] Style object.
62
+ # [block] Block object.
63
+ # [wrap] Boolean (default: false).
64
+ # [alignment] Symbol (default: <tt>:left</tt>).
65
+ # [scroll] Array of [y, x] integers (duck-typed via +to_int+).
66
+ def initialize(text:, style: RatatuiRuby::Style::Style.default, block: nil, wrap: false, alignment: :left, scroll: [0, 0])
67
+ super(
68
+ text:,
69
+ style:,
70
+ block:,
71
+ wrap:,
72
+ alignment:,
73
+ scroll: [Integer(scroll[0]), Integer(scroll[1])]
74
+ )
75
+ end
76
+
77
+ # Legacy constructor support.
78
+ def self.new(text:, style: nil, fg: nil, bg: nil, block: nil, wrap: false, alignment: :left, scroll: [0, 0])
79
+ style ||= RatatuiRuby::Style::Style.new(fg:, bg:)
80
+ coerced_scroll = [Integer(scroll[0]), Integer(scroll[1])]
81
+ super(text:, style:, block:, wrap:, alignment:, scroll: coerced_scroll)
82
+ end
83
+
84
+ # Returns the number of lines the paragraph would take up if rendered with the given width.
85
+ #
86
+ # [width] Integer (max width).
87
+ def line_count(width)
88
+ RatatuiRuby.warn_experimental_feature("Paragraph#line_count")
89
+ RatatuiRuby._paragraph_line_count(self, Integer(width))
90
+ end
91
+
92
+ # Returns the minimum width needed to not wrap any text.
93
+ def line_width
94
+ RatatuiRuby.warn_experimental_feature("Paragraph#line_width")
95
+ RatatuiRuby._paragraph_line_width(self)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,31 @@
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 the Ratatui logo.
9
+ #
10
+ # Branding is important for identity. Users need to recognize the tools they use.
11
+ #
12
+ # This widget renders the official Ratatui logo.
13
+ #
14
+ # Use it for splash screens, about sections, or terminal dashboards.
15
+ #
16
+ # {rdoc-image:/doc/images/widget_ratatui_logo_demo.png}[link:/examples/widget_ratatui_logo_demo/app_rb.html]
17
+ #
18
+ # === Example
19
+ #
20
+ # Run the interactive demo from the terminal:
21
+ #
22
+ # ruby examples/widget_ratatui_logo_demo/app.rb
23
+ class RatatuiLogo < Data.define
24
+ ##
25
+ # :method: new
26
+ # :call-seq: new -> RatatuiLogo
27
+ #
28
+ # Creates a new RatatuiLogo.
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
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 the Ratatui mascot.
9
+ #
10
+ # Interfaces without personality feel clinical and dry. Users appreciate a friendly face in their terminal.
11
+ #
12
+ # This widget renders the Ratatui mascot (a mouse).
13
+ #
14
+ # Use it to add charm to your application, greet users on startup, or as a decorative element in sidebars.
15
+ #
16
+ # {rdoc-image:/doc/images/widget_ratatui_mascot_demo.png}[link:/examples/widget_ratatui_mascot_demo/app_rb.html]
17
+ #
18
+ # === Example
19
+ #
20
+ # Run the interactive demo from the terminal:
21
+ #
22
+ # ruby examples/widget_ratatui_mascot_demo/app.rb
23
+ class RatatuiMascot < Data.define(:block)
24
+ ##
25
+ # :method: new
26
+ # :call-seq: new(block: nil) -> RatatuiMascot
27
+ #
28
+ # Creates a new RatatuiMascot.
29
+ #
30
+ # @param block [Block, nil] A block to wrap the widget in.
31
+ def initialize(block: nil)
32
+ super
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,68 @@
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 row combining cells with optional row-level styling.
9
+ #
10
+ # By default, Table rows are arrays of cell content. For more control over styling
11
+ # individual rows, wrap the cells in a Row object to apply row-level style.
12
+ #
13
+ # The cells can be Strings, Text::Spans, Text::Lines, Paragraphs, or Cells.
14
+ # The style applies to the entire row background.
15
+ #
16
+ # === Examples
17
+ #
18
+ # # Row with red background
19
+ # Row.new(cells: ["Error", "Something went wrong"], style: Style.new(bg: :red))
20
+ #
21
+ # # Row with styled cells and custom height
22
+ # Row.new(
23
+ # cells: [
24
+ # Text::Span.new(content: "Status", style: Style.new(modifiers: [:bold])),
25
+ # Text::Span.new(content: "OK", style: Style.new(fg: :green))
26
+ # ],
27
+ # height: 2
28
+ # )
29
+ class Row < Data.define(:cells, :style, :height, :top_margin, :bottom_margin)
30
+ ##
31
+ # :attr_reader: cells
32
+ # The cells to display (Array of Strings, Text::Spans, Text::Lines, Paragraphs, or Cells).
33
+
34
+ ##
35
+ # :attr_reader: style
36
+ # The style to apply to the row (optional Style).
37
+
38
+ ##
39
+ # :attr_reader: height
40
+ # Fixed row height in lines (optional Integer).
41
+
42
+ ##
43
+ # :attr_reader: top_margin
44
+ # Margin above the row in lines (optional Integer).
45
+
46
+ ##
47
+ # :attr_reader: bottom_margin
48
+ # Margin below the row in lines (optional Integer).
49
+
50
+ # Creates a new Row.
51
+ #
52
+ # [cells] Array of Strings, Text::Spans, Text::Lines, Paragraphs, or Cells.
53
+ # [style] Style object (optional).
54
+ # [height] Integer for fixed height (optional).
55
+ # [top_margin] Integer for top margin (optional).
56
+ # [bottom_margin] Integer for bottom margin (optional).
57
+ def initialize(cells:, style: nil, height: nil, top_margin: nil, bottom_margin: nil)
58
+ super(
59
+ cells:,
60
+ style:,
61
+ height: height.nil? ? nil : Integer(height),
62
+ top_margin: top_margin.nil? ? nil : Integer(top_margin),
63
+ bottom_margin: bottom_margin.nil? ? nil : Integer(bottom_margin)
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,143 @@
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
+ # Visualizes the scroll state of a viewport.
9
+ #
10
+ # Content overflows. Users get lost in long lists without landmarks. They need to know where they are and how much is left.
11
+ #
12
+ # This widget maps your context. It draws a track and a thumb, representing your current position relative to the total length.
13
+ #
14
+ # Overlay it on top of lists, paragraphs, or tables to provide spatial awareness.
15
+ #
16
+ # {rdoc-image:/doc/images/widget_scrollbar_demo.png}[link:/examples/widget_scrollbar_demo/app_rb.html]
17
+ #
18
+ # === Example
19
+ #
20
+ # Run the interactive demo from the terminal:
21
+ #
22
+ # ruby examples/widget_scrollbar_demo/app.rb
23
+ class Scrollbar < Data.define(
24
+ :content_length,
25
+ :position,
26
+ :orientation,
27
+ :thumb_symbol,
28
+ :thumb_style,
29
+ :track_symbol,
30
+ :track_style,
31
+ :begin_symbol,
32
+ :begin_style,
33
+ :end_symbol,
34
+ :end_style,
35
+ :style,
36
+ :block
37
+ )
38
+ ##
39
+ # :attr_reader: content_length
40
+ # Total items or lines in the content.
41
+
42
+ ##
43
+ # :attr_reader: position
44
+ # Current scroll offset (index).
45
+ #
46
+ # Maps to Ratatui's <tt>ScrollbarState::get_position()</tt> (new in 0.30.0).
47
+
48
+ ##
49
+ # :attr_reader: orientation
50
+ # Direction of the scrollbar.
51
+ #
52
+ # <tt>:vertical</tt> (default, alias for <tt>:vertical_right</tt>), <tt>:horizontal</tt> (alias for <tt>:horizontal_bottom</tt>),
53
+ # <tt>:vertical_left</tt>, <tt>:vertical_right</tt>, <tt>:horizontal_top</tt>, or <tt>:horizontal_bottom</tt>.
54
+
55
+ ##
56
+ # :attr_reader: thumb_symbol
57
+ # Symbol used to represent the current position indicator.
58
+
59
+ ##
60
+ # :attr_reader: thumb_style
61
+ # Style of the position indicator (thumb).
62
+
63
+ ##
64
+ # :attr_reader: track_symbol
65
+ # Symbol used to represent the empty space of the scrollbar.
66
+
67
+ ##
68
+ # :attr_reader: track_style
69
+ # Style of the filled track area.
70
+
71
+ ##
72
+ # :attr_reader: begin_symbol
73
+ # Symbol rendered at the start of the track (e.g., arrow).
74
+
75
+ ##
76
+ # :attr_reader: begin_style
77
+ # Style of the start symbol.
78
+
79
+ ##
80
+ # :attr_reader: end_symbol
81
+ # Symbol rendered at the end of the track (e.g., arrow).
82
+
83
+ ##
84
+ # :attr_reader: end_style
85
+ # Style of the end symbol.
86
+
87
+ ##
88
+ # :attr_reader: style
89
+ # Base style applied to the entire widget.
90
+
91
+ ##
92
+ # :attr_reader: block
93
+ # Optional wrapping block.
94
+
95
+ # Creates a new Scrollbar.
96
+ #
97
+ # [content_length] Integer.
98
+ # [position] Integer.
99
+ # [orientation] Symbol (default: <tt>:vertical</tt>).
100
+ # [thumb_symbol] String (default: <tt>"█"</tt>).
101
+ # [thumb_style] Style (optional).
102
+ # [track_symbol] String (optional).
103
+ # [track_style] Style (optional).
104
+ # [begin_symbol] String (optional).
105
+ # [begin_style] Style (optional).
106
+ # [end_symbol] String (optional).
107
+ # [end_style] Style (optional).
108
+ # [style] Style (optional).
109
+ # [block] Block (optional).
110
+ def initialize(
111
+ content_length:,
112
+ position:,
113
+ orientation: :vertical,
114
+ thumb_symbol: "█",
115
+ thumb_style: nil,
116
+ track_symbol: nil,
117
+ track_style: nil,
118
+ begin_symbol: nil,
119
+ begin_style: nil,
120
+ end_symbol: nil,
121
+ end_style: nil,
122
+ style: nil,
123
+ block: nil
124
+ )
125
+ super(
126
+ content_length: Integer(content_length),
127
+ position: Integer(position),
128
+ orientation:,
129
+ thumb_symbol:,
130
+ thumb_style:,
131
+ track_symbol:,
132
+ track_style:,
133
+ begin_symbol:,
134
+ begin_style:,
135
+ end_symbol:,
136
+ end_style:,
137
+ style:,
138
+ block:
139
+ )
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,68 @@
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
+ module Shape
9
+ # A text label on a canvas.
10
+ #
11
+ # Labels render text at specific coordinates in canvas space.
12
+ # Unlike shapes, labels are always rendered on top of all other canvas elements.
13
+ #
14
+ # [x] The x-coordinate in canvas space (Numeric).
15
+ # [y] The y-coordinate in canvas space (Numeric).
16
+ # [text] The text content (String or Text::Line).
17
+ # [style] Optional style for the text.
18
+ #
19
+ # === Examples
20
+ #
21
+ # # Simple label
22
+ # Shape::Label.new(x: 0, y: 0, text: "Origin")
23
+ #
24
+ # # Styled label
25
+ # Shape::Label.new(
26
+ # x: -122.4, y: 37.8,
27
+ # text: "San Francisco",
28
+ # style: Style.new(fg: :cyan, add_modifier: :bold)
29
+ # )
30
+ #
31
+ # # Label with Text::Line for rich formatting
32
+ # Shape::Label.new(
33
+ # x: 0.0, y: 0.0,
34
+ # text: Text::Line.new(spans: [
35
+ # Text::Span.new(content: "Hello ", style: Style.new(fg: :red)),
36
+ # Text::Span.new(content: "World", style: Style.new(fg: :blue))
37
+ # ])
38
+ # )
39
+ class Label < Data.define(:x, :y, :text, :style)
40
+ ##
41
+ # :attr_reader: x
42
+ # X coordinate in canvas space (Float, duck-typed via +to_f+).
43
+
44
+ ##
45
+ # :attr_reader: y
46
+ # Y coordinate in canvas space (Float, duck-typed via +to_f+).
47
+
48
+ ##
49
+ # :attr_reader: text
50
+ # Text content (String or Text::Line).
51
+
52
+ ##
53
+ # :attr_reader: style
54
+ # Optional style for the text.
55
+
56
+ # Creates a new Label.
57
+ #
58
+ # [x] X coordinate (Numeric).
59
+ # [y] Y coordinate (Numeric).
60
+ # [text] Text content (String or Text::Line).
61
+ # [style] Style (optional).
62
+ def initialize(x:, y:, text:, style: nil)
63
+ super(x: Float(x), y: Float(y), text:, style:)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,134 @@
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 high-density data in a compact row.
9
+ #
10
+ # Users need context. A single value ("90% CPU") tells you current status, but not the trend.
11
+ # Full charts take up too much room.
12
+ #
13
+ # This widget solves the density problem. It condenses history into a single line of variable-height blocks.
14
+ #
15
+ # Use it in dashboards, headers, or list items to providing trending data at a glance.
16
+ #
17
+ # {rdoc-image:/doc/images/widget_sparkline_demo.png}[link:/examples/widget_sparkline_demo/app_rb.html]
18
+ #
19
+ # === Example
20
+ #
21
+ # Run the interactive demo from the terminal:
22
+ #
23
+ # ruby examples/widget_sparkline_demo/app.rb
24
+ class Sparkline < Data.define(:data, :max, :style, :block, :direction, :absent_value_symbol, :absent_value_style, :bar_set)
25
+ ##
26
+ # :attr_reader: data
27
+ # Array of integer values to plot.
28
+
29
+ ##
30
+ # :attr_reader: max
31
+ # Maximum value for scaling (optional).
32
+ #
33
+ # If nil, derived from data max.
34
+
35
+ ##
36
+ # :attr_reader: style
37
+ # Style for the bars.
38
+
39
+ ##
40
+ # :attr_reader: block
41
+ # Optional wrapping block.
42
+
43
+ ##
44
+ # :attr_reader: direction
45
+ # Direction to render data.
46
+ #
47
+ # Accepts +:left_to_right+ (default) or +:right_to_left+.
48
+ # Use +:right_to_left+ when new data should appear on the left.
49
+
50
+ ##
51
+ # :attr_reader: absent_value_symbol
52
+ # Character to render for absent (nil) values (optional).
53
+ #
54
+ # If nil, absent values are rendered with a space.
55
+
56
+ ##
57
+ # :attr_reader: absent_value_style
58
+ # Style for absent (nil) values (optional).
59
+
60
+ ##
61
+ # :attr_reader: bar_set
62
+ # Custom characters for the bars (optional).
63
+ #
64
+ # A Hash with keys defining the characters for the bars.
65
+ # Keys: <tt>:empty</tt>, <tt>:one_eighth</tt>, <tt>:one_quarter</tt>, <tt>:three_eighths</tt>, <tt>:half</tt>, <tt>:five_eighths</tt>, <tt>:three_quarters</tt>, <tt>:seven_eighths</tt>, <tt>:full</tt>.
66
+ #
67
+ # You can also use integers (0-8) as keys, where 0 is empty, 4 is half, and 8 is full.
68
+ #
69
+ # Alternatively, you can pass an Array of 9 strings, where index 0 is empty and index 8 is full.
70
+ #
71
+ # === Examples
72
+ #
73
+ # bar_set: {
74
+ # empty: " ",
75
+ # one_eighth: " ",
76
+ # one_quarter: "▂",
77
+ # three_eighths: "▃",
78
+ # half: "▄",
79
+ # five_eighths: "▅",
80
+ # three_quarters: "▆",
81
+ # seven_eighths: "▇",
82
+ # full: "█"
83
+ # }
84
+ #
85
+ # # Numeric keys (0-8)
86
+ # bar_set: {
87
+ # 0 => " ", 1 => " ", 2 => "▂", 3 => "▃", 4 => "▄", 5 => "▅", 6 => "▆", 7 => "▇", 8 => "█"
88
+ # }
89
+ #
90
+ # # Array (9 items)
91
+ # bar_set: [" ", " ", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
92
+
93
+ BAR_KEYS = %i[empty one_eighth one_quarter three_eighths half five_eighths three_quarters seven_eighths full].freeze
94
+
95
+ # Creates a new Sparkline widget.
96
+ #
97
+ # [data] Array of Integers or nil. nil marks an absent value (distinct from 0).
98
+ # [max] Max value (optional).
99
+ # [style] Style (optional).
100
+ # [block] Block (optional).
101
+ # [direction] +:left_to_right+ or +:right_to_left+ (default: +:left_to_right+).
102
+ # [absent_value_symbol] Character for absent (nil) values (optional).
103
+ # [absent_value_style] Style for absent (nil) values (optional).
104
+ # [bar_set] Hash or Array of custom characters (optional).
105
+ def initialize(data:, max: nil, style: nil, block: nil, direction: :left_to_right, absent_value_symbol: nil, absent_value_style: nil, bar_set: nil)
106
+ if bar_set
107
+ if bar_set.is_a?(Array) && bar_set.size == 9
108
+ # Convert Array to Hash using BAR_KEYS order
109
+ bar_set = BAR_KEYS.zip(bar_set).to_h
110
+ else
111
+ bar_set = bar_set.dup
112
+ # Normalize numeric keys (0-8) to symbolic keys
113
+ BAR_KEYS.each_with_index do |key, i|
114
+ if (val = bar_set.delete(i) || bar_set.delete(i.to_s))
115
+ bar_set[key] = val
116
+ end
117
+ end
118
+ end
119
+ end
120
+ coerced_data = data.map { |v| v.nil? ? nil : Integer(v) }
121
+ super(
122
+ data: coerced_data,
123
+ max: max.nil? ? nil : Integer(max),
124
+ style:,
125
+ block:,
126
+ direction:,
127
+ absent_value_symbol:,
128
+ absent_value_style:,
129
+ bar_set:
130
+ )
131
+ end
132
+ end
133
+ end
134
+ end