whirled_peas 0.4.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 (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