whirled_peas 0.1.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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