whirled_peas 0.1.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 (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