whirled_peas 0.3.0 → 0.7.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +38 -0
- data/README.md +549 -85
- data/Rakefile +28 -3
- data/examples/intro.rb +52 -0
- data/examples/scrolling.rb +53 -0
- data/exe/whirled_peas +7 -0
- data/lib/whirled_peas.rb +12 -36
- data/lib/whirled_peas/command_line.rb +255 -0
- data/lib/whirled_peas/config.rb +21 -0
- data/lib/whirled_peas/errors.rb +7 -0
- data/lib/whirled_peas/frame.rb +0 -5
- data/lib/whirled_peas/frame/consumer.rb +30 -0
- data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
- data/lib/whirled_peas/frame/event_loop.rb +69 -38
- data/lib/whirled_peas/frame/producer.rb +36 -19
- data/lib/whirled_peas/graphics.rb +19 -0
- data/lib/whirled_peas/graphics/box_painter.rb +101 -0
- data/lib/whirled_peas/graphics/canvas.rb +118 -0
- data/lib/whirled_peas/graphics/composer.rb +80 -0
- data/lib/whirled_peas/graphics/container_coords.rb +72 -0
- data/lib/whirled_peas/graphics/container_dimensions.rb +93 -0
- data/lib/whirled_peas/graphics/container_painter.rb +272 -0
- data/lib/whirled_peas/graphics/debugger.rb +52 -0
- data/lib/whirled_peas/graphics/grid_painter.rb +69 -0
- data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
- data/lib/whirled_peas/graphics/painter.rb +23 -0
- data/lib/whirled_peas/graphics/renderer.rb +26 -0
- data/lib/whirled_peas/graphics/screen.rb +70 -0
- data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
- data/lib/whirled_peas/graphics/text_painter.rb +40 -0
- data/lib/whirled_peas/settings.rb +5 -0
- data/lib/whirled_peas/settings/alignment.rb +24 -0
- data/lib/whirled_peas/settings/bg_color.rb +24 -0
- data/lib/whirled_peas/settings/border.rb +101 -0
- data/lib/whirled_peas/settings/box_settings.rb +8 -0
- data/lib/whirled_peas/settings/color.rb +68 -0
- data/lib/whirled_peas/settings/container_settings.rb +223 -0
- data/lib/whirled_peas/settings/debugger.rb +96 -0
- data/lib/whirled_peas/settings/display_flow.rb +27 -0
- data/lib/whirled_peas/settings/element_settings.rb +61 -0
- data/lib/whirled_peas/settings/grid_settings.rb +19 -0
- data/lib/whirled_peas/settings/margin.rb +8 -0
- data/lib/whirled_peas/settings/padding.rb +8 -0
- data/lib/whirled_peas/settings/position.rb +15 -0
- data/lib/whirled_peas/settings/scrollbar.rb +15 -0
- data/lib/whirled_peas/settings/sizing.rb +19 -0
- data/lib/whirled_peas/settings/spacing.rb +58 -0
- data/lib/whirled_peas/settings/text_color.rb +21 -0
- data/lib/whirled_peas/settings/text_settings.rb +15 -0
- data/lib/whirled_peas/settings/vert_alignment.rb +24 -0
- data/lib/whirled_peas/utils.rb +5 -0
- data/lib/whirled_peas/utils/ansi.rb +53 -0
- data/lib/whirled_peas/utils/formatted_string.rb +64 -0
- data/lib/whirled_peas/utils/title_font.rb +75 -0
- data/lib/whirled_peas/version.rb +1 -1
- data/screen_test/elements/box.frame +1 -0
- data/screen_test/elements/box.rb +20 -0
- data/screen_test/elements/grid.frame +1 -0
- data/screen_test/elements/grid.rb +13 -0
- data/screen_test/elements/screen_overflow_x.frame +1 -0
- data/screen_test/elements/screen_overflow_x.rb +9 -0
- data/screen_test/elements/screen_overflow_y.frame +1 -0
- data/screen_test/elements/screen_overflow_y.rb +9 -0
- data/screen_test/elements/text.frame +1 -0
- data/screen_test/elements/text.rb +9 -0
- data/screen_test/elements/text_multiline.frame +1 -0
- data/screen_test/elements/text_multiline.rb +9 -0
- data/screen_test/settings/align/box_around.frame +1 -0
- data/screen_test/settings/align/box_around.rb +16 -0
- data/screen_test/settings/align/box_between.frame +1 -0
- data/screen_test/settings/align/box_between.rb +16 -0
- data/screen_test/settings/align/box_center.frame +1 -0
- data/screen_test/settings/align/box_center.rb +21 -0
- data/screen_test/settings/align/box_default.frame +1 -0
- data/screen_test/settings/align/box_default.rb +20 -0
- data/screen_test/settings/align/box_evenly.frame +1 -0
- data/screen_test/settings/align/box_evenly.rb +16 -0
- data/screen_test/settings/align/box_left.frame +1 -0
- data/screen_test/settings/align/box_left.rb +21 -0
- data/screen_test/settings/align/box_right.frame +1 -0
- data/screen_test/settings/align/box_right.rb +21 -0
- data/screen_test/settings/align/children_center.frame +1 -0
- data/screen_test/settings/align/children_center.rb +15 -0
- data/screen_test/settings/align/children_left.frame +1 -0
- data/screen_test/settings/align/children_left.rb +15 -0
- data/screen_test/settings/align/children_right.frame +1 -0
- data/screen_test/settings/align/children_right.rb +15 -0
- data/screen_test/settings/align/grid_center.frame +1 -0
- data/screen_test/settings/align/grid_center.rb +18 -0
- data/screen_test/settings/align/grid_default.frame +1 -0
- data/screen_test/settings/align/grid_default.rb +17 -0
- data/screen_test/settings/align/grid_left.frame +1 -0
- data/screen_test/settings/align/grid_left.rb +18 -0
- data/screen_test/settings/align/grid_right.frame +1 -0
- data/screen_test/settings/align/grid_right.rb +18 -0
- data/screen_test/settings/ansi/bold.frame +1 -0
- data/screen_test/settings/ansi/bold.rb +14 -0
- data/screen_test/settings/ansi/color.frame +1 -0
- data/screen_test/settings/ansi/color.rb +37 -0
- data/screen_test/settings/ansi/underline.frame +1 -0
- data/screen_test/settings/ansi/underline.rb +14 -0
- data/screen_test/settings/border.frame +1 -0
- data/screen_test/settings/border.rb +13 -0
- data/screen_test/settings/flow/box_b2t.frame +1 -0
- data/screen_test/settings/flow/box_b2t.rb +26 -0
- data/screen_test/settings/flow/box_l2r.frame +1 -0
- data/screen_test/settings/flow/box_l2r.rb +26 -0
- data/screen_test/settings/flow/box_r2l.frame +1 -0
- data/screen_test/settings/flow/box_r2l.rb +26 -0
- data/screen_test/settings/flow/box_t2b.frame +1 -0
- data/screen_test/settings/flow/box_t2b.rb +26 -0
- data/screen_test/settings/flow/grid_b2t.frame +1 -0
- data/screen_test/settings/flow/grid_b2t.rb +14 -0
- data/screen_test/settings/flow/grid_l2r.frame +1 -0
- data/screen_test/settings/flow/grid_l2r.rb +14 -0
- data/screen_test/settings/flow/grid_r2l.frame +1 -0
- data/screen_test/settings/flow/grid_r2l.rb +14 -0
- data/screen_test/settings/flow/grid_t2b.frame +1 -0
- data/screen_test/settings/flow/grid_t2b.rb +14 -0
- data/screen_test/settings/height/box.frame +1 -0
- data/screen_test/settings/height/box.rb +13 -0
- data/screen_test/settings/height/box_border_sizing.frame +1 -0
- data/screen_test/settings/height/box_border_sizing.rb +15 -0
- data/screen_test/settings/height/grid.frame +1 -0
- data/screen_test/settings/height/grid.rb +14 -0
- data/screen_test/settings/height/overflow_box.frame +1 -0
- data/screen_test/settings/height/overflow_box.rb +13 -0
- data/screen_test/settings/height/overflow_box_l2r.frame +1 -0
- data/screen_test/settings/height/overflow_box_l2r.rb +17 -0
- data/screen_test/settings/height/overflow_box_t2b.frame +1 -0
- data/screen_test/settings/height/overflow_box_t2b.rb +16 -0
- data/screen_test/settings/height/overflow_grid.frame +1 -0
- data/screen_test/settings/height/overflow_grid.rb +16 -0
- data/screen_test/settings/margin.frame +1 -0
- data/screen_test/settings/margin.rb +16 -0
- data/screen_test/settings/padding.frame +1 -0
- data/screen_test/settings/padding.rb +13 -0
- data/screen_test/settings/position/box_left.frame +1 -0
- data/screen_test/settings/position/box_left.rb +17 -0
- data/screen_test/settings/position/box_left_negative.frame +1 -0
- data/screen_test/settings/position/box_left_negative.rb +17 -0
- data/screen_test/settings/position/box_top.frame +1 -0
- data/screen_test/settings/position/box_top.rb +17 -0
- data/screen_test/settings/position/box_top_negative.frame +1 -0
- data/screen_test/settings/position/box_top_negative.rb +17 -0
- data/screen_test/settings/position/grid_left.frame +1 -0
- data/screen_test/settings/position/grid_left.rb +18 -0
- data/screen_test/settings/position/grid_left_negative.frame +1 -0
- data/screen_test/settings/position/grid_left_negative.rb +18 -0
- data/screen_test/settings/position/grid_top.frame +1 -0
- data/screen_test/settings/position/grid_top.rb +18 -0
- data/screen_test/settings/position/grid_top_negative.frame +1 -0
- data/screen_test/settings/position/grid_top_negative.rb +18 -0
- data/screen_test/settings/scroll/horiz_box.frame +1 -0
- data/screen_test/settings/scroll/horiz_box.rb +17 -0
- data/screen_test/settings/scroll/horiz_box_align_center.rb +18 -0
- data/screen_test/settings/scroll/horiz_box_align_right.rb +18 -0
- data/screen_test/settings/scroll/vert_box.frame +1 -0
- data/screen_test/settings/scroll/vert_box.rb +20 -0
- data/screen_test/settings/title_font.frame +1 -0
- data/screen_test/settings/title_font.rb +12 -0
- data/screen_test/settings/valign/box_around.frame +1 -0
- data/screen_test/settings/valign/box_around.rb +17 -0
- data/screen_test/settings/valign/box_between.frame +1 -0
- data/screen_test/settings/valign/box_between.rb +17 -0
- data/screen_test/settings/valign/box_bottom.frame +1 -0
- data/screen_test/settings/valign/box_bottom.rb +17 -0
- data/screen_test/settings/valign/box_default.frame +1 -0
- data/screen_test/settings/valign/box_default.rb +16 -0
- data/screen_test/settings/valign/box_evenly.frame +1 -0
- data/screen_test/settings/valign/box_evenly.rb +17 -0
- data/screen_test/settings/valign/box_middle.frame +1 -0
- data/screen_test/settings/valign/box_middle.rb +17 -0
- data/screen_test/settings/valign/box_top.frame +1 -0
- data/screen_test/settings/valign/box_top.rb +17 -0
- data/screen_test/settings/valign/grid_bottom.frame +1 -0
- data/screen_test/settings/valign/grid_bottom.rb +15 -0
- data/screen_test/settings/valign/grid_default.frame +1 -0
- data/screen_test/settings/valign/grid_default.rb +14 -0
- data/screen_test/settings/valign/grid_middle.frame +1 -0
- data/screen_test/settings/valign/grid_middle.rb +15 -0
- data/screen_test/settings/valign/grid_top.frame +1 -0
- data/screen_test/settings/valign/grid_top.rb +15 -0
- data/screen_test/settings/width/box_border_sizing.frame +1 -0
- data/screen_test/settings/width/box_border_sizing.rb +15 -0
- data/screen_test/settings/width/box_content.frame +1 -0
- data/screen_test/settings/width/box_content.rb +15 -0
- data/screen_test/settings/width/box_default.frame +1 -0
- data/screen_test/settings/width/box_default.rb +14 -0
- data/screen_test/settings/width/grid.frame +1 -0
- data/screen_test/settings/width/grid.rb +14 -0
- data/screen_test/settings/width/overflow_align_center.frame +1 -0
- data/screen_test/settings/width/overflow_align_center.rb +14 -0
- data/screen_test/settings/width/overflow_align_right.frame +1 -0
- data/screen_test/settings/width/overflow_align_right.rb +14 -0
- data/screen_test/settings/width/overflow_box.frame +1 -0
- data/screen_test/settings/width/overflow_box.rb +13 -0
- data/screen_test/settings/width/overflow_box_l2r.frame +1 -0
- data/screen_test/settings/width/overflow_box_l2r.rb +16 -0
- data/screen_test/settings/width/overflow_box_t2b.frame +1 -0
- data/screen_test/settings/width/overflow_box_t2b.rb +17 -0
- data/screen_test/settings/width/overflow_grid.frame +1 -0
- data/screen_test/settings/width/overflow_grid.rb +14 -0
- data/tools/whirled_peas/tools/screen_tester.rb +233 -0
- data/whirled_peas.gemspec +4 -1
- metadata +215 -17
- data/lib/whirled_peas/ui.rb +0 -7
- data/lib/whirled_peas/ui/ansi.rb +0 -103
- data/lib/whirled_peas/ui/canvas.rb +0 -68
- data/lib/whirled_peas/ui/color.rb +0 -101
- data/lib/whirled_peas/ui/element.rb +0 -224
- data/lib/whirled_peas/ui/painter.rb +0 -283
- data/lib/whirled_peas/ui/screen.rb +0 -60
- data/lib/whirled_peas/ui/settings.rb +0 -523
- data/sandbox/auto.rb +0 -13
- data/sandbox/box.rb +0 -19
- data/sandbox/grid.rb +0 -13
- data/sandbox/sandbox.rb +0 -17
- data/sandbox/text.rb +0 -33
@@ -0,0 +1,21 @@
|
|
1
|
+
module WhirledPeas
|
2
|
+
class Config
|
3
|
+
attr_writer :driver, :template_factory
|
4
|
+
attr_accessor :loading_template_factory
|
5
|
+
|
6
|
+
def driver
|
7
|
+
unless @driver
|
8
|
+
raise ConfigurationError, 'driver must be configured'
|
9
|
+
end
|
10
|
+
@driver
|
11
|
+
end
|
12
|
+
|
13
|
+
def template_factory
|
14
|
+
unless @template_factory
|
15
|
+
raise ConfigurationError, 'template_factory must be configured'
|
16
|
+
end
|
17
|
+
@template_factory
|
18
|
+
end
|
19
|
+
end
|
20
|
+
private_constant :Config
|
21
|
+
end
|
data/lib/whirled_peas/frame.rb
CHANGED
@@ -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,59 +1,90 @@
|
|
1
|
+
require 'whirled_peas/null_logger'
|
2
|
+
require 'whirled_peas/graphics/screen'
|
3
|
+
|
4
|
+
require_relative 'consumer'
|
5
|
+
|
1
6
|
module WhirledPeas
|
2
7
|
module Frame
|
3
|
-
class EventLoop
|
4
|
-
|
8
|
+
class EventLoop < Consumer
|
9
|
+
DEFAULT_REFRESH_RATE = 30
|
10
|
+
|
11
|
+
LOGGER_ID = 'EVENT LOOP'
|
12
|
+
|
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
|
+
)
|
5
20
|
@template_factory = template_factory
|
21
|
+
@loading_template_factory = loading_template_factory
|
6
22
|
@queue = Queue.new
|
7
|
-
@
|
23
|
+
@frame_duration = 1.0 / refresh_rate
|
8
24
|
@logger = logger
|
25
|
+
@screen = screen
|
9
26
|
end
|
10
27
|
|
11
|
-
def enqueue(name
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def running?
|
16
|
-
@running
|
28
|
+
def enqueue(name, duration, args)
|
29
|
+
# If duration is nil, set it to the duration of a single frame
|
30
|
+
queue.push([name, duration || frame_duration, args])
|
17
31
|
end
|
18
32
|
|
19
33
|
def start
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
sleep(0.01) while queue.empty? # Wait for the first event
|
24
|
-
remaining_frames = 1
|
25
|
-
template = nil
|
26
|
-
while @running && remaining_frames > 0
|
27
|
-
frame_at = Time.now
|
28
|
-
next_frame_at = frame_at + 1.0 / refresh_rate
|
29
|
-
remaining_frames -= 1
|
30
|
-
if remaining_frames > 0
|
31
|
-
screen.refresh if screen.needs_refresh?
|
32
|
-
elsif !queue.empty?
|
33
|
-
name, duration, args = queue.pop
|
34
|
-
remaining_frames = duration ? duration * refresh_rate : 1
|
35
|
-
template = template_factory.build(name, args)
|
36
|
-
screen.paint(template)
|
37
|
-
end
|
38
|
-
sleep([0, next_frame_at - Time.now].max)
|
39
|
-
end
|
40
|
-
logger.info('EVENT LOOP') { 'Exiting normally' }
|
34
|
+
super
|
35
|
+
wait_for_content
|
36
|
+
play_content
|
41
37
|
rescue
|
42
|
-
|
43
|
-
|
38
|
+
self.running = false
|
39
|
+
logger.warn(LOGGER_ID) { 'Exiting with error' }
|
44
40
|
raise
|
45
41
|
ensure
|
46
|
-
screen.finalize
|
42
|
+
screen.finalize
|
47
43
|
end
|
48
44
|
|
49
|
-
|
50
|
-
|
51
|
-
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :template_factory, :loading_template_factory, :queue, :frame_duration, :logger, :screen
|
48
|
+
|
49
|
+
def wait_for_content
|
50
|
+
if loading_template_factory
|
51
|
+
play_loading_screen
|
52
|
+
else
|
53
|
+
sleep(frame_duration) while queue.empty?
|
54
|
+
end
|
52
55
|
end
|
53
56
|
|
54
|
-
|
57
|
+
def play_loading_screen
|
58
|
+
while queue.empty?
|
59
|
+
screen.paint(loading_template_factory.build)
|
60
|
+
sleep(frame_duration)
|
61
|
+
end
|
62
|
+
end
|
55
63
|
|
56
|
-
|
64
|
+
def play_content
|
65
|
+
template = nil
|
66
|
+
frame_until = Time.new(0) # Tell the loop to immediately pick up a new frame
|
67
|
+
while running?
|
68
|
+
frame_start = Time.now
|
69
|
+
next_frame_at = frame_start + frame_duration
|
70
|
+
if frame_until > frame_start
|
71
|
+
# While we're still displaying the previous frame, refresh the screen
|
72
|
+
screen.refresh
|
73
|
+
elsif !queue.empty?
|
74
|
+
name, duration, args = queue.pop
|
75
|
+
if name == EOF
|
76
|
+
self.running = false
|
77
|
+
else
|
78
|
+
frame_until = frame_start + duration
|
79
|
+
template = template_factory.build(name, args)
|
80
|
+
screen.paint(template)
|
81
|
+
end
|
82
|
+
else
|
83
|
+
wait_for_content
|
84
|
+
end
|
85
|
+
sleep([0, next_frame_at - Time.now].max)
|
86
|
+
end
|
87
|
+
end
|
57
88
|
end
|
58
89
|
end
|
59
90
|
end
|
@@ -1,50 +1,67 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'json'
|
3
3
|
|
4
|
+
require 'whirled_peas/null_logger'
|
5
|
+
|
4
6
|
module WhirledPeas
|
5
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.
|
6
12
|
class Producer
|
7
13
|
LOGGER_ID = 'PRODUCER'
|
8
14
|
|
9
|
-
|
10
|
-
|
11
|
-
|
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`
|
20
|
+
def self.produce(consumer, logger=NullLogger.new)
|
21
|
+
producer = new(consumer, logger)
|
22
|
+
consumer_thread = Thread.new do
|
23
|
+
Thread.current.report_on_exception = false
|
24
|
+
consumer.start
|
25
|
+
end
|
12
26
|
yield producer
|
13
|
-
|
14
|
-
producer.send_frame(Frame::EOF)
|
15
|
-
logger.info(LOGGER_ID) { 'Exited normally' }
|
27
|
+
producer.flush
|
16
28
|
rescue => e
|
17
|
-
producer.send_frame(Frame::TERMINATE)
|
18
29
|
logger.warn(LOGGER_ID) { 'Exited with error' }
|
19
30
|
logger.error(LOGGER_ID) { e }
|
20
31
|
raise
|
32
|
+
ensure
|
33
|
+
consumer.stop
|
34
|
+
consumer_thread.join if consumer_thread
|
21
35
|
end
|
22
36
|
|
23
|
-
def initialize(
|
24
|
-
@
|
37
|
+
def initialize(consumer, logger=NullLogger.new)
|
38
|
+
@consumer = consumer
|
25
39
|
@logger = logger
|
26
40
|
@queue = Queue.new
|
27
41
|
end
|
28
42
|
|
43
|
+
# Buffer a frame to be played for the given duration. `#flush` must be called
|
44
|
+
# for frames to get pushed to the EventLoop.
|
45
|
+
#
|
46
|
+
# @param name [String] name of frame, which is passed to #build of the
|
47
|
+
# TemplateFactory
|
48
|
+
# @param duration [Float|Integer] duration in seconds the frame should be,
|
49
|
+
# displayed (default is nil, which results in a duration of a single refresh
|
50
|
+
# cycle)
|
51
|
+
# @param args [Hash] key/value pair of arguments, which is passed to #build of
|
52
|
+
# the TemplateFactory
|
29
53
|
def send_frame(name, duration: nil, args: {})
|
30
|
-
event_loop.enqueue(name: name, duration: duration, args: args)
|
31
|
-
logger.debug(LOGGER_ID) { "Sending frame: #{name}" }
|
32
|
-
end
|
33
|
-
|
34
|
-
def enqueue_frame(name, duration: nil, args: {})
|
35
54
|
queue.push([name, duration, args])
|
36
55
|
end
|
37
56
|
|
57
|
+
# Send any buffered frames to the EventLoop
|
38
58
|
def flush
|
39
|
-
while !queue.empty?
|
40
|
-
name, duration, args = queue.pop
|
41
|
-
send_frame(name: name, duration: duration, args: args)
|
42
|
-
end
|
59
|
+
consumer.enqueue(*queue.pop) while !queue.empty?
|
43
60
|
end
|
44
61
|
|
45
62
|
private
|
46
63
|
|
47
|
-
attr_reader :
|
64
|
+
attr_reader :consumer, :logger, :queue
|
48
65
|
end
|
49
66
|
end
|
50
67
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module WhirledPeas
|
2
|
+
module Graphics
|
3
|
+
class << self
|
4
|
+
attr_accessor :debug
|
5
|
+
|
6
|
+
def debugger(string_or_proc)
|
7
|
+
return unless @debug
|
8
|
+
@debugger ||= Logger.new(STDOUT, level: Logger::DEBUG)
|
9
|
+
if string_or_proc.is_a?(Proc)
|
10
|
+
string = string_or_proc.call
|
11
|
+
else
|
12
|
+
string = string_or_proc
|
13
|
+
end
|
14
|
+
@debugger.debug(string)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
private_constant :Graphics
|
19
|
+
end
|
@@ -0,0 +1,101 @@
|
|
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, left, top, &block)
|
8
|
+
super
|
9
|
+
canvas_coords = coords(left, top)
|
10
|
+
content_canvas = canvas.child(
|
11
|
+
canvas_coords.content_left,
|
12
|
+
canvas_coords.content_top,
|
13
|
+
dimensions.content_width,
|
14
|
+
dimensions.content_height
|
15
|
+
)
|
16
|
+
return unless content_canvas.writable?
|
17
|
+
if settings.horizontal_flow?
|
18
|
+
paint_horizontally(content_canvas, canvas_coords, &block)
|
19
|
+
else
|
20
|
+
paint_vertically(content_canvas, canvas_coords, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def dimensions
|
25
|
+
@dimensions ||= begin
|
26
|
+
content_width = 0
|
27
|
+
content_height = 0
|
28
|
+
if settings.horizontal_flow?
|
29
|
+
each_child do |child|
|
30
|
+
content_width += child.dimensions.outer_width
|
31
|
+
if child.dimensions.outer_height > content_height
|
32
|
+
content_height = child.dimensions.outer_height
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
each_child do |child|
|
37
|
+
if child.dimensions.outer_width > content_width
|
38
|
+
content_width = child.dimensions.outer_width
|
39
|
+
end
|
40
|
+
content_height += child.dimensions.outer_height
|
41
|
+
end
|
42
|
+
end
|
43
|
+
ContainerDimensions.new(settings, content_width, content_height)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def each_child(&block)
|
48
|
+
if settings.reverse_flow?
|
49
|
+
children.reverse.each(&block)
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def paint_horizontally(canvas, canvas_coords, &block)
|
58
|
+
stroke_left = canvas_coords.content_left
|
59
|
+
stroke_top = canvas_coords.content_top
|
60
|
+
children_width = 0
|
61
|
+
each_child { |c| children_width += c.dimensions.outer_width }
|
62
|
+
left_offset, spacing_offset = horiz_justify_offset(children_width)
|
63
|
+
stroke_left += left_offset
|
64
|
+
each_child do |child|
|
65
|
+
top_offset, _ = vert_justify_offset(child.dimensions.outer_height)
|
66
|
+
child_width = child.dimensions.outer_width
|
67
|
+
child_canvas = canvas.child(
|
68
|
+
stroke_left,
|
69
|
+
stroke_top + top_offset,
|
70
|
+
child_width,
|
71
|
+
child.dimensions.outer_height
|
72
|
+
)
|
73
|
+
child.paint(child_canvas, stroke_left, stroke_top + top_offset, &block)
|
74
|
+
stroke_left += child_width + spacing_offset
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def paint_vertically(canvas, canvas_coords, &block)
|
79
|
+
stroke_left = canvas_coords.content_left
|
80
|
+
stroke_top = canvas_coords.content_top
|
81
|
+
children_height = 0
|
82
|
+
each_child { |c| children_height += c.dimensions.outer_height }
|
83
|
+
top_offset, spacing_offset = vert_justify_offset(children_height)
|
84
|
+
stroke_top += top_offset
|
85
|
+
each_child do |child|
|
86
|
+
left_offset, _ = horiz_justify_offset(child.dimensions.outer_width)
|
87
|
+
child_height = child.dimensions.outer_height
|
88
|
+
child_canvas = canvas.child(
|
89
|
+
stroke_left + left_offset,
|
90
|
+
stroke_top ,
|
91
|
+
child.dimensions.outer_width,
|
92
|
+
child_height
|
93
|
+
)
|
94
|
+
child.paint(child_canvas, stroke_left + left_offset, stroke_top, &block)
|
95
|
+
stroke_top += child_height + spacing_offset
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
private_constant :BoxPainter
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'whirled_peas/utils/formatted_string'
|
2
|
+
|
3
|
+
module WhirledPeas
|
4
|
+
module Graphics
|
5
|
+
# Canvas represent the area of the screen a painter can paint on.
|
6
|
+
class Canvas
|
7
|
+
attr_reader :left, :top, :width, :height
|
8
|
+
|
9
|
+
def self.unwritable
|
10
|
+
new(-1, -1, 0, 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(left, top, width, height)
|
14
|
+
@left = left
|
15
|
+
@top = top
|
16
|
+
@width = width
|
17
|
+
@height = height
|
18
|
+
end
|
19
|
+
|
20
|
+
def writable?
|
21
|
+
width > 0 || height > 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def child(child_left, child_top, child_width, child_height)
|
25
|
+
Graphics.debugger(
|
26
|
+
proc do
|
27
|
+
"Create child: #{self.inspect}.child(left=#{child_left}, top=#{child_top}, width=#{child_width}, height=#{child_height})"
|
28
|
+
end
|
29
|
+
)
|
30
|
+
if child_left >= left + width
|
31
|
+
self.class.unwritable
|
32
|
+
elsif child_left + child_width <= left
|
33
|
+
self.class.unwritable
|
34
|
+
elsif child_top >= top + height
|
35
|
+
self.class.unwritable
|
36
|
+
elsif child_top + child_height <= top
|
37
|
+
self.class.unwritable
|
38
|
+
else
|
39
|
+
if child_left < left
|
40
|
+
child_width -= left - child_left
|
41
|
+
child_left = left
|
42
|
+
end
|
43
|
+
child_width = [width - (child_left - left), child_width].min
|
44
|
+
if child_top < top
|
45
|
+
child_height -= top - child_top
|
46
|
+
child_top = top
|
47
|
+
end
|
48
|
+
child_height = [height - (child_top - top), child_height].min
|
49
|
+
child_canvas = self.class.new(
|
50
|
+
child_left,
|
51
|
+
child_top,
|
52
|
+
child_width,
|
53
|
+
child_height,
|
54
|
+
)
|
55
|
+
Graphics.debugger(proc { " -> #{child_canvas.inspect}" })
|
56
|
+
child_canvas
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Yields a single line of formatted characters positioned on the canvas,
|
61
|
+
# verifying only characters within the canvas are included.
|
62
|
+
def stroke(stroke_left, stroke_top, raw, formatting=[], &block)
|
63
|
+
Graphics.debugger(
|
64
|
+
proc do
|
65
|
+
"Stroke: #{self.inspect}.stroke(left=#{stroke_left}, top=#{stroke_top}, length=#{raw.length})"
|
66
|
+
end
|
67
|
+
)
|
68
|
+
if stroke_left >= left + width
|
69
|
+
# The stroke starts to the right of the canvas
|
70
|
+
fstring = Utils::FormattedString.blank
|
71
|
+
elsif stroke_left + raw.length <= left
|
72
|
+
# The stroke ends to the left of the canvas
|
73
|
+
fstring = Utils::FormattedString.blank
|
74
|
+
elsif stroke_top < top
|
75
|
+
# The stroke is above the canvas
|
76
|
+
fstring = Utils::FormattedString.blank
|
77
|
+
elsif stroke_top >= top + height
|
78
|
+
# The stroke is below the canvas
|
79
|
+
fstring = Utils::FormattedString.blank
|
80
|
+
else
|
81
|
+
# In this section, we know that at least part of the stroke should be visible
|
82
|
+
# on the canvas. Chop off parts of the raw string that aren't within the
|
83
|
+
# canvas boundary and ensure the stroke start position is also within the
|
84
|
+
# canvas boundary
|
85
|
+
|
86
|
+
# If the stroke starts to the left of the canvas, set the start index to the
|
87
|
+
# first value that will be on the canvas, then update stroke_left to be on
|
88
|
+
# the canvas
|
89
|
+
start_index = stroke_left < left ? left - stroke_left : 0
|
90
|
+
stroke_left = left if stroke_left <= left
|
91
|
+
|
92
|
+
# Determine how many characters from the stroke will fit on the canvas
|
93
|
+
visible_length = [raw.length, width - (stroke_left - left)].min
|
94
|
+
end_index = start_index + visible_length - 1
|
95
|
+
fstring = Utils::FormattedString.new(raw[start_index..end_index], formatting)
|
96
|
+
end
|
97
|
+
Graphics.debugger(
|
98
|
+
proc do
|
99
|
+
" -> Stroke(left=#{stroke_left}, top=#{stroke_top}, length=#{fstring.length})"
|
100
|
+
end
|
101
|
+
)
|
102
|
+
yield stroke_left, stroke_top, fstring
|
103
|
+
end
|
104
|
+
|
105
|
+
def hash
|
106
|
+
[left, top, width, height].hash
|
107
|
+
end
|
108
|
+
|
109
|
+
def ==(other)
|
110
|
+
other.is_a?(self.class) && hash == other.hash
|
111
|
+
end
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
"Canvas(left=#{left}, top=#{top}, width=#{width}, height=#{height})"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|