whirled_peas 0.7.1 → 0.11.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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +25 -0
  4. data/README.md +530 -121
  5. data/Rakefile +1 -20
  6. data/bin/easing +33 -0
  7. data/bin/reset_cursor +11 -0
  8. data/bin/screen_test +68 -0
  9. data/examples/components.rb +35 -0
  10. data/examples/graph.rb +54 -0
  11. data/examples/intro.rb +3 -3
  12. data/examples/scrolling.rb +13 -23
  13. data/lib/data/themes.yaml +13 -0
  14. data/lib/whirled_peas.rb +33 -13
  15. data/lib/whirled_peas/animator.rb +5 -0
  16. data/lib/whirled_peas/animator/debug_consumer.rb +17 -0
  17. data/lib/whirled_peas/animator/easing.rb +72 -0
  18. data/lib/whirled_peas/animator/frame.rb +5 -0
  19. data/lib/whirled_peas/animator/frameset.rb +33 -0
  20. data/lib/whirled_peas/animator/producer.rb +35 -0
  21. data/lib/whirled_peas/animator/renderer_consumer.rb +31 -0
  22. data/lib/whirled_peas/command.rb +5 -0
  23. data/lib/whirled_peas/command/base.rb +86 -0
  24. data/lib/whirled_peas/command/config_command.rb +44 -0
  25. data/lib/whirled_peas/command/debug.rb +21 -0
  26. data/lib/whirled_peas/command/fonts.rb +22 -0
  27. data/lib/whirled_peas/command/frame_command.rb +34 -0
  28. data/lib/whirled_peas/command/frames.rb +24 -0
  29. data/lib/whirled_peas/command/help.rb +38 -0
  30. data/lib/whirled_peas/command/play.rb +112 -0
  31. data/lib/whirled_peas/command/record.rb +57 -0
  32. data/lib/whirled_peas/command/still.rb +29 -0
  33. data/lib/whirled_peas/command/themes.rb +77 -0
  34. data/lib/whirled_peas/command_line.rb +24 -212
  35. data/lib/whirled_peas/component.rb +35 -0
  36. data/lib/whirled_peas/component/list_with_active.rb +88 -0
  37. data/lib/whirled_peas/config.rb +56 -6
  38. data/lib/whirled_peas/device.rb +5 -0
  39. data/lib/whirled_peas/device/null_device.rb +8 -0
  40. data/lib/whirled_peas/device/output_file.rb +19 -0
  41. data/lib/whirled_peas/device/screen.rb +26 -0
  42. data/lib/whirled_peas/graphics/box_painter.rb +4 -4
  43. data/lib/whirled_peas/graphics/composer.rb +27 -2
  44. data/lib/whirled_peas/graphics/container_coords.rb +24 -2
  45. data/lib/whirled_peas/graphics/container_painter.rb +63 -82
  46. data/lib/whirled_peas/graphics/content_dimensions.rb +19 -0
  47. data/lib/whirled_peas/graphics/content_painter.rb +20 -0
  48. data/lib/whirled_peas/graphics/debugger.rb +1 -7
  49. data/lib/whirled_peas/graphics/graph_dimensions.rb +12 -0
  50. data/lib/whirled_peas/graphics/graph_painter.rb +97 -0
  51. data/lib/whirled_peas/graphics/grid_painter.rb +0 -3
  52. data/lib/whirled_peas/graphics/painter.rb +10 -0
  53. data/lib/whirled_peas/graphics/renderer.rb +8 -2
  54. data/lib/whirled_peas/graphics/scrollbar_helper.rb +124 -0
  55. data/lib/whirled_peas/graphics/text_painter.rb +7 -11
  56. data/lib/whirled_peas/null_logger.rb +0 -1
  57. data/lib/whirled_peas/settings/border.rb +25 -4
  58. data/lib/whirled_peas/settings/container_settings.rb +14 -9
  59. data/lib/whirled_peas/settings/debugger.rb +1 -1
  60. data/lib/whirled_peas/settings/element_settings.rb +12 -3
  61. data/lib/whirled_peas/settings/graph_settings.rb +21 -0
  62. data/lib/whirled_peas/settings/grid_settings.rb +7 -0
  63. data/lib/whirled_peas/settings/position.rb +17 -5
  64. data/lib/whirled_peas/settings/text_settings.rb +1 -0
  65. data/lib/whirled_peas/settings/theme.rb +40 -0
  66. data/lib/whirled_peas/settings/theme_library.rb +63 -0
  67. data/lib/whirled_peas/utils/ansi.rb +13 -0
  68. data/lib/whirled_peas/utils/file_handler.rb +57 -0
  69. data/lib/whirled_peas/version.rb +1 -1
  70. data/screen_test/components/list_with_active/l2r_active_color.frame +1 -0
  71. data/screen_test/components/list_with_active/l2r_active_color.rb +18 -0
  72. data/screen_test/components/list_with_active/l2r_position_end.frame +1 -0
  73. data/screen_test/components/list_with_active/l2r_position_end.rb +16 -0
  74. data/screen_test/components/list_with_active/l2r_position_middle.frame +1 -0
  75. data/screen_test/components/list_with_active/l2r_position_middle.rb +16 -0
  76. data/screen_test/components/list_with_active/l2r_position_start.frame +1 -0
  77. data/screen_test/components/list_with_active/l2r_position_start.rb +16 -0
  78. data/screen_test/components/list_with_active/l2r_separator.frame +1 -0
  79. data/screen_test/components/list_with_active/l2r_separator.rb +17 -0
  80. data/screen_test/components/list_with_active/t2b_active_color.frame +1 -0
  81. data/screen_test/components/list_with_active/t2b_active_color.rb +19 -0
  82. data/screen_test/components/list_with_active/t2b_position_end.frame +1 -0
  83. data/screen_test/components/list_with_active/t2b_position_end.rb +17 -0
  84. data/screen_test/components/list_with_active/t2b_position_middle.frame +1 -0
  85. data/screen_test/components/list_with_active/t2b_position_middle.rb +17 -0
  86. data/screen_test/components/list_with_active/t2b_position_start.frame +1 -0
  87. data/screen_test/components/list_with_active/t2b_position_start.rb +17 -0
  88. data/screen_test/components/list_with_active/t2b_separator.frame +1 -0
  89. data/screen_test/components/list_with_active/t2b_separator.rb +18 -0
  90. data/screen_test/elements/box.frame +1 -1
  91. data/screen_test/elements/box.rb +1 -1
  92. data/screen_test/elements/graph.frame +1 -0
  93. data/screen_test/elements/graph.rb +12 -0
  94. data/screen_test/elements/grid.frame +1 -1
  95. data/screen_test/elements/grid.rb +1 -1
  96. data/screen_test/elements/screen_overflow_x.frame +1 -1
  97. data/screen_test/elements/screen_overflow_x.rb +1 -1
  98. data/screen_test/elements/screen_overflow_y.frame +1 -1
  99. data/screen_test/elements/screen_overflow_y.rb +1 -1
  100. data/screen_test/elements/text.frame +1 -1
  101. data/screen_test/elements/text.rb +1 -1
  102. data/screen_test/elements/text_multiline.frame +1 -1
  103. data/screen_test/elements/text_multiline.rb +1 -1
  104. data/screen_test/elements/theme.frame +1 -0
  105. data/screen_test/elements/theme.rb +26 -0
  106. data/screen_test/settings/align/box_around.frame +1 -1
  107. data/screen_test/settings/align/box_around.rb +1 -1
  108. data/screen_test/settings/align/box_between.frame +1 -1
  109. data/screen_test/settings/align/box_between.rb +1 -1
  110. data/screen_test/settings/align/box_center.frame +1 -1
  111. data/screen_test/settings/align/box_center.rb +1 -1
  112. data/screen_test/settings/align/box_default.frame +1 -1
  113. data/screen_test/settings/align/box_default.rb +1 -1
  114. data/screen_test/settings/align/box_evenly.frame +1 -1
  115. data/screen_test/settings/align/box_evenly.rb +1 -1
  116. data/screen_test/settings/align/box_left.frame +1 -1
  117. data/screen_test/settings/align/box_left.rb +1 -1
  118. data/screen_test/settings/align/box_right.frame +1 -1
  119. data/screen_test/settings/align/box_right.rb +1 -1
  120. data/screen_test/settings/align/children_center.frame +1 -1
  121. data/screen_test/settings/align/children_center.rb +1 -1
  122. data/screen_test/settings/align/children_left.frame +1 -1
  123. data/screen_test/settings/align/children_left.rb +1 -1
  124. data/screen_test/settings/align/children_right.frame +1 -1
  125. data/screen_test/settings/align/children_right.rb +1 -1
  126. data/screen_test/settings/align/grid_center.frame +1 -1
  127. data/screen_test/settings/align/grid_center.rb +1 -1
  128. data/screen_test/settings/align/grid_default.frame +1 -1
  129. data/screen_test/settings/align/grid_default.rb +1 -1
  130. data/screen_test/settings/align/grid_left.frame +1 -1
  131. data/screen_test/settings/align/grid_left.rb +1 -1
  132. data/screen_test/settings/align/grid_right.frame +1 -1
  133. data/screen_test/settings/align/grid_right.rb +1 -1
  134. data/screen_test/settings/ansi/bold.frame +1 -1
  135. data/screen_test/settings/ansi/bold.rb +1 -1
  136. data/screen_test/settings/ansi/color.frame +1 -1
  137. data/screen_test/settings/ansi/color.rb +1 -1
  138. data/screen_test/settings/ansi/underline.frame +1 -1
  139. data/screen_test/settings/ansi/underline.rb +1 -1
  140. data/screen_test/settings/border.frame +1 -1
  141. data/screen_test/settings/border.rb +1 -1
  142. data/screen_test/settings/content_start/box_bottom.frame +1 -0
  143. data/screen_test/settings/content_start/box_bottom.rb +17 -0
  144. data/screen_test/settings/content_start/box_left.frame +1 -0
  145. data/screen_test/settings/{position → content_start}/box_left.rb +2 -2
  146. data/screen_test/settings/content_start/box_left_negative.frame +1 -0
  147. data/screen_test/settings/{position → content_start}/box_left_negative.rb +2 -2
  148. data/screen_test/settings/content_start/box_right.frame +1 -0
  149. data/screen_test/settings/content_start/box_right.rb +17 -0
  150. data/screen_test/settings/content_start/box_top.frame +1 -0
  151. data/screen_test/settings/{position → content_start}/box_top.rb +2 -2
  152. data/screen_test/settings/content_start/box_top_negative.frame +1 -0
  153. data/screen_test/settings/{position → content_start}/box_top_negative.rb +2 -2
  154. data/screen_test/settings/content_start/grid_left.frame +1 -0
  155. data/screen_test/settings/{position → content_start}/grid_left.rb +2 -2
  156. data/screen_test/settings/content_start/grid_left_negative.frame +1 -0
  157. data/screen_test/settings/{position → content_start}/grid_left_negative.rb +2 -2
  158. data/screen_test/settings/content_start/grid_top.frame +1 -0
  159. data/screen_test/settings/{position → content_start}/grid_top.rb +2 -2
  160. data/screen_test/settings/content_start/grid_top_negative.frame +1 -0
  161. data/screen_test/settings/{position → content_start}/grid_top_negative.rb +2 -2
  162. data/screen_test/settings/flow/box_b2t.frame +1 -1
  163. data/screen_test/settings/flow/box_b2t.rb +1 -1
  164. data/screen_test/settings/flow/box_l2r.frame +1 -1
  165. data/screen_test/settings/flow/box_l2r.rb +1 -1
  166. data/screen_test/settings/flow/box_r2l.frame +1 -1
  167. data/screen_test/settings/flow/box_r2l.rb +1 -1
  168. data/screen_test/settings/flow/box_t2b.frame +1 -1
  169. data/screen_test/settings/flow/box_t2b.rb +1 -1
  170. data/screen_test/settings/flow/grid_b2t.frame +1 -1
  171. data/screen_test/settings/flow/grid_b2t.rb +1 -1
  172. data/screen_test/settings/flow/grid_l2r.frame +1 -1
  173. data/screen_test/settings/flow/grid_l2r.rb +1 -1
  174. data/screen_test/settings/flow/grid_r2l.frame +1 -1
  175. data/screen_test/settings/flow/grid_r2l.rb +1 -1
  176. data/screen_test/settings/flow/grid_t2b.frame +1 -1
  177. data/screen_test/settings/flow/grid_t2b.rb +1 -1
  178. data/screen_test/settings/height/box.frame +1 -1
  179. data/screen_test/settings/height/box.rb +1 -1
  180. data/screen_test/settings/height/box_border_sizing.frame +1 -1
  181. data/screen_test/settings/height/box_border_sizing.rb +1 -1
  182. data/screen_test/settings/height/grid.frame +1 -1
  183. data/screen_test/settings/height/grid.rb +1 -1
  184. data/screen_test/settings/height/overflow_box.frame +1 -1
  185. data/screen_test/settings/height/overflow_box.rb +1 -1
  186. data/screen_test/settings/height/overflow_box_l2r.frame +1 -1
  187. data/screen_test/settings/height/overflow_box_l2r.rb +1 -1
  188. data/screen_test/settings/height/overflow_box_t2b.frame +1 -1
  189. data/screen_test/settings/height/overflow_box_t2b.rb +1 -1
  190. data/screen_test/settings/height/overflow_grid.frame +1 -1
  191. data/screen_test/settings/height/overflow_grid.rb +1 -1
  192. data/screen_test/settings/margin.frame +1 -1
  193. data/screen_test/settings/margin.rb +1 -1
  194. data/screen_test/settings/padding.frame +1 -1
  195. data/screen_test/settings/padding.rb +1 -1
  196. data/screen_test/settings/scroll/horiz_box.frame +1 -1
  197. data/screen_test/settings/scroll/horiz_box.rb +3 -5
  198. data/screen_test/settings/scroll/horiz_box_align_center.rb +3 -5
  199. data/screen_test/settings/scroll/horiz_box_align_right.rb +3 -5
  200. data/screen_test/settings/scroll/vert_box.frame +1 -1
  201. data/screen_test/settings/scroll/vert_box.rb +6 -8
  202. data/screen_test/settings/title_font.frame +1 -1
  203. data/screen_test/settings/title_font.rb +1 -1
  204. data/screen_test/settings/valign/box_around.frame +1 -1
  205. data/screen_test/settings/valign/box_around.rb +1 -1
  206. data/screen_test/settings/valign/box_between.frame +1 -1
  207. data/screen_test/settings/valign/box_between.rb +1 -1
  208. data/screen_test/settings/valign/box_bottom.frame +1 -1
  209. data/screen_test/settings/valign/box_bottom.rb +1 -1
  210. data/screen_test/settings/valign/box_default.frame +1 -1
  211. data/screen_test/settings/valign/box_default.rb +1 -1
  212. data/screen_test/settings/valign/box_evenly.frame +1 -1
  213. data/screen_test/settings/valign/box_evenly.rb +1 -1
  214. data/screen_test/settings/valign/box_middle.frame +1 -1
  215. data/screen_test/settings/valign/box_middle.rb +1 -1
  216. data/screen_test/settings/valign/box_top.frame +1 -1
  217. data/screen_test/settings/valign/box_top.rb +1 -1
  218. data/screen_test/settings/valign/grid_bottom.frame +1 -1
  219. data/screen_test/settings/valign/grid_bottom.rb +1 -1
  220. data/screen_test/settings/valign/grid_default.frame +1 -1
  221. data/screen_test/settings/valign/grid_default.rb +1 -1
  222. data/screen_test/settings/valign/grid_middle.frame +1 -1
  223. data/screen_test/settings/valign/grid_middle.rb +1 -1
  224. data/screen_test/settings/valign/grid_top.frame +1 -1
  225. data/screen_test/settings/valign/grid_top.rb +1 -1
  226. data/screen_test/settings/width/box_border_sizing.frame +1 -1
  227. data/screen_test/settings/width/box_border_sizing.rb +1 -1
  228. data/screen_test/settings/width/box_content.frame +1 -1
  229. data/screen_test/settings/width/box_content.rb +1 -1
  230. data/screen_test/settings/width/box_default.frame +1 -1
  231. data/screen_test/settings/width/box_default.rb +1 -1
  232. data/screen_test/settings/width/grid.frame +1 -1
  233. data/screen_test/settings/width/grid.rb +1 -1
  234. data/screen_test/settings/width/overflow_align_center.frame +1 -1
  235. data/screen_test/settings/width/overflow_align_center.rb +1 -1
  236. data/screen_test/settings/width/overflow_align_right.frame +1 -1
  237. data/screen_test/settings/width/overflow_align_right.rb +1 -1
  238. data/screen_test/settings/width/overflow_box.frame +1 -1
  239. data/screen_test/settings/width/overflow_box.rb +1 -1
  240. data/screen_test/settings/width/overflow_box_l2r.frame +1 -1
  241. data/screen_test/settings/width/overflow_box_l2r.rb +1 -1
  242. data/screen_test/settings/width/overflow_box_t2b.frame +1 -1
  243. data/screen_test/settings/width/overflow_box_t2b.rb +1 -1
  244. data/screen_test/settings/width/overflow_grid.frame +1 -1
  245. data/screen_test/settings/width/overflow_grid.rb +1 -1
  246. data/tools/whirled_peas/tools/screen_tester.rb +131 -65
  247. metadata +86 -25
  248. data/lib/whirled_peas/frame.rb +0 -6
  249. data/lib/whirled_peas/frame/consumer.rb +0 -30
  250. data/lib/whirled_peas/frame/debug_consumer.rb +0 -30
  251. data/lib/whirled_peas/frame/event_loop.rb +0 -90
  252. data/lib/whirled_peas/frame/producer.rb +0 -67
  253. data/lib/whirled_peas/graphics/screen.rb +0 -70
  254. data/lib/whirled_peas/graphics/text_dimensions.rb +0 -15
  255. data/screen_test/settings/position/box_left.frame +0 -1
  256. data/screen_test/settings/position/box_left_negative.frame +0 -1
  257. data/screen_test/settings/position/box_top.frame +0 -1
  258. data/screen_test/settings/position/box_top_negative.frame +0 -1
  259. data/screen_test/settings/position/grid_left.frame +0 -1
  260. data/screen_test/settings/position/grid_left_negative.frame +0 -1
  261. data/screen_test/settings/position/grid_top.frame +0 -1
  262. data/screen_test/settings/position/grid_top_negative.frame +0 -1
@@ -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
@@ -18,13 +18,7 @@ module WhirledPeas
18
18
  info << "#{indent + ' '}- Settings"
19
19
  info << Settings::Debugger.new(painter.settings).debug(indent + ' ')
20
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
21
+ content = painter.content
28
22
  info << "#{indent + ' '}- Content(value=#{content})"
29
23
  elsif painter.is_a?(ContainerPainter)
30
24
  info << "#{indent + ' '}- Children"
@@ -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
@@ -28,9 +28,6 @@ module WhirledPeas
28
28
 
29
29
  def dimensions
30
30
  @dimensions ||= begin
31
- if settings.num_cols.nil? || settings.num_cols == 0
32
- raise SettingsError, "`num_cols` must be set for GridSettings(#{name})"
33
- end
34
31
  num_cols = settings.num_cols
35
32
  num_rows = (num_children.to_f / num_cols).ceil
36
33
  content_width = 0
@@ -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,9 +10,17 @@ module WhirledPeas
8
10
  @name = name
9
11
  end
10
12
 
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.
11
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
16
26
 
@@ -9,13 +9,19 @@ module WhirledPeas
9
9
  @height = height
10
10
  end
11
11
 
12
- def paint(&block)
12
+ def paint
13
13
  # Modify the template's settings so that it fills the entire screen
14
14
  template.settings.width = width
15
15
  template.settings.height = height
16
16
  template.settings.sizing = :border
17
17
  template.settings.set_margin(left: 0, top: 0, right: 0, bottom: 0)
18
- template.paint(Canvas.new(0, 0, width, height), 0, 0, &block)
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
19
25
  end
20
26
 
21
27
  private
@@ -0,0 +1,124 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ module ScrollbarHelper
4
+ # Contants to paint scrollbars
5
+ HORIZONTAL = [' ', '▗', '▖', '▄']
6
+ VERTICAL = [
7
+ ' ',
8
+ '▗',
9
+ '▝',
10
+ '▐'
11
+ ]
12
+
13
+ # The number of units of scrollbar a single character can be divided into
14
+ SCROLL_HANDLE_SCALE = 2
15
+ private_constant :SCROLL_HANDLE_SCALE
16
+
17
+ class << self
18
+ # Determine the character to paint the horizontal scroll bar with for the given column
19
+ #
20
+ # @see #scroll_char for more details
21
+ def horiz_char(col_count, viewable_col_count, first_visible_col, curr_col)
22
+ scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
23
+ end
24
+
25
+ # Determine the character to paint the vertical scroll bar with for the given row
26
+ #
27
+ # @see #scroll_char for more details
28
+ def vert_char(row_count, viewable_row_count, first_visible_row, curr_row)
29
+ scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
30
+ end
31
+
32
+ private
33
+
34
+ # Determine which character to paint a for a scrollbar
35
+ #
36
+ # @param total_count [Integer] total number of rows/columns in the content
37
+ # @param viewable_count [Integer] number of rows/columns visible in the viewport
38
+ # @param first_visible [Integer] zero-based index of the first row/column that is visible
39
+ # in the viewport
40
+ # @param curr [Integer] zero-based index of the row/column (relative to the first visible
41
+ # row/column) that the painted character is being requested for
42
+ # @param chars [Array<String>] an array with three 1-character strings, the frist is the
43
+ # "second half" scrollbar character, the second is the "full" scrollbar character, and
44
+ # the third is the "first half" scrollbar character.
45
+ def scroll_char(total_count, viewable_count, first_visible, curr, chars)
46
+ return chars[0] if total_count == 0
47
+ return chars[0] if viewable_count == 0
48
+ return chars[0] if viewable_count >= total_count
49
+
50
+ # The scroll handle has the exact same relative size and position in the scroll gutter
51
+ # that the viewable content has in the total content area. For example, a content area
52
+ # that is 50 columns wide with a view port that is 20 columns wide might look like
53
+ #
54
+ # +---------1-----****2*********3******---4---------+
55
+ # | * * |
56
+ # | hidden * viewable * hidden |
57
+ # | * * |
58
+ # +---------1-----****2*********3******---4---------+
59
+ #
60
+ # The scoll gutter, would look like
61
+ #
62
+ # |......********.....|
63
+ #
64
+ # Scrolling all the way to the right results in
65
+ #
66
+ # +---------1---------2---------3*********4*********+
67
+ # | * *
68
+ # | hidden * viewable *
69
+ # | * *
70
+ # +---------1---------2---------3*********4*********+
71
+ # |...........********|
72
+ #
73
+ # Returning to the first example, we can match up the arguments to this method to the
74
+ # diagram
75
+ #
76
+ # total_count = 50
77
+ # |<----------------------------------------------->|
78
+ # | |
79
+ # | veiwable_count = 20 |
80
+ # | |<----------------->| |
81
+ # ↓ ↓ ↓ ↓
82
+ # +---------1-----****2*********3******---4---------+
83
+ # | * * |
84
+ # | hidden * viewable * hidden |
85
+ # | * * |
86
+ # +---------1-----****2*********3******---4---------+
87
+ # |......****?***.....|
88
+ # ↑ ↑
89
+ # first_visible = 16 |
90
+ # curr = 11
91
+
92
+ # Always use the same length for the scrollbar so it does not give an inchworm effect
93
+ # as it scrolls along. This will calculate the "ideal" length of the scroll bar if we
94
+ # could infinitely divide a character.
95
+ scrollbar_length = (viewable_count ** 2).to_f / total_count
96
+
97
+ # Round the length to the nearst "scaled" value
98
+ scrollbar_length = (SCROLL_HANDLE_SCALE * scrollbar_length).round.to_f / SCROLL_HANDLE_SCALE
99
+
100
+ # Ensure we have a scrollbar!
101
+ scrollbar_length = 1.0 / SCROLL_HANDLE_SCALE if scrollbar_length == 0
102
+
103
+ # Find the "ideal" position of where the scrollbar should start.
104
+ scrollbar_start = first_visible * viewable_count.to_f / total_count
105
+
106
+ # Round the start to the nearest "scaled" value
107
+ scrollbar_start = (SCROLL_HANDLE_SCALE * scrollbar_start).round.to_f / SCROLL_HANDLE_SCALE
108
+
109
+ # Make sure we didn't scroll off the page!
110
+ scrollbar_start -= scrollbar_length if scrollbar_start == viewable_count
111
+
112
+ # Create "scaled" indexes for the subdivided current character
113
+ curr_0 = curr
114
+ curr_1 = curr + 1.0 / SCROLL_HANDLE_SCALE
115
+
116
+ first_half = scrollbar_start <= curr_0 && curr_0 < scrollbar_start + scrollbar_length ? 2 : 0
117
+ second_half = scrollbar_start <= curr_1 && curr_1 < scrollbar_start + scrollbar_length ? 1 : 0
118
+
119
+ chars[second_half | first_half]
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -1,14 +1,12 @@
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
-
9
+ class TextPainter < ContentPainter
12
10
  def paint(canvas, left, top, &block)
13
11
  return unless canvas.writable?
14
12
  formatting = [*settings.color, *settings.bg_color]
@@ -16,17 +14,15 @@ module WhirledPeas
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|
17
+ content_lines.each.with_index do |line, index|
20
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")
@@ -15,6 +15,5 @@ module WhirledPeas
15
15
  def warn(*)
16
16
  end
17
17
  end
18
-
19
18
  private_constant :NullLogger
20
19
  end
@@ -51,8 +51,20 @@ module WhirledPeas
51
51
  end
52
52
  end
53
53
 
54
+ def self.inherit(parent)
55
+ border = new(parent.theme)
56
+ border.inherit(parent)
57
+ border
58
+ end
59
+
60
+ attr_reader :theme
61
+
54
62
  attr_writer :left, :top, :right, :bottom, :inner_horiz, :inner_vert
55
63
 
64
+ def initialize(theme)
65
+ @theme = theme
66
+ end
67
+
56
68
  def left?
57
69
  @left == true
58
70
  end
@@ -82,20 +94,29 @@ module WhirledPeas
82
94
  end
83
95
 
84
96
  def style
85
- @style || Styles::DEFAULT
97
+ @_style || Styles::DEFAULT
86
98
  end
87
99
 
88
100
  def style=(val)
89
- @style = Styles.validate!(val)
101
+ @_style = Styles.validate!(val)
90
102
  end
91
103
 
92
104
  def color=(val)
93
- @color = TextColor.validate!(val)
105
+ @_color = TextColor.validate!(val)
94
106
  end
95
107
 
96
108
  def color
97
- @color || TextColor::DEFAULT
109
+ @_color || theme.border_color
98
110
  end
111
+
112
+ def inherit(parent)
113
+ @_style = parent._style
114
+ @_color = parent._color
115
+ end
116
+
117
+ protected
118
+
119
+ attr_reader :_style, :_color
99
120
  end
100
121
  end
101
122
  end