whirled_peas 0.7.1 → 0.11.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/.gitignore +1 -0
- data/CHANGELOG.md +25 -0
- data/README.md +530 -121
- data/Rakefile +1 -20
- data/bin/easing +33 -0
- data/bin/reset_cursor +11 -0
- data/bin/screen_test +68 -0
- data/examples/components.rb +35 -0
- data/examples/graph.rb +54 -0
- data/examples/intro.rb +3 -3
- data/examples/scrolling.rb +13 -23
- data/lib/data/themes.yaml +13 -0
- data/lib/whirled_peas.rb +33 -13
- data/lib/whirled_peas/animator.rb +5 -0
- data/lib/whirled_peas/animator/debug_consumer.rb +17 -0
- data/lib/whirled_peas/animator/easing.rb +72 -0
- data/lib/whirled_peas/animator/frame.rb +5 -0
- data/lib/whirled_peas/animator/frameset.rb +33 -0
- data/lib/whirled_peas/animator/producer.rb +35 -0
- data/lib/whirled_peas/animator/renderer_consumer.rb +31 -0
- data/lib/whirled_peas/command.rb +5 -0
- data/lib/whirled_peas/command/base.rb +86 -0
- data/lib/whirled_peas/command/config_command.rb +44 -0
- data/lib/whirled_peas/command/debug.rb +21 -0
- data/lib/whirled_peas/command/fonts.rb +22 -0
- data/lib/whirled_peas/command/frame_command.rb +34 -0
- data/lib/whirled_peas/command/frames.rb +24 -0
- data/lib/whirled_peas/command/help.rb +38 -0
- data/lib/whirled_peas/command/play.rb +112 -0
- data/lib/whirled_peas/command/record.rb +57 -0
- data/lib/whirled_peas/command/still.rb +29 -0
- data/lib/whirled_peas/command/themes.rb +77 -0
- data/lib/whirled_peas/command_line.rb +24 -212
- data/lib/whirled_peas/component.rb +35 -0
- data/lib/whirled_peas/component/list_with_active.rb +88 -0
- data/lib/whirled_peas/config.rb +56 -6
- data/lib/whirled_peas/device.rb +5 -0
- data/lib/whirled_peas/device/null_device.rb +8 -0
- data/lib/whirled_peas/device/output_file.rb +19 -0
- data/lib/whirled_peas/device/screen.rb +26 -0
- data/lib/whirled_peas/graphics/box_painter.rb +4 -4
- data/lib/whirled_peas/graphics/composer.rb +27 -2
- data/lib/whirled_peas/graphics/container_coords.rb +24 -2
- data/lib/whirled_peas/graphics/container_painter.rb +63 -82
- data/lib/whirled_peas/graphics/content_dimensions.rb +19 -0
- data/lib/whirled_peas/graphics/content_painter.rb +20 -0
- data/lib/whirled_peas/graphics/debugger.rb +1 -7
- data/lib/whirled_peas/graphics/graph_dimensions.rb +12 -0
- data/lib/whirled_peas/graphics/graph_painter.rb +97 -0
- data/lib/whirled_peas/graphics/grid_painter.rb +0 -3
- data/lib/whirled_peas/graphics/painter.rb +10 -0
- data/lib/whirled_peas/graphics/renderer.rb +8 -2
- data/lib/whirled_peas/graphics/scrollbar_helper.rb +124 -0
- data/lib/whirled_peas/graphics/text_painter.rb +7 -11
- data/lib/whirled_peas/null_logger.rb +0 -1
- data/lib/whirled_peas/settings/border.rb +25 -4
- data/lib/whirled_peas/settings/container_settings.rb +14 -9
- data/lib/whirled_peas/settings/debugger.rb +1 -1
- data/lib/whirled_peas/settings/element_settings.rb +12 -3
- data/lib/whirled_peas/settings/graph_settings.rb +21 -0
- data/lib/whirled_peas/settings/grid_settings.rb +7 -0
- data/lib/whirled_peas/settings/position.rb +17 -5
- data/lib/whirled_peas/settings/text_settings.rb +1 -0
- data/lib/whirled_peas/settings/theme.rb +40 -0
- data/lib/whirled_peas/settings/theme_library.rb +63 -0
- data/lib/whirled_peas/utils/ansi.rb +13 -0
- data/lib/whirled_peas/utils/file_handler.rb +57 -0
- data/lib/whirled_peas/version.rb +1 -1
- data/screen_test/components/list_with_active/l2r_active_color.frame +1 -0
- data/screen_test/components/list_with_active/l2r_active_color.rb +18 -0
- data/screen_test/components/list_with_active/l2r_position_end.frame +1 -0
- data/screen_test/components/list_with_active/l2r_position_end.rb +16 -0
- data/screen_test/components/list_with_active/l2r_position_middle.frame +1 -0
- data/screen_test/components/list_with_active/l2r_position_middle.rb +16 -0
- data/screen_test/components/list_with_active/l2r_position_start.frame +1 -0
- data/screen_test/components/list_with_active/l2r_position_start.rb +16 -0
- data/screen_test/components/list_with_active/l2r_separator.frame +1 -0
- data/screen_test/components/list_with_active/l2r_separator.rb +17 -0
- data/screen_test/components/list_with_active/t2b_active_color.frame +1 -0
- data/screen_test/components/list_with_active/t2b_active_color.rb +19 -0
- data/screen_test/components/list_with_active/t2b_position_end.frame +1 -0
- data/screen_test/components/list_with_active/t2b_position_end.rb +17 -0
- data/screen_test/components/list_with_active/t2b_position_middle.frame +1 -0
- data/screen_test/components/list_with_active/t2b_position_middle.rb +17 -0
- data/screen_test/components/list_with_active/t2b_position_start.frame +1 -0
- data/screen_test/components/list_with_active/t2b_position_start.rb +17 -0
- data/screen_test/components/list_with_active/t2b_separator.frame +1 -0
- data/screen_test/components/list_with_active/t2b_separator.rb +18 -0
- data/screen_test/elements/box.frame +1 -1
- data/screen_test/elements/box.rb +1 -1
- data/screen_test/elements/graph.frame +1 -0
- data/screen_test/elements/graph.rb +12 -0
- data/screen_test/elements/grid.frame +1 -1
- data/screen_test/elements/grid.rb +1 -1
- data/screen_test/elements/screen_overflow_x.frame +1 -1
- data/screen_test/elements/screen_overflow_x.rb +1 -1
- data/screen_test/elements/screen_overflow_y.frame +1 -1
- data/screen_test/elements/screen_overflow_y.rb +1 -1
- data/screen_test/elements/text.frame +1 -1
- data/screen_test/elements/text.rb +1 -1
- data/screen_test/elements/text_multiline.frame +1 -1
- data/screen_test/elements/text_multiline.rb +1 -1
- data/screen_test/elements/theme.frame +1 -0
- data/screen_test/elements/theme.rb +26 -0
- data/screen_test/settings/align/box_around.frame +1 -1
- data/screen_test/settings/align/box_around.rb +1 -1
- data/screen_test/settings/align/box_between.frame +1 -1
- data/screen_test/settings/align/box_between.rb +1 -1
- data/screen_test/settings/align/box_center.frame +1 -1
- data/screen_test/settings/align/box_center.rb +1 -1
- data/screen_test/settings/align/box_default.frame +1 -1
- data/screen_test/settings/align/box_default.rb +1 -1
- data/screen_test/settings/align/box_evenly.frame +1 -1
- data/screen_test/settings/align/box_evenly.rb +1 -1
- data/screen_test/settings/align/box_left.frame +1 -1
- data/screen_test/settings/align/box_left.rb +1 -1
- data/screen_test/settings/align/box_right.frame +1 -1
- data/screen_test/settings/align/box_right.rb +1 -1
- data/screen_test/settings/align/children_center.frame +1 -1
- data/screen_test/settings/align/children_center.rb +1 -1
- data/screen_test/settings/align/children_left.frame +1 -1
- data/screen_test/settings/align/children_left.rb +1 -1
- data/screen_test/settings/align/children_right.frame +1 -1
- data/screen_test/settings/align/children_right.rb +1 -1
- data/screen_test/settings/align/grid_center.frame +1 -1
- data/screen_test/settings/align/grid_center.rb +1 -1
- data/screen_test/settings/align/grid_default.frame +1 -1
- data/screen_test/settings/align/grid_default.rb +1 -1
- data/screen_test/settings/align/grid_left.frame +1 -1
- data/screen_test/settings/align/grid_left.rb +1 -1
- data/screen_test/settings/align/grid_right.frame +1 -1
- data/screen_test/settings/align/grid_right.rb +1 -1
- data/screen_test/settings/ansi/bold.frame +1 -1
- data/screen_test/settings/ansi/bold.rb +1 -1
- data/screen_test/settings/ansi/color.frame +1 -1
- data/screen_test/settings/ansi/color.rb +1 -1
- data/screen_test/settings/ansi/underline.frame +1 -1
- data/screen_test/settings/ansi/underline.rb +1 -1
- data/screen_test/settings/border.frame +1 -1
- data/screen_test/settings/border.rb +1 -1
- data/screen_test/settings/content_start/box_bottom.frame +1 -0
- data/screen_test/settings/content_start/box_bottom.rb +17 -0
- data/screen_test/settings/content_start/box_left.frame +1 -0
- data/screen_test/settings/{position → content_start}/box_left.rb +2 -2
- data/screen_test/settings/content_start/box_left_negative.frame +1 -0
- data/screen_test/settings/{position → content_start}/box_left_negative.rb +2 -2
- data/screen_test/settings/content_start/box_right.frame +1 -0
- data/screen_test/settings/content_start/box_right.rb +17 -0
- data/screen_test/settings/content_start/box_top.frame +1 -0
- data/screen_test/settings/{position → content_start}/box_top.rb +2 -2
- data/screen_test/settings/content_start/box_top_negative.frame +1 -0
- data/screen_test/settings/{position → content_start}/box_top_negative.rb +2 -2
- data/screen_test/settings/content_start/grid_left.frame +1 -0
- data/screen_test/settings/{position → content_start}/grid_left.rb +2 -2
- data/screen_test/settings/content_start/grid_left_negative.frame +1 -0
- data/screen_test/settings/{position → content_start}/grid_left_negative.rb +2 -2
- data/screen_test/settings/content_start/grid_top.frame +1 -0
- data/screen_test/settings/{position → content_start}/grid_top.rb +2 -2
- data/screen_test/settings/content_start/grid_top_negative.frame +1 -0
- data/screen_test/settings/{position → content_start}/grid_top_negative.rb +2 -2
- data/screen_test/settings/flow/box_b2t.frame +1 -1
- data/screen_test/settings/flow/box_b2t.rb +1 -1
- data/screen_test/settings/flow/box_l2r.frame +1 -1
- data/screen_test/settings/flow/box_l2r.rb +1 -1
- data/screen_test/settings/flow/box_r2l.frame +1 -1
- data/screen_test/settings/flow/box_r2l.rb +1 -1
- data/screen_test/settings/flow/box_t2b.frame +1 -1
- data/screen_test/settings/flow/box_t2b.rb +1 -1
- data/screen_test/settings/flow/grid_b2t.frame +1 -1
- data/screen_test/settings/flow/grid_b2t.rb +1 -1
- data/screen_test/settings/flow/grid_l2r.frame +1 -1
- data/screen_test/settings/flow/grid_l2r.rb +1 -1
- data/screen_test/settings/flow/grid_r2l.frame +1 -1
- data/screen_test/settings/flow/grid_r2l.rb +1 -1
- data/screen_test/settings/flow/grid_t2b.frame +1 -1
- data/screen_test/settings/flow/grid_t2b.rb +1 -1
- data/screen_test/settings/height/box.frame +1 -1
- data/screen_test/settings/height/box.rb +1 -1
- data/screen_test/settings/height/box_border_sizing.frame +1 -1
- data/screen_test/settings/height/box_border_sizing.rb +1 -1
- data/screen_test/settings/height/grid.frame +1 -1
- data/screen_test/settings/height/grid.rb +1 -1
- data/screen_test/settings/height/overflow_box.frame +1 -1
- data/screen_test/settings/height/overflow_box.rb +1 -1
- data/screen_test/settings/height/overflow_box_l2r.frame +1 -1
- data/screen_test/settings/height/overflow_box_l2r.rb +1 -1
- data/screen_test/settings/height/overflow_box_t2b.frame +1 -1
- data/screen_test/settings/height/overflow_box_t2b.rb +1 -1
- data/screen_test/settings/height/overflow_grid.frame +1 -1
- data/screen_test/settings/height/overflow_grid.rb +1 -1
- data/screen_test/settings/margin.frame +1 -1
- data/screen_test/settings/margin.rb +1 -1
- data/screen_test/settings/padding.frame +1 -1
- data/screen_test/settings/padding.rb +1 -1
- data/screen_test/settings/scroll/horiz_box.frame +1 -1
- data/screen_test/settings/scroll/horiz_box.rb +3 -5
- data/screen_test/settings/scroll/horiz_box_align_center.rb +3 -5
- data/screen_test/settings/scroll/horiz_box_align_right.rb +3 -5
- data/screen_test/settings/scroll/vert_box.frame +1 -1
- data/screen_test/settings/scroll/vert_box.rb +6 -8
- data/screen_test/settings/title_font.frame +1 -1
- data/screen_test/settings/title_font.rb +1 -1
- data/screen_test/settings/valign/box_around.frame +1 -1
- data/screen_test/settings/valign/box_around.rb +1 -1
- data/screen_test/settings/valign/box_between.frame +1 -1
- data/screen_test/settings/valign/box_between.rb +1 -1
- data/screen_test/settings/valign/box_bottom.frame +1 -1
- data/screen_test/settings/valign/box_bottom.rb +1 -1
- data/screen_test/settings/valign/box_default.frame +1 -1
- data/screen_test/settings/valign/box_default.rb +1 -1
- data/screen_test/settings/valign/box_evenly.frame +1 -1
- data/screen_test/settings/valign/box_evenly.rb +1 -1
- data/screen_test/settings/valign/box_middle.frame +1 -1
- data/screen_test/settings/valign/box_middle.rb +1 -1
- data/screen_test/settings/valign/box_top.frame +1 -1
- data/screen_test/settings/valign/box_top.rb +1 -1
- data/screen_test/settings/valign/grid_bottom.frame +1 -1
- data/screen_test/settings/valign/grid_bottom.rb +1 -1
- data/screen_test/settings/valign/grid_default.frame +1 -1
- data/screen_test/settings/valign/grid_default.rb +1 -1
- data/screen_test/settings/valign/grid_middle.frame +1 -1
- data/screen_test/settings/valign/grid_middle.rb +1 -1
- data/screen_test/settings/valign/grid_top.frame +1 -1
- data/screen_test/settings/valign/grid_top.rb +1 -1
- data/screen_test/settings/width/box_border_sizing.frame +1 -1
- data/screen_test/settings/width/box_border_sizing.rb +1 -1
- data/screen_test/settings/width/box_content.frame +1 -1
- data/screen_test/settings/width/box_content.rb +1 -1
- data/screen_test/settings/width/box_default.frame +1 -1
- data/screen_test/settings/width/box_default.rb +1 -1
- data/screen_test/settings/width/grid.frame +1 -1
- data/screen_test/settings/width/grid.rb +1 -1
- data/screen_test/settings/width/overflow_align_center.frame +1 -1
- data/screen_test/settings/width/overflow_align_center.rb +1 -1
- data/screen_test/settings/width/overflow_align_right.frame +1 -1
- data/screen_test/settings/width/overflow_align_right.rb +1 -1
- data/screen_test/settings/width/overflow_box.frame +1 -1
- data/screen_test/settings/width/overflow_box.rb +1 -1
- data/screen_test/settings/width/overflow_box_l2r.frame +1 -1
- data/screen_test/settings/width/overflow_box_l2r.rb +1 -1
- data/screen_test/settings/width/overflow_box_t2b.frame +1 -1
- data/screen_test/settings/width/overflow_box_t2b.rb +1 -1
- data/screen_test/settings/width/overflow_grid.frame +1 -1
- data/screen_test/settings/width/overflow_grid.rb +1 -1
- data/tools/whirled_peas/tools/screen_tester.rb +131 -65
- metadata +86 -25
- data/lib/whirled_peas/frame.rb +0 -6
- data/lib/whirled_peas/frame/consumer.rb +0 -30
- data/lib/whirled_peas/frame/debug_consumer.rb +0 -30
- data/lib/whirled_peas/frame/event_loop.rb +0 -90
- data/lib/whirled_peas/frame/producer.rb +0 -67
- data/lib/whirled_peas/graphics/screen.rb +0 -70
- data/lib/whirled_peas/graphics/text_dimensions.rb +0 -15
- data/screen_test/settings/position/box_left.frame +0 -1
- data/screen_test/settings/position/box_left_negative.frame +0 -1
- data/screen_test/settings/position/box_top.frame +0 -1
- data/screen_test/settings/position/box_top_negative.frame +0 -1
- data/screen_test/settings/position/grid_left.frame +0 -1
- data/screen_test/settings/position/grid_left_negative.frame +0 -1
- data/screen_test/settings/position/grid_top.frame +0 -1
- data/screen_test/settings/position/grid_top_negative.frame +0 -1
@@ -0,0 +1,88 @@
|
|
1
|
+
module WhirledPeas
|
2
|
+
module Component
|
3
|
+
class ListWithActive
|
4
|
+
attr_accessor :active_index, :active_color, :active_bg_color, :separator
|
5
|
+
|
6
|
+
attr_reader :items
|
7
|
+
|
8
|
+
attr_writer :flow, :viewport_size
|
9
|
+
|
10
|
+
def items=(value)
|
11
|
+
@items = value.map(&:to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
def flow
|
15
|
+
@flow || :l2r
|
16
|
+
end
|
17
|
+
|
18
|
+
def total_size
|
19
|
+
@total_size ||= if flow == :l2r
|
20
|
+
size = separator.nil? ? 0 : (items.count - 1) * separator.length
|
21
|
+
items.each { |item| size += item.length }
|
22
|
+
size
|
23
|
+
else
|
24
|
+
items.count + (separator.nil? ? 0 : items.count - 1)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def viewport_size
|
29
|
+
@viewport_size || total_size
|
30
|
+
end
|
31
|
+
|
32
|
+
def compose(composer, settings)
|
33
|
+
%i[items active_index].each do |required_attr|
|
34
|
+
if send(required_attr).nil?
|
35
|
+
raise ArugmentError, "Require field #{required_attr} missing"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
composer.add_box('ListWithActive') do |composer, settings|
|
40
|
+
settings.flow = flow
|
41
|
+
settings.align = :left
|
42
|
+
settings.full_border
|
43
|
+
if flow == :l2r
|
44
|
+
settings.width = viewport_size
|
45
|
+
active_start = separator.nil? ? 0 : active_index * separator.length
|
46
|
+
items.first(active_index).each do |item|
|
47
|
+
active_start += item.length
|
48
|
+
end
|
49
|
+
active_size = items[active_index].length
|
50
|
+
else
|
51
|
+
settings.height = viewport_size
|
52
|
+
active_start = active_index + (separator.nil? ? 0 : active_index)
|
53
|
+
active_size = 1
|
54
|
+
end
|
55
|
+
|
56
|
+
if viewport_size < total_size
|
57
|
+
front_padding = (viewport_size - active_size) * 0.667
|
58
|
+
offset = (active_start - front_padding).round
|
59
|
+
if offset < 0
|
60
|
+
offset = 0
|
61
|
+
elsif offset > total_size - viewport_size
|
62
|
+
offset = total_size - viewport_size
|
63
|
+
end
|
64
|
+
|
65
|
+
if flow == :l2r
|
66
|
+
settings.content_start.left = -offset
|
67
|
+
settings.scrollbar.horiz = true
|
68
|
+
else
|
69
|
+
settings.content_start.top = -offset
|
70
|
+
settings.scrollbar.vert = true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
items.each.with_index do |item, index|
|
75
|
+
composer.add_text { separator } if !separator.nil? && index > 0
|
76
|
+
composer.add_text do |_, settings|
|
77
|
+
if index == active_index
|
78
|
+
settings.bg_color = active_bg_color
|
79
|
+
settings.color = active_color
|
80
|
+
end
|
81
|
+
item
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/whirled_peas/config.rb
CHANGED
@@ -1,13 +1,47 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
module WhirledPeas
|
2
4
|
class Config
|
3
|
-
|
4
|
-
|
5
|
+
# Refreshed rate measured in frames per second
|
6
|
+
DEFAULT_REFRESH_RATE = 30
|
7
|
+
|
8
|
+
DEFAULT_LOG_LEVEL = Logger::INFO
|
9
|
+
DEFAULT_LOG_FILE = 'whirled_peas.log'
|
5
10
|
|
6
|
-
|
7
|
-
|
8
|
-
|
11
|
+
# This formatter expects a loggers to send `progname` in each log call. This value
|
12
|
+
# should be an all uppercase version of the module or class that is invoking the
|
13
|
+
# logger. Ruby's logger supports setting this value on a per-log statement basis
|
14
|
+
# when the log message is passed in through a block:
|
15
|
+
#
|
16
|
+
# logger.<level>(progname, &block)
|
17
|
+
#
|
18
|
+
# E.g.
|
19
|
+
#
|
20
|
+
# class Foo
|
21
|
+
# def bar
|
22
|
+
# logger.warn('FOO') { 'Something fishy happened in #bar' }
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# The block format also has the advantage that the evaluation of the block only
|
27
|
+
# occurs if the message gets logged. So expensive to calculate debug statements
|
28
|
+
# will not impact the performance of the application if the log level is INFO or
|
29
|
+
# higher.
|
30
|
+
DEFAULT_FORMATTER = proc do |severity, datetime, progname, msg|
|
31
|
+
# Convert an instance of an exception into a nicely formatted message string
|
32
|
+
if msg.is_a?(Exception)
|
33
|
+
msg = %Q(#{msg.class}: #{msg.to_s}\n #{msg.backtrace.join("\n ")})
|
9
34
|
end
|
10
|
-
|
35
|
+
"[#{severity}] #{datetime.strftime('%Y-%m-%dT%H:%M:%S.%L')} (#{progname}) - #{msg}\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_writer :application, :template_factory, :refresh_rate, :log_level, :log_formatter, :log_file
|
39
|
+
|
40
|
+
def application
|
41
|
+
unless @application
|
42
|
+
raise ConfigurationError, 'application must be configured'
|
43
|
+
end
|
44
|
+
@application
|
11
45
|
end
|
12
46
|
|
13
47
|
def template_factory
|
@@ -16,6 +50,22 @@ module WhirledPeas
|
|
16
50
|
end
|
17
51
|
@template_factory
|
18
52
|
end
|
53
|
+
|
54
|
+
def refresh_rate
|
55
|
+
@refresh_rate || DEFAULT_REFRESH_RATE
|
56
|
+
end
|
57
|
+
|
58
|
+
def log_level
|
59
|
+
@log_level || DEFAULT_LOG_LEVEL
|
60
|
+
end
|
61
|
+
|
62
|
+
def log_formatter
|
63
|
+
@log_formatter || DEFAULT_FORMATTER
|
64
|
+
end
|
65
|
+
|
66
|
+
def log_file
|
67
|
+
@log_file || DEFAULT_LOG_FILE
|
68
|
+
end
|
19
69
|
end
|
20
70
|
private_constant :Config
|
21
71
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'whirled_peas/utils/file_handler'
|
2
|
+
|
3
|
+
module WhirledPeas
|
4
|
+
module Device
|
5
|
+
class OutputFile
|
6
|
+
def initialize(file)
|
7
|
+
@file = file
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle_renders(renders)
|
11
|
+
Utils::FileHandler.write(file, renders)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :file
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'highline'
|
2
|
+
|
3
|
+
module WhirledPeas
|
4
|
+
module Device
|
5
|
+
class Screen
|
6
|
+
def initialize(refresh_rate, output: STDOUT)
|
7
|
+
@refresh_rate = refresh_rate
|
8
|
+
@output = output
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle_renders(renders)
|
12
|
+
renders.each do |strokes|
|
13
|
+
frame_at = Time.now
|
14
|
+
output.print(strokes)
|
15
|
+
output.flush
|
16
|
+
next_frame_at = frame_at + 1.0 / refresh_rate
|
17
|
+
sleep([0, next_frame_at - Time.now].max)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :refresh_rate, :output
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -55,8 +55,8 @@ module WhirledPeas
|
|
55
55
|
private
|
56
56
|
|
57
57
|
def paint_horizontally(canvas, canvas_coords, &block)
|
58
|
-
stroke_left = canvas_coords.
|
59
|
-
stroke_top = canvas_coords.
|
58
|
+
stroke_left = canvas_coords.offset_content_left
|
59
|
+
stroke_top = canvas_coords.offset_content_top
|
60
60
|
children_width = 0
|
61
61
|
each_child { |c| children_width += c.dimensions.outer_width }
|
62
62
|
left_offset, spacing_offset = horiz_justify_offset(children_width)
|
@@ -76,8 +76,8 @@ module WhirledPeas
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def paint_vertically(canvas, canvas_coords, &block)
|
79
|
-
stroke_left = canvas_coords.
|
80
|
-
stroke_top = canvas_coords.
|
79
|
+
stroke_left = canvas_coords.offset_content_left
|
80
|
+
stroke_top = canvas_coords.offset_content_top
|
81
81
|
children_height = 0
|
82
82
|
each_child { |c| children_height += c.dimensions.outer_height }
|
83
83
|
top_offset, spacing_offset = vert_justify_offset(children_height)
|
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'whirled_peas/settings/box_settings'
|
2
|
+
require 'whirled_peas/settings/graph_settings'
|
2
3
|
require 'whirled_peas/settings/grid_settings'
|
3
4
|
require 'whirled_peas/settings/text_settings'
|
5
|
+
require 'whirled_peas/settings/theme_library'
|
4
6
|
|
5
7
|
require_relative 'box_painter'
|
8
|
+
require_relative 'graph_painter'
|
6
9
|
require_relative 'grid_painter'
|
7
10
|
require_relative 'text_painter'
|
8
11
|
|
@@ -23,8 +26,10 @@ module WhirledPeas
|
|
23
26
|
"Element-#{@counter}"
|
24
27
|
end
|
25
28
|
|
26
|
-
def self.build
|
27
|
-
|
29
|
+
def self.build(theme_name=nil, &block)
|
30
|
+
theme_name ||= Settings::ThemeLibrary.default_name
|
31
|
+
theme = Settings::ThemeLibrary.get(theme_name)
|
32
|
+
settings = Settings::BoxSettings.new(theme)
|
28
33
|
template = BoxPainter.new('TEMPLATE', settings)
|
29
34
|
composer = Composer.new(template)
|
30
35
|
value = yield composer, settings
|
@@ -45,6 +50,7 @@ module WhirledPeas
|
|
45
50
|
child = TextPainter.new(name, child_settings)
|
46
51
|
# TextPainters are not composable, so yield nil
|
47
52
|
content = yield nil, child_settings
|
53
|
+
child_settings.validate!
|
48
54
|
unless self.class.stringable?(content)
|
49
55
|
raise ArgumentError, "Unsupported type for text: #{content.class}"
|
50
56
|
end
|
@@ -52,11 +58,29 @@ module WhirledPeas
|
|
52
58
|
painter.add_child(child)
|
53
59
|
end
|
54
60
|
|
61
|
+
def add_graph(name=self.class.next_name, &block)
|
62
|
+
child_settings = Settings::GraphSettings.inherit(painter.settings)
|
63
|
+
child = GraphPainter.new(name, child_settings)
|
64
|
+
# GraphPainters are not composable, so yield nil
|
65
|
+
content = yield nil, child_settings
|
66
|
+
child_settings.validate!
|
67
|
+
unless content.is_a?(Array) && content.length > 0
|
68
|
+
raise ArgumentError, 'Graphs require a non-empty array as the content'
|
69
|
+
end
|
70
|
+
child.content = content
|
71
|
+
painter.add_child(child)
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_component(component)
|
75
|
+
component.compose(self, settings)
|
76
|
+
end
|
77
|
+
|
55
78
|
def add_box(name=self.class.next_name, &block)
|
56
79
|
child_settings = Settings::BoxSettings.inherit(painter.settings)
|
57
80
|
child = BoxPainter.new(name, child_settings)
|
58
81
|
composer = self.class.new(child)
|
59
82
|
value = yield composer, child.settings
|
83
|
+
child_settings.validate!
|
60
84
|
painter.add_child(child)
|
61
85
|
if !child.children? && self.class.stringable?(value)
|
62
86
|
composer.add_text("#{name}-Text") { value.to_s }
|
@@ -68,6 +92,7 @@ module WhirledPeas
|
|
68
92
|
child = GridPainter.new(name, child_settings)
|
69
93
|
composer = self.class.new(child)
|
70
94
|
values = yield composer, child.settings
|
95
|
+
child_settings.validate!
|
71
96
|
painter.add_child(child)
|
72
97
|
if !child.children? && values.is_a?(Array)
|
73
98
|
values.each.with_index do |value, index|
|
@@ -9,11 +9,11 @@ module WhirledPeas
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def left
|
12
|
-
start_left
|
12
|
+
start_left
|
13
13
|
end
|
14
14
|
|
15
15
|
def top
|
16
|
-
start_top
|
16
|
+
start_top
|
17
17
|
end
|
18
18
|
|
19
19
|
def border_left
|
@@ -36,10 +36,32 @@ module WhirledPeas
|
|
36
36
|
padding_left + settings.padding.left + col_index * grid_width
|
37
37
|
end
|
38
38
|
|
39
|
+
def offset_content_left(col_index=0)
|
40
|
+
if settings.content_start.left
|
41
|
+
content_left(col_index) + settings.content_start.left
|
42
|
+
elsif settings.content_start.right
|
43
|
+
left_offset = dimensions.content_width - dimensions.children_width
|
44
|
+
content_left(col_index) + left_offset - settings.content_start.right
|
45
|
+
else
|
46
|
+
content_left(col_index)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
39
50
|
def content_top(row_index=0)
|
40
51
|
padding_top + settings.padding.top + row_index * grid_height
|
41
52
|
end
|
42
53
|
|
54
|
+
def offset_content_top(row_index=0)
|
55
|
+
if settings.content_start.top
|
56
|
+
content_top(row_index) + settings.content_start.top
|
57
|
+
elsif settings.content_start.bottom
|
58
|
+
top_offset = dimensions.content_height - dimensions.children_height
|
59
|
+
content_top(row_index) + top_offset - settings.content_start.bottom
|
60
|
+
else
|
61
|
+
content_top(row_index)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
43
65
|
def inner_grid_width
|
44
66
|
settings.padding.left +
|
45
67
|
dimensions.content_width +
|
@@ -2,9 +2,12 @@ require 'whirled_peas/utils/formatted_string'
|
|
2
2
|
|
3
3
|
require_relative 'container_coords'
|
4
4
|
require_relative 'painter'
|
5
|
+
require_relative 'scrollbar_helper'
|
5
6
|
|
6
7
|
module WhirledPeas
|
7
8
|
module Graphics
|
9
|
+
# Abstract Painter for containers. Containers (as the name implies) contain other child
|
10
|
+
# elements and must delegate painting of the children to the children themselves.
|
8
11
|
class ContainerPainter < Painter
|
9
12
|
PADDING = ' '
|
10
13
|
|
@@ -13,38 +16,65 @@ module WhirledPeas
|
|
13
16
|
@children = []
|
14
17
|
end
|
15
18
|
|
19
|
+
# Paint the common attributes of containers (e.g. border and background color). Any
|
20
|
+
# class that inherits from this one should call `super` at the start of its #paint
|
21
|
+
# method, before painting its children.
|
16
22
|
def paint(canvas, left, top, &block)
|
17
23
|
return unless canvas.writable?
|
18
24
|
return unless needs_printing?
|
19
25
|
canvas_coords = coords(left, top)
|
26
|
+
|
27
|
+
# Paint the border, background color, and scrollbar starting from the top left
|
28
|
+
# border position, moving down row by row until we reach the bottom border
|
29
|
+
# position
|
20
30
|
stroke_left = canvas_coords.border_left
|
21
31
|
stroke_top = canvas_coords.border_top
|
32
|
+
|
33
|
+
# All strokes will have the same formatting options
|
22
34
|
formatting = [*settings.border.color, *settings.bg_color]
|
35
|
+
|
36
|
+
# Paint the top border if the settings call for it
|
23
37
|
if settings.border.top?
|
24
38
|
canvas.stroke(stroke_left, stroke_top, top_border_stroke(canvas_coords), formatting, &block)
|
25
39
|
stroke_top += 1
|
26
40
|
end
|
41
|
+
# Precalculate the middle border container grids with more than 1 row
|
27
42
|
middle_border = dimensions.num_rows > 1 ? middle_border_stroke(canvas_coords) : ''
|
43
|
+
|
44
|
+
# Paint each grid row by row
|
28
45
|
dimensions.num_rows.times do |row_num|
|
46
|
+
# In a grid with N rows, we will need to paint N - 1 inner horizontal borders.
|
47
|
+
# This code treats the inner horizontal border as the top of each row except for
|
48
|
+
# the first one.
|
29
49
|
if row_num > 0 && settings.border.inner_horiz?
|
30
50
|
canvas.stroke(stroke_left, stroke_top, middle_border, formatting, &block)
|
31
51
|
stroke_top += 1
|
32
52
|
end
|
53
|
+
|
54
|
+
# Paint the interior of each row (horizontal borders, veritical scroll bar and
|
55
|
+
# background color for the padding and content area)
|
33
56
|
canvas_coords.inner_grid_height.times do |row_within_cell|
|
34
57
|
canvas.stroke(stroke_left, stroke_top, content_line_stroke(canvas_coords, row_within_cell), formatting, &block)
|
35
58
|
stroke_top += 1
|
36
59
|
end
|
60
|
+
|
61
|
+
# Paint the horizontal scroll bar is the settings call for it
|
37
62
|
if settings.scrollbar.horiz?
|
38
63
|
canvas.stroke(stroke_left, stroke_top, bottom_scroll_stroke(canvas_coords), formatting, &block)
|
39
64
|
stroke_top += 1
|
40
65
|
end
|
41
66
|
end
|
67
|
+
|
68
|
+
# Paint the bottom border if the settings call for it
|
42
69
|
if settings.border.bottom?
|
43
70
|
canvas.stroke(stroke_left, stroke_top, bottom_border_stroke(canvas_coords), formatting, &block)
|
44
71
|
stroke_top += 1
|
45
72
|
end
|
46
73
|
end
|
47
74
|
|
75
|
+
# Tightly manage access to the children (rather than simply exposing the underlying
|
76
|
+
# array). This allows subclasses to easily modify behavior based on that element's
|
77
|
+
# specific settings.
|
48
78
|
def add_child(child)
|
49
79
|
children << child
|
50
80
|
end
|
@@ -65,6 +95,8 @@ module WhirledPeas
|
|
65
95
|
|
66
96
|
attr_reader :children
|
67
97
|
|
98
|
+
# Determine if there is anything to print for the container (this does not accont for
|
99
|
+
# children, just the border, scrollbar, and background color)
|
68
100
|
def needs_printing?
|
69
101
|
return true if settings.bg_color
|
70
102
|
return true if settings.border.outer?
|
@@ -73,10 +105,15 @@ module WhirledPeas
|
|
73
105
|
settings.scrollbar.horiz? || settings.scrollbar.vert?
|
74
106
|
end
|
75
107
|
|
108
|
+
# Return an object that allows easy access to important coordinates within the container,
|
109
|
+
# e.g. the left position where the left border is printed
|
76
110
|
def coords(left, top)
|
77
111
|
ContainerCoords.new(dimensions, settings, left, top)
|
78
112
|
end
|
79
113
|
|
114
|
+
# @return [Array<Integer>] a two-item array, the first being the amount of horizontal
|
115
|
+
# spacing to paint *before the first* child and the second being the amount of spacing
|
116
|
+
# to paint *between each* child
|
80
117
|
def horiz_justify_offset(containing_width)
|
81
118
|
if settings.align_center?
|
82
119
|
[(dimensions.content_width - containing_width) / 2, 0]
|
@@ -96,6 +133,9 @@ module WhirledPeas
|
|
96
133
|
end
|
97
134
|
end
|
98
135
|
|
136
|
+
# @return [Array<Integer>] a two-item array, the first being the amount of vertical
|
137
|
+
# spacing to paint *above the first* child and the second being the amount of spacing
|
138
|
+
# to paint *between each* child
|
99
139
|
def vert_justify_offset(containing_height)
|
100
140
|
if settings.valign_middle?
|
101
141
|
[(dimensions.content_height - containing_height) / 2, 0]
|
@@ -115,6 +155,16 @@ module WhirledPeas
|
|
115
155
|
end
|
116
156
|
end
|
117
157
|
|
158
|
+
# Return a stroke for one line of the container
|
159
|
+
#
|
160
|
+
# @param left_border [String] the character to print as the first character if there
|
161
|
+
# is a left border
|
162
|
+
# @param junc_border [String] the character to print as the junction between two grid
|
163
|
+
# columns if there is an inner vertical border
|
164
|
+
# @param right_border [String] the character to print as the last character if there
|
165
|
+
# is a right border
|
166
|
+
# @block [String] the block should yield a string that represents the interior
|
167
|
+
# (including padding) of a grid cell
|
118
168
|
def line_stroke(left_border, junc_border, right_border, &block)
|
119
169
|
stroke = ''
|
120
170
|
stroke += left_border if settings.border.left?
|
@@ -126,6 +176,7 @@ module WhirledPeas
|
|
126
176
|
stroke
|
127
177
|
end
|
128
178
|
|
179
|
+
# Return the stroke for the top border
|
129
180
|
def top_border_stroke(canvas_coords)
|
130
181
|
line_stroke(
|
131
182
|
settings.border.style.top_left,
|
@@ -136,6 +187,7 @@ module WhirledPeas
|
|
136
187
|
end
|
137
188
|
end
|
138
189
|
|
190
|
+
# Return the stroke for an inner horizontal border
|
139
191
|
def middle_border_stroke(canvas_coords)
|
140
192
|
line_stroke(
|
141
193
|
settings.border.style.left_junc,
|
@@ -146,6 +198,7 @@ module WhirledPeas
|
|
146
198
|
end
|
147
199
|
end
|
148
200
|
|
201
|
+
# Return the stroke for the bottom border
|
149
202
|
def bottom_border_stroke(canvas_coords)
|
150
203
|
line_stroke(
|
151
204
|
settings.border.style.bottom_left,
|
@@ -156,6 +209,7 @@ module WhirledPeas
|
|
156
209
|
end
|
157
210
|
end
|
158
211
|
|
212
|
+
# Return the stroke for a grid row between any borders
|
159
213
|
def content_line_stroke(canvas_coords, row_within_cell)
|
160
214
|
line_stroke(
|
161
215
|
settings.border.style.left_vert,
|
@@ -163,16 +217,12 @@ module WhirledPeas
|
|
163
217
|
settings.border.style.right_vert,
|
164
218
|
) do
|
165
219
|
if settings.scrollbar.vert?
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
-children.first.settings.position.top,
|
173
|
-
row_within_cell
|
174
|
-
)
|
175
|
-
end
|
220
|
+
scrollbar_char = ScrollbarHelper.vert_char(
|
221
|
+
dimensions.children_height + dimensions.padding_height,
|
222
|
+
canvas_coords.inner_grid_height,
|
223
|
+
canvas_coords.top - canvas_coords.offset_content_top,
|
224
|
+
row_within_cell
|
225
|
+
)
|
176
226
|
PADDING * canvas_coords.inner_grid_width + scrollbar_char
|
177
227
|
else
|
178
228
|
PADDING * canvas_coords.inner_grid_width
|
@@ -180,6 +230,7 @@ module WhirledPeas
|
|
180
230
|
end
|
181
231
|
end
|
182
232
|
|
233
|
+
# Return the stroke for the horizontal scroll bar
|
183
234
|
def bottom_scroll_stroke(canvas_coords)
|
184
235
|
line_stroke(
|
185
236
|
settings.border.style.left_vert,
|
@@ -187,85 +238,15 @@ module WhirledPeas
|
|
187
238
|
settings.border.style.right_vert,
|
188
239
|
) do
|
189
240
|
canvas_coords.inner_grid_width.times.map do |col_within_cell|
|
190
|
-
|
241
|
+
ScrollbarHelper.horiz_char(
|
191
242
|
dimensions.children_width + dimensions.padding_width,
|
192
243
|
canvas_coords.inner_grid_width,
|
193
|
-
-
|
244
|
+
canvas_coords.left - canvas_coords.offset_content_left,
|
194
245
|
col_within_cell
|
195
246
|
)
|
196
247
|
end.join
|
197
248
|
end
|
198
249
|
end
|
199
|
-
|
200
|
-
GUTTER = ' '
|
201
|
-
HORIZONTAL = %w[▗ ▄ ▖]
|
202
|
-
VERTICAL = %w[
|
203
|
-
▗
|
204
|
-
▐
|
205
|
-
▝
|
206
|
-
]
|
207
|
-
|
208
|
-
def horiz_scroll_char(col_count, viewable_col_count, first_visible_col, curr_col)
|
209
|
-
scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
|
210
|
-
end
|
211
|
-
|
212
|
-
def vert_scroll_char(row_count, viewable_row_count, first_visible_row, curr_row)
|
213
|
-
scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
|
214
|
-
end
|
215
|
-
|
216
|
-
private
|
217
|
-
|
218
|
-
def scroll_char(total_count, viewable_count, first_visible, curr, chars)
|
219
|
-
return GUTTER unless total_count > 0 && viewable_count > 0
|
220
|
-
# The scroll handle has the exact same relative size and position in the scroll gutter
|
221
|
-
# that the viewable content has in the total content area. For example, a content area
|
222
|
-
# that is 50 columns wide with a view port that is 20 columns wide might look like
|
223
|
-
#
|
224
|
-
# +---------1-----****2*********3******---4---------+
|
225
|
-
# | * * |
|
226
|
-
# | hidden * viewable * hidden |
|
227
|
-
# | * * |
|
228
|
-
# +---------1-----****2*********3******---4---------+
|
229
|
-
#
|
230
|
-
# The scoll gutter, would look like
|
231
|
-
#
|
232
|
-
# |......********.....|
|
233
|
-
#
|
234
|
-
# Scrolling all the way to the right results in
|
235
|
-
#
|
236
|
-
# +---------1---------2---------3*********4*********+
|
237
|
-
# | * *
|
238
|
-
# | hidden * viewable *
|
239
|
-
# | * *
|
240
|
-
# +---------1---------2---------3*********4*********+
|
241
|
-
# |...........********|
|
242
|
-
|
243
|
-
# The first task of determining how much of the handle is visible in a row/column is to
|
244
|
-
# calculate the range (as a precentage of the total) of viewable items
|
245
|
-
viewable_start = first_visible.to_f / total_count
|
246
|
-
viewable_end = (first_visible + viewable_count).to_f / total_count
|
247
|
-
|
248
|
-
# Always use the same length for the scroll bar so it does not give an inchworm effect
|
249
|
-
# as it scrolls along.
|
250
|
-
#
|
251
|
-
# Also, double the value now to get granularity for half width
|
252
|
-
# scrollbar characters.
|
253
|
-
scrollbar_length = ((2 * viewable_count ** 2).to_f / total_count).ceil
|
254
|
-
scrollbar_start = ((2 * first_visible * viewable_count).to_f / total_count).floor
|
255
|
-
|
256
|
-
first_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr)
|
257
|
-
second_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr + 1)
|
258
|
-
|
259
|
-
if first_half && second_half
|
260
|
-
chars[1]
|
261
|
-
elsif first_half
|
262
|
-
chars[2]
|
263
|
-
elsif second_half
|
264
|
-
chars[0]
|
265
|
-
else
|
266
|
-
GUTTER
|
267
|
-
end
|
268
|
-
end
|
269
250
|
end
|
270
251
|
private_constant :ContainerPainter
|
271
252
|
end
|