whirled_peas 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +111 -73
  4. data/Rakefile +37 -3
  5. data/bin/debug +35 -0
  6. data/lib/whirled_peas.rb +9 -7
  7. data/lib/whirled_peas/command_line.rb +25 -32
  8. data/lib/whirled_peas/debugger.rb +80 -0
  9. data/lib/whirled_peas/errors.rb +2 -0
  10. data/lib/whirled_peas/frame.rb +0 -1
  11. data/lib/whirled_peas/frame/consumer.rb +30 -0
  12. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  13. data/lib/whirled_peas/frame/event_loop.rb +29 -30
  14. data/lib/whirled_peas/frame/producer.rb +11 -5
  15. data/lib/whirled_peas/graphics.rb +5 -0
  16. data/lib/whirled_peas/graphics/box_painter.rb +100 -0
  17. data/lib/whirled_peas/graphics/canvas.rb +107 -0
  18. data/lib/whirled_peas/graphics/container_coords.rb +61 -0
  19. data/lib/whirled_peas/graphics/container_dimensions.rb +65 -0
  20. data/lib/whirled_peas/graphics/container_painter.rb +116 -0
  21. data/lib/whirled_peas/graphics/debugger.rb +43 -0
  22. data/lib/whirled_peas/graphics/grid_painter.rb +56 -0
  23. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  24. data/lib/whirled_peas/graphics/painter.rb +24 -0
  25. data/lib/whirled_peas/graphics/renderer.rb +49 -0
  26. data/lib/whirled_peas/graphics/screen.rb +65 -0
  27. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  28. data/lib/whirled_peas/graphics/text_painter.rb +38 -0
  29. data/lib/whirled_peas/settings.rb +5 -0
  30. data/lib/whirled_peas/settings/bg_color.rb +22 -0
  31. data/lib/whirled_peas/settings/border.rb +101 -0
  32. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  33. data/lib/whirled_peas/{utils → settings}/color.rb +3 -35
  34. data/lib/whirled_peas/settings/container_settings.rb +128 -0
  35. data/lib/whirled_peas/settings/debugger.rb +87 -0
  36. data/lib/whirled_peas/settings/display_flow.rb +25 -0
  37. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  38. data/lib/whirled_peas/settings/grid_settings.rb +11 -0
  39. data/lib/whirled_peas/settings/margin.rb +8 -0
  40. data/lib/whirled_peas/settings/padding.rb +8 -0
  41. data/lib/whirled_peas/settings/position.rb +15 -0
  42. data/lib/whirled_peas/settings/spacing.rb +24 -0
  43. data/lib/whirled_peas/settings/text_align.rb +19 -0
  44. data/lib/whirled_peas/settings/text_color.rb +19 -0
  45. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  46. data/lib/whirled_peas/template/box_element.rb +8 -0
  47. data/lib/whirled_peas/template/composer.rb +68 -0
  48. data/lib/whirled_peas/template/container.rb +28 -0
  49. data/lib/whirled_peas/template/debugger.rb +34 -0
  50. data/lib/whirled_peas/template/element.rb +1 -218
  51. data/lib/whirled_peas/template/grid_element.rb +8 -0
  52. data/lib/whirled_peas/template/text_element.rb +24 -0
  53. data/lib/whirled_peas/utils/ansi.rb +6 -56
  54. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  55. data/lib/whirled_peas/version.rb +1 -1
  56. data/screen_test/rendered/elements/box.frame +1 -0
  57. data/screen_test/rendered/elements/box.rb +20 -0
  58. data/screen_test/rendered/elements/grid.frame +1 -0
  59. data/screen_test/rendered/elements/grid.rb +13 -0
  60. data/screen_test/rendered/elements/screen_overflow.rb +9 -0
  61. data/screen_test/rendered/elements/text.frame +1 -0
  62. data/screen_test/rendered/elements/text.rb +9 -0
  63. data/screen_test/rendered/elements/text_multiline.frame +1 -0
  64. data/screen_test/rendered/elements/text_multiline.rb +9 -0
  65. data/screen_test/rendered/settings/align/box.frame +1 -0
  66. data/screen_test/rendered/settings/align/box.rb +20 -0
  67. data/screen_test/rendered/settings/align/children_center.frame +1 -0
  68. data/screen_test/rendered/settings/align/children_center.rb +13 -0
  69. data/screen_test/rendered/settings/align/children_left.frame +1 -0
  70. data/screen_test/rendered/settings/align/children_left.rb +13 -0
  71. data/screen_test/rendered/settings/align/children_right.frame +1 -0
  72. data/screen_test/rendered/settings/align/children_right.rb +13 -0
  73. data/screen_test/rendered/settings/align/grid.frame +1 -0
  74. data/screen_test/rendered/settings/align/grid.rb +20 -0
  75. data/screen_test/rendered/settings/ansi/bold.frame +1 -0
  76. data/screen_test/rendered/settings/ansi/bold.rb +15 -0
  77. data/screen_test/rendered/settings/ansi/color.frame +1 -0
  78. data/screen_test/rendered/settings/ansi/color.rb +37 -0
  79. data/screen_test/rendered/settings/ansi/underline.frame +1 -0
  80. data/screen_test/rendered/settings/ansi/underline.rb +15 -0
  81. data/screen_test/rendered/settings/border.frame +1 -0
  82. data/screen_test/rendered/settings/border.rb +13 -0
  83. data/screen_test/rendered/settings/flow/l2r.frame +1 -0
  84. data/screen_test/rendered/settings/flow/l2r.rb +24 -0
  85. data/screen_test/rendered/settings/flow/t2b.frame +1 -0
  86. data/screen_test/rendered/settings/flow/t2b.rb +24 -0
  87. data/screen_test/rendered/settings/height/box.frame +1 -0
  88. data/screen_test/rendered/settings/height/box.rb +13 -0
  89. data/screen_test/rendered/settings/height/grid.frame +1 -0
  90. data/screen_test/rendered/settings/height/grid.rb +14 -0
  91. data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
  92. data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
  93. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
  94. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
  95. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
  96. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
  97. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
  98. data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
  99. data/screen_test/rendered/settings/margin.frame +1 -0
  100. data/screen_test/rendered/settings/margin.rb +14 -0
  101. data/screen_test/rendered/settings/padding.frame +1 -0
  102. data/screen_test/rendered/settings/padding.rb +11 -0
  103. data/screen_test/rendered/settings/position/box_left.frame +1 -0
  104. data/screen_test/rendered/settings/position/box_left.rb +17 -0
  105. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
  106. data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
  107. data/screen_test/rendered/settings/position/box_top.frame +1 -0
  108. data/screen_test/rendered/settings/position/box_top.rb +17 -0
  109. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
  110. data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
  111. data/screen_test/rendered/settings/position/grid_left.frame +1 -0
  112. data/screen_test/rendered/settings/position/grid_left.rb +18 -0
  113. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
  114. data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
  115. data/screen_test/rendered/settings/position/grid_top.frame +1 -0
  116. data/screen_test/rendered/settings/position/grid_top.rb +18 -0
  117. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
  118. data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
  119. data/screen_test/rendered/settings/title_font.frame +1 -0
  120. data/screen_test/rendered/settings/title_font.rb +12 -0
  121. data/screen_test/rendered/settings/width/box.frame +1 -0
  122. data/screen_test/rendered/settings/width/box.rb +13 -0
  123. data/screen_test/rendered/settings/width/grid.frame +1 -0
  124. data/screen_test/rendered/settings/width/grid.rb +14 -0
  125. data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
  126. data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
  127. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
  128. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
  129. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
  130. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
  131. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
  132. data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
  133. data/screen_test/screen_tester.rb +191 -0
  134. metadata +122 -10
  135. data/bin/title_fonts +0 -6
  136. data/lib/whirled_peas/frame/print_consumer.rb +0 -33
  137. data/lib/whirled_peas/template/settings.rb +0 -530
  138. data/lib/whirled_peas/ui.rb +0 -5
  139. data/lib/whirled_peas/ui/canvas.rb +0 -68
  140. data/lib/whirled_peas/ui/painter.rb +0 -287
  141. data/lib/whirled_peas/ui/screen.rb +0 -63
@@ -1,7 +1,15 @@
1
+ require 'json'
2
+
3
+ require 'whirled_peas/graphics/debugger'
4
+ require 'whirled_peas/graphics/renderer'
5
+ require 'whirled_peas/graphics/screen'
6
+ require 'whirled_peas/frame/debug_consumer'
7
+ require 'whirled_peas/frame/event_loop'
8
+ require 'whirled_peas/frame/producer'
9
+ require 'whirled_peas/template/debugger'
10
+
1
11
  module WhirledPeas
2
12
  class Command
3
- DEFAULT_REFRESH_RATE = 30
4
-
5
13
  DEFAULT_LOG_LEVEL = Logger::INFO
6
14
  DEFAULT_FORMATTER = proc do |severity, datetime, progname, msg|
7
15
  if msg.is_a?(Exception)
@@ -95,16 +103,13 @@ module WhirledPeas
95
103
 
96
104
  def start
97
105
  super
98
- require 'whirled_peas/frame/event_loop'
99
- require 'whirled_peas/frame/producer'
100
106
 
101
107
  logger = self.class.build_logger(File.open('whirled_peas.log', 'a'))
102
108
 
103
109
  consumer = Frame::EventLoop.new(
104
110
  WhirledPeas.config.template_factory,
105
111
  WhirledPeas.config.loading_template_factory,
106
- DEFAULT_REFRESH_RATE,
107
- logger
112
+ logger: logger
108
113
  )
109
114
  Frame::Producer.produce(consumer, logger) do |producer|
110
115
  begin
@@ -121,18 +126,9 @@ module WhirledPeas
121
126
  class ListFramesCommand < ConfigCommand
122
127
  def start
123
128
  super
124
- require 'whirled_peas/frame/print_consumer'
125
- require 'whirled_peas/frame/producer'
126
129
 
127
- logger = self.class.build_logger(STDOUT)
128
- Frame::Producer.produce(Frame::PrintConsumer.new, logger) do |producer|
129
- begin
130
- WhirledPeas.config.driver.start(producer)
131
- rescue => e
132
- logger.warn(LOGGER_ID) { 'Driver exited with error, terminating producer...' }
133
- logger.error(LOGGER_ID) { e }
134
- raise
135
- end
130
+ Frame::Producer.produce(Frame::DebugConsumer.new) do |producer|
131
+ WhirledPeas.config.driver.start(producer)
136
132
  end
137
133
  end
138
134
  end
@@ -141,24 +137,25 @@ module WhirledPeas
141
137
  def start
142
138
  super
143
139
 
144
- if args.last == '--debug'
145
- puts WhirledPeas.config.template_factory.build(frame, frame_args).inspect
140
+ if args.last == '--template'
141
+ element = WhirledPeas.config.template_factory.build(frame, frame_args)
142
+ puts Template::Debugger.new(element).debug
143
+ exit
144
+ elsif args.last == '--painter'
145
+ element = WhirledPeas.config.template_factory.build(frame, frame_args)
146
+ painter = Graphics::Renderer.new(element, *Graphics::Screen.current_dimensions).painter
147
+ puts Graphics::Debugger.new(painter).debug
146
148
  exit
147
149
  end
148
150
 
149
- require 'whirled_peas/frame/event_loop'
150
- require 'whirled_peas/frame/producer'
151
-
152
151
  logger = self.class.build_logger(File.open('whirled_peas.log', 'a'))
153
152
 
154
153
  consumer = Frame::EventLoop.new(
155
154
  WhirledPeas.config.template_factory,
156
- WhirledPeas.config.loading_template_factory,
157
- DEFAULT_REFRESH_RATE,
158
- logger
155
+ logger: logger
159
156
  )
160
157
  Frame::Producer.produce(consumer, logger) do |producer|
161
- producer.send_frame(args[1], duration: 5, args: frame_args)
158
+ producer.send_frame(frame, args: frame_args)
162
159
  end
163
160
  end
164
161
 
@@ -173,9 +170,9 @@ module WhirledPeas
173
170
  elsif args.length < 2
174
171
  @error_text = "#{self.class.command_name} requires a frame name"
175
172
  else
176
- require 'json'
177
173
  @frame = args[1]
178
174
  @frame_args = {}
175
+ return if args.length < 3 || args[2][0..1] == '--'
179
176
  JSON.parse(args[2] || '{}').each do |key, value|
180
177
  @frame_args[key.to_sym] = value
181
178
  end
@@ -200,15 +197,11 @@ module WhirledPeas
200
197
  exit
201
198
  end
202
199
 
203
- require 'whirled_peas/frame/event_loop'
204
- require 'whirled_peas/frame/producer'
205
-
206
200
  logger = self.class.build_logger(File.open('whirled_peas.log', 'a'))
207
201
  consumer = Frame::EventLoop.new(
208
202
  WhirledPeas.config.template_factory,
209
203
  WhirledPeas.config.loading_template_factory,
210
- DEFAULT_REFRESH_RATE,
211
- logger
204
+ logger: logger
212
205
  )
213
206
  Frame::Producer.produce(consumer, logger) { sleep(5) }
214
207
  end
@@ -0,0 +1,80 @@
1
+ require 'whirled_peas/frame/event_loop'
2
+ require 'whirled_peas/frame/producer'
3
+ require 'whirled_peas/graphics/debugger'
4
+ require 'whirled_peas/graphics/mock_screen'
5
+ require 'whirled_peas/graphics/renderer'
6
+ require 'whirled_peas/graphics/screen'
7
+ require 'whirled_peas/template/debugger'
8
+
9
+ module WhirledPeas
10
+ class Debugger
11
+ module Option
12
+ DISABLE_SCREEN = :disable_screen
13
+ PAINTER = :painter
14
+ TEMPLATE = :template
15
+
16
+ VALID = [DISABLE_SCREEN, PAINTER, TEMPLATE]
17
+ end
18
+ private_constant :Option
19
+
20
+ FRAME = 'debug'
21
+ private_constant :FRAME
22
+
23
+ def initialize(template_file, option=nil)
24
+ unless option.nil? || Option::VALID.include?(option)
25
+ message =
26
+ "Invalid option: #{option}, expecting one of #{Option::VALID.join(', ')}"
27
+ raise ArgumentError, message
28
+ end
29
+ @template_file = template_file
30
+ @option = option
31
+ end
32
+
33
+ def debug
34
+ require template_file
35
+ template_factory = TemplateFactory.new
36
+ if play_frame?
37
+ screen = if disable_screen?
38
+ Graphics::MockScreen.new(80, 40)
39
+ else
40
+ Graphics::Screen.new
41
+ end
42
+ consumer = Frame::EventLoop.new(template_factory, screen: screen)
43
+ Frame::Producer.produce(consumer) do |producer|
44
+ producer.send_frame(FRAME, args: {})
45
+ end
46
+ return
47
+ end
48
+
49
+ template = template_factory.build(FRAME, {})
50
+ if print_painter?
51
+ painter = Graphics::Renderer.new(template, *Graphics::Screen.current_dimensions).painter
52
+ puts Graphics::Debugger.new(painter).debug
53
+ elsif print_template?
54
+ puts Template::Debugger.new(template).debug
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ attr_reader :template_file, :option
61
+
62
+ def play_frame?
63
+ option.nil? || option == Option::DISABLE_SCREEN
64
+ end
65
+
66
+ def disable_screen?
67
+ !option.nil? && option == Option::DISABLE_SCREEN
68
+ end
69
+
70
+ def print_painter?
71
+ !option.nil? && option == Option::PAINTER
72
+ end
73
+
74
+ def print_template?
75
+ !option.nil? && option == Option::TEMPLATE
76
+ end
77
+ end
78
+
79
+ private_constant :Debugger
80
+ end
@@ -2,4 +2,6 @@ module WhirledPeas
2
2
  class Error < StandardError; end
3
3
 
4
4
  class ConfigurationError < Error; end
5
+
6
+ class SettingsError < Error; end
5
7
  end
@@ -1,6 +1,5 @@
1
1
  module WhirledPeas
2
2
  module Frame
3
- EOF = '__EOF__'
4
3
  end
5
4
 
6
5
  private_constant :Frame
@@ -0,0 +1,30 @@
1
+ module WhirledPeas
2
+ module Frame
3
+ # Abstract class for consuming frame events.
4
+ class Consumer
5
+ EOF = '__EOF__'
6
+
7
+ def enqueue(name, duration, args)
8
+ raise NotImplemented, "#{self.class} must implement #enqueue"
9
+ end
10
+
11
+ def running?
12
+ @running == true
13
+ end
14
+
15
+ def start
16
+ self.running = true
17
+ end
18
+
19
+ def stop
20
+ enqueue(EOF, nil, {})
21
+ end
22
+
23
+ private
24
+
25
+ attr_writer :running
26
+ end
27
+
28
+ private_constant :Consumer
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'whirled_peas/null_logger'
2
+
3
+ require_relative 'consumer'
4
+
5
+ module WhirledPeas
6
+ module Frame
7
+ class DebugConsumer < Consumer
8
+ LOGGER_ID = 'PRINTER'
9
+
10
+ def initialize(output=STDOUT, logger=NullLogger.new)
11
+ @output = output
12
+ @logger = logger
13
+ end
14
+
15
+ def enqueue(name, duration, args)
16
+ if name == EOF
17
+ output.puts "EOF frame detected"
18
+ else
19
+ displayed_for = duration ? "#{duration} second(s)" : '1 frame'
20
+ args_str = args.empty? ? '' : " '#{JSON.generate(args)}'"
21
+ output.puts "Frame '#{name}' displayed for #{displayed_for}#{args_str}"
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :output, :logger
28
+ end
29
+ end
30
+ end
@@ -1,17 +1,28 @@
1
- require_relative '../null_logger'
2
- require_relative '../ui/screen'
1
+ require 'whirled_peas/null_logger'
2
+ require 'whirled_peas/graphics/screen'
3
+
4
+ require_relative 'consumer'
3
5
 
4
6
  module WhirledPeas
5
7
  module Frame
6
- class EventLoop
8
+ class EventLoop < Consumer
9
+ DEFAULT_REFRESH_RATE = 30
10
+
7
11
  LOGGER_ID = 'EVENT LOOP'
8
12
 
9
- def initialize(template_factory, loading_template_factory, refresh_rate, logger=NullLogger.new)
13
+ def initialize(
14
+ template_factory,
15
+ loading_template_factory=nil,
16
+ refresh_rate: DEFAULT_REFRESH_RATE,
17
+ logger: NullLogger.new,
18
+ screen: Graphics::Screen.new
19
+ )
10
20
  @template_factory = template_factory
11
21
  @loading_template_factory = loading_template_factory
12
22
  @queue = Queue.new
13
23
  @frame_duration = 1.0 / refresh_rate
14
24
  @logger = logger
25
+ @screen = screen
15
26
  end
16
27
 
17
28
  def enqueue(name, duration, args)
@@ -19,50 +30,38 @@ module WhirledPeas
19
30
  queue.push([name, duration || frame_duration, args])
20
31
  end
21
32
 
22
- def running?
23
- @running
24
- end
25
-
26
- def start(screen=UI::Screen.new)
27
- wait_for_content(screen)
28
- play_content(screen)
33
+ def start
34
+ super
35
+ wait_for_content
36
+ play_content
29
37
  rescue
38
+ self.running = false
30
39
  logger.warn(LOGGER_ID) { 'Exiting with error' }
31
40
  raise
32
41
  ensure
33
- # We may have exited due to an EOF or a raised exception, set state so that
34
- # instance reflects actual state.
35
- @running = false
36
- screen.finalize if screen
37
- end
38
-
39
- def stop
40
- logger.info(LOGGER_ID) { 'Stopping...' }
41
- enqueue(Frame::EOF, nil, {})
42
- @running = false
42
+ screen.finalize
43
43
  end
44
44
 
45
45
  private
46
46
 
47
- attr_reader :template_factory, :loading_template_factory, :queue, :frame_duration, :logger
47
+ attr_reader :template_factory, :loading_template_factory, :queue, :frame_duration, :logger, :screen
48
48
 
49
- def wait_for_content(screen)
49
+ def wait_for_content
50
50
  if loading_template_factory
51
- play_loading_screen(screen)
51
+ play_loading_screen
52
52
  else
53
53
  sleep(frame_duration) while queue.empty?
54
54
  end
55
55
  end
56
56
 
57
- def play_loading_screen(screen)
57
+ def play_loading_screen
58
58
  while queue.empty?
59
59
  screen.paint(loading_template_factory.build)
60
60
  sleep(frame_duration)
61
61
  end
62
62
  end
63
63
 
64
- def play_content(screen)
65
- @running = true
64
+ def play_content
66
65
  template = nil
67
66
  frame_until = Time.new(0) # Tell the loop to immediately pick up a new frame
68
67
  while running?
@@ -73,15 +72,15 @@ module WhirledPeas
73
72
  screen.refresh
74
73
  elsif !queue.empty?
75
74
  name, duration, args = queue.pop
76
- if name == Frame::EOF
77
- @running = false
75
+ if name == EOF
76
+ self.running = false
78
77
  else
79
78
  frame_until = frame_start + duration
80
79
  template = template_factory.build(name, args)
81
80
  screen.paint(template)
82
81
  end
83
82
  else
84
- wait_for_content(screen)
83
+ wait_for_content
85
84
  end
86
85
  sleep([0, next_frame_at - Time.now].max)
87
86
  end
@@ -1,15 +1,22 @@
1
1
  require 'socket'
2
2
  require 'json'
3
3
 
4
- require_relative '../null_logger'
4
+ require 'whirled_peas/null_logger'
5
5
 
6
6
  module WhirledPeas
7
7
  module Frame
8
+ # A Producer is the object given to the driver as the interface that allows
9
+ # the driver to emit frame events. The recommended way of creating a Producer
10
+ # is by invoking `Producer.produce` as it handles the lifecycle methods of
11
+ # the consumer.
8
12
  class Producer
9
13
  LOGGER_ID = 'PRODUCER'
10
14
 
11
- # Manages the EventLoop lifecycle and yields a Producer to send frames to the
12
- # EventLoop
15
+ # Manages the consumer lifecycle and yields a Producer to send frames to the
16
+ # consumer
17
+ #
18
+ # @param consumer [Consumer] instance that consumes frame events through
19
+ # `#enqueue`
13
20
  def self.produce(consumer, logger=NullLogger.new)
14
21
  producer = new(consumer, logger)
15
22
  consumer_thread = Thread.new do
@@ -17,14 +24,13 @@ module WhirledPeas
17
24
  consumer.start
18
25
  end
19
26
  yield producer
20
- producer.send_frame(Frame::EOF)
21
27
  producer.flush
22
28
  rescue => e
23
- consumer.stop if consumer
24
29
  logger.warn(LOGGER_ID) { 'Exited with error' }
25
30
  logger.error(LOGGER_ID) { e }
26
31
  raise
27
32
  ensure
33
+ consumer.stop
28
34
  consumer_thread.join if consumer_thread
29
35
  end
30
36
 
@@ -0,0 +1,5 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ end
4
+ private_constant :Graphics
5
+ end
@@ -0,0 +1,100 @@
1
+ require_relative 'container_painter'
2
+ require_relative 'container_dimensions'
3
+
4
+ module WhirledPeas
5
+ module Graphics
6
+ class BoxPainter < ContainerPainter
7
+ def paint(canvas, &block)
8
+ super
9
+ return unless canvas.writable?
10
+ if element.settings.horizontal_flow?
11
+ paint_horizontally(canvas, &block)
12
+ else
13
+ paint_vertically(canvas, &block)
14
+ end
15
+ end
16
+
17
+ def dimensions
18
+ @dimensions ||= begin
19
+ content_width = 0
20
+ content_height = 0
21
+ if settings.horizontal_flow?
22
+ each_child do |child|
23
+ content_width += child.dimensions.outer_width
24
+ if child.dimensions.outer_height > content_height
25
+ content_height = child.dimensions.outer_height
26
+ end
27
+ end
28
+ else
29
+ each_child do |child|
30
+ if child.dimensions.outer_width > content_width
31
+ content_width = child.dimensions.outer_width
32
+ end
33
+ content_height += child.dimensions.outer_height
34
+ end
35
+ end
36
+ ContainerDimensions.new(settings, content_width, content_height)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def paint_horizontally(canvas, &block)
43
+ stroke_top = coords(canvas).content_top
44
+ stroke_left = coords(canvas).content_left
45
+ total_child_width = 0
46
+ each_child { |c| total_child_width += c.dimensions.outer_width }
47
+ if settings.align_center?
48
+ stroke_left += (dimensions.content_width - total_child_width) / 2
49
+ elsif settings.align_right?
50
+ stroke_left += dimensions.content_width - total_child_width
51
+ end
52
+ given_width = 0
53
+ each_child do |child|
54
+ child_width = [
55
+ child.dimensions.outer_width,
56
+ dimensions.content_width - given_width
57
+ ].min
58
+ child_canvas = canvas.child(
59
+ stroke_left + given_width,
60
+ stroke_top,
61
+ child_width,
62
+ [dimensions.content_height, child.dimensions.outer_height].min
63
+ )
64
+ child.paint(child_canvas, &block)
65
+ given_width += child_width
66
+ break if given_width == dimensions.content_width
67
+ end
68
+ end
69
+
70
+ def paint_vertically(canvas, &block)
71
+ stroke_top = coords(canvas).content_top
72
+ stroke_left = coords(canvas).content_left
73
+ given_height = 0
74
+ each_child do |child|
75
+ if settings.align_center?
76
+ justify_offset = (dimensions.content_width - child.dimensions.outer_width) / 2
77
+ elsif settings.align_right?
78
+ justify_offset = dimensions.content_width - child.dimensions.outer_width
79
+ else
80
+ justify_offset = 0
81
+ end
82
+ child_height = [
83
+ child.dimensions.outer_height,
84
+ dimensions.content_height - given_height
85
+ ].min
86
+ child_canvas = canvas.child(
87
+ stroke_left + justify_offset,
88
+ stroke_top + given_height,
89
+ [dimensions.content_width, child.dimensions.outer_width].min,
90
+ child_height
91
+ )
92
+ child.paint(child_canvas, &block)
93
+ given_height += child_height
94
+ break if given_height == dimensions.content_height
95
+ end
96
+ end
97
+ end
98
+ private_constant :BoxPainter
99
+ end
100
+ end