1 | // Aseprite |
2 | // Copyright (C) 2018-2020 Igara Studio S.A. |
3 | // Copyright (C) 2015-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/deselect_mask.h" |
13 | #include "app/cmd/set_mask.h" |
14 | #include "app/doc.h" |
15 | #include "app/doc_api.h" |
16 | #include "app/script/docobj.h" |
17 | #include "app/script/engine.h" |
18 | #include "app/script/luacpp.h" |
19 | #include "app/transaction.h" |
20 | #include "app/tx.h" |
21 | #include "doc/mask.h" |
22 | |
23 | namespace app { |
24 | namespace script { |
25 | |
26 | using namespace doc; |
27 | |
28 | namespace { |
29 | |
30 | struct SelectionObj { |
31 | ObjectId maskId; |
32 | ObjectId spriteId; |
33 | SelectionObj(Mask* mask, Sprite* sprite) |
34 | : maskId(mask ? mask->id(): 0) |
35 | , spriteId(sprite ? sprite->id(): 0) { |
36 | } |
37 | SelectionObj(const SelectionObj&) = delete; |
38 | SelectionObj& operator=(const SelectionObj&) = delete; |
39 | |
40 | ~SelectionObj() { |
41 | ASSERT(!maskId); |
42 | } |
43 | |
44 | void gc(lua_State* L) { |
45 | if (!spriteId) |
46 | delete this->mask(L); |
47 | maskId = 0; |
48 | } |
49 | |
50 | Mask* mask(lua_State* L) { |
51 | if (maskId) |
52 | return check_docobj(L, doc::get<Mask>(maskId)); |
53 | else { |
54 | auto doc = static_cast<Doc*>(sprite(L)->document()); |
55 | ASSERT(doc); |
56 | |
57 | // The selection might be invisible but has something (e.g. when |
58 | // the user calls "Select > Deselect" option). In this case we |
59 | // want to show to the script an empty selection, so we'll clear |
60 | // the invisible selection so the script sees it empty. |
61 | // |
62 | // This breaks the "Select > Reselect" command, but it looks |
63 | // like the expected behavior for script authors. |
64 | if (!doc->isMaskVisible()) |
65 | doc->mask()->clear(); |
66 | |
67 | return doc->mask(); |
68 | } |
69 | } |
70 | Sprite* sprite(lua_State* L) { |
71 | if (spriteId) |
72 | return check_docobj(L, doc::get<Sprite>(spriteId)); |
73 | else |
74 | return nullptr; |
75 | } |
76 | }; |
77 | |
78 | int Selection_new(lua_State* L) |
79 | { |
80 | gfx::Rect bounds = convert_args_into_rect(L, 1); |
81 | |
82 | auto mask = new Mask; |
83 | if (!bounds.isEmpty()) |
84 | mask->replace(bounds); |
85 | |
86 | push_new<SelectionObj>(L, mask, nullptr); |
87 | return 1; |
88 | } |
89 | |
90 | int Selection_gc(lua_State* L) |
91 | { |
92 | auto obj = get_obj<SelectionObj>(L, 1); |
93 | obj->gc(L); |
94 | obj->~SelectionObj(); |
95 | return 0; |
96 | } |
97 | |
98 | int Selection_eq(lua_State* L) |
99 | { |
100 | auto a = get_obj<SelectionObj>(L, 1); |
101 | auto b = get_obj<SelectionObj>(L, 2); |
102 | auto aMask = a->mask(L); |
103 | auto bMask = b->mask(L); |
104 | const bool result = |
105 | (aMask->isEmpty() && bMask->isEmpty()) || |
106 | (!aMask->isEmpty() && !bMask->isEmpty() && |
107 | is_same_image(aMask->bitmap(), bMask->bitmap())); |
108 | lua_pushboolean(L, result); |
109 | return 1; |
110 | } |
111 | |
112 | int Selection_deselect(lua_State* L) |
113 | { |
114 | auto obj = get_obj<SelectionObj>(L, 1); |
115 | if (auto sprite = obj->sprite(L)) { |
116 | Doc* doc = static_cast<Doc*>(sprite->document()); |
117 | ASSERT(doc); |
118 | |
119 | if (doc->isMaskVisible()) { |
120 | Tx tx; |
121 | tx(new cmd::DeselectMask(doc)); |
122 | tx.commit(); |
123 | } |
124 | } |
125 | else { |
126 | auto mask = obj->mask(L); |
127 | mask->clear(); |
128 | } |
129 | return 0; |
130 | } |
131 | |
132 | template<typename T> |
133 | void replace(Mask& dst, const Mask& a, const T& b) { |
134 | if (b.isEmpty()) |
135 | dst.clear(); |
136 | else |
137 | dst.replace(b); |
138 | } |
139 | |
140 | template<typename T> |
141 | void add(Mask& dst, const Mask& a, const T& b) { |
142 | if (&dst != &a) |
143 | dst.replace(a); |
144 | if (!b.isEmpty()) |
145 | dst.add(b); |
146 | } |
147 | |
148 | template<typename T> |
149 | void subtract(Mask& dst, const Mask& a, const T& b) { |
150 | if (&dst != &a) |
151 | dst.replace(a); |
152 | if (!b.isEmpty()) |
153 | dst.subtract(b); |
154 | } |
155 | |
156 | template<typename T> |
157 | void intersect(Mask& dst, const Mask& a, const T& b) { |
158 | if (b.isEmpty()) |
159 | dst.clear(); |
160 | else { |
161 | if (&dst != &a) |
162 | dst.replace(a); |
163 | dst.intersect(b); |
164 | } |
165 | } |
166 | |
167 | template<typename OpMask, typename OpRect> |
168 | int Selection_op(lua_State* L, OpMask opMask, OpRect opRect) |
169 | { |
170 | auto obj = get_obj<SelectionObj>(L, 1); |
171 | auto mask = obj->mask(L); |
172 | auto sprite = obj->sprite(L); |
173 | auto otherObj = may_get_obj<SelectionObj>(L, 2); |
174 | if (otherObj) { |
175 | auto otherMask = otherObj->mask(L); |
176 | if (sprite) { |
177 | Doc* doc = static_cast<Doc*>(sprite->document()); |
178 | ASSERT(doc); |
179 | |
180 | Mask newMask; |
181 | opMask(newMask, *mask, *otherMask); |
182 | |
183 | Tx tx; |
184 | tx(new cmd::SetMask(doc, &newMask)); |
185 | tx.commit(); |
186 | } |
187 | else { |
188 | opMask(*mask, *mask, *otherMask); |
189 | } |
190 | } |
191 | // Try with a rectangle |
192 | else { |
193 | gfx::Rect bounds = convert_args_into_rect(L, 2); |
194 | if (sprite) { |
195 | Doc* doc = static_cast<Doc*>(sprite->document()); |
196 | ASSERT(doc); |
197 | |
198 | Mask newMask; |
199 | opRect(newMask, *mask, bounds); |
200 | |
201 | Tx tx; |
202 | tx(new cmd::SetMask(doc, &newMask)); |
203 | tx.commit(); |
204 | } |
205 | else { |
206 | opRect(*mask, *mask, bounds); |
207 | } |
208 | } |
209 | return 0; |
210 | } |
211 | |
212 | int Selection_select(lua_State* L) { return Selection_op(L, replace<Mask>, replace<gfx::Rect>); } |
213 | int Selection_add(lua_State* L) { return Selection_op(L, add<Mask>, add<gfx::Rect>); } |
214 | int Selection_subtract(lua_State* L) { return Selection_op(L, subtract<Mask>, subtract<gfx::Rect>); } |
215 | int Selection_intersect(lua_State* L) { return Selection_op(L, intersect<Mask>, intersect<gfx::Rect>); } |
216 | |
217 | int Selection_selectAll(lua_State* L) |
218 | { |
219 | auto obj = get_obj<SelectionObj>(L, 1); |
220 | if (auto sprite = obj->sprite(L)) { |
221 | Doc* doc = static_cast<Doc*>(sprite->document()); |
222 | |
223 | Mask newMask; |
224 | newMask.replace(sprite->bounds()); |
225 | |
226 | Tx tx; |
227 | tx(new cmd::SetMask(doc, &newMask)); |
228 | tx.commit(); |
229 | } |
230 | else { |
231 | auto mask = obj->mask(L); |
232 | gfx::Rect bounds = mask->bounds(); |
233 | if (!bounds.isEmpty()) |
234 | mask->replace(bounds); |
235 | } |
236 | return 0; |
237 | } |
238 | |
239 | int Selection_contains(lua_State* L) |
240 | { |
241 | const auto obj = get_obj<SelectionObj>(L, 1); |
242 | const auto mask = obj->mask(L); |
243 | auto pt = convert_args_into_point(L, 2); |
244 | lua_pushboolean(L, mask->containsPoint(pt.x, pt.y)); |
245 | return 1; |
246 | } |
247 | |
248 | int Selection_get_bounds(lua_State* L) |
249 | { |
250 | const auto obj = get_obj<SelectionObj>(L, 1); |
251 | if (auto sprite = obj->sprite(L)) { |
252 | Doc* doc = static_cast<Doc*>(sprite->document()); |
253 | if (doc->isMaskVisible()) { |
254 | push_obj(L, doc->mask()->bounds()); |
255 | } |
256 | else { // Empty rectangle |
257 | push_new<gfx::Rect>(L, 0, 0, 0, 0); |
258 | } |
259 | } |
260 | else { |
261 | const auto mask = obj->mask(L); |
262 | push_obj(L, mask->bounds()); |
263 | } |
264 | return 1; |
265 | } |
266 | |
267 | int Selection_get_origin(lua_State* L) |
268 | { |
269 | const auto obj = get_obj<SelectionObj>(L, 1); |
270 | if (auto sprite = obj->sprite(L)) { |
271 | Doc* doc = static_cast<Doc*>(sprite->document()); |
272 | if (doc->isMaskVisible()) { |
273 | push_obj(L, doc->mask()->bounds().origin()); |
274 | } |
275 | else { |
276 | push_new<gfx::Point>(L, 0, 0); |
277 | } |
278 | } |
279 | else { |
280 | const auto mask = obj->mask(L); |
281 | push_obj(L, mask->bounds().origin()); |
282 | } |
283 | return 1; |
284 | } |
285 | |
286 | int Selection_set_origin(lua_State* L) |
287 | { |
288 | auto obj = get_obj<SelectionObj>(L, 1); |
289 | const auto pt = convert_args_into_point(L, 2); |
290 | if (auto sprite = obj->sprite(L)) { |
291 | Doc* doc = static_cast<Doc*>(sprite->document()); |
292 | if (doc->isMaskVisible()) { |
293 | Tx tx; |
294 | doc->getApi(tx).setMaskPosition(pt.x, pt.y); |
295 | tx.commit(); |
296 | } |
297 | } |
298 | else { |
299 | const auto mask = obj->mask(L); |
300 | mask->setOrigin(pt.x, pt.y); |
301 | } |
302 | return 0; |
303 | } |
304 | |
305 | int Selection_get_isEmpty(lua_State* L) |
306 | { |
307 | const auto obj = get_obj<SelectionObj>(L, 1); |
308 | const auto mask = obj->mask(L); |
309 | lua_pushboolean(L, mask->isEmpty()); |
310 | return 1; |
311 | } |
312 | |
313 | const luaL_Reg Selection_methods[] = { |
314 | { "deselect" , Selection_deselect }, |
315 | { "select" , Selection_select }, |
316 | { "selectAll" , Selection_selectAll }, |
317 | { "add" , Selection_add }, |
318 | { "subtract" , Selection_subtract }, |
319 | { "intersect" , Selection_intersect }, |
320 | { "contains" , Selection_contains }, |
321 | { "__gc" , Selection_gc }, |
322 | { "__eq" , Selection_eq }, |
323 | { nullptr, nullptr } |
324 | }; |
325 | |
326 | const Property Selection_properties[] = { |
327 | { "bounds" , Selection_get_bounds, nullptr }, |
328 | { "origin" , Selection_get_origin, Selection_set_origin }, |
329 | { "isEmpty" , Selection_get_isEmpty, nullptr }, |
330 | { nullptr, nullptr, nullptr } |
331 | }; |
332 | |
333 | } // anonymous namespace |
334 | |
335 | DEF_MTNAME(SelectionObj); |
336 | DEF_MTNAME_ALIAS(SelectionObj, Mask); |
337 | |
338 | void register_selection_class(lua_State* L) |
339 | { |
340 | using Selection = SelectionObj; |
341 | REG_CLASS(L, Selection); |
342 | REG_CLASS_NEW(L, Selection); |
343 | REG_CLASS_PROPERTIES(L, Selection); |
344 | } |
345 | |
346 | void push_sprite_selection(lua_State* L, Sprite* sprite) |
347 | { |
348 | push_new<SelectionObj>(L, nullptr, sprite); |
349 | } |
350 | |
351 | const doc::Mask* get_mask_from_arg(lua_State* L, int index) |
352 | { |
353 | return get_obj<SelectionObj>(L, index)->mask(L); |
354 | } |
355 | |
356 | } // namespace script |
357 | } // namespace app |
358 | |