whirled_peas 0.3.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.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +38 -0
  4. data/README.md +549 -85
  5. data/Rakefile +28 -3
  6. data/examples/intro.rb +52 -0
  7. data/examples/scrolling.rb +53 -0
  8. data/exe/whirled_peas +7 -0
  9. data/lib/whirled_peas.rb +12 -36
  10. data/lib/whirled_peas/command_line.rb +255 -0
  11. data/lib/whirled_peas/config.rb +21 -0
  12. data/lib/whirled_peas/errors.rb +7 -0
  13. data/lib/whirled_peas/frame.rb +0 -5
  14. data/lib/whirled_peas/frame/consumer.rb +30 -0
  15. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  16. data/lib/whirled_peas/frame/event_loop.rb +69 -38
  17. data/lib/whirled_peas/frame/producer.rb +36 -19
  18. data/lib/whirled_peas/graphics.rb +19 -0
  19. data/lib/whirled_peas/graphics/box_painter.rb +101 -0
  20. data/lib/whirled_peas/graphics/canvas.rb +118 -0
  21. data/lib/whirled_peas/graphics/composer.rb +80 -0
  22. data/lib/whirled_peas/graphics/container_coords.rb +72 -0
  23. data/lib/whirled_peas/graphics/container_dimensions.rb +93 -0
  24. data/lib/whirled_peas/graphics/container_painter.rb +272 -0
  25. data/lib/whirled_peas/graphics/debugger.rb +52 -0
  26. data/lib/whirled_peas/graphics/grid_painter.rb +69 -0
  27. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  28. data/lib/whirled_peas/graphics/painter.rb +23 -0
  29. data/lib/whirled_peas/graphics/renderer.rb +26 -0
  30. data/lib/whirled_peas/graphics/screen.rb +70 -0
  31. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  32. data/lib/whirled_peas/graphics/text_painter.rb +40 -0
  33. data/lib/whirled_peas/settings.rb +5 -0
  34. data/lib/whirled_peas/settings/alignment.rb +24 -0
  35. data/lib/whirled_peas/settings/bg_color.rb +24 -0
  36. data/lib/whirled_peas/settings/border.rb +101 -0
  37. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  38. data/lib/whirled_peas/settings/color.rb +68 -0
  39. data/lib/whirled_peas/settings/container_settings.rb +223 -0
  40. data/lib/whirled_peas/settings/debugger.rb +96 -0
  41. data/lib/whirled_peas/settings/display_flow.rb +27 -0
  42. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  43. data/lib/whirled_peas/settings/grid_settings.rb +19 -0
  44. data/lib/whirled_peas/settings/margin.rb +8 -0
  45. data/lib/whirled_peas/settings/padding.rb +8 -0
  46. data/lib/whirled_peas/settings/position.rb +15 -0
  47. data/lib/whirled_peas/settings/scrollbar.rb +15 -0
  48. data/lib/whirled_peas/settings/sizing.rb +19 -0
  49. data/lib/whirled_peas/settings/spacing.rb +58 -0
  50. data/lib/whirled_peas/settings/text_color.rb +21 -0
  51. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  52. data/lib/whirled_peas/settings/vert_alignment.rb +24 -0
  53. data/lib/whirled_peas/utils.rb +5 -0
  54. data/lib/whirled_peas/utils/ansi.rb +53 -0
  55. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  56. data/lib/whirled_peas/utils/title_font.rb +75 -0
  57. data/lib/whirled_peas/version.rb +1 -1
  58. data/screen_test/elements/box.frame +1 -0
  59. data/screen_test/elements/box.rb +20 -0
  60. data/screen_test/elements/grid.frame +1 -0
  61. data/screen_test/elements/grid.rb +13 -0
  62. data/screen_test/elements/screen_overflow_x.frame +1 -0
  63. data/screen_test/elements/screen_overflow_x.rb +9 -0
  64. data/screen_test/elements/screen_overflow_y.frame +1 -0
  65. data/screen_test/elements/screen_overflow_y.rb +9 -0
  66. data/screen_test/elements/text.frame +1 -0
  67. data/screen_test/elements/text.rb +9 -0
  68. data/screen_test/elements/text_multiline.frame +1 -0
  69. data/screen_test/elements/text_multiline.rb +9 -0
  70. data/screen_test/settings/align/box_around.frame +1 -0
  71. data/screen_test/settings/align/box_around.rb +16 -0
  72. data/screen_test/settings/align/box_between.frame +1 -0
  73. data/screen_test/settings/align/box_between.rb +16 -0
  74. data/screen_test/settings/align/box_center.frame +1 -0
  75. data/screen_test/settings/align/box_center.rb +21 -0
  76. data/screen_test/settings/align/box_default.frame +1 -0
  77. data/screen_test/settings/align/box_default.rb +20 -0
  78. data/screen_test/settings/align/box_evenly.frame +1 -0
  79. data/screen_test/settings/align/box_evenly.rb +16 -0
  80. data/screen_test/settings/align/box_left.frame +1 -0
  81. data/screen_test/settings/align/box_left.rb +21 -0
  82. data/screen_test/settings/align/box_right.frame +1 -0
  83. data/screen_test/settings/align/box_right.rb +21 -0
  84. data/screen_test/settings/align/children_center.frame +1 -0
  85. data/screen_test/settings/align/children_center.rb +15 -0
  86. data/screen_test/settings/align/children_left.frame +1 -0
  87. data/screen_test/settings/align/children_left.rb +15 -0
  88. data/screen_test/settings/align/children_right.frame +1 -0
  89. data/screen_test/settings/align/children_right.rb +15 -0
  90. data/screen_test/settings/align/grid_center.frame +1 -0
  91. data/screen_test/settings/align/grid_center.rb +18 -0
  92. data/screen_test/settings/align/grid_default.frame +1 -0
  93. data/screen_test/settings/align/grid_default.rb +17 -0
  94. data/screen_test/settings/align/grid_left.frame +1 -0
  95. data/screen_test/settings/align/grid_left.rb +18 -0
  96. data/screen_test/settings/align/grid_right.frame +1 -0
  97. data/screen_test/settings/align/grid_right.rb +18 -0
  98. data/screen_test/settings/ansi/bold.frame +1 -0
  99. data/screen_test/settings/ansi/bold.rb +14 -0
  100. data/screen_test/settings/ansi/color.frame +1 -0
  101. data/screen_test/settings/ansi/color.rb +37 -0
  102. data/screen_test/settings/ansi/underline.frame +1 -0
  103. data/screen_test/settings/ansi/underline.rb +14 -0
  104. data/screen_test/settings/border.frame +1 -0
  105. data/screen_test/settings/border.rb +13 -0
  106. data/screen_test/settings/flow/box_b2t.frame +1 -0
  107. data/screen_test/settings/flow/box_b2t.rb +26 -0
  108. data/screen_test/settings/flow/box_l2r.frame +1 -0
  109. data/screen_test/settings/flow/box_l2r.rb +26 -0
  110. data/screen_test/settings/flow/box_r2l.frame +1 -0
  111. data/screen_test/settings/flow/box_r2l.rb +26 -0
  112. data/screen_test/settings/flow/box_t2b.frame +1 -0
  113. data/screen_test/settings/flow/box_t2b.rb +26 -0
  114. data/screen_test/settings/flow/grid_b2t.frame +1 -0
  115. data/screen_test/settings/flow/grid_b2t.rb +14 -0
  116. data/screen_test/settings/flow/grid_l2r.frame +1 -0
  117. data/screen_test/settings/flow/grid_l2r.rb +14 -0
  118. data/screen_test/settings/flow/grid_r2l.frame +1 -0
  119. data/screen_test/settings/flow/grid_r2l.rb +14 -0
  120. data/screen_test/settings/flow/grid_t2b.frame +1 -0
  121. data/screen_test/settings/flow/grid_t2b.rb +14 -0
  122. data/screen_test/settings/height/box.frame +1 -0
  123. data/screen_test/settings/height/box.rb +13 -0
  124. data/screen_test/settings/height/box_border_sizing.frame +1 -0
  125. data/screen_test/settings/height/box_border_sizing.rb +15 -0
  126. data/screen_test/settings/height/grid.frame +1 -0
  127. data/screen_test/settings/height/grid.rb +14 -0
  128. data/screen_test/settings/height/overflow_box.frame +1 -0
  129. data/screen_test/settings/height/overflow_box.rb +13 -0
  130. data/screen_test/settings/height/overflow_box_l2r.frame +1 -0
  131. data/screen_test/settings/height/overflow_box_l2r.rb +17 -0
  132. data/screen_test/settings/height/overflow_box_t2b.frame +1 -0
  133. data/screen_test/settings/height/overflow_box_t2b.rb +16 -0
  134. data/screen_test/settings/height/overflow_grid.frame +1 -0
  135. data/screen_test/settings/height/overflow_grid.rb +16 -0
  136. data/screen_test/settings/margin.frame +1 -0
  137. data/screen_test/settings/margin.rb +16 -0
  138. data/screen_test/settings/padding.frame +1 -0
  139. data/screen_test/settings/padding.rb +13 -0
  140. data/screen_test/settings/position/box_left.frame +1 -0
  141. data/screen_test/settings/position/box_left.rb +17 -0
  142. data/screen_test/settings/position/box_left_negative.frame +1 -0
  143. data/screen_test/settings/position/box_left_negative.rb +17 -0
  144. data/screen_test/settings/position/box_top.frame +1 -0
  145. data/screen_test/settings/position/box_top.rb +17 -0
  146. data/screen_test/settings/position/box_top_negative.frame +1 -0
  147. data/screen_test/settings/position/box_top_negative.rb +17 -0
  148. data/screen_test/settings/position/grid_left.frame +1 -0
  149. data/screen_test/settings/position/grid_left.rb +18 -0
  150. data/screen_test/settings/position/grid_left_negative.frame +1 -0
  151. data/screen_test/settings/position/grid_left_negative.rb +18 -0
  152. data/screen_test/settings/position/grid_top.frame +1 -0
  153. data/screen_test/settings/position/grid_top.rb +18 -0
  154. data/screen_test/settings/position/grid_top_negative.frame +1 -0
  155. data/screen_test/settings/position/grid_top_negative.rb +18 -0
  156. data/screen_test/settings/scroll/horiz_box.frame +1 -0
  157. data/screen_test/settings/scroll/horiz_box.rb +17 -0
  158. data/screen_test/settings/scroll/horiz_box_align_center.rb +18 -0
  159. data/screen_test/settings/scroll/horiz_box_align_right.rb +18 -0
  160. data/screen_test/settings/scroll/vert_box.frame +1 -0
  161. data/screen_test/settings/scroll/vert_box.rb +20 -0
  162. data/screen_test/settings/title_font.frame +1 -0
  163. data/screen_test/settings/title_font.rb +12 -0
  164. data/screen_test/settings/valign/box_around.frame +1 -0
  165. data/screen_test/settings/valign/box_around.rb +17 -0
  166. data/screen_test/settings/valign/box_between.frame +1 -0
  167. data/screen_test/settings/valign/box_between.rb +17 -0
  168. data/screen_test/settings/valign/box_bottom.frame +1 -0
  169. data/screen_test/settings/valign/box_bottom.rb +17 -0
  170. data/screen_test/settings/valign/box_default.frame +1 -0
  171. data/screen_test/settings/valign/box_default.rb +16 -0
  172. data/screen_test/settings/valign/box_evenly.frame +1 -0
  173. data/screen_test/settings/valign/box_evenly.rb +17 -0
  174. data/screen_test/settings/valign/box_middle.frame +1 -0
  175. data/screen_test/settings/valign/box_middle.rb +17 -0
  176. data/screen_test/settings/valign/box_top.frame +1 -0
  177. data/screen_test/settings/valign/box_top.rb +17 -0
  178. data/screen_test/settings/valign/grid_bottom.frame +1 -0
  179. data/screen_test/settings/valign/grid_bottom.rb +15 -0
  180. data/screen_test/settings/valign/grid_default.frame +1 -0
  181. data/screen_test/settings/valign/grid_default.rb +14 -0
  182. data/screen_test/settings/valign/grid_middle.frame +1 -0
  183. data/screen_test/settings/valign/grid_middle.rb +15 -0
  184. data/screen_test/settings/valign/grid_top.frame +1 -0
  185. data/screen_test/settings/valign/grid_top.rb +15 -0
  186. data/screen_test/settings/width/box_border_sizing.frame +1 -0
  187. data/screen_test/settings/width/box_border_sizing.rb +15 -0
  188. data/screen_test/settings/width/box_content.frame +1 -0
  189. data/screen_test/settings/width/box_content.rb +15 -0
  190. data/screen_test/settings/width/box_default.frame +1 -0
  191. data/screen_test/settings/width/box_default.rb +14 -0
  192. data/screen_test/settings/width/grid.frame +1 -0
  193. data/screen_test/settings/width/grid.rb +14 -0
  194. data/screen_test/settings/width/overflow_align_center.frame +1 -0
  195. data/screen_test/settings/width/overflow_align_center.rb +14 -0
  196. data/screen_test/settings/width/overflow_align_right.frame +1 -0
  197. data/screen_test/settings/width/overflow_align_right.rb +14 -0
  198. data/screen_test/settings/width/overflow_box.frame +1 -0
  199. data/screen_test/settings/width/overflow_box.rb +13 -0
  200. data/screen_test/settings/width/overflow_box_l2r.frame +1 -0
  201. data/screen_test/settings/width/overflow_box_l2r.rb +16 -0
  202. data/screen_test/settings/width/overflow_box_t2b.frame +1 -0
  203. data/screen_test/settings/width/overflow_box_t2b.rb +17 -0
  204. data/screen_test/settings/width/overflow_grid.frame +1 -0
  205. data/screen_test/settings/width/overflow_grid.rb +14 -0
  206. data/tools/whirled_peas/tools/screen_tester.rb +233 -0
  207. data/whirled_peas.gemspec +4 -1
  208. metadata +215 -17
  209. data/lib/whirled_peas/ui.rb +0 -7
  210. data/lib/whirled_peas/ui/ansi.rb +0 -103
  211. data/lib/whirled_peas/ui/canvas.rb +0 -68
  212. data/lib/whirled_peas/ui/color.rb +0 -101
  213. data/lib/whirled_peas/ui/element.rb +0 -224
  214. data/lib/whirled_peas/ui/painter.rb +0 -283
  215. data/lib/whirled_peas/ui/screen.rb +0 -60
  216. data/lib/whirled_peas/ui/settings.rb +0 -523
  217. data/sandbox/auto.rb +0 -13
  218. data/sandbox/box.rb +0 -19
  219. data/sandbox/grid.rb +0 -13
  220. data/sandbox/sandbox.rb +0 -17
  221. data/sandbox/text.rb +0 -33
@@ -1,224 +0,0 @@
1
- require_relative 'settings'
2
-
3
- module WhirledPeas
4
- module UI
5
- STRINGALBE_CLASSES = [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass]
6
-
7
- class Element
8
- attr_accessor :preferred_width, :preferred_height
9
- attr_reader :name, :settings
10
-
11
- def initialize(name, settings)
12
- @name = name
13
- @settings = settings
14
- end
15
- end
16
- private_constant :Element
17
-
18
- class TextElement < Element
19
- attr_reader :value
20
-
21
- def initialize(name, settings)
22
- super(name, TextSettings.merge(settings))
23
- end
24
-
25
- def value=(val)
26
- unless STRINGALBE_CLASSES.include?(val.class)
27
- raise ArgmentError, "Unsupported type for TextElement: #{val.class}"
28
- end
29
- @value = val.to_s
30
- @preferred_width = settings.width || value.length
31
- @preferred_height = 1
32
- end
33
-
34
- def inspect(indent='')
35
- dims = unless preferred_width.nil?
36
- "#{indent + ' '}- Dimensions: #{preferred_width}x#{preferred_height}"
37
- end
38
- [
39
- "#{indent}+ #{name} [#{self.class.name}]",
40
- dims,
41
- "#{indent + ' '}- Settings",
42
- settings.inspect(indent + ' ')
43
- ].compact.join("\n")
44
- end
45
- end
46
-
47
- class ComposableElement < Element
48
- class << self
49
- def next_name
50
- @counter ||= 0
51
- @counter += 1
52
- "Element-#{@counter}"
53
- end
54
- end
55
-
56
- def initialize(name, settings)
57
- super
58
- end
59
-
60
- def children
61
- @children ||= []
62
- end
63
-
64
- def add_text(name=self.class.next_name, &block)
65
- element = TextElement.new(name, settings)
66
- element.value = yield nil, element.settings
67
- children << element
68
- end
69
-
70
- def add_box(name=self.class.next_name, &block)
71
- element = BoxElement.new(name, settings)
72
- value = yield element, element.settings
73
- children << element
74
- if element.children.empty? && STRINGALBE_CLASSES.include?(value.class)
75
- element.add_text { value.to_s }
76
- end
77
- end
78
-
79
- def add_grid(name=self.class.next_name, &block)
80
- element = GridElement.new(name, settings)
81
- values = yield element, element.settings
82
- children << element
83
- if element.children.empty? && values.is_a?(Array)
84
- values.each { |v| element.add_text { v.to_s } }
85
- end
86
- end
87
-
88
- def inspect(indent='')
89
- kids = children.map { |c| c.inspect(indent + ' ') }.join("\n")
90
- dims = unless preferred_width.nil?
91
- "#{indent + ' '}- Dimensions: #{preferred_width}x#{preferred_height}"
92
- end
93
- [
94
- "#{indent}+ #{name} [#{self.class.name}]",
95
- dims,
96
- "#{indent + ' '}- Settings",
97
- settings.inspect(indent + ' '),
98
- "#{indent + ' '}- Children",
99
- kids
100
- ].compact.join("\n")
101
- end
102
-
103
- private
104
-
105
- def margin_width
106
- settings.margin.left + settings.margin.right
107
- end
108
-
109
- def margin_height
110
- settings.margin.top + settings.margin.bottom
111
- end
112
-
113
- def outer_border_width
114
- (settings.border.left? ? 1 : 0) + (settings.border.right? ? 1 : 0)
115
- end
116
-
117
- def outer_border_height
118
- (settings.border.top? ? 1 : 0) + (settings.border.bottom? ? 1 : 0)
119
- end
120
-
121
- def inner_border_width
122
- settings.border.inner_vert? ? 1 : 0
123
- end
124
-
125
- def inner_border_height
126
- settings.border.inner_horiz? ? 1 : 0
127
- end
128
-
129
- def padding_width
130
- settings.padding.left + settings.padding.right
131
- end
132
-
133
- def padding_height
134
- settings.padding.top + settings.padding.bottom
135
- end
136
- end
137
-
138
- class Template < ComposableElement
139
- def initialize(settings=TemplateSettings.new)
140
- super('TEMPLATE', settings)
141
- end
142
- end
143
-
144
- class BoxElement < ComposableElement
145
- attr_writer :content_width, :content_height
146
-
147
- def initialize(name, settings)
148
- super(name, BoxSettings.merge(settings))
149
- end
150
-
151
- def self.from_template(template, width, height)
152
- box = new(template.name, template.settings)
153
- template.children.each { |c| box.children << c }
154
- box.content_width = box.preferred_width = width
155
- box.content_height = box.preferred_height = height
156
- box
157
- end
158
-
159
- def content_width
160
- @content_width ||= begin
161
- child_widths = children.map(&:preferred_width)
162
- width = settings.horizontal_flow? ? child_widths.sum : (child_widths.max || 0)
163
- [width, *settings.width].max
164
- end
165
- end
166
-
167
- def preferred_width
168
- @preferred_width ||=
169
- margin_width + outer_border_width + padding_width + content_width
170
- end
171
-
172
- def content_height
173
- @content_height ||= begin
174
- child_heights = children.map(&:preferred_height)
175
- settings.vertical_flow? ? child_heights.sum : (child_heights.max || 0)
176
- end
177
- end
178
-
179
- def preferred_height
180
- @preferred_height ||=
181
- margin_height + outer_border_height + padding_height + content_height
182
- end
183
- end
184
-
185
- class GridElement < ComposableElement
186
- def initialize(name, settings)
187
- super(name, GridSettings.merge(settings))
188
- end
189
-
190
- def col_width
191
- return @col_width if @col_width
192
- @col_width = 0
193
- children.each do |child|
194
- @col_width = child.preferred_width if child.preferred_width > @col_width
195
- end
196
- @col_width
197
- end
198
-
199
- def row_height
200
- return @row_height if @row_height
201
- @row_height = 0
202
- children.each do |child|
203
- @row_height = child.preferred_height if child.preferred_height > @row_height
204
- end
205
- @row_height
206
- end
207
-
208
- def preferred_width
209
- margin_width +
210
- outer_border_width +
211
- settings.num_cols * (padding_width + col_width) +
212
- (settings.num_cols - 1) * inner_border_width
213
- end
214
-
215
- def preferred_height
216
- num_rows = (children.length / settings.num_cols).ceil
217
- margin_height +
218
- outer_border_height +
219
- num_rows * (padding_height + row_height) +
220
- (num_rows - 1) * inner_border_height
221
- end
222
- end
223
- end
224
- end
@@ -1,283 +0,0 @@
1
- require_relative 'ansi'
2
- require_relative 'canvas'
3
- require_relative 'settings'
4
-
5
- module WhirledPeas
6
- module UI
7
- DEBUG_SPACING = ARGV.include?('--debug-spacing')
8
-
9
- class TextPainter
10
- JUSTIFICATION = DEBUG_SPACING ? 'j' : ' '
11
-
12
- def initialize(text, canvas)
13
- @text = text
14
- @canvas = canvas
15
- end
16
-
17
- def paint(&block)
18
- yield canvas.stroke(canvas.left, canvas.top, justified)
19
- end
20
-
21
- private
22
-
23
- attr_reader :text, :canvas
24
-
25
- def visible
26
- if text.value.length <= text.preferred_width
27
- text.value
28
- elsif text.settings.align == TextAlign::LEFT
29
- text.value[0..text.preferred_width - 1]
30
- elsif text.settings.align == TextAlign::CENTER
31
- left_chop = (text.value.length - text.preferred_width) / 2
32
- right_chop = text.value.length - text.preferred_width - left_chop
33
- text.value[left_chop..-right_chop - 1]
34
- else
35
- text.value[-text.preferred_width..-1]
36
- end
37
- end
38
-
39
- def justified
40
- format_settings = [*text.settings.color, *text.settings.bg_color]
41
- format_settings << Ansi::BOLD if text.settings.bold?
42
- format_settings << Ansi::UNDERLINE if text.settings.underline?
43
-
44
- ljust = case text.settings.align
45
- when TextAlign::LEFT
46
- 0
47
- when TextAlign::CENTER
48
- [0, (text.preferred_width - text.value.length) / 2].max
49
- when TextAlign::RIGHT
50
- [0, text.preferred_width - text.value.length].max
51
- end
52
- rjust = [0, text.preferred_width - text.value.length - ljust].max
53
- Ansi.format(JUSTIFICATION * ljust, [*text.settings.bg_color]) +
54
- Ansi.format(visible, format_settings) +
55
- Ansi.format(JUSTIFICATION * rjust, [*text.settings.bg_color])
56
- end
57
- end
58
- private_constant :TextPainter
59
-
60
- class ContainerPainter
61
- PADDING = DEBUG_SPACING ? 'p' : ' '
62
-
63
- def initialize(container, canvas)
64
- @container = container
65
- @settings = container.settings
66
- @canvas = canvas
67
- end
68
-
69
- def paint(&block)
70
- return if container.num_rows == 0 || container.num_cols == 0
71
- top = canvas.top + settings.margin.top
72
- if settings.auto_margin?
73
- left = canvas.left + (canvas.width - container.preferred_width) / 2
74
- else
75
- left = canvas.left + settings.margin.left
76
- end
77
- if settings.border.top?
78
- yield canvas.stroke(left, top, top_border)
79
- top += 1
80
- end
81
- container.num_rows.times do |row_num|
82
- if row_num > 0 && settings.border.inner_horiz?
83
- yield canvas.stroke(left, top, middle_border)
84
- top += 1
85
- end
86
- (settings.padding.top + container.row_height + settings.padding.bottom).times do
87
- yield canvas.stroke(left, top, content_line)
88
- top += 1
89
- end
90
- end
91
- if settings.border.bottom?
92
- yield canvas.stroke(left, top, bottom_border)
93
- top += 1
94
- end
95
- end
96
-
97
- private
98
-
99
- attr_reader :container, :settings, :canvas
100
-
101
- def line_stroke(left_border, horiz_border, junc_border, right_border)
102
- stroke = ''
103
- stroke += left_border if settings.border.left?
104
- container.num_cols.times do |col_num|
105
- stroke += junc_border if col_num > 0 && settings.border.inner_horiz?
106
- stroke += horiz_border * (container.col_width + settings.padding.left + settings.padding.right)
107
- end
108
- stroke += right_border if settings.border.right?
109
- Ansi.format(stroke, [*settings.border.color, *settings.bg_color])
110
- end
111
-
112
- def top_border
113
- line_stroke(
114
- settings.border.style.top_left,
115
- settings.border.style.top_horiz,
116
- settings.border.style.top_junc,
117
- settings.border.style.top_right
118
- )
119
- end
120
-
121
- def content_line
122
- line_stroke(
123
- settings.border.style.left_vert,
124
- PADDING,
125
- settings.border.style.middle_vert,
126
- settings.border.style.right_vert
127
- )
128
- end
129
-
130
- def middle_border
131
- line_stroke(
132
- settings.border.style.left_junc,
133
- settings.border.style.middle_horiz,
134
- settings.border.style.cross_junc,
135
- settings.border.style.right_junc
136
- )
137
- end
138
-
139
- def bottom_border
140
- line_stroke(
141
- settings.border.style.bottom_left,
142
- settings.border.style.bottom_horiz,
143
- settings.border.style.bottom_junc,
144
- settings.border.style.bottom_right
145
- )
146
- end
147
- end
148
-
149
- class BoxContainer
150
- attr_reader :settings, :num_cols, :num_rows, :col_width, :row_height, :preferred_width
151
-
152
- def initialize(box)
153
- @settings = ContainerSettings.merge(box.settings)
154
- @num_cols = 1
155
- @num_rows = 1
156
- @col_width = box.content_width
157
- @row_height = box.content_height
158
- @preferred_width = box.preferred_width
159
- end
160
- end
161
-
162
- class BoxPainter
163
- def initialize(box, canvas)
164
- @box = box
165
- @canvas = canvas
166
- end
167
-
168
- def paint(&block)
169
- container = BoxContainer.new(box)
170
- ContainerPainter.new(container, canvas).paint(&block)
171
- top = canvas.top + box.settings.margin.top + (box.settings.border.top? ? 1 : 0) + box.settings.padding.top
172
- if box.settings.auto_margin?
173
- margin = (canvas.width - box.preferred_width) / 2
174
- else
175
- margin = box.settings.margin.left
176
- end
177
- left = canvas.left + margin + (box.settings.border.left? ? 1 : 0) + box.settings.padding.left
178
- greedy_width = box.settings.vertical_flow? || box.children.length == 1
179
- children = box.children
180
- children = children.reverse if box.settings.reverse_flow?
181
- children.each do |child|
182
- if greedy_width
183
- width = box.content_width
184
- height = child.preferred_height
185
- else
186
- width = child.preferred_width
187
- height = box.content_height
188
- end
189
- child_canvas = Canvas.new(left, top, width, height)
190
- Painter.paint(child, child_canvas, &block)
191
- if box.settings.horizontal_flow?
192
- left += child.preferred_width
193
- else
194
- top += child.preferred_height
195
- end
196
- end
197
- end
198
-
199
- private
200
-
201
- attr_reader :box, :canvas
202
- end
203
-
204
- class GridContainer
205
- attr_reader :settings, :num_cols, :num_rows, :col_width, :row_height, :preferred_width
206
-
207
- def initialize(grid, num_cols, num_rows)
208
- @settings = ContainerSettings.merge(grid.settings)
209
- @num_cols = num_cols
210
- @num_rows = num_rows
211
- @col_width = grid.col_width
212
- @row_height = grid.row_height
213
- @preferred_width = grid.preferred_width
214
- end
215
- end
216
-
217
- class GridPainter
218
- def initialize(grid, canvas)
219
- @grid = grid
220
- @canvas = canvas
221
- available_width = grid.preferred_width - (grid.settings.margin.left || 0) - (grid.settings.margin.right || 0)
222
- @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))
223
- end
224
-
225
- def paint(&block)
226
- return if grid.children.empty?
227
-
228
- container = GridContainer.new(grid, num_cols, (grid.children.length.to_f / num_cols).ceil)
229
- ContainerPainter.new(container, canvas).paint(&block)
230
-
231
- children = if grid.settings.transpose?
232
- grid.children.length.times.map do |i|
233
- grid.children[(i * num_cols) % grid.children.length + i / (grid.children.length / num_cols)]
234
- end.compact
235
- else
236
- grid.children
237
- end
238
-
239
- top = canvas.top + grid.settings.margin.top + (grid.settings.border.top? ? 1 : 0) + grid.settings.padding.top
240
- if grid.settings.auto_margin?
241
- margin = (canvas.width - grid.preferred_width) / 2
242
- else
243
- margin = grid.settings.margin.left
244
- end
245
- left = canvas.left + margin + (grid.settings.border.left? ? 1 : 0) + grid.settings.padding.left
246
- grid_height = grid.settings.padding.top + grid.row_height + grid.settings.padding.bottom + (grid.settings.border.inner_horiz? ? 1 : 0)
247
- grid_width = grid.settings.padding.left + grid.col_width + grid.settings.padding.right + (grid.settings.border.inner_vert? ? 1 : 0)
248
- children.each_slice(num_cols).each.with_index do |row, row_num|
249
- row_top = top + row_num * grid_height
250
- row.each.with_index do |element, col_num|
251
- col_left = left + col_num * grid_width
252
- child_canvas = Canvas.new(
253
- col_left,
254
- row_top,
255
- element.preferred_width,
256
- element.preferred_height
257
- )
258
- Painter.paint(element, child_canvas, &block)
259
- end
260
- end
261
- end
262
-
263
- private
264
-
265
- attr_reader :grid, :canvas, :num_cols
266
- end
267
-
268
- module Painter
269
- PAINTERS = {
270
- TextElement => TextPainter,
271
- BoxElement => BoxPainter,
272
- GridElement => GridPainter,
273
- }
274
-
275
- def self.paint(element, canvas, &block)
276
- if element.is_a?(Template)
277
- element = BoxElement.from_template(element, canvas.width, canvas.height)
278
- end
279
- PAINTERS[element.class].new(element, canvas).paint(&block)
280
- end
281
- end
282
- end
283
- end