1// Aseprite
2// Copyright (C) 2018-2022 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/app.h"
13#include "app/commands/commands.h"
14#include "app/commands/params.h"
15#include "app/context.h"
16#include "app/doc.h"
17#include "app/doc_access.h"
18#include "app/i18n/strings.h"
19#include "app/inline_command_execution.h"
20#include "app/loop_tag.h"
21#include "app/modules/palettes.h"
22#include "app/pref/preferences.h"
23#include "app/script/api_version.h"
24#include "app/script/docobj.h"
25#include "app/script/engine.h"
26#include "app/script/luacpp.h"
27#include "app/script/security.h"
28#include "app/site.h"
29#include "app/tools/active_tool.h"
30#include "app/tools/ink.h"
31#include "app/tools/tool_box.h"
32#include "app/tools/tool_loop.h"
33#include "app/tools/tool_loop_manager.h"
34#include "app/tx.h"
35#include "app/ui/context_bar.h"
36#include "app/ui/doc_view.h"
37#include "app/ui/editor/editor.h"
38#include "app/ui/editor/tool_loop_impl.h"
39#include "app/ui/timeline/timeline.h"
40#include "app/ui_context.h"
41#include "base/fs.h"
42#include "base/replace_string.h"
43#include "base/version.h"
44#include "doc/layer.h"
45#include "doc/primitives.h"
46#include "doc/tag.h"
47#include "render/render.h"
48#include "ui/alert.h"
49#include "ver/info.h"
50
51#include <cstring>
52#include <iostream>
53
54namespace app {
55namespace script {
56
57int load_sprite_from_file(lua_State* L, const char* filename,
58 const LoadSpriteFromFileParam param)
59{
60 std::string absFn = base::get_absolute_path(filename);
61 if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, ResourceType::File))
62 return luaL_error(L, "script doesn't have access to open file %s",
63 absFn.c_str());
64
65 app::Context* ctx = App::instance()->context();
66 Doc* oldDoc = ctx->activeDocument();
67
68 Command* openCommand =
69 Commands::instance()->byId(CommandId::OpenFile());
70 Params params;
71 params.set("filename", absFn.c_str());
72 if (param == LoadSpriteFromFileParam::OneFrameAsSprite ||
73 param == LoadSpriteFromFileParam::OneFrameAsImage)
74 params.set("oneframe", "true");
75 ctx->executeCommand(openCommand, params);
76
77 Doc* newDoc = ctx->activeDocument();
78 if (newDoc != oldDoc) {
79 if (param == LoadSpriteFromFileParam::OneFrameAsImage) {
80 doc::Sprite* sprite = newDoc->sprite();
81
82 // Render the first frame of the sprite
83 // TODO add "frame" parameter to render different frames
84 std::unique_ptr<doc::Image> image(doc::Image::create(sprite->spec()));
85 doc::clear_image(image.get(), sprite->transparentColor());
86 render::Render().renderSprite(image.get(), sprite, 0);
87
88 // Restore the old document and active and destroy the recently
89 // loaded sprite.
90 ctx->setActiveDocument(oldDoc);
91
92 try {
93 DocDestroyer destroyer(ctx, newDoc, 500);
94 destroyer.destroyDocument();
95 }
96 catch (const LockedDocException& ex) {
97 // Almost impossible?
98 luaL_error(L, "cannot lock document to close it\n%s", ex.what());
99 }
100
101 push_image(L, image.release());
102 return 1;
103 }
104 else {
105 push_docobj(L, newDoc->sprite());
106 }
107 }
108 else
109 lua_pushnil(L);
110 return 1;
111}
112
113namespace {
114
115int App_open(lua_State* L)
116{
117 return load_sprite_from_file(
118 L, luaL_checkstring(L, 1), LoadSpriteFromFileParam::FullAniAsSprite);
119}
120
121int App_exit(lua_State* L)
122{
123 app::Context* ctx = App::instance()->context();
124 if (ctx && ctx->isUIAvailable()) {
125 Command* exitCommand =
126 Commands::instance()->byId(CommandId::Exit());
127 ctx->executeCommand(exitCommand);
128 }
129 return 0;
130}
131
132int App_transaction(lua_State* L)
133{
134 int top = lua_gettop(L);
135 int nresults = 0;
136 if (lua_isfunction(L, 1)) {
137 Tx tx; // Create a new transaction so it exists in the whole
138 // duration of the argument function call.
139 lua_pushvalue(L, -1);
140 if (lua_pcall(L, 0, LUA_MULTRET, 0) == LUA_OK)
141 tx.commit();
142 else
143 return lua_error(L); // pcall already put an error object on the stack
144 nresults = lua_gettop(L) - top;
145 }
146 return nresults;
147}
148
149int App_undo(lua_State* L)
150{
151 app::Context* ctx = App::instance()->context();
152 if (ctx) {
153 Command* undo = Commands::instance()->byId(CommandId::Undo());
154 ctx->executeCommand(undo);
155 }
156 return 0;
157}
158
159int App_redo(lua_State* L)
160{
161 app::Context* ctx = App::instance()->context();
162 if (ctx) {
163 Command* redo = Commands::instance()->byId(CommandId::Redo());
164 ctx->executeCommand(redo);
165 }
166 return 0;
167}
168
169int App_alert(lua_State* L)
170{
171#ifdef ENABLE_UI
172 app::Context* ctx = App::instance()->context();
173 if (!ctx || !ctx->isUIAvailable())
174 return 0; // No UI to show the alert
175 // app.alert("text...")
176 else if (lua_isstring(L, 1)) {
177 ui::AlertPtr alert(new ui::Alert);
178 alert->addLabel(lua_tostring(L, 1), ui::CENTER);
179 alert->addButton(Strings::general_ok());
180 lua_pushinteger(L, alert->show());
181 return 1;
182 }
183 // app.alert{ ... }
184 else if (lua_istable(L, 1)) {
185 ui::AlertPtr alert(new ui::Alert);
186
187 int type = lua_getfield(L, 1, "title");
188 if (type != LUA_TNIL)
189 alert->setTitle(lua_tostring(L, -1));
190 lua_pop(L, 1);
191
192 type = lua_getfield(L, 1, "text");
193 if (type == LUA_TTABLE) {
194 lua_pushnil(L);
195 while (lua_next(L, -2) != 0) {
196 const char* v = luaL_tolstring(L, -1, nullptr);
197 if (v)
198 alert->addLabel(v, ui::LEFT);
199 lua_pop(L, 2);
200 }
201 }
202 else if (type == LUA_TSTRING) {
203 alert->addLabel(lua_tostring(L, -1), ui::LEFT);
204 }
205 lua_pop(L, 1);
206
207 int nbuttons = 0;
208 type = lua_getfield(L, 1, "buttons");
209 if (type == LUA_TTABLE) {
210 lua_pushnil(L);
211 while (lua_next(L, -2) != 0) {
212 const char* v = luaL_tolstring(L, -1, nullptr);
213 if (v) {
214 alert->addButton(v);
215 ++nbuttons;
216 }
217 lua_pop(L, 2);
218 }
219 }
220 else if (type == LUA_TSTRING) {
221 alert->addButton(lua_tostring(L, -1));
222 ++nbuttons;
223 }
224 lua_pop(L, 1);
225
226 if (nbuttons == 0)
227 alert->addButton(Strings::general_ok());
228
229 lua_pushinteger(L, alert->show());
230 return 1;
231 }
232#endif
233 return 0;
234}
235
236int App_refresh(lua_State* L)
237{
238#ifdef ENABLE_UI
239 app::Context* ctx = App::instance()->context();
240 if (ctx && ctx->isUIAvailable())
241 app_refresh_screen();
242#endif
243 return 0;
244}
245
246int App_useTool(lua_State* L)
247{
248 // First argument must be a table
249 if (!lua_istable(L, 1))
250 return luaL_error(L, "app.useTool() must be called with a table as its first argument");
251
252 auto ctx = App::instance()->context();
253 Site site = ctx->activeSite();
254
255 // Draw in a specific cel, layer, or frame
256 int type = lua_getfield(L, 1, "layer");
257 if (type != LUA_TNIL) {
258 if (auto layer = get_docobj<Layer>(L, -1)) {
259 site.document(static_cast<Doc*>(layer->sprite()->document()));
260 site.sprite(layer->sprite());
261 site.layer(layer);
262 }
263 }
264 lua_pop(L, 1);
265
266 type = lua_getfield(L, 1, "frame");
267 if (type != LUA_TNIL) {
268 site.frame(get_frame_number_from_arg(L, -1));
269 }
270 lua_pop(L, 1);
271
272 type = lua_getfield(L, 1, "cel");
273 if (type != LUA_TNIL) {
274 if (auto cel = get_docobj<Cel>(L, -1)) {
275 site.document(static_cast<Doc*>(cel->sprite()->document()));
276 site.sprite(cel->sprite());
277 site.layer(cel->layer());
278 site.frame(cel->frame());
279 }
280 }
281 lua_pop(L, 1);
282
283 if (!site.document())
284 return luaL_error(L, "there is no active document to draw with the tool");
285
286 // Options to create the ToolLoop (tool, ink, color, opacity, etc.)
287 ToolLoopParams params;
288
289 // Mouse button
290 params.button = tools::ToolLoop::Left;
291 type = lua_getfield(L, 1, "button");
292 if (type != LUA_TNIL) {
293 // Only supported button at the moment left (default) or right
294 if (lua_tointeger(L, -1) == (int)ui::kButtonRight)
295 params.button = tools::ToolLoop::Right;
296 }
297 lua_pop(L, 1);
298
299 // Select tool by name
300 const int buttonIdx = (params.button == tools::ToolLoop::Left ? 0: 1);
301 auto activeToolMgr = App::instance()->activeToolManager();
302 params.tool = activeToolMgr->activeTool();
303 params.ink = params.tool->getInk(buttonIdx);
304 params.controller = params.tool->getController(buttonIdx);
305 type = lua_getfield(L, 1, "tool");
306 if (type != LUA_TNIL) {
307 if (auto toolArg = get_tool_from_arg(L, -1)) {
308 params.tool = toolArg;
309 params.ink = params.tool->getInk(buttonIdx);
310 params.controller = params.tool->getController(buttonIdx);
311 }
312 else
313 return luaL_error(L, "invalid tool specified in app.useTool() function");
314 }
315 lua_pop(L, 1);
316
317 // Select ink by name
318 type = lua_getfield(L, 1, "ink");
319 if (type != LUA_TNIL)
320 params.inkType = get_value_from_lua<tools::InkType>(L, -1);
321 lua_pop(L, 1);
322
323 // Color
324 type = lua_getfield(L, 1, "color");
325 if (type != LUA_TNIL)
326 params.fg = convert_args_into_color(L, -1);
327 else {
328 // Default color is the active fgColor
329 params.fg = Preferences::instance().colorBar.fgColor();
330 }
331 lua_pop(L, 1);
332
333 type = lua_getfield(L, 1, "bgColor");
334 if (type != LUA_TNIL)
335 params.bg = convert_args_into_color(L, -1);
336 else
337 params.bg = params.fg;
338 lua_pop(L, 1);
339
340 // Adjust ink depending on "inkType" and "color"
341 // (e.g. InkType::SIMPLE depends on the color too, to adjust
342 // eraser/alpha compositing/opaque depending on the color alpha
343 // value).
344 params.ink = activeToolMgr->adjustToolInkDependingOnSelectedInkType(
345 params.ink, params.inkType, params.fg);
346
347 // Brush
348 type = lua_getfield(L, 1, "brush");
349 if (type != LUA_TNIL)
350 params.brush = get_brush_from_arg(L, -1);
351 else {
352 // Default brush is the active brush in the context bar
353#ifdef ENABLE_UI
354 if (App::instance()->isGui() &&
355 App::instance()->contextBar()) {
356 params.brush = App::instance()
357 ->contextBar()->activeBrush(params.tool,
358 params.ink);
359 }
360#endif
361 }
362 lua_pop(L, 1);
363 if (!params.brush) {
364 // In case the brush is nullptr (e.g. there is no UI) we use the
365 // default 1 pixel brush (e.g. to run scripts from CLI).
366 params.brush.reset(new Brush(BrushType::kCircleBrushType, 1, 0));
367 }
368
369 // Opacity, tolerance, and others
370 type = lua_getfield(L, 1, "opacity");
371 if (type != LUA_TNIL) {
372 params.opacity = lua_tointeger(L, -1);
373 params.opacity = std::clamp(params.opacity, 0, 255);
374 }
375 lua_pop(L, 1);
376
377 type = lua_getfield(L, 1, "tolerance");
378 if (type != LUA_TNIL) {
379 params.tolerance = lua_tointeger(L, -1);
380 params.tolerance = std::clamp(params.tolerance, 0, 255);
381 }
382 lua_pop(L, 1);
383
384 type = lua_getfield(L, 1, "contiguous");
385 if (type != LUA_TNIL)
386 params.contiguous = lua_toboolean(L, -1);
387 lua_pop(L, 1);
388
389 type = lua_getfield(L, 1, "freehandAlgorithm");
390 if (type != LUA_TNIL)
391 params.freehandAlgorithm = get_value_from_lua<tools::FreehandAlgorithm>(L, -1);
392 lua_pop(L, 1);
393
394 if (params.ink->isSelection()) {
395 gen::SelectionMode selectionMode = Preferences::instance().selection.mode();
396
397 type = lua_getfield(L, 1, "selection");
398 if (type != LUA_TNIL)
399 selectionMode = get_value_from_lua<gen::SelectionMode>(L, -1);
400 lua_pop(L, 1);
401
402 switch (selectionMode) {
403 case gen::SelectionMode::REPLACE:
404 params.modifiers = tools::ToolLoopModifiers::kReplaceSelection;
405 break;
406 case gen::SelectionMode::ADD:
407 params.modifiers = tools::ToolLoopModifiers::kAddSelection;
408 break;
409 case gen::SelectionMode::SUBTRACT:
410 params.modifiers = tools::ToolLoopModifiers::kSubtractSelection;
411 break;
412 case gen::SelectionMode::INTERSECT:
413 params.modifiers = tools::ToolLoopModifiers::kIntersectSelection;
414 break;
415 }
416 }
417
418 // Are we going to modify pixels or tiles?
419 type = lua_getfield(L, 1, "tilemapMode");
420 if (type != LUA_TNIL) {
421 site.tilemapMode(TilemapMode(lua_tointeger(L, -1)));
422 }
423 lua_pop(L, 1);
424
425 // How the tileset must be modified depending on this tool usage
426 type = lua_getfield(L, 1, "tilesetMode");
427 if (type != LUA_TNIL) {
428 site.tilesetMode(TilesetMode(lua_tointeger(L, -1)));
429 }
430 lua_pop(L, 1);
431
432 // Do the tool loop
433 type = lua_getfield(L, 1, "points");
434 if (type == LUA_TTABLE) {
435 InlineCommandExecution inlineCmd(ctx);
436
437 std::unique_ptr<tools::ToolLoop> loop(
438 create_tool_loop_for_script(ctx, site, params));
439 if (!loop)
440 return luaL_error(L, "cannot draw in the active site");
441
442 tools::ToolLoopManager manager(loop.get());
443 tools::Pointer lastPointer;
444 bool first = true;
445
446 lua_pushnil(L);
447 while (lua_next(L, -2) != 0) {
448 gfx::Point pt = convert_args_into_point(L, -1);
449
450 tools::Pointer pointer(
451 pt,
452 // TODO configurable params
453 tools::Vec2(0.0f, 0.0f),
454 tools::Pointer::Button::Left,
455 tools::Pointer::Type::Unknown,
456 0.0f);
457 if (first) {
458 first = false;
459 manager.prepareLoop(pointer);
460 manager.pressButton(pointer);
461 }
462 else {
463 manager.movement(pointer);
464 }
465 lastPointer = pointer;
466 lua_pop(L, 1);
467 }
468 if (!first)
469 manager.releaseButton(lastPointer);
470
471 manager.end();
472 }
473 lua_pop(L, 1);
474 return 0;
475}
476
477int App_get_events(lua_State* L)
478{
479 push_app_events(L);
480 return 1;
481}
482
483int App_get_activeSprite(lua_State* L)
484{
485 app::Context* ctx = App::instance()->context();
486 Doc* doc = ctx->activeDocument();
487 if (doc)
488 push_docobj(L, doc->sprite());
489 else
490 lua_pushnil(L);
491 return 1;
492}
493
494int App_get_activeLayer(lua_State* L)
495{
496 app::Context* ctx = App::instance()->context();
497 Site site = ctx->activeSite();
498 if (site.layer())
499 push_docobj(L, site.layer());
500 else
501 lua_pushnil(L);
502 return 1;
503}
504
505int App_get_activeFrame(lua_State* L)
506{
507 app::Context* ctx = App::instance()->context();
508 Site site = ctx->activeSite();
509 if (site.sprite())
510 push_sprite_frame(L, site.sprite(), site.frame());
511 else
512 lua_pushnil(L);
513 return 1;
514}
515
516int App_get_activeCel(lua_State* L)
517{
518 app::Context* ctx = App::instance()->context();
519 Site site = ctx->activeSite();
520 if (site.cel())
521 push_sprite_cel(L, site.cel());
522 else
523 lua_pushnil(L);
524 return 1;
525}
526
527int App_get_activeImage(lua_State* L)
528{
529 app::Context* ctx = App::instance()->context();
530 Site site = ctx->activeSite();
531 if (site.cel())
532 push_cel_image(L, site.cel());
533 else
534 lua_pushnil(L);
535 return 1;
536}
537
538int App_get_activeTag(lua_State* L)
539{
540 Tag* tag = nullptr;
541
542 app::Context* ctx = App::instance()->context();
543 Site site = ctx->activeSite();
544 if (site.sprite()) {
545#ifdef ENABLE_UI
546 if (App::instance()->timeline()) {
547 tag = App::instance()->timeline()->getTagByFrame(site.frame(), false);
548 }
549 else
550#endif
551 {
552 tag = get_animation_tag(site.sprite(), site.frame());
553 }
554 }
555
556 if (tag)
557 push_docobj(L, tag);
558 else
559 lua_pushnil(L);
560 return 1;
561}
562
563int App_get_sprites(lua_State* L)
564{
565 push_sprites(L);
566 return 1;
567}
568
569int App_get_fgColor(lua_State* L)
570{
571 push_obj<app::Color>(L, Preferences::instance().colorBar.fgColor());
572 return 1;
573}
574
575int App_set_fgColor(lua_State* L)
576{
577 Preferences::instance().colorBar.fgColor(convert_args_into_color(L, 2));
578 return 0;
579}
580
581int App_get_bgColor(lua_State* L)
582{
583 push_obj<app::Color>(L, Preferences::instance().colorBar.bgColor());
584 return 1;
585}
586
587int App_set_bgColor(lua_State* L)
588{
589 Preferences::instance().colorBar.bgColor(convert_args_into_color(L, 2));
590 return 0;
591}
592
593int App_get_site(lua_State* L)
594{
595 app::Context* ctx = App::instance()->context();
596 Site site = ctx->activeSite();
597 push_obj(L, site);
598 return 1;
599}
600
601int App_get_range(lua_State* L)
602{
603 app::Context* ctx = App::instance()->context();
604 Site site = ctx->activeSite();
605 push_doc_range(L, site);
606 return 1;
607}
608
609int App_get_isUIAvailable(lua_State* L)
610{
611 app::Context* ctx = App::instance()->context();
612 lua_pushboolean(L, ctx && ctx->isUIAvailable());
613 return 1;
614}
615
616int App_get_version(lua_State* L)
617{
618 std::string ver = get_app_version();
619 base::replace_string(ver, "-x64", ""); // Remove "-x64" suffix
620 push_version(L, base::Version(ver));
621 return 1;
622}
623
624int App_get_apiVersion(lua_State* L)
625{
626 lua_pushinteger(L, API_VERSION);
627 return 1;
628}
629
630int App_get_activeTool(lua_State* L)
631{
632 tools::Tool* tool = App::instance()->activeToolManager()->activeTool();
633 push_tool(L, tool);
634 return 1;
635}
636
637int App_get_activeBrush(lua_State* L)
638{
639#if ENABLE_UI
640 App* app = App::instance();
641 if (app->isGui()) {
642 doc::BrushRef brush = app->contextBar()->activeBrush();
643 push_brush(L, brush);
644 return 1;
645 }
646#endif
647 push_brush(L, doc::BrushRef(new doc::Brush()));
648 return 1;
649}
650
651int App_get_defaultPalette(lua_State* L)
652{
653 const Palette* pal = get_default_palette();
654 if (pal)
655 push_palette(L, new Palette(*pal));
656 else
657 lua_pushnil(L);
658 return 1;
659}
660
661int App_set_activeSprite(lua_State* L)
662{
663 auto sprite = get_docobj<Sprite>(L, 2);
664 app::Context* ctx = App::instance()->context();
665 doc::Document* doc = sprite->document();
666 ctx->setActiveDocument(static_cast<Doc*>(doc));
667 return 0;
668}
669
670int App_set_activeLayer(lua_State* L)
671{
672 auto layer = get_docobj<Layer>(L, 2);
673 app::Context* ctx = App::instance()->context();
674 ctx->setActiveLayer(layer);
675 return 0;
676}
677
678int App_set_activeFrame(lua_State* L)
679{
680 const doc::frame_t frame = get_frame_number_from_arg(L, 2);
681 app::Context* ctx = App::instance()->context();
682 ctx->setActiveFrame(frame);
683 return 0;
684}
685
686int App_set_activeCel(lua_State* L)
687{
688 const auto cel = get_docobj<Cel>(L, 2);
689 app::Context* ctx = App::instance()->context();
690 ctx->setActiveLayer(cel->layer());
691 ctx->setActiveFrame(cel->frame());
692 return 0;
693}
694
695int App_set_activeImage(lua_State* L)
696{
697 const auto cel = get_image_cel_from_arg(L, 2);
698 if (!cel)
699 return 0;
700
701 app::Context* ctx = App::instance()->context();
702 ctx->setActiveLayer(cel->layer());
703 ctx->setActiveFrame(cel->frame());
704 return 0;
705}
706
707int App_set_activeTool(lua_State* L)
708{
709 if (auto tool = get_tool_from_arg(L, 2))
710 App::instance()->activeToolManager()->setSelectedTool(tool);
711 return 0;
712}
713
714int App_set_activeBrush(lua_State* L)
715{
716#if ENABLE_UI
717 if (auto brush = get_brush_from_arg(L, 2)) {
718 App* app = App::instance();
719 if (app->isGui())
720 app->contextBar()->setActiveBrush(brush);
721 }
722#endif
723 return 0;
724}
725
726int App_set_defaultPalette(lua_State* L)
727{
728 if (const doc::Palette* pal = get_palette_from_arg(L, 2))
729 set_default_palette(pal);
730 return 0;
731}
732
733const luaL_Reg App_methods[] = {
734 { "open", App_open },
735 { "exit", App_exit },
736 { "transaction", App_transaction },
737 { "undo", App_undo },
738 { "redo", App_redo },
739 { "alert", App_alert },
740 { "refresh", App_refresh },
741 { "useTool", App_useTool },
742 { nullptr, nullptr }
743};
744
745const Property App_properties[] = {
746 { "activeSprite", App_get_activeSprite, App_set_activeSprite },
747 { "activeLayer", App_get_activeLayer, App_set_activeLayer },
748 { "activeFrame", App_get_activeFrame, App_set_activeFrame },
749 { "activeCel", App_get_activeCel, App_set_activeCel },
750 { "activeImage", App_get_activeImage, App_set_activeImage },
751 { "activeTag", App_get_activeTag, nullptr },
752 { "activeTool", App_get_activeTool, App_set_activeTool },
753 { "activeBrush", App_get_activeBrush, App_set_activeBrush },
754 { "sprites", App_get_sprites, nullptr },
755 { "fgColor", App_get_fgColor, App_set_fgColor },
756 { "bgColor", App_get_bgColor, App_set_bgColor },
757 { "version", App_get_version, nullptr },
758 { "apiVersion", App_get_apiVersion, nullptr },
759 { "site", App_get_site, nullptr },
760 { "range", App_get_range, nullptr },
761 { "isUIAvailable", App_get_isUIAvailable, nullptr },
762 { "defaultPalette", App_get_defaultPalette, App_set_defaultPalette },
763 { "events", App_get_events, nullptr },
764 { nullptr, nullptr, nullptr }
765};
766
767} // anonymous namespace
768
769DEF_MTNAME(App);
770
771void register_app_object(lua_State* L)
772{
773 REG_CLASS(L, App);
774 REG_CLASS_PROPERTIES(L, App);
775
776 lua_newtable(L); // Create a table which will be the "app" object
777 lua_pushvalue(L, -1);
778 luaL_getmetatable(L, get_mtname<App>());
779 lua_setmetatable(L, -2);
780 lua_setglobal(L, "app");
781 lua_pop(L, 1); // Pop app table
782}
783
784void set_app_params(lua_State* L, const Params& params)
785{
786 lua_getglobal(L, "app");
787 lua_newtable(L);
788 for (const auto& kv : params) {
789 lua_pushstring(L, kv.second.c_str());
790 lua_setfield(L, -2, kv.first.c_str());
791 }
792 lua_setfield(L, -2, "params");
793 lua_pop(L, 1);
794}
795
796} // namespace script
797} // namespace app
798