whirled_peas 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +6 -0
  4. data/README.md +62 -29
  5. data/Rakefile +3 -2
  6. data/examples/intro.rb +52 -0
  7. data/examples/scrolling.rb +53 -0
  8. data/lib/whirled_peas.rb +2 -8
  9. data/lib/whirled_peas/command_line.rb +25 -33
  10. data/lib/whirled_peas/graphics/box_painter.rb +9 -1
  11. data/lib/whirled_peas/graphics/composer.rb +80 -0
  12. data/lib/whirled_peas/graphics/container_coords.rb +18 -8
  13. data/lib/whirled_peas/graphics/container_dimensions.rb +17 -7
  14. data/lib/whirled_peas/graphics/container_painter.rb +151 -33
  15. data/lib/whirled_peas/graphics/debugger.rb +10 -1
  16. data/lib/whirled_peas/graphics/grid_painter.rb +14 -2
  17. data/lib/whirled_peas/graphics/mock_screen.rb +1 -1
  18. data/lib/whirled_peas/graphics/painter.rb +1 -6
  19. data/lib/whirled_peas/graphics/renderer.rb +1 -29
  20. data/lib/whirled_peas/graphics/screen.rb +8 -3
  21. data/lib/whirled_peas/graphics/text_painter.rb +6 -4
  22. data/lib/whirled_peas/settings/container_settings.rb +11 -0
  23. data/lib/whirled_peas/settings/debugger.rb +9 -0
  24. data/lib/whirled_peas/settings/grid_settings.rb +4 -0
  25. data/lib/whirled_peas/settings/scrollbar.rb +15 -0
  26. data/lib/whirled_peas/version.rb +1 -1
  27. data/screen_test/rendered/elements/box.frame +1 -1
  28. data/screen_test/rendered/elements/grid.frame +1 -1
  29. data/screen_test/rendered/elements/screen_overflow.frame +1 -0
  30. data/screen_test/rendered/elements/text.frame +1 -1
  31. data/screen_test/rendered/elements/text_multiline.frame +1 -1
  32. data/screen_test/rendered/settings/align/box.frame +1 -1
  33. data/screen_test/rendered/settings/align/box.rb +14 -10
  34. data/screen_test/rendered/settings/align/children_center.frame +1 -1
  35. data/screen_test/rendered/settings/align/children_left.frame +1 -1
  36. data/screen_test/rendered/settings/align/children_right.frame +1 -1
  37. data/screen_test/rendered/settings/align/grid.frame +1 -1
  38. data/screen_test/rendered/settings/ansi/bold.frame +1 -1
  39. data/screen_test/rendered/settings/ansi/color.frame +1 -1
  40. data/screen_test/rendered/settings/ansi/underline.frame +1 -1
  41. data/screen_test/rendered/settings/border.frame +1 -1
  42. data/screen_test/rendered/settings/flow/box_b2t.frame +1 -0
  43. data/screen_test/rendered/settings/flow/box_b2t.rb +24 -0
  44. data/screen_test/rendered/settings/flow/{l2r.frame → box_l2r.frame} +1 -1
  45. data/screen_test/rendered/settings/flow/{l2r.rb → box_l2r.rb} +0 -0
  46. data/screen_test/rendered/settings/flow/box_r2l.frame +1 -0
  47. data/screen_test/rendered/settings/flow/box_r2l.rb +24 -0
  48. data/screen_test/rendered/settings/flow/{t2b.frame → box_t2b.frame} +1 -1
  49. data/screen_test/rendered/settings/flow/{t2b.rb → box_t2b.rb} +0 -0
  50. data/screen_test/rendered/settings/flow/grid_b2t.frame +1 -0
  51. data/screen_test/rendered/settings/flow/grid_b2t.rb +14 -0
  52. data/screen_test/rendered/settings/flow/grid_l2r.frame +1 -0
  53. data/screen_test/rendered/settings/flow/grid_l2r.rb +14 -0
  54. data/screen_test/rendered/settings/flow/grid_r2l.frame +1 -0
  55. data/screen_test/rendered/settings/flow/grid_r2l.rb +14 -0
  56. data/screen_test/rendered/settings/flow/grid_t2b.frame +1 -0
  57. data/screen_test/rendered/settings/flow/grid_t2b.rb +14 -0
  58. data/screen_test/rendered/settings/height/box.frame +1 -1
  59. data/screen_test/rendered/settings/height/grid.frame +1 -1
  60. data/screen_test/rendered/settings/height/overflow_box.frame +1 -1
  61. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -1
  62. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -1
  63. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -1
  64. data/screen_test/rendered/settings/margin.frame +1 -1
  65. data/screen_test/rendered/settings/padding.frame +1 -1
  66. data/screen_test/rendered/settings/position/box_left.frame +1 -1
  67. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -1
  68. data/screen_test/rendered/settings/position/box_top.frame +1 -1
  69. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -1
  70. data/screen_test/rendered/settings/position/grid_left.frame +1 -1
  71. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -1
  72. data/screen_test/rendered/settings/position/grid_top.frame +1 -1
  73. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -1
  74. data/screen_test/rendered/settings/scroll/horiz_box.frame +1 -0
  75. data/screen_test/rendered/settings/scroll/horiz_box.rb +15 -0
  76. data/screen_test/rendered/settings/scroll/vert_box.frame +1 -0
  77. data/screen_test/rendered/settings/scroll/vert_box.rb +18 -0
  78. data/screen_test/rendered/settings/title_font.frame +1 -1
  79. data/screen_test/rendered/settings/width/box.frame +1 -1
  80. data/screen_test/rendered/settings/width/grid.frame +1 -1
  81. data/screen_test/rendered/settings/width/overflow_box.frame +1 -1
  82. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -1
  83. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -1
  84. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -1
  85. data/screen_test/screen_tester.rb +13 -3
  86. metadata +27 -16
  87. data/bin/debug +0 -35
  88. data/lib/whirled_peas/debugger.rb +0 -80
  89. data/lib/whirled_peas/template.rb +0 -5
  90. data/lib/whirled_peas/template/box_element.rb +0 -8
  91. data/lib/whirled_peas/template/composer.rb +0 -68
  92. data/lib/whirled_peas/template/container.rb +0 -28
  93. data/lib/whirled_peas/template/debugger.rb +0 -34
  94. data/lib/whirled_peas/template/element.rb +0 -13
  95. data/lib/whirled_peas/template/grid_element.rb +0 -8
  96. data/lib/whirled_peas/template/text_element.rb +0 -24
@@ -7,7 +7,7 @@ module WhirledPeas
7
7
  def paint(canvas, &block)
8
8
  super
9
9
  return unless canvas.writable?
10
- if element.settings.horizontal_flow?
10
+ if settings.horizontal_flow?
11
11
  paint_horizontally(canvas, &block)
12
12
  else
13
13
  paint_vertically(canvas, &block)
@@ -37,6 +37,14 @@ module WhirledPeas
37
37
  end
38
38
  end
39
39
 
40
+ def each_child(&block)
41
+ if settings.reverse_flow?
42
+ children.reverse.each(&block)
43
+ else
44
+ super
45
+ end
46
+ end
47
+
40
48
  private
41
49
 
42
50
  def paint_horizontally(canvas, &block)
@@ -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
@@ -32,27 +32,37 @@ module WhirledPeas
32
32
  end
33
33
 
34
34
  def content_left(col_index=0)
35
- padding_left + settings.padding.left + col_index * grid_width(true)
35
+ padding_left + settings.padding.left + col_index * grid_width
36
36
  end
37
37
 
38
38
  def content_top(row_index=0)
39
- padding_top + settings.padding.top + row_index * grid_height(true)
39
+ padding_top + settings.padding.top + row_index * grid_height
40
40
  end
41
41
 
42
- def grid_width(include_border)
43
- (include_border && settings.border.inner_vert? ? 1 : 0) +
44
- settings.padding.left +
42
+ def inner_grid_width
43
+ settings.padding.left +
45
44
  dimensions.content_width +
46
45
  settings.padding.right
47
46
  end
48
47
 
49
- def grid_height(include_border)
50
- (include_border && settings.border.inner_horiz? ? 1 : 0) +
51
- settings.padding.top +
48
+ def grid_width
49
+ (settings.border.inner_vert? ? 1 : 0) +
50
+ inner_grid_width +
51
+ (settings.scrollbar.vert? ? 1 : 0)
52
+ end
53
+
54
+ def inner_grid_height
55
+ settings.padding.top +
52
56
  dimensions.content_height +
53
57
  settings.padding.bottom
54
58
  end
55
59
 
60
+ def grid_height
61
+ (settings.border.inner_horiz? ? 1 : 0) +
62
+ inner_grid_height +
63
+ (settings.scrollbar.horiz? ? 1 : 0)
64
+ end
65
+
56
66
  private
57
67
 
58
68
  attr_reader :canvas, :settings, :dimensions
@@ -1,11 +1,13 @@
1
1
  module WhirledPeas
2
2
  module Graphics
3
3
  class ContainerDimensions
4
- attr_reader :content_width, :content_height, :num_cols, :num_rows
4
+ attr_reader :content_width, :content_height, :children_width, :children_height, :num_cols, :num_rows
5
5
 
6
6
  def initialize(settings, content_width, content_height, num_cols=1, num_rows=1)
7
7
  @content_width = settings.width || content_width
8
8
  @content_height = settings.height || content_height
9
+ @children_width = content_width
10
+ @children_height = content_height
9
11
  @num_cols = num_cols
10
12
  @num_rows = num_rows
11
13
  @settings = settings
@@ -14,21 +16,17 @@ module WhirledPeas
14
16
  def outer_width
15
17
  @outer_width ||= margin_width +
16
18
  outer_border_width +
17
- num_cols * (padding_width + content_width) +
19
+ num_cols * (padding_width + content_width + vert_scroll_width) +
18
20
  (num_cols - 1) * inner_border_width
19
21
  end
20
22
 
21
23
  def outer_height
22
24
  @outer_height ||= margin_height +
23
25
  outer_border_height +
24
- num_rows * (padding_height + content_height) +
26
+ num_rows * (padding_height + content_height + horiz_scroll_height) +
25
27
  (num_rows - 1) * inner_border_height
26
28
  end
27
29
 
28
- private
29
-
30
- attr_reader :settings
31
-
32
30
  def margin_width
33
31
  settings.margin.left + settings.margin.right
34
32
  end
@@ -60,6 +58,18 @@ module WhirledPeas
60
58
  def padding_height
61
59
  settings.padding.top + settings.padding.bottom
62
60
  end
61
+
62
+ def vert_scroll_width
63
+ settings.scrollbar.vert? ? 1 : 0
64
+ end
65
+
66
+ def horiz_scroll_height
67
+ settings.scrollbar.horiz? ? 1 : 0
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :settings
63
73
  end
64
74
  end
65
75
  end
@@ -8,38 +8,39 @@ module WhirledPeas
8
8
  class ContainerPainter < Painter
9
9
  PADDING = ' '
10
10
 
11
- def initialize(element, settings, name)
11
+ def initialize(name, settings)
12
12
  super
13
13
  @children = []
14
14
  end
15
15
 
16
16
  def paint(canvas, &block)
17
17
  return unless canvas.writable?
18
- has_inner_vert = dimensions.num_cols > 1 && settings.border.inner_vert?
19
- has_inner_horiz = dimensions.num_rows > 1 && settings.border.inner_horiz?
20
- has_border = settings.border.outer? || has_inner_vert || has_inner_horiz
21
- return unless settings.bg_color || has_border
22
- stroke_left = coords(canvas).border_left
23
- stroke_top = coords(canvas).border_top
18
+ return unless needs_printing?
19
+ canvas_coords = coords(canvas)
20
+ stroke_left = canvas_coords.border_left
21
+ stroke_top = canvas_coords.border_top
24
22
  formatting = [*settings.border.color, *settings.bg_color]
25
23
  if settings.border.top?
26
- canvas.stroke(stroke_left, stroke_top, top_border_stroke, formatting, &block)
24
+ canvas.stroke(stroke_left, stroke_top, top_border_stroke(canvas_coords), formatting, &block)
27
25
  stroke_top += 1
28
26
  end
29
- content_line = content_line_stroke
30
- middle_border = dimensions.num_rows > 1 ? middle_border_stroke : ''
27
+ middle_border = dimensions.num_rows > 1 ? middle_border_stroke(canvas_coords) : ''
31
28
  dimensions.num_rows.times do |row_num|
32
29
  if row_num > 0 && settings.border.inner_horiz?
33
30
  canvas.stroke(stroke_left, stroke_top, middle_border, formatting, &block)
34
31
  stroke_top += 1
35
32
  end
36
- coords(canvas).grid_height(false).times do
37
- canvas.stroke(stroke_left, stroke_top, content_line, formatting, &block)
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)
38
39
  stroke_top += 1
39
40
  end
40
41
  end
41
42
  if settings.border.bottom?
42
- canvas.stroke(stroke_left, stroke_top, bottom_border_stroke, formatting, &block)
43
+ canvas.stroke(stroke_left, stroke_top, bottom_border_stroke(canvas_coords), formatting, &block)
43
44
  stroke_top += 1
44
45
  end
45
46
  end
@@ -52,6 +53,10 @@ module WhirledPeas
52
53
  children.length
53
54
  end
54
55
 
56
+ def children?
57
+ num_children > 0
58
+ end
59
+
55
60
  def each_child(&block)
56
61
  children.each(&block)
57
62
  end
@@ -60,55 +65,168 @@ module WhirledPeas
60
65
 
61
66
  attr_reader :children
62
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
+
63
76
  def coords(canvas)
64
77
  ContainerCoords.new(canvas, dimensions, settings)
65
78
  end
66
79
 
67
- def line_stroke(left_border, horiz_border, junc_border, right_border)
80
+ def line_stroke(left_border, junc_border, right_border, &block)
68
81
  stroke = ''
69
82
  stroke += left_border if settings.border.left?
70
83
  dimensions.num_cols.times do |col_num|
71
84
  stroke += junc_border if col_num > 0 && settings.border.inner_vert?
72
- stroke += horiz_border * (dimensions.content_width + settings.padding.left + settings.padding.right)
85
+ stroke += yield
73
86
  end
74
87
  stroke += right_border if settings.border.right?
75
88
  stroke
76
89
  end
77
90
 
78
- def top_border_stroke
91
+ def top_border_stroke(canvas_coords)
79
92
  line_stroke(
80
93
  settings.border.style.top_left,
81
- settings.border.style.top_horiz,
82
94
  settings.border.style.top_junc,
83
95
  settings.border.style.top_right
84
- )
85
- end
86
-
87
- def content_line_stroke
88
- line_stroke(
89
- settings.border.style.left_vert,
90
- PADDING,
91
- settings.border.style.middle_vert,
92
- settings.border.style.right_vert
93
- )
96
+ ) do
97
+ settings.border.style.top_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
98
+ end
94
99
  end
95
100
 
96
- def middle_border_stroke
101
+ def middle_border_stroke(canvas_coords)
97
102
  line_stroke(
98
103
  settings.border.style.left_junc,
99
- settings.border.style.middle_horiz,
100
104
  settings.border.style.cross_junc,
101
105
  settings.border.style.right_junc
102
- )
106
+ ) do
107
+ settings.border.style.middle_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
108
+ end
103
109
  end
104
110
 
105
- def bottom_border_stroke
111
+ def bottom_border_stroke(canvas_coords)
106
112
  line_stroke(
107
113
  settings.border.style.bottom_left,
108
- settings.border.style.bottom_horiz,
109
114
  settings.border.style.bottom_junc,
110
115
  settings.border.style.bottom_right
111
- )
116
+ ) do
117
+ settings.border.style.bottom_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
118
+ end
119
+ end
120
+
121
+ def content_line_stroke(canvas_coords, row_within_cell)
122
+ line_stroke(
123
+ settings.border.style.left_vert,
124
+ settings.border.style.middle_vert,
125
+ settings.border.style.right_vert,
126
+ ) do
127
+ if settings.scrollbar.vert?
128
+ if dimensions.children_height <= canvas_coords.grid_height || children.first.settings.position.top > 0
129
+ scrollbar_char = GUTTER
130
+ else
131
+ scrollbar_char = vert_scroll_char(
132
+ dimensions.children_height + dimensions.padding_height,
133
+ canvas_coords.inner_grid_height,
134
+ -children.first.settings.position.top,
135
+ row_within_cell
136
+ )
137
+ end
138
+ PADDING * canvas_coords.inner_grid_width + scrollbar_char
139
+ else
140
+ PADDING * canvas_coords.inner_grid_width
141
+ end
142
+ end
143
+ end
144
+
145
+ def bottom_scroll_stroke(canvas_coords)
146
+ line_stroke(
147
+ settings.border.style.left_vert,
148
+ settings.border.style.middle_vert,
149
+ settings.border.style.right_vert,
150
+ ) do
151
+ canvas_coords.inner_grid_width.times.map do |col_within_cell|
152
+ horiz_scroll_char(
153
+ dimensions.children_width + dimensions.padding_width,
154
+ canvas_coords.inner_grid_width,
155
+ -children.first.settings.position.left,
156
+ col_within_cell
157
+ )
158
+ end.join
159
+ end
160
+ end
161
+
162
+ GUTTER = ' '
163
+ HORIZONTAL = %w[▗ ▄ ▖]
164
+ VERTICAL = %w[
165
+
166
+
167
+
168
+ ]
169
+
170
+ def horiz_scroll_char(col_count, viewable_col_count, first_visible_col, curr_col)
171
+ scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
172
+ end
173
+
174
+ def vert_scroll_char(row_count, viewable_row_count, first_visible_row, curr_row)
175
+ scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
176
+ end
177
+
178
+ private
179
+
180
+ def scroll_char(total_count, viewable_count, first_visible, curr, chars)
181
+ return GUTTER unless total_count > 0 && viewable_count > 0
182
+ # The scroll handle has the exact same relative size and position in the scroll gutter
183
+ # that the viewable content has in the total content area. For example, a content area
184
+ # that is 50 columns wide with a view port that is 20 columns wide might look like
185
+ #
186
+ # +---------1-----****2*********3******---4---------+
187
+ # | * * |
188
+ # | hidden * viewable * hidden |
189
+ # | * * |
190
+ # +---------1-----****2*********3******---4---------+
191
+ #
192
+ # The scoll gutter, would look like
193
+ #
194
+ # |......********.....|
195
+ #
196
+ # Scrolling all the way to the right results in
197
+ #
198
+ # +---------1---------2---------3*********4*********+
199
+ # | * *
200
+ # | hidden * viewable *
201
+ # | * *
202
+ # +---------1---------2---------3*********4*********+
203
+ # |...........********|
204
+
205
+ # The first task of determining how much of the handle is visible in a row/column is to
206
+ # calculate the range (as a precentage of the total) of viewable items
207
+ viewable_start = first_visible.to_f / total_count
208
+ viewable_end = (first_visible + viewable_count).to_f / total_count
209
+
210
+ # Always use the same length for the scroll bar so it does not give an inchworm effect
211
+ # as it scrolls along.
212
+ #
213
+ # Also, double the value now to get granularity for half width
214
+ # scrollbar characters.
215
+ scrollbar_length = ((2 * viewable_count ** 2).to_f / total_count).ceil
216
+ scrollbar_start = ((2 * first_visible * viewable_count).to_f / total_count).floor
217
+
218
+ first_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr)
219
+ second_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr + 1)
220
+
221
+ if first_half && second_half
222
+ chars[1]
223
+ elsif first_half
224
+ chars[2]
225
+ elsif second_half
226
+ chars[0]
227
+ else
228
+ GUTTER
229
+ end
112
230
  end
113
231
  end
114
232
  private_constant :ContainerPainter