whirled_peas 0.2.0 → 0.6.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 +3 -0
- data/CHANGELOG.md +38 -0
- data/Gemfile +1 -0
- data/README.md +275 -81
- data/Rakefile +38 -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 -42
- 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 -8
- data/lib/whirled_peas/frame/consumer.rb +14 -50
- data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
- data/lib/whirled_peas/frame/event_loop.rb +68 -38
- data/lib/whirled_peas/frame/producer.rb +36 -32
- data/lib/whirled_peas/graphics.rb +5 -0
- data/lib/whirled_peas/graphics/box_painter.rb +108 -0
- data/lib/whirled_peas/graphics/canvas.rb +107 -0
- data/lib/whirled_peas/graphics/composer.rb +80 -0
- data/lib/whirled_peas/graphics/container_coords.rb +71 -0
- data/lib/whirled_peas/graphics/container_dimensions.rb +75 -0
- data/lib/whirled_peas/graphics/container_painter.rb +234 -0
- data/lib/whirled_peas/graphics/debugger.rb +52 -0
- data/lib/whirled_peas/graphics/grid_painter.rb +68 -0
- data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
- data/lib/whirled_peas/graphics/painter.rb +19 -0
- data/lib/whirled_peas/graphics/renderer.rb +21 -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/bg_color.rb +22 -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 +69 -0
- data/lib/whirled_peas/settings/container_settings.rb +139 -0
- data/lib/whirled_peas/settings/debugger.rb +96 -0
- data/lib/whirled_peas/settings/display_flow.rb +25 -0
- data/lib/whirled_peas/settings/element_settings.rb +61 -0
- data/lib/whirled_peas/settings/grid_settings.rb +15 -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/spacing.rb +24 -0
- data/lib/whirled_peas/settings/text_align.rb +19 -0
- data/lib/whirled_peas/settings/text_color.rb +19 -0
- data/lib/whirled_peas/settings/text_settings.rb +15 -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/rendered/elements/box.frame +1 -0
- data/screen_test/rendered/elements/box.rb +20 -0
- data/screen_test/rendered/elements/grid.frame +1 -0
- data/screen_test/rendered/elements/grid.rb +13 -0
- data/screen_test/rendered/elements/screen_overflow.frame +1 -0
- data/screen_test/rendered/elements/screen_overflow.rb +9 -0
- data/screen_test/rendered/elements/text.frame +1 -0
- data/screen_test/rendered/elements/text.rb +9 -0
- data/screen_test/rendered/elements/text_multiline.frame +1 -0
- data/screen_test/rendered/elements/text_multiline.rb +9 -0
- data/screen_test/rendered/settings/align/box.frame +1 -0
- data/screen_test/rendered/settings/align/box.rb +24 -0
- data/screen_test/rendered/settings/align/children_center.frame +1 -0
- data/screen_test/rendered/settings/align/children_center.rb +13 -0
- data/screen_test/rendered/settings/align/children_left.frame +1 -0
- data/screen_test/rendered/settings/align/children_left.rb +13 -0
- data/screen_test/rendered/settings/align/children_right.frame +1 -0
- data/screen_test/rendered/settings/align/children_right.rb +13 -0
- data/screen_test/rendered/settings/align/grid.frame +1 -0
- data/screen_test/rendered/settings/align/grid.rb +20 -0
- data/screen_test/rendered/settings/ansi/bold.frame +1 -0
- data/screen_test/rendered/settings/ansi/bold.rb +15 -0
- data/screen_test/rendered/settings/ansi/color.frame +1 -0
- data/screen_test/rendered/settings/ansi/color.rb +37 -0
- data/screen_test/rendered/settings/ansi/underline.frame +1 -0
- data/screen_test/rendered/settings/ansi/underline.rb +15 -0
- data/screen_test/rendered/settings/border.frame +1 -0
- data/screen_test/rendered/settings/border.rb +13 -0
- data/screen_test/rendered/settings/flow/box_b2t.frame +1 -0
- data/screen_test/rendered/settings/flow/box_b2t.rb +24 -0
- data/screen_test/rendered/settings/flow/box_l2r.frame +1 -0
- data/screen_test/rendered/settings/flow/box_l2r.rb +24 -0
- data/screen_test/rendered/settings/flow/box_r2l.frame +1 -0
- data/screen_test/rendered/settings/flow/box_r2l.rb +24 -0
- data/screen_test/rendered/settings/flow/box_t2b.frame +1 -0
- data/screen_test/rendered/settings/flow/box_t2b.rb +24 -0
- data/screen_test/rendered/settings/flow/grid_b2t.frame +1 -0
- data/screen_test/rendered/settings/flow/grid_b2t.rb +14 -0
- data/screen_test/rendered/settings/flow/grid_l2r.frame +1 -0
- data/screen_test/rendered/settings/flow/grid_l2r.rb +14 -0
- data/screen_test/rendered/settings/flow/grid_r2l.frame +1 -0
- data/screen_test/rendered/settings/flow/grid_r2l.rb +14 -0
- data/screen_test/rendered/settings/flow/grid_t2b.frame +1 -0
- data/screen_test/rendered/settings/flow/grid_t2b.rb +14 -0
- data/screen_test/rendered/settings/height/box.frame +1 -0
- data/screen_test/rendered/settings/height/box.rb +13 -0
- data/screen_test/rendered/settings/height/grid.frame +1 -0
- data/screen_test/rendered/settings/height/grid.rb +14 -0
- data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
- data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
- data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
- data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
- data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
- data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
- data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
- data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
- data/screen_test/rendered/settings/margin.frame +1 -0
- data/screen_test/rendered/settings/margin.rb +14 -0
- data/screen_test/rendered/settings/padding.frame +1 -0
- data/screen_test/rendered/settings/padding.rb +11 -0
- data/screen_test/rendered/settings/position/box_left.frame +1 -0
- data/screen_test/rendered/settings/position/box_left.rb +17 -0
- data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
- data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
- data/screen_test/rendered/settings/position/box_top.frame +1 -0
- data/screen_test/rendered/settings/position/box_top.rb +17 -0
- data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
- data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
- data/screen_test/rendered/settings/position/grid_left.frame +1 -0
- data/screen_test/rendered/settings/position/grid_left.rb +18 -0
- data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
- data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
- data/screen_test/rendered/settings/position/grid_top.frame +1 -0
- data/screen_test/rendered/settings/position/grid_top.rb +18 -0
- data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
- data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
- data/screen_test/rendered/settings/scroll/horiz_box.frame +1 -0
- data/screen_test/rendered/settings/scroll/horiz_box.rb +15 -0
- data/screen_test/rendered/settings/scroll/vert_box.frame +1 -0
- data/screen_test/rendered/settings/scroll/vert_box.rb +18 -0
- data/screen_test/rendered/settings/title_font.frame +1 -0
- data/screen_test/rendered/settings/title_font.rb +12 -0
- data/screen_test/rendered/settings/width/box.frame +1 -0
- data/screen_test/rendered/settings/width/box.rb +13 -0
- data/screen_test/rendered/settings/width/grid.frame +1 -0
- data/screen_test/rendered/settings/width/grid.rb +14 -0
- data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
- data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
- data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
- data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
- data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
- data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
- data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
- data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
- data/screen_test/screen_tester.rb +201 -0
- data/whirled_peas.gemspec +4 -2
- metadata +147 -20
- data/lib/whirled_peas/ui.rb +0 -7
- data/lib/whirled_peas/ui/ansi.rb +0 -154
- data/lib/whirled_peas/ui/canvas.rb +0 -35
- data/lib/whirled_peas/ui/element.rb +0 -225
- data/lib/whirled_peas/ui/painter.rb +0 -283
- data/lib/whirled_peas/ui/screen.rb +0 -62
- data/lib/whirled_peas/ui/settings.rb +0 -521
- data/lib/whirled_peas/ui/stroke.rb +0 -29
- 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,107 @@
|
|
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, :start_left, :start_top
|
8
|
+
|
9
|
+
def self.unwritable
|
10
|
+
new(-1, -1, 0, 0, -1, -1)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(left, top, width, height, start_left, start_top)
|
14
|
+
@left = left
|
15
|
+
@top = top
|
16
|
+
@width = width
|
17
|
+
@height = height
|
18
|
+
@start_left = start_left
|
19
|
+
@start_top = start_top
|
20
|
+
end
|
21
|
+
|
22
|
+
def writable?
|
23
|
+
width > 0 || height > 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def child(start_left, start_top, child_width, child_height)
|
27
|
+
child_left = start_left
|
28
|
+
child_top = start_top
|
29
|
+
if child_left >= left + width
|
30
|
+
self.class.unwritable
|
31
|
+
elsif child_left + child_width <= left
|
32
|
+
self.class.unwritable
|
33
|
+
elsif child_top >= top + height
|
34
|
+
self.class.unwritable
|
35
|
+
elsif child_top + child_height <= top
|
36
|
+
self.class.unwritable
|
37
|
+
else
|
38
|
+
if child_left < left
|
39
|
+
child_width -= left - child_left
|
40
|
+
child_left = left
|
41
|
+
end
|
42
|
+
child_width = [width - (child_left - left), child_width].min
|
43
|
+
if child_top < top
|
44
|
+
child_height -= top - child_top
|
45
|
+
child_top = top
|
46
|
+
end
|
47
|
+
child_height = [height - (child_top - top), child_height].min
|
48
|
+
self.class.new(
|
49
|
+
child_left,
|
50
|
+
child_top,
|
51
|
+
child_width,
|
52
|
+
child_height,
|
53
|
+
start_left,
|
54
|
+
start_top
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Yields a single line of formatted characters positioned on the canvas,
|
60
|
+
# verifying only characters within the canvas are included.
|
61
|
+
def stroke(stroke_left, stroke_top, raw, formatting=[], &block)
|
62
|
+
if stroke_left >= left + width
|
63
|
+
# The stroke starts to the right of the canvas
|
64
|
+
fstring = Utils::FormattedString.blank
|
65
|
+
elsif stroke_left + raw.length <= left
|
66
|
+
# The stroke ends to the left of the canvas
|
67
|
+
fstring = Utils::FormattedString.blank
|
68
|
+
elsif stroke_top < top
|
69
|
+
# The stroke is above the canvas
|
70
|
+
fstring = Utils::FormattedString.blank
|
71
|
+
elsif stroke_top >= top + height
|
72
|
+
# The stroke is below the canvas
|
73
|
+
fstring = Utils::FormattedString.blank
|
74
|
+
else
|
75
|
+
# In this section, we know that at least part of the stroke should be visible
|
76
|
+
# on the canvas. Chop off parts of the raw string that aren't within the
|
77
|
+
# canvas boundary and ensure the stroke start position is also within the
|
78
|
+
# canvas boundary
|
79
|
+
|
80
|
+
# If the stroke starts to the left of the canvas, set the start index to the
|
81
|
+
# first value that will be on the canvas, then update stroke_left to be on
|
82
|
+
# the canvas
|
83
|
+
start_index = stroke_left < left ? left - stroke_left : 0
|
84
|
+
stroke_left = left if stroke_left <= left
|
85
|
+
|
86
|
+
# Determine how many characters from the stroke will fit on the canvas
|
87
|
+
visible_length = [raw.length, width - (stroke_left - left)].min
|
88
|
+
end_index = start_index + visible_length - 1
|
89
|
+
fstring = Utils::FormattedString.new(raw[start_index..end_index], formatting)
|
90
|
+
end
|
91
|
+
yield stroke_left, stroke_top, fstring
|
92
|
+
end
|
93
|
+
|
94
|
+
def hash
|
95
|
+
[left, top, width, height].hash
|
96
|
+
end
|
97
|
+
|
98
|
+
def ==(other)
|
99
|
+
other.is_a?(self.class) && hash == other.hash
|
100
|
+
end
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
"Canvas(left=#{left}, top=#{top}, width=#{width}, height=#{height})"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'whirled_peas/settings/box_settings'
|
2
|
+
require 'whirled_peas/settings/grid_settings'
|
3
|
+
require 'whirled_peas/settings/text_settings'
|
4
|
+
|
5
|
+
require_relative 'box_painter'
|
6
|
+
require_relative 'grid_painter'
|
7
|
+
require_relative 'text_painter'
|
8
|
+
|
9
|
+
module WhirledPeas
|
10
|
+
module Graphics
|
11
|
+
class Composer
|
12
|
+
# List of classes that convert nicely to a string
|
13
|
+
STRINGALBE_CLASSES = [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass]
|
14
|
+
private_constant :STRINGALBE_CLASSES
|
15
|
+
|
16
|
+
def self.stringable?(value)
|
17
|
+
STRINGALBE_CLASSES.include?(value.class)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.next_name
|
21
|
+
@counter ||= 0
|
22
|
+
@counter += 1
|
23
|
+
"Element-#{@counter}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.build
|
27
|
+
settings = Settings::BoxSettings.new
|
28
|
+
template = BoxPainter.new('TEMPLATE', settings)
|
29
|
+
composer = Composer.new(template)
|
30
|
+
value = yield composer, settings
|
31
|
+
if !template.children? && stringable?(value)
|
32
|
+
composer.add_text { value.to_s }
|
33
|
+
end
|
34
|
+
template
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :painter
|
38
|
+
|
39
|
+
def initialize(painter)
|
40
|
+
@painter = painter
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_text(name=self.class.next_name, &block)
|
44
|
+
child_settings = Settings::TextSettings.inherit(painter.settings)
|
45
|
+
child = TextPainter.new(name, child_settings)
|
46
|
+
# TextPainters are not composable, so yield nil
|
47
|
+
content = yield nil, child_settings
|
48
|
+
unless self.class.stringable?(content)
|
49
|
+
raise ArgumentError, "Unsupported type for text: #{content.class}"
|
50
|
+
end
|
51
|
+
child.content = content.to_s
|
52
|
+
painter.add_child(child)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_box(name=self.class.next_name, &block)
|
56
|
+
child_settings = Settings::BoxSettings.inherit(painter.settings)
|
57
|
+
child = BoxPainter.new(name, child_settings)
|
58
|
+
composer = self.class.new(child)
|
59
|
+
value = yield composer, child.settings
|
60
|
+
painter.add_child(child)
|
61
|
+
if !child.children? && self.class.stringable?(value)
|
62
|
+
composer.add_text("#{name}-Text") { value.to_s }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_grid(name=self.class.next_name, &block)
|
67
|
+
child_settings = Settings::GridSettings.inherit(painter.settings)
|
68
|
+
child = GridPainter.new(name, child_settings)
|
69
|
+
composer = self.class.new(child)
|
70
|
+
values = yield composer, child.settings
|
71
|
+
painter.add_child(child)
|
72
|
+
if !child.children? && values.is_a?(Array)
|
73
|
+
values.each.with_index do |value, index|
|
74
|
+
composer.add_text("#{name}-Text-#{index}") { value.to_s }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module WhirledPeas
|
2
|
+
module Graphics
|
3
|
+
class ContainerCoords
|
4
|
+
def initialize(canvas, dimensions, settings)
|
5
|
+
@canvas = canvas
|
6
|
+
@dimensions = dimensions
|
7
|
+
@settings = settings
|
8
|
+
end
|
9
|
+
|
10
|
+
def left
|
11
|
+
canvas.start_left + settings.position.left
|
12
|
+
end
|
13
|
+
|
14
|
+
def top
|
15
|
+
canvas.start_top + settings.position.top
|
16
|
+
end
|
17
|
+
|
18
|
+
def border_left
|
19
|
+
left + settings.margin.left
|
20
|
+
end
|
21
|
+
|
22
|
+
def border_top
|
23
|
+
top + settings.margin.top
|
24
|
+
end
|
25
|
+
|
26
|
+
def padding_left
|
27
|
+
border_left + (settings.border.left? ? 1 : 0)
|
28
|
+
end
|
29
|
+
|
30
|
+
def padding_top
|
31
|
+
border_top + (settings.border.top? ? 1 : 0)
|
32
|
+
end
|
33
|
+
|
34
|
+
def content_left(col_index=0)
|
35
|
+
padding_left + settings.padding.left + col_index * grid_width
|
36
|
+
end
|
37
|
+
|
38
|
+
def content_top(row_index=0)
|
39
|
+
padding_top + settings.padding.top + row_index * grid_height
|
40
|
+
end
|
41
|
+
|
42
|
+
def inner_grid_width
|
43
|
+
settings.padding.left +
|
44
|
+
dimensions.content_width +
|
45
|
+
settings.padding.right
|
46
|
+
end
|
47
|
+
|
48
|
+
def grid_width
|
49
|
+
(settings.border.inner_vert? ? 1 : 0) +
|
50
|
+
inner_grid_width +
|
51
|
+
(settings.scrollbar.vert? ? 1 : 0)
|
52
|
+
end
|
53
|
+
|
54
|
+
def inner_grid_height
|
55
|
+
settings.padding.top +
|
56
|
+
dimensions.content_height +
|
57
|
+
settings.padding.bottom
|
58
|
+
end
|
59
|
+
|
60
|
+
def grid_height
|
61
|
+
(settings.border.inner_horiz? ? 1 : 0) +
|
62
|
+
inner_grid_height +
|
63
|
+
(settings.scrollbar.horiz? ? 1 : 0)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
attr_reader :canvas, :settings, :dimensions
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module WhirledPeas
|
2
|
+
module Graphics
|
3
|
+
class ContainerDimensions
|
4
|
+
attr_reader :content_width, :content_height, :children_width, :children_height, :num_cols, :num_rows
|
5
|
+
|
6
|
+
def initialize(settings, content_width, content_height, num_cols=1, num_rows=1)
|
7
|
+
@content_width = settings.width || content_width
|
8
|
+
@content_height = settings.height || content_height
|
9
|
+
@children_width = content_width
|
10
|
+
@children_height = content_height
|
11
|
+
@num_cols = num_cols
|
12
|
+
@num_rows = num_rows
|
13
|
+
@settings = settings
|
14
|
+
end
|
15
|
+
|
16
|
+
def outer_width
|
17
|
+
@outer_width ||= margin_width +
|
18
|
+
outer_border_width +
|
19
|
+
num_cols * (padding_width + content_width + vert_scroll_width) +
|
20
|
+
(num_cols - 1) * inner_border_width
|
21
|
+
end
|
22
|
+
|
23
|
+
def outer_height
|
24
|
+
@outer_height ||= margin_height +
|
25
|
+
outer_border_height +
|
26
|
+
num_rows * (padding_height + content_height + horiz_scroll_height) +
|
27
|
+
(num_rows - 1) * inner_border_height
|
28
|
+
end
|
29
|
+
|
30
|
+
def margin_width
|
31
|
+
settings.margin.left + settings.margin.right
|
32
|
+
end
|
33
|
+
|
34
|
+
def margin_height
|
35
|
+
settings.margin.top + settings.margin.bottom
|
36
|
+
end
|
37
|
+
|
38
|
+
def outer_border_width
|
39
|
+
(settings.border.left? ? 1 : 0) + (settings.border.right? ? 1 : 0)
|
40
|
+
end
|
41
|
+
|
42
|
+
def outer_border_height
|
43
|
+
(settings.border.top? ? 1 : 0) + (settings.border.bottom? ? 1 : 0)
|
44
|
+
end
|
45
|
+
|
46
|
+
def inner_border_width
|
47
|
+
settings.border.inner_vert? ? 1 : 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def inner_border_height
|
51
|
+
settings.border.inner_horiz? ? 1 : 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def padding_width
|
55
|
+
settings.padding.left + settings.padding.right
|
56
|
+
end
|
57
|
+
|
58
|
+
def padding_height
|
59
|
+
settings.padding.top + settings.padding.bottom
|
60
|
+
end
|
61
|
+
|
62
|
+
def vert_scroll_width
|
63
|
+
settings.scrollbar.vert? ? 1 : 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def horiz_scroll_height
|
67
|
+
settings.scrollbar.horiz? ? 1 : 0
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
attr_reader :settings
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'whirled_peas/utils/formatted_string'
|
2
|
+
|
3
|
+
require_relative 'container_coords'
|
4
|
+
require_relative 'painter'
|
5
|
+
|
6
|
+
module WhirledPeas
|
7
|
+
module Graphics
|
8
|
+
class ContainerPainter < Painter
|
9
|
+
PADDING = ' '
|
10
|
+
|
11
|
+
def initialize(name, settings)
|
12
|
+
super
|
13
|
+
@children = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def paint(canvas, &block)
|
17
|
+
return unless canvas.writable?
|
18
|
+
return unless needs_printing?
|
19
|
+
canvas_coords = coords(canvas)
|
20
|
+
stroke_left = canvas_coords.border_left
|
21
|
+
stroke_top = canvas_coords.border_top
|
22
|
+
formatting = [*settings.border.color, *settings.bg_color]
|
23
|
+
if settings.border.top?
|
24
|
+
canvas.stroke(stroke_left, stroke_top, top_border_stroke(canvas_coords), formatting, &block)
|
25
|
+
stroke_top += 1
|
26
|
+
end
|
27
|
+
middle_border = dimensions.num_rows > 1 ? middle_border_stroke(canvas_coords) : ''
|
28
|
+
dimensions.num_rows.times do |row_num|
|
29
|
+
if row_num > 0 && settings.border.inner_horiz?
|
30
|
+
canvas.stroke(stroke_left, stroke_top, middle_border, formatting, &block)
|
31
|
+
stroke_top += 1
|
32
|
+
end
|
33
|
+
canvas_coords.inner_grid_height.times do |row_within_cell|
|
34
|
+
canvas.stroke(stroke_left, stroke_top, content_line_stroke(canvas_coords, row_within_cell), formatting, &block)
|
35
|
+
stroke_top += 1
|
36
|
+
end
|
37
|
+
if settings.scrollbar.horiz?
|
38
|
+
canvas.stroke(stroke_left, stroke_top, bottom_scroll_stroke(canvas_coords), formatting, &block)
|
39
|
+
stroke_top += 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
if settings.border.bottom?
|
43
|
+
canvas.stroke(stroke_left, stroke_top, bottom_border_stroke(canvas_coords), formatting, &block)
|
44
|
+
stroke_top += 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_child(child)
|
49
|
+
children << child
|
50
|
+
end
|
51
|
+
|
52
|
+
def num_children
|
53
|
+
children.length
|
54
|
+
end
|
55
|
+
|
56
|
+
def children?
|
57
|
+
num_children > 0
|
58
|
+
end
|
59
|
+
|
60
|
+
def each_child(&block)
|
61
|
+
children.each(&block)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :children
|
67
|
+
|
68
|
+
def needs_printing?
|
69
|
+
return true if settings.bg_color
|
70
|
+
return true if settings.border.outer?
|
71
|
+
return true if dimensions.num_cols > 1 && settings.border.inner_vert?
|
72
|
+
return true if dimensions.num_rows > 1 && settings.border.inner_horiz?
|
73
|
+
settings.scrollbar.horiz? || settings.scrollbar.vert?
|
74
|
+
end
|
75
|
+
|
76
|
+
def coords(canvas)
|
77
|
+
ContainerCoords.new(canvas, dimensions, settings)
|
78
|
+
end
|
79
|
+
|
80
|
+
def line_stroke(left_border, junc_border, right_border, &block)
|
81
|
+
stroke = ''
|
82
|
+
stroke += left_border if settings.border.left?
|
83
|
+
dimensions.num_cols.times do |col_num|
|
84
|
+
stroke += junc_border if col_num > 0 && settings.border.inner_vert?
|
85
|
+
stroke += yield
|
86
|
+
end
|
87
|
+
stroke += right_border if settings.border.right?
|
88
|
+
stroke
|
89
|
+
end
|
90
|
+
|
91
|
+
def top_border_stroke(canvas_coords)
|
92
|
+
line_stroke(
|
93
|
+
settings.border.style.top_left,
|
94
|
+
settings.border.style.top_junc,
|
95
|
+
settings.border.style.top_right
|
96
|
+
) do
|
97
|
+
settings.border.style.top_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def middle_border_stroke(canvas_coords)
|
102
|
+
line_stroke(
|
103
|
+
settings.border.style.left_junc,
|
104
|
+
settings.border.style.cross_junc,
|
105
|
+
settings.border.style.right_junc
|
106
|
+
) do
|
107
|
+
settings.border.style.middle_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def bottom_border_stroke(canvas_coords)
|
112
|
+
line_stroke(
|
113
|
+
settings.border.style.bottom_left,
|
114
|
+
settings.border.style.bottom_junc,
|
115
|
+
settings.border.style.bottom_right
|
116
|
+
) do
|
117
|
+
settings.border.style.bottom_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def content_line_stroke(canvas_coords, row_within_cell)
|
122
|
+
line_stroke(
|
123
|
+
settings.border.style.left_vert,
|
124
|
+
settings.border.style.middle_vert,
|
125
|
+
settings.border.style.right_vert,
|
126
|
+
) do
|
127
|
+
if settings.scrollbar.vert?
|
128
|
+
if dimensions.children_height <= canvas_coords.grid_height || children.first.settings.position.top > 0
|
129
|
+
scrollbar_char = GUTTER
|
130
|
+
else
|
131
|
+
scrollbar_char = vert_scroll_char(
|
132
|
+
dimensions.children_height + dimensions.padding_height,
|
133
|
+
canvas_coords.inner_grid_height,
|
134
|
+
-children.first.settings.position.top,
|
135
|
+
row_within_cell
|
136
|
+
)
|
137
|
+
end
|
138
|
+
PADDING * canvas_coords.inner_grid_width + scrollbar_char
|
139
|
+
else
|
140
|
+
PADDING * canvas_coords.inner_grid_width
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def bottom_scroll_stroke(canvas_coords)
|
146
|
+
line_stroke(
|
147
|
+
settings.border.style.left_vert,
|
148
|
+
settings.border.style.middle_vert,
|
149
|
+
settings.border.style.right_vert,
|
150
|
+
) do
|
151
|
+
canvas_coords.inner_grid_width.times.map do |col_within_cell|
|
152
|
+
horiz_scroll_char(
|
153
|
+
dimensions.children_width + dimensions.padding_width,
|
154
|
+
canvas_coords.inner_grid_width,
|
155
|
+
-children.first.settings.position.left,
|
156
|
+
col_within_cell
|
157
|
+
)
|
158
|
+
end.join
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
GUTTER = ' '
|
163
|
+
HORIZONTAL = %w[▗ ▄ ▖]
|
164
|
+
VERTICAL = %w[
|
165
|
+
▗
|
166
|
+
▐
|
167
|
+
▝
|
168
|
+
]
|
169
|
+
|
170
|
+
def horiz_scroll_char(col_count, viewable_col_count, first_visible_col, curr_col)
|
171
|
+
scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
|
172
|
+
end
|
173
|
+
|
174
|
+
def vert_scroll_char(row_count, viewable_row_count, first_visible_row, curr_row)
|
175
|
+
scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def scroll_char(total_count, viewable_count, first_visible, curr, chars)
|
181
|
+
return GUTTER unless total_count > 0 && viewable_count > 0
|
182
|
+
# The scroll handle has the exact same relative size and position in the scroll gutter
|
183
|
+
# that the viewable content has in the total content area. For example, a content area
|
184
|
+
# that is 50 columns wide with a view port that is 20 columns wide might look like
|
185
|
+
#
|
186
|
+
# +---------1-----****2*********3******---4---------+
|
187
|
+
# | * * |
|
188
|
+
# | hidden * viewable * hidden |
|
189
|
+
# | * * |
|
190
|
+
# +---------1-----****2*********3******---4---------+
|
191
|
+
#
|
192
|
+
# The scoll gutter, would look like
|
193
|
+
#
|
194
|
+
# |......********.....|
|
195
|
+
#
|
196
|
+
# Scrolling all the way to the right results in
|
197
|
+
#
|
198
|
+
# +---------1---------2---------3*********4*********+
|
199
|
+
# | * *
|
200
|
+
# | hidden * viewable *
|
201
|
+
# | * *
|
202
|
+
# +---------1---------2---------3*********4*********+
|
203
|
+
# |...........********|
|
204
|
+
|
205
|
+
# The first task of determining how much of the handle is visible in a row/column is to
|
206
|
+
# calculate the range (as a precentage of the total) of viewable items
|
207
|
+
viewable_start = first_visible.to_f / total_count
|
208
|
+
viewable_end = (first_visible + viewable_count).to_f / total_count
|
209
|
+
|
210
|
+
# Always use the same length for the scroll bar so it does not give an inchworm effect
|
211
|
+
# as it scrolls along.
|
212
|
+
#
|
213
|
+
# Also, double the value now to get granularity for half width
|
214
|
+
# scrollbar characters.
|
215
|
+
scrollbar_length = ((2 * viewable_count ** 2).to_f / total_count).ceil
|
216
|
+
scrollbar_start = ((2 * first_visible * viewable_count).to_f / total_count).floor
|
217
|
+
|
218
|
+
first_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr)
|
219
|
+
second_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr + 1)
|
220
|
+
|
221
|
+
if first_half && second_half
|
222
|
+
chars[1]
|
223
|
+
elsif first_half
|
224
|
+
chars[2]
|
225
|
+
elsif second_half
|
226
|
+
chars[0]
|
227
|
+
else
|
228
|
+
GUTTER
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
private_constant :ContainerPainter
|
233
|
+
end
|
234
|
+
end
|