whirled_peas 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +38 -0
  4. data/Gemfile +1 -0
  5. data/README.md +275 -81
  6. data/Rakefile +38 -3
  7. data/examples/intro.rb +52 -0
  8. data/examples/scrolling.rb +53 -0
  9. data/exe/whirled_peas +7 -0
  10. data/lib/whirled_peas.rb +12 -42
  11. data/lib/whirled_peas/command_line.rb +255 -0
  12. data/lib/whirled_peas/config.rb +21 -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 -50
  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 +36 -32
  19. data/lib/whirled_peas/graphics.rb +5 -0
  20. data/lib/whirled_peas/graphics/box_painter.rb +108 -0
  21. data/lib/whirled_peas/graphics/canvas.rb +107 -0
  22. data/lib/whirled_peas/graphics/composer.rb +80 -0
  23. data/lib/whirled_peas/graphics/container_coords.rb +71 -0
  24. data/lib/whirled_peas/graphics/container_dimensions.rb +75 -0
  25. data/lib/whirled_peas/graphics/container_painter.rb +234 -0
  26. data/lib/whirled_peas/graphics/debugger.rb +52 -0
  27. data/lib/whirled_peas/graphics/grid_painter.rb +68 -0
  28. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  29. data/lib/whirled_peas/graphics/painter.rb +19 -0
  30. data/lib/whirled_peas/graphics/renderer.rb +21 -0
  31. data/lib/whirled_peas/graphics/screen.rb +70 -0
  32. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  33. data/lib/whirled_peas/graphics/text_painter.rb +40 -0
  34. data/lib/whirled_peas/settings.rb +5 -0
  35. data/lib/whirled_peas/settings/bg_color.rb +22 -0
  36. data/lib/whirled_peas/settings/border.rb +101 -0
  37. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  38. data/lib/whirled_peas/settings/color.rb +69 -0
  39. data/lib/whirled_peas/settings/container_settings.rb +139 -0
  40. data/lib/whirled_peas/settings/debugger.rb +96 -0
  41. data/lib/whirled_peas/settings/display_flow.rb +25 -0
  42. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  43. data/lib/whirled_peas/settings/grid_settings.rb +15 -0
  44. data/lib/whirled_peas/settings/margin.rb +8 -0
  45. data/lib/whirled_peas/settings/padding.rb +8 -0
  46. data/lib/whirled_peas/settings/position.rb +15 -0
  47. data/lib/whirled_peas/settings/scrollbar.rb +15 -0
  48. data/lib/whirled_peas/settings/spacing.rb +24 -0
  49. data/lib/whirled_peas/settings/text_align.rb +19 -0
  50. data/lib/whirled_peas/settings/text_color.rb +19 -0
  51. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  52. data/lib/whirled_peas/utils.rb +5 -0
  53. data/lib/whirled_peas/utils/ansi.rb +53 -0
  54. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  55. data/lib/whirled_peas/utils/title_font.rb +75 -0
  56. data/lib/whirled_peas/version.rb +1 -1
  57. data/screen_test/rendered/elements/box.frame +1 -0
  58. data/screen_test/rendered/elements/box.rb +20 -0
  59. data/screen_test/rendered/elements/grid.frame +1 -0
  60. data/screen_test/rendered/elements/grid.rb +13 -0
  61. data/screen_test/rendered/elements/screen_overflow.frame +1 -0
  62. data/screen_test/rendered/elements/screen_overflow.rb +9 -0
  63. data/screen_test/rendered/elements/text.frame +1 -0
  64. data/screen_test/rendered/elements/text.rb +9 -0
  65. data/screen_test/rendered/elements/text_multiline.frame +1 -0
  66. data/screen_test/rendered/elements/text_multiline.rb +9 -0
  67. data/screen_test/rendered/settings/align/box.frame +1 -0
  68. data/screen_test/rendered/settings/align/box.rb +24 -0
  69. data/screen_test/rendered/settings/align/children_center.frame +1 -0
  70. data/screen_test/rendered/settings/align/children_center.rb +13 -0
  71. data/screen_test/rendered/settings/align/children_left.frame +1 -0
  72. data/screen_test/rendered/settings/align/children_left.rb +13 -0
  73. data/screen_test/rendered/settings/align/children_right.frame +1 -0
  74. data/screen_test/rendered/settings/align/children_right.rb +13 -0
  75. data/screen_test/rendered/settings/align/grid.frame +1 -0
  76. data/screen_test/rendered/settings/align/grid.rb +20 -0
  77. data/screen_test/rendered/settings/ansi/bold.frame +1 -0
  78. data/screen_test/rendered/settings/ansi/bold.rb +15 -0
  79. data/screen_test/rendered/settings/ansi/color.frame +1 -0
  80. data/screen_test/rendered/settings/ansi/color.rb +37 -0
  81. data/screen_test/rendered/settings/ansi/underline.frame +1 -0
  82. data/screen_test/rendered/settings/ansi/underline.rb +15 -0
  83. data/screen_test/rendered/settings/border.frame +1 -0
  84. data/screen_test/rendered/settings/border.rb +13 -0
  85. data/screen_test/rendered/settings/flow/box_b2t.frame +1 -0
  86. data/screen_test/rendered/settings/flow/box_b2t.rb +24 -0
  87. data/screen_test/rendered/settings/flow/box_l2r.frame +1 -0
  88. data/screen_test/rendered/settings/flow/box_l2r.rb +24 -0
  89. data/screen_test/rendered/settings/flow/box_r2l.frame +1 -0
  90. data/screen_test/rendered/settings/flow/box_r2l.rb +24 -0
  91. data/screen_test/rendered/settings/flow/box_t2b.frame +1 -0
  92. data/screen_test/rendered/settings/flow/box_t2b.rb +24 -0
  93. data/screen_test/rendered/settings/flow/grid_b2t.frame +1 -0
  94. data/screen_test/rendered/settings/flow/grid_b2t.rb +14 -0
  95. data/screen_test/rendered/settings/flow/grid_l2r.frame +1 -0
  96. data/screen_test/rendered/settings/flow/grid_l2r.rb +14 -0
  97. data/screen_test/rendered/settings/flow/grid_r2l.frame +1 -0
  98. data/screen_test/rendered/settings/flow/grid_r2l.rb +14 -0
  99. data/screen_test/rendered/settings/flow/grid_t2b.frame +1 -0
  100. data/screen_test/rendered/settings/flow/grid_t2b.rb +14 -0
  101. data/screen_test/rendered/settings/height/box.frame +1 -0
  102. data/screen_test/rendered/settings/height/box.rb +13 -0
  103. data/screen_test/rendered/settings/height/grid.frame +1 -0
  104. data/screen_test/rendered/settings/height/grid.rb +14 -0
  105. data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
  106. data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
  107. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
  108. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
  109. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
  110. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
  111. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
  112. data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
  113. data/screen_test/rendered/settings/margin.frame +1 -0
  114. data/screen_test/rendered/settings/margin.rb +14 -0
  115. data/screen_test/rendered/settings/padding.frame +1 -0
  116. data/screen_test/rendered/settings/padding.rb +11 -0
  117. data/screen_test/rendered/settings/position/box_left.frame +1 -0
  118. data/screen_test/rendered/settings/position/box_left.rb +17 -0
  119. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
  120. data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
  121. data/screen_test/rendered/settings/position/box_top.frame +1 -0
  122. data/screen_test/rendered/settings/position/box_top.rb +17 -0
  123. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
  124. data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
  125. data/screen_test/rendered/settings/position/grid_left.frame +1 -0
  126. data/screen_test/rendered/settings/position/grid_left.rb +18 -0
  127. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
  128. data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
  129. data/screen_test/rendered/settings/position/grid_top.frame +1 -0
  130. data/screen_test/rendered/settings/position/grid_top.rb +18 -0
  131. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
  132. data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
  133. data/screen_test/rendered/settings/scroll/horiz_box.frame +1 -0
  134. data/screen_test/rendered/settings/scroll/horiz_box.rb +15 -0
  135. data/screen_test/rendered/settings/scroll/vert_box.frame +1 -0
  136. data/screen_test/rendered/settings/scroll/vert_box.rb +18 -0
  137. data/screen_test/rendered/settings/title_font.frame +1 -0
  138. data/screen_test/rendered/settings/title_font.rb +12 -0
  139. data/screen_test/rendered/settings/width/box.frame +1 -0
  140. data/screen_test/rendered/settings/width/box.rb +13 -0
  141. data/screen_test/rendered/settings/width/grid.frame +1 -0
  142. data/screen_test/rendered/settings/width/grid.rb +14 -0
  143. data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
  144. data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
  145. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
  146. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
  147. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
  148. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
  149. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
  150. data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
  151. data/screen_test/screen_tester.rb +201 -0
  152. data/whirled_peas.gemspec +4 -2
  153. metadata +147 -20
  154. data/lib/whirled_peas/ui.rb +0 -7
  155. data/lib/whirled_peas/ui/ansi.rb +0 -154
  156. data/lib/whirled_peas/ui/canvas.rb +0 -35
  157. data/lib/whirled_peas/ui/element.rb +0 -225
  158. data/lib/whirled_peas/ui/painter.rb +0 -283
  159. data/lib/whirled_peas/ui/screen.rb +0 -62
  160. data/lib/whirled_peas/ui/settings.rb +0 -521
  161. data/lib/whirled_peas/ui/stroke.rb +0 -29
  162. data/sandbox/auto.rb +0 -13
  163. data/sandbox/box.rb +0 -19
  164. data/sandbox/grid.rb +0 -13
  165. data/sandbox/sandbox.rb +0 -17
  166. data/sandbox/text.rb +0 -33
@@ -1,283 +0,0 @@
1
- require_relative 'ansi'
2
- require_relative 'canvas'
3
- require_relative 'settings'
4
-
5
- module WhirledPeas
6
- module UI
7
- DEBUG_SPACING = ARGV.include?('--debug-spacing')
8
-
9
- class TextPainter
10
- JUSTIFICATION = DEBUG_SPACING ? 'j' : ' '
11
-
12
- def initialize(text, canvas)
13
- @text = text
14
- @canvas = canvas
15
- end
16
-
17
- def paint(&block)
18
- yield canvas.stroke(canvas.left, canvas.top, justified)
19
- end
20
-
21
- private
22
-
23
- attr_reader :text, :canvas
24
-
25
- def visible
26
- if text.value.length <= text.preferred_width
27
- text.value
28
- elsif text.settings.align == TextAlign::LEFT
29
- text.value[0..text.preferred_width - 1]
30
- elsif text.settings.align == TextAlign::CENTER
31
- left_chop = (text.value.length - text.preferred_width) / 2
32
- right_chop = text.value.length - text.preferred_width - left_chop
33
- text.value[left_chop..-right_chop - 1]
34
- else
35
- text.value[-text.preferred_width..-1]
36
- end
37
- end
38
-
39
- def justified
40
- format_settings = [*text.settings.color, *text.settings.bg_color]
41
- format_settings << TextFormat::BOLD if text.settings.bold?
42
- format_settings << TextFormat::UNDERLINE if text.settings.underline?
43
-
44
- ljust = case text.settings.align
45
- when TextAlign::LEFT
46
- 0
47
- when TextAlign::CENTER
48
- [0, (text.preferred_width - text.value.length) / 2].max
49
- when TextAlign::RIGHT
50
- [0, text.preferred_width - text.value.length].max
51
- end
52
- rjust = [0, text.preferred_width - text.value.length - ljust].max
53
- Ansi.format(JUSTIFICATION * ljust, [*text.settings.bg_color]) +
54
- Ansi.format(visible, format_settings) +
55
- Ansi.format(JUSTIFICATION * rjust, [*text.settings.bg_color])
56
- end
57
- end
58
- private_constant :TextPainter
59
-
60
- class ContainerPainter
61
- PADDING = DEBUG_SPACING ? 'p' : ' '
62
-
63
- def initialize(container, canvas)
64
- @container = container
65
- @settings = container.settings
66
- @canvas = canvas
67
- end
68
-
69
- def paint(&block)
70
- return if container.num_rows == 0 || container.num_cols == 0
71
- top = canvas.top + settings.margin.top
72
- if settings.auto_margin?
73
- left = canvas.left + (canvas.width - container.preferred_width) / 2
74
- else
75
- left = canvas.left + settings.margin.left
76
- end
77
- if settings.border.top?
78
- yield canvas.stroke(left, top, top_border)
79
- top += 1
80
- end
81
- container.num_rows.times do |row_num|
82
- if row_num > 0 && settings.border.inner_horiz?
83
- yield canvas.stroke(left, top, middle_border)
84
- top += 1
85
- end
86
- (settings.padding.top + container.row_height + settings.padding.bottom).times do
87
- yield canvas.stroke(left, top, content_line)
88
- top += 1
89
- end
90
- end
91
- if settings.border.bottom?
92
- yield canvas.stroke(left, top, bottom_border)
93
- top += 1
94
- end
95
- end
96
-
97
- private
98
-
99
- attr_reader :container, :settings, :canvas
100
-
101
- def line_stroke(left_border, horiz_border, junc_border, right_border)
102
- stroke = ''
103
- stroke += left_border if settings.border.left?
104
- container.num_cols.times do |col_num|
105
- stroke += junc_border if col_num > 0 && settings.border.inner_horiz?
106
- stroke += horiz_border * (container.col_width + settings.padding.left + settings.padding.right)
107
- end
108
- stroke += right_border if settings.border.right?
109
- Ansi.format(stroke, [*settings.border.color, *settings.bg_color])
110
- end
111
-
112
- def top_border
113
- line_stroke(
114
- settings.border.style.top_left,
115
- settings.border.style.top_horiz,
116
- settings.border.style.top_junc,
117
- settings.border.style.top_right
118
- )
119
- end
120
-
121
- def content_line
122
- line_stroke(
123
- settings.border.style.left_vert,
124
- PADDING,
125
- settings.border.style.middle_vert,
126
- settings.border.style.right_vert
127
- )
128
- end
129
-
130
- def middle_border
131
- line_stroke(
132
- settings.border.style.left_junc,
133
- settings.border.style.middle_horiz,
134
- settings.border.style.cross_junc,
135
- settings.border.style.right_junc
136
- )
137
- end
138
-
139
- def bottom_border
140
- line_stroke(
141
- settings.border.style.bottom_left,
142
- settings.border.style.bottom_horiz,
143
- settings.border.style.bottom_junc,
144
- settings.border.style.bottom_right
145
- )
146
- end
147
- end
148
-
149
- class BoxContainer
150
- attr_reader :settings, :num_cols, :num_rows, :col_width, :row_height, :preferred_width
151
-
152
- def initialize(box)
153
- @settings = ContainerSettings.merge(box.settings)
154
- @num_cols = 1
155
- @num_rows = 1
156
- @col_width = box.content_width
157
- @row_height = box.content_height
158
- @preferred_width = box.preferred_width
159
- end
160
- end
161
-
162
- class BoxPainter
163
- def initialize(box, canvas)
164
- @box = box
165
- @canvas = canvas
166
- end
167
-
168
- def paint(&block)
169
- container = BoxContainer.new(box)
170
- ContainerPainter.new(container, canvas).paint(&block)
171
- top = canvas.top + box.settings.margin.top + (box.settings.border.top? ? 1 : 0) + box.settings.padding.top
172
- if box.settings.auto_margin?
173
- margin = (canvas.width - box.preferred_width) / 2
174
- else
175
- margin = box.settings.margin.left
176
- end
177
- left = canvas.left + margin + (box.settings.border.left? ? 1 : 0) + box.settings.padding.left
178
- greedy_width = box.settings.vertical_flow? || box.children.length == 1
179
- children = box.children
180
- children = children.reverse if box.settings.reverse_flow?
181
- children.each do |child|
182
- if greedy_width
183
- width = box.content_width
184
- height = child.preferred_height
185
- else
186
- width = child.preferred_width
187
- height = box.content_height
188
- end
189
- child_canvas = Canvas.new(left, top, width, height)
190
- Painter.paint(child, child_canvas, &block)
191
- if box.settings.horizontal_flow?
192
- left += child.preferred_width
193
- else
194
- top += child.preferred_height
195
- end
196
- end
197
- end
198
-
199
- private
200
-
201
- attr_reader :box, :canvas
202
- end
203
-
204
- class GridContainer
205
- attr_reader :settings, :num_cols, :num_rows, :col_width, :row_height, :preferred_width
206
-
207
- def initialize(grid, num_cols, num_rows)
208
- @settings = ContainerSettings.merge(grid.settings)
209
- @num_cols = num_cols
210
- @num_rows = num_rows
211
- @col_width = grid.col_width
212
- @row_height = grid.row_height
213
- @preferred_width = grid.preferred_width
214
- end
215
- end
216
-
217
- class GridPainter
218
- def initialize(grid, canvas)
219
- @grid = grid
220
- @canvas = canvas
221
- available_width = grid.preferred_width - (grid.settings.margin.left || 0) - (grid.settings.margin.right || 0)
222
- @num_cols = grid.settings.num_cols || (available_width - (grid.settings.border.left? ? 1 : 0) - (grid.settings.border.right? ? 1 : 0) + (grid.border.inner_vert ? 1 : 0)) / (col_width + grid.settings.padding.left + grid.settings.right + (grid.border.inner_vert ? 1 : 0))
223
- end
224
-
225
- def paint(&block)
226
- return if grid.children.empty?
227
-
228
- container = GridContainer.new(grid, num_cols, (grid.children.length.to_f / num_cols).ceil)
229
- ContainerPainter.new(container, canvas).paint(&block)
230
-
231
- children = if grid.settings.transpose?
232
- grid.children.length.times.map do |i|
233
- grid.children[(i * num_cols) % grid.children.length + i / (grid.children.length / num_cols)]
234
- end.compact
235
- else
236
- grid.children
237
- end
238
-
239
- top = canvas.top + grid.settings.margin.top + (grid.settings.border.top? ? 1 : 0) + grid.settings.padding.top
240
- if grid.settings.auto_margin?
241
- margin = (canvas.width - grid.preferred_width) / 2
242
- else
243
- margin = grid.settings.margin.left
244
- end
245
- left = canvas.left + margin + (grid.settings.border.left? ? 1 : 0) + grid.settings.padding.left
246
- grid_height = grid.settings.padding.top + grid.row_height + grid.settings.padding.bottom + (grid.settings.border.inner_horiz? ? 1 : 0)
247
- grid_width = grid.settings.padding.left + grid.col_width + grid.settings.padding.right + (grid.settings.border.inner_vert? ? 1 : 0)
248
- children.each_slice(num_cols).each.with_index do |row, row_num|
249
- row_top = top + row_num * grid_height
250
- row.each.with_index do |element, col_num|
251
- col_left = left + col_num * grid_width
252
- child_canvas = Canvas.new(
253
- col_left,
254
- row_top,
255
- element.preferred_width,
256
- element.preferred_height
257
- )
258
- Painter.paint(element, child_canvas, &block)
259
- end
260
- end
261
- end
262
-
263
- private
264
-
265
- attr_reader :grid, :canvas, :num_cols
266
- end
267
-
268
- module Painter
269
- PAINTERS = {
270
- TextElement => TextPainter,
271
- BoxElement => BoxPainter,
272
- GridElement => GridPainter,
273
- }
274
-
275
- def self.paint(element, canvas, &block)
276
- if element.is_a?(Template)
277
- element = BoxElement.from_template(element, canvas.width, canvas.height)
278
- end
279
- PAINTERS[element.class].new(element, canvas).paint(&block)
280
- end
281
- end
282
- end
283
- end
@@ -1,62 +0,0 @@
1
- require 'highline'
2
- require 'tty-cursor'
3
-
4
- require_relative 'ansi'
5
- require_relative 'painter'
6
-
7
- module WhirledPeas
8
- module UI
9
- class Screen
10
- def initialize(print_output=true)
11
- @print_output = print_output
12
- @terminal = HighLine.new.terminal
13
- @cursor = TTY::Cursor
14
- @strokes = []
15
- refresh_size!
16
- Signal.trap('SIGWINCH', proc { self.refresh_size! })
17
- end
18
-
19
- def paint(template)
20
- @template = template
21
- refresh
22
- end
23
-
24
- def needs_refresh?
25
- @refreshed_width != width || @refreshed_height != height
26
- end
27
-
28
- def refresh
29
- strokes = [cursor.hide, cursor.move_to(0, 0), cursor.clear_screen_down]
30
- Painter.paint(@template, Canvas.new(0, 0, width, height)) do |stroke|
31
- unless stroke.chars.nil?
32
- strokes << cursor.move_to(stroke.left, stroke.top)
33
- strokes << stroke.chars
34
- end
35
- end
36
- return unless @print_output
37
- strokes.each(&method(:print))
38
- STDOUT.flush
39
- @refreshed_width = width
40
- @refreshed_height = height
41
- end
42
-
43
- def finalize
44
- return unless @print_output
45
- print UI::Ansi.clear
46
- print cursor.move_to(0, height - 1)
47
- print cursor.show
48
- STDOUT.flush
49
- end
50
-
51
- protected
52
-
53
- def refresh_size!
54
- @width, @height = terminal.terminal_size
55
- end
56
-
57
- private
58
-
59
- attr_reader :cursor, :terminal, :width, :height
60
- end
61
- end
62
- end
@@ -1,521 +0,0 @@
1
- require 'json'
2
-
3
- module WhirledPeas
4
- module UI
5
- module TextAlign
6
- LEFT = :left
7
- CENTER = :center
8
- RIGHT = :right
9
-
10
- def self.validate!(align)
11
- return unless align
12
- if [TextAlign::LEFT, TextAlign::CENTER, TextAlign::RIGHT].include?(align)
13
- align
14
- else
15
- raise ArgumentError, "Invalid alignment: #{align}"
16
- end
17
- end
18
- end
19
-
20
- module DisplayFlow
21
- LEFT_TO_RIGHT = :l2r
22
- RIGHT_TO_LEFT = :r2l
23
- TOP_TO_BOTTOM = :t2b
24
- BOTTOM_TO_TOP = :b2t
25
-
26
- def self.validate!(flow)
27
- return unless flow
28
- if [
29
- DisplayFlow::LEFT_TO_RIGHT,
30
- DisplayFlow::RIGHT_TO_LEFT,
31
- DisplayFlow::TOP_TO_BOTTOM,
32
- DisplayFlow::BOTTOM_TO_TOP
33
- ].include?(flow)
34
- flow
35
- else
36
- raise ArgumentError, "Invalid flow: #{flow}"
37
- end
38
- end
39
- end
40
-
41
- class Spacing
42
- def left
43
- @_left || 0
44
- end
45
-
46
- def left=(val)
47
- @_left = val
48
- end
49
-
50
- def top
51
- @_top || 0
52
- end
53
-
54
- def top=(val)
55
- @_top = val
56
- end
57
-
58
- def right
59
- @_right || 0
60
- end
61
-
62
- def right=(val)
63
- @_right = val
64
- end
65
-
66
- def bottom
67
- @_bottom || 0
68
- end
69
-
70
- def bottom=(val)
71
- @_bottom = val
72
- end
73
-
74
- def merge(parent)
75
- merged = Spacing.new
76
- merged._left = @_left || parent._left
77
- merged._top = @_top || parent._top
78
- merged._right = @_right || parent._right
79
- merged._bottom = @_bottom || parent._bottom
80
- merged
81
- end
82
-
83
- def inspect
84
- vals = {}
85
- vals[:left] = @_left if @_left
86
- vals[:top] = @_top if @_top
87
- vals[:right] = @_right if @_right
88
- vals[:bottom] = @_bottom if @_bottom
89
- JSON.generate(vals) if vals.any?
90
- end
91
-
92
- protected
93
-
94
- attr_accessor :_left, :_top, :_right, :_bottom
95
- end
96
- private_constant :Spacing
97
-
98
- class Padding < Spacing
99
- end
100
-
101
- class Margin < Spacing
102
- end
103
-
104
- BorderStyle = Struct.new(
105
- :top_left, :top_horiz, :top_junc, :top_right,
106
- :left_vert, :left_junc,
107
- :middle_vert, :cross_junc, :middle_horiz,
108
- :right_vert, :right_junc,
109
- :bottom_left, :bottom_horiz, :bottom_junc, :bottom_right
110
- )
111
-
112
- module BorderStyles
113
- BOLD = BorderStyle.new(
114
- '┏', '━', '┳', '┓',
115
- '┃', '┣',
116
- '┃', '╋', '━',
117
- '┃', '┫',
118
- '┗', '━', '┻', '┛'
119
- )
120
- SOFT = BorderStyle.new(
121
- '╭', '─', '┬', '╮',
122
- '│', '├',
123
- '│', '┼', '─',
124
- '│', '┤',
125
- '╰', '─', '┴', '╯'
126
- )
127
- DOUBLE = BorderStyle.new(
128
- '╔', '═', '╦', '╗',
129
- '║', '╠',
130
- '║', '╬', '═',
131
- '║', '╣',
132
- '╚', '═', '╩', '╝'
133
- )
134
-
135
- def self.validate!(style)
136
- return unless style
137
- if style.is_a?(Symbol)
138
- error_message = "Unsupported border style: #{style}"
139
- begin
140
- style = self.const_get(style.upcase)
141
- raise ArgumentError, error_message unless style.is_a?(BorderStyle)
142
- style
143
- rescue NameError
144
- raise ArgumentError, error_message
145
- end
146
- else
147
- style
148
- end
149
- end
150
- end
151
-
152
- class Border
153
- def left?
154
- @_left == true
155
- end
156
-
157
- def left=(val)
158
- @_left = val
159
- end
160
-
161
- def top?
162
- @_top == true
163
- end
164
-
165
- def top=(val)
166
- @_top = val
167
- end
168
-
169
- def right?
170
- @_right == true
171
- end
172
-
173
- def right=(val)
174
- @_right = val
175
- end
176
-
177
- def bottom?
178
- @_bottom == true
179
- end
180
-
181
- def bottom=(val)
182
- @_bottom = val
183
- end
184
-
185
- def inner_horiz?
186
- @_inner_horiz == true
187
- end
188
-
189
- def inner_horiz=(val)
190
- @_inner_horiz = val
191
- end
192
-
193
- def inner_vert?
194
- @_inner_vert == true
195
- end
196
-
197
- def inner_vert=(val)
198
- @_inner_vert = val
199
- end
200
-
201
- def style
202
- @_style || BorderStyles::BOLD
203
- end
204
-
205
- def style=(val)
206
- @_style = BorderStyles.validate!(val)
207
- end
208
-
209
- def color
210
- @_color || TextColor::WHITE
211
- end
212
-
213
- def color=(val)
214
- @_color = TextColor.validate!(val)
215
- end
216
-
217
- def merge(parent)
218
- merged = Border.new
219
- merged._left = @_left.nil? ? parent._left : @_left
220
- merged._top = @_top.nil? ? parent._top : @_top
221
- merged._right = @_right.nil? ? parent._right : @_right
222
- merged._bottom = @_bottom.nil? ? parent._bottom : @_bottom
223
- merged._inner_horiz = @_inner_horiz.nil? ? parent._inner_horiz : @_inner_horiz
224
- merged._inner_vert = @_inner_vert.nil? ? parent._inner_vert : @_inner_vert
225
- merged._style = @_style || parent._style
226
- merged._color = @_color || parent._color
227
- merged
228
- end
229
-
230
- def inspect
231
- vals = {}
232
- vals[:left] = @_left unless @_left.nil?
233
- vals[:top] = @_top unless @_top.nil?
234
- vals[:right] = @_right unless @_right.nil?
235
- vals[:bottom] = @_bottom unless @_bottom.nil?
236
- vals[:inner_horiz] = @_inner_horiz unless @_inner_horiz.nil?
237
- vals[:inner_vert] = @_inner_vert unless @_inner_vert.nil?
238
- JSON.generate(vals) if vals.any?
239
- end
240
-
241
- protected
242
-
243
- attr_accessor :_left, :_top, :_right, :_bottom, :_inner_horiz, :_inner_vert, :_style, :_color
244
- end
245
-
246
- class ElementSettings
247
- def self.merge(parent)
248
- self.new.merge(parent)
249
- end
250
-
251
- def color
252
- @_color
253
- end
254
-
255
- def color=(color)
256
- @_color = TextColor.validate!(color)
257
- end
258
-
259
- def bg_color
260
- @_bg_color
261
- end
262
-
263
- def bg_color=(color)
264
- @_bg_color = BgColor.validate!(color)
265
- end
266
-
267
- def bold?
268
- @_bold || false
269
- end
270
-
271
- def bold=(val)
272
- @_bold = val
273
- end
274
-
275
- def underline?
276
- @_underline || false
277
- end
278
-
279
- def underline=(val)
280
- @_underline = val
281
- end
282
-
283
- def merge(parent)
284
- merged = self.class.new
285
- merged.color = @color || parent.color
286
- merged.bg_color = @bg_color || parent.bg_color
287
- merged._bold = @_bold.nil? ? parent._bold : @_bold
288
- merged._underline = @_underline.nil? ? parent._underline : @_underline
289
- merged
290
- end
291
-
292
- def inspect(indent='')
293
- values = self.instance_variables.map do |v|
294
- unless instance_variable_get(v).nil? || instance_variable_get(v).inspect.nil?
295
- "#{indent} #{v} = #{instance_variable_get(v).inspect}"
296
- end
297
- end.compact
298
- details = values.length > 0 ? values.join("\n") : "#{indent} <default>"
299
- "#{indent}#{self.class.name}\n#{details}"
300
- end
301
-
302
- protected
303
-
304
- attr_accessor :_bold, :_underline
305
- end
306
- private_constant :ElementSettings
307
-
308
- module WidthSetting
309
- attr_accessor :width
310
- end
311
-
312
- module AlignSetting
313
- def align
314
- @_align || TextAlign::LEFT
315
- end
316
-
317
- def align=(align)
318
- @_align = TextAlign.validate!(align)
319
- end
320
-
321
- def merge(parent)
322
- merged = super
323
- merged._align = if @_align
324
- @_align
325
- elsif parent.is_a?(AlignSetting)
326
- parent._align
327
- end
328
- merged
329
- end
330
-
331
- protected
332
-
333
- attr_accessor :_align
334
- end
335
-
336
- module MarginSettings
337
- def set_margin(left: nil, top: nil, right: nil, bottom: nil)
338
- @_margin = Margin.new unless @_margin
339
- @_margin.left = left if left
340
- @_margin.top = top if top
341
- @_margin.right = right if right
342
- @_margin.bottom = bottom if bottom
343
- end
344
-
345
- def clear_margin
346
- set_margin(left: 0, top: 0, right: 0, bottom: 0)
347
- @_auto_margin = nil
348
- end
349
-
350
- def margin
351
- @_margin || Margin.new
352
- end
353
-
354
- def auto_margin=(val)
355
- @_auto_margin = val
356
- end
357
-
358
- def auto_margin?
359
- @_auto_margin || false
360
- end
361
-
362
- def merge(parent)
363
- merged = super
364
- if parent.is_a?(MarginSettings)
365
- merged._margin = margin.merge(parent.margin)
366
- merged._auto_margin = @_auto_margin.nil? ? parent._auto_margin : @_auto_margin
367
- else
368
- merged._margin = _margin
369
- merged._auto_margin = @_auto_margin
370
- end
371
- merged
372
- end
373
-
374
- protected
375
-
376
- attr_accessor :_margin, :_auto_margin
377
- end
378
- private_constant :MarginSettings
379
-
380
- module BorderSettings
381
- def set_border(
382
- left: nil, top: nil, right: nil, bottom: nil, inner_horiz: nil, inner_vert: nil, style: nil, color: nil
383
- )
384
- @_border = Border.new unless @_border
385
- @_border.left = left unless left.nil?
386
- @_border.top = top unless top.nil?
387
- @_border.right = right unless right.nil?
388
- @_border.bottom = bottom unless bottom.nil?
389
- @_border.inner_horiz = inner_horiz unless inner_horiz.nil?
390
- @_border.inner_vert = inner_vert unless inner_vert.nil?
391
- @_border.style = style unless style.nil?
392
- @_border.color = color unless color.nil?
393
- end
394
-
395
- def clear_border
396
- set_border(
397
- left: false, top: false, right: false, bottom: false, inner_horiz: false, inner_vert: false
398
- )
399
- end
400
-
401
- def full_border(style: nil, color: nil)
402
- set_border(
403
- left: true, top: true, right: true, bottom: true, inner_horiz: true, inner_vert: true, style: style, color: color
404
- )
405
- end
406
-
407
- def border
408
- @_border || Border.new
409
- end
410
-
411
- def merge(parent)
412
- merged = super
413
- merged._border = parent.is_a?(BorderSettings) ? border.merge(parent.border) : _border
414
- merged
415
- end
416
-
417
- protected
418
-
419
- attr_accessor :_border
420
- end
421
- private_constant :BorderSettings
422
-
423
- module PaddingSettings
424
- def set_padding(left: nil, top: nil, right: nil, bottom: nil)
425
- @_padding = Padding.new unless @_padding
426
- @_padding.left = left if left
427
- @_padding.top = top if top
428
- @_padding.right = right if right
429
- @_padding.bottom = bottom if bottom
430
- end
431
-
432
- def clear_padding
433
- set_padding(left: 0, top: 0, right: 0, bottom: 0)
434
- end
435
-
436
- def padding
437
- @_padding || Padding.new
438
- end
439
-
440
- def merge(parent)
441
- merged = super
442
- merged._padding = parent.is_a?(PaddingSettings) ? padding.merge(parent.padding) : _padding
443
- merged
444
- end
445
-
446
- protected
447
-
448
- attr_accessor :_padding
449
- end
450
- private_constant :PaddingSettings
451
-
452
- class TemplateSettings < ElementSettings
453
- end
454
-
455
- class TextSettings < ElementSettings
456
- include WidthSetting
457
- include AlignSetting
458
- end
459
-
460
- class ContainerSettings < ElementSettings
461
- include MarginSettings
462
- include BorderSettings
463
- include PaddingSettings
464
- end
465
-
466
- class BoxSettings < ContainerSettings
467
- include WidthSetting
468
- include AlignSetting
469
-
470
- def flow=(flow)
471
- @_flow = DisplayFlow.validate!(flow)
472
- end
473
-
474
- def flow
475
- @_flow || DisplayFlow::LEFT_TO_RIGHT
476
- end
477
-
478
- def horizontal_flow?
479
- %i[l2r r2l].include?(flow)
480
- end
481
-
482
- def vertical_flow?
483
- !horizontal_flow?
484
- end
485
-
486
- def forward_flow?
487
- %i[l2r t2b].include?(flow)
488
- end
489
-
490
- def reverse_flow?
491
- !forward_flow?
492
- end
493
-
494
- def merge(parent)
495
- merged = super
496
- merged._flow = if @_flow
497
- @_flow
498
- elsif parent.is_a?(BoxSettings)
499
- parent._flow
500
- end
501
- merged
502
- end
503
-
504
- protected
505
-
506
- attr_accessor :_flow
507
- end
508
-
509
- class GridSettings < ContainerSettings
510
- include WidthSetting
511
- include AlignSetting
512
-
513
- attr_accessor :num_cols
514
- attr_writer :transpose
515
-
516
- def transpose?
517
- @transpose || false
518
- end
519
- end
520
- end
521
- end