whirled_peas 0.1.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile +1 -0
  5. data/README.md +240 -79
  6. data/Rakefile +37 -3
  7. data/bin/debug +35 -0
  8. data/exe/whirled_peas +7 -0
  9. data/lib/whirled_peas.rb +17 -37
  10. data/lib/whirled_peas/command_line.rb +263 -0
  11. data/lib/whirled_peas/config.rb +21 -0
  12. data/lib/whirled_peas/debugger.rb +80 -0
  13. data/lib/whirled_peas/errors.rb +7 -0
  14. data/lib/whirled_peas/frame.rb +0 -8
  15. data/lib/whirled_peas/frame/consumer.rb +14 -51
  16. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  17. data/lib/whirled_peas/frame/event_loop.rb +68 -38
  18. data/lib/whirled_peas/frame/producer.rb +37 -34
  19. data/lib/whirled_peas/graphics.rb +5 -0
  20. data/lib/whirled_peas/graphics/box_painter.rb +100 -0
  21. data/lib/whirled_peas/graphics/canvas.rb +107 -0
  22. data/lib/whirled_peas/graphics/container_coords.rb +61 -0
  23. data/lib/whirled_peas/graphics/container_dimensions.rb +65 -0
  24. data/lib/whirled_peas/graphics/container_painter.rb +116 -0
  25. data/lib/whirled_peas/graphics/debugger.rb +43 -0
  26. data/lib/whirled_peas/graphics/grid_painter.rb +56 -0
  27. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  28. data/lib/whirled_peas/graphics/painter.rb +24 -0
  29. data/lib/whirled_peas/graphics/renderer.rb +49 -0
  30. data/lib/whirled_peas/graphics/screen.rb +65 -0
  31. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  32. data/lib/whirled_peas/graphics/text_painter.rb +38 -0
  33. data/lib/whirled_peas/settings.rb +5 -0
  34. data/lib/whirled_peas/settings/bg_color.rb +22 -0
  35. data/lib/whirled_peas/settings/border.rb +101 -0
  36. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  37. data/lib/whirled_peas/settings/color.rb +69 -0
  38. data/lib/whirled_peas/settings/container_settings.rb +128 -0
  39. data/lib/whirled_peas/settings/debugger.rb +87 -0
  40. data/lib/whirled_peas/settings/display_flow.rb +25 -0
  41. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  42. data/lib/whirled_peas/settings/grid_settings.rb +11 -0
  43. data/lib/whirled_peas/settings/margin.rb +8 -0
  44. data/lib/whirled_peas/settings/padding.rb +8 -0
  45. data/lib/whirled_peas/settings/position.rb +15 -0
  46. data/lib/whirled_peas/settings/spacing.rb +24 -0
  47. data/lib/whirled_peas/settings/text_align.rb +19 -0
  48. data/lib/whirled_peas/settings/text_color.rb +19 -0
  49. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  50. data/lib/whirled_peas/template.rb +5 -0
  51. data/lib/whirled_peas/template/box_element.rb +8 -0
  52. data/lib/whirled_peas/template/composer.rb +68 -0
  53. data/lib/whirled_peas/template/container.rb +28 -0
  54. data/lib/whirled_peas/template/debugger.rb +34 -0
  55. data/lib/whirled_peas/template/element.rb +13 -0
  56. data/lib/whirled_peas/template/grid_element.rb +8 -0
  57. data/lib/whirled_peas/template/text_element.rb +24 -0
  58. data/lib/whirled_peas/utils.rb +5 -0
  59. data/lib/whirled_peas/utils/ansi.rb +53 -0
  60. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  61. data/lib/whirled_peas/utils/title_font.rb +75 -0
  62. data/lib/whirled_peas/version.rb +1 -1
  63. data/screen_test/rendered/elements/box.frame +1 -0
  64. data/screen_test/rendered/elements/box.rb +20 -0
  65. data/screen_test/rendered/elements/grid.frame +1 -0
  66. data/screen_test/rendered/elements/grid.rb +13 -0
  67. data/screen_test/rendered/elements/screen_overflow.rb +9 -0
  68. data/screen_test/rendered/elements/text.frame +1 -0
  69. data/screen_test/rendered/elements/text.rb +9 -0
  70. data/screen_test/rendered/elements/text_multiline.frame +1 -0
  71. data/screen_test/rendered/elements/text_multiline.rb +9 -0
  72. data/screen_test/rendered/settings/align/box.frame +1 -0
  73. data/screen_test/rendered/settings/align/box.rb +20 -0
  74. data/screen_test/rendered/settings/align/children_center.frame +1 -0
  75. data/screen_test/rendered/settings/align/children_center.rb +13 -0
  76. data/screen_test/rendered/settings/align/children_left.frame +1 -0
  77. data/screen_test/rendered/settings/align/children_left.rb +13 -0
  78. data/screen_test/rendered/settings/align/children_right.frame +1 -0
  79. data/screen_test/rendered/settings/align/children_right.rb +13 -0
  80. data/screen_test/rendered/settings/align/grid.frame +1 -0
  81. data/screen_test/rendered/settings/align/grid.rb +20 -0
  82. data/screen_test/rendered/settings/ansi/bold.frame +1 -0
  83. data/screen_test/rendered/settings/ansi/bold.rb +15 -0
  84. data/screen_test/rendered/settings/ansi/color.frame +1 -0
  85. data/screen_test/rendered/settings/ansi/color.rb +37 -0
  86. data/screen_test/rendered/settings/ansi/underline.frame +1 -0
  87. data/screen_test/rendered/settings/ansi/underline.rb +15 -0
  88. data/screen_test/rendered/settings/border.frame +1 -0
  89. data/screen_test/rendered/settings/border.rb +13 -0
  90. data/screen_test/rendered/settings/flow/l2r.frame +1 -0
  91. data/screen_test/rendered/settings/flow/l2r.rb +24 -0
  92. data/screen_test/rendered/settings/flow/t2b.frame +1 -0
  93. data/screen_test/rendered/settings/flow/t2b.rb +24 -0
  94. data/screen_test/rendered/settings/height/box.frame +1 -0
  95. data/screen_test/rendered/settings/height/box.rb +13 -0
  96. data/screen_test/rendered/settings/height/grid.frame +1 -0
  97. data/screen_test/rendered/settings/height/grid.rb +14 -0
  98. data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
  99. data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
  100. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
  101. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
  102. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
  103. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
  104. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
  105. data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
  106. data/screen_test/rendered/settings/margin.frame +1 -0
  107. data/screen_test/rendered/settings/margin.rb +14 -0
  108. data/screen_test/rendered/settings/padding.frame +1 -0
  109. data/screen_test/rendered/settings/padding.rb +11 -0
  110. data/screen_test/rendered/settings/position/box_left.frame +1 -0
  111. data/screen_test/rendered/settings/position/box_left.rb +17 -0
  112. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
  113. data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
  114. data/screen_test/rendered/settings/position/box_top.frame +1 -0
  115. data/screen_test/rendered/settings/position/box_top.rb +17 -0
  116. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
  117. data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
  118. data/screen_test/rendered/settings/position/grid_left.frame +1 -0
  119. data/screen_test/rendered/settings/position/grid_left.rb +18 -0
  120. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
  121. data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
  122. data/screen_test/rendered/settings/position/grid_top.frame +1 -0
  123. data/screen_test/rendered/settings/position/grid_top.rb +18 -0
  124. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
  125. data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
  126. data/screen_test/rendered/settings/title_font.frame +1 -0
  127. data/screen_test/rendered/settings/title_font.rb +12 -0
  128. data/screen_test/rendered/settings/width/box.frame +1 -0
  129. data/screen_test/rendered/settings/width/box.rb +13 -0
  130. data/screen_test/rendered/settings/width/grid.frame +1 -0
  131. data/screen_test/rendered/settings/width/grid.rb +14 -0
  132. data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
  133. data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
  134. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
  135. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
  136. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
  137. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
  138. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
  139. data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
  140. data/screen_test/screen_tester.rb +191 -0
  141. data/whirled_peas.gemspec +4 -2
  142. metadata +136 -20
  143. data/lib/whirled_peas/ui.rb +0 -7
  144. data/lib/whirled_peas/ui/ansi.rb +0 -154
  145. data/lib/whirled_peas/ui/canvas.rb +0 -35
  146. data/lib/whirled_peas/ui/element.rb +0 -199
  147. data/lib/whirled_peas/ui/painter.rb +0 -283
  148. data/lib/whirled_peas/ui/screen.rb +0 -62
  149. data/lib/whirled_peas/ui/settings.rb +0 -512
  150. data/lib/whirled_peas/ui/stroke.rb +0 -29
  151. data/sandbox/auto.rb +0 -13
  152. data/sandbox/box.rb +0 -19
  153. data/sandbox/grid.rb +0 -13
  154. data/sandbox/sandbox.rb +0 -17
  155. data/sandbox/text.rb +0 -33
@@ -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
@@ -0,0 +1,69 @@
1
+ require 'whirled_peas/utils/ansi'
2
+
3
+ module WhirledPeas
4
+ module Settings
5
+ # An abstract class that encapsulates colors for a specific use case
6
+ class Color
7
+ # Validate the `color` argument is either (1) nil, (2) a valid Color constant in
8
+ # this class or (3) a symbol that maps to valid Color constant. E.g. if there
9
+ # is a RED constant in an implementing class, then :red or :bright_red are
10
+ # valid values for `color`
11
+ #
12
+ # @param color [Color|Symbol]
13
+ # @return [Color|Symbol] the value passed in if valid, otherwise an ArgumentError
14
+ # is raised.
15
+ def self.validate!(color)
16
+ return unless color
17
+ if color.is_a?(Symbol)
18
+ error_message = "Unsupported #{self.name.split('::').last}: #{color.inspect}"
19
+ match = color.to_s.match(/^(bright_)?(\w+)$/)
20
+ begin
21
+ color = self.const_get(match[2].upcase)
22
+ raise ArgumentError, error_message unless color.is_a?(Color)
23
+ if match[1]
24
+ raise ArgumentError, error_message if color.bright?
25
+ color.bright
26
+ else
27
+ color
28
+ end
29
+ rescue NameError
30
+ raise ArgumentError, error_message
31
+ end
32
+ else
33
+ color
34
+ end
35
+ end
36
+
37
+ def initialize(code, bright=false)
38
+ @code = code
39
+ @bright = bright
40
+ end
41
+
42
+ def bright?
43
+ @bright
44
+ end
45
+
46
+ def bright
47
+ bright? ? self : self.class.new(@code + Utils::Ansi::BRIGHT_OFFSET, true)
48
+ end
49
+
50
+ def hash
51
+ [@code, @bright].hash
52
+ end
53
+
54
+ def ==(other)
55
+ other.is_a?(self.class) && self.hash == other.hash
56
+ end
57
+ alias_method :eq?, :==
58
+
59
+ def to_s
60
+ @code.to_s
61
+ end
62
+
63
+ def inspect
64
+ "#{self.class.name.split('::').last}(code=#{@code}, bright=#{@bright})"
65
+ end
66
+ end
67
+ private_constant :Color
68
+ end
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