whirled_peas 0.4.1 → 0.5.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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +111 -73
  4. data/Rakefile +37 -3
  5. data/bin/debug +35 -0
  6. data/lib/whirled_peas.rb +9 -7
  7. data/lib/whirled_peas/command_line.rb +25 -32
  8. data/lib/whirled_peas/debugger.rb +80 -0
  9. data/lib/whirled_peas/errors.rb +2 -0
  10. data/lib/whirled_peas/frame.rb +0 -1
  11. data/lib/whirled_peas/frame/consumer.rb +30 -0
  12. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  13. data/lib/whirled_peas/frame/event_loop.rb +29 -30
  14. data/lib/whirled_peas/frame/producer.rb +11 -5
  15. data/lib/whirled_peas/graphics.rb +5 -0
  16. data/lib/whirled_peas/graphics/box_painter.rb +100 -0
  17. data/lib/whirled_peas/graphics/canvas.rb +107 -0
  18. data/lib/whirled_peas/graphics/container_coords.rb +61 -0
  19. data/lib/whirled_peas/graphics/container_dimensions.rb +65 -0
  20. data/lib/whirled_peas/graphics/container_painter.rb +116 -0
  21. data/lib/whirled_peas/graphics/debugger.rb +43 -0
  22. data/lib/whirled_peas/graphics/grid_painter.rb +56 -0
  23. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  24. data/lib/whirled_peas/graphics/painter.rb +24 -0
  25. data/lib/whirled_peas/graphics/renderer.rb +49 -0
  26. data/lib/whirled_peas/graphics/screen.rb +65 -0
  27. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  28. data/lib/whirled_peas/graphics/text_painter.rb +38 -0
  29. data/lib/whirled_peas/settings.rb +5 -0
  30. data/lib/whirled_peas/settings/bg_color.rb +22 -0
  31. data/lib/whirled_peas/settings/border.rb +101 -0
  32. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  33. data/lib/whirled_peas/{utils → settings}/color.rb +3 -35
  34. data/lib/whirled_peas/settings/container_settings.rb +128 -0
  35. data/lib/whirled_peas/settings/debugger.rb +87 -0
  36. data/lib/whirled_peas/settings/display_flow.rb +25 -0
  37. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  38. data/lib/whirled_peas/settings/grid_settings.rb +11 -0
  39. data/lib/whirled_peas/settings/margin.rb +8 -0
  40. data/lib/whirled_peas/settings/padding.rb +8 -0
  41. data/lib/whirled_peas/settings/position.rb +15 -0
  42. data/lib/whirled_peas/settings/spacing.rb +24 -0
  43. data/lib/whirled_peas/settings/text_align.rb +19 -0
  44. data/lib/whirled_peas/settings/text_color.rb +19 -0
  45. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  46. data/lib/whirled_peas/template/box_element.rb +8 -0
  47. data/lib/whirled_peas/template/composer.rb +68 -0
  48. data/lib/whirled_peas/template/container.rb +28 -0
  49. data/lib/whirled_peas/template/debugger.rb +34 -0
  50. data/lib/whirled_peas/template/element.rb +1 -218
  51. data/lib/whirled_peas/template/grid_element.rb +8 -0
  52. data/lib/whirled_peas/template/text_element.rb +24 -0
  53. data/lib/whirled_peas/utils/ansi.rb +6 -56
  54. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  55. data/lib/whirled_peas/version.rb +1 -1
  56. data/screen_test/rendered/elements/box.frame +1 -0
  57. data/screen_test/rendered/elements/box.rb +20 -0
  58. data/screen_test/rendered/elements/grid.frame +1 -0
  59. data/screen_test/rendered/elements/grid.rb +13 -0
  60. data/screen_test/rendered/elements/screen_overflow.rb +9 -0
  61. data/screen_test/rendered/elements/text.frame +1 -0
  62. data/screen_test/rendered/elements/text.rb +9 -0
  63. data/screen_test/rendered/elements/text_multiline.frame +1 -0
  64. data/screen_test/rendered/elements/text_multiline.rb +9 -0
  65. data/screen_test/rendered/settings/align/box.frame +1 -0
  66. data/screen_test/rendered/settings/align/box.rb +20 -0
  67. data/screen_test/rendered/settings/align/children_center.frame +1 -0
  68. data/screen_test/rendered/settings/align/children_center.rb +13 -0
  69. data/screen_test/rendered/settings/align/children_left.frame +1 -0
  70. data/screen_test/rendered/settings/align/children_left.rb +13 -0
  71. data/screen_test/rendered/settings/align/children_right.frame +1 -0
  72. data/screen_test/rendered/settings/align/children_right.rb +13 -0
  73. data/screen_test/rendered/settings/align/grid.frame +1 -0
  74. data/screen_test/rendered/settings/align/grid.rb +20 -0
  75. data/screen_test/rendered/settings/ansi/bold.frame +1 -0
  76. data/screen_test/rendered/settings/ansi/bold.rb +15 -0
  77. data/screen_test/rendered/settings/ansi/color.frame +1 -0
  78. data/screen_test/rendered/settings/ansi/color.rb +37 -0
  79. data/screen_test/rendered/settings/ansi/underline.frame +1 -0
  80. data/screen_test/rendered/settings/ansi/underline.rb +15 -0
  81. data/screen_test/rendered/settings/border.frame +1 -0
  82. data/screen_test/rendered/settings/border.rb +13 -0
  83. data/screen_test/rendered/settings/flow/l2r.frame +1 -0
  84. data/screen_test/rendered/settings/flow/l2r.rb +24 -0
  85. data/screen_test/rendered/settings/flow/t2b.frame +1 -0
  86. data/screen_test/rendered/settings/flow/t2b.rb +24 -0
  87. data/screen_test/rendered/settings/height/box.frame +1 -0
  88. data/screen_test/rendered/settings/height/box.rb +13 -0
  89. data/screen_test/rendered/settings/height/grid.frame +1 -0
  90. data/screen_test/rendered/settings/height/grid.rb +14 -0
  91. data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
  92. data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
  93. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
  94. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
  95. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
  96. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
  97. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
  98. data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
  99. data/screen_test/rendered/settings/margin.frame +1 -0
  100. data/screen_test/rendered/settings/margin.rb +14 -0
  101. data/screen_test/rendered/settings/padding.frame +1 -0
  102. data/screen_test/rendered/settings/padding.rb +11 -0
  103. data/screen_test/rendered/settings/position/box_left.frame +1 -0
  104. data/screen_test/rendered/settings/position/box_left.rb +17 -0
  105. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
  106. data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
  107. data/screen_test/rendered/settings/position/box_top.frame +1 -0
  108. data/screen_test/rendered/settings/position/box_top.rb +17 -0
  109. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
  110. data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
  111. data/screen_test/rendered/settings/position/grid_left.frame +1 -0
  112. data/screen_test/rendered/settings/position/grid_left.rb +18 -0
  113. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
  114. data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
  115. data/screen_test/rendered/settings/position/grid_top.frame +1 -0
  116. data/screen_test/rendered/settings/position/grid_top.rb +18 -0
  117. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
  118. data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
  119. data/screen_test/rendered/settings/title_font.frame +1 -0
  120. data/screen_test/rendered/settings/title_font.rb +12 -0
  121. data/screen_test/rendered/settings/width/box.frame +1 -0
  122. data/screen_test/rendered/settings/width/box.rb +13 -0
  123. data/screen_test/rendered/settings/width/grid.frame +1 -0
  124. data/screen_test/rendered/settings/width/grid.rb +14 -0
  125. data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
  126. data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
  127. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
  128. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
  129. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
  130. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
  131. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
  132. data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
  133. data/screen_test/screen_tester.rb +191 -0
  134. metadata +122 -10
  135. data/bin/title_fonts +0 -6
  136. data/lib/whirled_peas/frame/print_consumer.rb +0 -33
  137. data/lib/whirled_peas/template/settings.rb +0 -530
  138. data/lib/whirled_peas/ui.rb +0 -5
  139. data/lib/whirled_peas/ui/canvas.rb +0 -68
  140. data/lib/whirled_peas/ui/painter.rb +0 -287
  141. data/lib/whirled_peas/ui/screen.rb +0 -63
@@ -1,5 +0,0 @@
1
- module WhirledPeas
2
- module UI
3
- end
4
- private_constant :UI
5
- end
@@ -1,68 +0,0 @@
1
- require_relative '../utils/ansi'
2
-
3
- module WhirledPeas
4
- module UI
5
- # Canvas represent the area of the screen a painter can paint on.
6
- class Canvas
7
- # A Stroke is a single line, formatted string of characters that is painted at
8
- # a given position on a Canvas. This class is not meant to be instantiated
9
- # directly. Instead, use Canvas#stroke to create a new Stroke.
10
- class Stroke
11
- attr_reader :left, :top, :chars
12
-
13
- def initialize(left, top, chars)
14
- @left = left
15
- @top = top
16
- @chars = chars
17
- end
18
-
19
- def hash
20
- [left, top, chars].hash
21
- end
22
-
23
- def ==(other)
24
- other.is_a?(self.class) && self.hash == other.hash
25
- end
26
-
27
- def inspect
28
- "Stroke(left=#{left}, top=#{top}, chars=#{chars})"
29
- end
30
-
31
- alias_method :eq?, :==
32
- end
33
- private_constant :Stroke
34
-
35
- EMPTY_STROKE = Stroke.new(nil, nil, nil)
36
-
37
- attr_reader :left, :top, :width, :height
38
-
39
- def initialize(left, top, width, height)
40
- @left = left
41
- @top = top
42
- @width = width
43
- @height = height
44
- end
45
-
46
- # Return a new Stroke instance, verifying only characters within the canvas
47
- # are included in the stroke.
48
- def stroke(left, top, chars)
49
- if left >= self.left + self.width || left + chars.length <= self.left
50
- EMPTY_STROKE
51
- elsif top < self.top || top >= self.top + self.height
52
- EMPTY_STROKE
53
- else
54
- if left < self.left
55
- chars = chars[self.left - left..-1]
56
- left = self.left
57
- end
58
- num_chars = [self.left + self.width, left + chars.length].min - left
59
- Stroke.new(left, top, Ansi.substring(chars, 0, num_chars))
60
- end
61
- end
62
-
63
- def inspect
64
- "Canvas(left=#{left}, top=#{top}, width=#{width}, height=#{height})"
65
- end
66
- end
67
- end
68
- end
@@ -1,287 +0,0 @@
1
- require_relative '../template/element'
2
- require_relative '../template/settings'
3
- require_relative '../utils/ansi'
4
-
5
- require_relative 'canvas'
6
-
7
- module WhirledPeas
8
- module UI
9
- DEBUG_SPACING = ARGV.include?('--debug-spacing')
10
-
11
- class TextPainter
12
- JUSTIFICATION = DEBUG_SPACING ? 'j' : ' '
13
-
14
- def initialize(text, canvas)
15
- @text = text
16
- @canvas = canvas
17
- end
18
-
19
- def paint(&block)
20
- text.lines.each.with_index do |line, index|
21
- yield canvas.stroke(canvas.left, canvas.top + index, justified(line))
22
- end
23
- end
24
-
25
- private
26
-
27
- attr_reader :text, :canvas
28
-
29
- def visible(line)
30
- if line.length <= text.preferred_width
31
- line
32
- elsif text.settings.align == TextAlign::LEFT
33
- line[0..text.preferred_width - 1]
34
- elsif text.settings.align == TextAlign::CENTER
35
- left_chop = (line.length - text.preferred_width) / 2
36
- right_chop = line.length - text.preferred_width - left_chop
37
- line[left_chop..-right_chop - 1]
38
- else
39
- line[-text.preferred_width..-1]
40
- end
41
- end
42
-
43
- def justified(line)
44
- format_settings = [*text.settings.color, *text.settings.bg_color]
45
- format_settings << Ansi::BOLD if text.settings.bold?
46
- format_settings << Ansi::UNDERLINE if text.settings.underline?
47
-
48
- ljust = case text.settings.align
49
- when TextAlign::LEFT
50
- 0
51
- when TextAlign::CENTER
52
- [0, (text.preferred_width - line.length) / 2].max
53
- when TextAlign::RIGHT
54
- [0, text.preferred_width - line.length].max
55
- end
56
- rjust = [0, text.preferred_width - line.length - ljust].max
57
- Ansi.format(JUSTIFICATION * ljust, [*text.settings.bg_color]) +
58
- Ansi.format(visible(line), format_settings) +
59
- Ansi.format(JUSTIFICATION * rjust, [*text.settings.bg_color])
60
- end
61
- end
62
- private_constant :TextPainter
63
-
64
- class ContainerPainter
65
- PADDING = DEBUG_SPACING ? 'p' : ' '
66
-
67
- def initialize(container, canvas)
68
- @container = container
69
- @settings = container.settings
70
- @canvas = canvas
71
- end
72
-
73
- def paint(&block)
74
- return if container.num_rows == 0 || container.num_cols == 0
75
- top = canvas.top + settings.margin.top
76
- if settings.auto_margin?
77
- left = canvas.left + (canvas.width - container.preferred_width) / 2
78
- else
79
- left = canvas.left + settings.margin.left
80
- end
81
- if settings.border.top?
82
- yield canvas.stroke(left, top, top_border)
83
- top += 1
84
- end
85
- container.num_rows.times do |row_num|
86
- if row_num > 0 && settings.border.inner_horiz?
87
- yield canvas.stroke(left, top, middle_border)
88
- top += 1
89
- end
90
- (settings.padding.top + container.row_height + settings.padding.bottom).times do
91
- yield canvas.stroke(left, top, content_line)
92
- top += 1
93
- end
94
- end
95
- if settings.border.bottom?
96
- yield canvas.stroke(left, top, bottom_border)
97
- top += 1
98
- end
99
- end
100
-
101
- private
102
-
103
- attr_reader :container, :settings, :canvas
104
-
105
- def line_stroke(left_border, horiz_border, junc_border, right_border)
106
- stroke = ''
107
- stroke += left_border if settings.border.left?
108
- container.num_cols.times do |col_num|
109
- stroke += junc_border if col_num > 0 && settings.border.inner_horiz?
110
- stroke += horiz_border * (container.col_width + settings.padding.left + settings.padding.right)
111
- end
112
- stroke += right_border if settings.border.right?
113
- Ansi.format(stroke, [*settings.border.color, *settings.bg_color])
114
- end
115
-
116
- def top_border
117
- line_stroke(
118
- settings.border.style.top_left,
119
- settings.border.style.top_horiz,
120
- settings.border.style.top_junc,
121
- settings.border.style.top_right
122
- )
123
- end
124
-
125
- def content_line
126
- line_stroke(
127
- settings.border.style.left_vert,
128
- PADDING,
129
- settings.border.style.middle_vert,
130
- settings.border.style.right_vert
131
- )
132
- end
133
-
134
- def middle_border
135
- line_stroke(
136
- settings.border.style.left_junc,
137
- settings.border.style.middle_horiz,
138
- settings.border.style.cross_junc,
139
- settings.border.style.right_junc
140
- )
141
- end
142
-
143
- def bottom_border
144
- line_stroke(
145
- settings.border.style.bottom_left,
146
- settings.border.style.bottom_horiz,
147
- settings.border.style.bottom_junc,
148
- settings.border.style.bottom_right
149
- )
150
- end
151
- end
152
-
153
- class BoxContainer
154
- attr_reader :settings, :num_cols, :num_rows, :col_width, :row_height, :preferred_width
155
-
156
- def initialize(box)
157
- @settings = ContainerSettings.merge(box.settings)
158
- @num_cols = 1
159
- @num_rows = 1
160
- @col_width = box.content_width
161
- @row_height = box.content_height
162
- @preferred_width = box.preferred_width
163
- end
164
- end
165
-
166
- class BoxPainter
167
- def initialize(box, canvas)
168
- @box = box
169
- @canvas = canvas
170
- end
171
-
172
- def paint(&block)
173
- container = BoxContainer.new(box)
174
- ContainerPainter.new(container, canvas).paint(&block)
175
- top = canvas.top + box.settings.margin.top + (box.settings.border.top? ? 1 : 0) + box.settings.padding.top
176
- if box.settings.auto_margin?
177
- margin = (canvas.width - box.preferred_width) / 2
178
- else
179
- margin = box.settings.margin.left
180
- end
181
- left = canvas.left + margin + (box.settings.border.left? ? 1 : 0) + box.settings.padding.left
182
- greedy_width = box.settings.vertical_flow? || box.children.length == 1
183
- children = box.children
184
- children = children.reverse if box.settings.reverse_flow?
185
- children.each do |child|
186
- if greedy_width
187
- width = box.content_width
188
- height = child.preferred_height
189
- else
190
- width = child.preferred_width
191
- height = box.content_height
192
- end
193
- child_canvas = Canvas.new(left, top, width, height)
194
- Painter.paint(child, child_canvas, &block)
195
- if box.settings.horizontal_flow?
196
- left += child.preferred_width
197
- else
198
- top += child.preferred_height
199
- end
200
- end
201
- end
202
-
203
- private
204
-
205
- attr_reader :box, :canvas
206
- end
207
-
208
- class GridContainer
209
- attr_reader :settings, :num_cols, :num_rows, :col_width, :row_height, :preferred_width
210
-
211
- def initialize(grid, num_cols, num_rows)
212
- @settings = ContainerSettings.merge(grid.settings)
213
- @num_cols = num_cols
214
- @num_rows = num_rows
215
- @col_width = grid.col_width
216
- @row_height = grid.row_height
217
- @preferred_width = grid.preferred_width
218
- end
219
- end
220
-
221
- class GridPainter
222
- def initialize(grid, canvas)
223
- @grid = grid
224
- @canvas = canvas
225
- available_width = grid.preferred_width - (grid.settings.margin.left || 0) - (grid.settings.margin.right || 0)
226
- @num_cols = grid.settings.num_cols || (available_width - (grid.settings.border.left? ? 1 : 0) - (grid.settings.border.right? ? 1 : 0) + (grid.border.inner_vert ? 1 : 0)) / (col_width + grid.settings.padding.left + grid.settings.right + (grid.border.inner_vert ? 1 : 0))
227
- end
228
-
229
- def paint(&block)
230
- return if grid.children.empty?
231
-
232
- container = GridContainer.new(grid, num_cols, (grid.children.length.to_f / num_cols).ceil)
233
- ContainerPainter.new(container, canvas).paint(&block)
234
-
235
- children = if grid.settings.transpose?
236
- grid.children.length.times.map do |i|
237
- grid.children[(i * num_cols) % grid.children.length + i / (grid.children.length / num_cols)]
238
- end.compact
239
- else
240
- grid.children
241
- end
242
-
243
- top = canvas.top + grid.settings.margin.top + (grid.settings.border.top? ? 1 : 0) + grid.settings.padding.top
244
- if grid.settings.auto_margin?
245
- margin = (canvas.width - grid.preferred_width) / 2
246
- else
247
- margin = grid.settings.margin.left
248
- end
249
- left = canvas.left + margin + (grid.settings.border.left? ? 1 : 0) + grid.settings.padding.left
250
- grid_height = grid.settings.padding.top + grid.row_height + grid.settings.padding.bottom + (grid.settings.border.inner_horiz? ? 1 : 0)
251
- grid_width = grid.settings.padding.left + grid.col_width + grid.settings.padding.right + (grid.settings.border.inner_vert? ? 1 : 0)
252
- children.each_slice(num_cols).each.with_index do |row, row_num|
253
- row_top = top + row_num * grid_height
254
- row.each.with_index do |element, col_num|
255
- col_left = left + col_num * grid_width
256
- child_canvas = Canvas.new(
257
- col_left,
258
- row_top,
259
- element.preferred_width,
260
- element.preferred_height
261
- )
262
- Painter.paint(element, child_canvas, &block)
263
- end
264
- end
265
- end
266
-
267
- private
268
-
269
- attr_reader :grid, :canvas, :num_cols
270
- end
271
-
272
- module Painter
273
- PAINTERS = {
274
- TextElement => TextPainter,
275
- BoxElement => BoxPainter,
276
- GridElement => GridPainter,
277
- }
278
-
279
- def self.paint(element, canvas, &block)
280
- if element.is_a?(Template)
281
- element = BoxElement.from_template(element, canvas.width, canvas.height)
282
- end
283
- PAINTERS[element.class].new(element, canvas).paint(&block)
284
- end
285
- end
286
- end
287
- end
@@ -1,63 +0,0 @@
1
- require 'highline'
2
-
3
- require_relative '../utils/ansi'
4
-
5
- require_relative 'painter'
6
-
7
- module WhirledPeas
8
- module UI
9
- class Screen
10
- def initialize(print_output=true)
11
- @print_output = print_output
12
- @terminal = HighLine.new.terminal
13
- @strokes = []
14
- refresh_size!
15
- Signal.trap('SIGWINCH', proc { self.refresh_size! })
16
- end
17
-
18
- def paint(template)
19
- @template = template
20
- draw
21
- end
22
-
23
- def refresh
24
- # No need to refresh if the screen dimensions have not changed
25
- return if @refreshed_width == width || @refreshed_height == height
26
- draw
27
- end
28
-
29
- def finalize
30
- return unless @print_output
31
- print Ansi.clear
32
- print Ansi.cursor_pos(top: height - 1)
33
- print Ansi.cursor_visible(true)
34
- STDOUT.flush
35
- end
36
-
37
- protected
38
-
39
- def refresh_size!
40
- @width, @height = terminal.terminal_size
41
- end
42
-
43
- private
44
-
45
- attr_reader :cursor, :terminal, :width, :height
46
-
47
- def draw
48
- strokes = [Ansi.cursor_visible(false), Ansi.cursor_pos, Ansi.clear_down]
49
- Painter.paint(@template, Canvas.new(0, 0, width, height)) do |stroke|
50
- unless stroke.chars.nil?
51
- strokes << Ansi.cursor_pos(left: stroke.left, top: stroke.top)
52
- strokes << stroke.chars
53
- end
54
- end
55
- return unless @print_output
56
- strokes.each(&method(:print))
57
- STDOUT.flush
58
- @refreshed_width = width
59
- @refreshed_height = height
60
- end
61
- end
62
- end
63
- end