aseprite/src/app/script/app_object.cpp

902 lines
23 KiB
C++
Raw Normal View History

2016-04-07 02:37:13 +08:00
// Aseprite
Fix transparent color is possible on opaque sprites (fix #4370) To reproduce the error before this fix on RGBA/Grayscale Color Mode: - New 100x100 RGBA/Grayscale opaque sprite (white background). - Draw something with some gray color in the palette. - Keep the selected gray color as primary color. - Configure as secondary color the mask color (#000000 alpha=0). - Pick 'eraser' tool and erase over the gray color with right click. - Result: The sprite doesn't look more opaque, which is wrong. Also, if we export this sprite, the transparent parts will turn black. A similar problem occurs in Indexed Color Mode, but getting a transparent color in a Background sprite is inevitable if the color of a palette entry is transparent or semi-transparent, since the index must be set as is. This could be fixed in the future at the render stage, however, this could lead to other perceived inconsistencies. For now it'll be left as is. Original issue description: Downloaded PNG in RGB mode fails to support transparency: erase uses secondary color and export PNG replaces transparent color with black Added tests for 'eraser' in 'Replace Color Mode' To make the eraser work in 'Replace Color Mode' within the tests, was implemented the possibility of using the right button in the creation of the point vector. During testing with UI available it was observed that the 'bg' color was copied from the 'fg'. Changed this to be compatible with the way the default value of 'fg' is assigned when it is not specified. This last modification resulted in errors during 'tilemap.lua' due to incompatibility of the type of 'bg' color. This was corrected considering the color type of 'fg' color. Furthermore, it was found that the command 'app.range.tiles = { 1 }' did not finish assigning the tile picks to the activeSite, then 'assert(1, #app.range.tiles)' was failing. This was fixed too.
2024-08-22 22:07:54 +08:00
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
2016-04-07 02:37:13 +08:00
//
2016-08-27 04:02:58 +08:00
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
2016-04-07 02:37:13 +08:00
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
2018-07-07 22:54:44 +08:00
#include "app/context.h"
#include "app/context_access.h"
2018-07-07 22:54:44 +08:00
#include "app/doc.h"
#include "app/doc_access.h"
2018-09-08 03:42:58 +08:00
#include "app/i18n/strings.h"
#include "app/inline_command_execution.h"
2018-10-26 21:52:40 +08:00
#include "app/loop_tag.h"
#include "app/modules/palettes.h"
2018-09-07 00:02:23 +08:00
#include "app/pref/preferences.h"
2018-11-14 09:31:14 +08:00
#include "app/script/api_version.h"
#include "app/script/docobj.h"
2018-08-20 22:25:08 +08:00
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "app/script/security.h"
#include "app/site.h"
2019-03-16 04:03:02 +08:00
#include "app/tools/active_tool.h"
#include "app/tools/ink.h"
2019-03-16 04:03:02 +08:00
#include "app/tools/tool_box.h"
#include "app/tools/tool_loop.h"
#include "app/tools/tool_loop_manager.h"
#include "app/tx.h"
#include "app/ui/context_bar.h"
#include "app/ui/doc_view.h"
#include "app/ui/editor/editor.h"
2019-03-16 04:03:02 +08:00
#include "app/ui/editor/tool_loop_impl.h"
2018-10-26 21:52:40 +08:00
#include "app/ui/timeline/timeline.h"
#include "app/ui/main_window.h"
#include "app/ui_context.h"
#include "base/fs.h"
2019-05-22 01:06:35 +08:00
#include "base/replace_string.h"
2019-04-21 11:04:49 +08:00
#include "base/version.h"
#include "doc/layer.h"
#include "doc/primitives.h"
2019-10-02 01:55:08 +08:00
#include "doc/tag.h"
#include "render/render.h"
2018-09-08 03:42:58 +08:00
#include "ui/alert.h"
2023-03-21 05:26:44 +08:00
#include "ui/scale.h"
#include "ver/info.h"
2016-04-07 02:37:13 +08:00
#include <cstring>
2016-04-07 02:37:13 +08:00
#include <iostream>
namespace app {
2018-08-20 22:25:08 +08:00
namespace script {
2016-04-07 02:37:13 +08:00
int load_sprite_from_file(lua_State* L, const char* filename,
const LoadSpriteFromFileParam param)
{
std::string absFn = base::get_absolute_path(filename);
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, ResourceType::File))
return luaL_error(L, "script doesn't have access to open file %s",
absFn.c_str());
2018-08-20 22:25:08 +08:00
app::Context* ctx = App::instance()->context();
Doc* oldDoc = ctx->activeDocument();
2017-11-30 22:57:18 +08:00
Command* openCommand =
Commands::instance()->byId(CommandId::OpenFile());
Params params;
params.set("filename", absFn.c_str());
if (param == LoadSpriteFromFileParam::OneFrameAsSprite ||
param == LoadSpriteFromFileParam::OneFrameAsImage)
params.set("oneframe", "true");
2018-08-20 22:25:08 +08:00
ctx->executeCommand(openCommand, params);
2018-08-20 22:25:08 +08:00
Doc* newDoc = ctx->activeDocument();
if (newDoc != oldDoc) {
if (param == LoadSpriteFromFileParam::OneFrameAsImage) {
doc::Sprite* sprite = newDoc->sprite();
// Render the first frame of the sprite
// TODO add "frame" parameter to render different frames
std::unique_ptr<doc::Image> image(doc::Image::create(sprite->spec()));
doc::clear_image(image.get(), sprite->transparentColor());
render::Render().renderSprite(image.get(), sprite, 0);
// Restore the old document and active and destroy the recently
// loaded sprite.
ctx->setActiveDocument(oldDoc);
try {
DocDestroyer destroyer(ctx, newDoc, 500);
destroyer.destroyDocument();
}
catch (const LockedDocException& ex) {
// Almost impossible?
luaL_error(L, "cannot lock document to close it\n%s", ex.what());
}
push_image(L, image.release());
return 1;
}
else {
push_docobj(L, newDoc->sprite());
}
}
else
2018-08-20 22:25:08 +08:00
lua_pushnil(L);
return 1;
}
namespace {
int App_open(lua_State* L)
{
return load_sprite_from_file(
L, luaL_checkstring(L, 1), LoadSpriteFromFileParam::FullAniAsSprite);
}
2018-08-20 22:25:08 +08:00
int App_exit(lua_State* L)
2016-05-07 03:49:43 +08:00
{
2018-08-20 22:25:08 +08:00
app::Context* ctx = App::instance()->context();
if (ctx && ctx->isUIAvailable()) {
2017-11-30 22:57:18 +08:00
Command* exitCommand =
Commands::instance()->byId(CommandId::Exit());
2018-08-20 22:25:08 +08:00
ctx->executeCommand(exitCommand);
}
2018-08-20 22:25:08 +08:00
return 0;
2016-05-07 03:49:43 +08:00
}
2018-08-20 22:25:08 +08:00
int App_transaction(lua_State* L)
{
2018-08-20 22:25:08 +08:00
int top = lua_gettop(L);
int nresults = 0;
int index = 1;
std::string label = Tx::kDefaultTransactionName;
// This can be:
//
// app.transaction(function)
// app.transaction(string, function)
//
// Where if the string is the first argument, it will be the
// transaction name/undo-redo label.
if (lua_isstring(L, index)) {
label = lua_tostring(L, index);
++index;
}
if (lua_isfunction(L, index)) {
app::Context* ctx = App::instance()->context();
if (!ctx)
return luaL_error(L, "no context");
try {
// We lock the document in the whole transaction because the
// RWLock now is re-entrant and we are able to call commands
// inside the app.transaction() (creating inner ContextWriters).
ContextWriter writer(ctx);
Tx tx(writer, label);
lua_pushvalue(L, -1);
if (lua_pcall(L, 0, LUA_MULTRET, 0) == LUA_OK)
tx.commit();
else
return lua_error(L); // pcall already put an error object on the stack
nresults = lua_gettop(L) - top;
}
catch (const LockedDocException& ex) {
return luaL_error(L, "cannot lock document for transaction\n%s", ex.what());
}
}
2018-08-20 22:25:08 +08:00
return nresults;
}
2018-08-20 22:25:08 +08:00
int App_undo(lua_State* L)
2018-08-21 01:34:14 +08:00
{
2018-08-20 22:25:08 +08:00
app::Context* ctx = App::instance()->context();
if (ctx) {
2018-08-21 01:34:14 +08:00
Command* undo = Commands::instance()->byId(CommandId::Undo());
2018-08-20 22:25:08 +08:00
ctx->executeCommand(undo);
2018-08-21 01:34:14 +08:00
}
2018-08-20 22:25:08 +08:00
return 0;
2018-08-21 01:34:14 +08:00
}
2018-08-20 22:25:08 +08:00
int App_redo(lua_State* L)
2018-08-21 01:34:14 +08:00
{
2018-08-20 22:25:08 +08:00
app::Context* ctx = App::instance()->context();
if (ctx) {
2018-08-21 01:34:14 +08:00
Command* redo = Commands::instance()->byId(CommandId::Redo());
2018-08-20 22:25:08 +08:00
ctx->executeCommand(redo);
2018-08-21 01:34:14 +08:00
}
2018-08-20 22:25:08 +08:00
return 0;
2018-08-21 01:34:14 +08:00
}
2018-09-08 03:42:58 +08:00
int App_alert(lua_State* L)
{
app::Context* ctx = App::instance()->context();
if (!ctx || !ctx->isUIAvailable())
return 0; // No UI to show the alert
// app.alert("text...")
else if (lua_isstring(L, 1)) {
ui::AlertPtr alert(new ui::Alert);
alert->addLabel(lua_tostring(L, 1), ui::CENTER);
alert->addButton(Strings::general_ok());
lua_pushinteger(L, alert->show());
return 1;
}
// app.alert{ ... }
else if (lua_istable(L, 1)) {
ui::AlertPtr alert(new ui::Alert);
int type = lua_getfield(L, 1, "title");
if (type != LUA_TNIL)
alert->setTitle(lua_tostring(L, -1));
lua_pop(L, 1);
type = lua_getfield(L, 1, "text");
if (type == LUA_TTABLE) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
const char* v = luaL_tolstring(L, -1, nullptr);
if (v)
alert->addLabel(v, ui::LEFT);
lua_pop(L, 2);
}
}
else if (type == LUA_TSTRING) {
alert->addLabel(lua_tostring(L, -1), ui::LEFT);
}
lua_pop(L, 1);
int nbuttons = 0;
type = lua_getfield(L, 1, "buttons");
if (type == LUA_TTABLE) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
const char* v = luaL_tolstring(L, -1, nullptr);
if (v) {
alert->addButton(v);
++nbuttons;
}
lua_pop(L, 2);
}
}
else if (type == LUA_TSTRING) {
alert->addButton(lua_tostring(L, -1));
++nbuttons;
}
lua_pop(L, 1);
if (nbuttons == 0)
alert->addButton(Strings::general_ok());
lua_pushinteger(L, alert->show());
return 1;
}
return 0;
}
2018-11-27 21:24:13 +08:00
int App_refresh(lua_State* L)
{
app::Context* ctx = App::instance()->context();
if (ctx && ctx->isUIAvailable())
app_refresh_screen();
2018-11-27 21:24:13 +08:00
return 0;
}
int App_useTool(lua_State* L)
2019-03-16 04:03:02 +08:00
{
// First argument must be a table
if (!lua_istable(L, 1))
return luaL_error(L, "app.useTool() must be called with a table as its first argument");
2019-03-16 04:03:02 +08:00
auto ctx = App::instance()->context();
Site site = ctx->activeSite();
// Draw in a specific cel, layer, or frame
int type = lua_getfield(L, 1, "layer");
if (type != LUA_TNIL) {
if (auto layer = get_docobj<Layer>(L, -1)) {
site.document(static_cast<Doc*>(layer->sprite()->document()));
site.sprite(layer->sprite());
site.layer(layer);
}
}
lua_pop(L, 1);
type = lua_getfield(L, 1, "frame");
if (type != LUA_TNIL) {
site.frame(get_frame_number_from_arg(L, -1));
}
lua_pop(L, 1);
type = lua_getfield(L, 1, "cel");
if (type != LUA_TNIL) {
if (auto cel = get_docobj<Cel>(L, -1)) {
site.document(static_cast<Doc*>(cel->sprite()->document()));
site.sprite(cel->sprite());
site.layer(cel->layer());
site.frame(cel->frame());
}
}
lua_pop(L, 1);
if (!site.document())
2019-03-16 04:03:02 +08:00
return luaL_error(L, "there is no active document to draw with the tool");
// Options to create the ToolLoop (tool, ink, color, opacity, etc.)
ToolLoopParams params;
// Mouse button
params.button = tools::ToolLoop::Left;
type = lua_getfield(L, 1, "button");
if (type != LUA_TNIL) {
// Only supported button at the moment left (default) or right
if (lua_tointeger(L, -1) == (int)ui::kButtonRight)
params.button = tools::ToolLoop::Right;
}
lua_pop(L, 1);
2019-03-16 04:03:02 +08:00
// Select tool by name
const int buttonIdx = (params.button == tools::ToolLoop::Left ? 0: 1);
auto activeToolMgr = App::instance()->activeToolManager();
params.tool = activeToolMgr->activeTool();
params.ink = params.tool->getInk(buttonIdx);
params.controller = params.tool->getController(buttonIdx);
type = lua_getfield(L, 1, "tool");
if (type != LUA_TNIL) {
if (auto toolArg = get_tool_from_arg(L, -1)) {
params.tool = toolArg;
params.ink = params.tool->getInk(buttonIdx);
params.controller = params.tool->getController(buttonIdx);
}
else
return luaL_error(L, "invalid tool specified in app.useTool() function");
2019-03-16 04:03:02 +08:00
}
lua_pop(L, 1);
// Select ink by name
type = lua_getfield(L, 1, "ink");
if (type != LUA_TNIL)
params.inkType = get_value_from_lua<tools::InkType>(L, -1);
lua_pop(L, 1);
// Are we going to modify pixels or tiles?
type = lua_getfield(L, 1, "tilemapMode");
if (type != LUA_TNIL) {
site.tilemapMode(TilemapMode(lua_tointeger(L, -1)));
}
lua_pop(L, 1);
// How the tileset must be modified depending on this tool usage
type = lua_getfield(L, 1, "tilesetMode");
if (type != LUA_TNIL) {
site.tilesetMode(TilesetMode(lua_tointeger(L, -1)));
}
lua_pop(L, 1);
// Color
2019-03-16 04:03:02 +08:00
type = lua_getfield(L, 1, "color");
if (type != LUA_TNIL)
params.fg = convert_args_into_color(L, -1);
else if (site.tilemapMode() == TilemapMode::Tiles)
params.fg = Color::fromTile(Preferences::instance().colorBar.fgTile());
else // Default color is the active fgColor
params.fg = Preferences::instance().colorBar.fgColor();
2019-03-16 04:03:02 +08:00
lua_pop(L, 1);
type = lua_getfield(L, 1, "bgColor");
if (type != LUA_TNIL)
params.bg = convert_args_into_color(L, -1);
else if (site.tilemapMode() == TilemapMode::Tiles)
params.bg = Color::fromTile(Preferences::instance().colorBar.bgTile());
else
params.bg = Preferences::instance().colorBar.bgColor();
lua_pop(L, 1);
// Adjust ink depending on "inkType" and "color"
// (e.g. InkType::SIMPLE depends on the color too, to adjust
// eraser/alpha compositing/opaque depending on the color alpha
// value).
params.ink = activeToolMgr->adjustToolInkDependingOnSelectedInkType(
params.ink, params.inkType, params.fg);
// Brush
type = lua_getfield(L, 1, "brush");
if (type != LUA_TNIL)
params.brush = get_brush_from_arg(L, -1);
else {
// Default brush is the active brush in the context bar
if (App::instance()->isGui() &&
App::instance()->contextBar()) {
params.brush = App::instance()
->contextBar()->activeBrush(params.tool,
params.ink);
}
}
lua_pop(L, 1);
if (!params.brush) {
// In case the brush is nullptr (e.g. there is no UI) we use the
// default 1 pixel brush (e.g. to run scripts from CLI).
params.brush.reset(new Brush(BrushType::kCircleBrushType, 1, 0));
}
// Opacity, tolerance, and others
type = lua_getfield(L, 1, "opacity");
if (type != LUA_TNIL) {
params.opacity = lua_tointeger(L, -1);
params.opacity = std::clamp(params.opacity, 0, 255);
}
lua_pop(L, 1);
type = lua_getfield(L, 1, "tolerance");
if (type != LUA_TNIL) {
params.tolerance = lua_tointeger(L, -1);
params.tolerance = std::clamp(params.tolerance, 0, 255);
}
lua_pop(L, 1);
type = lua_getfield(L, 1, "contiguous");
if (type != LUA_TNIL)
params.contiguous = lua_toboolean(L, -1);
lua_pop(L, 1);
type = lua_getfield(L, 1, "freehandAlgorithm");
if (type != LUA_TNIL)
params.freehandAlgorithm = get_value_from_lua<tools::FreehandAlgorithm>(L, -1);
lua_pop(L, 1);
if (params.ink->isSelection()) {
gen::SelectionMode selectionMode = Preferences::instance().selection.mode();
type = lua_getfield(L, 1, "selection");
if (type != LUA_TNIL)
selectionMode = get_value_from_lua<gen::SelectionMode>(L, -1);
lua_pop(L, 1);
switch (selectionMode) {
case gen::SelectionMode::REPLACE:
params.modifiers = tools::ToolLoopModifiers::kReplaceSelection;
break;
case gen::SelectionMode::ADD:
params.modifiers = tools::ToolLoopModifiers::kAddSelection;
break;
case gen::SelectionMode::SUBTRACT:
params.modifiers = tools::ToolLoopModifiers::kSubtractSelection;
break;
case gen::SelectionMode::INTERSECT:
params.modifiers = tools::ToolLoopModifiers::kIntersectSelection;
break;
}
}
2019-03-16 04:03:02 +08:00
// Do the tool loop
type = lua_getfield(L, 1, "points");
if (type == LUA_TTABLE) {
InlineCommandExecution inlineCmd(ctx);
2019-03-16 04:03:02 +08:00
std::unique_ptr<tools::ToolLoop> loop(
create_tool_loop_for_script(ctx, site, params));
if (!loop)
return luaL_error(L, "cannot draw in the active site");
2019-03-16 04:03:02 +08:00
tools::ToolLoopManager manager(loop.get());
tools::Pointer lastPointer;
bool first = true;
lua_pushnil(L);
Fix transparent color is possible on opaque sprites (fix #4370) To reproduce the error before this fix on RGBA/Grayscale Color Mode: - New 100x100 RGBA/Grayscale opaque sprite (white background). - Draw something with some gray color in the palette. - Keep the selected gray color as primary color. - Configure as secondary color the mask color (#000000 alpha=0). - Pick 'eraser' tool and erase over the gray color with right click. - Result: The sprite doesn't look more opaque, which is wrong. Also, if we export this sprite, the transparent parts will turn black. A similar problem occurs in Indexed Color Mode, but getting a transparent color in a Background sprite is inevitable if the color of a palette entry is transparent or semi-transparent, since the index must be set as is. This could be fixed in the future at the render stage, however, this could lead to other perceived inconsistencies. For now it'll be left as is. Original issue description: Downloaded PNG in RGB mode fails to support transparency: erase uses secondary color and export PNG replaces transparent color with black Added tests for 'eraser' in 'Replace Color Mode' To make the eraser work in 'Replace Color Mode' within the tests, was implemented the possibility of using the right button in the creation of the point vector. During testing with UI available it was observed that the 'bg' color was copied from the 'fg'. Changed this to be compatible with the way the default value of 'fg' is assigned when it is not specified. This last modification resulted in errors during 'tilemap.lua' due to incompatibility of the type of 'bg' color. This was corrected considering the color type of 'fg' color. Furthermore, it was found that the command 'app.range.tiles = { 1 }' did not finish assigning the tile picks to the activeSite, then 'assert(1, #app.range.tiles)' was failing. This was fixed too.
2024-08-22 22:07:54 +08:00
tools::ToolBox* toolbox = App::instance()->toolBox();
const bool isSelectionInk =
(params.ink == toolbox->getInkById(tools::WellKnownInks::Selection));
const tools::Pointer::Button button =
(!isSelectionInk ? (buttonIdx == 0 ? tools::Pointer::Button::Left :
tools::Pointer::Button::Right) :
tools::Pointer::Button::Left);
2019-03-16 04:03:02 +08:00
while (lua_next(L, -2) != 0) {
gfx::Point pt = convert_args_into_point(L, -1);
tools::Pointer pointer(
pt,
// TODO configurable params
tools::Vec2(0.0f, 0.0f),
Fix transparent color is possible on opaque sprites (fix #4370) To reproduce the error before this fix on RGBA/Grayscale Color Mode: - New 100x100 RGBA/Grayscale opaque sprite (white background). - Draw something with some gray color in the palette. - Keep the selected gray color as primary color. - Configure as secondary color the mask color (#000000 alpha=0). - Pick 'eraser' tool and erase over the gray color with right click. - Result: The sprite doesn't look more opaque, which is wrong. Also, if we export this sprite, the transparent parts will turn black. A similar problem occurs in Indexed Color Mode, but getting a transparent color in a Background sprite is inevitable if the color of a palette entry is transparent or semi-transparent, since the index must be set as is. This could be fixed in the future at the render stage, however, this could lead to other perceived inconsistencies. For now it'll be left as is. Original issue description: Downloaded PNG in RGB mode fails to support transparency: erase uses secondary color and export PNG replaces transparent color with black Added tests for 'eraser' in 'Replace Color Mode' To make the eraser work in 'Replace Color Mode' within the tests, was implemented the possibility of using the right button in the creation of the point vector. During testing with UI available it was observed that the 'bg' color was copied from the 'fg'. Changed this to be compatible with the way the default value of 'fg' is assigned when it is not specified. This last modification resulted in errors during 'tilemap.lua' due to incompatibility of the type of 'bg' color. This was corrected considering the color type of 'fg' color. Furthermore, it was found that the command 'app.range.tiles = { 1 }' did not finish assigning the tile picks to the activeSite, then 'assert(1, #app.range.tiles)' was failing. This was fixed too.
2024-08-22 22:07:54 +08:00
button,
tools::Pointer::Type::Unknown,
0.0f);
2019-03-16 04:03:02 +08:00
if (first) {
first = false;
manager.prepareLoop(pointer);
manager.pressButton(pointer);
}
else {
manager.movement(pointer);
}
lastPointer = pointer;
lua_pop(L, 1);
}
if (!first)
manager.releaseButton(lastPointer);
manager.end();
2019-03-16 04:03:02 +08:00
}
lua_pop(L, 1);
return 0;
}
int App_get_events(lua_State* L)
{
push_app_events(L);
return 1;
}
int App_get_theme(lua_State* L)
{
push_app_theme(L);
return 1;
}
2023-03-21 05:26:44 +08:00
int App_get_uiScale(lua_State* L)
{
lua_pushinteger(L, ui::guiscale());
return 1;
}
int App_get_editor(lua_State* L)
{
auto ctx = UIContext::instance();
if (Editor* editor = ctx->activeEditor()) {
push_editor(L, editor);
return 1;
}
return 0;
}
int App_get_sprite(lua_State* L)
2016-04-07 02:37:13 +08:00
{
2018-08-20 22:25:08 +08:00
app::Context* ctx = App::instance()->context();
Doc* doc = ctx->activeDocument();
2016-04-07 02:37:13 +08:00
if (doc)
push_docobj(L, doc->sprite());
else
2018-08-20 22:25:08 +08:00
lua_pushnil(L);
return 1;
}
int App_get_layer(lua_State* L)
{
app::Context* ctx = App::instance()->context();
Site site = ctx->activeSite();
if (site.layer())
push_docobj(L, site.layer());
else
lua_pushnil(L);
return 1;
}
int App_get_frame(lua_State* L)
{
app::Context* ctx = App::instance()->context();
Site site = ctx->activeSite();
if (site.sprite())
push_sprite_frame(L, site.sprite(), site.frame());
else
lua_pushnil(L);
return 1;
}
int App_get_cel(lua_State* L)
{
app::Context* ctx = App::instance()->context();
Site site = ctx->activeSite();
if (site.cel())
push_sprite_cel(L, site.cel());
else
lua_pushnil(L);
return 1;
}
int App_get_image(lua_State* L)
{
2018-08-20 22:25:08 +08:00
app::Context* ctx = App::instance()->context();
Site site = ctx->activeSite();
if (site.cel())
push_cel_image(L, site.cel());
else
2018-08-20 22:25:08 +08:00
lua_pushnil(L);
return 1;
2016-04-07 02:37:13 +08:00
}
int App_get_tag(lua_State* L)
2018-10-26 21:52:40 +08:00
{
2019-10-02 01:55:08 +08:00
Tag* tag = nullptr;
2018-10-26 21:52:40 +08:00
app::Context* ctx = App::instance()->context();
Site site = ctx->activeSite();
if (site.sprite()) {
if (App::instance()->timeline()) {
2019-10-02 01:55:08 +08:00
tag = App::instance()->timeline()->getTagByFrame(site.frame(), false);
2018-10-26 21:52:40 +08:00
}
else {
2018-10-26 21:52:40 +08:00
tag = get_animation_tag(site.sprite(), site.frame());
}
}
if (tag)
push_docobj(L, tag);
2018-10-26 21:52:40 +08:00
else
lua_pushnil(L);
return 1;
}
2018-09-12 23:55:56 +08:00
int App_get_sprites(lua_State* L)
{
push_sprites(L);
return 1;
}
2018-09-07 00:02:23 +08:00
int App_get_fgColor(lua_State* L)
{
push_obj<app::Color>(L, Preferences::instance().colorBar.fgColor());
return 1;
}
int App_set_fgColor(lua_State* L)
{
Preferences::instance().colorBar.fgColor(convert_args_into_color(L, 2));
2018-09-07 00:02:23 +08:00
return 0;
}
int App_get_bgColor(lua_State* L)
{
push_obj<app::Color>(L, Preferences::instance().colorBar.bgColor());
return 1;
}
int App_set_bgColor(lua_State* L)
{
Preferences::instance().colorBar.bgColor(convert_args_into_color(L, 2));
2018-09-07 00:02:23 +08:00
return 0;
}
int App_get_fgTile(lua_State* L)
{
lua_pushinteger(L, Preferences::instance().colorBar.fgTile());
return 1;
}
int App_set_fgTile(lua_State* L)
{
Preferences::instance().colorBar.fgTile(lua_tointeger(L, 2));
return 0;
}
int App_get_bgTile(lua_State* L)
{
lua_pushinteger(L, Preferences::instance().colorBar.bgTile());
return 1;
}
int App_set_bgTile(lua_State* L)
{
Preferences::instance().colorBar.bgTile(lua_tointeger(L, 2));
return 0;
}
2018-08-20 22:25:08 +08:00
int App_get_site(lua_State* L)
{
2018-08-20 22:25:08 +08:00
app::Context* ctx = App::instance()->context();
Site site = ctx->activeSite();
push_obj(L, site);
return 1;
}
2018-11-23 23:56:30 +08:00
int App_get_range(lua_State* L)
{
app::Context* ctx = App::instance()->context();
Site site = ctx->activeSite();
push_doc_range(L, site);
2018-11-23 23:56:30 +08:00
return 1;
}
2018-09-08 03:06:32 +08:00
int App_get_isUIAvailable(lua_State* L)
{
app::Context* ctx = App::instance()->context();
lua_pushboolean(L, ctx && ctx->isUIAvailable());
return 1;
}
2018-08-20 22:25:08 +08:00
int App_get_version(lua_State* L)
2016-04-07 02:37:13 +08:00
{
std::string ver = get_app_version();
2019-05-22 01:06:35 +08:00
base::replace_string(ver, "-x64", ""); // Remove "-x64" suffix
push_version(L, base::Version(ver));
2018-08-20 22:25:08 +08:00
return 1;
2016-04-07 02:37:13 +08:00
}
2018-11-14 09:31:14 +08:00
int App_get_apiVersion(lua_State* L)
{
lua_pushinteger(L, API_VERSION);
return 1;
}
int App_get_tool(lua_State* L)
{
tools::Tool* tool = App::instance()->activeToolManager()->activeTool();
push_tool(L, tool);
return 1;
}
int App_get_brush(lua_State* L)
{
App* app = App::instance();
if (app->isGui()) {
doc::BrushRef brush = app->contextBar()->activeBrush();
push_brush(L, brush);
return 1;
}
push_brush(L, doc::BrushRef(new doc::Brush()));
return 1;
}
int App_get_defaultPalette(lua_State* L)
{
const Palette* pal = get_default_palette();
if (pal)
push_palette(L, new Palette(*pal));
else
lua_pushnil(L);
return 1;
}
int App_get_window(lua_State* L)
{
App* app = App::instance();
if (app && app->mainWindow()) {
push_ptr(L, (ui::Window*)app->mainWindow());
}
else {
lua_pushnil(L);
}
return 1;
}
int App_set_sprite(lua_State* L)
{
auto sprite = may_get_docobj<Sprite>(L, 2);
app::Context* ctx = App::instance()->context();
doc::Document* doc = (sprite ? sprite->document(): nullptr);
ctx->setActiveDocument(static_cast<Doc*>(doc));
return 0;
}
int App_set_layer(lua_State* L)
{
auto layer = get_docobj<Layer>(L, 2);
app::Context* ctx = App::instance()->context();
ctx->setActiveLayer(layer);
return 0;
}
int App_set_frame(lua_State* L)
{
const doc::frame_t frame = get_frame_number_from_arg(L, 2);
app::Context* ctx = App::instance()->context();
ctx->setActiveFrame(frame);
return 0;
}
int App_set_cel(lua_State* L)
{
const auto cel = get_docobj<Cel>(L, 2);
app::Context* ctx = App::instance()->context();
ctx->setActiveLayer(cel->layer());
ctx->setActiveFrame(cel->frame());
return 0;
}
int App_set_image(lua_State* L)
{
const auto cel = get_image_cel_from_arg(L, 2);
if (!cel)
return 0;
app::Context* ctx = App::instance()->context();
ctx->setActiveLayer(cel->layer());
ctx->setActiveFrame(cel->frame());
return 0;
}
int App_set_tool(lua_State* L)
2019-03-23 20:18:59 +08:00
{
if (auto tool = get_tool_from_arg(L, 2))
App::instance()->activeToolManager()->setSelectedTool(tool);
return 0;
}
int App_set_brush(lua_State* L)
{
if (auto brush = get_brush_from_arg(L, 2)) {
App* app = App::instance();
if (app->isGui())
app->contextBar()->setActiveBrush(brush);
}
return 0;
}
int App_set_defaultPalette(lua_State* L)
{
if (const doc::Palette* pal = get_palette_from_arg(L, 2))
set_default_palette(pal);
return 0;
}
2018-08-20 22:25:08 +08:00
const luaL_Reg App_methods[] = {
{ "open", App_open },
{ "exit", App_exit },
{ "transaction", App_transaction },
{ "undo", App_undo },
{ "redo", App_redo },
2018-09-08 03:42:58 +08:00
{ "alert", App_alert },
2018-11-27 21:24:13 +08:00
{ "refresh", App_refresh },
{ "useTool", App_useTool },
2018-08-20 22:25:08 +08:00
{ nullptr, nullptr }
2016-04-07 02:37:13 +08:00
};
2018-08-20 22:25:08 +08:00
const Property App_properties[] = {
// Deprecated longer fields
{ "activeSprite", App_get_sprite, App_set_sprite },
{ "activeLayer", App_get_layer, App_set_layer },
{ "activeFrame", App_get_frame, App_set_frame },
{ "activeCel", App_get_cel, App_set_cel },
{ "activeImage", App_get_image, App_set_image },
{ "activeTag", App_get_tag, nullptr },
{ "activeTool", App_get_tool, App_set_tool },
{ "activeBrush", App_get_brush, App_set_brush },
// New shorter fields
{ "sprite", App_get_sprite, App_set_sprite },
{ "layer", App_get_layer, App_set_layer },
{ "frame", App_get_frame, App_set_frame },
{ "cel", App_get_cel, App_set_cel },
{ "image", App_get_image, App_set_image },
{ "tag", App_get_tag, nullptr },
{ "tool", App_get_tool, App_set_tool },
{ "brush", App_get_brush, App_set_brush },
{ "sprites", App_get_sprites, nullptr },
{ "fgColor", App_get_fgColor, App_set_fgColor },
{ "bgColor", App_get_bgColor, App_set_bgColor },
{ "fgTile", App_get_fgTile, App_set_fgTile },
{ "bgTile", App_get_bgTile, App_set_bgTile },
{ "version", App_get_version, nullptr },
{ "apiVersion", App_get_apiVersion, nullptr },
{ "site", App_get_site, nullptr },
{ "range", App_get_range, nullptr },
{ "isUIAvailable", App_get_isUIAvailable, nullptr },
{ "defaultPalette", App_get_defaultPalette, App_set_defaultPalette },
{ "window", App_get_window, nullptr },
{ "events", App_get_events, nullptr },
{ "theme", App_get_theme, nullptr },
{ "uiScale", App_get_uiScale, nullptr },
{ "editor", App_get_editor, nullptr },
{ nullptr, nullptr, nullptr }
2016-04-07 02:37:13 +08:00
};
} // anonymous namespace
2018-08-20 22:25:08 +08:00
DEF_MTNAME(App);
void register_app_object(lua_State* L)
2016-04-07 02:37:13 +08:00
{
2018-08-20 22:25:08 +08:00
REG_CLASS(L, App);
REG_CLASS_PROPERTIES(L, App);
lua_newtable(L); // Create a table which will be the "app" object
lua_pushvalue(L, -1);
luaL_getmetatable(L, get_mtname<App>());
lua_setmetatable(L, -2);
lua_setglobal(L, "app");
lua_pop(L, 1); // Pop app table
2016-04-07 02:37:13 +08:00
}
void set_app_params(lua_State* L, const Params& params)
{
lua_getglobal(L, "app");
lua_newtable(L);
for (const auto& kv : params) {
lua_pushstring(L, kv.second.c_str());
lua_setfield(L, -2, kv.first.c_str());
}
lua_setfield(L, -2, "params");
lua_pop(L, 1);
}
2018-08-20 22:25:08 +08:00
} // namespace script
2016-04-07 02:37:13 +08:00
} // namespace app