whirled_peas 0.1.1 → 0.5.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile +1 -0
  5. data/README.md +240 -79
  6. data/Rakefile +37 -3
  7. data/bin/debug +35 -0
  8. data/exe/whirled_peas +7 -0
  9. data/lib/whirled_peas.rb +17 -37
  10. data/lib/whirled_peas/command_line.rb +263 -0
  11. data/lib/whirled_peas/config.rb +21 -0
  12. data/lib/whirled_peas/debugger.rb +80 -0
  13. data/lib/whirled_peas/errors.rb +7 -0
  14. data/lib/whirled_peas/frame.rb +0 -8
  15. data/lib/whirled_peas/frame/consumer.rb +14 -51
  16. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  17. data/lib/whirled_peas/frame/event_loop.rb +68 -38
  18. data/lib/whirled_peas/frame/producer.rb +37 -34
  19. data/lib/whirled_peas/graphics.rb +5 -0
  20. data/lib/whirled_peas/graphics/box_painter.rb +100 -0
  21. data/lib/whirled_peas/graphics/canvas.rb +107 -0
  22. data/lib/whirled_peas/graphics/container_coords.rb +61 -0
  23. data/lib/whirled_peas/graphics/container_dimensions.rb +65 -0
  24. data/lib/whirled_peas/graphics/container_painter.rb +116 -0
  25. data/lib/whirled_peas/graphics/debugger.rb +43 -0
  26. data/lib/whirled_peas/graphics/grid_painter.rb +56 -0
  27. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  28. data/lib/whirled_peas/graphics/painter.rb +24 -0
  29. data/lib/whirled_peas/graphics/renderer.rb +49 -0
  30. data/lib/whirled_peas/graphics/screen.rb +65 -0
  31. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  32. data/lib/whirled_peas/graphics/text_painter.rb +38 -0
  33. data/lib/whirled_peas/settings.rb +5 -0
  34. data/lib/whirled_peas/settings/bg_color.rb +22 -0
  35. data/lib/whirled_peas/settings/border.rb +101 -0
  36. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  37. data/lib/whirled_peas/settings/color.rb +69 -0
  38. data/lib/whirled_peas/settings/container_settings.rb +128 -0
  39. data/lib/whirled_peas/settings/debugger.rb +87 -0
  40. data/lib/whirled_peas/settings/display_flow.rb +25 -0
  41. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  42. data/lib/whirled_peas/settings/grid_settings.rb +11 -0
  43. data/lib/whirled_peas/settings/margin.rb +8 -0
  44. data/lib/whirled_peas/settings/padding.rb +8 -0
  45. data/lib/whirled_peas/settings/position.rb +15 -0
  46. data/lib/whirled_peas/settings/spacing.rb +24 -0
  47. data/lib/whirled_peas/settings/text_align.rb +19 -0
  48. data/lib/whirled_peas/settings/text_color.rb +19 -0
  49. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  50. data/lib/whirled_peas/template.rb +5 -0
  51. data/lib/whirled_peas/template/box_element.rb +8 -0
  52. data/lib/whirled_peas/template/composer.rb +68 -0
  53. data/lib/whirled_peas/template/container.rb +28 -0
  54. data/lib/whirled_peas/template/debugger.rb +34 -0
  55. data/lib/whirled_peas/template/element.rb +13 -0
  56. data/lib/whirled_peas/template/grid_element.rb +8 -0
  57. data/lib/whirled_peas/template/text_element.rb +24 -0
  58. data/lib/whirled_peas/utils.rb +5 -0
  59. data/lib/whirled_peas/utils/ansi.rb +53 -0
  60. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  61. data/lib/whirled_peas/utils/title_font.rb +75 -0
  62. data/lib/whirled_peas/version.rb +1 -1
  63. data/screen_test/rendered/elements/box.frame +1 -0
  64. data/screen_test/rendered/elements/box.rb +20 -0
  65. data/screen_test/rendered/elements/grid.frame +1 -0
  66. data/screen_test/rendered/elements/grid.rb +13 -0
  67. data/screen_test/rendered/elements/screen_overflow.rb +9 -0
  68. data/screen_test/rendered/elements/text.frame +1 -0
  69. data/screen_test/rendered/elements/text.rb +9 -0
  70. data/screen_test/rendered/elements/text_multiline.frame +1 -0
  71. data/screen_test/rendered/elements/text_multiline.rb +9 -0
  72. data/screen_test/rendered/settings/align/box.frame +1 -0
  73. data/screen_test/rendered/settings/align/box.rb +20 -0
  74. data/screen_test/rendered/settings/align/children_center.frame +1 -0
  75. data/screen_test/rendered/settings/align/children_center.rb +13 -0
  76. data/screen_test/rendered/settings/align/children_left.frame +1 -0
  77. data/screen_test/rendered/settings/align/children_left.rb +13 -0
  78. data/screen_test/rendered/settings/align/children_right.frame +1 -0
  79. data/screen_test/rendered/settings/align/children_right.rb +13 -0
  80. data/screen_test/rendered/settings/align/grid.frame +1 -0
  81. data/screen_test/rendered/settings/align/grid.rb +20 -0
  82. data/screen_test/rendered/settings/ansi/bold.frame +1 -0
  83. data/screen_test/rendered/settings/ansi/bold.rb +15 -0
  84. data/screen_test/rendered/settings/ansi/color.frame +1 -0
  85. data/screen_test/rendered/settings/ansi/color.rb +37 -0
  86. data/screen_test/rendered/settings/ansi/underline.frame +1 -0
  87. data/screen_test/rendered/settings/ansi/underline.rb +15 -0
  88. data/screen_test/rendered/settings/border.frame +1 -0
  89. data/screen_test/rendered/settings/border.rb +13 -0
  90. data/screen_test/rendered/settings/flow/l2r.frame +1 -0
  91. data/screen_test/rendered/settings/flow/l2r.rb +24 -0
  92. data/screen_test/rendered/settings/flow/t2b.frame +1 -0
  93. data/screen_test/rendered/settings/flow/t2b.rb +24 -0
  94. data/screen_test/rendered/settings/height/box.frame +1 -0
  95. data/screen_test/rendered/settings/height/box.rb +13 -0
  96. data/screen_test/rendered/settings/height/grid.frame +1 -0
  97. data/screen_test/rendered/settings/height/grid.rb +14 -0
  98. data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
  99. data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
  100. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
  101. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
  102. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
  103. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
  104. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
  105. data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
  106. data/screen_test/rendered/settings/margin.frame +1 -0
  107. data/screen_test/rendered/settings/margin.rb +14 -0
  108. data/screen_test/rendered/settings/padding.frame +1 -0
  109. data/screen_test/rendered/settings/padding.rb +11 -0
  110. data/screen_test/rendered/settings/position/box_left.frame +1 -0
  111. data/screen_test/rendered/settings/position/box_left.rb +17 -0
  112. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
  113. data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
  114. data/screen_test/rendered/settings/position/box_top.frame +1 -0
  115. data/screen_test/rendered/settings/position/box_top.rb +17 -0
  116. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
  117. data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
  118. data/screen_test/rendered/settings/position/grid_left.frame +1 -0
  119. data/screen_test/rendered/settings/position/grid_left.rb +18 -0
  120. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
  121. data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
  122. data/screen_test/rendered/settings/position/grid_top.frame +1 -0
  123. data/screen_test/rendered/settings/position/grid_top.rb +18 -0
  124. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
  125. data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
  126. data/screen_test/rendered/settings/title_font.frame +1 -0
  127. data/screen_test/rendered/settings/title_font.rb +12 -0
  128. data/screen_test/rendered/settings/width/box.frame +1 -0
  129. data/screen_test/rendered/settings/width/box.rb +13 -0
  130. data/screen_test/rendered/settings/width/grid.frame +1 -0
  131. data/screen_test/rendered/settings/width/grid.rb +14 -0
  132. data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
  133. data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
  134. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
  135. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
  136. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
  137. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
  138. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
  139. data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
  140. data/screen_test/screen_tester.rb +191 -0
  141. data/whirled_peas.gemspec +4 -2
  142. metadata +136 -20
  143. data/lib/whirled_peas/ui.rb +0 -7
  144. data/lib/whirled_peas/ui/ansi.rb +0 -154
  145. data/lib/whirled_peas/ui/canvas.rb +0 -35
  146. data/lib/whirled_peas/ui/element.rb +0 -199
  147. data/lib/whirled_peas/ui/painter.rb +0 -283
  148. data/lib/whirled_peas/ui/screen.rb +0 -62
  149. data/lib/whirled_peas/ui/settings.rb +0 -512
  150. data/lib/whirled_peas/ui/stroke.rb +0 -29
  151. data/sandbox/auto.rb +0 -13
  152. data/sandbox/box.rb +0 -19
  153. data/sandbox/grid.rb +0 -13
  154. data/sandbox/sandbox.rb +0 -17
  155. data/sandbox/text.rb +0 -33
@@ -1,7 +0,0 @@
1
- require_relative 'ui/element'
2
- require_relative 'ui/screen'
3
-
4
- module WhirledPeas
5
- module UI
6
- end
7
- end
@@ -1,154 +0,0 @@
1
- module WhirledPeas
2
- module UI
3
- DEBUG_COLOR = ARGV.include?('--debug-color')
4
-
5
- module Ansi
6
- BOLD = 1
7
- UNDERLINE = 4
8
-
9
- BLACK = 30
10
- RED = 31
11
- GREEN = 32
12
- YELLOW = 33
13
- BLUE = 34
14
- MAGENTA = 35
15
- CYAN = 36
16
- WHITE = 37
17
-
18
- END_FORMATTING = 0
19
-
20
- class << self
21
- def format(str, codes)
22
- if str.empty? || codes.length == 0
23
- str
24
- else
25
- start_formatting = codes.map(&method(:esc_seq)).join
26
- "#{start_formatting}#{str}#{esc_seq(END_FORMATTING)}"
27
- end
28
- end
29
-
30
- def clear
31
- esc_seq(END_FORMATTING)
32
- end
33
-
34
- def hidden_width(line)
35
- return 0 if DEBUG_COLOR
36
- width = 0
37
- line.scan(/\033\[\d+m/).each { |f| width += f.length }
38
- width
39
- end
40
-
41
- def close_formatting(line)
42
- codes = line.scan(DEBUG_COLOR ? /<(\d+)>/ : /\033\[(\d+)m/)
43
- if codes.length > 0 && codes.last[0] != END_FORMATTING.to_s
44
- "#{line}#{esc_seq(END_FORMATTING)}"
45
- else
46
- line
47
- end
48
- end
49
-
50
- def first(str, num_visible_chars)
51
- return str if str.length <= num_visible_chars + hidden_width(str)
52
- result = ''
53
- in_format = false
54
- visible_len = 0
55
- str.chars.each do |char|
56
- in_format = true if !in_format && char == "\033"
57
- result += char
58
- visible_len += 1 if !in_format
59
- in_format = false if in_format && char == 'm'
60
- break if visible_len == num_visible_chars
61
- end
62
- close_formatting(result)
63
- end
64
-
65
- private
66
-
67
- def esc_seq(code)
68
- DEBUG_COLOR ? "<#{code}>" : "\033[#{code}m"
69
- end
70
- end
71
- end
72
-
73
- class Color
74
- BRIGHT_OFFSET = 60
75
- private_constant :BRIGHT_OFFSET
76
-
77
- def self.validate!(color)
78
- return unless color
79
- if color.is_a?(Symbol)
80
- error_message = "Unsupported #{self.name.split('::').last}: #{color}"
81
- match = color.to_s.match(/^(bright_)?(\w+)$/)
82
- begin
83
- color = self.const_get(match[2].upcase)
84
- raise ArgumentError, error_message unless color.is_a?(Color)
85
- if match[1]
86
- raise ArgumentError, error_message if color.bright?
87
- color.bright
88
- else
89
- color
90
- end
91
- rescue NameError
92
- raise ArgumentError, error_message
93
- end
94
- else
95
- color
96
- end
97
- end
98
-
99
- def initialize(code, bright=false)
100
- @code = code
101
- @bright = bright
102
- end
103
-
104
- def bright?
105
- @bright
106
- end
107
-
108
- def bright
109
- bright? ? self : self.class.new(@code + BRIGHT_OFFSET, true)
110
- end
111
-
112
- def to_s
113
- @code.to_s
114
- end
115
-
116
- def inspect
117
- "#{self.class.name.split('::').last}(code=#{@code}, bright=#{@bright})"
118
- end
119
- end
120
- private_constant :Color
121
-
122
- class BgColor < Color
123
- BG_OFFSET = 10
124
- private_constant :BG_OFFSET
125
-
126
- BLACK = new(Ansi::BLACK + BG_OFFSET)
127
- RED = new(Ansi::RED + BG_OFFSET)
128
- GREEN = new(Ansi::GREEN + BG_OFFSET)
129
- YELLOW = new(Ansi::YELLOW + BG_OFFSET)
130
- BLUE = new(Ansi::BLUE + BG_OFFSET)
131
- MAGENTA = new(Ansi::MAGENTA + BG_OFFSET)
132
- CYAN = new(Ansi::CYAN + BG_OFFSET)
133
- WHITE = new(Ansi::WHITE + BG_OFFSET)
134
- GRAY = BLACK.bright
135
- end
136
-
137
- class TextColor < Color
138
- BLACK = new(Ansi::BLACK)
139
- RED = new(Ansi::RED)
140
- GREEN = new(Ansi::GREEN)
141
- YELLOW = new(Ansi::YELLOW)
142
- BLUE = new(Ansi::BLUE)
143
- MAGENTA = new(Ansi::MAGENTA)
144
- CYAN = new(Ansi::CYAN)
145
- WHITE = new(Ansi::WHITE)
146
- GRAY = BLACK.bright
147
- end
148
-
149
- module TextFormat
150
- BOLD = Ansi::BOLD
151
- UNDERLINE = Ansi::UNDERLINE
152
- end
153
- end
154
- end
@@ -1,35 +0,0 @@
1
- require_relative 'stroke'
2
-
3
- module WhirledPeas
4
- module UI
5
- class Canvas
6
- attr_reader :left, :top, :width, :height
7
-
8
- def initialize(left, top, width, height)
9
- @left = left
10
- @top = top
11
- @width = width
12
- @height = height
13
- end
14
-
15
- def stroke(left, top, chars)
16
- if left >= self.left + self.width || left + chars.length <= self.left
17
- Stroke::EMPTY
18
- elsif top < self.top || top >= self.top + self.height
19
- Stroke::EMPTY
20
- else
21
- if left < self.left
22
- chars = chars[self.left - left..-1]
23
- left = self.left
24
- end
25
- num_chars = [self.left + self.width, left + chars.length].min - left
26
- Stroke.new(left, top, Ansi.first(chars, num_chars))
27
- end
28
- end
29
-
30
- def inspect
31
- "Canvas(left=#{left}, top=#{top}, width=#{width}, height=#{height})"
32
- end
33
- end
34
- end
35
- end
@@ -1,199 +0,0 @@
1
- require_relative 'settings'
2
-
3
- module WhirledPeas
4
- module UI
5
- class Element
6
- attr_accessor :preferred_width, :preferred_height
7
- attr_reader :settings
8
-
9
- def initialize(settings)
10
- @settings = settings
11
- end
12
- end
13
- private_constant :Element
14
-
15
- class TextElement < Element
16
- attr_reader :value
17
-
18
- def initialize(settings)
19
- super(TextSettings.merge(settings))
20
- end
21
-
22
- def value=(val)
23
- @value = val
24
- @preferred_width = settings.width || value.length
25
- @preferred_height = 1
26
- end
27
-
28
- def inspect(indent='')
29
- dims = unless preferred_width.nil?
30
- "#{indent + ' '}Dimensions: #{preferred_width}x#{preferred_height}"
31
- end
32
- [
33
- "#{indent}#{self.class.name}",
34
- dims,
35
- "#{indent + ' '}Settings",
36
- settings.inspect(indent + ' ')
37
- ].compact.join("\n")
38
- end
39
- end
40
-
41
- class ComposableElement < Element
42
- def initialize(settings)
43
- super
44
- end
45
-
46
- def children
47
- @children ||= []
48
- end
49
-
50
- def add_text(&block)
51
- element = TextElement.new(settings)
52
- element.value = yield nil, element.settings
53
- children << element
54
- end
55
-
56
- def add_box(&block)
57
- element = BoxElement.new(settings)
58
- value = yield element, element.settings
59
- children << element
60
- if element.children.empty? && value.is_a?(String)
61
- element.add_text { value }
62
- end
63
- end
64
-
65
- def add_grid(&block)
66
- element = GridElement.new(settings)
67
- values = yield element, element.settings
68
- children << element
69
- if element.children.empty? && values.is_a?(Array)
70
- values.each { |v| element.add_text { v } }
71
- end
72
- end
73
-
74
- def inspect(indent='')
75
- kids = children.map { |c| c.inspect(indent + ' ') }.join("\n")
76
- dims = unless preferred_width.nil?
77
- "#{indent + ' '}Dimensions: #{preferred_width}x#{preferred_height}"
78
- end
79
- [
80
- "#{indent}#{self.class.name}",
81
- dims,
82
- "#{indent + ' '}Settings",
83
- settings.inspect(indent + ' '),
84
- "#{indent + ' '}Children",
85
- kids
86
- ].compact.join("\n")
87
- end
88
- end
89
-
90
- class Template < ComposableElement
91
- def initialize(settings=TemplateSettings.new)
92
- super(settings)
93
- end
94
- end
95
-
96
- class BoxElement < ComposableElement
97
- attr_writer :content_width, :content_height
98
-
99
- def initialize(settings)
100
- super(BoxSettings.merge(settings))
101
- end
102
-
103
- def self.from_template(template, width, height)
104
- box = new(template.settings)
105
- template.children.each { |c| box.children << c }
106
- box.content_width = box.preferred_width = width
107
- box.content_height = box.preferred_height = height
108
- box
109
- end
110
-
111
- def content_width
112
- @content_width ||= begin
113
- child_widths = children.map(&:preferred_width)
114
- width = settings.horizontal_flow? ? child_widths.sum : (child_widths.max || 0)
115
- [width, *settings.width].max
116
- end
117
- end
118
-
119
- def preferred_width
120
- @preferred_width ||= settings.margin.left +
121
- (settings.border.left? ? 1 : 0) +
122
- settings.padding.left +
123
- content_width +
124
- settings.padding.right +
125
- (settings.border.right? ? 1 : 0) +
126
- settings.margin.right
127
- end
128
-
129
- def content_height
130
- @content_height ||= begin
131
- child_heights = children.map(&:preferred_height)
132
- settings.vertical_flow? ? child_heights.sum : (child_heights.max || 0)
133
- end
134
- end
135
-
136
- def preferred_height
137
- @preferred_height ||= settings.margin.top +
138
- (settings.border.top? ? 1 : 0) +
139
- settings.padding.top +
140
- content_height +
141
- settings.padding.bottom +
142
- (settings.border.bottom? ? 1 : 0) +
143
- settings.margin.bottom
144
- end
145
- end
146
-
147
- class GridElement < ComposableElement
148
- def initialize(settings)
149
- super(GridSettings.merge(settings))
150
- end
151
-
152
- def col_width
153
- return @col_width if @col_width
154
- @col_width = 0
155
- children.each do |child|
156
- @col_width = child.preferred_width if child.preferred_width > @col_width
157
- end
158
- @col_width
159
- end
160
-
161
- def row_height
162
- return @row_height if @row_height
163
- @row_height = 0
164
- children.each do |child|
165
- @row_height = child.preferred_height if child.preferred_height > @row_height
166
- end
167
- @row_height
168
- end
169
-
170
-
171
- def preferred_width
172
- settings.margin.left +
173
- (settings.border.left? ? 1 : 0) +
174
- settings.num_cols * (
175
- settings.padding.left +
176
- col_width +
177
- settings.padding.right
178
- ) +
179
- (settings.num_cols - 1) * (settings.border.inner_vert? ? 1 : 0) +
180
- (settings.border.right? ? 1 : 0) +
181
- settings.margin.right
182
- end
183
-
184
- def preferred_height
185
- num_rows = (children.length / settings.num_cols).ceil
186
- settings.margin.top +
187
- (settings.border.top? ? 1 : 0) +
188
- num_rows * (
189
- settings.padding.top +
190
- row_height +
191
- settings.padding.bottom
192
- ) +
193
- (num_rows - 1) * (settings.border.inner_horiz? ? 1 : 0) +
194
- (settings.border.bottom? ? 1 : 0) +
195
- settings.margin.bottom
196
- end
197
- end
198
- end
199
- end
@@ -1,283 +0,0 @@
1
- require_relative 'ansi'
2
- require_relative 'canvas'
3
- require_relative 'settings'
4
-
5
- module WhirledPeas
6
- module UI
7
- DEBUG_SPACING = ARGV.include?('--debug-spacing')
8
-
9
- class TextPainter
10
- JUSTIFICATION = DEBUG_SPACING ? 'j' : ' '
11
-
12
- def initialize(text, canvas)
13
- @text = text
14
- @canvas = canvas
15
- end
16
-
17
- def paint(&block)
18
- yield canvas.stroke(canvas.left, canvas.top, justified)
19
- end
20
-
21
- private
22
-
23
- attr_reader :text, :canvas
24
-
25
- def visible
26
- if text.value.length <= text.preferred_width
27
- text.value
28
- elsif text.settings.align == TextAlign::LEFT
29
- text.value[0..text.preferred_width - 1]
30
- elsif text.settings.align == TextAlign::CENTER
31
- left_chop = (text.value.length - text.preferred_width) / 2
32
- right_chop = text.value.length - text.preferred_width - left_chop
33
- text.value[left_chop..-right_chop - 1]
34
- else
35
- text.value[-text.preferred_width..-1]
36
- end
37
- end
38
-
39
- def justified
40
- format_settings = [*text.settings.color, *text.settings.bg_color]
41
- format_settings << TextFormat::BOLD if text.settings.bold?
42
- format_settings << TextFormat::UNDERLINE if text.settings.underline?
43
-
44
- ljust = case text.settings.align
45
- when TextAlign::LEFT
46
- 0
47
- when TextAlign::CENTER
48
- [0, (text.preferred_width - text.value.length) / 2].max
49
- when TextAlign::RIGHT
50
- [0, text.preferred_width - text.value.length].max
51
- end
52
- rjust = [0, text.preferred_width - text.value.length - ljust].max
53
- Ansi.format(JUSTIFICATION * ljust, [*text.settings.bg_color]) +
54
- Ansi.format(visible, format_settings) +
55
- Ansi.format(JUSTIFICATION * rjust, [*text.settings.bg_color])
56
- end
57
- end
58
- private_constant :TextPainter
59
-
60
- class ContainerPainter
61
- PADDING = DEBUG_SPACING ? 'p' : ' '
62
-
63
- def initialize(container, canvas)
64
- @container = container
65
- @settings = container.settings
66
- @canvas = canvas
67
- end
68
-
69
- def paint(&block)
70
- return if container.num_rows == 0 || container.num_cols == 0
71
- top = canvas.top + settings.margin.top
72
- if settings.auto_margin?
73
- left = canvas.left + (canvas.width - container.preferred_width) / 2
74
- else
75
- left = canvas.left + settings.margin.left
76
- end
77
- if settings.border.top?
78
- yield canvas.stroke(left, top, top_border)
79
- top += 1
80
- end
81
- container.num_rows.times do |row_num|
82
- if row_num > 0 && settings.border.inner_horiz?
83
- yield canvas.stroke(left, top, middle_border)
84
- top += 1
85
- end
86
- (settings.padding.top + container.row_height + settings.padding.bottom).times do
87
- yield canvas.stroke(left, top, content_line)
88
- top += 1
89
- end
90
- end
91
- if settings.border.bottom?
92
- yield canvas.stroke(left, top, bottom_border)
93
- top += 1
94
- end
95
- end
96
-
97
- private
98
-
99
- attr_reader :container, :settings, :canvas
100
-
101
- def line_stroke(left_border, horiz_border, junc_border, right_border)
102
- stroke = ''
103
- stroke += left_border if settings.border.left?
104
- container.num_cols.times do |col_num|
105
- stroke += junc_border if col_num > 0 && settings.border.inner_horiz?
106
- stroke += horiz_border * (container.col_width + settings.padding.left + settings.padding.right)
107
- end
108
- stroke += right_border if settings.border.right?
109
- Ansi.format(stroke, [*settings.border.color, *settings.bg_color])
110
- end
111
-
112
- def top_border
113
- line_stroke(
114
- settings.border.style.top_left,
115
- settings.border.style.top_horiz,
116
- settings.border.style.top_junc,
117
- settings.border.style.top_right
118
- )
119
- end
120
-
121
- def content_line
122
- line_stroke(
123
- settings.border.style.left_vert,
124
- PADDING,
125
- settings.border.style.middle_vert,
126
- settings.border.style.right_vert
127
- )
128
- end
129
-
130
- def middle_border
131
- line_stroke(
132
- settings.border.style.left_junc,
133
- settings.border.style.middle_horiz,
134
- settings.border.style.cross_junc,
135
- settings.border.style.right_junc
136
- )
137
- end
138
-
139
- def bottom_border
140
- line_stroke(
141
- settings.border.style.bottom_left,
142
- settings.border.style.bottom_horiz,
143
- settings.border.style.bottom_junc,
144
- settings.border.style.bottom_right
145
- )
146
- end
147
- end
148
-
149
- class BoxContainer
150
- attr_reader :settings, :num_cols, :num_rows, :col_width, :row_height, :preferred_width
151
-
152
- def initialize(box)
153
- @settings = ContainerSettings.merge(box.settings)
154
- @num_cols = 1
155
- @num_rows = 1
156
- @col_width = box.content_width
157
- @row_height = box.content_height
158
- @preferred_width = box.preferred_width
159
- end
160
- end
161
-
162
- class BoxPainter
163
- def initialize(box, canvas)
164
- @box = box
165
- @canvas = canvas
166
- end
167
-
168
- def paint(&block)
169
- container = BoxContainer.new(box)
170
- ContainerPainter.new(container, canvas).paint(&block)
171
- top = canvas.top + box.settings.margin.top + (box.settings.border.top? ? 1 : 0) + box.settings.padding.top
172
- if box.settings.auto_margin?
173
- margin = (canvas.width - box.preferred_width) / 2
174
- else
175
- margin = box.settings.margin.left
176
- end
177
- left = canvas.left + margin + (box.settings.border.left? ? 1 : 0) + box.settings.padding.left
178
- greedy_width = box.settings.vertical_flow? || box.children.length == 1
179
- children = box.children
180
- children = children.reverse if box.settings.reverse_flow?
181
- children.each do |child|
182
- if greedy_width
183
- width = box.content_width
184
- height = child.preferred_height
185
- else
186
- width = child.preferred_width
187
- height = box.content_height
188
- end
189
- child_canvas = Canvas.new(left, top, width, height)
190
- Painter.paint(child, child_canvas, &block)
191
- if box.settings.horizontal_flow?
192
- left += child.preferred_width
193
- else
194
- top += child.preferred_height
195
- end
196
- end
197
- end
198
-
199
- private
200
-
201
- attr_reader :box, :canvas
202
- end
203
-
204
- class GridContainer
205
- attr_reader :settings, :num_cols, :num_rows, :col_width, :row_height, :preferred_width
206
-
207
- def initialize(grid, num_cols, num_rows)
208
- @settings = ContainerSettings.merge(grid.settings)
209
- @num_cols = num_cols
210
- @num_rows = num_rows
211
- @col_width = grid.col_width
212
- @row_height = grid.row_height
213
- @preferred_width = grid.preferred_width
214
- end
215
- end
216
-
217
- class GridPainter
218
- def initialize(grid, canvas)
219
- @grid = grid
220
- @canvas = canvas
221
- available_width = grid.preferred_width - (grid.settings.margin.left || 0) - (grid.settings.margin.right || 0)
222
- @num_cols = grid.settings.num_cols || (available_width - (grid.settings.border.left? ? 1 : 0) - (grid.settings.border.right? ? 1 : 0) + (grid.border.inner_vert ? 1 : 0)) / (col_width + grid.settings.padding.left + grid.settings.right + (grid.border.inner_vert ? 1 : 0))
223
- end
224
-
225
- def paint(&block)
226
- return if grid.children.empty?
227
-
228
- container = GridContainer.new(grid, num_cols, (grid.children.length.to_f / num_cols).ceil)
229
- ContainerPainter.new(container, canvas).paint(&block)
230
-
231
- children = if grid.settings.transpose?
232
- grid.children.length.times.map do |i|
233
- grid.children[(i * num_cols) % grid.children.length + i / (grid.children.length / num_cols)]
234
- end.compact
235
- else
236
- grid.children
237
- end
238
-
239
- top = canvas.top + grid.settings.margin.top + (grid.settings.border.top? ? 1 : 0) + grid.settings.padding.top
240
- if grid.settings.auto_margin?
241
- margin = (canvas.width - grid.preferred_width) / 2
242
- else
243
- margin = grid.settings.margin.left
244
- end
245
- left = canvas.left + margin + (grid.settings.border.left? ? 1 : 0) + grid.settings.padding.left
246
- grid_height = grid.settings.padding.top + grid.row_height + grid.settings.padding.bottom + (grid.settings.border.inner_horiz? ? 1 : 0)
247
- grid_width = grid.settings.padding.left + grid.col_width + grid.settings.padding.right + (grid.settings.border.inner_vert? ? 1 : 0)
248
- children.each_slice(num_cols).each.with_index do |row, row_num|
249
- row_top = top + row_num * grid_height
250
- row.each.with_index do |element, col_num|
251
- col_left = left + col_num * grid_width
252
- child_canvas = Canvas.new(
253
- col_left,
254
- row_top,
255
- element.preferred_width,
256
- element.preferred_height
257
- )
258
- Painter.paint(element, child_canvas, &block)
259
- end
260
- end
261
- end
262
-
263
- private
264
-
265
- attr_reader :grid, :canvas, :num_cols
266
- end
267
-
268
- module Painter
269
- PAINTERS = {
270
- TextElement => TextPainter,
271
- BoxElement => BoxPainter,
272
- GridElement => GridPainter,
273
- }
274
-
275
- def self.paint(element, canvas, &block)
276
- if element.is_a?(Template)
277
- element = BoxElement.from_template(element, canvas.width, canvas.height)
278
- end
279
- PAINTERS[element.class].new(element, canvas).paint(&block)
280
- end
281
- end
282
- end
283
- end