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
23namespace app {
24namespace script {
25
26using namespace doc;
27
28namespace {
29
30struct 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
78int 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
90int 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
98int 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
112int 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
132template<typename T>
133void replace(Mask& dst, const Mask& a, const T& b) {
134 if (b.isEmpty())
135 dst.clear();
136 else
137 dst.replace(b);
138}
139
140template<typename T>
141void 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
148template<typename T>
149void 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
156template<typename T>
157void 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
167template<typename OpMask, typename OpRect>
168int 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
212int Selection_select(lua_State* L) { return Selection_op(L, replace<Mask>, replace<gfx::Rect>); }
213int Selection_add(lua_State* L) { return Selection_op(L, add<Mask>, add<gfx::Rect>); }
214int Selection_subtract(lua_State* L) { return Selection_op(L, subtract<Mask>, subtract<gfx::Rect>); }
215int Selection_intersect(lua_State* L) { return Selection_op(L, intersect<Mask>, intersect<gfx::Rect>); }
216
217int 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
239int 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
248int 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
267int 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
286int 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
305int 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
313const 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
326const 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
335DEF_MTNAME(SelectionObj);
336DEF_MTNAME_ALIAS(SelectionObj, Mask);
337
338void 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
346void push_sprite_selection(lua_State* L, Sprite* sprite)
347{
348 push_new<SelectionObj>(L, nullptr, sprite);
349}
350
351const 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