whirled_peas 0.5.0 → 0.9.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 (291) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -0
  4. data/CHANGELOG.md +31 -0
  5. data/README.md +748 -137
  6. data/Rakefile +4 -32
  7. data/bin/easing +33 -0
  8. data/bin/reset_cursor +11 -0
  9. data/bin/screen_test +68 -0
  10. data/examples/graph.rb +54 -0
  11. data/examples/intro.rb +52 -0
  12. data/examples/scrolling.rb +54 -0
  13. data/lib/whirled_peas.rb +4 -12
  14. data/lib/whirled_peas/animator.rb +5 -0
  15. data/lib/whirled_peas/animator/debug_consumer.rb +17 -0
  16. data/lib/whirled_peas/animator/easing.rb +72 -0
  17. data/lib/whirled_peas/animator/frame.rb +5 -0
  18. data/lib/whirled_peas/animator/frameset.rb +33 -0
  19. data/lib/whirled_peas/animator/producer.rb +35 -0
  20. data/lib/whirled_peas/animator/renderer_consumer.rb +31 -0
  21. data/lib/whirled_peas/command.rb +5 -0
  22. data/lib/whirled_peas/command/base.rb +86 -0
  23. data/lib/whirled_peas/command/config_command.rb +44 -0
  24. data/lib/whirled_peas/command/debug.rb +21 -0
  25. data/lib/whirled_peas/command/fonts.rb +22 -0
  26. data/lib/whirled_peas/command/frame_command.rb +34 -0
  27. data/lib/whirled_peas/command/frames.rb +24 -0
  28. data/lib/whirled_peas/command/help.rb +38 -0
  29. data/lib/whirled_peas/command/play.rb +108 -0
  30. data/lib/whirled_peas/command/record.rb +57 -0
  31. data/lib/whirled_peas/command/still.rb +29 -0
  32. data/lib/whirled_peas/command_line.rb +22 -220
  33. data/lib/whirled_peas/config.rb +56 -6
  34. data/lib/whirled_peas/device.rb +5 -0
  35. data/lib/whirled_peas/device/null_device.rb +8 -0
  36. data/lib/whirled_peas/device/output_file.rb +19 -0
  37. data/lib/whirled_peas/device/screen.rb +26 -0
  38. data/lib/whirled_peas/graphics.rb +14 -0
  39. data/lib/whirled_peas/graphics/box_painter.rb +48 -47
  40. data/lib/whirled_peas/graphics/canvas.rb +22 -11
  41. data/lib/whirled_peas/graphics/composer.rb +98 -0
  42. data/lib/whirled_peas/graphics/container_coords.rb +24 -13
  43. data/lib/whirled_peas/graphics/container_dimensions.rb +37 -9
  44. data/lib/whirled_peas/graphics/container_painter.rb +283 -36
  45. data/lib/whirled_peas/graphics/content_dimensions.rb +19 -0
  46. data/lib/whirled_peas/graphics/content_painter.rb +20 -0
  47. data/lib/whirled_peas/graphics/debugger.rb +10 -1
  48. data/lib/whirled_peas/graphics/graph_dimensions.rb +12 -0
  49. data/lib/whirled_peas/graphics/graph_painter.rb +95 -0
  50. data/lib/whirled_peas/graphics/grid_painter.rb +25 -15
  51. data/lib/whirled_peas/graphics/mock_screen.rb +1 -1
  52. data/lib/whirled_peas/graphics/painter.rb +15 -6
  53. data/lib/whirled_peas/graphics/renderer.rb +13 -30
  54. data/lib/whirled_peas/graphics/text_painter.rb +11 -13
  55. data/lib/whirled_peas/settings/alignment.rb +24 -0
  56. data/lib/whirled_peas/settings/bg_color.rb +2 -0
  57. data/lib/whirled_peas/settings/border.rb +16 -16
  58. data/lib/whirled_peas/settings/color.rb +13 -14
  59. data/lib/whirled_peas/settings/container_settings.rb +133 -40
  60. data/lib/whirled_peas/settings/debugger.rb +9 -0
  61. data/lib/whirled_peas/settings/display_flow.rb +13 -11
  62. data/lib/whirled_peas/settings/element_settings.rb +8 -3
  63. data/lib/whirled_peas/settings/graph_settings.rb +21 -0
  64. data/lib/whirled_peas/settings/grid_settings.rb +15 -0
  65. data/lib/whirled_peas/settings/scrollbar.rb +15 -0
  66. data/lib/whirled_peas/settings/sizing.rb +19 -0
  67. data/lib/whirled_peas/settings/spacing.rb +34 -0
  68. data/lib/whirled_peas/settings/text_color.rb +2 -0
  69. data/lib/whirled_peas/settings/vert_alignment.rb +24 -0
  70. data/lib/whirled_peas/utils/ansi.rb +13 -0
  71. data/lib/whirled_peas/utils/file_handler.rb +57 -0
  72. data/lib/whirled_peas/utils/title_font.rb +1 -1
  73. data/lib/whirled_peas/version.rb +1 -1
  74. data/screen_test/{rendered/elements → elements}/box.frame +1 -1
  75. data/screen_test/{rendered/elements → elements}/box.rb +0 -0
  76. data/screen_test/elements/graph.frame +1 -0
  77. data/screen_test/elements/graph.rb +12 -0
  78. data/screen_test/elements/grid.frame +1 -0
  79. data/screen_test/{rendered/elements → elements}/grid.rb +0 -0
  80. data/screen_test/elements/screen_overflow_x.frame +1 -0
  81. data/screen_test/{rendered/elements/screen_overflow.rb → elements/screen_overflow_x.rb} +0 -0
  82. data/screen_test/elements/screen_overflow_y.frame +1 -0
  83. data/screen_test/elements/screen_overflow_y.rb +9 -0
  84. data/screen_test/elements/text.frame +1 -0
  85. data/screen_test/{rendered/elements → elements}/text.rb +0 -0
  86. data/screen_test/elements/text_multiline.frame +1 -0
  87. data/screen_test/{rendered/elements → elements}/text_multiline.rb +0 -0
  88. data/screen_test/settings/align/box_around.frame +1 -0
  89. data/screen_test/settings/align/box_around.rb +16 -0
  90. data/screen_test/settings/align/box_between.frame +1 -0
  91. data/screen_test/settings/align/box_between.rb +16 -0
  92. data/screen_test/settings/align/box_center.frame +1 -0
  93. data/screen_test/{rendered/settings/align/box.rb → settings/align/box_center.rb} +5 -4
  94. data/screen_test/settings/align/box_default.frame +1 -0
  95. data/screen_test/{rendered/settings/align/grid.rb → settings/align/box_default.rb} +6 -6
  96. data/screen_test/settings/align/box_evenly.frame +1 -0
  97. data/screen_test/settings/align/box_evenly.rb +16 -0
  98. data/screen_test/settings/align/box_left.frame +1 -0
  99. data/screen_test/settings/align/box_left.rb +21 -0
  100. data/screen_test/settings/align/box_right.frame +1 -0
  101. data/screen_test/settings/align/box_right.rb +21 -0
  102. data/screen_test/settings/align/children_center.frame +1 -0
  103. data/screen_test/settings/align/children_center.rb +15 -0
  104. data/screen_test/settings/align/children_left.frame +1 -0
  105. data/screen_test/settings/align/children_left.rb +15 -0
  106. data/screen_test/settings/align/children_right.frame +1 -0
  107. data/screen_test/settings/align/children_right.rb +15 -0
  108. data/screen_test/settings/align/grid_center.frame +1 -0
  109. data/screen_test/settings/align/grid_center.rb +18 -0
  110. data/screen_test/settings/align/grid_default.frame +1 -0
  111. data/screen_test/settings/align/grid_default.rb +17 -0
  112. data/screen_test/settings/align/grid_left.frame +1 -0
  113. data/screen_test/settings/align/grid_left.rb +18 -0
  114. data/screen_test/settings/align/grid_right.frame +1 -0
  115. data/screen_test/settings/align/grid_right.rb +18 -0
  116. data/screen_test/settings/ansi/bold.frame +1 -0
  117. data/screen_test/{rendered/settings/width/overflow_box_t2b.rb → settings/ansi/bold.rb} +4 -5
  118. data/screen_test/{rendered/settings → settings}/ansi/color.frame +1 -1
  119. data/screen_test/{rendered/settings → settings}/ansi/color.rb +0 -0
  120. data/screen_test/settings/ansi/underline.frame +1 -0
  121. data/screen_test/{rendered/settings → settings}/ansi/underline.rb +4 -5
  122. data/screen_test/{rendered/settings → settings}/border.frame +1 -1
  123. data/screen_test/{rendered/settings → settings}/border.rb +1 -1
  124. data/screen_test/settings/flow/box_b2t.frame +1 -0
  125. data/screen_test/settings/flow/box_b2t.rb +26 -0
  126. data/screen_test/settings/flow/box_l2r.frame +1 -0
  127. data/screen_test/settings/flow/box_l2r.rb +26 -0
  128. data/screen_test/settings/flow/box_r2l.frame +1 -0
  129. data/screen_test/settings/flow/box_r2l.rb +26 -0
  130. data/screen_test/settings/flow/box_t2b.frame +1 -0
  131. data/screen_test/settings/flow/box_t2b.rb +26 -0
  132. data/screen_test/settings/flow/grid_b2t.frame +1 -0
  133. data/screen_test/settings/flow/grid_b2t.rb +14 -0
  134. data/screen_test/settings/flow/grid_l2r.frame +1 -0
  135. data/screen_test/{rendered/settings/margin.rb → settings/flow/grid_l2r.rb} +4 -4
  136. data/screen_test/settings/flow/grid_r2l.frame +1 -0
  137. data/screen_test/settings/flow/grid_r2l.rb +14 -0
  138. data/screen_test/settings/flow/grid_t2b.frame +1 -0
  139. data/screen_test/settings/flow/grid_t2b.rb +14 -0
  140. data/screen_test/settings/height/box.frame +1 -0
  141. data/screen_test/{rendered/settings → settings}/height/box.rb +0 -0
  142. data/screen_test/settings/height/box_border_sizing.frame +1 -0
  143. data/screen_test/settings/height/box_border_sizing.rb +15 -0
  144. data/screen_test/settings/height/grid.frame +1 -0
  145. data/screen_test/{rendered/settings → settings}/height/grid.rb +0 -0
  146. data/screen_test/settings/height/overflow_box.frame +1 -0
  147. data/screen_test/settings/height/overflow_box.rb +13 -0
  148. data/screen_test/settings/height/overflow_box_l2r.frame +1 -0
  149. data/screen_test/settings/height/overflow_box_l2r.rb +17 -0
  150. data/screen_test/settings/height/overflow_box_t2b.frame +1 -0
  151. data/screen_test/settings/height/overflow_box_t2b.rb +16 -0
  152. data/screen_test/settings/height/overflow_grid.frame +1 -0
  153. data/screen_test/{rendered/settings → settings}/height/overflow_grid.rb +0 -0
  154. data/screen_test/settings/margin.frame +1 -0
  155. data/screen_test/settings/margin.rb +16 -0
  156. data/screen_test/settings/padding.frame +1 -0
  157. data/screen_test/settings/padding.rb +13 -0
  158. data/screen_test/settings/position/box_left.frame +1 -0
  159. data/screen_test/{rendered/settings → settings}/position/box_left.rb +2 -2
  160. data/screen_test/settings/position/box_left_negative.frame +1 -0
  161. data/screen_test/{rendered/settings → settings}/position/box_left_negative.rb +2 -2
  162. data/screen_test/settings/position/box_top.frame +1 -0
  163. data/screen_test/{rendered/settings → settings}/position/box_top.rb +1 -1
  164. data/screen_test/settings/position/box_top_negative.frame +1 -0
  165. data/screen_test/{rendered/settings → settings}/position/box_top_negative.rb +2 -2
  166. data/screen_test/settings/position/grid_left.frame +1 -0
  167. data/screen_test/{rendered/settings → settings}/position/grid_left.rb +1 -1
  168. data/screen_test/settings/position/grid_left_negative.frame +1 -0
  169. data/screen_test/{rendered/settings → settings}/position/grid_left_negative.rb +1 -1
  170. data/screen_test/settings/position/grid_top.frame +1 -0
  171. data/screen_test/{rendered/settings → settings}/position/grid_top.rb +1 -1
  172. data/screen_test/settings/position/grid_top_negative.frame +1 -0
  173. data/screen_test/{rendered/settings → settings}/position/grid_top_negative.rb +1 -1
  174. data/screen_test/settings/scroll/horiz_box.frame +1 -0
  175. data/screen_test/settings/scroll/horiz_box.rb +17 -0
  176. data/screen_test/settings/scroll/horiz_box_align_center.rb +18 -0
  177. data/screen_test/settings/scroll/horiz_box_align_right.rb +18 -0
  178. data/screen_test/settings/scroll/vert_box.frame +1 -0
  179. data/screen_test/settings/scroll/vert_box.rb +20 -0
  180. data/screen_test/settings/title_font.frame +1 -0
  181. data/screen_test/{rendered/settings → settings}/title_font.rb +1 -1
  182. data/screen_test/settings/valign/box_around.frame +1 -0
  183. data/screen_test/settings/valign/box_around.rb +17 -0
  184. data/screen_test/settings/valign/box_between.frame +1 -0
  185. data/screen_test/settings/valign/box_between.rb +17 -0
  186. data/screen_test/settings/valign/box_bottom.frame +1 -0
  187. data/screen_test/settings/valign/box_bottom.rb +17 -0
  188. data/screen_test/settings/valign/box_default.frame +1 -0
  189. data/screen_test/settings/valign/box_default.rb +16 -0
  190. data/screen_test/settings/valign/box_evenly.frame +1 -0
  191. data/screen_test/settings/valign/box_evenly.rb +17 -0
  192. data/screen_test/settings/valign/box_middle.frame +1 -0
  193. data/screen_test/settings/valign/box_middle.rb +17 -0
  194. data/screen_test/settings/valign/box_top.frame +1 -0
  195. data/screen_test/settings/valign/box_top.rb +17 -0
  196. data/screen_test/settings/valign/grid_bottom.frame +1 -0
  197. data/screen_test/settings/valign/grid_bottom.rb +15 -0
  198. data/screen_test/settings/valign/grid_default.frame +1 -0
  199. data/screen_test/settings/valign/grid_default.rb +14 -0
  200. data/screen_test/settings/valign/grid_middle.frame +1 -0
  201. data/screen_test/settings/valign/grid_middle.rb +15 -0
  202. data/screen_test/settings/valign/grid_top.frame +1 -0
  203. data/screen_test/settings/valign/grid_top.rb +15 -0
  204. data/screen_test/settings/width/box_border_sizing.frame +1 -0
  205. data/screen_test/settings/width/box_border_sizing.rb +15 -0
  206. data/screen_test/settings/width/box_content.frame +1 -0
  207. data/screen_test/settings/width/box_content.rb +15 -0
  208. data/screen_test/settings/width/box_default.frame +1 -0
  209. data/screen_test/{rendered/settings/width/box.rb → settings/width/box_default.rb} +3 -2
  210. data/screen_test/settings/width/grid.frame +1 -0
  211. data/screen_test/{rendered/settings → settings}/width/grid.rb +2 -2
  212. data/screen_test/settings/width/overflow_align_center.frame +1 -0
  213. data/screen_test/settings/width/overflow_align_center.rb +14 -0
  214. data/screen_test/settings/width/overflow_align_right.frame +1 -0
  215. data/screen_test/settings/width/overflow_align_right.rb +14 -0
  216. data/screen_test/settings/width/overflow_box.frame +1 -0
  217. data/screen_test/settings/width/overflow_box.rb +13 -0
  218. data/screen_test/settings/width/overflow_box_l2r.frame +1 -0
  219. data/screen_test/settings/width/overflow_box_l2r.rb +16 -0
  220. data/screen_test/settings/width/overflow_box_t2b.frame +1 -0
  221. data/screen_test/settings/width/overflow_box_t2b.rb +17 -0
  222. data/screen_test/settings/width/overflow_grid.frame +1 -0
  223. data/screen_test/{rendered/settings → settings}/width/overflow_grid.rb +0 -0
  224. data/tools/whirled_peas/tools/screen_tester.rb +285 -0
  225. metadata +192 -98
  226. data/bin/debug +0 -35
  227. data/lib/whirled_peas/debugger.rb +0 -80
  228. data/lib/whirled_peas/frame.rb +0 -6
  229. data/lib/whirled_peas/frame/consumer.rb +0 -30
  230. data/lib/whirled_peas/frame/debug_consumer.rb +0 -30
  231. data/lib/whirled_peas/frame/event_loop.rb +0 -90
  232. data/lib/whirled_peas/frame/producer.rb +0 -67
  233. data/lib/whirled_peas/graphics/screen.rb +0 -65
  234. data/lib/whirled_peas/graphics/text_dimensions.rb +0 -15
  235. data/lib/whirled_peas/settings/text_align.rb +0 -19
  236. data/lib/whirled_peas/template.rb +0 -5
  237. data/lib/whirled_peas/template/box_element.rb +0 -8
  238. data/lib/whirled_peas/template/composer.rb +0 -68
  239. data/lib/whirled_peas/template/container.rb +0 -28
  240. data/lib/whirled_peas/template/debugger.rb +0 -34
  241. data/lib/whirled_peas/template/element.rb +0 -13
  242. data/lib/whirled_peas/template/grid_element.rb +0 -8
  243. data/lib/whirled_peas/template/text_element.rb +0 -24
  244. data/screen_test/rendered/elements/grid.frame +0 -1
  245. data/screen_test/rendered/elements/text.frame +0 -1
  246. data/screen_test/rendered/elements/text_multiline.frame +0 -1
  247. data/screen_test/rendered/settings/align/box.frame +0 -1
  248. data/screen_test/rendered/settings/align/children_center.frame +0 -1
  249. data/screen_test/rendered/settings/align/children_center.rb +0 -13
  250. data/screen_test/rendered/settings/align/children_left.frame +0 -1
  251. data/screen_test/rendered/settings/align/children_left.rb +0 -13
  252. data/screen_test/rendered/settings/align/children_right.frame +0 -1
  253. data/screen_test/rendered/settings/align/children_right.rb +0 -13
  254. data/screen_test/rendered/settings/align/grid.frame +0 -1
  255. data/screen_test/rendered/settings/ansi/bold.frame +0 -1
  256. data/screen_test/rendered/settings/ansi/bold.rb +0 -15
  257. data/screen_test/rendered/settings/ansi/underline.frame +0 -1
  258. data/screen_test/rendered/settings/flow/l2r.frame +0 -1
  259. data/screen_test/rendered/settings/flow/l2r.rb +0 -24
  260. data/screen_test/rendered/settings/flow/t2b.frame +0 -1
  261. data/screen_test/rendered/settings/flow/t2b.rb +0 -24
  262. data/screen_test/rendered/settings/height/box.frame +0 -1
  263. data/screen_test/rendered/settings/height/grid.frame +0 -1
  264. data/screen_test/rendered/settings/height/overflow_box.frame +0 -1
  265. data/screen_test/rendered/settings/height/overflow_box.rb +0 -13
  266. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +0 -1
  267. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +0 -15
  268. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +0 -1
  269. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +0 -14
  270. data/screen_test/rendered/settings/height/overflow_grid.frame +0 -1
  271. data/screen_test/rendered/settings/margin.frame +0 -1
  272. data/screen_test/rendered/settings/padding.frame +0 -1
  273. data/screen_test/rendered/settings/padding.rb +0 -11
  274. data/screen_test/rendered/settings/position/box_left.frame +0 -1
  275. data/screen_test/rendered/settings/position/box_left_negative.frame +0 -1
  276. data/screen_test/rendered/settings/position/box_top.frame +0 -1
  277. data/screen_test/rendered/settings/position/box_top_negative.frame +0 -1
  278. data/screen_test/rendered/settings/position/grid_left.frame +0 -1
  279. data/screen_test/rendered/settings/position/grid_left_negative.frame +0 -1
  280. data/screen_test/rendered/settings/position/grid_top.frame +0 -1
  281. data/screen_test/rendered/settings/position/grid_top_negative.frame +0 -1
  282. data/screen_test/rendered/settings/title_font.frame +0 -1
  283. data/screen_test/rendered/settings/width/box.frame +0 -1
  284. data/screen_test/rendered/settings/width/grid.frame +0 -1
  285. data/screen_test/rendered/settings/width/overflow_box.frame +0 -1
  286. data/screen_test/rendered/settings/width/overflow_box.rb +0 -11
  287. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +0 -1
  288. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +0 -14
  289. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +0 -1
  290. data/screen_test/rendered/settings/width/overflow_grid.frame +0 -1
  291. data/screen_test/screen_tester.rb +0 -191
@@ -5,45 +5,75 @@ require_relative 'painter'
5
5
 
6
6
  module WhirledPeas
7
7
  module Graphics
8
+ # Abstract Painter for containers. Containers (as the name implies) contain other child
9
+ # elements and must delegate painting of the children to the children themselves.
8
10
  class ContainerPainter < Painter
9
11
  PADDING = ' '
10
12
 
11
- def initialize(element, settings, name)
13
+ def initialize(name, settings)
12
14
  super
13
15
  @children = []
14
16
  end
15
17
 
16
- def paint(canvas, &block)
18
+ # Paint the common attributes of containers (e.g. border and background color). Any
19
+ # class that inherits from this one should call `super` at the start of its #paint
20
+ # method, before painting its children.
21
+ def paint(canvas, left, top, &block)
17
22
  return unless canvas.writable?
18
- has_inner_vert = dimensions.num_cols > 1 && settings.border.inner_vert?
19
- has_inner_horiz = dimensions.num_rows > 1 && settings.border.inner_horiz?
20
- has_border = settings.border.outer? || has_inner_vert || has_inner_horiz
21
- return unless settings.bg_color || has_border
22
- stroke_left = coords(canvas).border_left
23
- stroke_top = coords(canvas).border_top
23
+ return unless needs_printing?
24
+ canvas_coords = coords(left, top)
25
+
26
+ # Paint the border, background color, and scrollbar starting from the top left
27
+ # border position, moving down row by row until we reach the bottom border
28
+ # position
29
+ stroke_left = canvas_coords.border_left
30
+ stroke_top = canvas_coords.border_top
31
+
32
+ # All strokes will have the same formatting options
24
33
  formatting = [*settings.border.color, *settings.bg_color]
34
+
35
+ # Paint the top border if the settings call for it
25
36
  if settings.border.top?
26
- canvas.stroke(stroke_left, stroke_top, top_border_stroke, formatting, &block)
37
+ canvas.stroke(stroke_left, stroke_top, top_border_stroke(canvas_coords), formatting, &block)
27
38
  stroke_top += 1
28
39
  end
29
- content_line = content_line_stroke
30
- middle_border = dimensions.num_rows > 1 ? middle_border_stroke : ''
40
+ # Precalculate the middle border container grids with more than 1 row
41
+ middle_border = dimensions.num_rows > 1 ? middle_border_stroke(canvas_coords) : ''
42
+
43
+ # Paint each grid row by row
31
44
  dimensions.num_rows.times do |row_num|
45
+ # In a grid with N rows, we will need to paint N - 1 inner horizontal borders.
46
+ # This code treats the inner horizontal border as the top of each row except for
47
+ # the first one.
32
48
  if row_num > 0 && settings.border.inner_horiz?
33
49
  canvas.stroke(stroke_left, stroke_top, middle_border, formatting, &block)
34
50
  stroke_top += 1
35
51
  end
36
- coords(canvas).grid_height(false).times do
37
- canvas.stroke(stroke_left, stroke_top, content_line, formatting, &block)
52
+
53
+ # Paint the interior of each row (horizontal borders, veritical scroll bar and
54
+ # background color for the padding and content area)
55
+ canvas_coords.inner_grid_height.times do |row_within_cell|
56
+ canvas.stroke(stroke_left, stroke_top, content_line_stroke(canvas_coords, row_within_cell), formatting, &block)
57
+ stroke_top += 1
58
+ end
59
+
60
+ # Paint the horizontal scroll bar is the settings call for it
61
+ if settings.scrollbar.horiz?
62
+ canvas.stroke(stroke_left, stroke_top, bottom_scroll_stroke(canvas_coords), formatting, &block)
38
63
  stroke_top += 1
39
64
  end
40
65
  end
66
+
67
+ # Paint the bottom border if the settings call for it
41
68
  if settings.border.bottom?
42
- canvas.stroke(stroke_left, stroke_top, bottom_border_stroke, formatting, &block)
69
+ canvas.stroke(stroke_left, stroke_top, bottom_border_stroke(canvas_coords), formatting, &block)
43
70
  stroke_top += 1
44
71
  end
45
72
  end
46
73
 
74
+ # Tightly manage access to the children (rather than simply exposing the underlying
75
+ # array). This allows subclasses to easily modify behavior based on that element's
76
+ # specific settings.
47
77
  def add_child(child)
48
78
  children << child
49
79
  end
@@ -52,6 +82,10 @@ module WhirledPeas
52
82
  children.length
53
83
  end
54
84
 
85
+ def children?
86
+ num_children > 0
87
+ end
88
+
55
89
  def each_child(&block)
56
90
  children.each(&block)
57
91
  end
@@ -60,55 +94,268 @@ module WhirledPeas
60
94
 
61
95
  attr_reader :children
62
96
 
63
- def coords(canvas)
64
- ContainerCoords.new(canvas, dimensions, settings)
97
+ # Determine if there is anything to print for the container (this does not accont for
98
+ # children, just the border, scrollbar, and background color)
99
+ def needs_printing?
100
+ return true if settings.bg_color
101
+ return true if settings.border.outer?
102
+ return true if dimensions.num_cols > 1 && settings.border.inner_vert?
103
+ return true if dimensions.num_rows > 1 && settings.border.inner_horiz?
104
+ settings.scrollbar.horiz? || settings.scrollbar.vert?
65
105
  end
66
106
 
67
- def line_stroke(left_border, horiz_border, junc_border, right_border)
107
+ # Return an object that allows easy access to important coordinates within the container,
108
+ # e.g. the left position where the left border is printed
109
+ def coords(left, top)
110
+ ContainerCoords.new(dimensions, settings, left, top)
111
+ end
112
+
113
+ # @return [Array<Integer>] a two-item array, the first being the amount of horizontal
114
+ # spacing to paint *before the first* child and the second being the amount of spacing
115
+ # to paint *between each* child
116
+ def horiz_justify_offset(containing_width)
117
+ if settings.align_center?
118
+ [(dimensions.content_width - containing_width) / 2, 0]
119
+ elsif settings.align_right?
120
+ [dimensions.content_width - containing_width, 0]
121
+ elsif settings.align_between?
122
+ return [0, 0] if num_children == 1
123
+ [0, (dimensions.content_width - containing_width) / (num_children - 1)]
124
+ elsif settings.align_around?
125
+ full_spacing = (dimensions.content_width - containing_width) / num_children
126
+ [full_spacing / 2, full_spacing]
127
+ elsif settings.align_evenly?
128
+ spacing = (dimensions.content_width - containing_width) / (num_children + 1)
129
+ [spacing, spacing]
130
+ else
131
+ [0, 0]
132
+ end
133
+ end
134
+
135
+ # @return [Array<Integer>] a two-item array, the first being the amount of vertical
136
+ # spacing to paint *above the first* child and the second being the amount of spacing
137
+ # to paint *between each* child
138
+ def vert_justify_offset(containing_height)
139
+ if settings.valign_middle?
140
+ [(dimensions.content_height - containing_height) / 2, 0]
141
+ elsif settings.valign_bottom?
142
+ [dimensions.content_height - containing_height, 0]
143
+ elsif settings.valign_between?
144
+ return [0, 0] if num_children == 1
145
+ [0, (dimensions.content_height - containing_height) / (num_children - 1)]
146
+ elsif settings.valign_around?
147
+ full_spacing = (dimensions.content_height - containing_height) / num_children
148
+ [full_spacing / 2, full_spacing]
149
+ elsif settings.valign_evenly?
150
+ spacing = (dimensions.content_height - containing_height) / (num_children + 1)
151
+ [spacing, spacing]
152
+ else
153
+ [0, 0]
154
+ end
155
+ end
156
+
157
+ # Return a stroke for one line of the container
158
+ #
159
+ # @param left_border [String] the character to print as the first character if there
160
+ # is a left border
161
+ # @param junc_border [String] the character to print as the junction between two grid
162
+ # columns if there is an inner vertical border
163
+ # @param right_border [String] the character to print as the last character if there
164
+ # is a right border
165
+ # @block [String] the block should yield a string that represents the interior
166
+ # (including padding) of a grid cell
167
+ def line_stroke(left_border, junc_border, right_border, &block)
68
168
  stroke = ''
69
169
  stroke += left_border if settings.border.left?
70
170
  dimensions.num_cols.times do |col_num|
71
171
  stroke += junc_border if col_num > 0 && settings.border.inner_vert?
72
- stroke += horiz_border * (dimensions.content_width + settings.padding.left + settings.padding.right)
172
+ stroke += yield
73
173
  end
74
174
  stroke += right_border if settings.border.right?
75
175
  stroke
76
176
  end
77
177
 
78
- def top_border_stroke
178
+ # Return the stroke for the top border
179
+ def top_border_stroke(canvas_coords)
79
180
  line_stroke(
80
181
  settings.border.style.top_left,
81
- settings.border.style.top_horiz,
82
182
  settings.border.style.top_junc,
83
183
  settings.border.style.top_right
84
- )
85
- end
86
-
87
- def content_line_stroke
88
- line_stroke(
89
- settings.border.style.left_vert,
90
- PADDING,
91
- settings.border.style.middle_vert,
92
- settings.border.style.right_vert
93
- )
184
+ ) do
185
+ settings.border.style.top_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
186
+ end
94
187
  end
95
188
 
96
- def middle_border_stroke
189
+ # Return the stroke for an inner horizontal border
190
+ def middle_border_stroke(canvas_coords)
97
191
  line_stroke(
98
192
  settings.border.style.left_junc,
99
- settings.border.style.middle_horiz,
100
193
  settings.border.style.cross_junc,
101
194
  settings.border.style.right_junc
102
- )
195
+ ) do
196
+ settings.border.style.middle_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
197
+ end
103
198
  end
104
199
 
105
- def bottom_border_stroke
200
+ # Return the stroke for the bottom border
201
+ def bottom_border_stroke(canvas_coords)
106
202
  line_stroke(
107
203
  settings.border.style.bottom_left,
108
- settings.border.style.bottom_horiz,
109
204
  settings.border.style.bottom_junc,
110
205
  settings.border.style.bottom_right
111
- )
206
+ ) do
207
+ settings.border.style.bottom_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
208
+ end
209
+ end
210
+
211
+ # Return the stroke for a grid row between any borders
212
+ def content_line_stroke(canvas_coords, row_within_cell)
213
+ line_stroke(
214
+ settings.border.style.left_vert,
215
+ settings.border.style.middle_vert,
216
+ settings.border.style.right_vert,
217
+ ) do
218
+ if settings.scrollbar.vert?
219
+ if dimensions.children_height <= canvas_coords.grid_height || children.first.settings.position.top > 0
220
+ scrollbar_char = GUTTER
221
+ else
222
+ scrollbar_char = vert_scroll_char(
223
+ dimensions.children_height + dimensions.padding_height,
224
+ canvas_coords.inner_grid_height,
225
+ -children.first.settings.position.top,
226
+ row_within_cell
227
+ )
228
+ end
229
+ PADDING * canvas_coords.inner_grid_width + scrollbar_char
230
+ else
231
+ PADDING * canvas_coords.inner_grid_width
232
+ end
233
+ end
234
+ end
235
+
236
+ # Return the stroke for the horizontal scroll bar
237
+ def bottom_scroll_stroke(canvas_coords)
238
+ line_stroke(
239
+ settings.border.style.left_vert,
240
+ settings.border.style.middle_vert,
241
+ settings.border.style.right_vert,
242
+ ) do
243
+ canvas_coords.inner_grid_width.times.map do |col_within_cell|
244
+ horiz_scroll_char(
245
+ dimensions.children_width + dimensions.padding_width,
246
+ canvas_coords.inner_grid_width,
247
+ -children.first.settings.position.left,
248
+ col_within_cell
249
+ )
250
+ end.join
251
+ end
252
+ end
253
+
254
+ # Contants to paint scrollbars
255
+ GUTTER = ' '
256
+ HORIZONTAL = %w[▗ ▄ ▖]
257
+ VERTICAL = %w[
258
+
259
+
260
+
261
+ ]
262
+
263
+ # Determine the character to paint the horizontal scroll bar with for the given column
264
+ #
265
+ # @see #scroll_char for more details
266
+ def horiz_scroll_char(col_count, viewable_col_count, first_visible_col, curr_col)
267
+ scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
268
+ end
269
+
270
+ # Determine the character to paint the vertical scroll bar with for the given row
271
+ #
272
+ # @see #scroll_char for more details
273
+ def vert_scroll_char(row_count, viewable_row_count, first_visible_row, curr_row)
274
+ scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
275
+ end
276
+
277
+ private
278
+
279
+ # Determine which character to paint a for a scrollbar
280
+ #
281
+ # @param total_count [Integer] total number of rows/columns in the content
282
+ # @param viewable_count [Integer] number of rows/columns visible in the viewport
283
+ # @param first_visible [Integer] zero-based index of the first row/column that is visible
284
+ # in the viewport
285
+ # @param curr [Integer] zero-based index of the row/column (relative to the first visible
286
+ # row/column) that the painted character is being requested for
287
+ # @param chars [Array<String>] an array with three 1-character strings, the frist is the
288
+ # "second half" scrollbar character, the second is the "full" scrollbar character, and
289
+ # the third is the "first half" scrollbar character.
290
+ def scroll_char(total_count, viewable_count, first_visible, curr, chars)
291
+ return GUTTER unless total_count > 0 && viewable_count > 0
292
+ # The scroll handle has the exact same relative size and position in the scroll gutter
293
+ # that the viewable content has in the total content area. For example, a content area
294
+ # that is 50 columns wide with a view port that is 20 columns wide might look like
295
+ #
296
+ # +---------1-----****2*********3******---4---------+
297
+ # | * * |
298
+ # | hidden * viewable * hidden |
299
+ # | * * |
300
+ # +---------1-----****2*********3******---4---------+
301
+ #
302
+ # The scoll gutter, would look like
303
+ #
304
+ # |......********.....|
305
+ #
306
+ # Scrolling all the way to the right results in
307
+ #
308
+ # +---------1---------2---------3*********4*********+
309
+ # | * *
310
+ # | hidden * viewable *
311
+ # | * *
312
+ # +---------1---------2---------3*********4*********+
313
+ # |...........********|
314
+ #
315
+ # Returning to the first example, we can match up the arguments to this method to the
316
+ # diagram
317
+ #
318
+ # total_count = 50
319
+ # |<----------------------------------------------->|
320
+ # | |
321
+ # | veiwable_count = 20 |
322
+ # | |<----------------->| |
323
+ # ↓ ↓ ↓ ↓
324
+ # +---------1-----****2*********3******---4---------+
325
+ # | * * |
326
+ # | hidden * viewable * hidden |
327
+ # | * * |
328
+ # +---------1-----****2*********3******---4---------+
329
+ # |......****?***.....|
330
+ # ↑ ↑
331
+ # first_visible = 16 |
332
+ # curr = 11
333
+
334
+ # The first task of determining how much of the handle is visible in a row/column is to
335
+ # calculate the range (as a precentage of the total) of viewable items
336
+ viewable_start = first_visible.to_f / total_count
337
+ viewable_end = (first_visible + viewable_count).to_f / total_count
338
+
339
+ # Always use the same length for the scroll bar so it does not give an inchworm effect
340
+ # as it scrolls along.
341
+ #
342
+ # Also, double the value now to get granularity for half width
343
+ # scrollbar characters.
344
+ scrollbar_length = ((2 * viewable_count ** 2).to_f / total_count).ceil
345
+ scrollbar_start = ((2 * first_visible * viewable_count).to_f / total_count).floor
346
+
347
+ first_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr)
348
+ second_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr + 1)
349
+
350
+ if first_half && second_half
351
+ chars[1]
352
+ elsif first_half
353
+ chars[2]
354
+ elsif second_half
355
+ chars[0]
356
+ else
357
+ GUTTER
358
+ end
112
359
  end
113
360
  end
114
361
  private_constant :ContainerPainter
@@ -0,0 +1,19 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ class ContentDimensions
4
+ attr_reader :outer_width, :outer_height
5
+
6
+ def initialize(settings, content)
7
+ if settings.width
8
+ @outer_width = settings.width
9
+ else
10
+ @outer_width = 0
11
+ content.each do |line|
12
+ @outer_width = line.length if line.length > @outer_width
13
+ end
14
+ end
15
+ @outer_height = settings.height || content.length
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'painter'
2
+
3
+ module WhirledPeas
4
+ module Graphics
5
+ class ContentPainter < Painter
6
+ attr_accessor :content
7
+
8
+ def dimensions
9
+ ContentDimensions.new(settings, content_lines)
10
+ end
11
+
12
+ private
13
+
14
+ def content_lines
15
+ raise NotImplementedError, "#{self.class} must implement #content_lines"
16
+ end
17
+ end
18
+ private_constant :ContentPainter
19
+ end
20
+ end
@@ -17,7 +17,16 @@ module WhirledPeas
17
17
  info << "#{indent + ' '}- Dimensions(#{dimensions})"
18
18
  info << "#{indent + ' '}- Settings"
19
19
  info << Settings::Debugger.new(painter.settings).debug(indent + ' ')
20
- if painter.is_a?(ContainerPainter)
20
+ if painter.is_a?(TextPainter)
21
+ content = if painter.content.length > 1
22
+ '<multiline>'
23
+ elsif painter.content.first.length > 12
24
+ "#{painter.content.first[0..9]}...".inspect
25
+ else
26
+ painter.content.first.inspect
27
+ end
28
+ info << "#{indent + ' '}- Content(value=#{content})"
29
+ elsif painter.is_a?(ContainerPainter)
21
30
  info << "#{indent + ' '}- Children"
22
31
  info += painter.each_child.map { |c| Debugger.new(c).debug(indent + ' ') }
23
32
  end
@@ -0,0 +1,12 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ class TextDimensions
4
+ attr_reader :outer_width, :outer_height
5
+
6
+ def initialize(settings, content)
7
+ @outer_width = settings.width
8
+ @outer_height = settings.height
9
+ end
10
+ end
11
+ end
12
+ end