whirled_peas 0.3.0 → 0.7.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 (221) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +38 -0
  4. data/README.md +549 -85
  5. data/Rakefile +28 -3
  6. data/examples/intro.rb +52 -0
  7. data/examples/scrolling.rb +53 -0
  8. data/exe/whirled_peas +7 -0
  9. data/lib/whirled_peas.rb +12 -36
  10. data/lib/whirled_peas/command_line.rb +255 -0
  11. data/lib/whirled_peas/config.rb +21 -0
  12. data/lib/whirled_peas/errors.rb +7 -0
  13. data/lib/whirled_peas/frame.rb +0 -5
  14. data/lib/whirled_peas/frame/consumer.rb +30 -0
  15. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  16. data/lib/whirled_peas/frame/event_loop.rb +69 -38
  17. data/lib/whirled_peas/frame/producer.rb +36 -19
  18. data/lib/whirled_peas/graphics.rb +19 -0
  19. data/lib/whirled_peas/graphics/box_painter.rb +101 -0
  20. data/lib/whirled_peas/graphics/canvas.rb +118 -0
  21. data/lib/whirled_peas/graphics/composer.rb +80 -0
  22. data/lib/whirled_peas/graphics/container_coords.rb +72 -0
  23. data/lib/whirled_peas/graphics/container_dimensions.rb +93 -0
  24. data/lib/whirled_peas/graphics/container_painter.rb +272 -0
  25. data/lib/whirled_peas/graphics/debugger.rb +52 -0
  26. data/lib/whirled_peas/graphics/grid_painter.rb +69 -0
  27. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  28. data/lib/whirled_peas/graphics/painter.rb +23 -0
  29. data/lib/whirled_peas/graphics/renderer.rb +26 -0
  30. data/lib/whirled_peas/graphics/screen.rb +70 -0
  31. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  32. data/lib/whirled_peas/graphics/text_painter.rb +40 -0
  33. data/lib/whirled_peas/settings.rb +5 -0
  34. data/lib/whirled_peas/settings/alignment.rb +24 -0
  35. data/lib/whirled_peas/settings/bg_color.rb +24 -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 +68 -0
  39. data/lib/whirled_peas/settings/container_settings.rb +223 -0
  40. data/lib/whirled_peas/settings/debugger.rb +96 -0
  41. data/lib/whirled_peas/settings/display_flow.rb +27 -0
  42. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  43. data/lib/whirled_peas/settings/grid_settings.rb +19 -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/sizing.rb +19 -0
  49. data/lib/whirled_peas/settings/spacing.rb +58 -0
  50. data/lib/whirled_peas/settings/text_color.rb +21 -0
  51. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  52. data/lib/whirled_peas/settings/vert_alignment.rb +24 -0
  53. data/lib/whirled_peas/utils.rb +5 -0
  54. data/lib/whirled_peas/utils/ansi.rb +53 -0
  55. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  56. data/lib/whirled_peas/utils/title_font.rb +75 -0
  57. data/lib/whirled_peas/version.rb +1 -1
  58. data/screen_test/elements/box.frame +1 -0
  59. data/screen_test/elements/box.rb +20 -0
  60. data/screen_test/elements/grid.frame +1 -0
  61. data/screen_test/elements/grid.rb +13 -0
  62. data/screen_test/elements/screen_overflow_x.frame +1 -0
  63. data/screen_test/elements/screen_overflow_x.rb +9 -0
  64. data/screen_test/elements/screen_overflow_y.frame +1 -0
  65. data/screen_test/elements/screen_overflow_y.rb +9 -0
  66. data/screen_test/elements/text.frame +1 -0
  67. data/screen_test/elements/text.rb +9 -0
  68. data/screen_test/elements/text_multiline.frame +1 -0
  69. data/screen_test/elements/text_multiline.rb +9 -0
  70. data/screen_test/settings/align/box_around.frame +1 -0
  71. data/screen_test/settings/align/box_around.rb +16 -0
  72. data/screen_test/settings/align/box_between.frame +1 -0
  73. data/screen_test/settings/align/box_between.rb +16 -0
  74. data/screen_test/settings/align/box_center.frame +1 -0
  75. data/screen_test/settings/align/box_center.rb +21 -0
  76. data/screen_test/settings/align/box_default.frame +1 -0
  77. data/screen_test/settings/align/box_default.rb +20 -0
  78. data/screen_test/settings/align/box_evenly.frame +1 -0
  79. data/screen_test/settings/align/box_evenly.rb +16 -0
  80. data/screen_test/settings/align/box_left.frame +1 -0
  81. data/screen_test/settings/align/box_left.rb +21 -0
  82. data/screen_test/settings/align/box_right.frame +1 -0
  83. data/screen_test/settings/align/box_right.rb +21 -0
  84. data/screen_test/settings/align/children_center.frame +1 -0
  85. data/screen_test/settings/align/children_center.rb +15 -0
  86. data/screen_test/settings/align/children_left.frame +1 -0
  87. data/screen_test/settings/align/children_left.rb +15 -0
  88. data/screen_test/settings/align/children_right.frame +1 -0
  89. data/screen_test/settings/align/children_right.rb +15 -0
  90. data/screen_test/settings/align/grid_center.frame +1 -0
  91. data/screen_test/settings/align/grid_center.rb +18 -0
  92. data/screen_test/settings/align/grid_default.frame +1 -0
  93. data/screen_test/settings/align/grid_default.rb +17 -0
  94. data/screen_test/settings/align/grid_left.frame +1 -0
  95. data/screen_test/settings/align/grid_left.rb +18 -0
  96. data/screen_test/settings/align/grid_right.frame +1 -0
  97. data/screen_test/settings/align/grid_right.rb +18 -0
  98. data/screen_test/settings/ansi/bold.frame +1 -0
  99. data/screen_test/settings/ansi/bold.rb +14 -0
  100. data/screen_test/settings/ansi/color.frame +1 -0
  101. data/screen_test/settings/ansi/color.rb +37 -0
  102. data/screen_test/settings/ansi/underline.frame +1 -0
  103. data/screen_test/settings/ansi/underline.rb +14 -0
  104. data/screen_test/settings/border.frame +1 -0
  105. data/screen_test/settings/border.rb +13 -0
  106. data/screen_test/settings/flow/box_b2t.frame +1 -0
  107. data/screen_test/settings/flow/box_b2t.rb +26 -0
  108. data/screen_test/settings/flow/box_l2r.frame +1 -0
  109. data/screen_test/settings/flow/box_l2r.rb +26 -0
  110. data/screen_test/settings/flow/box_r2l.frame +1 -0
  111. data/screen_test/settings/flow/box_r2l.rb +26 -0
  112. data/screen_test/settings/flow/box_t2b.frame +1 -0
  113. data/screen_test/settings/flow/box_t2b.rb +26 -0
  114. data/screen_test/settings/flow/grid_b2t.frame +1 -0
  115. data/screen_test/settings/flow/grid_b2t.rb +14 -0
  116. data/screen_test/settings/flow/grid_l2r.frame +1 -0
  117. data/screen_test/settings/flow/grid_l2r.rb +14 -0
  118. data/screen_test/settings/flow/grid_r2l.frame +1 -0
  119. data/screen_test/settings/flow/grid_r2l.rb +14 -0
  120. data/screen_test/settings/flow/grid_t2b.frame +1 -0
  121. data/screen_test/settings/flow/grid_t2b.rb +14 -0
  122. data/screen_test/settings/height/box.frame +1 -0
  123. data/screen_test/settings/height/box.rb +13 -0
  124. data/screen_test/settings/height/box_border_sizing.frame +1 -0
  125. data/screen_test/settings/height/box_border_sizing.rb +15 -0
  126. data/screen_test/settings/height/grid.frame +1 -0
  127. data/screen_test/settings/height/grid.rb +14 -0
  128. data/screen_test/settings/height/overflow_box.frame +1 -0
  129. data/screen_test/settings/height/overflow_box.rb +13 -0
  130. data/screen_test/settings/height/overflow_box_l2r.frame +1 -0
  131. data/screen_test/settings/height/overflow_box_l2r.rb +17 -0
  132. data/screen_test/settings/height/overflow_box_t2b.frame +1 -0
  133. data/screen_test/settings/height/overflow_box_t2b.rb +16 -0
  134. data/screen_test/settings/height/overflow_grid.frame +1 -0
  135. data/screen_test/settings/height/overflow_grid.rb +16 -0
  136. data/screen_test/settings/margin.frame +1 -0
  137. data/screen_test/settings/margin.rb +16 -0
  138. data/screen_test/settings/padding.frame +1 -0
  139. data/screen_test/settings/padding.rb +13 -0
  140. data/screen_test/settings/position/box_left.frame +1 -0
  141. data/screen_test/settings/position/box_left.rb +17 -0
  142. data/screen_test/settings/position/box_left_negative.frame +1 -0
  143. data/screen_test/settings/position/box_left_negative.rb +17 -0
  144. data/screen_test/settings/position/box_top.frame +1 -0
  145. data/screen_test/settings/position/box_top.rb +17 -0
  146. data/screen_test/settings/position/box_top_negative.frame +1 -0
  147. data/screen_test/settings/position/box_top_negative.rb +17 -0
  148. data/screen_test/settings/position/grid_left.frame +1 -0
  149. data/screen_test/settings/position/grid_left.rb +18 -0
  150. data/screen_test/settings/position/grid_left_negative.frame +1 -0
  151. data/screen_test/settings/position/grid_left_negative.rb +18 -0
  152. data/screen_test/settings/position/grid_top.frame +1 -0
  153. data/screen_test/settings/position/grid_top.rb +18 -0
  154. data/screen_test/settings/position/grid_top_negative.frame +1 -0
  155. data/screen_test/settings/position/grid_top_negative.rb +18 -0
  156. data/screen_test/settings/scroll/horiz_box.frame +1 -0
  157. data/screen_test/settings/scroll/horiz_box.rb +17 -0
  158. data/screen_test/settings/scroll/horiz_box_align_center.rb +18 -0
  159. data/screen_test/settings/scroll/horiz_box_align_right.rb +18 -0
  160. data/screen_test/settings/scroll/vert_box.frame +1 -0
  161. data/screen_test/settings/scroll/vert_box.rb +20 -0
  162. data/screen_test/settings/title_font.frame +1 -0
  163. data/screen_test/settings/title_font.rb +12 -0
  164. data/screen_test/settings/valign/box_around.frame +1 -0
  165. data/screen_test/settings/valign/box_around.rb +17 -0
  166. data/screen_test/settings/valign/box_between.frame +1 -0
  167. data/screen_test/settings/valign/box_between.rb +17 -0
  168. data/screen_test/settings/valign/box_bottom.frame +1 -0
  169. data/screen_test/settings/valign/box_bottom.rb +17 -0
  170. data/screen_test/settings/valign/box_default.frame +1 -0
  171. data/screen_test/settings/valign/box_default.rb +16 -0
  172. data/screen_test/settings/valign/box_evenly.frame +1 -0
  173. data/screen_test/settings/valign/box_evenly.rb +17 -0
  174. data/screen_test/settings/valign/box_middle.frame +1 -0
  175. data/screen_test/settings/valign/box_middle.rb +17 -0
  176. data/screen_test/settings/valign/box_top.frame +1 -0
  177. data/screen_test/settings/valign/box_top.rb +17 -0
  178. data/screen_test/settings/valign/grid_bottom.frame +1 -0
  179. data/screen_test/settings/valign/grid_bottom.rb +15 -0
  180. data/screen_test/settings/valign/grid_default.frame +1 -0
  181. data/screen_test/settings/valign/grid_default.rb +14 -0
  182. data/screen_test/settings/valign/grid_middle.frame +1 -0
  183. data/screen_test/settings/valign/grid_middle.rb +15 -0
  184. data/screen_test/settings/valign/grid_top.frame +1 -0
  185. data/screen_test/settings/valign/grid_top.rb +15 -0
  186. data/screen_test/settings/width/box_border_sizing.frame +1 -0
  187. data/screen_test/settings/width/box_border_sizing.rb +15 -0
  188. data/screen_test/settings/width/box_content.frame +1 -0
  189. data/screen_test/settings/width/box_content.rb +15 -0
  190. data/screen_test/settings/width/box_default.frame +1 -0
  191. data/screen_test/settings/width/box_default.rb +14 -0
  192. data/screen_test/settings/width/grid.frame +1 -0
  193. data/screen_test/settings/width/grid.rb +14 -0
  194. data/screen_test/settings/width/overflow_align_center.frame +1 -0
  195. data/screen_test/settings/width/overflow_align_center.rb +14 -0
  196. data/screen_test/settings/width/overflow_align_right.frame +1 -0
  197. data/screen_test/settings/width/overflow_align_right.rb +14 -0
  198. data/screen_test/settings/width/overflow_box.frame +1 -0
  199. data/screen_test/settings/width/overflow_box.rb +13 -0
  200. data/screen_test/settings/width/overflow_box_l2r.frame +1 -0
  201. data/screen_test/settings/width/overflow_box_l2r.rb +16 -0
  202. data/screen_test/settings/width/overflow_box_t2b.frame +1 -0
  203. data/screen_test/settings/width/overflow_box_t2b.rb +17 -0
  204. data/screen_test/settings/width/overflow_grid.frame +1 -0
  205. data/screen_test/settings/width/overflow_grid.rb +14 -0
  206. data/tools/whirled_peas/tools/screen_tester.rb +233 -0
  207. data/whirled_peas.gemspec +4 -1
  208. metadata +215 -17
  209. data/lib/whirled_peas/ui.rb +0 -7
  210. data/lib/whirled_peas/ui/ansi.rb +0 -103
  211. data/lib/whirled_peas/ui/canvas.rb +0 -68
  212. data/lib/whirled_peas/ui/color.rb +0 -101
  213. data/lib/whirled_peas/ui/element.rb +0 -224
  214. data/lib/whirled_peas/ui/painter.rb +0 -283
  215. data/lib/whirled_peas/ui/screen.rb +0 -60
  216. data/lib/whirled_peas/ui/settings.rb +0 -523
  217. data/sandbox/auto.rb +0 -13
  218. data/sandbox/box.rb +0 -19
  219. data/sandbox/grid.rb +0 -13
  220. data/sandbox/sandbox.rb +0 -17
  221. data/sandbox/text.rb +0 -33
@@ -0,0 +1,52 @@
1
+ require 'whirled_peas/settings/debugger'
2
+
3
+ require_relative 'container_painter'
4
+ require_relative 'text_painter'
5
+
6
+ module WhirledPeas
7
+ module Graphics
8
+ class Debugger
9
+ def initialize(painter)
10
+ @painter = painter
11
+ end
12
+
13
+ def debug(indent='')
14
+ info = [
15
+ "#{indent}* #{painter.class}(#{painter.name})",
16
+ ]
17
+ info << "#{indent + ' '}- Dimensions(#{dimensions})"
18
+ info << "#{indent + ' '}- Settings"
19
+ info << Settings::Debugger.new(painter.settings).debug(indent + ' ')
20
+ if painter.is_a?(TextPainter)
21
+ content = if painter.content.length > 1
22
+ '<multiline>'
23
+ elsif painter.content.first.length > 12
24
+ "#{painter.content.first[0..9]}...".inspect
25
+ else
26
+ painter.content.first.inspect
27
+ end
28
+ info << "#{indent + ' '}- Content(value=#{content})"
29
+ elsif painter.is_a?(ContainerPainter)
30
+ info << "#{indent + ' '}- Children"
31
+ info += painter.each_child.map { |c| Debugger.new(c).debug(indent + ' ') }
32
+ end
33
+ info.join("\n")
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :painter
39
+
40
+ def dimensions
41
+ outer = "#{painter.dimensions.outer_width}x#{painter.dimensions.outer_height}"
42
+ if painter.is_a?(ContainerPainter)
43
+ content = "#{painter.dimensions.content_width}x#{painter.dimensions.content_height}"
44
+ grid = "#{painter.dimensions.num_cols}x#{painter.dimensions.num_rows}"
45
+ "outer=#{outer}, content=#{content}, grid=#{grid}"
46
+ else
47
+ "outer=#{outer}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,69 @@
1
+ require_relative 'container_dimensions'
2
+ require_relative 'container_painter'
3
+
4
+ module WhirledPeas
5
+ module Graphics
6
+ class GridPainter < ContainerPainter
7
+ def paint(canvas, left, top, &block)
8
+ super
9
+ return unless canvas.writable?
10
+ each_child.with_index do |child, index|
11
+ col_index, row_index = grid_cell(index)
12
+ left_offset, _ = horiz_justify_offset(child.dimensions.outer_width)
13
+ top_offset, _ = vert_justify_offset(child.dimensions.outer_height)
14
+ child_canvas = canvas.child(
15
+ coords(left, top).content_left(col_index) + left_offset,
16
+ coords(left, top).content_top(row_index) + top_offset,
17
+ [dimensions.content_width, child.dimensions.outer_width].min,
18
+ [dimensions.content_height, child.dimensions.outer_height].min
19
+ )
20
+ child.paint(
21
+ child_canvas,
22
+ coords(left, top).content_left(col_index) + left_offset,
23
+ coords(left, top).content_top(row_index) + top_offset,
24
+ &block
25
+ )
26
+ end
27
+ end
28
+
29
+ def dimensions
30
+ @dimensions ||= begin
31
+ if settings.num_cols.nil? || settings.num_cols == 0
32
+ raise SettingsError, "`num_cols` must be set for GridSettings(#{name})"
33
+ end
34
+ num_cols = settings.num_cols
35
+ num_rows = (num_children.to_f / num_cols).ceil
36
+ content_width = 0
37
+ each_child do |child|
38
+ if child.dimensions.outer_width > content_width
39
+ content_width = child.dimensions.outer_width
40
+ end
41
+ end
42
+ content_height = 0
43
+ each_child do |child|
44
+ if child.dimensions.outer_height > content_height
45
+ content_height = child.dimensions.outer_height
46
+ end
47
+ end
48
+ ContainerDimensions.new(
49
+ settings, content_width, content_height, num_cols, num_rows
50
+ )
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def grid_cell(index)
57
+ if settings.horizontal_flow?
58
+ col_index, row_index = [index % dimensions.num_cols, index / dimensions.num_cols]
59
+ col_index = dimensions.num_cols - col_index - 1 if settings.reverse_flow?
60
+ else
61
+ col_index, row_index = [index / dimensions.num_cols, index % dimensions.num_cols]
62
+ row_index = dimensions.num_rows - row_index - 1 if settings.reverse_flow?
63
+ end
64
+ [col_index, row_index]
65
+ end
66
+ end
67
+ private_constant :BoxPainter
68
+ end
69
+ end
@@ -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,23 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ class Painter
4
+ attr_reader :name, :settings
5
+
6
+ def initialize(name, settings)
7
+ @settings = settings
8
+ @name = name
9
+ end
10
+
11
+ def paint(canvas, left, top, &block)
12
+ end
13
+
14
+ def dimensions
15
+ end
16
+
17
+ def inspect
18
+ "#{self.class.name.split('::').last}(name=#{name.inspect})"
19
+ end
20
+ end
21
+ private_constant :Painter
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'canvas'
2
+
3
+ module WhirledPeas
4
+ module Graphics
5
+ class Renderer
6
+ def initialize(template, width, height)
7
+ @template = template
8
+ @width = width
9
+ @height = height
10
+ end
11
+
12
+ def paint(&block)
13
+ # Modify the template's settings so that it fills the entire screen
14
+ template.settings.width = width
15
+ template.settings.height = height
16
+ template.settings.sizing = :border
17
+ template.settings.set_margin(left: 0, top: 0, right: 0, bottom: 0)
18
+ template.paint(Canvas.new(0, 0, width, height), 0, 0, &block)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :template, :width, :height
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,70 @@
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(width: nil, height: nil, output: STDOUT)
16
+ @output = output
17
+ @terminal = HighLine.new.terminal
18
+ @strokes = []
19
+ if width && height
20
+ @width = width
21
+ @height = height
22
+ else
23
+ refresh_size!
24
+ Signal.trap('SIGWINCH', proc { self.refresh_size! })
25
+ end
26
+ end
27
+
28
+ def paint(template)
29
+ @template = template
30
+ draw
31
+ end
32
+
33
+ def refresh
34
+ # No need to refresh if the screen dimensions have not changed
35
+ return if @refreshed_width == width || @refreshed_height == height
36
+ draw
37
+ end
38
+
39
+ def finalize
40
+ output.print Utils::Ansi.clear
41
+ output.print Utils::Ansi.cursor_pos(top: height - 1)
42
+ output.print Utils::Ansi.cursor_visible(true)
43
+ output.flush
44
+ end
45
+
46
+ protected
47
+
48
+ def refresh_size!
49
+ @width, @height = self.class.current_dimensions
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :output, :cursor, :terminal, :width, :height
55
+
56
+ def draw
57
+ strokes = [Utils::Ansi.cursor_visible(false), Utils::Ansi.cursor_pos, Utils::Ansi.clear_down]
58
+ Renderer.new(@template, width, height).paint do |left, top, fstring|
59
+ next unless fstring.length > 0
60
+ strokes << Utils::Ansi.cursor_pos(left: left, top: top)
61
+ strokes << fstring
62
+ end
63
+ strokes.each { |stroke| output.print(stroke) }
64
+ output.flush
65
+ @refreshed_width = width
66
+ @refreshed_height = height
67
+ end
68
+ end
69
+ end
70
+ 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,40 @@
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
+ attr_reader :content
11
+
12
+ def paint(canvas, left, top, &block)
13
+ return unless canvas.writable?
14
+ formatting = [*settings.color, *settings.bg_color]
15
+ formatting << Utils::Ansi::BOLD if settings.bold?
16
+ if settings.underline? && settings.title_font.nil?
17
+ formatting << Utils::Ansi::UNDERLINE
18
+ end
19
+ content.each.with_index do |line, index|
20
+ canvas.stroke(left, top + index, line, formatting, &block)
21
+ end
22
+ end
23
+
24
+ def dimensions
25
+ TextDimensions.new(content)
26
+ end
27
+
28
+ def content=(content)
29
+ @content = if settings.title_font
30
+ Utils::TitleFont.to_s(
31
+ content, settings.title_font
32
+ ).split("\n")
33
+ else
34
+ content.split("\n")
35
+ end
36
+ end
37
+ end
38
+ private_constant :TextPainter
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ module WhirledPeas
2
+ module Settings
3
+ end
4
+ private_constant :Settings
5
+ end
@@ -0,0 +1,24 @@
1
+ module WhirledPeas
2
+ module Settings
3
+ module Alignment
4
+ LEFT = :left
5
+ CENTER = :center
6
+ RIGHT = :right
7
+ BETWEEN = :between
8
+ AROUND = :around
9
+ EVENLY = :evenly
10
+
11
+ DEFAULT = LEFT
12
+
13
+ VALID = [LEFT, CENTER, RIGHT, BETWEEN, AROUND, EVENLY]
14
+ private_constant :VALID
15
+
16
+ def self.validate!(align)
17
+ return if align.nil?
18
+ return align if VALID.include?(align)
19
+ raise ArgumentError, "Unsupported alignment: #{align.inspect}"
20
+ end
21
+ end
22
+ private_constant :Alignment
23
+ end
24
+ end
@@ -0,0 +1,24 @@
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
+
21
+ DEFAULT = nil # Use system defined default
22
+ end
23
+ end
24
+ 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
+ DEFAULT = BOLD
38
+
39
+ def self.validate!(style)
40
+ return if style.nil?
41
+ return style if style.is_a?(Border::Style)
42
+ if style.is_a?(Symbol)
43
+ style_class = self.const_get(style.upcase)
44
+ return style_class if style_class.is_a?(Border::Style)
45
+ end
46
+ error_message = "Unsupported border style: #{style.inspect}"
47
+ raise ArgumentError, error_message
48
+ rescue NameError
49
+ error_message = "Unsupported border style: #{style.inspect}"
50
+ raise ArgumentError, error_message
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::DEFAULT
86
+ end
87
+
88
+ def style=(val)
89
+ @style = Styles.validate!(val)
90
+ end
91
+
92
+ def color=(val)
93
+ @color = TextColor.validate!(val)
94
+ end
95
+
96
+ def color
97
+ @color || TextColor::DEFAULT
98
+ end
99
+ end
100
+ end
101
+ end