rubytui 1.2.3
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +574 -0
- data/lib/rubytui/app.rb +174 -0
- data/lib/rubytui/backend.rb +158 -0
- data/lib/rubytui/backends/ansi_backend.rb +391 -0
- data/lib/rubytui/backends/test_backend.rb +223 -0
- data/lib/rubytui/buffer.rb +400 -0
- data/lib/rubytui/cell.rb +55 -0
- data/lib/rubytui/color.rb +153 -0
- data/lib/rubytui/color_mode.rb +203 -0
- data/lib/rubytui/errors.rb +13 -0
- data/lib/rubytui/event.rb +161 -0
- data/lib/rubytui/frame.rb +70 -0
- data/lib/rubytui/input/key.rb +93 -0
- data/lib/rubytui/input/parser.rb +231 -0
- data/lib/rubytui/input/reader.rb +119 -0
- data/lib/rubytui/layout/constraint.rb +83 -0
- data/lib/rubytui/layout/flex.rb +16 -0
- data/lib/rubytui/layout/layout.rb +205 -0
- data/lib/rubytui/modifier.rb +67 -0
- data/lib/rubytui/rect.rb +126 -0
- data/lib/rubytui/stateful_widget.rb +20 -0
- data/lib/rubytui/style.rb +143 -0
- data/lib/rubytui/symbols.rb +88 -0
- data/lib/rubytui/terminal.rb +218 -0
- data/lib/rubytui/text/line.rb +67 -0
- data/lib/rubytui/text/span.rb +34 -0
- data/lib/rubytui/text/text.rb +82 -0
- data/lib/rubytui/unicode.rb +162 -0
- data/lib/rubytui/version.rb +6 -0
- data/lib/rubytui/widget.rb +20 -0
- data/lib/rubytui/widgets/async_image.rb +248 -0
- data/lib/rubytui/widgets/block.rb +260 -0
- data/lib/rubytui/widgets/canvas.rb +248 -0
- data/lib/rubytui/widgets/chart.rb +224 -0
- data/lib/rubytui/widgets/gauge.rb +139 -0
- data/lib/rubytui/widgets/image.rb +330 -0
- data/lib/rubytui/widgets/input_field.rb +245 -0
- data/lib/rubytui/widgets/list.rb +186 -0
- data/lib/rubytui/widgets/paragraph.rb +181 -0
- data/lib/rubytui/widgets/popup.rb +140 -0
- data/lib/rubytui/widgets/scrollbar.rb +175 -0
- data/lib/rubytui/widgets/sparkline.rb +86 -0
- data/lib/rubytui/widgets/table.rb +231 -0
- data/lib/rubytui/widgets/tabs.rb +90 -0
- data/lib/rubytui.rb +389 -0
- metadata +89 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyTUI
|
|
4
|
+
module Widgets
|
|
5
|
+
# Tabs renders a horizontal tab bar with selectable tabs.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# tabs = RubyTUI::Widgets::Tabs.new(titles: ["Home", "Logs", "Help"], selected: 1)
|
|
9
|
+
# frame.render_widget(tabs, area)
|
|
10
|
+
class Tabs
|
|
11
|
+
include Widget
|
|
12
|
+
|
|
13
|
+
# @param titles [Array<#to_s>] tab titles (default: [])
|
|
14
|
+
# @param selected [Integer] index of the active tab (default: 0)
|
|
15
|
+
# @param style [Style] base style, patched with +active_style+ or +inactive_style+
|
|
16
|
+
# (default: {Style::DEFAULT})
|
|
17
|
+
# @param active_style [Style, nil] style of the active tab (default: bold yellow foreground)
|
|
18
|
+
# @param inactive_style [Style, nil] style of inactive tabs and dividers
|
|
19
|
+
# (default: bright black foreground)
|
|
20
|
+
# @param divider [String] separator drawn between tabs (default: <tt>" │ "</tt>)
|
|
21
|
+
# @param highlight_style [Style, nil] currently unused by rendering (default: nil)
|
|
22
|
+
# @param block [Widgets::Block, nil] optional wrapping block
|
|
23
|
+
def initialize(
|
|
24
|
+
titles: [],
|
|
25
|
+
selected: 0,
|
|
26
|
+
style: Style::DEFAULT,
|
|
27
|
+
active_style: nil,
|
|
28
|
+
inactive_style: nil,
|
|
29
|
+
divider: " \u2502 ",
|
|
30
|
+
highlight_style: nil,
|
|
31
|
+
block: nil
|
|
32
|
+
)
|
|
33
|
+
@titles = titles
|
|
34
|
+
@selected = selected
|
|
35
|
+
@style = style
|
|
36
|
+
@active_style = active_style || Style.new.fg(Color::YELLOW).bold
|
|
37
|
+
@inactive_style = inactive_style || Style.new.fg(Color::BRIGHT_BLACK)
|
|
38
|
+
@divider = divider
|
|
39
|
+
@highlight_style = highlight_style
|
|
40
|
+
@block = block
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Draw the titles left to right on the first row of the area, separated by
|
|
44
|
+
# +divider+ and truncated at the right edge.
|
|
45
|
+
#
|
|
46
|
+
# @param area [Rect] the rectangular area to render into
|
|
47
|
+
# @param buf [Buffer] the buffer to write cells into
|
|
48
|
+
# @return [void]
|
|
49
|
+
def render(area, buf)
|
|
50
|
+
return if area.empty?
|
|
51
|
+
|
|
52
|
+
render_area = area
|
|
53
|
+
if @block
|
|
54
|
+
@block.render(area, buf)
|
|
55
|
+
render_area = @block.inner(area)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return if render_area.empty? || @titles.empty?
|
|
59
|
+
|
|
60
|
+
x = render_area.x
|
|
61
|
+
y = render_area.y
|
|
62
|
+
|
|
63
|
+
@titles.each_with_index do |title, i|
|
|
64
|
+
break if x >= render_area.right
|
|
65
|
+
|
|
66
|
+
is_active = i == @selected
|
|
67
|
+
tab_style = is_active ? @style.patch(@active_style) : @style.patch(@inactive_style)
|
|
68
|
+
|
|
69
|
+
# Render tab title
|
|
70
|
+
display = title.to_s
|
|
71
|
+
available = render_area.right - x
|
|
72
|
+
display = Buffer.truncate_to_width(display, available)
|
|
73
|
+
display_width = Buffer.string_width(display)
|
|
74
|
+
|
|
75
|
+
buf.set_string(x, y, display, tab_style)
|
|
76
|
+
x += display_width
|
|
77
|
+
|
|
78
|
+
# Render divider (except after last tab)
|
|
79
|
+
if i < @titles.length - 1
|
|
80
|
+
available = render_area.right - x
|
|
81
|
+
divider_text = Buffer.truncate_to_width(@divider, available)
|
|
82
|
+
divider_width = Buffer.string_width(divider_text)
|
|
83
|
+
buf.set_string(x, y, divider_text, @style.patch(@inactive_style))
|
|
84
|
+
x += divider_width
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/rubytui.rb
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Core primitives
|
|
4
|
+
require_relative "rubytui/version"
|
|
5
|
+
require_relative "rubytui/errors"
|
|
6
|
+
require_relative "rubytui/modifier"
|
|
7
|
+
require_relative "rubytui/color"
|
|
8
|
+
require_relative "rubytui/color_mode"
|
|
9
|
+
require_relative "rubytui/rect"
|
|
10
|
+
require_relative "rubytui/style"
|
|
11
|
+
require_relative "rubytui/cell"
|
|
12
|
+
require_relative "rubytui/unicode"
|
|
13
|
+
require_relative "rubytui/buffer"
|
|
14
|
+
require_relative "rubytui/symbols"
|
|
15
|
+
|
|
16
|
+
# Layout
|
|
17
|
+
require_relative "rubytui/layout/flex"
|
|
18
|
+
require_relative "rubytui/layout/constraint"
|
|
19
|
+
require_relative "rubytui/layout/layout"
|
|
20
|
+
|
|
21
|
+
# Widget protocol
|
|
22
|
+
require_relative "rubytui/widget"
|
|
23
|
+
require_relative "rubytui/stateful_widget"
|
|
24
|
+
|
|
25
|
+
# Backend
|
|
26
|
+
require_relative "rubytui/backend"
|
|
27
|
+
require_relative "rubytui/backends/test_backend"
|
|
28
|
+
require_relative "rubytui/backends/ansi_backend"
|
|
29
|
+
|
|
30
|
+
# Rendering pipeline
|
|
31
|
+
require_relative "rubytui/frame"
|
|
32
|
+
require_relative "rubytui/terminal"
|
|
33
|
+
|
|
34
|
+
# Text primitives
|
|
35
|
+
require_relative "rubytui/text/span"
|
|
36
|
+
require_relative "rubytui/text/line"
|
|
37
|
+
require_relative "rubytui/text/text"
|
|
38
|
+
|
|
39
|
+
# Widgets
|
|
40
|
+
require_relative "rubytui/widgets/block"
|
|
41
|
+
require_relative "rubytui/widgets/paragraph"
|
|
42
|
+
require_relative "rubytui/widgets/list"
|
|
43
|
+
require_relative "rubytui/widgets/table"
|
|
44
|
+
require_relative "rubytui/widgets/gauge"
|
|
45
|
+
require_relative "rubytui/widgets/sparkline"
|
|
46
|
+
require_relative "rubytui/widgets/scrollbar"
|
|
47
|
+
require_relative "rubytui/widgets/tabs"
|
|
48
|
+
require_relative "rubytui/widgets/canvas"
|
|
49
|
+
require_relative "rubytui/widgets/popup"
|
|
50
|
+
require_relative "rubytui/widgets/input_field"
|
|
51
|
+
require_relative "rubytui/widgets/chart"
|
|
52
|
+
require_relative "rubytui/widgets/image"
|
|
53
|
+
require_relative "rubytui/widgets/async_image"
|
|
54
|
+
|
|
55
|
+
# Input
|
|
56
|
+
require_relative "rubytui/input/key"
|
|
57
|
+
require_relative "rubytui/event"
|
|
58
|
+
require_relative "rubytui/input/parser"
|
|
59
|
+
require_relative "rubytui/input/reader"
|
|
60
|
+
|
|
61
|
+
# App framework
|
|
62
|
+
require_relative "rubytui/app"
|
|
63
|
+
|
|
64
|
+
# A pure Ruby terminal user interface library.
|
|
65
|
+
#
|
|
66
|
+
# RubyTUI provides immediate-mode rendering with double buffering,
|
|
67
|
+
# constraint-based layouts, and a duck-typed widget protocol.
|
|
68
|
+
#
|
|
69
|
+
# @example Interactive TUI application
|
|
70
|
+
# RubyTUI.run do |terminal, reader|
|
|
71
|
+
# loop do
|
|
72
|
+
# terminal.draw do |frame|
|
|
73
|
+
# frame.buffer.set_string(0, 0, "Hello!", Style.new.fg(Color::GREEN))
|
|
74
|
+
# end
|
|
75
|
+
# event = reader.poll(timeout_ms: 50)
|
|
76
|
+
# break if event.is_a?(RubyTUI::KeyEvent) && event.char == 'q'
|
|
77
|
+
# end
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
# @example One-shot styled output (no event loop)
|
|
81
|
+
# RubyTUI.render_once(width: 80) do |frame|
|
|
82
|
+
# block = RubyTUI::Widgets::Block.new(title: " Info ", borders: :all)
|
|
83
|
+
# frame.render_widget(block, frame.area)
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
# @example Quick table print
|
|
87
|
+
# RubyTUI.print_table(
|
|
88
|
+
# header: ["Name", "Value"],
|
|
89
|
+
# rows: [["Alice", "100"], ["Bob", "200"]],
|
|
90
|
+
# title: " Data "
|
|
91
|
+
# )
|
|
92
|
+
module RubyTUI
|
|
93
|
+
# Convenience alias for {Layout::Constraint}
|
|
94
|
+
Constraint = Layout::Constraint
|
|
95
|
+
|
|
96
|
+
class << self
|
|
97
|
+
# Initialize a terminal with ANSI backend.
|
|
98
|
+
#
|
|
99
|
+
# Enables raw mode, hides the cursor, and optionally enters alternate screen.
|
|
100
|
+
# Use {.restore} to undo these changes, or prefer {.run} for automatic cleanup.
|
|
101
|
+
#
|
|
102
|
+
# @param output [IO] output stream (default: $stdout)
|
|
103
|
+
# @param input [IO] input stream (default: $stdin)
|
|
104
|
+
# @param mouse [Boolean] enable mouse event reporting (default: false)
|
|
105
|
+
# @param alternate_screen [Boolean] enter alternate screen buffer (default: true)
|
|
106
|
+
# @return [Array(Terminal, Input::Reader)] terminal and reader pair
|
|
107
|
+
# @see .run
|
|
108
|
+
# @see .restore
|
|
109
|
+
def init(output: $stdout, input: $stdin, mouse: false, alternate_screen: true)
|
|
110
|
+
backend = AnsiBackend.new(output: output, input: input)
|
|
111
|
+
terminal = Terminal.new(backend: backend)
|
|
112
|
+
reader = Input::Reader.new(input: input)
|
|
113
|
+
|
|
114
|
+
backend.enable_raw_mode
|
|
115
|
+
backend.hide_cursor
|
|
116
|
+
terminal.enter_alternate_screen if alternate_screen
|
|
117
|
+
backend.enable_mouse if mouse
|
|
118
|
+
backend.flush
|
|
119
|
+
|
|
120
|
+
[terminal, reader]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Restore terminal to normal state.
|
|
124
|
+
#
|
|
125
|
+
# Disables mouse, shows cursor, leaves alternate screen, and disables raw mode.
|
|
126
|
+
#
|
|
127
|
+
# @param terminal [Terminal] the terminal to restore
|
|
128
|
+
# @param alternate_screen [Boolean] whether alternate screen was used (must match {.init})
|
|
129
|
+
# (default: true)
|
|
130
|
+
# @return [void]
|
|
131
|
+
def restore(terminal, alternate_screen: true)
|
|
132
|
+
terminal.backend.disable_mouse
|
|
133
|
+
terminal.backend.show_cursor
|
|
134
|
+
terminal.leave_alternate_screen if alternate_screen
|
|
135
|
+
terminal.backend.disable_raw_mode
|
|
136
|
+
terminal.backend.flush
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Run a TUI application with automatic setup and teardown.
|
|
140
|
+
#
|
|
141
|
+
# Sets up the terminal (raw mode, hidden cursor, alternate screen) and
|
|
142
|
+
# yields a terminal and reader. Restores the terminal when the block exits.
|
|
143
|
+
#
|
|
144
|
+
# @param output [IO] output stream (default: $stdout)
|
|
145
|
+
# @param input [IO] input stream (default: $stdin)
|
|
146
|
+
# @param mouse [Boolean] enable mouse event reporting (default: false)
|
|
147
|
+
# @param alternate_screen [Boolean] use alternate screen buffer (default: true).
|
|
148
|
+
# Set to +false+ to keep output in terminal scrollback after exit.
|
|
149
|
+
# @yield [terminal, reader] the initialized terminal and input reader
|
|
150
|
+
# @yieldparam terminal [Terminal]
|
|
151
|
+
# @yieldparam reader [Input::Reader]
|
|
152
|
+
# @return [void]
|
|
153
|
+
#
|
|
154
|
+
# @example Basic interactive app
|
|
155
|
+
# RubyTUI.run do |terminal, reader|
|
|
156
|
+
# loop do
|
|
157
|
+
# terminal.draw { |frame| frame.buffer.set_string(0, 0, "Hi!") }
|
|
158
|
+
# event = reader.poll(timeout_ms: 50)
|
|
159
|
+
# break if event.is_a?(RubyTUI::KeyEvent) && event.char == 'q'
|
|
160
|
+
# end
|
|
161
|
+
# end
|
|
162
|
+
#
|
|
163
|
+
# @example Non-alternate-screen mode (output persists after exit)
|
|
164
|
+
# RubyTUI.run(alternate_screen: false) do |terminal, reader|
|
|
165
|
+
# terminal.draw { |frame| frame.buffer.set_string(0, 0, "Stays visible") }
|
|
166
|
+
# end
|
|
167
|
+
def run(output: $stdout, input: $stdin, mouse: false, alternate_screen: true)
|
|
168
|
+
terminal, reader = init(output: output, input: input, mouse: mouse,
|
|
169
|
+
alternate_screen: alternate_screen)
|
|
170
|
+
yield terminal, reader
|
|
171
|
+
ensure
|
|
172
|
+
restore(terminal, alternate_screen: alternate_screen) if terminal
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Render widgets once and print to stdout.
|
|
176
|
+
#
|
|
177
|
+
# No event loop, no alternate screen, no raw mode. Output stays in
|
|
178
|
+
# terminal scrollback. Ideal for CLI tools that want styled output
|
|
179
|
+
# using the widget/layout system without a full TUI.
|
|
180
|
+
#
|
|
181
|
+
# @param width [Integer, nil] output width in columns (default: auto-detect)
|
|
182
|
+
# @param height [Integer, nil] output height in rows (default: auto-detect)
|
|
183
|
+
# @param output [IO] output stream (default: $stdout)
|
|
184
|
+
# @yield [frame] a Frame to render widgets into
|
|
185
|
+
# @yieldparam frame [Frame]
|
|
186
|
+
# @return [Frame] the rendered frame
|
|
187
|
+
#
|
|
188
|
+
# @example Render a block widget
|
|
189
|
+
# RubyTUI.render_once(width: 60, height: 10) do |frame|
|
|
190
|
+
# block = RubyTUI::Widgets::Block.new(title: " Info ", borders: :all)
|
|
191
|
+
# frame.render_widget(block, frame.area)
|
|
192
|
+
# end
|
|
193
|
+
def render_once(width: nil, height: nil, output: $stdout)
|
|
194
|
+
w = width || terminal_width(output)
|
|
195
|
+
h = height || terminal_height(output)
|
|
196
|
+
area = Rect.new(0, 0, w, h)
|
|
197
|
+
buf = Buffer.new(area)
|
|
198
|
+
frame = Frame.new(buf, area)
|
|
199
|
+
yield frame
|
|
200
|
+
output.write(buf.to_ansi)
|
|
201
|
+
output.write("\n")
|
|
202
|
+
frame
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Print a styled table to stdout. No event loop needed.
|
|
206
|
+
#
|
|
207
|
+
# Renders a {Widgets::Table} with optional header, borders, and title,
|
|
208
|
+
# then prints the ANSI output directly. Auto-calculates the required height.
|
|
209
|
+
#
|
|
210
|
+
# @param header [Array<String>, nil] column header labels
|
|
211
|
+
# @param rows [Array<Array<String>>] table data rows
|
|
212
|
+
# @param widths [Array<Integer>] column widths (auto-calculated if empty)
|
|
213
|
+
# @param title [String, nil] box title rendered on the top border
|
|
214
|
+
# @param border [Symbol, Symbols::BorderSet] border style — +:rounded+, +:double+,
|
|
215
|
+
# +:thick+, +:light+, +:plain+, or a {Symbols::BorderSet} (default: +:rounded+)
|
|
216
|
+
# @param border_style [Style] style for the border characters (default: {Style::DEFAULT})
|
|
217
|
+
# @param header_style [Style, nil] style for header row (default: cyan bold)
|
|
218
|
+
# @param style [Style] base style for data rows (default: {Style::DEFAULT})
|
|
219
|
+
# @param highlight_symbol [String] prefix for the selected row (default: " ")
|
|
220
|
+
# @param column_spacing [Integer] gap between columns (default: 1)
|
|
221
|
+
# @param width [Integer, nil] total width in columns (default: auto-detect)
|
|
222
|
+
# @param output [IO] output stream (default: $stdout)
|
|
223
|
+
# @return [void]
|
|
224
|
+
#
|
|
225
|
+
# @example
|
|
226
|
+
# RubyTUI.print_table(
|
|
227
|
+
# header: ["Name", "Price", "Change"],
|
|
228
|
+
# rows: [["AAPL", "150.00", "+2.3%"], ["GOOG", "2800", "-1.1%"]],
|
|
229
|
+
# border: :rounded,
|
|
230
|
+
# title: " Stocks "
|
|
231
|
+
# )
|
|
232
|
+
def print_table(header: nil, rows: [], widths: [], title: nil,
|
|
233
|
+
border: :rounded, border_style: Style::DEFAULT,
|
|
234
|
+
header_style: nil, style: Style::DEFAULT,
|
|
235
|
+
highlight_symbol: " ", column_spacing: 1,
|
|
236
|
+
width: nil, output: $stdout)
|
|
237
|
+
border_set = resolve_border_set(border)
|
|
238
|
+
block_widget = title ? Widgets::Block.new(
|
|
239
|
+
title: title, borders: :all, border_set: border_set,
|
|
240
|
+
border_style: border_style
|
|
241
|
+
) : nil
|
|
242
|
+
|
|
243
|
+
table = Widgets::Table.new(
|
|
244
|
+
header: header, rows: rows, widths: widths, style: style,
|
|
245
|
+
header_style: header_style, highlight_symbol: highlight_symbol,
|
|
246
|
+
column_spacing: column_spacing, block: block_widget
|
|
247
|
+
)
|
|
248
|
+
state = Widgets::TableState.new(selected: -1) # No selection for one-shot
|
|
249
|
+
|
|
250
|
+
# Calculate height: header(1) + separator(1) + rows + border(2)
|
|
251
|
+
h = rows.length + (header ? 2 : 0) + (title ? 2 : 0)
|
|
252
|
+
w = width || terminal_width(output)
|
|
253
|
+
|
|
254
|
+
area = Rect.new(0, 0, w, h)
|
|
255
|
+
buf = Buffer.new(area)
|
|
256
|
+
table.render(area, buf, state)
|
|
257
|
+
output.write(buf.to_ansi)
|
|
258
|
+
output.write("\n")
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Print a styled box with text content to stdout. No event loop needed.
|
|
262
|
+
#
|
|
263
|
+
# Renders a {Widgets::Paragraph} inside a {Widgets::Block} with borders,
|
|
264
|
+
# then prints the ANSI output directly.
|
|
265
|
+
#
|
|
266
|
+
# @param title [String, nil] box title rendered on the top border
|
|
267
|
+
# @param content [String, Array<String>] text content (newlines or array of lines)
|
|
268
|
+
# @param border [Symbol, Symbols::BorderSet] border style — +:rounded+, +:double+,
|
|
269
|
+
# +:thick+, +:light+, +:plain+, or a {Symbols::BorderSet} (default: +:rounded+)
|
|
270
|
+
# @param border_style [Style] style for the border characters (default: {Style::DEFAULT})
|
|
271
|
+
# @param style [Style] background style for the box area (default: {Style::DEFAULT})
|
|
272
|
+
# @param text_style [Style] style for the text content (default: {Style::DEFAULT})
|
|
273
|
+
# @param alignment [Symbol] text alignment — +:left+, +:center+, +:right+ (default: +:left+)
|
|
274
|
+
# @param width [Integer, nil] total width in columns (default: auto-detect)
|
|
275
|
+
# @param output [IO] output stream (default: $stdout)
|
|
276
|
+
# @return [void]
|
|
277
|
+
#
|
|
278
|
+
# @example
|
|
279
|
+
# RubyTUI.print_box(
|
|
280
|
+
# title: " Summary ",
|
|
281
|
+
# content: "Total: $1,234\nProfit: +5.2%",
|
|
282
|
+
# border_style: Style.new.fg(Color::YELLOW)
|
|
283
|
+
# )
|
|
284
|
+
def print_box(title: nil, content: "", border: :rounded,
|
|
285
|
+
border_style: Style::DEFAULT, style: Style::DEFAULT,
|
|
286
|
+
text_style: Style::DEFAULT, alignment: :left,
|
|
287
|
+
width: nil, output: $stdout)
|
|
288
|
+
border_set = resolve_border_set(border)
|
|
289
|
+
block_widget = Widgets::Block.new(
|
|
290
|
+
title: title, borders: :all, border_set: border_set,
|
|
291
|
+
border_style: border_style, style: style
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
text = case content
|
|
295
|
+
when String then Text.from(content)
|
|
296
|
+
when Array then Text.from(content.join("\n"))
|
|
297
|
+
else content
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
para = Widgets::Paragraph.new(
|
|
301
|
+
text: text, style: text_style, alignment: alignment,
|
|
302
|
+
block: block_widget
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
w = width || terminal_width(output)
|
|
306
|
+
line_count = case content
|
|
307
|
+
when String then content.count("\n") + 1
|
|
308
|
+
when Array then content.length
|
|
309
|
+
else 1
|
|
310
|
+
end
|
|
311
|
+
h = line_count + 2 # +2 for borders
|
|
312
|
+
|
|
313
|
+
area = Rect.new(0, 0, w, h)
|
|
314
|
+
buf = Buffer.new(area)
|
|
315
|
+
para.render(area, buf)
|
|
316
|
+
output.write(buf.to_ansi)
|
|
317
|
+
output.write("\n")
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Print a styled gauge/progress bar to stdout. No event loop needed.
|
|
321
|
+
#
|
|
322
|
+
# Renders a {Widgets::Gauge} with optional title block, then prints
|
|
323
|
+
# the ANSI output directly.
|
|
324
|
+
#
|
|
325
|
+
# @param ratio [Float] progress value between 0.0 and 1.0 (default: 0.0)
|
|
326
|
+
# @param label [String, nil] custom label (default: percentage)
|
|
327
|
+
# @param width [Integer, nil] total width in columns (default: auto-detect)
|
|
328
|
+
# @param gauge_style [Style, nil] style for the filled portion (default: green)
|
|
329
|
+
# @param title [String, nil] box title rendered on the top border
|
|
330
|
+
# @param border [Symbol, Symbols::BorderSet] border style — +:rounded+, +:double+,
|
|
331
|
+
# +:thick+, +:light+, +:plain+, or a {Symbols::BorderSet} (default: +:rounded+)
|
|
332
|
+
# @param border_style [Style] style for the border characters (default: {Style::DEFAULT})
|
|
333
|
+
# @param output [IO] output stream (default: $stdout)
|
|
334
|
+
# @return [void]
|
|
335
|
+
#
|
|
336
|
+
# @example
|
|
337
|
+
# RubyTUI.print_gauge(ratio: 0.75, title: " Progress ", width: 40)
|
|
338
|
+
def print_gauge(ratio: 0.0, label: nil, width: nil,
|
|
339
|
+
gauge_style: nil, title: nil,
|
|
340
|
+
border: :rounded, border_style: Style::DEFAULT,
|
|
341
|
+
output: $stdout)
|
|
342
|
+
block_widget = title ? Widgets::Block.new(
|
|
343
|
+
title: title, borders: :all,
|
|
344
|
+
border_set: resolve_border_set(border),
|
|
345
|
+
border_style: border_style
|
|
346
|
+
) : nil
|
|
347
|
+
|
|
348
|
+
gauge = Widgets::Gauge.new(
|
|
349
|
+
ratio: ratio, label: label, gauge_style: gauge_style,
|
|
350
|
+
block: block_widget
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
w = width || terminal_width(output)
|
|
354
|
+
h = title ? 3 : 1
|
|
355
|
+
|
|
356
|
+
area = Rect.new(0, 0, w, h)
|
|
357
|
+
buf = Buffer.new(area)
|
|
358
|
+
gauge.render(area, buf)
|
|
359
|
+
output.write(buf.to_ansi)
|
|
360
|
+
output.write("\n")
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
private
|
|
364
|
+
|
|
365
|
+
def terminal_width(output)
|
|
366
|
+
output.respond_to?(:winsize) ? output.winsize[1] : 80
|
|
367
|
+
rescue Errno::ENOTTY, Errno::ENODEV, NoMethodError
|
|
368
|
+
80
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def terminal_height(output)
|
|
372
|
+
output.respond_to?(:winsize) ? output.winsize[0] : 24
|
|
373
|
+
rescue Errno::ENOTTY, Errno::ENODEV, NoMethodError
|
|
374
|
+
24
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def resolve_border_set(border)
|
|
378
|
+
case border
|
|
379
|
+
when :rounded then Symbols::ROUNDED
|
|
380
|
+
when :double then Symbols::DOUBLE
|
|
381
|
+
when :thick then Symbols::THICK
|
|
382
|
+
when :light then Symbols::LIGHT
|
|
383
|
+
when :plain then Symbols::PLAIN
|
|
384
|
+
else
|
|
385
|
+
border.is_a?(Symbols::BorderSet) ? border : Symbols::ROUNDED
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubytui
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.2.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- CoCL
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: |
|
|
13
|
+
A pure Ruby, zero-dependency terminal user interface library featuring
|
|
14
|
+
immediate-mode rendering with double buffering, constraint-based layouts,
|
|
15
|
+
a duck-typed widget system, cross-platform input handling, and Kitty
|
|
16
|
+
graphics protocol support for inline images.
|
|
17
|
+
email:
|
|
18
|
+
- ''
|
|
19
|
+
executables: []
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- LICENSE
|
|
24
|
+
- README.md
|
|
25
|
+
- lib/rubytui.rb
|
|
26
|
+
- lib/rubytui/app.rb
|
|
27
|
+
- lib/rubytui/backend.rb
|
|
28
|
+
- lib/rubytui/backends/ansi_backend.rb
|
|
29
|
+
- lib/rubytui/backends/test_backend.rb
|
|
30
|
+
- lib/rubytui/buffer.rb
|
|
31
|
+
- lib/rubytui/cell.rb
|
|
32
|
+
- lib/rubytui/color.rb
|
|
33
|
+
- lib/rubytui/color_mode.rb
|
|
34
|
+
- lib/rubytui/errors.rb
|
|
35
|
+
- lib/rubytui/event.rb
|
|
36
|
+
- lib/rubytui/frame.rb
|
|
37
|
+
- lib/rubytui/input/key.rb
|
|
38
|
+
- lib/rubytui/input/parser.rb
|
|
39
|
+
- lib/rubytui/input/reader.rb
|
|
40
|
+
- lib/rubytui/layout/constraint.rb
|
|
41
|
+
- lib/rubytui/layout/flex.rb
|
|
42
|
+
- lib/rubytui/layout/layout.rb
|
|
43
|
+
- lib/rubytui/modifier.rb
|
|
44
|
+
- lib/rubytui/rect.rb
|
|
45
|
+
- lib/rubytui/stateful_widget.rb
|
|
46
|
+
- lib/rubytui/style.rb
|
|
47
|
+
- lib/rubytui/symbols.rb
|
|
48
|
+
- lib/rubytui/terminal.rb
|
|
49
|
+
- lib/rubytui/text/line.rb
|
|
50
|
+
- lib/rubytui/text/span.rb
|
|
51
|
+
- lib/rubytui/text/text.rb
|
|
52
|
+
- lib/rubytui/unicode.rb
|
|
53
|
+
- lib/rubytui/version.rb
|
|
54
|
+
- lib/rubytui/widget.rb
|
|
55
|
+
- lib/rubytui/widgets/async_image.rb
|
|
56
|
+
- lib/rubytui/widgets/block.rb
|
|
57
|
+
- lib/rubytui/widgets/canvas.rb
|
|
58
|
+
- lib/rubytui/widgets/chart.rb
|
|
59
|
+
- lib/rubytui/widgets/gauge.rb
|
|
60
|
+
- lib/rubytui/widgets/image.rb
|
|
61
|
+
- lib/rubytui/widgets/input_field.rb
|
|
62
|
+
- lib/rubytui/widgets/list.rb
|
|
63
|
+
- lib/rubytui/widgets/paragraph.rb
|
|
64
|
+
- lib/rubytui/widgets/popup.rb
|
|
65
|
+
- lib/rubytui/widgets/scrollbar.rb
|
|
66
|
+
- lib/rubytui/widgets/sparkline.rb
|
|
67
|
+
- lib/rubytui/widgets/table.rb
|
|
68
|
+
- lib/rubytui/widgets/tabs.rb
|
|
69
|
+
licenses:
|
|
70
|
+
- MIT
|
|
71
|
+
metadata: {}
|
|
72
|
+
rdoc_options: []
|
|
73
|
+
require_paths:
|
|
74
|
+
- lib
|
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: 3.2.0
|
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
85
|
+
requirements: []
|
|
86
|
+
rubygems_version: 4.0.19
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Pure Ruby, zero-dependency terminal user interface library
|
|
89
|
+
test_files: []
|