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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +201 -0
  3. data/examples/log_tailer.rb +460 -0
  4. data/lib/ratatat/ansi_backend.rb +175 -0
  5. data/lib/ratatat/app.rb +342 -0
  6. data/lib/ratatat/binding.rb +74 -0
  7. data/lib/ratatat/buffer.rb +238 -0
  8. data/lib/ratatat/cell.rb +166 -0
  9. data/lib/ratatat/color.rb +191 -0
  10. data/lib/ratatat/css_parser.rb +192 -0
  11. data/lib/ratatat/dom_query.rb +124 -0
  12. data/lib/ratatat/driver.rb +200 -0
  13. data/lib/ratatat/input.rb +208 -0
  14. data/lib/ratatat/message.rb +147 -0
  15. data/lib/ratatat/reactive.rb +79 -0
  16. data/lib/ratatat/styles.rb +293 -0
  17. data/lib/ratatat/terminal.rb +168 -0
  18. data/lib/ratatat/version.rb +3 -0
  19. data/lib/ratatat/widget.rb +337 -0
  20. data/lib/ratatat/widgets/button.rb +43 -0
  21. data/lib/ratatat/widgets/checkbox.rb +68 -0
  22. data/lib/ratatat/widgets/container.rb +50 -0
  23. data/lib/ratatat/widgets/data_table.rb +123 -0
  24. data/lib/ratatat/widgets/grid.rb +40 -0
  25. data/lib/ratatat/widgets/horizontal.rb +52 -0
  26. data/lib/ratatat/widgets/log.rb +97 -0
  27. data/lib/ratatat/widgets/modal.rb +161 -0
  28. data/lib/ratatat/widgets/progress_bar.rb +80 -0
  29. data/lib/ratatat/widgets/radio_set.rb +91 -0
  30. data/lib/ratatat/widgets/scrollable_container.rb +88 -0
  31. data/lib/ratatat/widgets/select.rb +100 -0
  32. data/lib/ratatat/widgets/sparkline.rb +61 -0
  33. data/lib/ratatat/widgets/spinner.rb +93 -0
  34. data/lib/ratatat/widgets/static.rb +23 -0
  35. data/lib/ratatat/widgets/tabbed_content.rb +114 -0
  36. data/lib/ratatat/widgets/text_area.rb +183 -0
  37. data/lib/ratatat/widgets/text_input.rb +143 -0
  38. data/lib/ratatat/widgets/toast.rb +55 -0
  39. data/lib/ratatat/widgets/tooltip.rb +66 -0
  40. data/lib/ratatat/widgets/tree.rb +172 -0
  41. data/lib/ratatat/widgets/vertical.rb +52 -0
  42. data/lib/ratatat.rb +51 -0
  43. 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: []