author_engine 0.1.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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +7 -0
  4. data/API.md +81 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +26 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +38 -0
  9. data/Rakefile +10 -0
  10. data/SAVEFILE.md +23 -0
  11. data/assets/fonts/Connection.otf +0 -0
  12. data/assets/fonts/ConnectionBold.otf +0 -0
  13. data/assets/fonts/README.md +3 -0
  14. data/assets/fonts/SIL Open Font License.txt +94 -0
  15. data/assets/ui/bucket_icon.png +0 -0
  16. data/assets/ui/code_icon.png +0 -0
  17. data/assets/ui/error_icon.png +0 -0
  18. data/assets/ui/level_icon.png +0 -0
  19. data/assets/ui/loading_icon.png +0 -0
  20. data/assets/ui/lock_icon.png +0 -0
  21. data/assets/ui/pencil_icon.png +0 -0
  22. data/assets/ui/play_icon.png +0 -0
  23. data/assets/ui/sprite_icon.png +0 -0
  24. data/assets/ui/unlock_icon.png +0 -0
  25. data/author_engine.gemspec +41 -0
  26. data/bin/author_engine +4 -0
  27. data/lib/author_engine/button.rb +154 -0
  28. data/lib/author_engine/code_editor/cursor.rb +339 -0
  29. data/lib/author_engine/code_editor/highlighting.rb +49 -0
  30. data/lib/author_engine/container.rb +24 -0
  31. data/lib/author_engine/containers/editor.rb +97 -0
  32. data/lib/author_engine/containers/loader.rb +105 -0
  33. data/lib/author_engine/game/game.rb +26 -0
  34. data/lib/author_engine/game/parts/colors.rb +59 -0
  35. data/lib/author_engine/game/parts/common.rb +13 -0
  36. data/lib/author_engine/game/parts/graphics.rb +41 -0
  37. data/lib/author_engine/game/parts/input.rb +25 -0
  38. data/lib/author_engine/image.rb +53 -0
  39. data/lib/author_engine/palette.rb +114 -0
  40. data/lib/author_engine/save_file.rb +134 -0
  41. data/lib/author_engine/sprite.rb +4 -0
  42. data/lib/author_engine/sprite_picker.rb +154 -0
  43. data/lib/author_engine/support.rb +22 -0
  44. data/lib/author_engine/text.rb +41 -0
  45. data/lib/author_engine/version.rb +3 -0
  46. data/lib/author_engine/view.rb +55 -0
  47. data/lib/author_engine/views/code_editor.rb +154 -0
  48. data/lib/author_engine/views/level_editor.rb +7 -0
  49. data/lib/author_engine/views/play_viewer.rb +163 -0
  50. data/lib/author_engine/views/sprite_editor.rb +333 -0
  51. data/lib/author_engine/window.rb +132 -0
  52. data/lib/author_engine.rb +32 -0
  53. data/test_3.authorengine +519 -0
  54. data/testing.authorengine +578 -0
  55. metadata +169 -0
@@ -0,0 +1,154 @@
1
+ class AuthorEngine
2
+ class CodeEditor < View
3
+
4
+ class CodeInput < Gosu::TextInput
5
+ def filter(text_in)
6
+ return text_in
7
+ end
8
+ end
9
+
10
+ DEFAULT_STRING = <<-EOF
11
+ def init
12
+ @size = 10
13
+ @x = width/2 - @size/2
14
+ @y = height/2 - @size/2
15
+ end
16
+
17
+ def draw
18
+ rect(@x, @y, @size, @size, pink)
19
+ text("x: \#{@x}, y: \#{@y}", 0, 0, 8)
20
+ end
21
+
22
+ def update
23
+ @x+=1 if button?("right")
24
+ @x-=1 if button?("left")
25
+ @y-=1 if button?("up")
26
+ @y+=1 if button?("down")
27
+
28
+ @x = 0 if @x < 0
29
+ @x = 128-@size if @x > 128-@size
30
+
31
+ @y = 0 if @y < 0
32
+ @y = 128-@size if @y > 128-@size
33
+ end
34
+ EOF
35
+
36
+
37
+ attr_accessor :x_offset, :y_offset
38
+ def setup
39
+ @font_size = 5 * window.square_scale.floor
40
+ @font = Gosu::Font.new(@font_size, name: Text::FONT_DEFAULT_BOLD) # "Consolas"
41
+ @line_numbers_spacing = "00"
42
+ @line_numbers_width = @font.text_width(@line_numbers_spacing)
43
+
44
+ @text_input = CodeInput.new
45
+ if window.container.savefile.code.nil?
46
+ @text_input.text = DEFAULT_STRING
47
+ else
48
+ @text_input.text = window.container.savefile.code
49
+ end
50
+ @text = AuthorEngine::Text.new(message: "", size: @font_size, x: @line_numbers_width+@x_padding, y: window.container.header_height, font: Text::FONT_DEFAULT) # "DejaVu Sans Mono"
51
+
52
+ @cursor = Cursor.new(view: self, text_input: @text_input, text: @text)
53
+ @highlighting = Highlighting.new
54
+
55
+ @x_offset, @y_offset = 0, 0
56
+ end
57
+
58
+ def focus
59
+ window.text_input = @text_input
60
+ window.caption = "Code Editor"
61
+ end
62
+
63
+ def blur
64
+ window.text_input = nil
65
+ end
66
+
67
+ def draw
68
+ # Gosu.draw_rect(0, window.container.header_height, @width, @height, white)
69
+ super
70
+ Gosu.clip_to(0, window.container.header_height, window.width, window.height - window.container.header_height) do
71
+ Gosu.draw_rect(0, window.container.header_height, @line_numbers_width, @height, dark_gray)
72
+
73
+ Gosu.translate(0, @y_offset) do
74
+ min_width = @font.text_width("0")+@x_padding
75
+ (@text.message.lines.map(&:chomp)).each_with_index do |line, index|
76
+ min_width = @font.text_width("#{index+1}") if @font.text_width("#{index+1}") > min_width
77
+
78
+ @font.draw_text("#{index+1}", 1, window.container.header_height + (@font.height * index), 0)
79
+ end
80
+
81
+ @line_numbers_width = min_width
82
+ @text.x = @line_numbers_width+@x_padding
83
+ end
84
+ end
85
+
86
+ Gosu.clip_to(@line_numbers_width, window.container.header_height, window.width, @height) do
87
+ Gosu.translate(@x_offset, @y_offset) do
88
+ @text.draw_markup
89
+ @cursor.draw
90
+ end
91
+ end
92
+ end
93
+
94
+ def update
95
+ super
96
+ # @text_input.text+="\n" if Gosu.button_down?(Gosu::KbEnter) || Gosu.button_down?(Gosu::KbReturn) # FIXME
97
+ @caret_pos = @text_input.caret_pos
98
+ @highlighting.highlight(string: @text_input.text, text: @text)
99
+
100
+ @cursor.update
101
+ end
102
+
103
+ def code; @text_input.text; end
104
+
105
+ def button_down(id)
106
+ @cursor.button_down(id)
107
+ end
108
+
109
+ def button_up(id)
110
+ cursor_pos = @text_input.caret_pos # get a copy of the current cursor location
111
+
112
+ if id == Gosu::KbEnter || id == Gosu::KbReturn
113
+ # raise if @caret_pos != @text_input.caret_pos
114
+ @text_input.text = @text_input.text.insert(@text_input.caret_pos, "\n")
115
+ @cursor.set_position(cursor_pos+1)
116
+ end
117
+
118
+ @cursor.move(:up) if id == Gosu::KbUp
119
+ @cursor.move(:down) if id == Gosu::KbDown
120
+
121
+ if id == Gosu::KbTab
122
+ text = @text_input.text
123
+
124
+ if window.shift_button_down?
125
+ # FIXME: remove spaces behide cursor
126
+
127
+ # chars = @text_input.text.chars
128
+
129
+ # if text[cursor_pos] == " " && text[cursor_pos-1] == " "
130
+ # chars.delete_at(cursor_pos-1)
131
+ # chars.delete_at(cursor_pos)
132
+ # @cursor.set_position(cursor_pos-2)
133
+
134
+ # text = chars.join
135
+ # elsif text[cursor_pos] == " "
136
+ # chars.delete_at(cursor_pos-1)
137
+ # @cursor.set_position(cursor_pos-1)
138
+
139
+ # text = chars.join
140
+ # else
141
+ # p text[cursor_pos]
142
+ # end
143
+
144
+ else
145
+ @text_input.text = @text_input.text.insert(cursor_pos, " ")
146
+ p cursor_pos+2
147
+ @cursor.set_position(cursor_pos+2)
148
+ end
149
+ end
150
+
151
+ @cursor.button_up(id)
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,7 @@
1
+ class AuthorEngine
2
+ class LevelEditor < View
3
+ def focus
4
+ window.caption = "Level Editor"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,163 @@
1
+ class AuthorEngine
2
+ class PlayViewer < View
3
+ def setup
4
+ @running = false
5
+
6
+ @error_icon = AuthorEngine::Image.new("assets/ui/error_icon.png", retro: true)
7
+ @error_icon_color = 0
8
+ @error_icon_colors = [yellow, orange]
9
+
10
+ @loading_icon = AuthorEngine::Image.new("assets/ui/loading_icon.png", retro: true)
11
+ @loading_icon_angle = 0
12
+ @loading_icon_rot_step = 45
13
+
14
+ @icon_rot_period = 500
15
+ @last_icon_rot = Gosu.milliseconds
16
+
17
+ @error_message = Text.new(message: "", x: @x_padding, y: window.container.header_height, z: Float::INFINITY)
18
+
19
+ @text = Text.new(message: "Press [Control+P] to Play")
20
+ @text.x = window.width/2 - @text.width/2
21
+ @text.y = window.height/2 + @text.height + @loading_icon.height * window.square_scale
22
+
23
+ @last_error = nil
24
+ end
25
+
26
+ def run(code: nil)
27
+ @last_error = nil
28
+ @game = Game.new(code: code)
29
+
30
+ @running = true
31
+ window.show_cursor = false
32
+ window.container.lock
33
+
34
+ @game.init
35
+ end
36
+
37
+ def catch_error(&block)
38
+ begin
39
+ block.call if block
40
+ rescue => error
41
+ error_response(error)
42
+ rescue SyntaxError => error
43
+ error_response(error)
44
+ end
45
+ end
46
+
47
+ def error_response(error)
48
+ stop
49
+
50
+ @last_error = error
51
+ format_error(text: @error_message, error: error)
52
+ end
53
+
54
+ def format_error(text:, error:)
55
+ puts "#{error.class}: #{error.message}"
56
+ puts error.backtrace.join("\n")
57
+
58
+ max_width = window.width - (@x_padding*2)
59
+ char_width= text.font.text_width("0")
60
+ chars_line= (max_width.to_f / char_width.to_f).ceil
61
+
62
+ backtrace = "<c=#{xml_color(orange)}>#{error.class}</c>\nBacktrace:\n"
63
+ error.backtrace.each {|trace| next unless trace.include?("(eval)"); backtrace+=" #{trace}\n"}
64
+ trace_buffer = "#{error.message}"
65
+ buffer = ""
66
+
67
+ trace_buffer.lines do |line|
68
+ line.chomp.chars.each_slice(chars_line).each do |slice|
69
+ string = slice.join
70
+ buffer += " #{string}\n"
71
+ end
72
+ end
73
+
74
+ text.message = "#{backtrace}#{buffer}"
75
+ end
76
+
77
+ def focus
78
+ window.caption = "Play"
79
+ play
80
+ end
81
+
82
+ def play
83
+ catch_error do
84
+ run(code: code_editor.code)
85
+ end
86
+ end
87
+
88
+ def draw
89
+ if @running
90
+ Gosu.draw_rect(0, 0, window.width, window.height, @background)
91
+ Gosu.flush
92
+ catch_error do
93
+ draw_game
94
+ end
95
+ else
96
+ super
97
+ if @last_error
98
+ Gosu.scale(2,2, (window.width / 2) - (@error_icon.width / 2), (window.height / 2) - (@error_icon.height / 2)) do
99
+ @error_icon.draw_rot(window.width / 2, window.height / 2, 10, 0, 0.5, 0.5, 1 * window.square_scale, 1 * window.square_scale, @error_icon_colors[@error_icon_color])
100
+ end
101
+ @error_message.draw_markup
102
+ else
103
+ @loading_icon.draw_rot(window.width / 2, window.height / 2, 10, @loading_icon_angle, 0.5, 0.5, 1 * window.square_scale, 1 * window.square_scale)
104
+ @text.draw
105
+ end
106
+ end
107
+ end
108
+
109
+ def update
110
+ # super
111
+ if @running
112
+ catch_error do
113
+ update_game
114
+ end
115
+ else
116
+ if (Gosu.milliseconds - @last_icon_rot) > @icon_rot_period
117
+ @loading_icon_angle = (@loading_icon_angle + @loading_icon_rot_step) % 360
118
+ @error_icon_color += 1
119
+ @error_icon_color = 0 if @error_icon_color == @error_icon_colors.size
120
+ @last_icon_rot = Gosu.milliseconds
121
+ end
122
+ end
123
+ end
124
+
125
+ def draw_game
126
+ x_offset = 0
127
+ ratio = Window::VIEW_WIDTH / window.width
128
+ if window.scale_x != window.scale_y
129
+ x_offset = window.width / 2 - (window.width * ratio * window.square_scale) / 2
130
+ end
131
+
132
+ Gosu.clip_to(x_offset, 0, (window.base_size * window.square_scale), (window.base_size * window.square_scale)) do
133
+ Gosu.translate(x_offset, 0) do
134
+ Gosu.scale(window.square_scale, window.square_scale) do
135
+ @game.draw_background
136
+ @game.draw
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def update_game
143
+ @game.update if @game
144
+ end
145
+
146
+ def stop
147
+ @running = false
148
+ window.show_cursor = true
149
+ window.container.unlock
150
+ end
151
+
152
+ def button_up(id)
153
+ if id == Gosu::MsLeft && window.mouse_x.between?((window.width / 2) - (@loading_icon.width/2) * window.square_scale, (window.width / 2) + (@loading_icon.width/2) * window.square_scale)
154
+ if window.mouse_y.between?((window.height / 2) - (@loading_icon.height/2) * window.square_scale, (window.height / 2) + (@loading_icon.height/2) * window.square_scale)
155
+ play
156
+ end
157
+ end
158
+
159
+ stop if id == Gosu::KbEscape
160
+ play if id == Gosu::KbP && window.control_button_down?
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,333 @@
1
+ class AuthorEngine
2
+ class SpriteEditor < View
3
+ BLANK_COLOR = Gosu::Color.rgba(0,0,0, 0)
4
+ class Pixel
5
+ attr_accessor :x, :y, :width, :height, :color
6
+ def initialize(x, y, width, height, color)
7
+ @x, @y, @width, @height, @color = x, y, width, height, color
8
+ end
9
+
10
+ def draw
11
+ Gosu.draw_rect(
12
+ @x, @y,
13
+ @width, @height,
14
+ @color,
15
+ 5
16
+ )
17
+ end
18
+ end
19
+
20
+ def setup
21
+ @pixels = []
22
+ @active_color = Gosu::Color.rgba(101,1,101, 255)
23
+
24
+ create_grid(16, 16, 4)
25
+ @canvas_changed = false
26
+ @scale = 1 * window.square_scale
27
+ @palette = Palette.new(x: @grid_x + @grid_width + @grid_pixel_size, y: @grid_y)
28
+ @sprites_picker = SpritePicker.new(y: @grid_y + @grid_height + (@grid_pixel_size * 2))
29
+ @coordinates = Text.new(message: "1:1", x: @scale, y: @grid_y)
30
+
31
+ @sprites= Array.new(@sprites_picker.rows*@sprites_picker.columns, nil)
32
+
33
+ @tools = []
34
+ @pixel_lock = false
35
+ @pixel_floodfill = false # aka bucket tool
36
+
37
+ @tools << Button.new(image: "assets/ui/unlock_icon.png", tooltip: "Toggle pixel lock", x: @palette.x, y: @palette.y + @palette.height + (window.square_scale * 2), color: dark_purple) do |b|
38
+ @unlock_icon ||= b.image
39
+ @lock_icon ||= AuthorEngine::Image.new("assets/ui/lock_icon.png", retro: true)
40
+
41
+ @pixel_lock = !@pixel_lock
42
+
43
+ if @pixel_lock
44
+ b.image = @lock_icon
45
+ else
46
+ b.image = @unlock_icon
47
+ end
48
+ end
49
+
50
+ @pencil_icon = AuthorEngine::Image.new("assets/ui/pencil_icon.png", retro: true)
51
+ @bucket_icon = AuthorEngine::Image.new("assets/ui/bucket_icon.png", retro: true)
52
+
53
+ @tools << Button.new(image: @pencil_icon, tooltip: "Toggle pencil/bucket", x: @palette.x + @tools.first.width + 1, y: @palette.y + @palette.height + (window.square_scale * 2), color: dark_purple) do |b|
54
+ @pixel_floodfill = !@pixel_floodfill
55
+
56
+ if @pixel_floodfill
57
+ b.image = @bucket_icon
58
+ else
59
+ b.image = @pencil_icon
60
+ end
61
+ end
62
+
63
+ import_spritesheet(window.container.savefile.sprites)
64
+ end
65
+
66
+ def focus
67
+ window.show_cursor = true
68
+ window.caption = "Sprite Editor"
69
+ end
70
+
71
+ def draw
72
+ super
73
+ @pixels.each(&:draw)
74
+ highlight_pixel
75
+ @coordinates.draw
76
+
77
+ Gosu.draw_rect(@grid_x-window.square_scale, @grid_y-window.square_scale, @grid_width+(window.square_scale*2), @grid_height+(window.square_scale*2), Gosu::Color::WHITE)
78
+ Gosu.draw_rect(@grid_x, @grid_y, @grid_width, @grid_height, Gosu::Color.rgba(10, 10, 10, 200))
79
+ @palette.draw
80
+ @sprites_picker.draw
81
+
82
+ @tools.each(&:draw)
83
+
84
+ Gosu.clip_to(@grid_x, @grid_y, @grid_width, @grid_height) do
85
+ if @pixel_floodfill
86
+ @bucket_icon.draw(window.mouse_x, window.mouse_y - (@bucket_icon.width * @scale), 1000, @scale, @scale)
87
+ else
88
+ @pencil_icon.draw(window.mouse_x, window.mouse_y - (@pencil_icon.width * @scale), 1000, @scale, @scale)
89
+ end
90
+ end
91
+ end
92
+
93
+ def update
94
+ super
95
+ unless @pixel_floodfill
96
+ paint if Gosu.button_down?(Gosu::MsLeft)
97
+ erase if Gosu.button_down?(Gosu::MsRight)
98
+ end
99
+ @palette.update
100
+
101
+ update_coordinates
102
+ end
103
+
104
+ def update_coordinates
105
+ x = normalize_x(window.mouse_x)
106
+ y = normalize_y(window.mouse_y)
107
+ return if (x >= @grid_columns || y >= @grid_rows)
108
+ return if (x < 0 || y < 0)
109
+
110
+ @coordinates.message = "#{x+1}:#{y+1}"
111
+ end
112
+
113
+ def create_grid(x, y, size)
114
+ size = size * window.square_scale
115
+
116
+ @grid_x = (window.width / 2) - (((size * x) / 2) + size*2 )
117
+ @grid_y = window.container.header_height + size
118
+ @grid_width = x * size
119
+ @grid_height = y * size
120
+ @grid_pixel_size = size
121
+ @grid_columns = x
122
+ @grid_rows = y
123
+
124
+ y.times do |_y|
125
+ x.times do |_x|
126
+ @pixels << Pixel.new(
127
+ @grid_x+(_x*size), @grid_y+(_y*size),
128
+ size, size,
129
+ BLANK_COLOR
130
+ )
131
+ end
132
+ end
133
+ end
134
+
135
+ def highlight_pixel
136
+ return unless @palette.color
137
+
138
+ pixel = get_pixel_at(window.mouse_x, window.mouse_y)
139
+ return unless pixel
140
+ Gosu.draw_rect(
141
+ pixel.x, pixel.y,
142
+ pixel.width, pixel.height,
143
+ @palette.color,
144
+ 6
145
+ )
146
+
147
+ Gosu.draw_rect(
148
+ pixel.x, pixel.y,
149
+ pixel.width, pixel.height,
150
+ Gosu::Color.rgba(255,255,255, 100),
151
+ 6
152
+ )
153
+ end
154
+
155
+ def paint(color = @palette.color)
156
+ pixel = get_pixel_at(window.mouse_x, window.mouse_y)
157
+
158
+ return unless pixel
159
+ return if color.nil?
160
+ return if pixel.color != BLANK_COLOR && @pixel_lock
161
+
162
+ pixel.color = color
163
+ @canvas_changed = true
164
+ end
165
+
166
+ def erase
167
+ paint(BLANK_COLOR)
168
+ end
169
+
170
+ def get_pixel_at(x, y)
171
+ return if (x >= @grid_x+@grid_width || y >= @grid_y+@grid_height)
172
+ x = normalize_x(x)
173
+ y = normalize_y(y)
174
+ return if (x < 0 || y < 0)
175
+
176
+ @pixels[(x + (@grid_columns * y))]
177
+ end
178
+
179
+ def normalize_x(int)
180
+ return ((int - @grid_x) / @grid_pixel_size).floor
181
+ end
182
+
183
+ def normalize_y(int)
184
+ return ((int - @grid_y) / @grid_pixel_size).floor
185
+ end
186
+
187
+ # AKA The Bucket Tool
188
+ # @param pixel [Pixel]
189
+ # @param target_color [Gosu::Color] color to search and replace with replacement_color
190
+ # @param replacement_color [Gosu::Color] color to replace Pixel's current color
191
+ def floodfill(pixel, target_color, replacement_color)
192
+ return unless pixel
193
+ return if pixel.color == replacement_color
194
+ return if pixel.color != target_color
195
+ return if target_color != BLANK_COLOR && @pixel_lock # don't replace non-blank pixels with color if pixels are locked
196
+
197
+ pixel.color = replacement_color
198
+ @canvas_changed = true
199
+
200
+ # UP
201
+ _pixel = get_pixel_at(pixel.x, pixel.y - @grid_pixel_size)
202
+ floodfill(_pixel, target_color, replacement_color)
203
+
204
+ # DOWN
205
+ _pixel = get_pixel_at(pixel.x, pixel.y + @grid_pixel_size)
206
+ floodfill(_pixel, target_color, replacement_color)
207
+
208
+ # LEFT
209
+ _pixel = get_pixel_at(pixel.x - @grid_pixel_size, pixel.y)
210
+ floodfill(_pixel, target_color, replacement_color)
211
+
212
+ # RIGHT
213
+ _pixel = get_pixel_at(pixel.x + @grid_pixel_size, pixel.y)
214
+ floodfill(_pixel, target_color, replacement_color)
215
+ end
216
+
217
+ def sprites
218
+ @sprites
219
+ end
220
+
221
+ def set_sprite
222
+ if @sprites[@sprites_picker.active_sprite]
223
+ sprite_pixels(@sprites[@sprites_picker.active_sprite])
224
+ else
225
+ @pixels.each do |pixel|
226
+ pixel.color = BLANK_COLOR
227
+ end
228
+ end
229
+ end
230
+
231
+ def sprite_pixels(image)
232
+ pixels = []
233
+ image.to_blob.bytes.to_a.each_slice(4).each_with_index do |pixel|
234
+ buffer = []
235
+ pixel.each do |chunk|
236
+ buffer << Integer(chunk)
237
+ end
238
+ pixels << buffer
239
+ end
240
+
241
+ pixels.each_with_index do |pixel, index|
242
+ @pixels[index].color = Gosu::Color.rgba(pixel[0], pixel[1], pixel[2], pixel[3])
243
+ end
244
+ end
245
+
246
+ def update_sprite
247
+ list = []
248
+
249
+ @pixels.each_slice(window.sprite_size).each do |row|
250
+ list << row
251
+ end
252
+
253
+ image = Gosu.render(window.sprite_size, window.sprite_size, retro: true) do
254
+ list.each_with_index do |row, y|
255
+ row.each_with_index do |pixel, x|
256
+ Gosu.draw_rect(x, y, 1, 1, pixel.color)
257
+ end
258
+ end
259
+ end
260
+
261
+ @sprites[@sprites_picker.active_sprite] = nil # release image for garbage collection?
262
+ @sprites[@sprites_picker.active_sprite] = image
263
+ @canvas_changed = false
264
+ end
265
+
266
+ def copy_sprite
267
+ @copied_pixels = []
268
+ @pixels.each {|pixel| @copied_pixels << pixel.dup}
269
+ end
270
+
271
+ def paste_sprite
272
+ if @copied_pixels
273
+ @pixels.each_with_index do |pixel, i|
274
+ pixel.color = @copied_pixels[i].color
275
+ end
276
+ @canvas_changed = true
277
+ update_sprite
278
+ end
279
+
280
+ @copied_pixels = nil
281
+ end
282
+
283
+ def spritesheet
284
+ sheet = Gosu.render(512, 128, retro: true) do
285
+ @sprites.each_slice(512/16).each_with_index do |row, y|
286
+ row.each_with_index do |sprite, x|
287
+ next if sprite.nil?
288
+ sprite.draw(x * 16, y * 16, 0)
289
+ end
290
+ end
291
+ end
292
+
293
+ return sheet
294
+ end
295
+
296
+ def import_spritesheet(data)
297
+ return if data.to_blob.size < 4
298
+ sprites = Gosu::Image.load_tiles(data, 16, 16, retro: true, tileable: true)
299
+ @sprites.clear
300
+
301
+ sprites.each do |sprite|
302
+ @sprites.push(sprite)
303
+ end
304
+
305
+ set_sprite
306
+ end
307
+
308
+ def button_down(id)
309
+ super
310
+
311
+ copy_sprite if window.control_button_down? && id == Gosu::KbC
312
+ paste_sprite if window.control_button_down? && id == Gosu::KbV
313
+ end
314
+
315
+ def button_up(id)
316
+ super
317
+ @palette.button_up(id)
318
+ @sprites_picker.button_up(id)
319
+
320
+ @tools.each{ |b| b.button_up(id) }
321
+
322
+ if @pixel_floodfill
323
+ pixel = get_pixel_at(window.mouse_x, window.mouse_y)
324
+ if pixel
325
+ floodfill(pixel, pixel.color, @palette.color) if id == Gosu::MsLeft if @palette.color
326
+ floodfill(pixel, pixel.color, BLANK_COLOR) if id == Gosu::MsRight
327
+ end
328
+ end
329
+
330
+ update_sprite if (id == Gosu::MsLeft || id == Gosu::MsRight) && @canvas_changed
331
+ end
332
+ end
333
+ end