whirled_peas 0.3.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +38 -0
  4. data/README.md +549 -85
  5. data/Rakefile +28 -3
  6. data/examples/intro.rb +52 -0
  7. data/examples/scrolling.rb +53 -0
  8. data/exe/whirled_peas +7 -0
  9. data/lib/whirled_peas.rb +12 -36
  10. data/lib/whirled_peas/command_line.rb +255 -0
  11. data/lib/whirled_peas/config.rb +21 -0
  12. data/lib/whirled_peas/errors.rb +7 -0
  13. data/lib/whirled_peas/frame.rb +0 -5
  14. data/lib/whirled_peas/frame/consumer.rb +30 -0
  15. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  16. data/lib/whirled_peas/frame/event_loop.rb +69 -38
  17. data/lib/whirled_peas/frame/producer.rb +36 -19
  18. data/lib/whirled_peas/graphics.rb +19 -0
  19. data/lib/whirled_peas/graphics/box_painter.rb +101 -0
  20. data/lib/whirled_peas/graphics/canvas.rb +118 -0
  21. data/lib/whirled_peas/graphics/composer.rb +80 -0
  22. data/lib/whirled_peas/graphics/container_coords.rb +72 -0
  23. data/lib/whirled_peas/graphics/container_dimensions.rb +93 -0
  24. data/lib/whirled_peas/graphics/container_painter.rb +272 -0
  25. data/lib/whirled_peas/graphics/debugger.rb +52 -0
  26. data/lib/whirled_peas/graphics/grid_painter.rb +69 -0
  27. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  28. data/lib/whirled_peas/graphics/painter.rb +23 -0
  29. data/lib/whirled_peas/graphics/renderer.rb +26 -0
  30. data/lib/whirled_peas/graphics/screen.rb +70 -0
  31. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  32. data/lib/whirled_peas/graphics/text_painter.rb +40 -0
  33. data/lib/whirled_peas/settings.rb +5 -0
  34. data/lib/whirled_peas/settings/alignment.rb +24 -0
  35. data/lib/whirled_peas/settings/bg_color.rb +24 -0
  36. data/lib/whirled_peas/settings/border.rb +101 -0
  37. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  38. data/lib/whirled_peas/settings/color.rb +68 -0
  39. data/lib/whirled_peas/settings/container_settings.rb +223 -0
  40. data/lib/whirled_peas/settings/debugger.rb +96 -0
  41. data/lib/whirled_peas/settings/display_flow.rb +27 -0
  42. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  43. data/lib/whirled_peas/settings/grid_settings.rb +19 -0
  44. data/lib/whirled_peas/settings/margin.rb +8 -0
  45. data/lib/whirled_peas/settings/padding.rb +8 -0
  46. data/lib/whirled_peas/settings/position.rb +15 -0
  47. data/lib/whirled_peas/settings/scrollbar.rb +15 -0
  48. data/lib/whirled_peas/settings/sizing.rb +19 -0
  49. data/lib/whirled_peas/settings/spacing.rb +58 -0
  50. data/lib/whirled_peas/settings/text_color.rb +21 -0
  51. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  52. data/lib/whirled_peas/settings/vert_alignment.rb +24 -0
  53. data/lib/whirled_peas/utils.rb +5 -0
  54. data/lib/whirled_peas/utils/ansi.rb +53 -0
  55. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  56. data/lib/whirled_peas/utils/title_font.rb +75 -0
  57. data/lib/whirled_peas/version.rb +1 -1
  58. data/screen_test/elements/box.frame +1 -0
  59. data/screen_test/elements/box.rb +20 -0
  60. data/screen_test/elements/grid.frame +1 -0
  61. data/screen_test/elements/grid.rb +13 -0
  62. data/screen_test/elements/screen_overflow_x.frame +1 -0
  63. data/screen_test/elements/screen_overflow_x.rb +9 -0
  64. data/screen_test/elements/screen_overflow_y.frame +1 -0
  65. data/screen_test/elements/screen_overflow_y.rb +9 -0
  66. data/screen_test/elements/text.frame +1 -0
  67. data/screen_test/elements/text.rb +9 -0
  68. data/screen_test/elements/text_multiline.frame +1 -0
  69. data/screen_test/elements/text_multiline.rb +9 -0
  70. data/screen_test/settings/align/box_around.frame +1 -0
  71. data/screen_test/settings/align/box_around.rb +16 -0
  72. data/screen_test/settings/align/box_between.frame +1 -0
  73. data/screen_test/settings/align/box_between.rb +16 -0
  74. data/screen_test/settings/align/box_center.frame +1 -0
  75. data/screen_test/settings/align/box_center.rb +21 -0
  76. data/screen_test/settings/align/box_default.frame +1 -0
  77. data/screen_test/settings/align/box_default.rb +20 -0
  78. data/screen_test/settings/align/box_evenly.frame +1 -0
  79. data/screen_test/settings/align/box_evenly.rb +16 -0
  80. data/screen_test/settings/align/box_left.frame +1 -0
  81. data/screen_test/settings/align/box_left.rb +21 -0
  82. data/screen_test/settings/align/box_right.frame +1 -0
  83. data/screen_test/settings/align/box_right.rb +21 -0
  84. data/screen_test/settings/align/children_center.frame +1 -0
  85. data/screen_test/settings/align/children_center.rb +15 -0
  86. data/screen_test/settings/align/children_left.frame +1 -0
  87. data/screen_test/settings/align/children_left.rb +15 -0
  88. data/screen_test/settings/align/children_right.frame +1 -0
  89. data/screen_test/settings/align/children_right.rb +15 -0
  90. data/screen_test/settings/align/grid_center.frame +1 -0
  91. data/screen_test/settings/align/grid_center.rb +18 -0
  92. data/screen_test/settings/align/grid_default.frame +1 -0
  93. data/screen_test/settings/align/grid_default.rb +17 -0
  94. data/screen_test/settings/align/grid_left.frame +1 -0
  95. data/screen_test/settings/align/grid_left.rb +18 -0
  96. data/screen_test/settings/align/grid_right.frame +1 -0
  97. data/screen_test/settings/align/grid_right.rb +18 -0
  98. data/screen_test/settings/ansi/bold.frame +1 -0
  99. data/screen_test/settings/ansi/bold.rb +14 -0
  100. data/screen_test/settings/ansi/color.frame +1 -0
  101. data/screen_test/settings/ansi/color.rb +37 -0
  102. data/screen_test/settings/ansi/underline.frame +1 -0
  103. data/screen_test/settings/ansi/underline.rb +14 -0
  104. data/screen_test/settings/border.frame +1 -0
  105. data/screen_test/settings/border.rb +13 -0
  106. data/screen_test/settings/flow/box_b2t.frame +1 -0
  107. data/screen_test/settings/flow/box_b2t.rb +26 -0
  108. data/screen_test/settings/flow/box_l2r.frame +1 -0
  109. data/screen_test/settings/flow/box_l2r.rb +26 -0
  110. data/screen_test/settings/flow/box_r2l.frame +1 -0
  111. data/screen_test/settings/flow/box_r2l.rb +26 -0
  112. data/screen_test/settings/flow/box_t2b.frame +1 -0
  113. data/screen_test/settings/flow/box_t2b.rb +26 -0
  114. data/screen_test/settings/flow/grid_b2t.frame +1 -0
  115. data/screen_test/settings/flow/grid_b2t.rb +14 -0
  116. data/screen_test/settings/flow/grid_l2r.frame +1 -0
  117. data/screen_test/settings/flow/grid_l2r.rb +14 -0
  118. data/screen_test/settings/flow/grid_r2l.frame +1 -0
  119. data/screen_test/settings/flow/grid_r2l.rb +14 -0
  120. data/screen_test/settings/flow/grid_t2b.frame +1 -0
  121. data/screen_test/settings/flow/grid_t2b.rb +14 -0
  122. data/screen_test/settings/height/box.frame +1 -0
  123. data/screen_test/settings/height/box.rb +13 -0
  124. data/screen_test/settings/height/box_border_sizing.frame +1 -0
  125. data/screen_test/settings/height/box_border_sizing.rb +15 -0
  126. data/screen_test/settings/height/grid.frame +1 -0
  127. data/screen_test/settings/height/grid.rb +14 -0
  128. data/screen_test/settings/height/overflow_box.frame +1 -0
  129. data/screen_test/settings/height/overflow_box.rb +13 -0
  130. data/screen_test/settings/height/overflow_box_l2r.frame +1 -0
  131. data/screen_test/settings/height/overflow_box_l2r.rb +17 -0
  132. data/screen_test/settings/height/overflow_box_t2b.frame +1 -0
  133. data/screen_test/settings/height/overflow_box_t2b.rb +16 -0
  134. data/screen_test/settings/height/overflow_grid.frame +1 -0
  135. data/screen_test/settings/height/overflow_grid.rb +16 -0
  136. data/screen_test/settings/margin.frame +1 -0
  137. data/screen_test/settings/margin.rb +16 -0
  138. data/screen_test/settings/padding.frame +1 -0
  139. data/screen_test/settings/padding.rb +13 -0
  140. data/screen_test/settings/position/box_left.frame +1 -0
  141. data/screen_test/settings/position/box_left.rb +17 -0
  142. data/screen_test/settings/position/box_left_negative.frame +1 -0
  143. data/screen_test/settings/position/box_left_negative.rb +17 -0
  144. data/screen_test/settings/position/box_top.frame +1 -0
  145. data/screen_test/settings/position/box_top.rb +17 -0
  146. data/screen_test/settings/position/box_top_negative.frame +1 -0
  147. data/screen_test/settings/position/box_top_negative.rb +17 -0
  148. data/screen_test/settings/position/grid_left.frame +1 -0
  149. data/screen_test/settings/position/grid_left.rb +18 -0
  150. data/screen_test/settings/position/grid_left_negative.frame +1 -0
  151. data/screen_test/settings/position/grid_left_negative.rb +18 -0
  152. data/screen_test/settings/position/grid_top.frame +1 -0
  153. data/screen_test/settings/position/grid_top.rb +18 -0
  154. data/screen_test/settings/position/grid_top_negative.frame +1 -0
  155. data/screen_test/settings/position/grid_top_negative.rb +18 -0
  156. data/screen_test/settings/scroll/horiz_box.frame +1 -0
  157. data/screen_test/settings/scroll/horiz_box.rb +17 -0
  158. data/screen_test/settings/scroll/horiz_box_align_center.rb +18 -0
  159. data/screen_test/settings/scroll/horiz_box_align_right.rb +18 -0
  160. data/screen_test/settings/scroll/vert_box.frame +1 -0
  161. data/screen_test/settings/scroll/vert_box.rb +20 -0
  162. data/screen_test/settings/title_font.frame +1 -0
  163. data/screen_test/settings/title_font.rb +12 -0
  164. data/screen_test/settings/valign/box_around.frame +1 -0
  165. data/screen_test/settings/valign/box_around.rb +17 -0
  166. data/screen_test/settings/valign/box_between.frame +1 -0
  167. data/screen_test/settings/valign/box_between.rb +17 -0
  168. data/screen_test/settings/valign/box_bottom.frame +1 -0
  169. data/screen_test/settings/valign/box_bottom.rb +17 -0
  170. data/screen_test/settings/valign/box_default.frame +1 -0
  171. data/screen_test/settings/valign/box_default.rb +16 -0
  172. data/screen_test/settings/valign/box_evenly.frame +1 -0
  173. data/screen_test/settings/valign/box_evenly.rb +17 -0
  174. data/screen_test/settings/valign/box_middle.frame +1 -0
  175. data/screen_test/settings/valign/box_middle.rb +17 -0
  176. data/screen_test/settings/valign/box_top.frame +1 -0
  177. data/screen_test/settings/valign/box_top.rb +17 -0
  178. data/screen_test/settings/valign/grid_bottom.frame +1 -0
  179. data/screen_test/settings/valign/grid_bottom.rb +15 -0
  180. data/screen_test/settings/valign/grid_default.frame +1 -0
  181. data/screen_test/settings/valign/grid_default.rb +14 -0
  182. data/screen_test/settings/valign/grid_middle.frame +1 -0
  183. data/screen_test/settings/valign/grid_middle.rb +15 -0
  184. data/screen_test/settings/valign/grid_top.frame +1 -0
  185. data/screen_test/settings/valign/grid_top.rb +15 -0
  186. data/screen_test/settings/width/box_border_sizing.frame +1 -0
  187. data/screen_test/settings/width/box_border_sizing.rb +15 -0
  188. data/screen_test/settings/width/box_content.frame +1 -0
  189. data/screen_test/settings/width/box_content.rb +15 -0
  190. data/screen_test/settings/width/box_default.frame +1 -0
  191. data/screen_test/settings/width/box_default.rb +14 -0
  192. data/screen_test/settings/width/grid.frame +1 -0
  193. data/screen_test/settings/width/grid.rb +14 -0
  194. data/screen_test/settings/width/overflow_align_center.frame +1 -0
  195. data/screen_test/settings/width/overflow_align_center.rb +14 -0
  196. data/screen_test/settings/width/overflow_align_right.frame +1 -0
  197. data/screen_test/settings/width/overflow_align_right.rb +14 -0
  198. data/screen_test/settings/width/overflow_box.frame +1 -0
  199. data/screen_test/settings/width/overflow_box.rb +13 -0
  200. data/screen_test/settings/width/overflow_box_l2r.frame +1 -0
  201. data/screen_test/settings/width/overflow_box_l2r.rb +16 -0
  202. data/screen_test/settings/width/overflow_box_t2b.frame +1 -0
  203. data/screen_test/settings/width/overflow_box_t2b.rb +17 -0
  204. data/screen_test/settings/width/overflow_grid.frame +1 -0
  205. data/screen_test/settings/width/overflow_grid.rb +14 -0
  206. data/tools/whirled_peas/tools/screen_tester.rb +233 -0
  207. data/whirled_peas.gemspec +4 -1
  208. metadata +215 -17
  209. data/lib/whirled_peas/ui.rb +0 -7
  210. data/lib/whirled_peas/ui/ansi.rb +0 -103
  211. data/lib/whirled_peas/ui/canvas.rb +0 -68
  212. data/lib/whirled_peas/ui/color.rb +0 -101
  213. data/lib/whirled_peas/ui/element.rb +0 -224
  214. data/lib/whirled_peas/ui/painter.rb +0 -283
  215. data/lib/whirled_peas/ui/screen.rb +0 -60
  216. data/lib/whirled_peas/ui/settings.rb +0 -523
  217. data/sandbox/auto.rb +0 -13
  218. data/sandbox/box.rb +0 -19
  219. data/sandbox/grid.rb +0 -13
  220. data/sandbox/sandbox.rb +0 -17
  221. data/sandbox/text.rb +0 -33
@@ -0,0 +1,80 @@
1
+ require 'whirled_peas/settings/box_settings'
2
+ require 'whirled_peas/settings/grid_settings'
3
+ require 'whirled_peas/settings/text_settings'
4
+
5
+ require_relative 'box_painter'
6
+ require_relative 'grid_painter'
7
+ require_relative 'text_painter'
8
+
9
+ module WhirledPeas
10
+ module Graphics
11
+ class Composer
12
+ # List of classes that convert nicely to a string
13
+ STRINGALBE_CLASSES = [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass]
14
+ private_constant :STRINGALBE_CLASSES
15
+
16
+ def self.stringable?(value)
17
+ STRINGALBE_CLASSES.include?(value.class)
18
+ end
19
+
20
+ def self.next_name
21
+ @counter ||= 0
22
+ @counter += 1
23
+ "Element-#{@counter}"
24
+ end
25
+
26
+ def self.build
27
+ settings = Settings::BoxSettings.new
28
+ template = BoxPainter.new('TEMPLATE', settings)
29
+ composer = Composer.new(template)
30
+ value = yield composer, settings
31
+ if !template.children? && stringable?(value)
32
+ composer.add_text { value.to_s }
33
+ end
34
+ template
35
+ end
36
+
37
+ attr_reader :painter
38
+
39
+ def initialize(painter)
40
+ @painter = painter
41
+ end
42
+
43
+ def add_text(name=self.class.next_name, &block)
44
+ child_settings = Settings::TextSettings.inherit(painter.settings)
45
+ child = TextPainter.new(name, child_settings)
46
+ # TextPainters are not composable, so yield nil
47
+ content = yield nil, child_settings
48
+ unless self.class.stringable?(content)
49
+ raise ArgumentError, "Unsupported type for text: #{content.class}"
50
+ end
51
+ child.content = content.to_s
52
+ painter.add_child(child)
53
+ end
54
+
55
+ def add_box(name=self.class.next_name, &block)
56
+ child_settings = Settings::BoxSettings.inherit(painter.settings)
57
+ child = BoxPainter.new(name, child_settings)
58
+ composer = self.class.new(child)
59
+ value = yield composer, child.settings
60
+ painter.add_child(child)
61
+ if !child.children? && self.class.stringable?(value)
62
+ composer.add_text("#{name}-Text") { value.to_s }
63
+ end
64
+ end
65
+
66
+ def add_grid(name=self.class.next_name, &block)
67
+ child_settings = Settings::GridSettings.inherit(painter.settings)
68
+ child = GridPainter.new(name, child_settings)
69
+ composer = self.class.new(child)
70
+ values = yield composer, child.settings
71
+ painter.add_child(child)
72
+ if !child.children? && values.is_a?(Array)
73
+ values.each.with_index do |value, index|
74
+ composer.add_text("#{name}-Text-#{index}") { value.to_s }
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,72 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ class ContainerCoords
4
+ def initialize(dimensions, settings, start_left, start_top)
5
+ @dimensions = dimensions
6
+ @settings = settings
7
+ @start_left = start_left
8
+ @start_top = start_top
9
+ end
10
+
11
+ def left
12
+ start_left + settings.position.left
13
+ end
14
+
15
+ def top
16
+ start_top + settings.position.top
17
+ end
18
+
19
+ def border_left
20
+ left + settings.margin.left
21
+ end
22
+
23
+ def border_top
24
+ top + settings.margin.top
25
+ end
26
+
27
+ def padding_left
28
+ border_left + (settings.border.left? ? 1 : 0)
29
+ end
30
+
31
+ def padding_top
32
+ border_top + (settings.border.top? ? 1 : 0)
33
+ end
34
+
35
+ def content_left(col_index=0)
36
+ padding_left + settings.padding.left + col_index * grid_width
37
+ end
38
+
39
+ def content_top(row_index=0)
40
+ padding_top + settings.padding.top + row_index * grid_height
41
+ end
42
+
43
+ def inner_grid_width
44
+ settings.padding.left +
45
+ dimensions.content_width +
46
+ settings.padding.right
47
+ end
48
+
49
+ def grid_width
50
+ (settings.border.inner_vert? ? 1 : 0) +
51
+ inner_grid_width +
52
+ (settings.scrollbar.vert? ? 1 : 0)
53
+ end
54
+
55
+ def inner_grid_height
56
+ settings.padding.top +
57
+ dimensions.content_height +
58
+ settings.padding.bottom
59
+ end
60
+
61
+ def grid_height
62
+ (settings.border.inner_horiz? ? 1 : 0) +
63
+ inner_grid_height +
64
+ (settings.scrollbar.horiz? ? 1 : 0)
65
+ end
66
+
67
+ private
68
+
69
+ attr_reader :settings, :dimensions, :start_left, :start_top
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,93 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ class ContainerDimensions
4
+ attr_reader :children_width, :children_height, :num_cols, :num_rows
5
+
6
+ def initialize(settings, content_width, content_height, num_cols=1, num_rows=1)
7
+ @orig_content_width = content_width
8
+ @orig_content_height = content_height
9
+ @children_width = content_width
10
+ @children_height = content_height
11
+ @num_cols = num_cols
12
+ @num_rows = num_rows
13
+ @settings = settings
14
+ end
15
+
16
+ def content_width
17
+ return orig_content_width unless settings.width
18
+ if settings.border_sizing?
19
+ settings.width - outer_border_width - scrollbar_width - padding_width
20
+ else
21
+ settings.width
22
+ end
23
+ end
24
+
25
+ def content_height
26
+ return orig_content_height unless settings.height
27
+ if settings.border_sizing?
28
+ settings.height - outer_border_height - scrollbar_height - padding_height
29
+ else
30
+ settings.height
31
+ end
32
+ end
33
+
34
+ def outer_width
35
+ @outer_width ||= margin_width +
36
+ outer_border_width +
37
+ num_cols * (padding_width + content_width + scrollbar_width) +
38
+ (num_cols - 1) * inner_border_width
39
+ end
40
+
41
+ def outer_height
42
+ @outer_height ||= margin_height +
43
+ outer_border_height +
44
+ num_rows * (padding_height + content_height + scrollbar_height) +
45
+ (num_rows - 1) * inner_border_height
46
+ end
47
+
48
+ def margin_width
49
+ settings.margin.left + settings.margin.right
50
+ end
51
+
52
+ def margin_height
53
+ settings.margin.top + settings.margin.bottom
54
+ end
55
+
56
+ def outer_border_width
57
+ (settings.border.left? ? 1 : 0) + (settings.border.right? ? 1 : 0)
58
+ end
59
+
60
+ def outer_border_height
61
+ (settings.border.top? ? 1 : 0) + (settings.border.bottom? ? 1 : 0)
62
+ end
63
+
64
+ def inner_border_width
65
+ settings.border.inner_vert? ? 1 : 0
66
+ end
67
+
68
+ def inner_border_height
69
+ settings.border.inner_horiz? ? 1 : 0
70
+ end
71
+
72
+ def padding_width
73
+ settings.padding.left + settings.padding.right
74
+ end
75
+
76
+ def padding_height
77
+ settings.padding.top + settings.padding.bottom
78
+ end
79
+
80
+ def scrollbar_width
81
+ settings.scrollbar.vert? ? 1 : 0
82
+ end
83
+
84
+ def scrollbar_height
85
+ settings.scrollbar.horiz? ? 1 : 0
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :settings, :orig_content_width, :orig_content_height
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,272 @@
1
+ require 'whirled_peas/utils/formatted_string'
2
+
3
+ require_relative 'container_coords'
4
+ require_relative 'painter'
5
+
6
+ module WhirledPeas
7
+ module Graphics
8
+ class ContainerPainter < Painter
9
+ PADDING = ' '
10
+
11
+ def initialize(name, settings)
12
+ super
13
+ @children = []
14
+ end
15
+
16
+ def paint(canvas, left, top, &block)
17
+ return unless canvas.writable?
18
+ return unless needs_printing?
19
+ canvas_coords = coords(left, top)
20
+ stroke_left = canvas_coords.border_left
21
+ stroke_top = canvas_coords.border_top
22
+ formatting = [*settings.border.color, *settings.bg_color]
23
+ if settings.border.top?
24
+ canvas.stroke(stroke_left, stroke_top, top_border_stroke(canvas_coords), formatting, &block)
25
+ stroke_top += 1
26
+ end
27
+ middle_border = dimensions.num_rows > 1 ? middle_border_stroke(canvas_coords) : ''
28
+ dimensions.num_rows.times do |row_num|
29
+ if row_num > 0 && settings.border.inner_horiz?
30
+ canvas.stroke(stroke_left, stroke_top, middle_border, formatting, &block)
31
+ stroke_top += 1
32
+ end
33
+ canvas_coords.inner_grid_height.times do |row_within_cell|
34
+ canvas.stroke(stroke_left, stroke_top, content_line_stroke(canvas_coords, row_within_cell), formatting, &block)
35
+ stroke_top += 1
36
+ end
37
+ if settings.scrollbar.horiz?
38
+ canvas.stroke(stroke_left, stroke_top, bottom_scroll_stroke(canvas_coords), formatting, &block)
39
+ stroke_top += 1
40
+ end
41
+ end
42
+ if settings.border.bottom?
43
+ canvas.stroke(stroke_left, stroke_top, bottom_border_stroke(canvas_coords), formatting, &block)
44
+ stroke_top += 1
45
+ end
46
+ end
47
+
48
+ def add_child(child)
49
+ children << child
50
+ end
51
+
52
+ def num_children
53
+ children.length
54
+ end
55
+
56
+ def children?
57
+ num_children > 0
58
+ end
59
+
60
+ def each_child(&block)
61
+ children.each(&block)
62
+ end
63
+
64
+ private
65
+
66
+ attr_reader :children
67
+
68
+ def needs_printing?
69
+ return true if settings.bg_color
70
+ return true if settings.border.outer?
71
+ return true if dimensions.num_cols > 1 && settings.border.inner_vert?
72
+ return true if dimensions.num_rows > 1 && settings.border.inner_horiz?
73
+ settings.scrollbar.horiz? || settings.scrollbar.vert?
74
+ end
75
+
76
+ def coords(left, top)
77
+ ContainerCoords.new(dimensions, settings, left, top)
78
+ end
79
+
80
+ def horiz_justify_offset(containing_width)
81
+ if settings.align_center?
82
+ [(dimensions.content_width - containing_width) / 2, 0]
83
+ elsif settings.align_right?
84
+ [dimensions.content_width - containing_width, 0]
85
+ elsif settings.align_between?
86
+ return [0, 0] if num_children == 1
87
+ [0, (dimensions.content_width - containing_width) / (num_children - 1)]
88
+ elsif settings.align_around?
89
+ full_spacing = (dimensions.content_width - containing_width) / num_children
90
+ [full_spacing / 2, full_spacing]
91
+ elsif settings.align_evenly?
92
+ spacing = (dimensions.content_width - containing_width) / (num_children + 1)
93
+ [spacing, spacing]
94
+ else
95
+ [0, 0]
96
+ end
97
+ end
98
+
99
+ def vert_justify_offset(containing_height)
100
+ if settings.valign_middle?
101
+ [(dimensions.content_height - containing_height) / 2, 0]
102
+ elsif settings.valign_bottom?
103
+ [dimensions.content_height - containing_height, 0]
104
+ elsif settings.valign_between?
105
+ return [0, 0] if num_children == 1
106
+ [0, (dimensions.content_height - containing_height) / (num_children - 1)]
107
+ elsif settings.valign_around?
108
+ full_spacing = (dimensions.content_height - containing_height) / num_children
109
+ [full_spacing / 2, full_spacing]
110
+ elsif settings.valign_evenly?
111
+ spacing = (dimensions.content_height - containing_height) / (num_children + 1)
112
+ [spacing, spacing]
113
+ else
114
+ [0, 0]
115
+ end
116
+ end
117
+
118
+ def line_stroke(left_border, junc_border, right_border, &block)
119
+ stroke = ''
120
+ stroke += left_border if settings.border.left?
121
+ dimensions.num_cols.times do |col_num|
122
+ stroke += junc_border if col_num > 0 && settings.border.inner_vert?
123
+ stroke += yield
124
+ end
125
+ stroke += right_border if settings.border.right?
126
+ stroke
127
+ end
128
+
129
+ def top_border_stroke(canvas_coords)
130
+ line_stroke(
131
+ settings.border.style.top_left,
132
+ settings.border.style.top_junc,
133
+ settings.border.style.top_right
134
+ ) do
135
+ settings.border.style.top_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
136
+ end
137
+ end
138
+
139
+ def middle_border_stroke(canvas_coords)
140
+ line_stroke(
141
+ settings.border.style.left_junc,
142
+ settings.border.style.cross_junc,
143
+ settings.border.style.right_junc
144
+ ) do
145
+ settings.border.style.middle_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
146
+ end
147
+ end
148
+
149
+ def bottom_border_stroke(canvas_coords)
150
+ line_stroke(
151
+ settings.border.style.bottom_left,
152
+ settings.border.style.bottom_junc,
153
+ settings.border.style.bottom_right
154
+ ) do
155
+ settings.border.style.bottom_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
156
+ end
157
+ end
158
+
159
+ def content_line_stroke(canvas_coords, row_within_cell)
160
+ line_stroke(
161
+ settings.border.style.left_vert,
162
+ settings.border.style.middle_vert,
163
+ settings.border.style.right_vert,
164
+ ) do
165
+ if settings.scrollbar.vert?
166
+ if dimensions.children_height <= canvas_coords.grid_height || children.first.settings.position.top > 0
167
+ scrollbar_char = GUTTER
168
+ else
169
+ scrollbar_char = vert_scroll_char(
170
+ dimensions.children_height + dimensions.padding_height,
171
+ canvas_coords.inner_grid_height,
172
+ -children.first.settings.position.top,
173
+ row_within_cell
174
+ )
175
+ end
176
+ PADDING * canvas_coords.inner_grid_width + scrollbar_char
177
+ else
178
+ PADDING * canvas_coords.inner_grid_width
179
+ end
180
+ end
181
+ end
182
+
183
+ def bottom_scroll_stroke(canvas_coords)
184
+ line_stroke(
185
+ settings.border.style.left_vert,
186
+ settings.border.style.middle_vert,
187
+ settings.border.style.right_vert,
188
+ ) do
189
+ canvas_coords.inner_grid_width.times.map do |col_within_cell|
190
+ horiz_scroll_char(
191
+ dimensions.children_width + dimensions.padding_width,
192
+ canvas_coords.inner_grid_width,
193
+ -children.first.settings.position.left,
194
+ col_within_cell
195
+ )
196
+ end.join
197
+ end
198
+ end
199
+
200
+ GUTTER = ' '
201
+ HORIZONTAL = %w[▗ ▄ ▖]
202
+ VERTICAL = %w[
203
+
204
+
205
+
206
+ ]
207
+
208
+ def horiz_scroll_char(col_count, viewable_col_count, first_visible_col, curr_col)
209
+ scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
210
+ end
211
+
212
+ def vert_scroll_char(row_count, viewable_row_count, first_visible_row, curr_row)
213
+ scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
214
+ end
215
+
216
+ private
217
+
218
+ def scroll_char(total_count, viewable_count, first_visible, curr, chars)
219
+ return GUTTER unless total_count > 0 && viewable_count > 0
220
+ # The scroll handle has the exact same relative size and position in the scroll gutter
221
+ # that the viewable content has in the total content area. For example, a content area
222
+ # that is 50 columns wide with a view port that is 20 columns wide might look like
223
+ #
224
+ # +---------1-----****2*********3******---4---------+
225
+ # | * * |
226
+ # | hidden * viewable * hidden |
227
+ # | * * |
228
+ # +---------1-----****2*********3******---4---------+
229
+ #
230
+ # The scoll gutter, would look like
231
+ #
232
+ # |......********.....|
233
+ #
234
+ # Scrolling all the way to the right results in
235
+ #
236
+ # +---------1---------2---------3*********4*********+
237
+ # | * *
238
+ # | hidden * viewable *
239
+ # | * *
240
+ # +---------1---------2---------3*********4*********+
241
+ # |...........********|
242
+
243
+ # The first task of determining how much of the handle is visible in a row/column is to
244
+ # calculate the range (as a precentage of the total) of viewable items
245
+ viewable_start = first_visible.to_f / total_count
246
+ viewable_end = (first_visible + viewable_count).to_f / total_count
247
+
248
+ # Always use the same length for the scroll bar so it does not give an inchworm effect
249
+ # as it scrolls along.
250
+ #
251
+ # Also, double the value now to get granularity for half width
252
+ # scrollbar characters.
253
+ scrollbar_length = ((2 * viewable_count ** 2).to_f / total_count).ceil
254
+ scrollbar_start = ((2 * first_visible * viewable_count).to_f / total_count).floor
255
+
256
+ first_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr)
257
+ second_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr + 1)
258
+
259
+ if first_half && second_half
260
+ chars[1]
261
+ elsif first_half
262
+ chars[2]
263
+ elsif second_half
264
+ chars[0]
265
+ else
266
+ GUTTER
267
+ end
268
+ end
269
+ end
270
+ private_constant :ContainerPainter
271
+ end
272
+ end