whirled_peas 0.6.0 → 0.9.1

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/CHANGELOG.md +30 -1
  4. data/README.md +710 -136
  5. data/Rakefile +3 -32
  6. data/bin/easing +33 -0
  7. data/bin/reset_cursor +11 -0
  8. data/bin/screen_test +68 -0
  9. data/examples/graph.rb +54 -0
  10. data/examples/intro.rb +3 -3
  11. data/examples/scrolling.rb +5 -4
  12. data/lib/whirled_peas.rb +2 -4
  13. data/lib/whirled_peas/animator.rb +5 -0
  14. data/lib/whirled_peas/animator/debug_consumer.rb +17 -0
  15. data/lib/whirled_peas/animator/easing.rb +72 -0
  16. data/lib/whirled_peas/animator/frame.rb +5 -0
  17. data/lib/whirled_peas/animator/frameset.rb +33 -0
  18. data/lib/whirled_peas/animator/producer.rb +35 -0
  19. data/lib/whirled_peas/animator/renderer_consumer.rb +31 -0
  20. data/lib/whirled_peas/command.rb +5 -0
  21. data/lib/whirled_peas/command/base.rb +86 -0
  22. data/lib/whirled_peas/command/config_command.rb +44 -0
  23. data/lib/whirled_peas/command/debug.rb +21 -0
  24. data/lib/whirled_peas/command/fonts.rb +22 -0
  25. data/lib/whirled_peas/command/frame_command.rb +34 -0
  26. data/lib/whirled_peas/command/frames.rb +24 -0
  27. data/lib/whirled_peas/command/help.rb +38 -0
  28. data/lib/whirled_peas/command/play.rb +108 -0
  29. data/lib/whirled_peas/command/record.rb +57 -0
  30. data/lib/whirled_peas/command/still.rb +29 -0
  31. data/lib/whirled_peas/command_line.rb +22 -212
  32. data/lib/whirled_peas/config.rb +56 -6
  33. data/lib/whirled_peas/device.rb +5 -0
  34. data/lib/whirled_peas/device/null_device.rb +8 -0
  35. data/lib/whirled_peas/device/output_file.rb +19 -0
  36. data/lib/whirled_peas/device/screen.rb +26 -0
  37. data/lib/whirled_peas/graphics.rb +14 -0
  38. data/lib/whirled_peas/graphics/box_painter.rb +39 -46
  39. data/lib/whirled_peas/graphics/canvas.rb +22 -11
  40. data/lib/whirled_peas/graphics/composer.rb +18 -0
  41. data/lib/whirled_peas/graphics/container_coords.rb +6 -5
  42. data/lib/whirled_peas/graphics/container_dimensions.rb +26 -8
  43. data/lib/whirled_peas/graphics/container_painter.rb +133 -4
  44. data/lib/whirled_peas/graphics/content_dimensions.rb +19 -0
  45. data/lib/whirled_peas/graphics/content_painter.rb +20 -0
  46. data/lib/whirled_peas/graphics/graph_dimensions.rb +12 -0
  47. data/lib/whirled_peas/graphics/graph_painter.rb +97 -0
  48. data/lib/whirled_peas/graphics/grid_painter.rb +12 -14
  49. data/lib/whirled_peas/graphics/painter.rb +15 -1
  50. data/lib/whirled_peas/graphics/renderer.rb +13 -2
  51. data/lib/whirled_peas/graphics/text_painter.rb +9 -13
  52. data/lib/whirled_peas/settings/alignment.rb +24 -0
  53. data/lib/whirled_peas/settings/bg_color.rb +2 -0
  54. data/lib/whirled_peas/settings/border.rb +16 -16
  55. data/lib/whirled_peas/settings/color.rb +13 -14
  56. data/lib/whirled_peas/settings/container_settings.rb +126 -44
  57. data/lib/whirled_peas/settings/display_flow.rb +13 -11
  58. data/lib/whirled_peas/settings/element_settings.rb +8 -3
  59. data/lib/whirled_peas/settings/graph_settings.rb +21 -0
  60. data/lib/whirled_peas/settings/grid_settings.rb +12 -1
  61. data/lib/whirled_peas/settings/sizing.rb +19 -0
  62. data/lib/whirled_peas/settings/spacing.rb +34 -0
  63. data/lib/whirled_peas/settings/text_color.rb +2 -0
  64. data/lib/whirled_peas/settings/vert_alignment.rb +24 -0
  65. data/lib/whirled_peas/utils/ansi.rb +13 -0
  66. data/lib/whirled_peas/utils/file_handler.rb +57 -0
  67. data/lib/whirled_peas/utils/title_font.rb +1 -1
  68. data/lib/whirled_peas/version.rb +1 -1
  69. data/screen_test/{rendered/elements → elements}/box.frame +1 -1
  70. data/screen_test/{rendered/elements → elements}/box.rb +0 -0
  71. data/screen_test/elements/graph.frame +1 -0
  72. data/screen_test/elements/graph.rb +12 -0
  73. data/screen_test/elements/grid.frame +1 -0
  74. data/screen_test/{rendered/elements → elements}/grid.rb +0 -0
  75. data/screen_test/elements/screen_overflow_x.frame +1 -0
  76. data/screen_test/{rendered/elements/screen_overflow.rb → elements/screen_overflow_x.rb} +0 -0
  77. data/screen_test/elements/screen_overflow_y.frame +1 -0
  78. data/screen_test/elements/screen_overflow_y.rb +9 -0
  79. data/screen_test/elements/text.frame +1 -0
  80. data/screen_test/{rendered/elements → elements}/text.rb +0 -0
  81. data/screen_test/elements/text_multiline.frame +1 -0
  82. data/screen_test/{rendered/elements → elements}/text_multiline.rb +0 -0
  83. data/screen_test/settings/align/box_around.frame +1 -0
  84. data/screen_test/settings/align/box_around.rb +16 -0
  85. data/screen_test/settings/align/box_between.frame +1 -0
  86. data/screen_test/settings/align/box_between.rb +16 -0
  87. data/screen_test/settings/align/box_center.frame +1 -0
  88. data/screen_test/{rendered/settings/align/grid.rb → settings/align/box_center.rb} +7 -6
  89. data/screen_test/settings/align/box_default.frame +1 -0
  90. data/screen_test/settings/align/box_default.rb +20 -0
  91. data/screen_test/settings/align/box_evenly.frame +1 -0
  92. data/screen_test/settings/align/box_evenly.rb +16 -0
  93. data/screen_test/settings/align/box_left.frame +1 -0
  94. data/screen_test/settings/align/box_left.rb +21 -0
  95. data/screen_test/settings/align/box_right.frame +1 -0
  96. data/screen_test/settings/align/box_right.rb +21 -0
  97. data/screen_test/settings/align/children_center.frame +1 -0
  98. data/screen_test/settings/align/children_center.rb +15 -0
  99. data/screen_test/settings/align/children_left.frame +1 -0
  100. data/screen_test/settings/align/children_left.rb +15 -0
  101. data/screen_test/settings/align/children_right.frame +1 -0
  102. data/screen_test/settings/align/children_right.rb +15 -0
  103. data/screen_test/settings/align/grid_center.frame +1 -0
  104. data/screen_test/settings/align/grid_center.rb +18 -0
  105. data/screen_test/settings/align/grid_default.frame +1 -0
  106. data/screen_test/settings/align/grid_default.rb +17 -0
  107. data/screen_test/settings/align/grid_left.frame +1 -0
  108. data/screen_test/settings/align/grid_left.rb +18 -0
  109. data/screen_test/settings/align/grid_right.frame +1 -0
  110. data/screen_test/settings/align/grid_right.rb +18 -0
  111. data/screen_test/settings/ansi/bold.frame +1 -0
  112. data/screen_test/{rendered/settings/height/overflow_box_t2b.rb → settings/ansi/bold.rb} +4 -4
  113. data/screen_test/{rendered/settings → settings}/ansi/color.frame +1 -1
  114. data/screen_test/{rendered/settings → settings}/ansi/color.rb +0 -0
  115. data/screen_test/settings/ansi/underline.frame +1 -0
  116. data/screen_test/{rendered/settings → settings}/ansi/underline.rb +4 -5
  117. data/screen_test/{rendered/settings → settings}/border.frame +1 -1
  118. data/screen_test/{rendered/settings → settings}/border.rb +1 -1
  119. data/screen_test/settings/flow/box_b2t.frame +1 -0
  120. data/screen_test/settings/flow/box_b2t.rb +26 -0
  121. data/screen_test/settings/flow/box_l2r.frame +1 -0
  122. data/screen_test/settings/flow/box_l2r.rb +26 -0
  123. data/screen_test/settings/flow/box_r2l.frame +1 -0
  124. data/screen_test/settings/flow/box_r2l.rb +26 -0
  125. data/screen_test/settings/flow/box_t2b.frame +1 -0
  126. data/screen_test/settings/flow/box_t2b.rb +26 -0
  127. data/screen_test/settings/flow/grid_b2t.frame +1 -0
  128. data/screen_test/{rendered/settings → settings}/flow/grid_b2t.rb +1 -1
  129. data/screen_test/settings/flow/grid_l2r.frame +1 -0
  130. data/screen_test/{rendered/settings → settings}/flow/grid_l2r.rb +1 -1
  131. data/screen_test/settings/flow/grid_r2l.frame +1 -0
  132. data/screen_test/{rendered/settings → settings}/flow/grid_r2l.rb +1 -1
  133. data/screen_test/settings/flow/grid_t2b.frame +1 -0
  134. data/screen_test/{rendered/settings → settings}/flow/grid_t2b.rb +1 -1
  135. data/screen_test/settings/height/box.frame +1 -0
  136. data/screen_test/{rendered/settings → settings}/height/box.rb +0 -0
  137. data/screen_test/settings/height/box_border_sizing.frame +1 -0
  138. data/screen_test/settings/height/box_border_sizing.rb +15 -0
  139. data/screen_test/settings/height/grid.frame +1 -0
  140. data/screen_test/{rendered/settings → settings}/height/grid.rb +0 -0
  141. data/screen_test/settings/height/overflow_box.frame +1 -0
  142. data/screen_test/settings/height/overflow_box.rb +13 -0
  143. data/screen_test/settings/height/overflow_box_l2r.frame +1 -0
  144. data/screen_test/settings/height/overflow_box_l2r.rb +17 -0
  145. data/screen_test/settings/height/overflow_box_t2b.frame +1 -0
  146. data/screen_test/settings/height/overflow_box_t2b.rb +16 -0
  147. data/screen_test/settings/height/overflow_grid.frame +1 -0
  148. data/screen_test/{rendered/settings → settings}/height/overflow_grid.rb +0 -0
  149. data/screen_test/settings/margin.frame +1 -0
  150. data/screen_test/settings/margin.rb +16 -0
  151. data/screen_test/settings/padding.frame +1 -0
  152. data/screen_test/settings/padding.rb +13 -0
  153. data/screen_test/settings/position/box_left.frame +1 -0
  154. data/screen_test/{rendered/settings → settings}/position/box_left.rb +2 -2
  155. data/screen_test/settings/position/box_left_negative.frame +1 -0
  156. data/screen_test/{rendered/settings → settings}/position/box_left_negative.rb +2 -2
  157. data/screen_test/settings/position/box_top.frame +1 -0
  158. data/screen_test/{rendered/settings → settings}/position/box_top.rb +1 -1
  159. data/screen_test/settings/position/box_top_negative.frame +1 -0
  160. data/screen_test/{rendered/settings → settings}/position/box_top_negative.rb +2 -2
  161. data/screen_test/settings/position/grid_left.frame +1 -0
  162. data/screen_test/{rendered/settings → settings}/position/grid_left.rb +1 -1
  163. data/screen_test/settings/position/grid_left_negative.frame +1 -0
  164. data/screen_test/{rendered/settings → settings}/position/grid_left_negative.rb +1 -1
  165. data/screen_test/settings/position/grid_top.frame +1 -0
  166. data/screen_test/{rendered/settings → settings}/position/grid_top.rb +1 -1
  167. data/screen_test/settings/position/grid_top_negative.frame +1 -0
  168. data/screen_test/{rendered/settings → settings}/position/grid_top_negative.rb +1 -1
  169. data/screen_test/settings/scroll/horiz_box.frame +1 -0
  170. data/screen_test/settings/scroll/horiz_box.rb +17 -0
  171. data/screen_test/settings/scroll/horiz_box_align_center.rb +18 -0
  172. data/screen_test/settings/scroll/horiz_box_align_right.rb +18 -0
  173. data/screen_test/settings/scroll/vert_box.frame +1 -0
  174. data/screen_test/settings/scroll/vert_box.rb +20 -0
  175. data/screen_test/settings/title_font.frame +1 -0
  176. data/screen_test/{rendered/settings → settings}/title_font.rb +1 -1
  177. data/screen_test/settings/valign/box_around.frame +1 -0
  178. data/screen_test/settings/valign/box_around.rb +17 -0
  179. data/screen_test/settings/valign/box_between.frame +1 -0
  180. data/screen_test/settings/valign/box_between.rb +17 -0
  181. data/screen_test/settings/valign/box_bottom.frame +1 -0
  182. data/screen_test/settings/valign/box_bottom.rb +17 -0
  183. data/screen_test/settings/valign/box_default.frame +1 -0
  184. data/screen_test/settings/valign/box_default.rb +16 -0
  185. data/screen_test/settings/valign/box_evenly.frame +1 -0
  186. data/screen_test/settings/valign/box_evenly.rb +17 -0
  187. data/screen_test/settings/valign/box_middle.frame +1 -0
  188. data/screen_test/settings/valign/box_middle.rb +17 -0
  189. data/screen_test/settings/valign/box_top.frame +1 -0
  190. data/screen_test/settings/valign/box_top.rb +17 -0
  191. data/screen_test/settings/valign/grid_bottom.frame +1 -0
  192. data/screen_test/settings/valign/grid_bottom.rb +15 -0
  193. data/screen_test/settings/valign/grid_default.frame +1 -0
  194. data/screen_test/settings/valign/grid_default.rb +14 -0
  195. data/screen_test/settings/valign/grid_middle.frame +1 -0
  196. data/screen_test/settings/valign/grid_middle.rb +15 -0
  197. data/screen_test/settings/valign/grid_top.frame +1 -0
  198. data/screen_test/settings/valign/grid_top.rb +15 -0
  199. data/screen_test/settings/width/box_border_sizing.frame +1 -0
  200. data/screen_test/settings/width/box_border_sizing.rb +15 -0
  201. data/screen_test/settings/width/box_content.frame +1 -0
  202. data/screen_test/settings/width/box_content.rb +15 -0
  203. data/screen_test/settings/width/box_default.frame +1 -0
  204. data/screen_test/{rendered/settings/width/box.rb → settings/width/box_default.rb} +3 -2
  205. data/screen_test/settings/width/grid.frame +1 -0
  206. data/screen_test/{rendered/settings → settings}/width/grid.rb +2 -2
  207. data/screen_test/settings/width/overflow_align_center.frame +1 -0
  208. data/screen_test/settings/width/overflow_align_center.rb +14 -0
  209. data/screen_test/settings/width/overflow_align_right.frame +1 -0
  210. data/screen_test/settings/width/overflow_align_right.rb +14 -0
  211. data/screen_test/settings/width/overflow_box.frame +1 -0
  212. data/screen_test/settings/width/overflow_box.rb +13 -0
  213. data/screen_test/settings/width/overflow_box_l2r.frame +1 -0
  214. data/screen_test/settings/width/overflow_box_l2r.rb +16 -0
  215. data/screen_test/settings/width/overflow_box_t2b.frame +1 -0
  216. data/screen_test/settings/width/overflow_box_t2b.rb +17 -0
  217. data/screen_test/settings/width/overflow_grid.frame +1 -0
  218. data/screen_test/{rendered/settings → settings}/width/overflow_grid.rb +0 -0
  219. data/tools/whirled_peas/tools/screen_tester.rb +285 -0
  220. metadata +188 -105
  221. data/lib/whirled_peas/frame.rb +0 -6
  222. data/lib/whirled_peas/frame/consumer.rb +0 -30
  223. data/lib/whirled_peas/frame/debug_consumer.rb +0 -30
  224. data/lib/whirled_peas/frame/event_loop.rb +0 -90
  225. data/lib/whirled_peas/frame/producer.rb +0 -67
  226. data/lib/whirled_peas/graphics/screen.rb +0 -70
  227. data/lib/whirled_peas/graphics/text_dimensions.rb +0 -15
  228. data/lib/whirled_peas/settings/text_align.rb +0 -19
  229. data/screen_test/rendered/elements/grid.frame +0 -1
  230. data/screen_test/rendered/elements/screen_overflow.frame +0 -1
  231. data/screen_test/rendered/elements/text.frame +0 -1
  232. data/screen_test/rendered/elements/text_multiline.frame +0 -1
  233. data/screen_test/rendered/settings/align/box.frame +0 -1
  234. data/screen_test/rendered/settings/align/box.rb +0 -24
  235. data/screen_test/rendered/settings/align/children_center.frame +0 -1
  236. data/screen_test/rendered/settings/align/children_center.rb +0 -13
  237. data/screen_test/rendered/settings/align/children_left.frame +0 -1
  238. data/screen_test/rendered/settings/align/children_left.rb +0 -13
  239. data/screen_test/rendered/settings/align/children_right.frame +0 -1
  240. data/screen_test/rendered/settings/align/children_right.rb +0 -13
  241. data/screen_test/rendered/settings/align/grid.frame +0 -1
  242. data/screen_test/rendered/settings/ansi/bold.frame +0 -1
  243. data/screen_test/rendered/settings/ansi/bold.rb +0 -15
  244. data/screen_test/rendered/settings/ansi/underline.frame +0 -1
  245. data/screen_test/rendered/settings/flow/box_b2t.frame +0 -1
  246. data/screen_test/rendered/settings/flow/box_b2t.rb +0 -24
  247. data/screen_test/rendered/settings/flow/box_l2r.frame +0 -1
  248. data/screen_test/rendered/settings/flow/box_l2r.rb +0 -24
  249. data/screen_test/rendered/settings/flow/box_r2l.frame +0 -1
  250. data/screen_test/rendered/settings/flow/box_r2l.rb +0 -24
  251. data/screen_test/rendered/settings/flow/box_t2b.frame +0 -1
  252. data/screen_test/rendered/settings/flow/box_t2b.rb +0 -24
  253. data/screen_test/rendered/settings/flow/grid_b2t.frame +0 -1
  254. data/screen_test/rendered/settings/flow/grid_l2r.frame +0 -1
  255. data/screen_test/rendered/settings/flow/grid_r2l.frame +0 -1
  256. data/screen_test/rendered/settings/flow/grid_t2b.frame +0 -1
  257. data/screen_test/rendered/settings/height/box.frame +0 -1
  258. data/screen_test/rendered/settings/height/grid.frame +0 -1
  259. data/screen_test/rendered/settings/height/overflow_box.frame +0 -1
  260. data/screen_test/rendered/settings/height/overflow_box.rb +0 -13
  261. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +0 -1
  262. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +0 -15
  263. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +0 -1
  264. data/screen_test/rendered/settings/height/overflow_grid.frame +0 -1
  265. data/screen_test/rendered/settings/margin.frame +0 -1
  266. data/screen_test/rendered/settings/margin.rb +0 -14
  267. data/screen_test/rendered/settings/padding.frame +0 -1
  268. data/screen_test/rendered/settings/padding.rb +0 -11
  269. data/screen_test/rendered/settings/position/box_left.frame +0 -1
  270. data/screen_test/rendered/settings/position/box_left_negative.frame +0 -1
  271. data/screen_test/rendered/settings/position/box_top.frame +0 -1
  272. data/screen_test/rendered/settings/position/box_top_negative.frame +0 -1
  273. data/screen_test/rendered/settings/position/grid_left.frame +0 -1
  274. data/screen_test/rendered/settings/position/grid_left_negative.frame +0 -1
  275. data/screen_test/rendered/settings/position/grid_top.frame +0 -1
  276. data/screen_test/rendered/settings/position/grid_top_negative.frame +0 -1
  277. data/screen_test/rendered/settings/scroll/horiz_box.frame +0 -1
  278. data/screen_test/rendered/settings/scroll/horiz_box.rb +0 -15
  279. data/screen_test/rendered/settings/scroll/vert_box.frame +0 -1
  280. data/screen_test/rendered/settings/scroll/vert_box.rb +0 -18
  281. data/screen_test/rendered/settings/title_font.frame +0 -1
  282. data/screen_test/rendered/settings/width/box.frame +0 -1
  283. data/screen_test/rendered/settings/width/grid.frame +0 -1
  284. data/screen_test/rendered/settings/width/overflow_box.frame +0 -1
  285. data/screen_test/rendered/settings/width/overflow_box.rb +0 -11
  286. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +0 -1
  287. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +0 -14
  288. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +0 -1
  289. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +0 -15
  290. data/screen_test/rendered/settings/width/overflow_grid.frame +0 -1
  291. data/screen_test/screen_tester.rb +0 -201
@@ -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
@@ -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
@@ -0,0 +1,97 @@
1
+ require 'whirled_peas/utils/ansi'
2
+
3
+ require_relative 'content_painter'
4
+
5
+ module WhirledPeas
6
+ module Graphics
7
+ class GraphPainter < ContentPainter
8
+ def paint(canvas, left, top, &block)
9
+ axis_formatting = [*settings.axis_color, *settings.bg_color]
10
+ plot_formatting = [*settings.color, *settings.bg_color]
11
+ axes_lines.each.with_index do |axis_line, row_index|
12
+ canvas.stroke(left, top + row_index, axis_line, axis_formatting, &block)
13
+ next if row_index >= plot_lines.length
14
+ canvas.stroke(left + 1, top + row_index, plot_lines[row_index], plot_formatting, &block)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def content_lines
21
+ axes_lines
22
+ end
23
+
24
+ def plot_lines
25
+ return @plot_lines if @plot_lines
26
+ min_y = 1.0 / 0
27
+ max_y = -1.0 / 0
28
+ if settings.width
29
+ interpolated = inner_width.times.map do |i|
30
+ x = (i * (content.length - 1).to_f / inner_width).floor
31
+ max_y = content[x] if content[x] > max_y
32
+ min_y = content[x] if content[x] < min_y
33
+ content[x]
34
+ end
35
+ else
36
+ interpolated = content
37
+ content.each do |y|
38
+ max_y = y if y > max_y
39
+ min_y = y if y < min_y
40
+ end
41
+ end
42
+ scaled = interpolated.map do |y|
43
+ (2 * inner_height * (y - min_y).to_f / (max_y - min_y)).floor
44
+ end
45
+ @plot_lines = Array.new(inner_height) { '' }
46
+ scaled.each.with_index do |y, x_index|
47
+ @plot_lines.each.with_index do |row, row_index|
48
+ bottom_half_index = 2 * (inner_height - row_index - 1)
49
+ top_half_index = bottom_half_index + 1
50
+
51
+ asc, next_y = if scaled.length == 1
52
+ [true, y]
53
+ elsif x_index == scaled.length - 1
54
+ y >= scaled[x_index - 1]
55
+ [true, y]
56
+ else
57
+ scaled[x_index + 1] >= y
58
+ [true, scaled[x_index + 1]]
59
+ end
60
+ if asc
61
+ top_half = y == top_half_index || (y...next_y).include?(top_half_index)
62
+ bottom_half = y == bottom_half_index || (y...next_y).include?(bottom_half_index)
63
+ else
64
+ top_half = y == top_half_index || (next_y...y).include?(top_half_index)
65
+ bottom_half = y == bottom_half_index || (next_y...y).include?(bottom_half_index)
66
+ end
67
+ row << if top_half && bottom_half
68
+ '█'
69
+ elsif top_half
70
+ '▀'
71
+ elsif bottom_half
72
+ '▄'
73
+ else
74
+ ' '
75
+ end
76
+ end
77
+ end
78
+ @plot_lines
79
+ end
80
+
81
+ def inner_height
82
+ settings.height - 1
83
+ end
84
+
85
+ def inner_width
86
+ settings.width.nil? ? content.length : settings.width - 1
87
+ end
88
+
89
+ def axes_lines
90
+ return @axes_lines if @axes_lines
91
+ @axes_lines = inner_height.times.map { '┃' }
92
+ @axes_lines << '┗' + '━' * inner_width
93
+ @axes_lines
94
+ end
95
+ end
96
+ end
97
+ end
@@ -4,32 +4,30 @@ require_relative 'container_painter'
4
4
  module WhirledPeas
5
5
  module Graphics
6
6
  class GridPainter < ContainerPainter
7
- def paint(canvas, &block)
7
+ def paint(canvas, left, top, &block)
8
8
  super
9
9
  return unless canvas.writable?
10
10
  each_child.with_index do |child, index|
11
11
  col_index, row_index = grid_cell(index)
12
- content_left = coords(canvas).content_left(col_index)
13
- if settings.align_center?
14
- content_left += (dimensions.content_width - child.dimensions.outer_width) / 2
15
- elsif settings.align_right?
16
- content_left += dimensions.content_width - child.dimensions.outer_width
17
- end
12
+ left_offset, _ = horiz_justify_offset(child.dimensions.outer_width)
13
+ top_offset, _ = vert_justify_offset(child.dimensions.outer_height)
18
14
  child_canvas = canvas.child(
19
- content_left,
20
- coords(canvas).content_top(row_index),
15
+ coords(left, top).content_left(col_index) + left_offset,
16
+ coords(left, top).content_top(row_index) + top_offset,
21
17
  [dimensions.content_width, child.dimensions.outer_width].min,
22
18
  [dimensions.content_height, child.dimensions.outer_height].min
23
19
  )
24
- child.paint(child_canvas, &block)
20
+ child.paint(
21
+ child_canvas,
22
+ coords(left, top).content_left(col_index) + left_offset,
23
+ coords(left, top).content_top(row_index) + top_offset,
24
+ &block
25
+ )
25
26
  end
26
27
  end
27
28
 
28
29
  def dimensions
29
30
  @dimensions ||= begin
30
- if settings.num_cols.nil? || settings.num_cols == 0
31
- raise SettingsError, "`num_cols` must be set for GridSettings(#{name})"
32
- end
33
31
  num_cols = settings.num_cols
34
32
  num_rows = (num_children.to_f / num_cols).ceil
35
33
  content_width = 0
@@ -57,7 +55,7 @@ module WhirledPeas
57
55
  col_index, row_index = [index % dimensions.num_cols, index / dimensions.num_cols]
58
56
  col_index = dimensions.num_cols - col_index - 1 if settings.reverse_flow?
59
57
  else
60
- col_index, row_index = [index / dimensions.num_cols, index % dimensions.num_cols]
58
+ col_index, row_index = [index / dimensions.num_rows, index % dimensions.num_rows]
61
59
  row_index = dimensions.num_rows - row_index - 1 if settings.reverse_flow?
62
60
  end
63
61
  [col_index, row_index]
@@ -1,5 +1,7 @@
1
1
  module WhirledPeas
2
2
  module Graphics
3
+ # Abstract base Painter class. Given a canvas and start coordinates (left, top), a painter
4
+ # is responsible for generating the "strokes" that display the element.
3
5
  class Painter
4
6
  attr_reader :name, :settings
5
7
 
@@ -8,11 +10,23 @@ module WhirledPeas
8
10
  @name = name
9
11
  end
10
12
 
11
- def paint(canvas, &block)
13
+ # Paint the element onto the canvas by yielding strokes to the block. A stroke is composed
14
+ # of a left, top, and chars. E.g.
15
+ #
16
+ # yield 10, 3, 'Hello World!'
17
+ #
18
+ # paints the string "Hello World!" in the 10th column from the left, 3rd row down.
19
+ def paint(canvas, left, top, &block)
12
20
  end
13
21
 
22
+ # Return a dimension object that provider the `outer_width` and `outer_height` of the
23
+ # element being painted.
14
24
  def dimensions
15
25
  end
26
+
27
+ def inspect
28
+ "#{self.class.name.split('::').last}(name=#{name.inspect})"
29
+ end
16
30
  end
17
31
  private_constant :Painter
18
32
  end
@@ -9,8 +9,19 @@ module WhirledPeas
9
9
  @height = height
10
10
  end
11
11
 
12
- def paint(&block)
13
- template.paint(Canvas.new(0, 0, width, height, 0, 0), &block)
12
+ def paint
13
+ # Modify the template's settings so that it fills the entire screen
14
+ template.settings.width = width
15
+ template.settings.height = height
16
+ template.settings.sizing = :border
17
+ template.settings.set_margin(left: 0, top: 0, right: 0, bottom: 0)
18
+ strokes = [Utils::Ansi.cursor_visible(false), Utils::Ansi.cursor_pos, Utils::Ansi.clear_down]
19
+ template.paint(Canvas.new(0, 0, width, height), 0, 0) do |left, top, fstring|
20
+ next unless fstring.length > 0
21
+ strokes << Utils::Ansi.cursor_pos(left: left, top: top)
22
+ strokes << fstring
23
+ end
24
+ strokes.join
14
25
  end
15
26
 
16
27
  private
@@ -1,32 +1,28 @@
1
1
  require 'whirled_peas/utils/formatted_string'
2
2
  require 'whirled_peas/utils/title_font'
3
3
 
4
- require_relative 'painter'
5
- require_relative 'text_dimensions'
4
+ require_relative 'content_dimensions'
5
+ require_relative 'content_painter'
6
6
 
7
7
  module WhirledPeas
8
8
  module Graphics
9
- class TextPainter < Painter
10
- attr_reader :content
11
-
12
- def paint(canvas, &block)
9
+ class TextPainter < ContentPainter
10
+ def paint(canvas, left, top, &block)
13
11
  return unless canvas.writable?
14
12
  formatting = [*settings.color, *settings.bg_color]
15
13
  formatting << Utils::Ansi::BOLD if settings.bold?
16
14
  if settings.underline? && settings.title_font.nil?
17
15
  formatting << Utils::Ansi::UNDERLINE
18
16
  end
19
- content.each.with_index do |line, index|
20
- canvas.stroke(canvas.start_left, canvas.start_top + index, line, formatting, &block)
17
+ content_lines.each.with_index do |line, index|
18
+ canvas.stroke(left, top + index, line, formatting, &block)
21
19
  end
22
20
  end
23
21
 
24
- def dimensions
25
- TextDimensions.new(content)
26
- end
22
+ private
27
23
 
28
- def content=(content)
29
- @content = if settings.title_font
24
+ def content_lines
25
+ @content_lines = if settings.title_font
30
26
  Utils::TitleFont.to_s(
31
27
  content, settings.title_font
32
28
  ).split("\n")
@@ -0,0 +1,24 @@
1
+ module WhirledPeas
2
+ module Settings
3
+ module Alignment
4
+ LEFT = :left
5
+ CENTER = :center
6
+ RIGHT = :right
7
+ BETWEEN = :between
8
+ AROUND = :around
9
+ EVENLY = :evenly
10
+
11
+ DEFAULT = LEFT
12
+
13
+ VALID = [LEFT, CENTER, RIGHT, BETWEEN, AROUND, EVENLY]
14
+ private_constant :VALID
15
+
16
+ def self.validate!(align)
17
+ return if align.nil?
18
+ return align if VALID.include?(align)
19
+ raise ArgumentError, "Unsupported alignment: #{align.inspect}"
20
+ end
21
+ end
22
+ private_constant :Alignment
23
+ end
24
+ end
@@ -17,6 +17,8 @@ module WhirledPeas
17
17
  CYAN = new(Utils::Ansi::CYAN + BG_OFFSET)
18
18
  WHITE = new(Utils::Ansi::WHITE + BG_OFFSET)
19
19
  GRAY = BLACK.bright
20
+
21
+ DEFAULT = nil # Use system defined default
20
22
  end
21
23
  end
22
24
  end
@@ -34,20 +34,20 @@ module WhirledPeas
34
34
  '╚', '═', '╩', '╝'
35
35
  )
36
36
 
37
+ DEFAULT = BOLD
38
+
37
39
  def self.validate!(style)
38
- return unless style
40
+ return if style.nil?
41
+ return style if style.is_a?(Border::Style)
39
42
  if style.is_a?(Symbol)
40
- error_message = "Unsupported border style: #{style}"
41
- begin
42
- style = self.const_get(style.upcase)
43
- raise ArgumentError, error_message unless style.is_a?(Style)
44
- style
45
- rescue NameError
46
- raise ArgumentError, error_message
47
- end
48
- else
49
- style
43
+ style_class = self.const_get(style.upcase)
44
+ return style_class if style_class.is_a?(Border::Style)
50
45
  end
46
+ error_message = "Unsupported border style: #{style.inspect}"
47
+ raise ArgumentError, error_message
48
+ rescue NameError
49
+ error_message = "Unsupported border style: #{style.inspect}"
50
+ raise ArgumentError, error_message
51
51
  end
52
52
  end
53
53
 
@@ -82,20 +82,20 @@ module WhirledPeas
82
82
  end
83
83
 
84
84
  def style
85
- @style || Styles::BOLD
85
+ @style || Styles::DEFAULT
86
86
  end
87
87
 
88
88
  def style=(val)
89
89
  @style = Styles.validate!(val)
90
90
  end
91
91
 
92
- def color
93
- @color || TextColor::WHITE
94
- end
95
-
96
92
  def color=(val)
97
93
  @color = TextColor.validate!(val)
98
94
  end
95
+
96
+ def color
97
+ @color || TextColor::DEFAULT
98
+ end
99
99
  end
100
100
  end
101
101
  end
@@ -13,25 +13,24 @@ module WhirledPeas
13
13
  # @return [Color|Symbol] the value passed in if valid, otherwise an ArgumentError
14
14
  # is raised.
15
15
  def self.validate!(color)
16
- return unless color
16
+ return if color.nil?
17
+ return color if color.is_a?(self)
17
18
  if color.is_a?(Symbol)
18
- error_message = "Unsupported #{self.name.split('::').last}: #{color.inspect}"
19
19
  match = color.to_s.match(/^(bright_)?(\w+)$/)
20
- begin
21
- color = self.const_get(match[2].upcase)
22
- raise ArgumentError, error_message unless color.is_a?(Color)
23
- if match[1]
24
- raise ArgumentError, error_message if color.bright?
25
- color.bright
26
- else
27
- color
20
+ color_class = self.const_get(match[2].upcase)
21
+ if color_class.is_a?(self)
22
+ if !match[1]
23
+ return color_class
24
+ elsif !color_class.bright?
25
+ return color_class.bright
28
26
  end
29
- rescue NameError
30
- raise ArgumentError, error_message
31
27
  end
32
- else
33
- color
34
28
  end
29
+ error_message = "Unsupported #{self.name.split('::').last}: #{color.inspect}"
30
+ raise ArgumentError, error_message
31
+ rescue NameError
32
+ error_message = "Unsupported #{self.name.split('::').last}: #{color.inspect}"
33
+ raise ArgumentError, error_message
35
34
  end
36
35
 
37
36
  def initialize(code, bright=false)
@@ -1,3 +1,4 @@
1
+ require_relative 'alignment'
1
2
  require_relative 'border'
2
3
  require_relative 'display_flow'
3
4
  require_relative 'element_settings'
@@ -5,44 +6,87 @@ require_relative 'margin'
5
6
  require_relative 'padding'
6
7
  require_relative 'position'
7
8
  require_relative 'scrollbar'
9
+ require_relative 'sizing'
10
+ require_relative 'vert_alignment'
8
11
 
9
12
  module WhirledPeas
10
13
  module Settings
11
14
  class ContainerSettings < ElementSettings
12
- attr_accessor :width, :height
13
-
14
15
  def align
15
- @_align || TextAlign::LEFT
16
+ @_align || Alignment::DEFAULT
16
17
  end
17
18
 
18
19
  def align_left?
19
- align == TextAlign::LEFT
20
+ align == Alignment::LEFT
20
21
  end
21
22
 
22
23
  def align_center?
23
- align == TextAlign::CENTER
24
+ align == Alignment::CENTER
24
25
  end
25
26
 
26
27
  def align_right?
27
- align == TextAlign::RIGHT
28
+ align == Alignment::RIGHT
29
+ end
30
+
31
+ def align_between?
32
+ align == Alignment::BETWEEN
33
+ end
34
+
35
+ def align_around?
36
+ align == Alignment::AROUND
37
+ end
38
+
39
+ def align_evenly?
40
+ align == Alignment::EVENLY
28
41
  end
29
42
 
30
43
  def align=(align)
31
- @_align = TextAlign.validate!(align)
44
+ @_align = Alignment.validate!(align)
45
+ end
46
+
47
+ def valign
48
+ @_valign || VertAlignment::DEFAULT
49
+ end
50
+
51
+ def valign_top?
52
+ valign == VertAlignment::TOP
53
+ end
54
+
55
+ def valign_middle?
56
+ valign == VertAlignment::MIDDLE
57
+ end
58
+
59
+ def valign_bottom?
60
+ valign == VertAlignment::BOTTOM
61
+ end
62
+
63
+ def valign_between?
64
+ valign == VertAlignment::BETWEEN
65
+ end
66
+
67
+ def valign_around?
68
+ valign == VertAlignment::AROUND
69
+ end
70
+
71
+ def valign_evenly?
72
+ valign == VertAlignment::EVENLY
73
+ end
74
+
75
+ def valign=(valign)
76
+ @_valign = VertAlignment.validate!(valign)
32
77
  end
33
78
 
34
79
  def set_border(
35
80
  left: nil, top: nil, right: nil, bottom: nil, inner_horiz: nil, inner_vert: nil, style: nil, color: nil
36
81
  )
37
- @_border = Border.new unless @_border
38
- @_border.left = left unless left.nil?
39
- @_border.top = top unless top.nil?
40
- @_border.right = right unless right.nil?
41
- @_border.bottom = bottom unless bottom.nil?
42
- @_border.inner_horiz = inner_horiz unless inner_horiz.nil?
43
- @_border.inner_vert = inner_vert unless inner_vert.nil?
44
- @_border.style = style unless style.nil?
45
- @_border.color = color unless color.nil?
82
+ border.left = left unless left.nil?
83
+ border.top = top unless top.nil?
84
+ border.right = right unless right.nil?
85
+ border.bottom = bottom unless bottom.nil?
86
+ border.inner_horiz = inner_horiz unless inner_horiz.nil?
87
+ border.inner_vert = inner_vert unless inner_vert.nil?
88
+ border.style = style unless style.nil?
89
+ border.color = color unless color.nil?
46
90
  end
47
91
 
48
92
  def full_border(style: nil, color: nil)
@@ -52,7 +96,7 @@ module WhirledPeas
52
96
  end
53
97
 
54
98
  def border
55
- @_border || Border.new
99
+ @_border ||= Border.new
56
100
  end
57
101
 
58
102
  def flow=(flow)
@@ -60,7 +104,7 @@ module WhirledPeas
60
104
  end
61
105
 
62
106
  def flow
63
- @_flow || DisplayFlow::LEFT_TO_RIGHT
107
+ @_flow || DisplayFlow::DEFAULT
64
108
  end
65
109
 
66
110
  def horizontal_flow?
@@ -79,61 +123,99 @@ module WhirledPeas
79
123
  !forward_flow?
80
124
  end
81
125
 
82
- def set_margin(left: nil, top: nil, right: nil, bottom: nil)
83
- @_margin = Margin.new unless @_margin
84
- @_margin.left = left if left
85
- @_margin.top = top if top
86
- @_margin.right = right if right
87
- @_margin.bottom = bottom if bottom
126
+ def set_margin(left: nil, top: nil, right: nil, bottom: nil, horiz: nil, vert: nil)
127
+ if horiz && (left || right)
128
+ raise ArgumentError, 'Cannot set horizontal margin when setting left/right'
129
+ elsif vert && (top || bottom)
130
+ raise ArgumentError, 'Cannot set vertical margin when setting top/bottom'
131
+ end
132
+
133
+ if horiz
134
+ margin.horiz = horiz
135
+ else
136
+ margin.left = left if left
137
+ margin.right = right if right
138
+ end
139
+
140
+ if vert
141
+ margin.vert = vert
142
+ else
143
+ margin.top = top if top
144
+ margin.bottom = bottom if bottom
145
+ end
88
146
  end
89
147
 
90
148
  def margin
91
- @_margin || Margin.new
149
+ @_margin ||= Margin.new
92
150
  end
93
151
 
94
- def set_padding(left: nil, top: nil, right: nil, bottom: nil)
95
- @_padding = Padding.new unless @_padding
96
- @_padding.left = left if left
97
- @_padding.top = top if top
98
- @_padding.right = right if right
99
- @_padding.bottom = bottom if bottom
152
+ def set_padding(left: nil, top: nil, right: nil, bottom: nil, horiz: nil, vert: nil)
153
+ if horiz && (left || right)
154
+ raise ArgumentError, 'Cannot set horizontal padding when setting left/right'
155
+ elsif vert && (top || bottom)
156
+ raise ArgumentError, 'Cannot set vertical padding when setting top/bottom'
157
+ end
158
+
159
+ if horiz
160
+ padding.horiz = horiz
161
+ else
162
+ padding.left = left if left
163
+ padding.right = right if right
164
+ end
165
+
166
+ if vert
167
+ padding.vert = vert
168
+ else
169
+ padding.top = top if top
170
+ padding.bottom = bottom if bottom
171
+ end
100
172
  end
101
173
 
102
174
  def padding
103
- @_padding || Padding.new
175
+ @_padding ||= Padding.new
104
176
  end
105
177
 
106
178
  def set_position(left: nil, top: nil)
107
- @_position = Position.new unless @_position
108
- @_position.left = left if left
109
- @_position.top = top if top
179
+ position.left = left if left
180
+ position.top = top if top
110
181
  end
111
182
 
112
183
  def position
113
- @_position || Position.new
184
+ @_position ||= Position.new
114
185
  end
115
186
 
116
187
  def set_scrollbar(horiz: nil, vert: nil)
117
- @_scrollbar = Scrollbar.new unless @_scrollbar
118
- @_scrollbar.horiz = horiz unless horiz.nil?
119
- @_scrollbar.vert = vert unless vert.nil?
188
+ scrollbar.horiz = horiz unless horiz.nil?
189
+ scrollbar.vert = vert unless vert.nil?
120
190
  end
121
191
 
122
192
  def scrollbar
123
- @_scrollbar || Scrollbar.new
193
+ @_scrollbar ||= Scrollbar.new
194
+ end
195
+
196
+ def sizing
197
+ @_sizing || Sizing::DEFAULT
198
+ end
199
+
200
+ def border_sizing?
201
+ @_sizing == Sizing::BORDER
202
+ end
203
+
204
+ def content_sizing?
205
+ @_sizing == Sizing::CONTENT
206
+ end
207
+
208
+ def sizing=(sizing)
209
+ @_sizing = Sizing.validate!(sizing)
124
210
  end
125
211
 
126
212
  def inherit(parent)
127
213
  super
128
214
  return unless parent.is_a?(ContainerSettings)
129
- @_align = parent._align
130
- @_flow = parent._flow
131
215
  set_border(color: parent.border.color, style: parent.border.style)
132
216
  end
133
217
 
134
218
  protected
135
-
136
- attr_accessor :_align, :_flow
137
219
  end
138
220
  end
139
221
  end