ratatat 1.0.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 +7 -0
- data/README.md +201 -0
- data/examples/log_tailer.rb +460 -0
- data/lib/ratatat/ansi_backend.rb +175 -0
- data/lib/ratatat/app.rb +342 -0
- data/lib/ratatat/binding.rb +74 -0
- data/lib/ratatat/buffer.rb +238 -0
- data/lib/ratatat/cell.rb +166 -0
- data/lib/ratatat/color.rb +191 -0
- data/lib/ratatat/css_parser.rb +192 -0
- data/lib/ratatat/dom_query.rb +124 -0
- data/lib/ratatat/driver.rb +200 -0
- data/lib/ratatat/input.rb +208 -0
- data/lib/ratatat/message.rb +147 -0
- data/lib/ratatat/reactive.rb +79 -0
- data/lib/ratatat/styles.rb +293 -0
- data/lib/ratatat/terminal.rb +168 -0
- data/lib/ratatat/version.rb +3 -0
- data/lib/ratatat/widget.rb +337 -0
- data/lib/ratatat/widgets/button.rb +43 -0
- data/lib/ratatat/widgets/checkbox.rb +68 -0
- data/lib/ratatat/widgets/container.rb +50 -0
- data/lib/ratatat/widgets/data_table.rb +123 -0
- data/lib/ratatat/widgets/grid.rb +40 -0
- data/lib/ratatat/widgets/horizontal.rb +52 -0
- data/lib/ratatat/widgets/log.rb +97 -0
- data/lib/ratatat/widgets/modal.rb +161 -0
- data/lib/ratatat/widgets/progress_bar.rb +80 -0
- data/lib/ratatat/widgets/radio_set.rb +91 -0
- data/lib/ratatat/widgets/scrollable_container.rb +88 -0
- data/lib/ratatat/widgets/select.rb +100 -0
- data/lib/ratatat/widgets/sparkline.rb +61 -0
- data/lib/ratatat/widgets/spinner.rb +93 -0
- data/lib/ratatat/widgets/static.rb +23 -0
- data/lib/ratatat/widgets/tabbed_content.rb +114 -0
- data/lib/ratatat/widgets/text_area.rb +183 -0
- data/lib/ratatat/widgets/text_input.rb +143 -0
- data/lib/ratatat/widgets/toast.rb +55 -0
- data/lib/ratatat/widgets/tooltip.rb +66 -0
- data/lib/ratatat/widgets/tree.rb +172 -0
- data/lib/ratatat/widgets/vertical.rb +52 -0
- data/lib/ratatat.rb +51 -0
- metadata +142 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Ratatat
|
|
5
|
+
# A node in a tree structure
|
|
6
|
+
class TreeNode
|
|
7
|
+
extend T::Sig
|
|
8
|
+
|
|
9
|
+
sig { returns(String) }
|
|
10
|
+
attr_reader :label
|
|
11
|
+
|
|
12
|
+
sig { returns(T::Array[TreeNode]) }
|
|
13
|
+
attr_reader :children
|
|
14
|
+
|
|
15
|
+
sig { returns(T.untyped) }
|
|
16
|
+
attr_reader :data
|
|
17
|
+
|
|
18
|
+
sig { returns(T::Boolean) }
|
|
19
|
+
attr_accessor :expanded
|
|
20
|
+
|
|
21
|
+
sig { params(label: String, children: T::Array[TreeNode], data: T.untyped).void }
|
|
22
|
+
def initialize(label, children = [], data: nil)
|
|
23
|
+
@label = label
|
|
24
|
+
@children = children
|
|
25
|
+
@data = data
|
|
26
|
+
@expanded = T.let(false, T::Boolean)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
sig { returns(T::Boolean) }
|
|
30
|
+
def expandable?
|
|
31
|
+
!@children.empty?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Tree view widget with expandable nodes
|
|
36
|
+
class Tree < Widget
|
|
37
|
+
extend T::Sig
|
|
38
|
+
|
|
39
|
+
CAN_FOCUS = true
|
|
40
|
+
|
|
41
|
+
# Emitted when a node is selected (Space pressed)
|
|
42
|
+
class NodeSelected < Message
|
|
43
|
+
extend T::Sig
|
|
44
|
+
|
|
45
|
+
sig { returns(TreeNode) }
|
|
46
|
+
attr_reader :node
|
|
47
|
+
|
|
48
|
+
sig { params(sender: Widget, node: TreeNode).void }
|
|
49
|
+
def initialize(sender:, node:)
|
|
50
|
+
super(sender: sender)
|
|
51
|
+
@node = node
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sig { returns(T::Array[TreeNode]) }
|
|
56
|
+
attr_reader :roots
|
|
57
|
+
|
|
58
|
+
reactive :cursor, default: 0, repaint: true
|
|
59
|
+
|
|
60
|
+
sig { params(roots: T::Array[TreeNode], id: T.nilable(String), classes: T::Array[String]).void }
|
|
61
|
+
def initialize(roots: [], id: nil, classes: [])
|
|
62
|
+
super(id: id, classes: classes)
|
|
63
|
+
@roots = roots
|
|
64
|
+
@cursor = 0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
sig { params(message: Key).void }
|
|
68
|
+
def on_key(message)
|
|
69
|
+
case message.key
|
|
70
|
+
when "down", "j"
|
|
71
|
+
move_cursor(1)
|
|
72
|
+
message.stop
|
|
73
|
+
when "up", "k"
|
|
74
|
+
move_cursor(-1)
|
|
75
|
+
message.stop
|
|
76
|
+
when "enter"
|
|
77
|
+
toggle_current
|
|
78
|
+
message.stop
|
|
79
|
+
when "right", "l"
|
|
80
|
+
expand_current
|
|
81
|
+
message.stop
|
|
82
|
+
when "left", "h"
|
|
83
|
+
collapse_current
|
|
84
|
+
message.stop
|
|
85
|
+
when " "
|
|
86
|
+
select_current
|
|
87
|
+
message.stop
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
sig { params(buffer: Buffer, x: Integer, y: Integer, width: Integer, height: Integer).void }
|
|
92
|
+
def render(buffer, x:, y:, width:, height:)
|
|
93
|
+
visible = visible_nodes
|
|
94
|
+
visible.each_with_index do |(node, depth), i|
|
|
95
|
+
break if i >= height
|
|
96
|
+
|
|
97
|
+
prefix = @cursor == i ? "> " : " "
|
|
98
|
+
indent = " " * depth
|
|
99
|
+
icon = if node.expandable?
|
|
100
|
+
node.expanded ? "v " : "> "
|
|
101
|
+
else
|
|
102
|
+
" "
|
|
103
|
+
end
|
|
104
|
+
text = "#{prefix}#{indent}#{icon}#{node.label}"
|
|
105
|
+
buffer.put_string(x, y + i, text[0, width])
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
sig { returns(T::Array[[TreeNode, Integer]]) }
|
|
112
|
+
def visible_nodes
|
|
113
|
+
result = T.let([], T::Array[[TreeNode, Integer]])
|
|
114
|
+
collect_visible(@roots, 0, result)
|
|
115
|
+
result
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
sig { params(nodes: T::Array[TreeNode], depth: Integer, result: T::Array[[TreeNode, Integer]]).void }
|
|
119
|
+
def collect_visible(nodes, depth, result)
|
|
120
|
+
nodes.each do |node|
|
|
121
|
+
result << [node, depth]
|
|
122
|
+
collect_visible(node.children, depth + 1, result) if node.expanded
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
sig { params(delta: Integer).void }
|
|
127
|
+
def move_cursor(delta)
|
|
128
|
+
max = visible_nodes.length - 1
|
|
129
|
+
@cursor = (@cursor + delta).clamp(0, [max, 0].max)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
sig { returns(T.nilable(TreeNode)) }
|
|
133
|
+
def current_node
|
|
134
|
+
visible_nodes[@cursor]&.first
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
sig { void }
|
|
138
|
+
def toggle_current
|
|
139
|
+
node = current_node
|
|
140
|
+
return unless node&.expandable?
|
|
141
|
+
|
|
142
|
+
node.expanded = !node.expanded
|
|
143
|
+
refresh
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
sig { void }
|
|
147
|
+
def expand_current
|
|
148
|
+
node = current_node
|
|
149
|
+
return unless node&.expandable?
|
|
150
|
+
|
|
151
|
+
node.expanded = true
|
|
152
|
+
refresh
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
sig { void }
|
|
156
|
+
def collapse_current
|
|
157
|
+
node = current_node
|
|
158
|
+
return unless node&.expandable?
|
|
159
|
+
|
|
160
|
+
node.expanded = false
|
|
161
|
+
refresh
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
sig { void }
|
|
165
|
+
def select_current
|
|
166
|
+
node = current_node
|
|
167
|
+
return unless node
|
|
168
|
+
|
|
169
|
+
parent&.dispatch(NodeSelected.new(sender: self, node: node))
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Ratatat
|
|
5
|
+
# Vertical flexbox-like layout widget
|
|
6
|
+
class Vertical < Widget
|
|
7
|
+
extend T::Sig
|
|
8
|
+
|
|
9
|
+
sig { returns(Integer) }
|
|
10
|
+
attr_reader :gap
|
|
11
|
+
|
|
12
|
+
sig { returns(T.nilable(T::Array[Float])) }
|
|
13
|
+
attr_reader :ratios
|
|
14
|
+
|
|
15
|
+
sig { params(gap: Integer, ratios: T.nilable(T::Array[Float]), id: T.nilable(String), classes: T::Array[String]).void }
|
|
16
|
+
def initialize(gap: 0, ratios: nil, id: nil, classes: [])
|
|
17
|
+
super(id: id, classes: classes)
|
|
18
|
+
@gap = gap
|
|
19
|
+
@ratios = ratios
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
sig { params(buffer: Buffer, x: Integer, y: Integer, width: Integer, height: Integer).void }
|
|
23
|
+
def render(buffer, x:, y:, width:, height:)
|
|
24
|
+
return if children.empty?
|
|
25
|
+
|
|
26
|
+
total_gap = @gap * (children.length - 1)
|
|
27
|
+
available_height = height - total_gap
|
|
28
|
+
|
|
29
|
+
heights = calculate_heights(available_height)
|
|
30
|
+
current_y = y
|
|
31
|
+
|
|
32
|
+
children.each_with_index do |child, i|
|
|
33
|
+
child_height = heights[i] || 0
|
|
34
|
+
child.render(buffer, x: x, y: current_y, width: width, height: child_height) if child.respond_to?(:render)
|
|
35
|
+
current_y += child_height + @gap
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
sig { params(available_height: Integer).returns(T::Array[Integer]) }
|
|
42
|
+
def calculate_heights(available_height)
|
|
43
|
+
if @ratios && @ratios.length == children.length
|
|
44
|
+
@ratios.map { |r| (available_height * r).to_i }
|
|
45
|
+
else
|
|
46
|
+
# Equal distribution
|
|
47
|
+
child_height = available_height / children.length
|
|
48
|
+
Array.new(children.length, child_height)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/ratatat.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require_relative "ratatat/version"
|
|
2
|
+
|
|
3
|
+
# Core rendering engine (order matters for dependencies)
|
|
4
|
+
require_relative "ratatat/color"
|
|
5
|
+
require_relative "ratatat/cell"
|
|
6
|
+
require_relative "ratatat/buffer"
|
|
7
|
+
require_relative "ratatat/ansi_backend"
|
|
8
|
+
require_relative "ratatat/terminal"
|
|
9
|
+
require_relative "ratatat/input"
|
|
10
|
+
|
|
11
|
+
# Drivers
|
|
12
|
+
require_relative "ratatat/driver"
|
|
13
|
+
|
|
14
|
+
# Message system
|
|
15
|
+
require_relative "ratatat/message"
|
|
16
|
+
require_relative "ratatat/binding"
|
|
17
|
+
require_relative "ratatat/reactive"
|
|
18
|
+
require_relative "ratatat/styles"
|
|
19
|
+
require_relative "ratatat/css_parser"
|
|
20
|
+
require_relative "ratatat/dom_query"
|
|
21
|
+
require_relative "ratatat/widget"
|
|
22
|
+
|
|
23
|
+
# Application framework
|
|
24
|
+
require_relative "ratatat/app"
|
|
25
|
+
|
|
26
|
+
# Widgets
|
|
27
|
+
require_relative "ratatat/widgets/static"
|
|
28
|
+
require_relative "ratatat/widgets/button"
|
|
29
|
+
require_relative "ratatat/widgets/text_input"
|
|
30
|
+
require_relative "ratatat/widgets/container"
|
|
31
|
+
require_relative "ratatat/widgets/checkbox"
|
|
32
|
+
require_relative "ratatat/widgets/select"
|
|
33
|
+
require_relative "ratatat/widgets/data_table"
|
|
34
|
+
require_relative "ratatat/widgets/tree"
|
|
35
|
+
require_relative "ratatat/widgets/progress_bar"
|
|
36
|
+
require_relative "ratatat/widgets/modal"
|
|
37
|
+
require_relative "ratatat/widgets/text_area"
|
|
38
|
+
require_relative "ratatat/widgets/radio_set"
|
|
39
|
+
require_relative "ratatat/widgets/sparkline"
|
|
40
|
+
require_relative "ratatat/widgets/log"
|
|
41
|
+
require_relative "ratatat/widgets/grid"
|
|
42
|
+
require_relative "ratatat/widgets/scrollable_container"
|
|
43
|
+
require_relative "ratatat/widgets/tabbed_content"
|
|
44
|
+
require_relative "ratatat/widgets/toast"
|
|
45
|
+
require_relative "ratatat/widgets/tooltip"
|
|
46
|
+
require_relative "ratatat/widgets/spinner"
|
|
47
|
+
require_relative "ratatat/widgets/horizontal"
|
|
48
|
+
require_relative "ratatat/widgets/vertical"
|
|
49
|
+
|
|
50
|
+
module Ratatat
|
|
51
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ratatat
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Vicente Reig Rincon de Arellano
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2025-11-28 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sorbet-runtime
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.5'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.5'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.12'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.12'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: sorbet
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.5'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.5'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: tapioca
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.11'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.11'
|
|
68
|
+
description: Build terminal user interfaces with a reactive, component-based architecture.
|
|
69
|
+
Features reactive properties, CSS-like styling, message-driven communication, and
|
|
70
|
+
a rich widget library.
|
|
71
|
+
email:
|
|
72
|
+
- hey@vicente.services
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- README.md
|
|
78
|
+
- examples/log_tailer.rb
|
|
79
|
+
- lib/ratatat.rb
|
|
80
|
+
- lib/ratatat/ansi_backend.rb
|
|
81
|
+
- lib/ratatat/app.rb
|
|
82
|
+
- lib/ratatat/binding.rb
|
|
83
|
+
- lib/ratatat/buffer.rb
|
|
84
|
+
- lib/ratatat/cell.rb
|
|
85
|
+
- lib/ratatat/color.rb
|
|
86
|
+
- lib/ratatat/css_parser.rb
|
|
87
|
+
- lib/ratatat/dom_query.rb
|
|
88
|
+
- lib/ratatat/driver.rb
|
|
89
|
+
- lib/ratatat/input.rb
|
|
90
|
+
- lib/ratatat/message.rb
|
|
91
|
+
- lib/ratatat/reactive.rb
|
|
92
|
+
- lib/ratatat/styles.rb
|
|
93
|
+
- lib/ratatat/terminal.rb
|
|
94
|
+
- lib/ratatat/version.rb
|
|
95
|
+
- lib/ratatat/widget.rb
|
|
96
|
+
- lib/ratatat/widgets/button.rb
|
|
97
|
+
- lib/ratatat/widgets/checkbox.rb
|
|
98
|
+
- lib/ratatat/widgets/container.rb
|
|
99
|
+
- lib/ratatat/widgets/data_table.rb
|
|
100
|
+
- lib/ratatat/widgets/grid.rb
|
|
101
|
+
- lib/ratatat/widgets/horizontal.rb
|
|
102
|
+
- lib/ratatat/widgets/log.rb
|
|
103
|
+
- lib/ratatat/widgets/modal.rb
|
|
104
|
+
- lib/ratatat/widgets/progress_bar.rb
|
|
105
|
+
- lib/ratatat/widgets/radio_set.rb
|
|
106
|
+
- lib/ratatat/widgets/scrollable_container.rb
|
|
107
|
+
- lib/ratatat/widgets/select.rb
|
|
108
|
+
- lib/ratatat/widgets/sparkline.rb
|
|
109
|
+
- lib/ratatat/widgets/spinner.rb
|
|
110
|
+
- lib/ratatat/widgets/static.rb
|
|
111
|
+
- lib/ratatat/widgets/tabbed_content.rb
|
|
112
|
+
- lib/ratatat/widgets/text_area.rb
|
|
113
|
+
- lib/ratatat/widgets/text_input.rb
|
|
114
|
+
- lib/ratatat/widgets/toast.rb
|
|
115
|
+
- lib/ratatat/widgets/tooltip.rb
|
|
116
|
+
- lib/ratatat/widgets/tree.rb
|
|
117
|
+
- lib/ratatat/widgets/vertical.rb
|
|
118
|
+
homepage: https://github.com/vicentereig/ratatat
|
|
119
|
+
licenses:
|
|
120
|
+
- MIT
|
|
121
|
+
metadata:
|
|
122
|
+
homepage_uri: https://github.com/vicentereig/ratatat
|
|
123
|
+
source_code_uri: https://github.com/vicentereig/ratatat
|
|
124
|
+
documentation_uri: https://github.com/vicentereig/ratatat#readme
|
|
125
|
+
rdoc_options: []
|
|
126
|
+
require_paths:
|
|
127
|
+
- lib
|
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
129
|
+
requirements:
|
|
130
|
+
- - ">="
|
|
131
|
+
- !ruby/object:Gem::Version
|
|
132
|
+
version: '3.1'
|
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0'
|
|
138
|
+
requirements: []
|
|
139
|
+
rubygems_version: 3.6.5
|
|
140
|
+
specification_version: 4
|
|
141
|
+
summary: A pure Ruby TUI framework inspired by Textual
|
|
142
|
+
test_files: []
|