whirled_peas 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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