whirled_peas 0.1.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile +1 -0
  5. data/README.md +240 -79
  6. data/Rakefile +37 -3
  7. data/bin/debug +35 -0
  8. data/exe/whirled_peas +7 -0
  9. data/lib/whirled_peas.rb +17 -37
  10. data/lib/whirled_peas/command_line.rb +263 -0
  11. data/lib/whirled_peas/config.rb +21 -0
  12. data/lib/whirled_peas/debugger.rb +80 -0
  13. data/lib/whirled_peas/errors.rb +7 -0
  14. data/lib/whirled_peas/frame.rb +0 -8
  15. data/lib/whirled_peas/frame/consumer.rb +14 -51
  16. data/lib/whirled_peas/frame/debug_consumer.rb +30 -0
  17. data/lib/whirled_peas/frame/event_loop.rb +68 -38
  18. data/lib/whirled_peas/frame/producer.rb +37 -34
  19. data/lib/whirled_peas/graphics.rb +5 -0
  20. data/lib/whirled_peas/graphics/box_painter.rb +100 -0
  21. data/lib/whirled_peas/graphics/canvas.rb +107 -0
  22. data/lib/whirled_peas/graphics/container_coords.rb +61 -0
  23. data/lib/whirled_peas/graphics/container_dimensions.rb +65 -0
  24. data/lib/whirled_peas/graphics/container_painter.rb +116 -0
  25. data/lib/whirled_peas/graphics/debugger.rb +43 -0
  26. data/lib/whirled_peas/graphics/grid_painter.rb +56 -0
  27. data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
  28. data/lib/whirled_peas/graphics/painter.rb +24 -0
  29. data/lib/whirled_peas/graphics/renderer.rb +49 -0
  30. data/lib/whirled_peas/graphics/screen.rb +65 -0
  31. data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
  32. data/lib/whirled_peas/graphics/text_painter.rb +38 -0
  33. data/lib/whirled_peas/settings.rb +5 -0
  34. data/lib/whirled_peas/settings/bg_color.rb +22 -0
  35. data/lib/whirled_peas/settings/border.rb +101 -0
  36. data/lib/whirled_peas/settings/box_settings.rb +8 -0
  37. data/lib/whirled_peas/settings/color.rb +69 -0
  38. data/lib/whirled_peas/settings/container_settings.rb +128 -0
  39. data/lib/whirled_peas/settings/debugger.rb +87 -0
  40. data/lib/whirled_peas/settings/display_flow.rb +25 -0
  41. data/lib/whirled_peas/settings/element_settings.rb +61 -0
  42. data/lib/whirled_peas/settings/grid_settings.rb +11 -0
  43. data/lib/whirled_peas/settings/margin.rb +8 -0
  44. data/lib/whirled_peas/settings/padding.rb +8 -0
  45. data/lib/whirled_peas/settings/position.rb +15 -0
  46. data/lib/whirled_peas/settings/spacing.rb +24 -0
  47. data/lib/whirled_peas/settings/text_align.rb +19 -0
  48. data/lib/whirled_peas/settings/text_color.rb +19 -0
  49. data/lib/whirled_peas/settings/text_settings.rb +15 -0
  50. data/lib/whirled_peas/template.rb +5 -0
  51. data/lib/whirled_peas/template/box_element.rb +8 -0
  52. data/lib/whirled_peas/template/composer.rb +68 -0
  53. data/lib/whirled_peas/template/container.rb +28 -0
  54. data/lib/whirled_peas/template/debugger.rb +34 -0
  55. data/lib/whirled_peas/template/element.rb +13 -0
  56. data/lib/whirled_peas/template/grid_element.rb +8 -0
  57. data/lib/whirled_peas/template/text_element.rb +24 -0
  58. data/lib/whirled_peas/utils.rb +5 -0
  59. data/lib/whirled_peas/utils/ansi.rb +53 -0
  60. data/lib/whirled_peas/utils/formatted_string.rb +64 -0
  61. data/lib/whirled_peas/utils/title_font.rb +75 -0
  62. data/lib/whirled_peas/version.rb +1 -1
  63. data/screen_test/rendered/elements/box.frame +1 -0
  64. data/screen_test/rendered/elements/box.rb +20 -0
  65. data/screen_test/rendered/elements/grid.frame +1 -0
  66. data/screen_test/rendered/elements/grid.rb +13 -0
  67. data/screen_test/rendered/elements/screen_overflow.rb +9 -0
  68. data/screen_test/rendered/elements/text.frame +1 -0
  69. data/screen_test/rendered/elements/text.rb +9 -0
  70. data/screen_test/rendered/elements/text_multiline.frame +1 -0
  71. data/screen_test/rendered/elements/text_multiline.rb +9 -0
  72. data/screen_test/rendered/settings/align/box.frame +1 -0
  73. data/screen_test/rendered/settings/align/box.rb +20 -0
  74. data/screen_test/rendered/settings/align/children_center.frame +1 -0
  75. data/screen_test/rendered/settings/align/children_center.rb +13 -0
  76. data/screen_test/rendered/settings/align/children_left.frame +1 -0
  77. data/screen_test/rendered/settings/align/children_left.rb +13 -0
  78. data/screen_test/rendered/settings/align/children_right.frame +1 -0
  79. data/screen_test/rendered/settings/align/children_right.rb +13 -0
  80. data/screen_test/rendered/settings/align/grid.frame +1 -0
  81. data/screen_test/rendered/settings/align/grid.rb +20 -0
  82. data/screen_test/rendered/settings/ansi/bold.frame +1 -0
  83. data/screen_test/rendered/settings/ansi/bold.rb +15 -0
  84. data/screen_test/rendered/settings/ansi/color.frame +1 -0
  85. data/screen_test/rendered/settings/ansi/color.rb +37 -0
  86. data/screen_test/rendered/settings/ansi/underline.frame +1 -0
  87. data/screen_test/rendered/settings/ansi/underline.rb +15 -0
  88. data/screen_test/rendered/settings/border.frame +1 -0
  89. data/screen_test/rendered/settings/border.rb +13 -0
  90. data/screen_test/rendered/settings/flow/l2r.frame +1 -0
  91. data/screen_test/rendered/settings/flow/l2r.rb +24 -0
  92. data/screen_test/rendered/settings/flow/t2b.frame +1 -0
  93. data/screen_test/rendered/settings/flow/t2b.rb +24 -0
  94. data/screen_test/rendered/settings/height/box.frame +1 -0
  95. data/screen_test/rendered/settings/height/box.rb +13 -0
  96. data/screen_test/rendered/settings/height/grid.frame +1 -0
  97. data/screen_test/rendered/settings/height/grid.rb +14 -0
  98. data/screen_test/rendered/settings/height/overflow_box.frame +1 -0
  99. data/screen_test/rendered/settings/height/overflow_box.rb +13 -0
  100. data/screen_test/rendered/settings/height/overflow_box_l2r.frame +1 -0
  101. data/screen_test/rendered/settings/height/overflow_box_l2r.rb +15 -0
  102. data/screen_test/rendered/settings/height/overflow_box_t2b.frame +1 -0
  103. data/screen_test/rendered/settings/height/overflow_box_t2b.rb +14 -0
  104. data/screen_test/rendered/settings/height/overflow_grid.frame +1 -0
  105. data/screen_test/rendered/settings/height/overflow_grid.rb +16 -0
  106. data/screen_test/rendered/settings/margin.frame +1 -0
  107. data/screen_test/rendered/settings/margin.rb +14 -0
  108. data/screen_test/rendered/settings/padding.frame +1 -0
  109. data/screen_test/rendered/settings/padding.rb +11 -0
  110. data/screen_test/rendered/settings/position/box_left.frame +1 -0
  111. data/screen_test/rendered/settings/position/box_left.rb +17 -0
  112. data/screen_test/rendered/settings/position/box_left_negative.frame +1 -0
  113. data/screen_test/rendered/settings/position/box_left_negative.rb +17 -0
  114. data/screen_test/rendered/settings/position/box_top.frame +1 -0
  115. data/screen_test/rendered/settings/position/box_top.rb +17 -0
  116. data/screen_test/rendered/settings/position/box_top_negative.frame +1 -0
  117. data/screen_test/rendered/settings/position/box_top_negative.rb +17 -0
  118. data/screen_test/rendered/settings/position/grid_left.frame +1 -0
  119. data/screen_test/rendered/settings/position/grid_left.rb +18 -0
  120. data/screen_test/rendered/settings/position/grid_left_negative.frame +1 -0
  121. data/screen_test/rendered/settings/position/grid_left_negative.rb +18 -0
  122. data/screen_test/rendered/settings/position/grid_top.frame +1 -0
  123. data/screen_test/rendered/settings/position/grid_top.rb +18 -0
  124. data/screen_test/rendered/settings/position/grid_top_negative.frame +1 -0
  125. data/screen_test/rendered/settings/position/grid_top_negative.rb +18 -0
  126. data/screen_test/rendered/settings/title_font.frame +1 -0
  127. data/screen_test/rendered/settings/title_font.rb +12 -0
  128. data/screen_test/rendered/settings/width/box.frame +1 -0
  129. data/screen_test/rendered/settings/width/box.rb +13 -0
  130. data/screen_test/rendered/settings/width/grid.frame +1 -0
  131. data/screen_test/rendered/settings/width/grid.rb +14 -0
  132. data/screen_test/rendered/settings/width/overflow_box.frame +1 -0
  133. data/screen_test/rendered/settings/width/overflow_box.rb +11 -0
  134. data/screen_test/rendered/settings/width/overflow_box_l2r.frame +1 -0
  135. data/screen_test/rendered/settings/width/overflow_box_l2r.rb +14 -0
  136. data/screen_test/rendered/settings/width/overflow_box_t2b.frame +1 -0
  137. data/screen_test/rendered/settings/width/overflow_box_t2b.rb +15 -0
  138. data/screen_test/rendered/settings/width/overflow_grid.frame +1 -0
  139. data/screen_test/rendered/settings/width/overflow_grid.rb +14 -0
  140. data/screen_test/screen_tester.rb +191 -0
  141. data/whirled_peas.gemspec +4 -2
  142. metadata +136 -20
  143. data/lib/whirled_peas/ui.rb +0 -7
  144. data/lib/whirled_peas/ui/ansi.rb +0 -154
  145. data/lib/whirled_peas/ui/canvas.rb +0 -35
  146. data/lib/whirled_peas/ui/element.rb +0 -199
  147. data/lib/whirled_peas/ui/painter.rb +0 -283
  148. data/lib/whirled_peas/ui/screen.rb +0 -62
  149. data/lib/whirled_peas/ui/settings.rb +0 -512
  150. data/lib/whirled_peas/ui/stroke.rb +0 -29
  151. data/sandbox/auto.rb +0 -13
  152. data/sandbox/box.rb +0 -19
  153. data/sandbox/grid.rb +0 -13
  154. data/sandbox/sandbox.rb +0 -17
  155. data/sandbox/text.rb +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6608e1e6c9505eefea5409bcdc0f8af117b5bcf390564d163315f05b9fe73323
4
- data.tar.gz: 713b7ff1c83c462b137e1a9203282ad74f8dc71e660237da35b40d472ae1d486
3
+ metadata.gz: 1b374c9d4250b281d3f87284f446e82c01ece7ff2fe196687be50502b2e42960
4
+ data.tar.gz: 0ebd7b96a154277121767eb8c33d63b831db37770ff0172b76f28fcf89c5dec9
5
5
  SHA512:
6
- metadata.gz: 81d5ae8c86907fa5a32025be54fd4649d1c75c93726d0f581f0f63cd7182f8381285451cef1c2350b223c378ba98c00682b2a96ac65b31cabc05b5871cbaf1fc
7
- data.tar.gz: 52cdda8685d24cd1ad89c5a2acc70b7fb77c22cf6c21cb3d3eb20dce5df2660bc2e9d285e04fbb13018189f9b2f9700203e3915c13f678ecb4d92f2ce173bfab
6
+ metadata.gz: f6dcc7dc00852a343f55056646db886903bc5735a21bd9af71a885e28ff70aa85bb3f26ff4f97c23dc4292fa401c1c624284c347b17fb957fb8e0069301b7ef9
7
+ data.tar.gz: 1f0ae0e07baf45703dff90396be7b6c705f17f8c9cb4fcd4e0b4b433513efc94dd8c3a056f50f1d3f47f7e3179c308d36264ef4e4be0c179815e650a6136a11a
@@ -3,4 +3,6 @@ language: ruby
3
3
  cache: bundler
4
4
  rvm:
5
5
  - 2.6.6
6
+ - 2.7.2
7
+ - 3.0.0
6
8
  before_install: gem install bundler -v 2.1.4
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.5.0 - 2021-01-25
4
+
5
+ BREAKING: there was a significant amount of structural refactoring in this release. While that aspect did not add or remove any features, it fixed several layout bugs (some known and some unknown), so most template with moderate complexity will now be laid out slightly differently.
6
+
7
+ - [3ddfede](https://github.com/tcollier/whirled_peas/tree/3ddfedee4ab2fadeecbe82c7c9caf25c2988f095): Allow relative positioning of containers
8
+ - [507e77c](https://github.com/tcollier/whirled_peas/tree/507e77c551bcb9cc832d5c24e2f24eb1afe4eddc): Add height attribute for container settings
9
+ - [af8ef19](https://github.com/tcollier/whirled_peas/tree/af8ef1950edebcd23a57a04982b22a56296ee09b): Add automated screen testing
10
+
11
+ ## v0.4.1 - 2021-01-22
12
+
13
+ - [0f7aa6c](https://github.com/tcollier/whirled_peas/tree/0f7aa6ccc07323230dd602cb43e0341de5a69ad8): Allow relative path for config files
14
+
15
+ ## v0.4.0 - 2021-01-22
16
+
17
+ - [7fd6712](https://github.com/tcollier/whirled_peas/tree/7fd6712818c94cdbfd81828277ca67c705e01793): BREAKING: replace `WhirledPeas.start` with command line executable
18
+ - [2535342](https://github.com/tcollier/whirled_peas/tree/25353424f1ab4af4880f44eb7ddd28afefbbb9b2): Add support for loading screen
19
+ - [7388fc2](https://github.com/tcollier/whirled_peas/tree/7388fc2eacdc8045b725311c11d650d6b8654be8): Add support for title fonts
20
+ - [b345155](https://github.com/tcollier/whirled_peas/tree/b345155b1c212cabe73f9a2562ac8dbbedbbb6df): Add command to list title fonts to executable
21
+ - [d3a8324](https://github.com/tcollier/whirled_peas/tree/d3a832496c36985993217ff11b6d83dd4697c4ed): Add commands for debugging application to executable
22
+
23
+ ## v0.3.0 - 2021-01-21
24
+
25
+ - [617f802](https://github.com/tcollier/whirled_peas/tree/617f8027d6688a2ec81a3e594e529c94485cee85): BREAKING: send frames directly to EventLoop (`Producer#send` renamed to `Producer#send_frame`)
26
+
27
+ ## v0.2.0 - 2021-01-20
28
+
29
+ - [73eb326](https://github.com/tcollier/whirled_peas/tree/73eb326426f9814e91e3bc7a60dfd87be3d69f7e): Convert "primitive" data types to strings
30
+ - [f28b69d](https://github.com/tcollier/whirled_peas/tree/f28b69df8b6cfc973da2ebc0b8da29b278f23433): Give elements names
31
+ - [1ae1c929](https://github.com/tcollier/whirled_peas/tree/1ae1c929429c2f8520054d33a064c2b6d71955fe): Consistently format exceptions in logs
32
+ - [4c2114f](https://github.com/tcollier/whirled_peas/tree/4c2114fd360fd98c65e6e32f905a377f09b919ee): Fix bug with negative sleep times
33
+ - [627bf12](https://github.com/tcollier/whirled_peas/tree/627bf126dd7f9c845f65105e0826d14a35a0a953): Don't reraise pipe error
34
+
3
35
  ## v0.1.1 - 2021-01-20
4
36
 
5
37
  - [3852c8c](https://github.com/tcollier/whirled_peas/tree/3852c8c700c2e8fb92e65bbca1c99be74304c6d0): Improve error handling
data/Gemfile CHANGED
@@ -6,3 +6,4 @@ gemspec
6
6
  gem 'rake', '~> 12.0'
7
7
  gem 'rspec', '~> 3.0'
8
8
  gem 'pry-byebug'
9
+ gem 'tty-cursor', '~> 0.7'
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.com/tcollier/whirled_peas.svg?branch=main)](https://travis-ci.com/tcollier/whirled_peas)
2
+
1
3
  # WhirledPeas
2
4
 
3
5
  Visualize your code's execution with Whirled Peas!
@@ -20,15 +22,24 @@ Or install it yourself as:
20
22
 
21
23
  ## Usage
22
24
 
25
+ A Whirled Peas application consists of the following pieces
26
+
27
+ - The driver (required) - the code that is to be visualized, it emits lightweight frame events through a producer
28
+ - The main template factory (required) - builds templates to convert frame events from the driver into terminal graphics
29
+ - A loading screen template factory (optional) - builds templates to display while content is loading
30
+
31
+ These pieces are configured as following
32
+
23
33
  ```ruby
34
+ # visualize.rb
24
35
  require 'whirled_peas'
25
36
 
26
37
  class TemplateFactory
27
38
  def build(frame, args)
28
- WhirledPeas.template do |body|
29
- body.add_box do |_, settings|
39
+ WhirledPeas.template do |composer|
40
+ composer.add_box('Title') do |_, settings|
30
41
  settings.underline = true
31
- "Hello #{args['name']}"
42
+ "Hello #{args[:name]}"
32
43
  end
33
44
  # ...
34
45
  end
@@ -37,18 +48,44 @@ end
37
48
 
38
49
  class Driver
39
50
  def start(producer)
40
- producer.send('starting', args: { 'name' => 'World' })
51
+ producer.send_frame('starting', args: { name: 'World' })
41
52
  # ...
42
53
  end
43
54
  end
44
55
 
45
- WhirledPeas.start(Driver.new, TemplateFactory.new)
56
+ WhirledPeas.configure do |config|
57
+ config.driver = Driver.new
58
+ config.template_factory = TemplateFactory.new
59
+ end
60
+ ```
61
+
62
+ Then the visualizer is started on the command line with
63
+
46
64
  ```
65
+ $ whirled_peas start visualize.rb
66
+ ```
67
+
68
+ The optional loading screen can be configured like
47
69
 
48
- A Whirled Peas application consists of two pieces
70
+ ```ruby
71
+ class LoadingTemplateFactory
72
+ def build
73
+ WhirledPeas.template do |composer|
74
+ composer.add_box('Loading') do |_, settings|
75
+ settings.set_margin(top: 15)
76
+ settings.align = :center
77
+ settings.full_border(color: :blue, style: :double)
78
+ "Loading..."
79
+ end
80
+ end
81
+ end
82
+ end
49
83
 
50
- 1. The driver, which emits lightweight frame events
51
- 1. The template factory, which builds templates to convert frame events from the driver into terminal graphics
84
+ WhirledPeas.configure do |config|
85
+ # ...
86
+ config.loading_template_factory = LoadingTemplateFactory.new
87
+ end
88
+ ```
52
89
 
53
90
  ### Driver
54
91
 
@@ -70,13 +107,14 @@ The producer provides a single method
70
107
  #
71
108
  # @param name [String] application defined name for the frame. The template factory will be provided this name
72
109
  # @param duration [Number] time in seconds this frame should be displayed for (defaults to 1 frame)
73
- # @param args [Hash] key value pairs to send as arguments to the template factory, these values will be
74
- # serialized/deserialized
75
- def send(name, duration:, args:)
110
+ # @param args [Hash<Symbol, Object>] key value pairs to send as arguments to the template factory
111
+ def send_frame(name, duration:, args:)
76
112
  # implementation
77
113
  end
78
114
  ```
79
115
 
116
+ **IMPORTANT**: the keys in the `args` hash must be symbols!
117
+
80
118
  #### Example
81
119
 
82
120
  Simple application that loads a set of numbers and looks for a pair that adds up to 1,000
@@ -85,52 +123,56 @@ Simple application that loads a set of numbers and looks for a pair that adds up
85
123
  class Driver
86
124
  def start(producer)
87
125
  numbers = File.readlines('/path/to/numbers.txt').map(&:to_i)
88
- producer.send('load-numbers', duration: 3, args: { numbers: numbers })
126
+ producer.send_frame('load-numbers', duration: 3, args: { numbers: numbers })
89
127
  numbers.sort!
90
- producer.send('sort-numbers', duration: 3, args: { numbers: numbers })
128
+ producer.send_frame('sort-numbers', duration: 3, args: { numbers: numbers })
91
129
  low = 0
92
130
  high = numbers.length - 1
93
131
  while low < high
94
132
  sum = numbers[low] + numbers[high]
95
133
  if sum == 1000
96
- producer.send('found-pair', duration: 5, args: { low: low, high: high, sum: sum })
134
+ producer.send_frame('found-pair', duration: 5, args: { low: low, high: high, sum: sum })
97
135
  return
98
136
  elsif sum < 1000
99
- producer.send('too-low', args: { low: low, high: high, sum: sum })
137
+ producer.send_frame('too-low', args: { low: low, high: high, sum: sum })
100
138
  low += 1
101
139
  else
102
- producer.send('too-high', args: { low: low, high: high, sum: sum })
140
+ producer.send_frame('too-high', args: { low: low, high: high, sum: sum })
103
141
  high -= 1
104
142
  end
105
143
  end
106
- producer.send('no-solution', duration: 5)
144
+ producer.send_frame('no-solution', duration: 5)
107
145
  end
108
146
  end
109
147
  ```
110
148
 
111
149
  ### Template Factory
112
150
 
113
- To render the frame events sent by the driver, the application requires a template factory. This factory will be called for each frame event, with the frame name and the arguments supplied by the driver. A template factory can be a simple ruby class and thus can maintain state. Whirled Peas provides a few basic building blocks to make simple, yet elegant terminal-based UIs.
151
+ To render the frame events sent by the driver, the application requires a template factory. This factory will be called for each frame event, with the frame name and the arguments supplied by the driver. A template factory can be an instance of ruby class and thus can maintain state. Whirled Peas provides a few basic building blocks to make simple, yet elegant terminal-based UIs.
152
+
153
+ #### Loading Template Factory
154
+
155
+ `WhirledPeas.configure` takes an optional template factory to build a loading screen. This instance must implement `#build` (taking no arguments). The template returned by that method will be painted while the event loop is waiting for frames. The factory method will be called once per refresh cycle, so it's possible to implement animation.
114
156
 
115
157
  #### Building Blocks
116
158
 
117
- A template is created with `WhirledPeas.template`, which yields a `Template` object and `TemplateSettings`. This template object is a `ComposableElement`, which allows for attaching child elements and setting layout options. `GridElement` and `BoxElement` are two other composable elements and `TextElement` is a simple element that can hold a text/number value and has layout options, but cannot have any child elements.
159
+ A template is created with `WhirledPeas.template`, which yields a `Composer` object for a `BoxElement` and `BoxSettings`. The composer allows for attaching child elements and the settings for setting layout options.
118
160
 
119
- A `ComposableElement` provides the following methods to add child elements
161
+ A `Composer` provides the following methods to add child elements, each of these takes an optional string argument that is set as the name of the element (which can be useful when debugging).
120
162
 
121
- - `add_box` - yields a `ComposableElement` and a `BoxSettings`, which will be added to the parent's children
122
- - `add_grid` - yields a `ComposableElement` and a `GridSettings`, which will be added to the parent's children
163
+ - `add_box` - yields a `Composer` and a `BoxSettings`, which will be added to the parent's children
164
+ - `add_grid` - yields a `Composer` and a `GridSettings`, which will be added to the parent's children
123
165
  - `add_text` - yields `nil` and a `TextSettings`, which will be added to the parent's children
124
166
 
125
167
  E.g.
126
168
 
127
169
  ```ruby
128
- WhirledPeas.template do |template, template_settings|
129
- template_settings.bg_color = :blue
130
- template.add_grid do |grid, grid_settings|
131
- grid_settings.num_cols = 10
170
+ WhirledPeas.template do |composer, settings|
171
+ settings.bg_color = :blue
172
+ composer.add_grid do |composer, settings|
173
+ settings.num_cols = 10
132
174
  100.times do |i|
133
- grid.add_text { i.to_s }
175
+ composer.add_text { i }
134
176
  end
135
177
  end
136
178
  end
@@ -139,79 +181,93 @@ end
139
181
  The above template can also be broken down into more manageable methods, e.g.
140
182
 
141
183
  ```ruby
142
- def number_grid(grid, settings)
184
+ def number_grid(_composer, settings)
143
185
  settings.num_cols = 10
144
- 100.times do |i|
145
- grid.add_text { i.to_s }
146
- end
186
+ 100.times.map(&:itself)
147
187
  end
148
188
 
149
- WhirledPeas.template do |template, settings|
189
+ WhirledPeas.template do |composer, settings|
150
190
  settings.bg_color = :blue
151
- template.add_grid(&method(:number_grid))
191
+ composer.add_grid(&method(:number_grid))
152
192
  end
153
193
  ```
154
194
 
155
195
  Additionally, if no child element is explicitly added to a `GridElement`, but the block returns an array of strings or numbers, those will be converted to `TextElements` and added as children to the `GridElement`. For example, these are identical ways to create a grid of strings
156
196
 
157
197
  ```ruby
158
- template.add_grid do |g|
198
+ template.add_grid do |composer|
159
199
  100.times do |i|
160
- g.add_text { i.to_s }
200
+ composer.add_text { i }
161
201
  end
162
202
  end
163
203
 
164
- template.add_grid do |g|
165
- 100.times.map(&:to_s)
204
+ template.add_grid do
205
+ 100.times.map(&:itself)
166
206
  end
167
207
  ```
168
208
 
169
209
  Similarly, if no child element is explicilty added to a `BoxElement`, but the block returns a string or number, that value will be converted to a `TextElement` and added as a child. For example, these are identical ways to create a box with string content
170
210
 
171
211
  ```ruby
172
- template.add_box do |b|
173
- b.add_text { "Hello!" }
212
+ template.add_box do |composer|
213
+ composer.add_text { "Hello!" }
174
214
  end
175
215
 
176
- template.add_box do |b|
216
+ template.add_box do
177
217
  "Hello!"
178
218
  end
179
219
  ```
180
220
 
181
221
  #### Settings
182
222
 
183
- Each element type has an associated settings type, which provide a custom set of options to format the output. Parent settings may be merged into child settings (assuming the child supports those settings)
223
+ Each element type has an associated settings type, which provide a custom set of options to format the output. Child settings will inherit from the parent, where applicable
184
224
  The available settigs are
185
225
 
186
- | Setting | Description | Default | Availability | Merged? |
187
- | ------------- | ------------------------------------------------------------------ | ------- | --------------------------------- | ------- |
188
- | `align` | Justifies the text (allowed values: `:left`, `:center`, `:right`) | `:left` | `Box`, `Grid`, `Text` | Yes |
189
- | `auto_margin` | Evenly distribute side margin (overrides left/right in `margin`) | `false` | `Box`, `Grid` | Yes |
190
- | `bg_color` | Background color (see [Colors](#colors)) | | `Box`, `Grid`, `Template`, `Text` | Yes |
191
- | `bold` | `true` makes the font bold | `false` | `Box`, `Grid`, `Template`, `Text` | Yes |
192
- | `border` | Set the border for the lements | none | `Box`, `Grid`, | Yes |
193
- | `color` | Foreground text color (see [Colors](#colors)) | | `Box`, `Grid`, `Template`, `Text` | Yes |
194
- | `flow` | Flow to display child elements (see [Display Flow](#display-flow)) | `:l2r` | `Box` | Yes |
195
- | `margin` | Set the (left, top, right, bottom) margin of the element | `0` | `Box`, `Grid` | Yes |
196
- | `padding` | Set the (left, top, right, bottom) padding of the element | `0` | `Box`, `Grid` | Yes |
197
- | `transpose` | Display grid elements top-to-bottom, then left-to-right | `false` | `Grid` | No |
198
- | `underline` | `true` underlines the font | `false` | `Box`, `Grid`, `Template`, `Text` | Yes |
199
- | `width` | Override the calculated with of an element | | `Box`, `Grid`, `Text` | No |
200
-
201
- ##### Margin and Padding
202
-
203
- Margin and padding settings allow for setting the spacing on each of the 4 sides of the element independently. The set these values, use
226
+ | Setting | Description | Default | Availability | Inherited |
227
+ | ------------ | ------------------------------------------------------------------------------- | ------- | --------------------- | -------------------- |
228
+ | `align` | Justifies the content (allowed values: `:left`, `:center`, `:right`) | `:left` | `Box`, `Grid` | Yes |
229
+ | `bg_color` | Background color (see [Colors](#colors)) | | `Box`, `Grid`, `Text` | Yes |
230
+ | `bold` | `true` makes the font bold | `false` | `Box`, `Grid`, `Text` | Yes |
231
+ | `border` | Set the border for the lements | none | `Box`, `Grid`, | Only style and color |
232
+ | `color` | Foreground text color (see [Colors](#colors)) | | `Box`, `Grid`, `Text` | Yes |
233
+ | `flow` | Flow to display child elements (see [Display Flow](#display-flow)) | `:l2r` | `Box`, `Grid` | Yes |
234
+ | `height` | Override the calculated height of an element's content area | | `Box`, `Grid` | No |
235
+ | `margin` | Set the (left, top, right, bottom) margin of the element | `0` | `Box`, `Grid` | No |
236
+ | `num_cols` | Number of columns in the grid (must be set!) | | `Grid` | No |
237
+ | `padding` | Set the (left, top, right, bottom) padding of the element | `0` | `Box`, `Grid` | No |
238
+ | `position` | Set the (left, top) position of the element relative to parent content area | `0` | `Box`, `Grid` | No |
239
+ | `title_font` | Font used for "large" text (see [Large Text](#large-text), ignores `underline`) | | `Text` | No |
240
+ | `underline` | `true` underlines the font | `false` | `Box`, `Grid`, `Text` | Yes |
241
+ | `width` | Override the calculated width of an element's content area | | `Box`, `Grid` | No |
242
+
243
+ ##### Position
244
+
245
+ Position settings dictate the relative position from where the painter would have preferred to place the container. Negative numbers move the container left/up and positive numbers move it right/down. To set these values, use
246
+
247
+ - `set_position(left:, top:)`
248
+
249
+ ##### Margin
250
+
251
+ Margin settings dictate the spacing on the outside (i.e. outside of the border) of each of the 4 sides of the container independently. To set these values, use
204
252
 
205
253
  - `set_margin(left:, top:, right:, bottom:)`
254
+
255
+ Note: values cannot be negative
256
+
257
+ ##### Padding
258
+
259
+ Padding settings dictate the spacing on the inside (i.e. inside of the border) of each of the 4 sides of the container independently. To set these values, use
260
+
206
261
  - `set_padding(left:, top:, right:, bottom:)`
207
262
 
208
- Any argument value not provided will result in that value being 0.
263
+ Note: values cannot be negative
209
264
 
210
265
  ##### Border
211
266
 
212
267
  The border settings consist of 6 boolean values (border are either width 1 or not shown), the 4 obvious values (`left`, `top`, `right`, and `bottom`) along with 2 other values for inner borders (`inner_horiz` and `inner_vert`) in a grid. A border also has a foreground color (defaults to `:white`) and a style. The background color is determined by the `bg_color` of the element. Border values can be set with
213
268
 
214
269
  - `set_border(left:, top:, right:, bottom:, inner_horiz:, inner_vert:, color:, style:)`
270
+ - `full_border(style:, color:)`
215
271
 
216
272
  Available border styles are
217
273
 
@@ -302,14 +358,40 @@ Many of these also have a "bright" option:
302
358
  - `:bright_red`
303
359
  - `:bright_yellow`
304
360
 
361
+ ##### Large Text
362
+
363
+ The `title_font` setting for `TextElement`s converts the standard terminal font into a large block font. The available fonts vary from system to system. Every system will have a `:default` font available, this font could look like
364
+
365
+ ```
366
+ ██████╗ ███████╗███████╗ █████╗ ██╗ ██╗██╗ ████████╗
367
+ ██╔══██╗██╔════╝██╔════╝██╔══██╗██║ ██║██║ ╚══██╔══╝
368
+ ██║ ██║█████╗ █████╗ ███████║██║ ██║██║ ██║
369
+ ██║ ██║██╔══╝ ██╔══╝ ██╔══██║██║ ██║██║ ██║
370
+ ██████╔╝███████╗██║ ██║ ██║╚██████╔╝███████╗ ██║
371
+ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝
372
+ ```
373
+
374
+ To print out a list of all available fonts as well as sample text in that font, run
375
+
376
+ ```
377
+ $ whirled_peas title_fonts
378
+ ```
379
+
380
+ Note: when using a title font with WhirledPeas for the first time on a system, the gem loads all fonts to check which ones are available. This can be a slow process and may cause a noticeable delay when running a visualization. Running the command above will cache the results and thus when a WhirledPeas visualization is run, there will be no lag from loading fonts.
381
+
305
382
  ### Example
306
383
 
307
384
  ```ruby
308
385
  class TemplateFactory
309
386
  def build(frame, args)
310
387
  set_state(frame, args)
311
- WhirledPeas.template do |t|
312
- t.add_box(&method(:body))
388
+ WhirledPeas.template do |composer, settings|
389
+ settings.flow = :l2r
390
+ settings.align = :center
391
+
392
+ composer.add_box('Title', &method(:title))
393
+ composer.add_box('Sum', &method(:sum))
394
+ composer.add_grid('NumberGrid', &method(:number_grid))
313
395
  end
314
396
  end
315
397
 
@@ -317,41 +399,105 @@ class TemplateFactory
317
399
 
318
400
  def set_state(frame, args)
319
401
  @frame = frame
320
- @numbers = args.key?('numbers') ? args['numbers'] || []
321
- @sum = args['sum'] if args.key?('sum')
322
- @low = args['low'] if args.key?('low')
323
- @high = args['high'] if args.key?('high')
402
+ @numbers = args.key?(:numbers) ? args[:numbers] || []
403
+ @sum = args[:sum] if args.key?(:sum)
404
+ @low = args[:low] if args.key?(:low)
405
+ @high = args[:high] if args.key?(:high)
324
406
  end
325
407
 
326
- def title(_elem, settings)
408
+ def title(_composer, settings)
327
409
  settings.underline = true
328
410
  "Pair Finder"
329
411
  end
330
412
 
331
- def sum(_elem, settings)
413
+ def sum(_composer, settings)
332
414
  settings.color = @frame == 'found-pair' ? :green : :red
333
415
  @sum ? "Sum: #{@sum}" : 'N/A'
334
416
  end
335
417
 
336
- def number_grid(elem, settings)
418
+ def number_grid(composer, settings)
337
419
  settings.full_border
338
420
  @numbers.each.with_index do |num, index|
339
- g.add_text do |_, settings|
421
+ composer.add_text do |_, settings|
340
422
  settings.bg_color = (@low == index || @high == index) ? :cyan : :white
341
- num.to_s
423
+ num
342
424
  end
343
425
  end
344
426
  end
427
+ end
428
+ ```
345
429
 
346
- def body(elem, settings)
347
- settings.flow = :l2r
348
- settings.auto_margin = true
430
+ ### Debugging
349
431
 
350
- elem.add_box(&method(:title))
351
- elem.add_box(&method(:sum))
352
- elem.add_grid(&method(:number_grid))
353
- end
354
- end
432
+ The `whirled_peas` executable provides some commands that are helpful for debugging.
433
+
434
+ #### list_frames
435
+
436
+ List the frames sent by the driver
437
+
438
+ ```
439
+ $ whirled_peas <config file> list_frames
440
+ Frame 'start' displayed for 5 second(s)
441
+ Frame 'move' displayed for 1 frame ({:direction=>'N'})
442
+ ...
443
+ EOF frame detected
444
+ ```
445
+
446
+ #### play_frame
447
+
448
+ Displays a single frame for several seconds
449
+
450
+ ```
451
+ $ whirled_peas <config file> play_frame move '{"direction":"N"}'
452
+ ```
453
+
454
+ Adding the `--template` flag will result in printing out debug information for the template, e.g.
455
+
456
+ ```
457
+ $ whirled_peas <config file> play_frame move '{"direction":"N"}' --template
458
+ + TEMPLATE [WhirledPeas::Template::BoxElement]
459
+ - Settings
460
+ WhirledPeas::Settings::BoxSettings
461
+ <default>
462
+ - Children
463
+ + TitleContainer [WhirledPeas::Template::BoxElement]
464
+ ...
465
+ ```
466
+
467
+ Adding the `--painter` flag will result in printing out debug information the painter (the rendered template), e.g.
468
+
469
+ ```
470
+ $ whirled_peas <config file> play_frame move '{"direction":"N"}' --painter
471
+ + TEMPLATE [WhirledPeas::Graphics::BoxPainter]
472
+ - Settings
473
+ WhirledPeas::Settings::BoxSettings
474
+ <default>
475
+ - Dimensions
476
+ - Canvas:
477
+ - Children
478
+ + TitleContainer [WhirledPeas::Graphics::BoxPainter]
479
+ ...
480
+ ```
481
+
482
+ #### loading
483
+
484
+ Displays the configured loading screen for several seconds
485
+
486
+ ```
487
+ $ whirled_peas <config file> loading
488
+ ```
489
+
490
+ Adding the `--debug` flag will result in just printing out the loading template's debug information, e.g.
491
+
492
+ ```
493
+ $ whirled_peas <config file> loading --debug
494
+ + TEMPLATE [WhirledPeas::UI::Template]
495
+ - Settings
496
+ WhirledPeas::UI::TemplateSettings
497
+ <default>
498
+ - Children
499
+ + TitleContainer [WhirledPeas::UI::BoxElement]
500
+ ...
355
501
  ```
356
502
 
357
503
  ## Development
@@ -360,6 +506,21 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
360
506
 
361
507
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
362
508
 
509
+ ### Testing
510
+
511
+ In addition to standard RSpec tests, WhirledPeas has custom tests for rendered templates. These files live in `screen_test/rendered`. Each ruby file is expected to define a class named `TemplateFactory` that responds to `#build(name, args)` returning a template (the standard template factory role). Each file should also be accompanied by a `.frame` file with the same base name. This file will contain the output of the rendered screen and is considered the correct output when running tests.
512
+
513
+ Note: viewing `.frame` files with `cat` works better than most other text editors.
514
+
515
+ The following rake tasks are provided to interact with the screen tests
516
+
517
+ - `screen_test` runs all screen tests in the `screen_test/rendered` directory
518
+ - `screen_test:debug[path/to/file.rb]` print the rendered template debug tree
519
+ - `screen_test:run[path/to/file.rb]` runs a single screen test
520
+ - `screen_test:save[path/to/file.rb]` saves the output generated by the template in the `.frame` file, overwriting any existing file
521
+ - `screen_test:view[path/to/file.rb]` views the output generated by the template
522
+ - `screen_test:update_all[path/to/file.rb]` interactively step through each pending or failed screen test to compare/set the expected output
523
+
363
524
  ## Contributing
364
525
 
365
526
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/whirled_peas. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/whirled_peas/blob/master/CODE_OF_CONDUCT.md).