reight 0.1.7 β†’ 0.1.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f71e95252b70e9d2cf1eb2ec6022d58912ccb4409a583b30f2bb9a63926d6216
4
- data.tar.gz: b3abfeb44477f44f79eaea1323f47eeef1e41d9bdcff6bf8c67991a4362d7055
3
+ metadata.gz: f33c4762be06696447cc2975a6363c5865325115c438d6d2ba62bae45f0c1362
4
+ data.tar.gz: 785b2dc2beb430356875301d0ca8a4423bcea7327789055878e76ea5f0a13c33
5
5
  SHA512:
6
- metadata.gz: 254f31c3435558aac8e57f65346a4f4e5f72eef0bf6c2a70e6cba20c248111c5b7ea8896cf6d37ebfce7c40ec85f0b231c45f2a90dc7ac8c2bdd83b9586e36cf
7
- data.tar.gz: 4cbcedca74269acc8f29ee80826a0691fff58b4c0c201c685f009ea09a6a2db76f78e5c17ba2f9b953c1f2ca21f3dc5b805d909ce8e209787d9a246425fd429d
6
+ metadata.gz: 825762a3025e5270621ad31e0f85994509adc2c9e504e4ff820bcb22bf453b263813ebc8c0a29574489943ace7e30c8d1384e7d2b1ac33ed7a3fa3cae2e6b4ca
7
+ data.tar.gz: 12bb191d3da0b11a72692157fd9d68c4598042ddb0ea8c5d36261d2fd8195765719a5fc4f4423996f8e16dd4bc923b213954e7bf839b9d7e23a643d96659f0d6
@@ -0,0 +1,12 @@
1
+ ## Pull Requests Not Accepted 🚫
2
+
3
+ Thank you for your interest in contributing!
4
+ However, this repository does not accept pull requests directly.
5
+
6
+ ### Where to Contribute?
7
+
8
+ Please submit your changes to the [xord/all](https://github.com/xord/all) monorepo, which serves as the primary repository for all our main libraries.
9
+
10
+ For more details, please refer to our [contribution guidelines](../CONTRIBUTING.md).
11
+
12
+ Thanks for your understanding! πŸ™Œ
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,7 @@
1
+ # Contribution Guide
2
+
3
+ Thank you for your interest in contributing!
4
+ However, this repository does not accept pull requests.
5
+ Instead, please submit your changes to the [xord/all](https://github.com/xord/all) monorepo, which serves as the primary repository for all our main libraries.
6
+
7
+ For any questions, feel free to open an issue.
data/ChangeLog.md CHANGED
@@ -1,6 +1,30 @@
1
1
  # reight ChangeLog
2
2
 
3
3
 
4
+ ## [v0.1.8] - 2025-03-24
5
+
6
+ - Add '--edit' command-line option to enable edit mode
7
+ - Add Text#editable?
8
+ - Add SpriteEditor::chips_index
9
+ - Add frame_changed and selection_changed to Sprite::Canvas
10
+ - Add Index control for maps
11
+ - Add PULL_REQUEST_TEMPLATE.md
12
+ - Add CONTRIBUTING.md
13
+ - Update layout
14
+ - Share the chips.rb
15
+ - Move chip_sizes above chips
16
+ - Refine visuals of button and index
17
+ - Index has min and max
18
+ - Text::initialize can take 'align:' keyword parameter
19
+ - The text input area will not be shaked if the value determined by the click is empty
20
+ - Fix the text value if not reverted on exiting edit mode with invalid value
21
+ - Delete App#name
22
+ - Simplify inspect() text
23
+ - Update readme: Add link to examples (by @kaibadash)
24
+
25
+ - Fix some crashes
26
+
27
+
4
28
  ## [v0.1.7] - 2025-03-07
5
29
 
6
30
  - Add Reight::Context
data/README.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  # Reight - A Retro Game Engine for Ruby
3
2
 
4
3
  Reight is an open-source Ruby library inspired by the powerful [Processing](https://processing.org/) API, designed to make creative coding accessible and enjoyable for everyone. With support for both Mac and Windows, this library brings the joy of visual programming to Ruby developers.
@@ -38,20 +37,22 @@ Here’s a simple example to get you started:
38
37
  # Create a window and draw something
39
38
  draw do
40
39
  background 0
41
- $sprites ||= project.maps.first.map(&:to_sprite)
42
- sprite $sprites
40
+ $sprites ||= project.maps.first.map(&:to_sprite)
41
+ sprite $sprites
43
42
  end
44
43
  ```
45
44
 
46
45
  Run the script and watch your window come to life!
47
46
 
48
- ```
47
+ ```bash
49
48
  $ bundle exec r8 .
50
49
  ```
51
50
 
51
+ You can find example projects in [xord/reight-examples](https://github.com/xord/reight-examples).
52
+
52
53
  ## Documentation
53
54
 
54
- Comprehensive documentation and guides can be found [here](https://www.rubydoc.info/gems/reight/0.1/index).
55
+ Comprehensive documentation and guides can be found [here](https://www.rubydoc.info/gems/reight/).
55
56
 
56
57
  ## License
57
58
 
data/Rakefile CHANGED
@@ -29,5 +29,5 @@ build_ruby_gem
29
29
  task :run do
30
30
  libs = %w[xot rucy beeps rays reflex processing rubysketch]
31
31
  .map {|lib| "-I#{ENV['ALL']}/#{lib}/lib"}
32
- sh %( ruby #{libs.join ' '} -Ilib bin/r8 '#{ENV["dir"] || "."}' )
32
+ sh %( ruby #{libs.join ' '} -Ilib bin/r8 --edit '#{ENV["dir"] || "demo/hello"}' )
33
33
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.7
1
+ 0.1.8
data/bin/r8 CHANGED
@@ -1,21 +1,31 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'optparse'
3
4
  require 'reight'
4
- using Reight
5
5
 
6
6
 
7
- def r8 = $r8__
7
+ opt = OptionParser.new
8
+ opt.banner = "Usage: r8 [options] [DIR]"
9
+ opt.version = Reight::Extension.version
10
+
11
+ opt.on '-e', '--edit', "edit mode"
12
+ opt.on_tail '-h', '--help'
13
+
14
+ params = {}
15
+ argv = opt.parse! ARGV, into: params
8
16
 
9
- def usage()
10
- <<~END
11
- USAGE: r8 [path]
12
- END
17
+ if params.key? :help
18
+ puts opt.help
19
+ exit
13
20
  end
14
21
 
22
+
23
+ def r8 = $r8__
24
+
15
25
  Reight::CONTEXT__.tap do |c|
16
- path = ARGV.shift or return puts usage
26
+ path = argv.shift || '.'
17
27
  path = File.expand_path path, Dir.pwd unless path.start_with?('/')
18
- c.setup {Reight::R8.new(path).setup}
28
+ c.setup {Reight::R8.new(path, edit: params[:edit]).setup}
19
29
  c.draw {r8.draw}
20
30
  c.key_pressed {r8.key_pressed}
21
31
  c.key_released {r8.key_released}
data/lib/reight/all.rb CHANGED
@@ -36,6 +36,7 @@ require 'reight/sound'
36
36
  require 'reight/app'
37
37
  require 'reight/app/navigator'
38
38
  require 'reight/app/runner'
39
+ require 'reight/app/chips'
39
40
  require 'reight/app/sprite'
40
41
  require 'reight/app/map'
41
42
  require 'reight/app/sound'
@@ -0,0 +1,124 @@
1
+ using Reight
2
+
3
+
4
+ class Reight::App::Chips
5
+
6
+ include Reight::Hookable
7
+
8
+ def initialize(
9
+ app, chips, size = 8,
10
+ page_width = app.project.chips_page_width,
11
+ page_height = app.project.chips_page_height)
12
+
13
+ hook :frame_changed
14
+ hook :offset_changed
15
+
16
+ @app, @chips = app, chips
17
+ @page_size = create_vector page_width, page_height
18
+ @offset = create_vector 0, 0
19
+
20
+ set_frame 0, 0, size, size
21
+ end
22
+
23
+ attr_reader :x, :y, :size, :offset
24
+
25
+ def chip = @chips.at x, y, size, size
26
+
27
+ def set_frame(x, y, w = size, h = size)
28
+ raise 'Chips: width != height' if w != h
29
+ @x = align_to_grid(x).clamp(0..@chips.image.width)
30
+ @y = align_to_grid(y).clamp(0..@chips.image.height)
31
+ @size = w
32
+ frame_changed! @x, @y, @size, @size
33
+ end
34
+
35
+ def offset=(pos)
36
+ sp = sprite
37
+ x = pos.x.clamp([-(@chips.image.width - sp.w), 0].min..0)
38
+ y = pos.y.clamp([-(@chips.image.height - sp.h), 0].min..0)
39
+ offset = create_vector x, y
40
+ return if offset == @offset
41
+ @offset = offset
42
+ offset_changed! @offset
43
+ end
44
+
45
+ def index2offset(index)
46
+ pw, ph = @page_size.x.to_i, @page_size.y.to_i
47
+ size = @chips.image.width / pw
48
+ create_vector -(index % size).to_i * pw, -(index / size).to_i * ph
49
+ end
50
+
51
+ def offset2index(offset = self.offset)
52
+ iw = @chips.image.width
53
+ pw, ph = @page_size.x.to_i, @page_size.y.to_i
54
+ x, y = (-offset.x / ph).to_i, (-offset.y / pw).to_i
55
+ w = (iw / pw).to_i
56
+ y * w + x
57
+ end
58
+
59
+ def draw()
60
+ sp = sprite
61
+ clip sp.x, sp.y, sp.w, sp.h
62
+
63
+ fill 0
64
+ no_stroke
65
+ rect 0, 0, sp.w, sp.h
66
+
67
+ translate offset.x, offset.y
68
+ draw_offset_grids
69
+ image @chips.image, 0, 0
70
+ draw_frame
71
+ end
72
+
73
+ def draw_offset_grids()
74
+ no_fill
75
+ stroke 50
76
+ iw, ih = @chips.image.width, @chips.image.height
77
+ cw, ch = @page_size.x, @page_size.y
78
+ (cw...iw).step(cw) {|x| line x, 0, x, ih}
79
+ (ch...ih).step(ch) {|y| line 0, y, iw, y}
80
+ end
81
+
82
+ def draw_frame()
83
+ no_fill
84
+ stroke 255
85
+ stroke_weight 1
86
+ rect @x, @y, @size, @size
87
+ end
88
+
89
+ def mouse_pressed(x, y)
90
+ @prev_pos = create_vector x, y
91
+ end
92
+
93
+ def mouse_released(x, y)
94
+ end
95
+
96
+ def mouse_dragged(x, y)
97
+ pos = create_vector x, y
98
+ self.offset += pos - @prev_pos if @prev_pos
99
+ @prev_pos = pos
100
+ end
101
+
102
+ def mouse_clicked(x, y)
103
+ set_frame(
104
+ -offset.x + align_to_grid(x),
105
+ -offset.y + align_to_grid(y))
106
+ end
107
+
108
+ def sprite()
109
+ @sprite ||= RubySketch::Sprite.new.tap do |sp|
110
+ sp.draw {draw}
111
+ sp.mouse_pressed {mouse_pressed sp.mouse_x, sp.mouse_y}
112
+ sp.mouse_released {mouse_released sp.mouse_x, sp.mouse_y}
113
+ sp.mouse_dragged {mouse_dragged sp.mouse_x, sp.mouse_y}
114
+ sp.mouse_clicked {mouse_clicked sp.mouse_x, sp.mouse_y}
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def align_to_grid(n)
121
+ n.to_i / 8 * 8
122
+ end
123
+
124
+ end# Chips
@@ -3,8 +3,8 @@ using Reight
3
3
 
4
4
  class Reight::MapEditor::Canvas
5
5
 
6
- def initialize(app, map, path)
7
- @app, @map, @path = app, map, path
6
+ def initialize(app, map)
7
+ @app, @map = app, map
8
8
  @x, @y, @tool, @cursor = 0, 0, nil, nil, nil
9
9
  end
10
10
 
@@ -12,6 +12,10 @@ class Reight::MapEditor::Canvas
12
12
 
13
13
  attr_reader :map, :cursor
14
14
 
15
+ def map=(map)
16
+ @map = map
17
+ end
18
+
15
19
  def save()
16
20
  @app.project.save
17
21
  end
@@ -4,11 +4,15 @@ using Reight
4
4
  class Reight::MapEditor < Reight::App
5
5
 
6
6
  def canvas()
7
- @canvas ||= Canvas.new self, project.maps.first, project.maps_json_path
7
+ @canvas ||= Canvas.new self, project.maps.first
8
8
  end
9
9
 
10
10
  def chips()
11
- @chips ||= Chips.new self, project.chips
11
+ @chips ||= Chips.new(self, project.chips).tap do |chips|
12
+ chips.offset_changed do |offset|
13
+ chips_index.index = chips.offset2index offset
14
+ end
15
+ end
12
16
  end
13
17
 
14
18
  def setup()
@@ -42,25 +46,36 @@ class Reight::MapEditor < Reight::App
42
46
 
43
47
  def window_resized()
44
48
  super
49
+ [chip_sizes, tools].flatten.map(&:sprite)
50
+ .each {|sp| sp.w = sp.h = BUTTON_SIZE}
51
+
52
+ chips_index.sprite.tap do |sp|
53
+ sp.w, sp.h = INDEX_SIZE, BUTTON_SIZE
54
+ sp.x = SPACE
55
+ sp.y = NAVIGATOR_HEIGHT + SPACE
56
+ end
57
+ chip_sizes.reverse.map {_1.sprite}.each.with_index do |sp, index|
58
+ sp.x = SPACE + CHIPS_WIDTH - (sp.w + (sp.w + 1) * index)
59
+ sp.y = chips_index.sprite.y
60
+ end
45
61
  chips.sprite.tap do |sp|
46
62
  sp.x = SPACE
47
- sp.y = NAVIGATOR_HEIGHT + SPACE
48
- sp.w = CHIPS_WIDTH
63
+ sp.y = chip_sizes.last.sprite.bottom + SPACE
64
+ sp.right = chip_sizes.last.sprite.right
49
65
  sp.bottom = height - SPACE
50
66
  end
67
+ map_index.sprite.tap do |sp|
68
+ sp.w, sp.h = INDEX_SIZE, BUTTON_SIZE
69
+ sp.x = chip_sizes.last.sprite.right + SPACE
70
+ sp.y = chip_sizes.last.sprite.y
71
+ end
51
72
  tools.map {_1.sprite}.each.with_index do |sp, index|
52
- sp.w = sp.h = BUTTON_SIZE
53
73
  sp.x = chips.sprite.right + SPACE + (sp.w + 1) * index
54
74
  sp.y = height - (SPACE + sp.h)
55
75
  end
56
- chip_sizes.reverse.map {_1.sprite}.each.with_index do |sp, index|
57
- sp.w = sp.h = BUTTON_SIZE
58
- sp.x = width - (SPACE + sp.w * (index + 1) + index)
59
- sp.y = height - (SPACE + sp.h)
60
- end
61
76
  canvas.sprite.tap do |sp|
62
- sp.x = chips.sprite.right + SPACE
63
- sp.y = chips.sprite.y
77
+ sp.x = map_index.sprite.x
78
+ sp.y = map_index.sprite.bottom + SPACE
64
79
  sp.right = width - SPACE
65
80
  sp.bottom = tools.first.sprite.top - SPACE
66
81
  end
@@ -93,10 +108,16 @@ class Reight::MapEditor < Reight::App
93
108
  private
94
109
 
95
110
  def sprites()
96
- [canvas, chips, *chip_sizes, *tools]
111
+ [chips_index, *chip_sizes, chips, map_index, *tools, canvas]
97
112
  .map(&:sprite) + super
98
113
  end
99
114
 
115
+ def chips_index()
116
+ @chips_index ||= Reight::Index.new max: project.chips_npages - 1 do |index|
117
+ chips.offset = chips.index2offset index if index != chips.offset2index
118
+ end
119
+ end
120
+
100
121
  def chip_sizes()
101
122
  @chip_sizes ||= group(*[8, 16, 32].map {|size|
102
123
  Reight::Button.new name: "#{size}x#{size}", label: size do
@@ -105,6 +126,12 @@ class Reight::MapEditor < Reight::App
105
126
  })
106
127
  end
107
128
 
129
+ def map_index()
130
+ @map_index ||= Reight::Index.new do |index|
131
+ canvas.map = project.maps[index] ||= Reight::Map.new
132
+ end
133
+ end
134
+
108
135
  def tools()
109
136
  @tools ||= group brush, line, stroke_rect, fill_rect
110
137
  end
@@ -1,6 +1,5 @@
1
1
  require 'reight/app/map/editor'
2
2
  require 'reight/app/map/canvas'
3
- require 'reight/app/map/chips'
4
3
  require 'reight/app/map/tool'
5
4
  require 'reight/app/map/brush_base'
6
5
  require 'reight/app/map/brush'
@@ -32,7 +32,7 @@ class Reight::Navigator
32
32
 
33
33
  def key_pressed()
34
34
  index = [F1, F2, F3, F4, F5].index(key_code)
35
- app_buttons[index].click if index
35
+ app_buttons[index]&.click if index
36
36
  end
37
37
 
38
38
  def window_resized()
@@ -77,20 +77,11 @@ class Reight::Navigator
77
77
  private
78
78
 
79
79
  def app_buttons()
80
- @app_buttons ||= [
81
- Reight::Button.new(name: 'Run', icon: @app.icon(0, 0, 8)) {
82
- switch_app Reight::Runner
83
- },
84
- Reight::Button.new(name: 'Sprite Editor', icon: @app.icon(1, 0, 8)) {
85
- switch_app Reight::SpriteEditor
86
- },
87
- Reight::Button.new(name: 'Map Editor', icon: @app.icon(2, 0, 8)) {
88
- switch_app Reight::MapEditor
89
- },
90
- Reight::Button.new(name: 'Sound Editor', icon: @app.icon(3, 0, 8)) {
91
- switch_app Reight::SoundEditor
92
- },
93
- ]
80
+ @app_buttons ||= r8.apps.map.with_index {|app, index|
81
+ Reight::Button.new(name: app.label, icon: app.icon(index, 0, 8)) {
82
+ r8.current = app
83
+ }
84
+ }
94
85
  end
95
86
 
96
87
  def history_buttons()
@@ -134,11 +125,6 @@ class Reight::Navigator
134
125
  @message ||= Message.new
135
126
  end
136
127
 
137
- def switch_app(klass)
138
- app = r8.apps.find {_1.class == klass}
139
- r8.current = app if app
140
- end
141
-
142
128
  end# Navigator
143
129
 
144
130
 
@@ -6,6 +6,12 @@ class Reight::Runner < Reight::App
6
6
 
7
7
  TEMPORARY_HASH = {}
8
8
 
9
+ def label = 'Run'
10
+
11
+ def setup()
12
+ navigator.visible = false
13
+ end
14
+
9
15
  def activated()
10
16
  run force: true
11
17
  @context.call_activated__ {|&b| call_event(ignore_pause: true, &b)}
@@ -61,7 +67,7 @@ class Reight::Runner < Reight::App
61
67
 
62
68
  def mouse_moved()
63
69
  super
64
- navigator.visible = ROOT_CONTEXT.mouse_y < NAVIGATOR_HEIGHT
70
+ navigator.visible = ROOT_CONTEXT.mouse_y < NAVIGATOR_HEIGHT if r8.edit?
65
71
  call_event {@context.mouse_moved}
66
72
  end
67
73
 
@@ -113,15 +119,18 @@ class Reight::Runner < Reight::App
113
119
  private
114
120
 
115
121
  def call_event(push: true, ignore_pause: false, &block)
116
- return unless @context
117
- @context.beginDraw__
118
- @context.push if push
119
- block.call unless paused?
122
+ if @context
123
+ @context.beginDraw__
124
+ @context.push if push
125
+ block.call unless paused?
126
+ end
120
127
  rescue ScriptError, StandardError => e
121
128
  puts e.full_message
122
129
  ensure
123
- @context.pop if push
124
- @context.endDraw__
130
+ if @context
131
+ @context.pop if push
132
+ @context.endDraw__
133
+ end
125
134
  end
126
135
 
127
136
  def running? = @context && !@paused
@@ -175,6 +184,10 @@ class Reight::Runner < Reight::App
175
184
  caller.call {draw}
176
185
  end
177
186
 
187
+ def inspect()
188
+ "#<#{self.class.name}:0x#{object_id}>"
189
+ end
190
+
178
191
  methods = (instance_methods - Object.instance_methods)
179
192
  .reject {_1.end_with? '__'}
180
193
  Processing.to_snake_case__(methods).each do |camel, snake|
@@ -255,23 +268,17 @@ class Reight::Runner < Reight::App
255
268
  TEMPORARY_HASH.delete :params
256
269
  end
257
270
 
271
+ EXCLUDE_GLOBAL_VARS = [:$FILENAME]
272
+
258
273
  def backup_global_vars()
259
- @global_vars = global_variables
274
+ @global_vars = (global_variables - EXCLUDE_GLOBAL_VARS)
260
275
  .each.with_object({}) {|name, hash| hash[name] = eval name.to_s}
261
276
  .freeze
262
277
  end
263
278
 
264
- def clear_all_timers()
265
- prefix = Reight::Context::TIMER_PREFIX__
266
- ROOT_CONTEXT.instance_eval do
267
- @timers__ .delete_if {|id| id in [prefix, _]}
268
- @firingTimers__.delete_if {|id| id in [prefix, _]}
269
- end
270
- end
271
-
272
279
  def restore_global_vars()
273
280
  return unless @global_vars
274
- global_variables
281
+ (global_variables - EXCLUDE_GLOBAL_VARS)
275
282
  .map {|name| [name, eval(name.to_s)]}
276
283
  .select {|name, value| value != nil && @global_vars[name] == nil}
277
284
  .each {|name, value| global_var_set name, nil}
@@ -289,4 +296,12 @@ class Reight::Runner < Reight::App
289
296
  TEMPORARY_HASH.delete :value
290
297
  end
291
298
 
299
+ def clear_all_timers()
300
+ prefix = Reight::Context::TIMER_PREFIX__
301
+ ROOT_CONTEXT.instance_eval do
302
+ @timers__ .delete_if {|id| id in [prefix, _]}
303
+ @firingTimers__.delete_if {|id| id in [prefix, _]}
304
+ end
305
+ end
306
+
292
307
  end# Runner
@@ -107,7 +107,7 @@ class Reight::SoundEditor < Reight::App
107
107
 
108
108
  def bpm()
109
109
  @bpm ||= Reight::Text.new(
110
- canvas.sound.bpm, label: 'BPM ', regexp: /^\-?\d+$/
110
+ canvas.sound.bpm, label: 'BPM ', regexp: /^\-?\d+$/, editable: true
111
111
  ) do |str, text|
112
112
  bpm = str.to_i.clamp(0, Reight::Sound::BPM_MAX)
113
113
  next text.revert if bpm <= 0
@@ -6,6 +6,8 @@ class Reight::SpriteEditor::Canvas
6
6
  include Reight::Hookable
7
7
 
8
8
  def initialize(app, image, path)
9
+ hook :frame_changed
10
+ hook :selection_changed
9
11
  hook :color_changed
10
12
 
11
13
  @app, @image, @path = app, image, path
@@ -34,7 +36,7 @@ class Reight::SpriteEditor::Canvas
34
36
  new = correct_bounds x, y, w, h
35
37
  return if new == old
36
38
  @x, @y, @w, @h = new
37
- @app.history.append [:frame, old, new]
39
+ frame_changed! old, new
38
40
  end
39
41
 
40
42
  def frame = [x, y, w, h]
@@ -50,7 +52,7 @@ class Reight::SpriteEditor::Canvas
50
52
  new = correct_bounds x, y, w, h
51
53
  return if new == old
52
54
  @selection = new
53
- @app.history.append [:select, old, new]
55
+ selection_changed! old, new
54
56
  end
55
57
 
56
58
  def selection()
@@ -65,7 +67,7 @@ class Reight::SpriteEditor::Canvas
65
67
  def deselect()
66
68
  return if @selection == nil
67
69
  old, @selection = @selection, nil
68
- @app.history.append [:deselect, old]
70
+ selection_changed! old, nil
69
71
  end
70
72
 
71
73
  def paint(&block)