whirled_peas 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +111 -73
  4. data/Rakefile +37 -3
  5. data/bin/debug +35 -0
  6. data/lib/whirled_peas.rb +9 -7
  7. data/lib/whirled_peas/command_line.rb +25 -32
  8. data/lib/whirled_peas/debugger.rb +80 -0
  9. data/lib/whirled_peas/errors.rb +2 -0
  10. data/lib/whirled_peas/frame.rb +0 -1
  11. data/lib/whirled_peas/frame/consumer.rb +30 -0
  12. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  13. data/lib/whirled_peas/frame/event_loop.rb +29 -30
  14. data/lib/whirled_peas/frame/producer.rb +11 -5
  15. data/lib/whirled_peas/graphics.rb +5 -0
  16. data/lib/whirled_peas/graphics/box_painter.rb +100 -0
  17. data/lib/whirled_peas/graphics/canvas.rb +107 -0
  18. data/lib/whirled_peas/graphics/container_coords.rb +61 -0
  19. data/lib/whirled_peas/graphics/container_dimensions.rb +65 -0
  20. data/lib/whirled_peas/graphics/container_painter.rb +116 -0
  21. data/lib/whirled_peas/graphics/debugger.rb +43 -0
  22. data/lib/whirled_peas/graphics/grid_painter.rb +56 -0
  23. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  24. data/lib/whirled_peas/graphics/painter.rb +24 -0
  25. data/lib/whirled_peas/graphics/renderer.rb +49 -0
  26. data/lib/whirled_peas/graphics/screen.rb +65 -0
  27. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  28. data/lib/whirled_peas/graphics/text_painter.rb +38 -0
  29. data/lib/whirled_peas/settings.rb +5 -0
  30. data/lib/whirled_peas/settings/bg_color.rb +22 -0
  31. data/lib/whirled_peas/settings/border.rb +101 -0
  32. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  33. data/lib/whirled_peas/{utils → settings}/color.rb +3 -35
  34. data/lib/whirled_peas/settings/container_settings.rb +128 -0
  35. data/lib/whirled_peas/settings/debugger.rb +87 -0
  36. data/lib/whirled_peas/settings/display_flow.rb +25 -0
  37. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  38. data/lib/whirled_peas/settings/grid_settings.rb +11 -0
  39. data/lib/whirled_peas/settings/margin.rb +8 -0
  40. data/lib/whirled_peas/settings/padding.rb +8 -0
  41. data/lib/whirled_peas/settings/position.rb +15 -0
  42. data/lib/whirled_peas/settings/spacing.rb +24 -0
  43. data/lib/whirled_peas/settings/text_align.rb +19 -0
  44. data/lib/whirled_peas/settings/text_color.rb +19 -0
  45. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  46. data/lib/whirled_peas/template/box_element.rb +8 -0
  47. data/lib/whirled_peas/template/composer.rb +68 -0
  48. data/lib/whirled_peas/template/container.rb +28 -0
  49. data/lib/whirled_peas/template/debugger.rb +34 -0
  50. data/lib/whirled_peas/template/element.rb +1 -218
  51. data/lib/whirled_peas/template/grid_element.rb +8 -0
  52. data/lib/whirled_peas/template/text_element.rb +24 -0
  53. data/lib/whirled_peas/utils/ansi.rb +6 -56
  54. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  55. data/lib/whirled_peas/version.rb +1 -1
  56. data/screen_test/rendered/elements/box.frame +1 -0
  57. data/screen_test/rendered/elements/box.rb +20 -0
  58. data/screen_test/rendered/elements/grid.frame +1 -0
  59. data/screen_test/rendered/elements/grid.rb +13 -0
  60. data/screen_test/rendered/elements/screen_overflow.rb +9 -0
  61. data/screen_test/rendered/elements/text.frame +1 -0
  62. data/screen_test/rendered/elements/text.rb +9 -0
  63. data/screen_test/rendered/elements/text_multiline.frame +1 -0
  64. data/screen_test/rendered/elements/text_multiline.rb +9 -0
  65. data/screen_test/rendered/settings/align/box.frame +1 -0
  66. data/screen_test/rendered/settings/align/box.rb +20 -0
  67. data/screen_test/rendered/settings/align/children_center.frame +1 -0
  68. data/screen_test/rendered/settings/align/children_center.rb +13 -0
  69. data/screen_test/rendered/settings/align/children_left.frame +1 -0
  70. data/screen_test/rendered/settings/align/children_left.rb +13 -0
  71. data/screen_test/rendered/settings/align/children_right.frame +1 -0
  72. data/screen_test/rendered/settings/align/children_right.rb +13 -0
  73. data/screen_test/rendered/settings/align/grid.frame +1 -0
  74. data/screen_test/rendered/settings/align/grid.rb +20 -0
  75. data/screen_test/rendered/settings/ansi/bold.frame +1 -0
  76. data/screen_test/rendered/settings/ansi/bold.rb +15 -0
  77. data/screen_test/rendered/settings/ansi/color.frame +1 -0
  78. data/screen_test/rendered/settings/ansi/color.rb +37 -0
  79. data/screen_test/rendered/settings/ansi/underline.frame +1 -0
  80. data/screen_test/rendered/settings/ansi/underline.rb +15 -0
  81. data/screen_test/rendered/settings/border.frame +1 -0
  82. data/screen_test/rendered/settings/border.rb +13 -0
  83. data/screen_test/rendered/settings/flow/l2r.frame +1 -0
  84. data/screen_test/rendered/settings/flow/l2r.rb +24 -0
  85. data/screen_test/rendered/settings/flow/t2b.frame +1 -0
  86. data/screen_test/rendered/settings/flow/t2b.rb +24 -0
  87. data/screen_test/rendered/settings/height/box.frame +1 -0
  88. data/screen_test/rendered/settings/height/box.rb +13 -0
  89. data/screen_test/rendered/settings/height/grid.frame +1 -0
  90. data/screen_test/rendered/settings/height/grid.rb +14 -0
  91. data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
  92. data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
  93. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
  94. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
  95. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
  96. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
  97. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
  98. data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
  99. data/screen_test/rendered/settings/margin.frame +1 -0
  100. data/screen_test/rendered/settings/margin.rb +14 -0
  101. data/screen_test/rendered/settings/padding.frame +1 -0
  102. data/screen_test/rendered/settings/padding.rb +11 -0
  103. data/screen_test/rendered/settings/position/box_left.frame +1 -0
  104. data/screen_test/rendered/settings/position/box_left.rb +17 -0
  105. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
  106. data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
  107. data/screen_test/rendered/settings/position/box_top.frame +1 -0
  108. data/screen_test/rendered/settings/position/box_top.rb +17 -0
  109. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
  110. data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
  111. data/screen_test/rendered/settings/position/grid_left.frame +1 -0
  112. data/screen_test/rendered/settings/position/grid_left.rb +18 -0
  113. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
  114. data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
  115. data/screen_test/rendered/settings/position/grid_top.frame +1 -0
  116. data/screen_test/rendered/settings/position/grid_top.rb +18 -0
  117. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
  118. data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
  119. data/screen_test/rendered/settings/title_font.frame +1 -0
  120. data/screen_test/rendered/settings/title_font.rb +12 -0
  121. data/screen_test/rendered/settings/width/box.frame +1 -0
  122. data/screen_test/rendered/settings/width/box.rb +13 -0
  123. data/screen_test/rendered/settings/width/grid.frame +1 -0
  124. data/screen_test/rendered/settings/width/grid.rb +14 -0
  125. data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
  126. data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
  127. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
  128. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
  129. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
  130. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
  131. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
  132. data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
  133. data/screen_test/screen_tester.rb +191 -0
  134. metadata +122 -10
  135. data/bin/title_fonts +0 -6
  136. data/lib/whirled_peas/frame/print_consumer.rb +0 -33
  137. data/lib/whirled_peas/template/settings.rb +0 -530
  138. data/lib/whirled_peas/ui.rb +0 -5
  139. data/lib/whirled_peas/ui/canvas.rb +0 -68
  140. data/lib/whirled_peas/ui/painter.rb +0 -287
  141. data/lib/whirled_peas/ui/screen.rb +0 -63
@@ -1,13 +1,6 @@
1
- require_relative '../utils/title_font'
2
-
3
- require_relative 'settings'
4
-
5
1
  module WhirledPeas
6
- module UI
7
- STRINGALBE_CLASSES = [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass]
8
-
2
+ module Template
9
3
  class Element
10
- attr_accessor :preferred_width, :preferred_height
11
4
  attr_reader :name, :settings
12
5
 
13
6
  def initialize(name, settings)
@@ -16,215 +9,5 @@ module WhirledPeas
16
9
  end
17
10
  end
18
11
  private_constant :Element
19
-
20
- class TextElement < Element
21
- attr_reader :lines
22
-
23
- def initialize(name, settings)
24
- super(name, TextSettings.merge(settings))
25
- end
26
-
27
- def lines=(val)
28
- unless STRINGALBE_CLASSES.include?(val.class)
29
- raise ArgmentError, "Unsupported type for TextElement: #{val.class}"
30
- end
31
- if settings.title_font
32
- @lines = Utils::TitleFont.to_s(val.to_s, settings.title_font).split("\n")
33
- else
34
- @lines = [val.to_s]
35
- end
36
- @preferred_width = settings.width || @lines.first.length
37
- @preferred_height = @lines.length
38
- end
39
-
40
- def inspect(indent='')
41
- dims = unless preferred_width.nil?
42
- "#{indent + ' '}- Dimensions: #{preferred_width}x#{preferred_height}"
43
- end
44
- [
45
- "#{indent}+ #{name} [#{self.class.name}]",
46
- dims,
47
- "#{indent + ' '}- Settings",
48
- settings.inspect(indent + ' ')
49
- ].compact.join("\n")
50
- end
51
- end
52
-
53
- class ComposableElement < Element
54
- class << self
55
- def next_name
56
- @counter ||= 0
57
- @counter += 1
58
- "Element-#{@counter}"
59
- end
60
- end
61
-
62
- def initialize(name, settings)
63
- super
64
- end
65
-
66
- def children
67
- @children ||= []
68
- end
69
-
70
- def add_text(name=self.class.next_name, &block)
71
- element = TextElement.new(name, settings)
72
- element.lines = yield nil, element.settings
73
- children << element
74
- end
75
-
76
- def add_box(name=self.class.next_name, &block)
77
- element = BoxElement.new(name, settings)
78
- value = yield element, element.settings
79
- children << element
80
- if element.children.empty? && STRINGALBE_CLASSES.include?(value.class)
81
- element.add_text { value.to_s }
82
- end
83
- end
84
-
85
- def add_grid(name=self.class.next_name, &block)
86
- element = GridElement.new(name, settings)
87
- values = yield element, element.settings
88
- children << element
89
- if element.children.empty? && values.is_a?(Array)
90
- values.each { |v| element.add_text { v.to_s } }
91
- end
92
- end
93
-
94
- def inspect(indent='')
95
- kids = children.map { |c| c.inspect(indent + ' ') }.join("\n")
96
- dims = unless preferred_width.nil?
97
- "#{indent + ' '}- Dimensions: #{preferred_width}x#{preferred_height}"
98
- end
99
- [
100
- "#{indent}+ #{name} [#{self.class.name}]",
101
- dims,
102
- "#{indent + ' '}- Settings",
103
- settings.inspect(indent + ' '),
104
- "#{indent + ' '}- Children",
105
- kids
106
- ].compact.join("\n")
107
- end
108
-
109
- private
110
-
111
- def margin_width
112
- settings.margin.left + settings.margin.right
113
- end
114
-
115
- def margin_height
116
- settings.margin.top + settings.margin.bottom
117
- end
118
-
119
- def outer_border_width
120
- (settings.border.left? ? 1 : 0) + (settings.border.right? ? 1 : 0)
121
- end
122
-
123
- def outer_border_height
124
- (settings.border.top? ? 1 : 0) + (settings.border.bottom? ? 1 : 0)
125
- end
126
-
127
- def inner_border_width
128
- settings.border.inner_vert? ? 1 : 0
129
- end
130
-
131
- def inner_border_height
132
- settings.border.inner_horiz? ? 1 : 0
133
- end
134
-
135
- def padding_width
136
- settings.padding.left + settings.padding.right
137
- end
138
-
139
- def padding_height
140
- settings.padding.top + settings.padding.bottom
141
- end
142
- end
143
-
144
- class Template < ComposableElement
145
- def initialize(settings=TemplateSettings.new)
146
- super('TEMPLATE', settings)
147
- end
148
- end
149
-
150
- class BoxElement < ComposableElement
151
- attr_writer :content_width, :content_height
152
-
153
- def initialize(name, settings)
154
- super(name, BoxSettings.merge(settings))
155
- end
156
-
157
- def self.from_template(template, width, height)
158
- box = new(template.name, template.settings)
159
- template.children.each { |c| box.children << c }
160
- box.content_width = box.preferred_width = width
161
- box.content_height = box.preferred_height = height
162
- box
163
- end
164
-
165
- def content_width
166
- @content_width ||= begin
167
- child_widths = children.map(&:preferred_width)
168
- width = settings.horizontal_flow? ? child_widths.sum : (child_widths.max || 0)
169
- [width, *settings.width].max
170
- end
171
- end
172
-
173
- def preferred_width
174
- @preferred_width ||=
175
- margin_width + outer_border_width + padding_width + content_width
176
- end
177
-
178
- def content_height
179
- @content_height ||= begin
180
- child_heights = children.map(&:preferred_height)
181
- settings.vertical_flow? ? child_heights.sum : (child_heights.max || 0)
182
- end
183
- end
184
-
185
- def preferred_height
186
- @preferred_height ||=
187
- margin_height + outer_border_height + padding_height + content_height
188
- end
189
- end
190
-
191
- class GridElement < ComposableElement
192
- def initialize(name, settings)
193
- super(name, GridSettings.merge(settings))
194
- end
195
-
196
- def col_width
197
- return @col_width if @col_width
198
- @col_width = 0
199
- children.each do |child|
200
- @col_width = child.preferred_width if child.preferred_width > @col_width
201
- end
202
- @col_width
203
- end
204
-
205
- def row_height
206
- return @row_height if @row_height
207
- @row_height = 0
208
- children.each do |child|
209
- @row_height = child.preferred_height if child.preferred_height > @row_height
210
- end
211
- @row_height
212
- end
213
-
214
- def preferred_width
215
- margin_width +
216
- outer_border_width +
217
- settings.num_cols * (padding_width + col_width) +
218
- (settings.num_cols - 1) * inner_border_width
219
- end
220
-
221
- def preferred_height
222
- num_rows = (children.length / settings.num_cols).ceil
223
- margin_height +
224
- outer_border_height +
225
- num_rows * (padding_height + row_height) +
226
- (num_rows - 1) * inner_border_height
227
- end
228
- end
229
12
  end
230
13
  end
@@ -0,0 +1,8 @@
1
+ require_relative 'container'
2
+
3
+ module WhirledPeas
4
+ module Template
5
+ class GridElement < Container
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'element'
2
+
3
+ module WhirledPeas
4
+ module Template
5
+ class TextElement < Element
6
+ # List of classes that convert nicely to a string
7
+ STRINGALBE_CLASSES = [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass]
8
+ private_constant :STRINGALBE_CLASSES
9
+
10
+ def self.stringable?(value)
11
+ STRINGALBE_CLASSES.include?(value.class)
12
+ end
13
+
14
+ attr_reader :content
15
+
16
+ def content=(val)
17
+ unless self.class.stringable?(val)
18
+ raise ArgumentError, "Unsupported type for TextElement: #{val.class}"
19
+ end
20
+ @content = val.to_s
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  module WhirledPeas
2
- module UI
2
+ module Utils
3
3
  # Helper module for working with ANSI escape codes. The most useful ANSI escape codes
4
4
  # relate to text formatting.
5
5
  #
@@ -7,6 +7,9 @@ module WhirledPeas
7
7
  module Ansi
8
8
  ESC = "\033"
9
9
 
10
+ END_FORMATTING = 0
11
+ private_constant :END_FORMATTING
12
+
10
13
  # Text formatting constants
11
14
  BOLD = 1
12
15
  UNDERLINE = 4
@@ -21,8 +24,8 @@ module WhirledPeas
21
24
  CYAN = 36
22
25
  WHITE = 37
23
26
 
24
- END_FORMATTING = 0
25
- private_constant :END_FORMATTING
27
+ # Bright colors are offset by this much from their standard versions
28
+ BRIGHT_OFFSET = 60
26
29
 
27
30
  class << self
28
31
  def cursor_pos(top: 0, left: 0)
@@ -37,63 +40,10 @@ module WhirledPeas
37
40
  "#{ESC}[J"
38
41
  end
39
42
 
40
- # Format the string with the ANSI escapes codes for the given integer codes
41
- #
42
- # @param str [String] the string to format
43
- # @param codes [Array<Integer>] the integer part of the ANSI escape code (see
44
- # constants in this module for codes and meanings)
45
- def format(str, codes)
46
- if str.empty? || codes.length == 0
47
- str
48
- else
49
- start_formatting = codes.map(&method(:esc_seq)).join
50
- "#{start_formatting}#{str}#{esc_seq(END_FORMATTING)}"
51
- end
52
- end
53
-
54
43
  def clear
55
44
  esc_seq(END_FORMATTING)
56
45
  end
57
46
 
58
- # If the string has unclosed formatting, add the end formatting characters to
59
- # the end of the string
60
- def close_formatting(str)
61
- codes = str.scan(/#{ESC}\[(\d+)m/)
62
- if codes.length > 0 && codes.last[0] != END_FORMATTING.to_s
63
- "#{str}#{esc_seq(END_FORMATTING)}"
64
- else
65
- str
66
- end
67
- end
68
-
69
- # Return a substring of the input string that preservse the formatting
70
- #
71
- # @param str [String] the (possibly formatted) string
72
- # @param first_visible_character [Integer] the index of the first character to
73
- # include in the substring (ignoring all hidden formatting characters)
74
- # @param num_visible_chars [Integer] the maximum number of visible characters to
75
- # include in the substring (ignoring all hidden formatting characters)
76
- def substring(str, first_visible_character, num_visible_chars)
77
- substr = ''
78
- is_visible = true
79
- visible_index = 0
80
- substr_visible_len = 0
81
- str.chars.each do |char|
82
- in_substring = (visible_index >= first_visible_character)
83
- is_visible = false if is_visible && char == ESC
84
- visible_index += 1 if is_visible
85
- if !is_visible || in_substring
86
- substr += char
87
- substr_visible_len += 1 if is_visible
88
- end
89
- is_visible = true if !is_visible && char == 'm'
90
- break if substr_visible_len == num_visible_chars
91
- end
92
- close_formatting(substr)
93
- end
94
-
95
- private
96
-
97
47
  def esc_seq(code)
98
48
  "#{ESC}[#{code}m"
99
49
  end
@@ -0,0 +1,64 @@
1
+ require_relative 'ansi'
2
+
3
+ module WhirledPeas
4
+ module Utils
5
+ class FormattedString
6
+ def self.blank
7
+ new('')
8
+ end
9
+
10
+ def initialize(raw, formatting=nil)
11
+ @raw = raw
12
+ @formatting = [*formatting]
13
+ end
14
+
15
+ def length
16
+ raw.length
17
+ end
18
+
19
+ def [](index_or_range)
20
+ self.class.new(raw[index_or_range], foramtting)
21
+ end
22
+
23
+ def blank?
24
+ raw.empty?
25
+ end
26
+
27
+ def hash
28
+ [raw, formatting].hash
29
+ end
30
+
31
+ def ==(other)
32
+ case other
33
+ when self.class
34
+ hash == other.hash
35
+ when String
36
+ formatting.empty? && raw == other
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ def to_s
43
+ if raw.empty? || formatting.length == 0
44
+ raw
45
+ else
46
+ start_formatting = formatting.map { |code| Ansi.esc_seq(code) }.join
47
+ "#{start_formatting}#{raw}#{Ansi.clear}"
48
+ end
49
+ end
50
+
51
+ def inspect
52
+ if raw.empty? || formatting.length == 0
53
+ raw.inspect
54
+ else
55
+ "<#{formatting.join(', ')}>#{raw}<0>".inspect
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :raw, :formatting
62
+ end
63
+ end
64
+ end
@@ -1,3 +1,3 @@
1
1
  module WhirledPeas
2
- VERSION = '0.4.1'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -0,0 +1 @@
1
+ [?25lTOP LEFTBOTTOM LEFT TOP RIGHT  BOTTOM RIGHT[?25h
@@ -0,0 +1,20 @@
1
+ require 'whirled_peas'
2
+
3
+ class TemplateFactory
4
+ def build(*)
5
+ WhirledPeas.template do |composer|
6
+ composer.add_box('Container') do |composer, settings|
7
+ composer.add_box do |composer, settings|
8
+ settings.flow = :t2b
9
+ composer.add_box('TopLeft') { "TOP LEFT" }
10
+ composer.add_box('BottomLeft') { "BOTTOM LEFT" }
11
+ end
12
+ composer.add_box do |composer, settings|
13
+ settings.flow = :t2b
14
+ composer.add_box('TopRight') { " TOP RIGHT " }
15
+ composer.add_box('BottomRight') { " BOTTOM RIGHT" }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end