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.
- 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 +35 -0
- data/README.md +26 -1
- data/doc/application_architecture.md +16 -16
- data/doc/application_testing.md +1 -1
- 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/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/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/sourcehut.rake +4 -1
- metadata +62 -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,195 @@
|
|
|
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
|
+
# Widget factory methods for Session.
|
|
9
|
+
#
|
|
10
|
+
# Provides convenient access to all Widgets::* classes without
|
|
11
|
+
# fully qualifying the class names. This is the largest mixin,
|
|
12
|
+
# covering all renderable UI components.
|
|
13
|
+
module WidgetFactories
|
|
14
|
+
# Creates a Widgets::Block.
|
|
15
|
+
# @return [Widgets::Block]
|
|
16
|
+
def block(...)
|
|
17
|
+
Widgets::Block.new(...)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Creates a Widgets::Paragraph.
|
|
21
|
+
# @return [Widgets::Paragraph]
|
|
22
|
+
def paragraph(...)
|
|
23
|
+
Widgets::Paragraph.new(...)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Creates a Widgets::List.
|
|
27
|
+
# @return [Widgets::List]
|
|
28
|
+
def list(...)
|
|
29
|
+
Widgets::List.new(...)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Creates a Widgets::ListItem.
|
|
33
|
+
# @return [Widgets::ListItem]
|
|
34
|
+
def list_item(...)
|
|
35
|
+
Widgets::ListItem.new(...)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Creates a Widgets::Table.
|
|
39
|
+
# @return [Widgets::Table]
|
|
40
|
+
def table(...)
|
|
41
|
+
Widgets::Table.new(...)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Creates a Widgets::Row (for Table rows).
|
|
45
|
+
# @return [Widgets::Row]
|
|
46
|
+
def row(...)
|
|
47
|
+
Widgets::Row.new(...)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Creates a Widgets::Row (alias for table row).
|
|
51
|
+
# @return [Widgets::Row]
|
|
52
|
+
def table_row(...)
|
|
53
|
+
Widgets::Row.new(...)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Creates a Widgets::Cell (for Table cells).
|
|
57
|
+
# @return [Widgets::Cell]
|
|
58
|
+
def table_cell(...)
|
|
59
|
+
Widgets::Cell.new(...)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Creates a Widgets::Tabs.
|
|
63
|
+
# @return [Widgets::Tabs]
|
|
64
|
+
def tabs(...)
|
|
65
|
+
Widgets::Tabs.new(...)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Creates a Widgets::Gauge.
|
|
69
|
+
# @return [Widgets::Gauge]
|
|
70
|
+
def gauge(...)
|
|
71
|
+
Widgets::Gauge.new(...)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Creates a Widgets::LineGauge.
|
|
75
|
+
# @return [Widgets::LineGauge]
|
|
76
|
+
def line_gauge(...)
|
|
77
|
+
Widgets::LineGauge.new(...)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Creates a Widgets::Sparkline.
|
|
81
|
+
# @return [Widgets::Sparkline]
|
|
82
|
+
def sparkline(...)
|
|
83
|
+
Widgets::Sparkline.new(...)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Creates a Widgets::BarChart.
|
|
87
|
+
# @return [Widgets::BarChart]
|
|
88
|
+
def bar_chart(...)
|
|
89
|
+
Widgets::BarChart.new(...)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Creates a Widgets::BarChart::Bar.
|
|
93
|
+
# @return [Widgets::BarChart::Bar]
|
|
94
|
+
def bar(...)
|
|
95
|
+
Widgets::BarChart::Bar.new(...)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Creates a Widgets::BarChart::BarGroup.
|
|
99
|
+
# @return [Widgets::BarChart::BarGroup]
|
|
100
|
+
def bar_group(...)
|
|
101
|
+
Widgets::BarChart::BarGroup.new(...)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Creates a Widgets::BarChart::Bar (alias).
|
|
105
|
+
# @return [Widgets::BarChart::Bar]
|
|
106
|
+
def bar_chart_bar(...)
|
|
107
|
+
Widgets::BarChart::Bar.new(...)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Creates a Widgets::BarChart::BarGroup (alias).
|
|
111
|
+
# @return [Widgets::BarChart::BarGroup]
|
|
112
|
+
def bar_chart_bar_group(...)
|
|
113
|
+
Widgets::BarChart::BarGroup.new(...)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Creates a Widgets::Chart.
|
|
117
|
+
# @return [Widgets::Chart]
|
|
118
|
+
def chart(...)
|
|
119
|
+
Widgets::Chart.new(...)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Creates a Widgets::Dataset.
|
|
123
|
+
# @return [Widgets::Dataset]
|
|
124
|
+
def dataset(...)
|
|
125
|
+
Widgets::Dataset.new(...)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Creates a Widgets::Axis.
|
|
129
|
+
# @return [Widgets::Axis]
|
|
130
|
+
def axis(...)
|
|
131
|
+
Widgets::Axis.new(...)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Creates a Widgets::Scrollbar.
|
|
135
|
+
# @return [Widgets::Scrollbar]
|
|
136
|
+
def scrollbar(...)
|
|
137
|
+
Widgets::Scrollbar.new(...)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Creates a Widgets::Calendar.
|
|
141
|
+
# @return [Widgets::Calendar]
|
|
142
|
+
def calendar(...)
|
|
143
|
+
Widgets::Calendar.new(...)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Creates a Widgets::Canvas.
|
|
147
|
+
# @return [Widgets::Canvas]
|
|
148
|
+
def canvas(...)
|
|
149
|
+
Widgets::Canvas.new(...)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Creates a Widgets::Clear.
|
|
153
|
+
# @return [Widgets::Clear]
|
|
154
|
+
def clear(...)
|
|
155
|
+
Widgets::Clear.new(...)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Creates a Widgets::Cursor.
|
|
159
|
+
# @return [Widgets::Cursor]
|
|
160
|
+
def cursor(...)
|
|
161
|
+
Widgets::Cursor.new(...)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Creates a Widgets::Overlay.
|
|
165
|
+
# @return [Widgets::Overlay]
|
|
166
|
+
def overlay(...)
|
|
167
|
+
Widgets::Overlay.new(...)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Creates a Widgets::Center.
|
|
171
|
+
# @return [Widgets::Center]
|
|
172
|
+
def center(...)
|
|
173
|
+
Widgets::Center.new(...)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Creates a Widgets::RatatuiLogo.
|
|
177
|
+
# @return [Widgets::RatatuiLogo]
|
|
178
|
+
def ratatui_logo(...)
|
|
179
|
+
Widgets::RatatuiLogo.new(...)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Creates a Widgets::RatatuiMascot.
|
|
183
|
+
# @return [Widgets::RatatuiMascot]
|
|
184
|
+
def ratatui_mascot(...)
|
|
185
|
+
Widgets::RatatuiMascot.new(...)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Creates a Widgets::Shape::Label.
|
|
189
|
+
# @return [Widgets::Shape::Label]
|
|
190
|
+
def shape_label(...)
|
|
191
|
+
Widgets::Shape::Label.new(...)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
require_relative "tui/core"
|
|
7
|
+
require_relative "tui/layout_factories"
|
|
8
|
+
require_relative "tui/style_factories"
|
|
9
|
+
require_relative "tui/widget_factories"
|
|
10
|
+
require_relative "tui/text_factories"
|
|
11
|
+
require_relative "tui/state_factories"
|
|
12
|
+
require_relative "tui/canvas_factories"
|
|
13
|
+
require_relative "tui/buffer_factories"
|
|
14
|
+
|
|
15
|
+
module RatatuiRuby
|
|
16
|
+
# Manages the terminal lifecycle and provides a concise API for the render loop.
|
|
17
|
+
#
|
|
18
|
+
# Writing a TUI loop involves repetitive boilerplate. You constantly instantiate widgets
|
|
19
|
+
# (<tt>RatatuiRuby::Widgets::Paragraph.new</tt>) and call global methods (<tt>RatatuiRuby.draw</tt>).
|
|
20
|
+
# This is verbose and hard to read.
|
|
21
|
+
#
|
|
22
|
+
# The Session object simplifies this. It acts as a factory and a facade. It provides short helper
|
|
23
|
+
# methods for every widget and delegates core commands to the main module.
|
|
24
|
+
#
|
|
25
|
+
# Use it within <tt>RatatuiRuby.run</tt> to build your interface cleanly.
|
|
26
|
+
#
|
|
27
|
+
# == Thread/Ractor Safety
|
|
28
|
+
#
|
|
29
|
+
# Session is an *I/O handle*, not a data object. It has side effects (draw,
|
|
30
|
+
# poll_event) and is intentionally *not* Ractor-shareable. Caching it in
|
|
31
|
+
# instance variables (<tt>@tui = tui</tt>) during your application's run loop
|
|
32
|
+
# is fine. However, do not include it in immutable Models/Messages or
|
|
33
|
+
# pass it to other Ractors.
|
|
34
|
+
#
|
|
35
|
+
# == Included Mixins
|
|
36
|
+
#
|
|
37
|
+
# [Core] Terminal operations: draw, poll_event, get_cell_at, draw_cell.
|
|
38
|
+
# [LayoutFactories] Layout helpers: rect, constraint_*, layout, layout_split.
|
|
39
|
+
# [StyleFactories] Style helpers: style.
|
|
40
|
+
# [WidgetFactories] Widget creation: block, paragraph, list, table, etc.
|
|
41
|
+
# [TextFactories] Text helpers: span, line, text_width.
|
|
42
|
+
# [StateFactories] State objects: list_state, table_state, scrollbar_state.
|
|
43
|
+
# [CanvasFactories] Canvas shapes: shape_map, shape_line, shape_point, etc.
|
|
44
|
+
# [BufferFactories] Buffer inspection: cell.
|
|
45
|
+
#
|
|
46
|
+
# === Examples
|
|
47
|
+
#
|
|
48
|
+
# ==== Basic Usage (Recommended)
|
|
49
|
+
#
|
|
50
|
+
# RatatuiRuby.run do |tui|
|
|
51
|
+
# loop do
|
|
52
|
+
# tui.draw \
|
|
53
|
+
# tui.paragraph \
|
|
54
|
+
# text: "Hello, Ratatui! Press 'q' to quit.",
|
|
55
|
+
# alignment: :center,
|
|
56
|
+
# block: tui.block(
|
|
57
|
+
# title: "My Ruby TUI App",
|
|
58
|
+
# borders: [:all],
|
|
59
|
+
# border_color: "cyan"
|
|
60
|
+
# )
|
|
61
|
+
# event = tui.poll_event
|
|
62
|
+
# break if event == "q" || event == :ctrl_c
|
|
63
|
+
# end
|
|
64
|
+
# end
|
|
65
|
+
class TUI
|
|
66
|
+
include Core
|
|
67
|
+
include LayoutFactories
|
|
68
|
+
include StyleFactories
|
|
69
|
+
include WidgetFactories
|
|
70
|
+
include TextFactories
|
|
71
|
+
include StateFactories
|
|
72
|
+
include CanvasFactories
|
|
73
|
+
include BufferFactories
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/ratatui_ruby/version.rb
CHANGED
|
@@ -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
|
+
class BarChart
|
|
9
|
+
# A bar in a grouped bar chart.
|
|
10
|
+
#
|
|
11
|
+
# === Examples
|
|
12
|
+
#
|
|
13
|
+
# BarChart::Bar.new(value: 10, style: Style.new(fg: :red), label: "A")
|
|
14
|
+
class Bar < Data.define(:value, :label, :style, :value_style, :text_value)
|
|
15
|
+
##
|
|
16
|
+
# :attr_reader: value
|
|
17
|
+
# The value of the bar (Integer).
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# :attr_reader: label
|
|
21
|
+
# The label of the bar (optional String, Text::Span, or Text::Line for rich styling).
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# :attr_reader: style
|
|
25
|
+
# The style of the bar (optional Style).
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# :attr_reader: value_style
|
|
29
|
+
# The style of the value (optional Style).
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# :attr_reader: text_value
|
|
33
|
+
# The text to display as the value (optional String, Text::Span, or Text::Line for rich styling).
|
|
34
|
+
|
|
35
|
+
def initialize(value:, label: nil, style: nil, value_style: nil, text_value: nil)
|
|
36
|
+
super(
|
|
37
|
+
value: Integer(value),
|
|
38
|
+
label:,
|
|
39
|
+
style:,
|
|
40
|
+
value_style:,
|
|
41
|
+
text_value:
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
class BarChart
|
|
9
|
+
# A group of bars in a grouped bar chart.
|
|
10
|
+
#
|
|
11
|
+
# === Examples
|
|
12
|
+
#
|
|
13
|
+
# BarChart::BarGroup.new(label: "Q1", bars: [BarChart::Bar.new(value: 10), BarChart::Bar.new(value: 20)])
|
|
14
|
+
class BarGroup < Data.define(:label, :bars)
|
|
15
|
+
##
|
|
16
|
+
# :attr_reader: label
|
|
17
|
+
# The label of the group (String).
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# :attr_reader: bars
|
|
21
|
+
# The bars in the group (Array of Bar).
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,239 @@
|
|
|
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 categorical data as bars.
|
|
9
|
+
#
|
|
10
|
+
# Raw tables of numbers are hard to scan. Comparing magnitudes requires mental arithmetic, which slows down decision-making.
|
|
11
|
+
#
|
|
12
|
+
# This widget visualizes the data. It renders vertical bars proportional to their value.
|
|
13
|
+
#
|
|
14
|
+
# Use it to compare server loads, sales figures, or any discrete datasets.
|
|
15
|
+
#
|
|
16
|
+
# {rdoc-image:/doc/images/widget_barchart_demo.png}[link:/examples/widget_barchart_demo/app_rb.html]
|
|
17
|
+
#
|
|
18
|
+
# === Example
|
|
19
|
+
#
|
|
20
|
+
# Run the interactive demo from the terminal:
|
|
21
|
+
#
|
|
22
|
+
# ruby examples/widget_barchart_demo/app.rb
|
|
23
|
+
#
|
|
24
|
+
# # Grouped Bar Chart
|
|
25
|
+
# BarChart.new(
|
|
26
|
+
# data: [
|
|
27
|
+
# BarGroup.new(label: "Q1", bars: [Bar.new(value: 40), Bar.new(value: 45)]),
|
|
28
|
+
# BarGroup.new(label: "Q2", bars: [Bar.new(value: 50), Bar.new(value: 55)])
|
|
29
|
+
# ],
|
|
30
|
+
# bar_width: 5,
|
|
31
|
+
# group_gap: 3
|
|
32
|
+
# )
|
|
33
|
+
class BarChart < Data.define(:data, :bar_width, :bar_gap, :group_gap, :max, :style, :block, :direction, :label_style, :value_style, :bar_set)
|
|
34
|
+
##
|
|
35
|
+
##
|
|
36
|
+
##
|
|
37
|
+
##
|
|
38
|
+
# :attr_reader: data
|
|
39
|
+
# The data to display.
|
|
40
|
+
#
|
|
41
|
+
# Supports multiple formats:
|
|
42
|
+
# [<tt>Hash</tt>]
|
|
43
|
+
# Mapping labels (<tt>String</tt> or <tt>Symbol</tt>) to values (<tt>Integer</tt>).
|
|
44
|
+
# [<tt>Array</tt> of tuples]
|
|
45
|
+
# Ordered list of <tt>["Label", Value]</tt> or <tt>["Label", Value, Style]</tt> pairs.
|
|
46
|
+
# [<tt>Array</tt> of <tt>BarChart::BarGroup</tt>]
|
|
47
|
+
# List of <tt>BarChart::BarGroup</tt> objects for grouped charts.
|
|
48
|
+
#
|
|
49
|
+
# === Examples
|
|
50
|
+
#
|
|
51
|
+
# Hash (Simple):
|
|
52
|
+
# { "Apples" => 10, :Oranges => 15 }
|
|
53
|
+
#
|
|
54
|
+
# Array of Tuples (Ordered):
|
|
55
|
+
# [["Mon", 20], ["Tue", 30], ["Wed", 25]]
|
|
56
|
+
#
|
|
57
|
+
# BarGroup (Grouped):
|
|
58
|
+
# [
|
|
59
|
+
# RatatuiRuby::BarChart::BarGroup.new(label: "Q1", bars: [
|
|
60
|
+
# RatatuiRuby::BarChart::Bar.new(value: 50, label: "Rev"),
|
|
61
|
+
# RatatuiRuby::BarChart::Bar.new(value: 30, label: "Cost")
|
|
62
|
+
# ])
|
|
63
|
+
# ]
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# :attr_reader: bar_width
|
|
67
|
+
# Width of each bar in characters.
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# :attr_reader: bar_gap
|
|
71
|
+
# Spaces between bars.
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# :attr_reader: group_gap
|
|
75
|
+
# Spaces between groups (for grouped bar charts).
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# :attr_reader: max
|
|
79
|
+
# Maximum value for the Y-axis (optional).
|
|
80
|
+
#
|
|
81
|
+
# If nil, it is calculated from the data.
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# :attr_reader: style
|
|
85
|
+
# Style for the bars.
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# :attr_reader: block
|
|
89
|
+
# Optional wrapping block.
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# :attr_reader: label_style
|
|
93
|
+
# Style for the bar labels (optional).
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# :attr_reader: value_style
|
|
97
|
+
# Style for the bar values (optional).
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
# :attr_reader: bar_set
|
|
101
|
+
# Custom characters for the bars (optional).
|
|
102
|
+
#
|
|
103
|
+
# A Hash with keys defining the characters for the bars.
|
|
104
|
+
# 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>.
|
|
105
|
+
#
|
|
106
|
+
# You can also use integers (0-8) as keys, where 0 is empty, 4 is half, and 8 is full.
|
|
107
|
+
#
|
|
108
|
+
# Alternatively, you can pass an Array of 9 strings, where index 0 is empty and index 8 is full.
|
|
109
|
+
#
|
|
110
|
+
# === Examples
|
|
111
|
+
#
|
|
112
|
+
# bar_set: {
|
|
113
|
+
# empty: " ",
|
|
114
|
+
# one_eighth: " ",
|
|
115
|
+
# one_quarter: "▂",
|
|
116
|
+
# three_eighths: "▃",
|
|
117
|
+
# half: "▄",
|
|
118
|
+
# five_eighths: "▅",
|
|
119
|
+
# three_quarters: "▆",
|
|
120
|
+
# seven_eighths: "▇",
|
|
121
|
+
# full: "█"
|
|
122
|
+
# }
|
|
123
|
+
#
|
|
124
|
+
# # Numeric keys (0-8)
|
|
125
|
+
# bar_set: {
|
|
126
|
+
# 0 => " ", 1 => " ", 2 => "▂", 3 => "▃", 4 => "▄", 5 => "▅", 6 => "▆", 7 => "▇", 8 => "█"
|
|
127
|
+
# }
|
|
128
|
+
#
|
|
129
|
+
# # Array (9 items)
|
|
130
|
+
# bar_set: [" ", " ", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
|
|
131
|
+
|
|
132
|
+
BAR_KEYS = %i[empty one_eighth one_quarter three_eighths half five_eighths three_quarters seven_eighths full].freeze
|
|
133
|
+
|
|
134
|
+
# Creates a new BarChart widget.
|
|
135
|
+
#
|
|
136
|
+
# [data]
|
|
137
|
+
# Data to display. Hash, Array of arrays, or Array of BarGroup.
|
|
138
|
+
# [bar_width]
|
|
139
|
+
# Width of each bar (Integer).
|
|
140
|
+
# [bar_gap]
|
|
141
|
+
# Gap between bars (Integer).
|
|
142
|
+
# [group_gap]
|
|
143
|
+
# Gap between groups (Integer).
|
|
144
|
+
# [max]
|
|
145
|
+
# Maximum value of the bar chart (Integer).
|
|
146
|
+
# [style]
|
|
147
|
+
# Base style for the widget (Style).
|
|
148
|
+
# [block]
|
|
149
|
+
# Block to render around the chart (Block).
|
|
150
|
+
# [direction]
|
|
151
|
+
# Direction of the bars (:vertical or :horizontal).
|
|
152
|
+
# [label_style]
|
|
153
|
+
# Style object for labels (optional).
|
|
154
|
+
# [value_style]
|
|
155
|
+
# Style object for values (optional).
|
|
156
|
+
# [bar_set]
|
|
157
|
+
# Hash or Array: Custom characters for the bars.
|
|
158
|
+
def initialize(data:, bar_width: 3, bar_gap: 1, group_gap: 0, max: nil, style: nil, block: nil, direction: :vertical, label_style: nil, value_style: nil, bar_set: nil)
|
|
159
|
+
if bar_set
|
|
160
|
+
if bar_set.is_a?(Array) && bar_set.size == 9
|
|
161
|
+
# Convert Array to Hash using BAR_KEYS order
|
|
162
|
+
bar_set = BAR_KEYS.zip(bar_set).to_h
|
|
163
|
+
else
|
|
164
|
+
bar_set = bar_set.dup
|
|
165
|
+
# Normalize numeric keys (0-8) to symbolic keys
|
|
166
|
+
BAR_KEYS.each_with_index do |key, i|
|
|
167
|
+
if (val = bar_set.delete(i) || bar_set.delete(i.to_s))
|
|
168
|
+
bar_set[key] = val
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Normalize data to Array of BarGroup
|
|
175
|
+
data = if data.is_a?(Hash)
|
|
176
|
+
if direction == :horizontal
|
|
177
|
+
bars = data.map do |label, value|
|
|
178
|
+
Bar.new(value:, label: label.to_s)
|
|
179
|
+
end
|
|
180
|
+
[BarGroup.new(label: "", bars:)]
|
|
181
|
+
else
|
|
182
|
+
data.map do |label, value|
|
|
183
|
+
BarGroup.new(label: label.to_s, bars: [Bar.new(value:)])
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
elsif data.is_a?(Array)
|
|
187
|
+
if data.empty?
|
|
188
|
+
[]
|
|
189
|
+
elsif data.first.is_a?(BarGroup)
|
|
190
|
+
data
|
|
191
|
+
elsif data.first.is_a?(Array)
|
|
192
|
+
# Tuples
|
|
193
|
+
if direction == :horizontal
|
|
194
|
+
bars = data.map do |item|
|
|
195
|
+
label = item[0].to_s
|
|
196
|
+
value = item[1]
|
|
197
|
+
style = item[2]
|
|
198
|
+
|
|
199
|
+
bar = Bar.new(value:, label:)
|
|
200
|
+
bar = bar.with(style:) if style
|
|
201
|
+
bar
|
|
202
|
+
end
|
|
203
|
+
[BarGroup.new(label: "", bars:)]
|
|
204
|
+
else
|
|
205
|
+
data.map do |item|
|
|
206
|
+
label = item[0].to_s
|
|
207
|
+
value = item[1]
|
|
208
|
+
style = item[2]
|
|
209
|
+
|
|
210
|
+
bar = Bar.new(value:)
|
|
211
|
+
bar = bar.with(style:) if style
|
|
212
|
+
BarGroup.new(label:, bars: [bar])
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
else
|
|
216
|
+
# Fallback
|
|
217
|
+
data
|
|
218
|
+
end
|
|
219
|
+
else
|
|
220
|
+
data
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
super(
|
|
224
|
+
data:,
|
|
225
|
+
bar_width: Integer(bar_width),
|
|
226
|
+
bar_gap: Integer(bar_gap),
|
|
227
|
+
group_gap: Integer(group_gap),
|
|
228
|
+
max: max.nil? ? nil : Integer(max),
|
|
229
|
+
style:,
|
|
230
|
+
block:,
|
|
231
|
+
direction:,
|
|
232
|
+
label_style:,
|
|
233
|
+
value_style:,
|
|
234
|
+
bar_set:
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|