whirled_peas 0.3.0 → 0.7.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 (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