1 | // Aseprite |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/cmd/set_layer_blend_mode.h" |
13 | #include "app/cmd/set_layer_name.h" |
14 | #include "app/cmd/set_layer_opacity.h" |
15 | #include "app/doc.h" |
16 | #include "app/doc_api.h" |
17 | #include "app/script/docobj.h" |
18 | #include "app/script/engine.h" |
19 | #include "app/script/luacpp.h" |
20 | #include "app/script/userdata.h" |
21 | #include "app/tx.h" |
22 | #include "doc/layer.h" |
23 | #include "doc/layer_tilemap.h" |
24 | #include "doc/sprite.h" |
25 | |
26 | namespace app { |
27 | namespace script { |
28 | |
29 | using namespace doc; |
30 | |
31 | namespace { |
32 | |
33 | int Layer_eq(lua_State* L) |
34 | { |
35 | const auto a = may_get_docobj<Layer>(L, 1); |
36 | const auto b = may_get_docobj<Layer>(L, 2); |
37 | lua_pushboolean(L, (!a && !b) || (a && b && a->id() == b->id())); |
38 | return 1; |
39 | } |
40 | |
41 | int Layer_cel(lua_State* L) |
42 | { |
43 | auto layer = get_docobj<Layer>(L, 1); |
44 | auto cel = layer->cel(get_frame_number_from_arg(L, 2)); |
45 | if (cel) |
46 | push_docobj<Cel>(L, cel); |
47 | else |
48 | lua_pushnil(L); |
49 | return 1; |
50 | } |
51 | |
52 | int Layer_get_sprite(lua_State* L) |
53 | { |
54 | auto layer = get_docobj<Layer>(L, 1); |
55 | push_docobj<Sprite>(L, layer->sprite()); |
56 | return 1; |
57 | } |
58 | |
59 | int Layer_get_parent(lua_State* L) |
60 | { |
61 | auto layer = get_docobj<Layer>(L, 1); |
62 | if (layer->parent() == layer->sprite()->root()) |
63 | push_docobj<Sprite>(L, layer->sprite()); |
64 | else |
65 | push_docobj<Layer>(L, layer->parent()); |
66 | return 1; |
67 | } |
68 | |
69 | int Layer_get_layers(lua_State* L) |
70 | { |
71 | auto layer = get_docobj<Layer>(L, 1); |
72 | if (layer->isGroup()) |
73 | push_group_layers(L, static_cast<LayerGroup*>(layer)); |
74 | else |
75 | lua_pushnil(L); |
76 | return 1; |
77 | } |
78 | |
79 | int Layer_get_stackIndex(lua_State* L) |
80 | { |
81 | auto layer = get_docobj<Layer>(L, 1); |
82 | const auto& layers = layer->parent()->layers(); |
83 | auto it = std::find(layers.begin(), layers.end(), layer); |
84 | ASSERT(it != layers.end()); |
85 | if (it != layers.end()) |
86 | lua_pushinteger(L, it - layers.begin() + 1); |
87 | else |
88 | lua_pushnil(L); |
89 | return 1; |
90 | } |
91 | |
92 | int Layer_get_previous(lua_State* L) |
93 | { |
94 | auto layer = get_docobj<Layer>(L, 1); |
95 | if (auto previous = layer->getPrevious()) |
96 | push_docobj<Layer>(L, previous); |
97 | else |
98 | lua_pushnil(L); |
99 | return 1; |
100 | } |
101 | |
102 | int Layer_get_next(lua_State* L) |
103 | { |
104 | auto layer = get_docobj<Layer>(L, 1); |
105 | if (auto next = layer->getNext()) |
106 | push_docobj<Layer>(L, next); |
107 | else |
108 | lua_pushnil(L); |
109 | return 1; |
110 | } |
111 | |
112 | int Layer_get_name(lua_State* L) |
113 | { |
114 | auto layer = get_docobj<Layer>(L, 1); |
115 | lua_pushstring(L, layer->name().c_str()); |
116 | return 1; |
117 | } |
118 | |
119 | int Layer_get_opacity(lua_State* L) |
120 | { |
121 | auto layer = get_docobj<Layer>(L, 1); |
122 | if (layer->isImage()) { |
123 | lua_pushinteger(L, static_cast<LayerImage*>(layer)->opacity()); |
124 | return 1; |
125 | } |
126 | else |
127 | return 0; |
128 | } |
129 | |
130 | int Layer_get_blendMode(lua_State* L) |
131 | { |
132 | auto layer = get_docobj<Layer>(L, 1); |
133 | if (layer->isImage()) { |
134 | lua_pushinteger(L, (int)static_cast<LayerImage*>(layer)->blendMode()); |
135 | return 1; |
136 | } |
137 | else |
138 | return 0; |
139 | } |
140 | |
141 | int Layer_get_isImage(lua_State* L) |
142 | { |
143 | auto layer = get_docobj<Layer>(L, 1); |
144 | lua_pushboolean(L, layer->isImage()); |
145 | return 1; |
146 | } |
147 | |
148 | int Layer_get_isGroup(lua_State* L) |
149 | { |
150 | auto layer = get_docobj<Layer>(L, 1); |
151 | lua_pushboolean(L, layer->isGroup()); |
152 | return 1; |
153 | } |
154 | |
155 | int Layer_get_isTilemap(lua_State* L) |
156 | { |
157 | auto layer = get_docobj<Layer>(L, 1); |
158 | lua_pushboolean(L, layer->isTilemap()); |
159 | return 1; |
160 | } |
161 | |
162 | int Layer_get_isTransparent(lua_State* L) |
163 | { |
164 | auto layer = get_docobj<Layer>(L, 1); |
165 | lua_pushboolean(L, layer->isTransparent()); |
166 | return 1; |
167 | } |
168 | |
169 | int Layer_get_isBackground(lua_State* L) |
170 | { |
171 | auto layer = get_docobj<Layer>(L, 1); |
172 | lua_pushboolean(L, layer->isBackground()); |
173 | return 1; |
174 | } |
175 | |
176 | int Layer_get_isEditable(lua_State* L) |
177 | { |
178 | auto layer = get_docobj<Layer>(L, 1); |
179 | lua_pushboolean(L, layer->isEditable()); |
180 | return 1; |
181 | } |
182 | |
183 | int Layer_get_isVisible(lua_State* L) |
184 | { |
185 | auto layer = get_docobj<Layer>(L, 1); |
186 | lua_pushboolean(L, layer->isVisible()); |
187 | return 1; |
188 | } |
189 | |
190 | int Layer_get_isContinuous(lua_State* L) |
191 | { |
192 | auto layer = get_docobj<Layer>(L, 1); |
193 | lua_pushboolean(L, layer->isContinuous()); |
194 | return 1; |
195 | } |
196 | |
197 | int Layer_get_isCollapsed(lua_State* L) |
198 | { |
199 | auto layer = get_docobj<Layer>(L, 1); |
200 | lua_pushboolean(L, layer->isCollapsed()); |
201 | return 1; |
202 | } |
203 | |
204 | int Layer_get_isExpanded(lua_State* L) |
205 | { |
206 | auto layer = get_docobj<Layer>(L, 1); |
207 | lua_pushboolean(L, layer->isExpanded()); |
208 | return 1; |
209 | } |
210 | |
211 | int Layer_get_isReference(lua_State* L) |
212 | { |
213 | auto layer = get_docobj<Layer>(L, 1); |
214 | lua_pushboolean(L, layer->isReference()); |
215 | return 1; |
216 | } |
217 | |
218 | int Layer_get_cels(lua_State* L) |
219 | { |
220 | auto layer = get_docobj<Layer>(L, 1); |
221 | push_cels(L, layer); |
222 | return 1; |
223 | } |
224 | |
225 | int Layer_get_tileset(lua_State* L) |
226 | { |
227 | auto layer = get_docobj<Layer>(L, 1); |
228 | if (layer->isTilemap()) |
229 | push_tileset(L, static_cast<doc::LayerTilemap*>(layer)->tileset()); |
230 | else |
231 | lua_pushnil(L); |
232 | return 1; |
233 | } |
234 | |
235 | int Layer_set_name(lua_State* L) |
236 | { |
237 | auto layer = get_docobj<Layer>(L, 1); |
238 | const char* name = lua_tostring(L, 2); |
239 | if (name) { |
240 | Tx tx; |
241 | tx(new cmd::SetLayerName(layer, name)); |
242 | tx.commit(); |
243 | } |
244 | return 0; |
245 | } |
246 | |
247 | int Layer_set_opacity(lua_State* L) |
248 | { |
249 | auto layer = get_docobj<Layer>(L, 1); |
250 | const int opacity = lua_tointeger(L, 2); |
251 | if (layer->isImage()) { |
252 | Tx tx; |
253 | tx(new cmd::SetLayerOpacity(static_cast<LayerImage*>(layer), opacity)); |
254 | tx.commit(); |
255 | } |
256 | return 0; |
257 | } |
258 | |
259 | int Layer_set_blendMode(lua_State* L) |
260 | { |
261 | auto layer = get_docobj<Layer>(L, 1); |
262 | const int blendMode = lua_tointeger(L, 2); |
263 | if (layer->isImage()) { |
264 | Tx tx; |
265 | tx(new cmd::SetLayerBlendMode(static_cast<LayerImage*>(layer), |
266 | (doc::BlendMode)blendMode)); |
267 | tx.commit(); |
268 | } |
269 | return 0; |
270 | } |
271 | |
272 | int Layer_set_stackIndex(lua_State* L) |
273 | { |
274 | auto layer = get_docobj<Layer>(L, 1); |
275 | const auto& layers = layer->parent()->layers(); |
276 | int newStackIndex = lua_tointeger(L, 2); |
277 | int stackIndex = 1; |
278 | auto parent = layer->parent(); |
279 | |
280 | // First we need to know this layer stackIndex because we'll use |
281 | auto it = std::find(layers.begin(), layers.end(), layer); |
282 | ASSERT(it != layers.end()); |
283 | if (it != layers.end()) |
284 | stackIndex = it - layers.begin() + 1; |
285 | |
286 | Layer* beforeThis = nullptr; |
287 | if (newStackIndex > stackIndex) { |
288 | ++newStackIndex; |
289 | } |
290 | |
291 | if (newStackIndex-1 < int(parent->layers().size())) { |
292 | beforeThis = parent->layers()[std::clamp(newStackIndex-1, 0, (int)parent->layers().size())]; |
293 | } |
294 | else { |
295 | beforeThis = nullptr; |
296 | } |
297 | |
298 | // Do nothing |
299 | if (beforeThis == layer) |
300 | return 0; |
301 | |
302 | Doc* doc = static_cast<Doc*>(layer->sprite()->document()); |
303 | Tx tx; |
304 | DocApi(doc, tx).restackLayerBefore(layer, parent, beforeThis); |
305 | tx.commit(); |
306 | return 0; |
307 | } |
308 | |
309 | int Layer_set_isEditable(lua_State* L) |
310 | { |
311 | auto layer = get_docobj<Layer>(L, 1); |
312 | layer->setEditable(lua_toboolean(L, 2)); |
313 | return 0; |
314 | } |
315 | |
316 | int Layer_set_isVisible(lua_State* L) |
317 | { |
318 | auto layer = get_docobj<Layer>(L, 1); |
319 | layer->setVisible(lua_toboolean(L, 2)); |
320 | return 0; |
321 | } |
322 | |
323 | int Layer_set_isContinuous(lua_State* L) |
324 | { |
325 | auto layer = get_docobj<Layer>(L, 1); |
326 | layer->setContinuous(lua_toboolean(L, 2)); |
327 | return 0; |
328 | } |
329 | |
330 | int Layer_set_isCollapsed(lua_State* L) |
331 | { |
332 | auto layer = get_docobj<Layer>(L, 1); |
333 | layer->setCollapsed(lua_toboolean(L, 2)); |
334 | return 0; |
335 | } |
336 | |
337 | int Layer_set_isExpanded(lua_State* L) |
338 | { |
339 | auto layer = get_docobj<Layer>(L, 1); |
340 | layer->setCollapsed(!lua_toboolean(L, 2)); |
341 | return 0; |
342 | } |
343 | |
344 | int Layer_set_parent(lua_State* L) |
345 | { |
346 | auto layer = get_docobj<Layer>(L, 1); |
347 | LayerGroup* parent = nullptr; |
348 | if (auto sprite = may_get_docobj<Sprite>(L, 2)) { |
349 | parent = sprite->root(); |
350 | } |
351 | else if (auto parentLayer = may_get_docobj<Layer>(L, 2)) { |
352 | if (parentLayer->isGroup()) |
353 | parent = static_cast<LayerGroup*>(parentLayer); |
354 | else |
355 | return luaL_error(L, "the given parent is not a layer group or sprite" ); |
356 | } |
357 | |
358 | if (!parent) |
359 | return luaL_error(L, "parent cannot be nil" ); |
360 | else if (parent == layer) |
361 | return luaL_error(L, "the parent of a layer cannot be the layer itself" ); |
362 | |
363 | // TODO Why? should we be able to do this? It would require some hard work: |
364 | // 1. convert color modes |
365 | // 2. multiple transactions for both modified sprites? |
366 | if (parent->sprite() != layer->sprite()) |
367 | return luaL_error(L, "you cannot move layers between sprites" ); |
368 | |
369 | if (parent) { |
370 | Doc* doc = static_cast<Doc*>(layer->sprite()->document()); |
371 | Tx tx; |
372 | DocApi(doc, tx).restackLayerAfter( |
373 | layer, parent, parent->lastLayer()); |
374 | tx.commit(); |
375 | } |
376 | return 0; |
377 | } |
378 | |
379 | const luaL_Reg Layer_methods[] = { |
380 | { "__eq" , Layer_eq }, |
381 | { "cel" , Layer_cel }, |
382 | { nullptr, nullptr } |
383 | }; |
384 | |
385 | const Property Layer_properties[] = { |
386 | { "sprite" , Layer_get_sprite, nullptr }, |
387 | { "parent" , Layer_get_parent, Layer_set_parent }, |
388 | { "layers" , Layer_get_layers, nullptr }, |
389 | { "stackIndex" , Layer_get_stackIndex, Layer_set_stackIndex }, |
390 | { "previous" , Layer_get_previous, nullptr }, |
391 | { "next" , Layer_get_next, nullptr }, |
392 | { "name" , Layer_get_name, Layer_set_name }, |
393 | { "opacity" , Layer_get_opacity, Layer_set_opacity }, |
394 | { "blendMode" , Layer_get_blendMode, Layer_set_blendMode }, |
395 | { "isImage" , Layer_get_isImage, nullptr }, |
396 | { "isGroup" , Layer_get_isGroup, nullptr }, |
397 | { "isTilemap" , Layer_get_isTilemap, nullptr }, |
398 | { "isTransparent" , Layer_get_isTransparent, nullptr }, |
399 | { "isBackground" , Layer_get_isBackground, nullptr }, |
400 | { "isEditable" , Layer_get_isEditable, Layer_set_isEditable }, |
401 | { "isVisible" , Layer_get_isVisible, Layer_set_isVisible }, |
402 | { "isContinuous" , Layer_get_isContinuous, Layer_set_isContinuous }, |
403 | { "isCollapsed" , Layer_get_isCollapsed, Layer_set_isCollapsed }, |
404 | { "isExpanded" , Layer_get_isExpanded, Layer_set_isExpanded }, |
405 | { "isReference" , Layer_get_isReference, nullptr }, |
406 | { "cels" , Layer_get_cels, nullptr }, |
407 | { "color" , UserData_get_color<Layer>, UserData_set_color<Layer> }, |
408 | { "data" , UserData_get_text<Layer>, UserData_set_text<Layer> }, |
409 | { "tileset" , Layer_get_tileset, nullptr }, |
410 | { nullptr, nullptr, nullptr } |
411 | }; |
412 | |
413 | } // anonymous namespace |
414 | |
415 | DEF_MTNAME(Layer); |
416 | |
417 | void register_layer_class(lua_State* L) |
418 | { |
419 | using Layer = doc::Layer; |
420 | REG_CLASS(L, Layer); |
421 | REG_CLASS_PROPERTIES(L, Layer); |
422 | } |
423 | |
424 | } // namespace script |
425 | } // namespace app |
426 | |