whirled_peas 0.8.0 → 0.11.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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/CHANGELOG.md +23 -0
  4. data/README.md +9 -777
  5. data/doc/application.md +92 -0
  6. data/doc/cli.md +115 -0
  7. data/doc/components.md +55 -0
  8. data/doc/easing.md +257 -0
  9. data/doc/elements.md +69 -0
  10. data/doc/screen_test.md +23 -0
  11. data/doc/settings.md +466 -0
  12. data/doc/template_factory.md +53 -0
  13. data/doc/themes.md +15 -0
  14. data/examples/components.rb +35 -0
  15. data/examples/graph.rb +54 -0
  16. data/examples/scrolling.rb +9 -20
  17. data/lib/data/themes.yaml +17 -0
  18. data/lib/whirled_peas.rb +31 -9
  19. data/lib/whirled_peas/animator/frameset.rb +1 -1
  20. data/lib/whirled_peas/animator/producer.rb +1 -1
  21. data/lib/whirled_peas/command/config_command.rb +3 -3
  22. data/lib/whirled_peas/command/play.rb +4 -0
  23. data/lib/whirled_peas/command/themes.rb +77 -0
  24. data/lib/whirled_peas/command_line.rb +3 -1
  25. data/lib/whirled_peas/component.rb +35 -0
  26. data/lib/whirled_peas/component/list_with_active.rb +88 -0
  27. data/lib/whirled_peas/graphics/box_painter.rb +4 -4
  28. data/lib/whirled_peas/graphics/composer.rb +27 -2
  29. data/lib/whirled_peas/graphics/container_coords.rb +23 -25
  30. data/lib/whirled_peas/graphics/container_dimensions.rb +24 -0
  31. data/lib/whirled_peas/graphics/container_painter.rb +27 -139
  32. data/lib/whirled_peas/graphics/content_dimensions.rb +19 -0
  33. data/lib/whirled_peas/graphics/content_painter.rb +20 -0
  34. data/lib/whirled_peas/graphics/debugger.rb +2 -7
  35. data/lib/whirled_peas/graphics/graph_dimensions.rb +12 -0
  36. data/lib/whirled_peas/graphics/graph_painter.rb +104 -0
  37. data/lib/whirled_peas/graphics/grid_painter.rb +0 -3
  38. data/lib/whirled_peas/graphics/scrollbar_helper.rb +126 -0
  39. data/lib/whirled_peas/graphics/text_painter.rb +7 -11
  40. data/lib/whirled_peas/null_logger.rb +0 -1
  41. data/lib/whirled_peas/settings/bg_color.rb +5 -1
  42. data/lib/whirled_peas/settings/border.rb +25 -4
  43. data/lib/whirled_peas/settings/color.rb +8 -4
  44. data/lib/whirled_peas/settings/container_settings.rb +14 -9
  45. data/lib/whirled_peas/settings/debugger.rb +3 -1
  46. data/lib/whirled_peas/settings/element_settings.rb +22 -5
  47. data/lib/whirled_peas/settings/graph_settings.rb +21 -0
  48. data/lib/whirled_peas/settings/grid_settings.rb +7 -0
  49. data/lib/whirled_peas/settings/position.rb +17 -5
  50. data/lib/whirled_peas/settings/text_color.rb +5 -0
  51. data/lib/whirled_peas/settings/text_settings.rb +1 -0
  52. data/lib/whirled_peas/settings/theme.rb +61 -0
  53. data/lib/whirled_peas/settings/theme_library.rb +69 -0
  54. data/lib/whirled_peas/utils/ansi.rb +1 -1
  55. data/lib/whirled_peas/version.rb +1 -1
  56. data/screen_test/components/list_with_active/l2r_active_color.frame +1 -0
  57. data/screen_test/components/list_with_active/l2r_active_color.rb +18 -0
  58. data/screen_test/components/list_with_active/l2r_position_end.frame +1 -0
  59. data/screen_test/components/list_with_active/l2r_position_end.rb +16 -0
  60. data/screen_test/components/list_with_active/l2r_position_middle.frame +1 -0
  61. data/screen_test/components/list_with_active/l2r_position_middle.rb +16 -0
  62. data/screen_test/components/list_with_active/l2r_position_start.frame +1 -0
  63. data/screen_test/components/list_with_active/l2r_position_start.rb +16 -0
  64. data/screen_test/components/list_with_active/l2r_separator.frame +1 -0
  65. data/screen_test/components/list_with_active/l2r_separator.rb +17 -0
  66. data/screen_test/components/list_with_active/t2b_active_color.frame +1 -0
  67. data/screen_test/components/list_with_active/t2b_active_color.rb +19 -0
  68. data/screen_test/components/list_with_active/t2b_position_end.frame +1 -0
  69. data/screen_test/components/list_with_active/t2b_position_end.rb +17 -0
  70. data/screen_test/components/list_with_active/t2b_position_middle.frame +1 -0
  71. data/screen_test/components/list_with_active/t2b_position_middle.rb +17 -0
  72. data/screen_test/components/list_with_active/t2b_position_start.frame +1 -0
  73. data/screen_test/components/list_with_active/t2b_position_start.rb +17 -0
  74. data/screen_test/components/list_with_active/t2b_separator.frame +1 -0
  75. data/screen_test/components/list_with_active/t2b_separator.rb +18 -0
  76. data/screen_test/elements/box.frame +1 -1
  77. data/screen_test/elements/box.rb +1 -1
  78. data/screen_test/elements/graph_asc.frame +1 -0
  79. data/screen_test/elements/graph_asc.rb +12 -0
  80. data/screen_test/elements/graph_desc.frame +1 -0
  81. data/screen_test/elements/graph_desc.rb +12 -0
  82. data/screen_test/elements/graph_horiz.frame +1 -0
  83. data/screen_test/elements/graph_horiz.rb +12 -0
  84. data/screen_test/elements/graph_sin.frame +1 -0
  85. data/screen_test/elements/graph_sin.rb +12 -0
  86. data/screen_test/elements/grid.frame +1 -1
  87. data/screen_test/elements/grid.rb +1 -1
  88. data/screen_test/elements/screen_overflow_x.frame +1 -1
  89. data/screen_test/elements/screen_overflow_x.rb +1 -1
  90. data/screen_test/elements/screen_overflow_y.frame +1 -1
  91. data/screen_test/elements/screen_overflow_y.rb +1 -1
  92. data/screen_test/elements/text.frame +1 -1
  93. data/screen_test/elements/text.rb +1 -1
  94. data/screen_test/elements/text_multiline.frame +1 -1
  95. data/screen_test/elements/text_multiline.rb +1 -1
  96. data/screen_test/elements/theme.frame +1 -0
  97. data/screen_test/elements/theme.rb +26 -0
  98. data/screen_test/settings/align/box_around.frame +1 -1
  99. data/screen_test/settings/align/box_around.rb +1 -1
  100. data/screen_test/settings/align/box_between.frame +1 -1
  101. data/screen_test/settings/align/box_between.rb +1 -1
  102. data/screen_test/settings/align/box_center.frame +1 -1
  103. data/screen_test/settings/align/box_center.rb +1 -1
  104. data/screen_test/settings/align/box_default.frame +1 -1
  105. data/screen_test/settings/align/box_default.rb +1 -1
  106. data/screen_test/settings/align/box_evenly.frame +1 -1
  107. data/screen_test/settings/align/box_evenly.rb +1 -1
  108. data/screen_test/settings/align/box_left.frame +1 -1
  109. data/screen_test/settings/align/box_left.rb +1 -1
  110. data/screen_test/settings/align/box_right.frame +1 -1
  111. data/screen_test/settings/align/box_right.rb +1 -1
  112. data/screen_test/settings/align/children_center.frame +1 -1
  113. data/screen_test/settings/align/children_center.rb +1 -1
  114. data/screen_test/settings/align/children_left.frame +1 -1
  115. data/screen_test/settings/align/children_left.rb +1 -1
  116. data/screen_test/settings/align/children_right.frame +1 -1
  117. data/screen_test/settings/align/children_right.rb +1 -1
  118. data/screen_test/settings/align/grid_center.frame +1 -1
  119. data/screen_test/settings/align/grid_center.rb +1 -1
  120. data/screen_test/settings/align/grid_default.frame +1 -1
  121. data/screen_test/settings/align/grid_default.rb +1 -1
  122. data/screen_test/settings/align/grid_left.frame +1 -1
  123. data/screen_test/settings/align/grid_left.rb +1 -1
  124. data/screen_test/settings/align/grid_right.frame +1 -1
  125. data/screen_test/settings/align/grid_right.rb +1 -1
  126. data/screen_test/settings/ansi/bold.frame +1 -1
  127. data/screen_test/settings/ansi/bold.rb +1 -1
  128. data/screen_test/settings/ansi/color.frame +1 -1
  129. data/screen_test/settings/ansi/color.rb +1 -1
  130. data/screen_test/settings/ansi/underline.frame +1 -1
  131. data/screen_test/settings/ansi/underline.rb +1 -1
  132. data/screen_test/settings/border.frame +1 -1
  133. data/screen_test/settings/border.rb +1 -1
  134. data/screen_test/settings/content_start/box_bottom.frame +1 -0
  135. data/screen_test/settings/content_start/box_bottom.rb +17 -0
  136. data/screen_test/settings/content_start/box_left.frame +1 -0
  137. data/screen_test/settings/{position → content_start}/box_left.rb +2 -2
  138. data/screen_test/settings/content_start/box_left_negative.frame +1 -0
  139. data/screen_test/settings/{position → content_start}/box_left_negative.rb +2 -2
  140. data/screen_test/settings/content_start/box_right.frame +1 -0
  141. data/screen_test/settings/content_start/box_right.rb +17 -0
  142. data/screen_test/settings/content_start/box_top.frame +1 -0
  143. data/screen_test/settings/{position → content_start}/box_top.rb +2 -2
  144. data/screen_test/settings/content_start/box_top_negative.frame +1 -0
  145. data/screen_test/settings/{position → content_start}/box_top_negative.rb +2 -2
  146. data/screen_test/settings/content_start/grid_left.frame +1 -0
  147. data/screen_test/settings/{position → content_start}/grid_left.rb +2 -2
  148. data/screen_test/settings/content_start/grid_left_negative.frame +1 -0
  149. data/screen_test/settings/{position → content_start}/grid_left_negative.rb +2 -2
  150. data/screen_test/settings/content_start/grid_top.frame +1 -0
  151. data/screen_test/settings/{position → content_start}/grid_top.rb +2 -2
  152. data/screen_test/settings/content_start/grid_top_negative.frame +1 -0
  153. data/screen_test/settings/{position → content_start}/grid_top_negative.rb +2 -2
  154. data/screen_test/settings/flow/box_b2t.frame +1 -1
  155. data/screen_test/settings/flow/box_b2t.rb +1 -1
  156. data/screen_test/settings/flow/box_l2r.frame +1 -1
  157. data/screen_test/settings/flow/box_l2r.rb +1 -1
  158. data/screen_test/settings/flow/box_r2l.frame +1 -1
  159. data/screen_test/settings/flow/box_r2l.rb +1 -1
  160. data/screen_test/settings/flow/box_t2b.frame +1 -1
  161. data/screen_test/settings/flow/box_t2b.rb +1 -1
  162. data/screen_test/settings/flow/grid_b2t.frame +1 -1
  163. data/screen_test/settings/flow/grid_b2t.rb +1 -1
  164. data/screen_test/settings/flow/grid_l2r.frame +1 -1
  165. data/screen_test/settings/flow/grid_l2r.rb +1 -1
  166. data/screen_test/settings/flow/grid_r2l.frame +1 -1
  167. data/screen_test/settings/flow/grid_r2l.rb +1 -1
  168. data/screen_test/settings/flow/grid_t2b.frame +1 -1
  169. data/screen_test/settings/flow/grid_t2b.rb +1 -1
  170. data/screen_test/settings/height/box.frame +1 -1
  171. data/screen_test/settings/height/box.rb +1 -1
  172. data/screen_test/settings/height/box_border_sizing.frame +1 -1
  173. data/screen_test/settings/height/box_border_sizing.rb +1 -1
  174. data/screen_test/settings/height/grid.frame +1 -1
  175. data/screen_test/settings/height/grid.rb +1 -1
  176. data/screen_test/settings/height/overflow_box.frame +1 -1
  177. data/screen_test/settings/height/overflow_box.rb +1 -1
  178. data/screen_test/settings/height/overflow_box_l2r.frame +1 -1
  179. data/screen_test/settings/height/overflow_box_l2r.rb +1 -1
  180. data/screen_test/settings/height/overflow_box_t2b.frame +1 -1
  181. data/screen_test/settings/height/overflow_box_t2b.rb +1 -1
  182. data/screen_test/settings/height/overflow_grid.frame +1 -1
  183. data/screen_test/settings/height/overflow_grid.rb +1 -1
  184. data/screen_test/settings/margin.frame +1 -1
  185. data/screen_test/settings/margin.rb +1 -1
  186. data/screen_test/settings/padding.frame +1 -1
  187. data/screen_test/settings/padding.rb +1 -1
  188. data/screen_test/settings/scroll/horiz_box.frame +1 -1
  189. data/screen_test/settings/scroll/horiz_box.rb +3 -5
  190. data/screen_test/settings/scroll/horiz_box_align_center.rb +3 -5
  191. data/screen_test/settings/scroll/horiz_box_align_right.rb +3 -5
  192. data/screen_test/settings/scroll/vert_box.frame +1 -1
  193. data/screen_test/settings/scroll/vert_box.rb +6 -8
  194. data/screen_test/settings/title_font.frame +1 -1
  195. data/screen_test/settings/title_font.rb +1 -1
  196. data/screen_test/settings/valign/box_around.frame +1 -1
  197. data/screen_test/settings/valign/box_around.rb +1 -1
  198. data/screen_test/settings/valign/box_between.frame +1 -1
  199. data/screen_test/settings/valign/box_between.rb +1 -1
  200. data/screen_test/settings/valign/box_bottom.frame +1 -1
  201. data/screen_test/settings/valign/box_bottom.rb +1 -1
  202. data/screen_test/settings/valign/box_default.frame +1 -1
  203. data/screen_test/settings/valign/box_default.rb +1 -1
  204. data/screen_test/settings/valign/box_evenly.frame +1 -1
  205. data/screen_test/settings/valign/box_evenly.rb +1 -1
  206. data/screen_test/settings/valign/box_middle.frame +1 -1
  207. data/screen_test/settings/valign/box_middle.rb +1 -1
  208. data/screen_test/settings/valign/box_top.frame +1 -1
  209. data/screen_test/settings/valign/box_top.rb +1 -1
  210. data/screen_test/settings/valign/grid_bottom.frame +1 -1
  211. data/screen_test/settings/valign/grid_bottom.rb +1 -1
  212. data/screen_test/settings/valign/grid_default.frame +1 -1
  213. data/screen_test/settings/valign/grid_default.rb +1 -1
  214. data/screen_test/settings/valign/grid_middle.frame +1 -1
  215. data/screen_test/settings/valign/grid_middle.rb +1 -1
  216. data/screen_test/settings/valign/grid_top.frame +1 -1
  217. data/screen_test/settings/valign/grid_top.rb +1 -1
  218. data/screen_test/settings/width/box_border_sizing.frame +1 -1
  219. data/screen_test/settings/width/box_border_sizing.rb +1 -1
  220. data/screen_test/settings/width/box_content.frame +1 -1
  221. data/screen_test/settings/width/box_content.rb +1 -1
  222. data/screen_test/settings/width/box_default.frame +1 -1
  223. data/screen_test/settings/width/box_default.rb +1 -1
  224. data/screen_test/settings/width/grid.frame +1 -1
  225. data/screen_test/settings/width/grid.rb +1 -1
  226. data/screen_test/settings/width/overflow_align_center.frame +1 -1
  227. data/screen_test/settings/width/overflow_align_center.rb +1 -1
  228. data/screen_test/settings/width/overflow_align_right.frame +1 -1
  229. data/screen_test/settings/width/overflow_align_right.rb +1 -1
  230. data/screen_test/settings/width/overflow_box.frame +1 -1
  231. data/screen_test/settings/width/overflow_box.rb +1 -1
  232. data/screen_test/settings/width/overflow_box_l2r.frame +1 -1
  233. data/screen_test/settings/width/overflow_box_l2r.rb +1 -1
  234. data/screen_test/settings/width/overflow_box_t2b.frame +1 -1
  235. data/screen_test/settings/width/overflow_box_t2b.rb +1 -1
  236. data/screen_test/settings/width/overflow_grid.frame +1 -1
  237. data/screen_test/settings/width/overflow_grid.rb +1 -1
  238. data/tools/whirled_peas/tools/screen_tester.rb +15 -1
  239. metadata +75 -20
  240. data/lib/whirled_peas/graphics/mock_screen.rb +0 -26
  241. data/lib/whirled_peas/graphics/text_dimensions.rb +0 -15
  242. data/screen_test/settings/position/box_left.frame +0 -1
  243. data/screen_test/settings/position/box_left_negative.frame +0 -1
  244. data/screen_test/settings/position/box_top.frame +0 -1
  245. data/screen_test/settings/position/box_top_negative.frame +0 -1
  246. data/screen_test/settings/position/grid_left.frame +0 -1
  247. data/screen_test/settings/position/grid_left_negative.frame +0 -1
  248. data/screen_test/settings/position/grid_top.frame +0 -1
  249. 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
@@ -15,16 +15,11 @@ module WhirledPeas
15
15
  "#{indent}* #{painter.class}(#{painter.name})",
16
16
  ]
17
17
  info << "#{indent + ' '}- Dimensions(#{dimensions})"
18
+ info << "#{indent + ' '}- Theme=#{painter.settings.theme.inspect}" if indent == ''
18
19
  info << "#{indent + ' '}- Settings"
19
20
  info << Settings::Debugger.new(painter.settings).debug(indent + ' ')
20
21
  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
22
+ content = painter.content
28
23
  info << "#{indent + ' '}- Content(value=#{content})"
29
24
  elsif painter.is_a?(ContainerPainter)
30
25
  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,104 @@
1
+ require 'whirled_peas/utils/ansi'
2
+
3
+ require_relative 'content_painter'
4
+
5
+ module WhirledPeas
6
+ module Graphics
7
+ class GraphPainter < ContentPainter
8
+ # The number of units along the Y-axis a single character can be divided into
9
+ Y_AXIS_SCALE = 2
10
+ private_constant :Y_AXIS_SCALE
11
+
12
+ def paint(canvas, left, top, &block)
13
+ axis_formatting = [*settings.axis_color, *settings.bg_color]
14
+ plot_formatting = [*settings.color, *settings.bg_color]
15
+ axes_lines.each.with_index do |axis_line, row_index|
16
+ canvas.stroke(left, top + row_index, axis_line, axis_formatting, &block)
17
+ next if row_index >= plot_lines.length
18
+ canvas.stroke(left + 1, top + row_index, plot_lines[row_index], plot_formatting, &block)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def content_lines
25
+ axes_lines
26
+ end
27
+
28
+ def plot_lines
29
+ return @plot_lines if @plot_lines
30
+ min_y = 1.0 / 0
31
+ max_y = -1.0 / 0
32
+ if settings.width
33
+ interpolated = (inner_width + 1).times.map do |i|
34
+ x = (i * (content.length - 1).to_f / (inner_width + 1)).floor
35
+ max_y = content[x] if content[x] > max_y
36
+ min_y = content[x] if content[x] < min_y
37
+ content[x]
38
+ end
39
+ else
40
+ interpolated = content
41
+ content.each do |y|
42
+ max_y = y if y > max_y
43
+ min_y = y if y < min_y
44
+ end
45
+ end
46
+ if min_y == max_y
47
+ min_y -= 1
48
+ max_y += 1
49
+ end
50
+ scaled = interpolated.map do |y|
51
+ inner_height * (y - min_y).to_f / (max_y - min_y)
52
+ end
53
+
54
+ @plot_lines = Array.new(inner_height) { '' }
55
+ scaled[0..-2].each.with_index do |y, x_index|
56
+ last = x_index == scaled.length - 2
57
+
58
+ prev_y = x_index == 0 ? y : scaled[x_index - 1]
59
+ next_y = scaled[x_index + 1]
60
+
61
+ min_y = [(prev_y + y) / 2, y, last ? next_y : (y + next_y) / 2].min
62
+ min_y = (Y_AXIS_SCALE * min_y).round.to_f / Y_AXIS_SCALE
63
+
64
+ max_y = [(prev_y + y) / 2, y, last ? next_y : (y + next_y) / 2].max
65
+ max_y = (Y_AXIS_SCALE * max_y).round.to_f / Y_AXIS_SCALE
66
+
67
+ @plot_lines.each.with_index do |row, row_index|
68
+ bottom_value = inner_height - row_index - 1
69
+ top_value = bottom_value + 1.0 / Y_AXIS_SCALE
70
+
71
+ top_half = min_y == top_value || (min_y <= top_value && top_value < max_y)
72
+ bottom_half = min_y == bottom_value || (min_y <= bottom_value && bottom_value < max_y)
73
+
74
+ row << if top_half && bottom_half
75
+ '█'
76
+ elsif top_half
77
+ '▀'
78
+ elsif bottom_half
79
+ '▄'
80
+ else
81
+ ' '
82
+ end
83
+ end
84
+ end
85
+ @plot_lines
86
+ end
87
+
88
+ def inner_height
89
+ settings.height - 1
90
+ end
91
+
92
+ def inner_width
93
+ (settings.width.nil? ? content.length : settings.width) - 1
94
+ end
95
+
96
+ def axes_lines
97
+ return @axes_lines if @axes_lines
98
+ @axes_lines = inner_height.times.map { '┃' }
99
+ @axes_lines << '┗' + '━' * inner_width
100
+ @axes_lines
101
+ end
102
+ end
103
+ end
104
+ 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
@@ -0,0 +1,126 @@
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
+ # Return the characters to paint the horizontal scroll bar with for the given column
19
+ #
20
+ # @see #scroll_chars for more details
21
+ def horiz(col_count, viewable_col_count, first_visible_col)
22
+ scroll_chars(col_count, viewable_col_count, first_visible_col, HORIZONTAL)
23
+ end
24
+
25
+ # Return the characters to paint the vertical scroll bar with for the given row
26
+ #
27
+ # @see #scroll_chars for more details
28
+ def vert(row_count, viewable_row_count, first_visible_row)
29
+ scroll_chars(row_count, viewable_row_count, first_visible_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 chars [Array<String>] an array with four 1-character strings, the frist is the
41
+ # gutter (i.e. no scrolbar handle visible), then the "second half" scrollbar character,
42
+ # then second is the "first half" scrollbar character, and finally the "full"
43
+ def scroll_chars(total_count, viewable_count, first_visible, chars)
44
+ # Start by initializing the scroll back to to all gutters
45
+ scrollbar = Array.new(viewable_count) { chars[0] }
46
+
47
+ return scrollbar if total_count == 0
48
+ return scrollbar if viewable_count == 0
49
+ return scrollbar if viewable_count >= total_count
50
+
51
+ # The scroll handle has the exact same relative size and position in the scroll gutter
52
+ # that the viewable content has in the total content area. For example, a content area
53
+ # that is 50 columns wide with a view port that is 20 columns wide might look like
54
+ #
55
+ # +---------1-----****2*********3******---4---------+
56
+ # | * * |
57
+ # | hidden * viewable * hidden |
58
+ # | * * |
59
+ # +---------1-----****2*********3******---4---------+
60
+ #
61
+ # The scoll gutter, would look like
62
+ #
63
+ # |......********.....|
64
+ #
65
+ # Scrolling all the way to the right results in
66
+ #
67
+ # +---------1---------2---------3*********4*********+
68
+ # | * *
69
+ # | hidden * viewable *
70
+ # | * *
71
+ # +---------1---------2---------3*********4*********+
72
+ # |...........********|
73
+ #
74
+ # Returning to the first example, we can match up the arguments to this method to the
75
+ # diagram
76
+ #
77
+ # total_count = 50
78
+ # |<----------------------------------------------->|
79
+ # | |
80
+ # | veiwable_count = 20 |
81
+ # | |<----------------->| |
82
+ # ↓ ↓ ↓ ↓
83
+ # +---------1-----****2*********3******---4---------+
84
+ # | * * |
85
+ # | hidden * viewable * hidden |
86
+ # | * * |
87
+ # +---------1-----****2*********3******---4---------+
88
+ # |......****?***.....|
89
+ # ↑ ↑
90
+ # first_visible = 16 |
91
+ # curr = 11
92
+
93
+ # Always use the same length for the scrollbar so it does not give an inchworm effect
94
+ # as it scrolls along. This will calculate the "ideal" length of the scroll bar if we
95
+ # could infinitely divide a character.
96
+ length = (viewable_count ** 2).to_f / total_count
97
+
98
+ # Round the length to the nearst "scaled" value
99
+ length = (SCROLL_HANDLE_SCALE * length).round.to_f / SCROLL_HANDLE_SCALE
100
+
101
+ # Ensure we have a scrollbar!
102
+ length = 1.0 / SCROLL_HANDLE_SCALE if length == 0
103
+
104
+ # Find the "ideal" position of where the scrollbar should start.
105
+ start = first_visible * viewable_count.to_f / total_count
106
+
107
+ # Round the start to the nearest "scaled" value
108
+ start = (SCROLL_HANDLE_SCALE * start).round.to_f / SCROLL_HANDLE_SCALE
109
+
110
+ # Make sure we didn't scroll off the page!
111
+ start -= length if start == viewable_count
112
+
113
+ (start.floor..[(start + length).floor, viewable_count - 1].min).each do |curr_0|
114
+ curr_1 = curr_0 + 1.0 / SCROLL_HANDLE_SCALE
115
+
116
+ first_half = start <= curr_0 && curr_0 < start + length ? 2 : 0
117
+ second_half = start <= curr_1 && curr_1 < start + length ? 1 : 0
118
+
119
+ scrollbar[curr_0] = chars[second_half | first_half]
120
+ end
121
+ scrollbar
122
+ end
123
+ end
124
+ end
125
+ end
126
+ 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
@@ -1,12 +1,12 @@
1
1
  require 'whirled_peas/utils/ansi'
2
2
 
3
3
  require_relative 'color'
4
+ require_relative 'text_color'
4
5
 
5
6
  module WhirledPeas
6
7
  module Settings
7
8
  class BgColor < Color
8
9
  BG_OFFSET = 10
9
- private_constant :BG_OFFSET
10
10
 
11
11
  BLACK = new(Utils::Ansi::BLACK + BG_OFFSET)
12
12
  RED = new(Utils::Ansi::RED + BG_OFFSET)
@@ -19,6 +19,10 @@ module WhirledPeas
19
19
  GRAY = BLACK.bright
20
20
 
21
21
  DEFAULT = nil # Use system defined default
22
+
23
+ def as_text_color
24
+ TextColor.new(code - BG_OFFSET, bright?)
25
+ end
22
26
  end
23
27
  end
24
28
  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 || theme.border_style
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
@@ -43,11 +43,11 @@ module WhirledPeas
43
43
  end
44
44
 
45
45
  def bright
46
- bright? ? self : self.class.new(@code + Utils::Ansi::BRIGHT_OFFSET, true)
46
+ bright? ? self : self.class.new(code + Utils::Ansi::BRIGHT_OFFSET, true)
47
47
  end
48
48
 
49
49
  def hash
50
- [@code, @bright].hash
50
+ [code, bright].hash
51
51
  end
52
52
 
53
53
  def ==(other)
@@ -56,12 +56,16 @@ module WhirledPeas
56
56
  alias_method :eq?, :==
57
57
 
58
58
  def to_s
59
- @code.to_s
59
+ code.to_s
60
60
  end
61
61
 
62
62
  def inspect
63
- "#{self.class.name.split('::').last}(code=#{@code}, bright=#{@bright})"
63
+ "#{self.class.name.split('::').last}(code=#{code}, bright=#{bright?})"
64
64
  end
65
+
66
+ private
67
+
68
+ attr_reader :code
65
69
  end
66
70
  private_constant :Color
67
71
  end