1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3//
4// This program is distributed under the terms of
5// the End-User License Agreement for Aseprite.
6
7#ifdef HAVE_CONFIG_H
8#include "config.h"
9#endif
10
11#include "app/app.h"
12#include "app/context.h"
13#include "app/doc_range.h"
14#include "app/modules/editors.h"
15#include "app/script/docobj.h"
16#include "app/script/engine.h"
17#include "app/script/luacpp.h"
18#include "app/site.h"
19#include "app/ui/editor/editor.h"
20#include "app/util/range_utils.h"
21#include "doc/cel.h"
22#include "doc/layer.h"
23#include "doc/object_ids.h"
24#include "doc/slice.h"
25#include "doc/slices.h"
26#include "doc/sprite.h"
27#include "doc/tile.h"
28
29#include <set>
30#include <vector>
31
32namespace app {
33namespace script {
34
35namespace {
36
37struct RangeObj { // This is like DocRange but referencing objects with IDs
38 DocRange::Type type;
39 ObjectId spriteId;
40 doc::SelectedObjects layers;
41 std::vector<frame_t> frames;
42 doc::SelectedObjects cels;
43 doc::SelectedObjects slices;
44 std::vector<color_t> colors;
45 std::vector<tile_index> tiles;
46
47 RangeObj(Site& site) {
48 updateFromSite(site);
49 }
50 RangeObj(const RangeObj&) = delete;
51 RangeObj& operator=(const RangeObj&) = delete;
52
53 void updateFromSite(const Site& site) {
54 if (!site.sprite()) {
55 type = DocRange::kNone;
56 spriteId = NullId;
57 return;
58 }
59
60 const DocRange& range = site.range();
61
62 spriteId = site.sprite()->id();
63 type = range.type();
64
65 layers.clear();
66 frames.clear();
67 cels.clear();
68 colors.clear();
69
70 if (range.enabled()) {
71 for (const Layer* layer : range.selectedLayers())
72 layers.insert(layer->id());
73 for (const frame_t frame : range.selectedFrames())
74 frames.push_back(frame);
75
76 // TODO improve this, in the best case we should defer layers,
77 // frames, and cels vectors when the properties are accessed, but
78 // it might not be possible because we have to save the IDs of the
79 // objects (and we cannot store the DocRange because it contains
80 // pointers instead of IDs).
81 for (const Cel* cel : get_cels(site.sprite(), range))
82 cels.insert(cel->id());
83 }
84 else {
85 // Put the active frame/layer/cel information in the range
86 frames.push_back(site.frame());
87 if (site.layer()) layers.insert(site.layer()->id());
88 if (site.cel()) cels.insert(site.cel()->id());
89 }
90
91 if (site.selectedColors().picks() > 0)
92 colors = site.selectedColors().toVectorOfIndexes();
93
94 if (site.selectedTiles().picks() > 0)
95 tiles = site.selectedTiles().toVectorOfIndexes();
96
97 slices = site.selectedSlices();
98 }
99
100 Sprite* sprite(lua_State* L) { return check_docobj(L, doc::get<Sprite>(spriteId)); }
101
102 bool contains(const Layer* layer) const {
103 return layers.contains(layer->id());
104 }
105 bool contains(const frame_t frame) const {
106 return std::find(frames.begin(), frames.end(), frame) != frames.end();
107 }
108 bool contains(const Cel* cel) const {
109 return cels.contains(cel->id());
110 }
111 bool contains(const Slice* slice) const {
112 return slices.contains(slice->id());
113 }
114 bool containsColor(const color_t color) const {
115 return (std::find(colors.begin(), colors.end(), color) != colors.end());
116 }
117 bool containsTile(const tile_t tile) const {
118 return (std::find(tiles.begin(), tiles.end(), tile) != tiles.end());
119 }
120};
121
122int Range_gc(lua_State* L)
123{
124 get_obj<RangeObj>(L, 1)->~RangeObj();
125 return 0;
126}
127
128int Range_get_sprite(lua_State* L)
129{
130 auto obj = get_obj<RangeObj>(L, 1);
131 push_docobj<Sprite>(L, obj->spriteId);
132 return 1;
133}
134
135int Range_get_type(lua_State* L)
136{
137 auto obj = get_obj<RangeObj>(L, 1);
138 lua_pushinteger(L, int(obj->type));
139 return 1;
140}
141
142int Range_contains(lua_State* L)
143{
144 bool result = false;
145 auto obj = get_obj<RangeObj>(L, 1);
146 if (Layer* layer = may_get_docobj<Layer>(L, 2)) {
147 result = obj->contains(layer);
148 }
149 else if (Cel* cel = may_get_docobj<Cel>(L, 2)) {
150 result = obj->contains(cel);
151 }
152 else if (Slice* slice = may_get_docobj<Slice>(L, 2)) {
153 result = obj->contains(slice);
154 }
155 else {
156 frame_t frame = get_frame_number_from_arg(L, 2);
157 result = obj->contains(frame);
158 }
159 lua_pushboolean(L, result);
160 return 1;
161}
162
163int Range_containsColor(lua_State* L)
164{
165 auto obj = get_obj<RangeObj>(L, 1);
166 const color_t color = lua_tointeger(L, 2);
167 lua_pushboolean(L, obj->containsColor(color));
168 return 1;
169}
170
171int Range_containsTile(lua_State* L)
172{
173 auto obj = get_obj<RangeObj>(L, 1);
174 const tile_index tile = lua_tointeger(L, 2);
175 lua_pushboolean(L, obj->containsTile(tile));
176 return 1;
177}
178
179int Range_clear(lua_State* L)
180{
181 auto obj = get_obj<RangeObj>(L, 1);
182 auto ctx = App::instance()->context();
183
184 // Set an empty range
185 DocRange range;
186 ctx->setRange(range);
187
188 // Set empty palette picks
189 doc::PalettePicks picks;
190 ctx->setSelectedColors(picks);
191
192#ifdef ENABLE_UI
193 // Empty selected slices in the current editor
194 // TODO add a new function to Context class for this
195 if (current_editor)
196 current_editor->clearSlicesSelection();
197#endif
198
199 obj->updateFromSite(ctx->activeSite());
200 return 0;
201}
202
203int Range_get_isEmpty(lua_State* L)
204{
205 auto obj = get_obj<RangeObj>(L, 1);
206 lua_pushboolean(L, obj->type == DocRange::kNone);
207 return 1;
208}
209
210int Range_get_layers(lua_State* L)
211{
212 auto obj = get_obj<RangeObj>(L, 1);
213 ObjectIds layers(obj->layers.size());
214 int i = 0;
215 for (auto id : obj->layers)
216 layers[i++] = id;
217 push_layers(L, layers);
218 return 1;
219}
220
221int Range_get_frames(lua_State* L)
222{
223 auto obj = get_obj<RangeObj>(L, 1);
224 push_sprite_frames(L, obj->sprite(L), obj->frames);
225 return 1;
226}
227
228int Range_get_cels(lua_State* L)
229{
230 auto obj = get_obj<RangeObj>(L, 1);
231 ObjectIds cels(obj->cels.size());
232 int i = 0;
233 for (auto id : obj->cels)
234 cels[i++] = id;
235 push_cels(L, cels);
236 return 1;
237}
238
239int Range_get_images(lua_State* L)
240{
241 auto obj = get_obj<RangeObj>(L, 1);
242 std::set<ObjectId> set;
243 for (auto celId : obj->cels) {
244 Cel* cel = check_docobj(L, doc::get<Cel>(celId));
245 if (Cel* link = cel->link())
246 cel = link;
247 set.insert(cel->id());
248 }
249 ObjectIds cels;
250 for (auto celId : set)
251 cels.push_back(celId);
252 push_cel_images(L, cels);
253 return 1;
254}
255
256int Range_get_editableImages(lua_State* L)
257{
258 auto obj = get_obj<RangeObj>(L, 1);
259 std::set<ObjectId> set;
260 for (auto celId : obj->cels) {
261 Cel* cel = check_docobj(L, doc::get<Cel>(celId));
262 if (Cel* link = cel->link())
263 cel = link;
264 if (cel->layer()->isEditable())
265 set.insert(cel->id());
266 }
267 ObjectIds cels;
268 for (auto celId : set)
269 cels.push_back(celId);
270 push_cel_images(L, cels);
271 return 1;
272}
273
274int Range_get_colors(lua_State* L)
275{
276 auto obj = get_obj<RangeObj>(L, 1);
277 lua_newtable(L);
278 int j = 1;
279 for (color_t i : obj->colors) {
280 lua_pushinteger(L, i);
281 lua_rawseti(L, -2, j++);
282 }
283 return 1;
284}
285
286int Range_get_tiles(lua_State* L)
287{
288 auto obj = get_obj<RangeObj>(L, 1);
289 lua_newtable(L);
290 int j = 1;
291 for (tile_index i : obj->tiles) {
292 lua_pushinteger(L, i);
293 lua_rawseti(L, -2, j++);
294 }
295 return 1;
296}
297
298int Range_get_slices(lua_State* L)
299{
300 auto obj = get_obj<RangeObj>(L, 1);
301 lua_newtable(L);
302 int j = 1;
303 for (auto sliceId : obj->slices) {
304 push_docobj<Slice>(L, sliceId);
305 lua_rawseti(L, -2, j++);
306 }
307 return 1;
308}
309
310int Range_set_layers(lua_State* L)
311{
312 auto obj = get_obj<RangeObj>(L, 1);
313 app::Context* ctx = App::instance()->context();
314 DocRange range = ctx->activeSite().range();
315
316 doc::SelectedLayers layers;
317 if (lua_istable(L, 2)) {
318 lua_pushnil(L);
319 while (lua_next(L, 2) != 0) {
320 if (Layer* layer = may_get_docobj<Layer>(L, -1))
321 layers.insert(layer);
322 lua_pop(L, 1);
323 }
324 }
325 range.setSelectedLayers(layers);
326
327 ctx->setRange(range);
328 obj->updateFromSite(ctx->activeSite());
329 return 0;
330}
331
332int Range_set_frames(lua_State* L)
333{
334 auto obj = get_obj<RangeObj>(L, 1);
335 app::Context* ctx = App::instance()->context();
336 DocRange range = ctx->activeSite().range();
337
338 doc::SelectedFrames frames;
339 if (lua_istable(L, 2)) {
340 lua_pushnil(L);
341 while (lua_next(L, 2) != 0) {
342 doc::frame_t f = get_frame_number_from_arg(L, -1);
343 frames.insert(f);
344 lua_pop(L, 1);
345 }
346 }
347 range.setSelectedFrames(frames);
348
349 ctx->setRange(range);
350 obj->updateFromSite(ctx->activeSite());
351 return 0;
352}
353
354int Range_set_colors(lua_State* L)
355{
356 auto obj = get_obj<RangeObj>(L, 1);
357 app::Context* ctx = App::instance()->context();
358 doc::PalettePicks picks;
359 if (lua_istable(L, 2)) {
360 lua_pushnil(L);
361 while (lua_next(L, 2) != 0) {
362 int i = lua_tointeger(L, -1);
363 if (i >= picks.size())
364 picks.resize(i+1);
365 picks[i] = true;
366 lua_pop(L, 1);
367 }
368 }
369
370 ctx->setSelectedColors(picks);
371 obj->updateFromSite(ctx->activeSite());
372 return 0;
373}
374
375int Range_set_tiles(lua_State* L)
376{
377 auto obj = get_obj<RangeObj>(L, 1);
378 app::Context* ctx = App::instance()->context();
379 doc::PalettePicks picks;
380 if (lua_istable(L, 2)) {
381 lua_pushnil(L);
382 while (lua_next(L, 2) != 0) {
383 int i = lua_tointeger(L, -1);
384 if (i >= picks.size())
385 picks.resize(i+1);
386 picks[i] = true;
387 lua_pop(L, 1);
388 }
389 }
390 ctx->setSelectedTiles(picks);
391 obj->updateFromSite(ctx->activeSite());
392 return 0;
393}
394
395int Range_set_slices(lua_State* L)
396{
397 auto obj = get_obj<RangeObj>(L, 1);
398 app::Context* ctx = App::instance()->context();
399
400 // TODO we should add support to CLI scripts
401#ifdef ENABLE_UI
402 if (current_editor) {
403 current_editor->clearSlicesSelection();
404 const int len = luaL_len(L, 2);
405 for (int i = 1; i <= len; i++) {
406 if (lua_geti(L, 2, i) != LUA_TNIL)
407 current_editor->selectSlice(get_docobj<Slice>(L, -1));
408 lua_pop(L, 1);
409 }
410 }
411#endif
412
413 obj->updateFromSite(ctx->activeSite());
414 return 0;
415}
416
417const luaL_Reg Range_methods[] = {
418 { "__gc", Range_gc },
419 { "contains", Range_contains },
420 { "containsColor", Range_containsColor },
421 { "containsTile", Range_containsTile },
422 { "clear", Range_clear },
423 { nullptr, nullptr }
424};
425
426const Property Range_properties[] = {
427 { "sprite", Range_get_sprite, nullptr },
428 { "type", Range_get_type, nullptr },
429 { "isEmpty", Range_get_isEmpty, nullptr },
430 { "layers", Range_get_layers, Range_set_layers },
431 { "frames", Range_get_frames, Range_set_frames },
432 { "cels", Range_get_cels, nullptr },
433 { "images", Range_get_images, nullptr },
434 { "editableImages", Range_get_editableImages, nullptr },
435 { "colors", Range_get_colors, Range_set_colors },
436 { "tiles", Range_get_tiles, Range_set_tiles },
437 { "slices", Range_get_slices, Range_set_slices },
438 { nullptr, nullptr, nullptr }
439};
440
441} // anonymous namespace
442
443DEF_MTNAME(RangeObj);
444
445void register_range_class(lua_State* L)
446{
447 using Range = RangeObj;
448 REG_CLASS(L, Range);
449 REG_CLASS_PROPERTIES(L, Range);
450}
451
452void push_doc_range(lua_State* L, Site& site)
453{
454 push_new<RangeObj>(L, site);
455}
456
457} // namespace script
458} // namespace app
459