whirled_peas 0.7.0 → 0.10.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 +467 -120
- data/Rakefile +1 -20
- data/bin/easing +33 -0
- data/bin/reset_cursor +11 -0
- data/bin/screen_test +68 -0
- data/examples/graph.rb +54 -0
- data/examples/intro.rb +3 -3
- data/examples/scrolling.rb +5 -4
- data/lib/data/themes.yaml +13 -0
- data/lib/whirled_peas.rb +12 -6
- 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 +43 -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 +111 -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 +76 -0
- data/lib/whirled_peas/command_line.rb +24 -212
- 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/composer.rb +23 -2
- data/lib/whirled_peas/graphics/container_painter.rb +91 -0
- 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/graph_dimensions.rb +12 -0
- data/lib/whirled_peas/graphics/graph_painter.rb +97 -0
- data/lib/whirled_peas/graphics/grid_painter.rb +1 -4
- data/lib/whirled_peas/graphics/painter.rb +10 -0
- data/lib/whirled_peas/graphics/renderer.rb +8 -2
- data/lib/whirled_peas/graphics/text_painter.rb +7 -11
- data/lib/whirled_peas/settings/border.rb +25 -4
- data/lib/whirled_peas/settings/container_settings.rb +2 -4
- 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/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/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/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 +2 -2
- data/screen_test/settings/flow/grid_l2r.frame +1 -1
- data/screen_test/settings/flow/grid_l2r.rb +2 -2
- data/screen_test/settings/flow/grid_r2l.frame +1 -1
- data/screen_test/settings/flow/grid_r2l.rb +2 -2
- data/screen_test/settings/flow/grid_t2b.frame +1 -1
- data/screen_test/settings/flow/grid_t2b.rb +2 -2
- 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/position/box_left.frame +1 -1
- data/screen_test/settings/position/box_left.rb +1 -1
- data/screen_test/settings/position/box_left_negative.frame +1 -1
- data/screen_test/settings/position/box_left_negative.rb +1 -1
- data/screen_test/settings/position/box_top.frame +1 -1
- data/screen_test/settings/position/box_top.rb +1 -1
- data/screen_test/settings/position/box_top_negative.frame +1 -1
- data/screen_test/settings/position/box_top_negative.rb +1 -1
- data/screen_test/settings/position/grid_left.frame +1 -1
- data/screen_test/settings/position/grid_left.rb +1 -1
- data/screen_test/settings/position/grid_left_negative.frame +1 -1
- data/screen_test/settings/position/grid_left_negative.rb +1 -1
- data/screen_test/settings/position/grid_top.frame +1 -1
- data/screen_test/settings/position/grid_top.rb +1 -1
- data/screen_test/settings/position/grid_top_negative.frame +1 -1
- data/screen_test/settings/position/grid_top_negative.rb +1 -1
- data/screen_test/settings/scroll/horiz_box.frame +1 -1
- data/screen_test/settings/scroll/horiz_box.rb +1 -1
- data/screen_test/settings/scroll/horiz_box_align_center.rb +1 -1
- data/screen_test/settings/scroll/horiz_box_align_right.rb +1 -1
- data/screen_test/settings/scroll/vert_box.frame +1 -1
- data/screen_test/settings/scroll/vert_box.rb +1 -1
- 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 +42 -9
- 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
@@ -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
|
@@ -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,25 @@ 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
|
+
|
55
74
|
def add_box(name=self.class.next_name, &block)
|
56
75
|
child_settings = Settings::BoxSettings.inherit(painter.settings)
|
57
76
|
child = BoxPainter.new(name, child_settings)
|
58
77
|
composer = self.class.new(child)
|
59
78
|
value = yield composer, child.settings
|
79
|
+
child_settings.validate!
|
60
80
|
painter.add_child(child)
|
61
81
|
if !child.children? && self.class.stringable?(value)
|
62
82
|
composer.add_text("#{name}-Text") { value.to_s }
|
@@ -68,6 +88,7 @@ module WhirledPeas
|
|
68
88
|
child = GridPainter.new(name, child_settings)
|
69
89
|
composer = self.class.new(child)
|
70
90
|
values = yield composer, child.settings
|
91
|
+
child_settings.validate!
|
71
92
|
painter.add_child(child)
|
72
93
|
if !child.children? && values.is_a?(Array)
|
73
94
|
values.each.with_index do |value, index|
|
@@ -5,6 +5,8 @@ require_relative 'painter'
|
|
5
5
|
|
6
6
|
module WhirledPeas
|
7
7
|
module Graphics
|
8
|
+
# Abstract Painter for containers. Containers (as the name implies) contain other child
|
9
|
+
# elements and must delegate painting of the children to the children themselves.
|
8
10
|
class ContainerPainter < Painter
|
9
11
|
PADDING = ' '
|
10
12
|
|
@@ -13,38 +15,65 @@ module WhirledPeas
|
|
13
15
|
@children = []
|
14
16
|
end
|
15
17
|
|
18
|
+
# Paint the common attributes of containers (e.g. border and background color). Any
|
19
|
+
# class that inherits from this one should call `super` at the start of its #paint
|
20
|
+
# method, before painting its children.
|
16
21
|
def paint(canvas, left, top, &block)
|
17
22
|
return unless canvas.writable?
|
18
23
|
return unless needs_printing?
|
19
24
|
canvas_coords = coords(left, top)
|
25
|
+
|
26
|
+
# Paint the border, background color, and scrollbar starting from the top left
|
27
|
+
# border position, moving down row by row until we reach the bottom border
|
28
|
+
# position
|
20
29
|
stroke_left = canvas_coords.border_left
|
21
30
|
stroke_top = canvas_coords.border_top
|
31
|
+
|
32
|
+
# All strokes will have the same formatting options
|
22
33
|
formatting = [*settings.border.color, *settings.bg_color]
|
34
|
+
|
35
|
+
# Paint the top border if the settings call for it
|
23
36
|
if settings.border.top?
|
24
37
|
canvas.stroke(stroke_left, stroke_top, top_border_stroke(canvas_coords), formatting, &block)
|
25
38
|
stroke_top += 1
|
26
39
|
end
|
40
|
+
# Precalculate the middle border container grids with more than 1 row
|
27
41
|
middle_border = dimensions.num_rows > 1 ? middle_border_stroke(canvas_coords) : ''
|
42
|
+
|
43
|
+
# Paint each grid row by row
|
28
44
|
dimensions.num_rows.times do |row_num|
|
45
|
+
# In a grid with N rows, we will need to paint N - 1 inner horizontal borders.
|
46
|
+
# This code treats the inner horizontal border as the top of each row except for
|
47
|
+
# the first one.
|
29
48
|
if row_num > 0 && settings.border.inner_horiz?
|
30
49
|
canvas.stroke(stroke_left, stroke_top, middle_border, formatting, &block)
|
31
50
|
stroke_top += 1
|
32
51
|
end
|
52
|
+
|
53
|
+
# Paint the interior of each row (horizontal borders, veritical scroll bar and
|
54
|
+
# background color for the padding and content area)
|
33
55
|
canvas_coords.inner_grid_height.times do |row_within_cell|
|
34
56
|
canvas.stroke(stroke_left, stroke_top, content_line_stroke(canvas_coords, row_within_cell), formatting, &block)
|
35
57
|
stroke_top += 1
|
36
58
|
end
|
59
|
+
|
60
|
+
# Paint the horizontal scroll bar is the settings call for it
|
37
61
|
if settings.scrollbar.horiz?
|
38
62
|
canvas.stroke(stroke_left, stroke_top, bottom_scroll_stroke(canvas_coords), formatting, &block)
|
39
63
|
stroke_top += 1
|
40
64
|
end
|
41
65
|
end
|
66
|
+
|
67
|
+
# Paint the bottom border if the settings call for it
|
42
68
|
if settings.border.bottom?
|
43
69
|
canvas.stroke(stroke_left, stroke_top, bottom_border_stroke(canvas_coords), formatting, &block)
|
44
70
|
stroke_top += 1
|
45
71
|
end
|
46
72
|
end
|
47
73
|
|
74
|
+
# Tightly manage access to the children (rather than simply exposing the underlying
|
75
|
+
# array). This allows subclasses to easily modify behavior based on that element's
|
76
|
+
# specific settings.
|
48
77
|
def add_child(child)
|
49
78
|
children << child
|
50
79
|
end
|
@@ -65,6 +94,8 @@ module WhirledPeas
|
|
65
94
|
|
66
95
|
attr_reader :children
|
67
96
|
|
97
|
+
# Determine if there is anything to print for the container (this does not accont for
|
98
|
+
# children, just the border, scrollbar, and background color)
|
68
99
|
def needs_printing?
|
69
100
|
return true if settings.bg_color
|
70
101
|
return true if settings.border.outer?
|
@@ -73,10 +104,15 @@ module WhirledPeas
|
|
73
104
|
settings.scrollbar.horiz? || settings.scrollbar.vert?
|
74
105
|
end
|
75
106
|
|
107
|
+
# Return an object that allows easy access to important coordinates within the container,
|
108
|
+
# e.g. the left position where the left border is printed
|
76
109
|
def coords(left, top)
|
77
110
|
ContainerCoords.new(dimensions, settings, left, top)
|
78
111
|
end
|
79
112
|
|
113
|
+
# @return [Array<Integer>] a two-item array, the first being the amount of horizontal
|
114
|
+
# spacing to paint *before the first* child and the second being the amount of spacing
|
115
|
+
# to paint *between each* child
|
80
116
|
def horiz_justify_offset(containing_width)
|
81
117
|
if settings.align_center?
|
82
118
|
[(dimensions.content_width - containing_width) / 2, 0]
|
@@ -96,6 +132,9 @@ module WhirledPeas
|
|
96
132
|
end
|
97
133
|
end
|
98
134
|
|
135
|
+
# @return [Array<Integer>] a two-item array, the first being the amount of vertical
|
136
|
+
# spacing to paint *above the first* child and the second being the amount of spacing
|
137
|
+
# to paint *between each* child
|
99
138
|
def vert_justify_offset(containing_height)
|
100
139
|
if settings.valign_middle?
|
101
140
|
[(dimensions.content_height - containing_height) / 2, 0]
|
@@ -115,6 +154,16 @@ module WhirledPeas
|
|
115
154
|
end
|
116
155
|
end
|
117
156
|
|
157
|
+
# Return a stroke for one line of the container
|
158
|
+
#
|
159
|
+
# @param left_border [String] the character to print as the first character if there
|
160
|
+
# is a left border
|
161
|
+
# @param junc_border [String] the character to print as the junction between two grid
|
162
|
+
# columns if there is an inner vertical border
|
163
|
+
# @param right_border [String] the character to print as the last character if there
|
164
|
+
# is a right border
|
165
|
+
# @block [String] the block should yield a string that represents the interior
|
166
|
+
# (including padding) of a grid cell
|
118
167
|
def line_stroke(left_border, junc_border, right_border, &block)
|
119
168
|
stroke = ''
|
120
169
|
stroke += left_border if settings.border.left?
|
@@ -126,6 +175,7 @@ module WhirledPeas
|
|
126
175
|
stroke
|
127
176
|
end
|
128
177
|
|
178
|
+
# Return the stroke for the top border
|
129
179
|
def top_border_stroke(canvas_coords)
|
130
180
|
line_stroke(
|
131
181
|
settings.border.style.top_left,
|
@@ -136,6 +186,7 @@ module WhirledPeas
|
|
136
186
|
end
|
137
187
|
end
|
138
188
|
|
189
|
+
# Return the stroke for an inner horizontal border
|
139
190
|
def middle_border_stroke(canvas_coords)
|
140
191
|
line_stroke(
|
141
192
|
settings.border.style.left_junc,
|
@@ -146,6 +197,7 @@ module WhirledPeas
|
|
146
197
|
end
|
147
198
|
end
|
148
199
|
|
200
|
+
# Return the stroke for the bottom border
|
149
201
|
def bottom_border_stroke(canvas_coords)
|
150
202
|
line_stroke(
|
151
203
|
settings.border.style.bottom_left,
|
@@ -156,6 +208,7 @@ module WhirledPeas
|
|
156
208
|
end
|
157
209
|
end
|
158
210
|
|
211
|
+
# Return the stroke for a grid row between any borders
|
159
212
|
def content_line_stroke(canvas_coords, row_within_cell)
|
160
213
|
line_stroke(
|
161
214
|
settings.border.style.left_vert,
|
@@ -180,6 +233,7 @@ module WhirledPeas
|
|
180
233
|
end
|
181
234
|
end
|
182
235
|
|
236
|
+
# Return the stroke for the horizontal scroll bar
|
183
237
|
def bottom_scroll_stroke(canvas_coords)
|
184
238
|
line_stroke(
|
185
239
|
settings.border.style.left_vert,
|
@@ -197,6 +251,7 @@ module WhirledPeas
|
|
197
251
|
end
|
198
252
|
end
|
199
253
|
|
254
|
+
# Contants to paint scrollbars
|
200
255
|
GUTTER = ' '
|
201
256
|
HORIZONTAL = %w[▗ ▄ ▖]
|
202
257
|
VERTICAL = %w[
|
@@ -205,16 +260,33 @@ module WhirledPeas
|
|
205
260
|
▝
|
206
261
|
]
|
207
262
|
|
263
|
+
# Determine the character to paint the horizontal scroll bar with for the given column
|
264
|
+
#
|
265
|
+
# @see #scroll_char for more details
|
208
266
|
def horiz_scroll_char(col_count, viewable_col_count, first_visible_col, curr_col)
|
209
267
|
scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
|
210
268
|
end
|
211
269
|
|
270
|
+
# Determine the character to paint the vertical scroll bar with for the given row
|
271
|
+
#
|
272
|
+
# @see #scroll_char for more details
|
212
273
|
def vert_scroll_char(row_count, viewable_row_count, first_visible_row, curr_row)
|
213
274
|
scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
|
214
275
|
end
|
215
276
|
|
216
277
|
private
|
217
278
|
|
279
|
+
# Determine which character to paint a for a scrollbar
|
280
|
+
#
|
281
|
+
# @param total_count [Integer] total number of rows/columns in the content
|
282
|
+
# @param viewable_count [Integer] number of rows/columns visible in the viewport
|
283
|
+
# @param first_visible [Integer] zero-based index of the first row/column that is visible
|
284
|
+
# in the viewport
|
285
|
+
# @param curr [Integer] zero-based index of the row/column (relative to the first visible
|
286
|
+
# row/column) that the painted character is being requested for
|
287
|
+
# @param chars [Array<String>] an array with three 1-character strings, the frist is the
|
288
|
+
# "second half" scrollbar character, the second is the "full" scrollbar character, and
|
289
|
+
# the third is the "first half" scrollbar character.
|
218
290
|
def scroll_char(total_count, viewable_count, first_visible, curr, chars)
|
219
291
|
return GUTTER unless total_count > 0 && viewable_count > 0
|
220
292
|
# The scroll handle has the exact same relative size and position in the scroll gutter
|
@@ -239,6 +311,25 @@ module WhirledPeas
|
|
239
311
|
# | * *
|
240
312
|
# +---------1---------2---------3*********4*********+
|
241
313
|
# |...........********|
|
314
|
+
#
|
315
|
+
# Returning to the first example, we can match up the arguments to this method to the
|
316
|
+
# diagram
|
317
|
+
#
|
318
|
+
# total_count = 50
|
319
|
+
# |<----------------------------------------------->|
|
320
|
+
# | |
|
321
|
+
# | veiwable_count = 20 |
|
322
|
+
# | |<----------------->| |
|
323
|
+
# ↓ ↓ ↓ ↓
|
324
|
+
# +---------1-----****2*********3******---4---------+
|
325
|
+
# | * * |
|
326
|
+
# | hidden * viewable * hidden |
|
327
|
+
# | * * |
|
328
|
+
# +---------1-----****2*********3******---4---------+
|
329
|
+
# |......****?***.....|
|
330
|
+
# ↑ ↑
|
331
|
+
# first_visible = 16 |
|
332
|
+
# curr = 11
|
242
333
|
|
243
334
|
# The first task of determining how much of the handle is visible in a row/column is to
|
244
335
|
# calculate the range (as a precentage of the total) of viewable items
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module WhirledPeas
|
2
|
+
module Graphics
|
3
|
+
class ContentDimensions
|
4
|
+
attr_reader :outer_width, :outer_height
|
5
|
+
|
6
|
+
def initialize(settings, content)
|
7
|
+
if settings.width
|
8
|
+
@outer_width = settings.width
|
9
|
+
else
|
10
|
+
@outer_width = 0
|
11
|
+
content.each do |line|
|
12
|
+
@outer_width = line.length if line.length > @outer_width
|
13
|
+
end
|
14
|
+
end
|
15
|
+
@outer_height = settings.height || content.length
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'painter'
|
2
|
+
|
3
|
+
module WhirledPeas
|
4
|
+
module Graphics
|
5
|
+
class ContentPainter < Painter
|
6
|
+
attr_accessor :content
|
7
|
+
|
8
|
+
def dimensions
|
9
|
+
ContentDimensions.new(settings, content_lines)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def content_lines
|
15
|
+
raise NotImplementedError, "#{self.class} must implement #content_lines"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
private_constant :ContentPainter
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'whirled_peas/utils/ansi'
|
2
|
+
|
3
|
+
require_relative 'content_painter'
|
4
|
+
|
5
|
+
module WhirledPeas
|
6
|
+
module Graphics
|
7
|
+
class GraphPainter < ContentPainter
|
8
|
+
def paint(canvas, left, top, &block)
|
9
|
+
axis_formatting = [*settings.axis_color, *settings.bg_color]
|
10
|
+
plot_formatting = [*settings.color, *settings.bg_color]
|
11
|
+
axes_lines.each.with_index do |axis_line, row_index|
|
12
|
+
canvas.stroke(left, top + row_index, axis_line, axis_formatting, &block)
|
13
|
+
next if row_index >= plot_lines.length
|
14
|
+
canvas.stroke(left + 1, top + row_index, plot_lines[row_index], plot_formatting, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def content_lines
|
21
|
+
axes_lines
|
22
|
+
end
|
23
|
+
|
24
|
+
def plot_lines
|
25
|
+
return @plot_lines if @plot_lines
|
26
|
+
min_y = 1.0 / 0
|
27
|
+
max_y = -1.0 / 0
|
28
|
+
if settings.width
|
29
|
+
interpolated = inner_width.times.map do |i|
|
30
|
+
x = (i * (content.length - 1).to_f / inner_width).floor
|
31
|
+
max_y = content[x] if content[x] > max_y
|
32
|
+
min_y = content[x] if content[x] < min_y
|
33
|
+
content[x]
|
34
|
+
end
|
35
|
+
else
|
36
|
+
interpolated = content
|
37
|
+
content.each do |y|
|
38
|
+
max_y = y if y > max_y
|
39
|
+
min_y = y if y < min_y
|
40
|
+
end
|
41
|
+
end
|
42
|
+
scaled = interpolated.map do |y|
|
43
|
+
(2 * inner_height * (y - min_y).to_f / (max_y - min_y)).floor
|
44
|
+
end
|
45
|
+
@plot_lines = Array.new(inner_height) { '' }
|
46
|
+
scaled.each.with_index do |y, x_index|
|
47
|
+
@plot_lines.each.with_index do |row, row_index|
|
48
|
+
bottom_half_index = 2 * (inner_height - row_index - 1)
|
49
|
+
top_half_index = bottom_half_index + 1
|
50
|
+
|
51
|
+
asc, next_y = if scaled.length == 1
|
52
|
+
[true, y]
|
53
|
+
elsif x_index == scaled.length - 1
|
54
|
+
y >= scaled[x_index - 1]
|
55
|
+
[true, y]
|
56
|
+
else
|
57
|
+
scaled[x_index + 1] >= y
|
58
|
+
[true, scaled[x_index + 1]]
|
59
|
+
end
|
60
|
+
if asc
|
61
|
+
top_half = y == top_half_index || (y...next_y).include?(top_half_index)
|
62
|
+
bottom_half = y == bottom_half_index || (y...next_y).include?(bottom_half_index)
|
63
|
+
else
|
64
|
+
top_half = y == top_half_index || (next_y...y).include?(top_half_index)
|
65
|
+
bottom_half = y == bottom_half_index || (next_y...y).include?(bottom_half_index)
|
66
|
+
end
|
67
|
+
row << if top_half && bottom_half
|
68
|
+
'█'
|
69
|
+
elsif top_half
|
70
|
+
'▀'
|
71
|
+
elsif bottom_half
|
72
|
+
'▄'
|
73
|
+
else
|
74
|
+
' '
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@plot_lines
|
79
|
+
end
|
80
|
+
|
81
|
+
def inner_height
|
82
|
+
settings.height - 1
|
83
|
+
end
|
84
|
+
|
85
|
+
def inner_width
|
86
|
+
settings.width.nil? ? content.length : settings.width - 1
|
87
|
+
end
|
88
|
+
|
89
|
+
def axes_lines
|
90
|
+
return @axes_lines if @axes_lines
|
91
|
+
@axes_lines = inner_height.times.map { '┃' }
|
92
|
+
@axes_lines << '┗' + '━' * inner_width
|
93
|
+
@axes_lines
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|