whirled_peas 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +111 -73
  4. data/Rakefile +37 -3
  5. data/bin/debug +35 -0
  6. data/lib/whirled_peas.rb +9 -7
  7. data/lib/whirled_peas/command_line.rb +25 -32
  8. data/lib/whirled_peas/debugger.rb +80 -0
  9. data/lib/whirled_peas/errors.rb +2 -0
  10. data/lib/whirled_peas/frame.rb +0 -1
  11. data/lib/whirled_peas/frame/consumer.rb +30 -0
  12. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  13. data/lib/whirled_peas/frame/event_loop.rb +29 -30
  14. data/lib/whirled_peas/frame/producer.rb +11 -5
  15. data/lib/whirled_peas/graphics.rb +5 -0
  16. data/lib/whirled_peas/graphics/box_painter.rb +100 -0
  17. data/lib/whirled_peas/graphics/canvas.rb +107 -0
  18. data/lib/whirled_peas/graphics/container_coords.rb +61 -0
  19. data/lib/whirled_peas/graphics/container_dimensions.rb +65 -0
  20. data/lib/whirled_peas/graphics/container_painter.rb +116 -0
  21. data/lib/whirled_peas/graphics/debugger.rb +43 -0
  22. data/lib/whirled_peas/graphics/grid_painter.rb +56 -0
  23. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  24. data/lib/whirled_peas/graphics/painter.rb +24 -0
  25. data/lib/whirled_peas/graphics/renderer.rb +49 -0
  26. data/lib/whirled_peas/graphics/screen.rb +65 -0
  27. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  28. data/lib/whirled_peas/graphics/text_painter.rb +38 -0
  29. data/lib/whirled_peas/settings.rb +5 -0
  30. data/lib/whirled_peas/settings/bg_color.rb +22 -0
  31. data/lib/whirled_peas/settings/border.rb +101 -0
  32. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  33. data/lib/whirled_peas/{utils → settings}/color.rb +3 -35
  34. data/lib/whirled_peas/settings/container_settings.rb +128 -0
  35. data/lib/whirled_peas/settings/debugger.rb +87 -0
  36. data/lib/whirled_peas/settings/display_flow.rb +25 -0
  37. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  38. data/lib/whirled_peas/settings/grid_settings.rb +11 -0
  39. data/lib/whirled_peas/settings/margin.rb +8 -0
  40. data/lib/whirled_peas/settings/padding.rb +8 -0
  41. data/lib/whirled_peas/settings/position.rb +15 -0
  42. data/lib/whirled_peas/settings/spacing.rb +24 -0
  43. data/lib/whirled_peas/settings/text_align.rb +19 -0
  44. data/lib/whirled_peas/settings/text_color.rb +19 -0
  45. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  46. data/lib/whirled_peas/template/box_element.rb +8 -0
  47. data/lib/whirled_peas/template/composer.rb +68 -0
  48. data/lib/whirled_peas/template/container.rb +28 -0
  49. data/lib/whirled_peas/template/debugger.rb +34 -0
  50. data/lib/whirled_peas/template/element.rb +1 -218
  51. data/lib/whirled_peas/template/grid_element.rb +8 -0
  52. data/lib/whirled_peas/template/text_element.rb +24 -0
  53. data/lib/whirled_peas/utils/ansi.rb +6 -56
  54. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  55. data/lib/whirled_peas/version.rb +1 -1
  56. data/screen_test/rendered/elements/box.frame +1 -0
  57. data/screen_test/rendered/elements/box.rb +20 -0
  58. data/screen_test/rendered/elements/grid.frame +1 -0
  59. data/screen_test/rendered/elements/grid.rb +13 -0
  60. data/screen_test/rendered/elements/screen_overflow.rb +9 -0
  61. data/screen_test/rendered/elements/text.frame +1 -0
  62. data/screen_test/rendered/elements/text.rb +9 -0
  63. data/screen_test/rendered/elements/text_multiline.frame +1 -0
  64. data/screen_test/rendered/elements/text_multiline.rb +9 -0
  65. data/screen_test/rendered/settings/align/box.frame +1 -0
  66. data/screen_test/rendered/settings/align/box.rb +20 -0
  67. data/screen_test/rendered/settings/align/children_center.frame +1 -0
  68. data/screen_test/rendered/settings/align/children_center.rb +13 -0
  69. data/screen_test/rendered/settings/align/children_left.frame +1 -0
  70. data/screen_test/rendered/settings/align/children_left.rb +13 -0
  71. data/screen_test/rendered/settings/align/children_right.frame +1 -0
  72. data/screen_test/rendered/settings/align/children_right.rb +13 -0
  73. data/screen_test/rendered/settings/align/grid.frame +1 -0
  74. data/screen_test/rendered/settings/align/grid.rb +20 -0
  75. data/screen_test/rendered/settings/ansi/bold.frame +1 -0
  76. data/screen_test/rendered/settings/ansi/bold.rb +15 -0
  77. data/screen_test/rendered/settings/ansi/color.frame +1 -0
  78. data/screen_test/rendered/settings/ansi/color.rb +37 -0
  79. data/screen_test/rendered/settings/ansi/underline.frame +1 -0
  80. data/screen_test/rendered/settings/ansi/underline.rb +15 -0
  81. data/screen_test/rendered/settings/border.frame +1 -0
  82. data/screen_test/rendered/settings/border.rb +13 -0
  83. data/screen_test/rendered/settings/flow/l2r.frame +1 -0
  84. data/screen_test/rendered/settings/flow/l2r.rb +24 -0
  85. data/screen_test/rendered/settings/flow/t2b.frame +1 -0
  86. data/screen_test/rendered/settings/flow/t2b.rb +24 -0
  87. data/screen_test/rendered/settings/height/box.frame +1 -0
  88. data/screen_test/rendered/settings/height/box.rb +13 -0
  89. data/screen_test/rendered/settings/height/grid.frame +1 -0
  90. data/screen_test/rendered/settings/height/grid.rb +14 -0
  91. data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
  92. data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
  93. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
  94. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
  95. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
  96. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
  97. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
  98. data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
  99. data/screen_test/rendered/settings/margin.frame +1 -0
  100. data/screen_test/rendered/settings/margin.rb +14 -0
  101. data/screen_test/rendered/settings/padding.frame +1 -0
  102. data/screen_test/rendered/settings/padding.rb +11 -0
  103. data/screen_test/rendered/settings/position/box_left.frame +1 -0
  104. data/screen_test/rendered/settings/position/box_left.rb +17 -0
  105. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
  106. data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
  107. data/screen_test/rendered/settings/position/box_top.frame +1 -0
  108. data/screen_test/rendered/settings/position/box_top.rb +17 -0
  109. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
  110. data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
  111. data/screen_test/rendered/settings/position/grid_left.frame +1 -0
  112. data/screen_test/rendered/settings/position/grid_left.rb +18 -0
  113. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
  114. data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
  115. data/screen_test/rendered/settings/position/grid_top.frame +1 -0
  116. data/screen_test/rendered/settings/position/grid_top.rb +18 -0
  117. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
  118. data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
  119. data/screen_test/rendered/settings/title_font.frame +1 -0
  120. data/screen_test/rendered/settings/title_font.rb +12 -0
  121. data/screen_test/rendered/settings/width/box.frame +1 -0
  122. data/screen_test/rendered/settings/width/box.rb +13 -0
  123. data/screen_test/rendered/settings/width/grid.frame +1 -0
  124. data/screen_test/rendered/settings/width/grid.rb +14 -0
  125. data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
  126. data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
  127. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
  128. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
  129. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
  130. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
  131. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
  132. data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
  133. data/screen_test/screen_tester.rb +191 -0
  134. metadata +122 -10
  135. data/bin/title_fonts +0 -6
  136. data/lib/whirled_peas/frame/print_consumer.rb +0 -33
  137. data/lib/whirled_peas/template/settings.rb +0 -530
  138. data/lib/whirled_peas/ui.rb +0 -5
  139. data/lib/whirled_peas/ui/canvas.rb +0 -68
  140. data/lib/whirled_peas/ui/painter.rb +0 -287
  141. data/lib/whirled_peas/ui/screen.rb +0 -63
@@ -0,0 +1,26 @@
1
+ require_relative 'renderer'
2
+
3
+ module WhirledPeas
4
+ module Graphics
5
+ class MockScreen
6
+ def initialize(width, height)
7
+ @width = width
8
+ @height = height
9
+ end
10
+
11
+ def paint(template)
12
+ Renderer.new(template, width, height).paint { }
13
+ end
14
+
15
+ def refresh
16
+ end
17
+
18
+ def finalize
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :width, :height
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ class Painter
4
+ attr_reader :name, :settings
5
+
6
+ def initialize(element, settings, name)
7
+ @element = element
8
+ @settings = settings
9
+ @name = name
10
+ end
11
+
12
+ def paint(canvas, &block)
13
+ end
14
+
15
+ def dimensions
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :element
21
+ end
22
+ private_constant :Painter
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ require 'whirled_peas/template/box_element'
2
+ require 'whirled_peas/template/container'
3
+ require 'whirled_peas/template/grid_element'
4
+ require 'whirled_peas/template/text_element'
5
+
6
+ require_relative 'box_painter'
7
+ require_relative 'canvas'
8
+ require_relative 'grid_painter'
9
+ require_relative 'text_painter'
10
+
11
+ module WhirledPeas
12
+ module Graphics
13
+ class Renderer
14
+ PAINTERS = {
15
+ Template::BoxElement => BoxPainter,
16
+ Template::GridElement => GridPainter,
17
+ Template::TextElement => TextPainter
18
+ }
19
+ def initialize(template, width, height)
20
+ @template = template
21
+ @width = width
22
+ @height = height
23
+ end
24
+
25
+ def paint(&block)
26
+ painter.paint(Canvas.new(0, 0, width, height, 0, 0), &block)
27
+ end
28
+
29
+ def painter
30
+ build(template)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :template, :width, :height
36
+
37
+ def build(element)
38
+ unless PAINTERS.key?(element.class)
39
+ raise ArgumentError, "Unable to render #{element.class}"
40
+ end
41
+ painter = PAINTERS[element.class].new(element, element.settings, element.name)
42
+ if element.is_a?(Template::Container)
43
+ element.each_child { |child| painter.add_child(build(child)) }
44
+ end
45
+ painter
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,65 @@
1
+ require 'highline'
2
+
3
+ require 'whirled_peas/utils/ansi'
4
+
5
+ require_relative 'renderer'
6
+
7
+ module WhirledPeas
8
+ module Graphics
9
+ class Screen
10
+ def self.current_dimensions
11
+ width, height = HighLine.new.terminal.terminal_size
12
+ [width || 0, height || 0]
13
+ end
14
+
15
+ def initialize(output=STDOUT)
16
+ @output = output
17
+ @terminal = HighLine.new.terminal
18
+ @strokes = []
19
+ refresh_size!
20
+ Signal.trap('SIGWINCH', proc { self.refresh_size! })
21
+ end
22
+
23
+ def paint(template)
24
+ @template = template
25
+ draw
26
+ end
27
+
28
+ def refresh
29
+ # No need to refresh if the screen dimensions have not changed
30
+ return if @refreshed_width == width || @refreshed_height == height
31
+ draw
32
+ end
33
+
34
+ def finalize
35
+ output.print Utils::Ansi.clear
36
+ output.print Utils::Ansi.cursor_pos(top: height - 1)
37
+ output.print Utils::Ansi.cursor_visible(true)
38
+ output.flush
39
+ end
40
+
41
+ protected
42
+
43
+ def refresh_size!
44
+ @width, @height = self.class.current_dimensions
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :output, :cursor, :terminal, :width, :height
50
+
51
+ def draw
52
+ strokes = [Utils::Ansi.cursor_visible(false), Utils::Ansi.cursor_pos, Utils::Ansi.clear_down]
53
+ Renderer.new(@template, width, height).paint do |left, top, fstring|
54
+ next unless fstring.length > 0
55
+ strokes << Utils::Ansi.cursor_pos(left: left, top: top)
56
+ strokes << fstring
57
+ end
58
+ strokes.each { |stroke| output.print(stroke) }
59
+ output.flush
60
+ @refreshed_width = width
61
+ @refreshed_height = height
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,15 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ class TextDimensions
4
+ attr_reader :outer_width, :outer_height
5
+
6
+ def initialize(content)
7
+ @outer_width = 0
8
+ content.each do |line|
9
+ @outer_width = line.length if line.length > @outer_width
10
+ end
11
+ @outer_height = content.length
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ require 'whirled_peas/utils/formatted_string'
2
+ require 'whirled_peas/utils/title_font'
3
+
4
+ require_relative 'painter'
5
+ require_relative 'text_dimensions'
6
+
7
+ module WhirledPeas
8
+ module Graphics
9
+ class TextPainter < Painter
10
+ def paint(canvas, &block)
11
+ return unless canvas.writable?
12
+ formatting = [*settings.color, *settings.bg_color]
13
+ formatting << Utils::Ansi::BOLD if settings.bold?
14
+ if settings.underline? && settings.title_font.nil?
15
+ formatting << Utils::Ansi::UNDERLINE
16
+ end
17
+ content.each.with_index do |line, index|
18
+ canvas.stroke(canvas.start_left, canvas.start_top + index, line, formatting, &block)
19
+ end
20
+ end
21
+
22
+ def dimensions
23
+ TextDimensions.new(content)
24
+ end
25
+
26
+ def content
27
+ if settings.title_font
28
+ Utils::TitleFont.to_s(
29
+ element.content, settings.title_font
30
+ ).split("\n")
31
+ else
32
+ element.content.split("\n")
33
+ end
34
+ end
35
+ end
36
+ private_constant :TextPainter
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ module WhirledPeas
2
+ module Settings
3
+ end
4
+ private_constant :Settings
5
+ end
@@ -0,0 +1,22 @@
1
+ require 'whirled_peas/utils/ansi'
2
+
3
+ require_relative 'color'
4
+
5
+ module WhirledPeas
6
+ module Settings
7
+ class BgColor < Color
8
+ BG_OFFSET = 10
9
+ private_constant :BG_OFFSET
10
+
11
+ BLACK = new(Utils::Ansi::BLACK + BG_OFFSET)
12
+ RED = new(Utils::Ansi::RED + BG_OFFSET)
13
+ GREEN = new(Utils::Ansi::GREEN + BG_OFFSET)
14
+ YELLOW = new(Utils::Ansi::YELLOW + BG_OFFSET)
15
+ BLUE = new(Utils::Ansi::BLUE + BG_OFFSET)
16
+ MAGENTA = new(Utils::Ansi::MAGENTA + BG_OFFSET)
17
+ CYAN = new(Utils::Ansi::CYAN + BG_OFFSET)
18
+ WHITE = new(Utils::Ansi::WHITE + BG_OFFSET)
19
+ GRAY = BLACK.bright
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,101 @@
1
+ require_relative 'text_color'
2
+
3
+ module WhirledPeas
4
+ module Settings
5
+ class Border
6
+ Style = Struct.new(
7
+ :top_left, :top_horiz, :top_junc, :top_right,
8
+ :left_vert, :left_junc,
9
+ :middle_vert, :cross_junc, :middle_horiz,
10
+ :right_vert, :right_junc,
11
+ :bottom_left, :bottom_horiz, :bottom_junc, :bottom_right
12
+ )
13
+
14
+ module Styles
15
+ BOLD = Style.new(
16
+ '┏', '━', '┳', '┓',
17
+ '┃', '┣',
18
+ '┃', '╋', '━',
19
+ '┃', '┫',
20
+ '┗', '━', '┻', '┛'
21
+ )
22
+ SOFT = Style.new(
23
+ '╭', '─', '┬', '╮',
24
+ '│', '├',
25
+ '│', '┼', '─',
26
+ '│', '┤',
27
+ '╰', '─', '┴', '╯'
28
+ )
29
+ DOUBLE = Style.new(
30
+ '╔', '═', '╦', '╗',
31
+ '║', '╠',
32
+ '║', '╬', '═',
33
+ '║', '╣',
34
+ '╚', '═', '╩', '╝'
35
+ )
36
+
37
+ def self.validate!(style)
38
+ return unless style
39
+ if style.is_a?(Symbol)
40
+ error_message = "Unsupported border style: #{style}"
41
+ begin
42
+ style = self.const_get(style.upcase)
43
+ raise ArgumentError, error_message unless style.is_a?(Style)
44
+ style
45
+ rescue NameError
46
+ raise ArgumentError, error_message
47
+ end
48
+ else
49
+ style
50
+ end
51
+ end
52
+ end
53
+
54
+ attr_writer :left, :top, :right, :bottom, :inner_horiz, :inner_vert
55
+
56
+ def left?
57
+ @left == true
58
+ end
59
+
60
+ def top?
61
+ @top == true
62
+ end
63
+
64
+ def right?
65
+ @right == true
66
+ end
67
+
68
+ def bottom?
69
+ @bottom == true
70
+ end
71
+
72
+ def inner_horiz?
73
+ @inner_horiz == true
74
+ end
75
+
76
+ def inner_vert?
77
+ @inner_vert == true
78
+ end
79
+
80
+ def outer?
81
+ left? || top? || right? || bottom?
82
+ end
83
+
84
+ def style
85
+ @style || Styles::BOLD
86
+ end
87
+
88
+ def style=(val)
89
+ @style = Styles.validate!(val)
90
+ end
91
+
92
+ def color
93
+ @color || TextColor::WHITE
94
+ end
95
+
96
+ def color=(val)
97
+ @color = TextColor.validate!(val)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'container_settings'
2
+
3
+ module WhirledPeas
4
+ module Settings
5
+ class BoxSettings < ContainerSettings
6
+ end
7
+ end
8
+ end
@@ -1,14 +1,9 @@
1
- require_relative 'ansi'
1
+ require 'whirled_peas/utils/ansi'
2
2
 
3
3
  module WhirledPeas
4
- module UI
4
+ module Settings
5
5
  # An abstract class that encapsulates colors for a specific use case
6
6
  class Color
7
- # The ANSI codes for bright colors are offset by this much from their
8
- # standard versions
9
- BRIGHT_OFFSET = 60
10
- private_constant :BRIGHT_OFFSET
11
-
12
7
  # Validate the `color` argument is either (1) nil, (2) a valid Color constant in
13
8
  # this class or (3) a symbol that maps to valid Color constant. E.g. if there
14
9
  # is a RED constant in an implementing class, then :red or :bright_red are
@@ -49,7 +44,7 @@ module WhirledPeas
49
44
  end
50
45
 
51
46
  def bright
52
- bright? ? self : self.class.new(@code + BRIGHT_OFFSET, true)
47
+ bright? ? self : self.class.new(@code + Utils::Ansi::BRIGHT_OFFSET, true)
53
48
  end
54
49
 
55
50
  def hash
@@ -70,32 +65,5 @@ module WhirledPeas
70
65
  end
71
66
  end
72
67
  private_constant :Color
73
-
74
- class BgColor < Color
75
- BG_OFFSET = 10
76
- private_constant :BG_OFFSET
77
-
78
- BLACK = new(Ansi::BLACK + BG_OFFSET)
79
- RED = new(Ansi::RED + BG_OFFSET)
80
- GREEN = new(Ansi::GREEN + BG_OFFSET)
81
- YELLOW = new(Ansi::YELLOW + BG_OFFSET)
82
- BLUE = new(Ansi::BLUE + BG_OFFSET)
83
- MAGENTA = new(Ansi::MAGENTA + BG_OFFSET)
84
- CYAN = new(Ansi::CYAN + BG_OFFSET)
85
- WHITE = new(Ansi::WHITE + BG_OFFSET)
86
- GRAY = BLACK.bright
87
- end
88
-
89
- class TextColor < Color
90
- BLACK = new(Ansi::BLACK)
91
- RED = new(Ansi::RED)
92
- GREEN = new(Ansi::GREEN)
93
- YELLOW = new(Ansi::YELLOW)
94
- BLUE = new(Ansi::BLUE)
95
- MAGENTA = new(Ansi::MAGENTA)
96
- CYAN = new(Ansi::CYAN)
97
- WHITE = new(Ansi::WHITE)
98
- GRAY = BLACK.bright
99
- end
100
68
  end
101
69
  end
@@ -0,0 +1,128 @@
1
+ require_relative 'border'
2
+ require_relative 'display_flow'
3
+ require_relative 'element_settings'
4
+ require_relative 'margin'
5
+ require_relative 'padding'
6
+ require_relative 'position'
7
+
8
+ module WhirledPeas
9
+ module Settings
10
+ class ContainerSettings < ElementSettings
11
+ attr_accessor :width, :height
12
+
13
+ def align
14
+ @_align || TextAlign::LEFT
15
+ end
16
+
17
+ def align_left?
18
+ align == TextAlign::LEFT
19
+ end
20
+
21
+ def align_center?
22
+ align == TextAlign::CENTER
23
+ end
24
+
25
+ def align_right?
26
+ align == TextAlign::RIGHT
27
+ end
28
+
29
+ def align=(align)
30
+ @_align = TextAlign.validate!(align)
31
+ end
32
+
33
+ def set_border(
34
+ left: nil, top: nil, right: nil, bottom: nil, inner_horiz: nil, inner_vert: nil, style: nil, color: nil
35
+ )
36
+ @_border = Border.new unless @_border
37
+ @_border.left = left unless left.nil?
38
+ @_border.top = top unless top.nil?
39
+ @_border.right = right unless right.nil?
40
+ @_border.bottom = bottom unless bottom.nil?
41
+ @_border.inner_horiz = inner_horiz unless inner_horiz.nil?
42
+ @_border.inner_vert = inner_vert unless inner_vert.nil?
43
+ @_border.style = style unless style.nil?
44
+ @_border.color = color unless color.nil?
45
+ end
46
+
47
+ def full_border(style: nil, color: nil)
48
+ set_border(
49
+ left: true, top: true, right: true, bottom: true, inner_horiz: true, inner_vert: true, style: style, color: color
50
+ )
51
+ end
52
+
53
+ def border
54
+ @_border || Border.new
55
+ end
56
+
57
+ def flow=(flow)
58
+ @_flow = DisplayFlow.validate!(flow)
59
+ end
60
+
61
+ def flow
62
+ @_flow || DisplayFlow::LEFT_TO_RIGHT
63
+ end
64
+
65
+ def horizontal_flow?
66
+ %i[l2r r2l].include?(flow)
67
+ end
68
+
69
+ def vertical_flow?
70
+ !horizontal_flow?
71
+ end
72
+
73
+ def forward_flow?
74
+ %i[l2r t2b].include?(flow)
75
+ end
76
+
77
+ def reverse_flow?
78
+ !forward_flow?
79
+ end
80
+
81
+ def set_margin(left: nil, top: nil, right: nil, bottom: nil)
82
+ @_margin = Margin.new unless @_margin
83
+ @_margin.left = left if left
84
+ @_margin.top = top if top
85
+ @_margin.right = right if right
86
+ @_margin.bottom = bottom if bottom
87
+ end
88
+
89
+ def margin
90
+ @_margin || Margin.new
91
+ end
92
+
93
+ def set_padding(left: nil, top: nil, right: nil, bottom: nil)
94
+ @_padding = Padding.new unless @_padding
95
+ @_padding.left = left if left
96
+ @_padding.top = top if top
97
+ @_padding.right = right if right
98
+ @_padding.bottom = bottom if bottom
99
+ end
100
+
101
+ def padding
102
+ @_padding || Padding.new
103
+ end
104
+
105
+ def set_position(left: nil, top: nil)
106
+ @_position = Position.new unless @_position
107
+ @_position.left = left if left
108
+ @_position.top = top if top
109
+ end
110
+
111
+ def position
112
+ @_position || Position.new
113
+ end
114
+
115
+ def inherit(parent)
116
+ super
117
+ return unless parent.is_a?(ContainerSettings)
118
+ @_align = parent._align
119
+ @_flow = parent._flow
120
+ set_border(color: parent.border.color, style: parent.border.style)
121
+ end
122
+
123
+ protected
124
+
125
+ attr_accessor :_align, :_flow
126
+ end
127
+ end
128
+ end