mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 13:21:34 +00:00
Merge 67b2b85d32dd1190abaf49996f021f1492e28eea into be6d2251aad2e0a609f63ce8c7e46fefa858ded1
This commit is contained in:
commit
112a7b9d42
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit 65829107c838817987f3cf6374cc68c583e5d538
|
||||
Subproject commit 23c11583b626b36b125dbe74a2d1cde880780623
|
@ -52,6 +52,7 @@ protected:
|
||||
docPref.grid.snap(newValue);
|
||||
|
||||
StatusBar::instance()->showSnapToGridWarning(newValue);
|
||||
StatusBar::instance()->showRunningScriptsWindow(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -73,7 +73,7 @@ void RunScriptCommand::onExecute(Context* context)
|
||||
return;
|
||||
}
|
||||
|
||||
App::instance()->scriptEngine()->evalUserFile(m_filename, m_params);
|
||||
App::instance()->scriptEngine()->evalUserFileInTask(m_filename, m_params);
|
||||
|
||||
if (context->isUIAvailable())
|
||||
ui::Manager::getDefault()->invalidate();
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "base/fs.h"
|
||||
#include "base/paths.h"
|
||||
#include "base/remove_from_container.h"
|
||||
#include "ui/await.h"
|
||||
#include "ui/box.h"
|
||||
#include "ui/button.h"
|
||||
#include "ui/combobox.h"
|
||||
@ -125,6 +126,7 @@ struct Dialog {
|
||||
// Reference used to keep the dialog alive (so it's not garbage
|
||||
// collected) when it's visible.
|
||||
int showRef = LUA_REFNIL;
|
||||
int dlgStateRef = LUA_REFNIL;
|
||||
lua_State* L = nullptr;
|
||||
|
||||
Dialog(const ui::Window::Type windowType, const std::string& title)
|
||||
@ -152,6 +154,11 @@ struct Dialog {
|
||||
this->L = L;
|
||||
lua_pushvalue(L, 1);
|
||||
showRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
// Keep a reference of this thread state while the dialog is still alive
|
||||
// because it will be reused when calling the event handlers to create
|
||||
// new lua threads.
|
||||
lua_pushthread(L);
|
||||
dlgStateRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,8 +170,14 @@ struct Dialog {
|
||||
void unrefShow()
|
||||
{
|
||||
if (showRef != LUA_REFNIL) {
|
||||
int status = lua_status(this->L);
|
||||
TRACE("status: %d %d\n", showRef, status);
|
||||
|
||||
// this->L
|
||||
luaL_unref(this->L, LUA_REGISTRYINDEX, showRef);
|
||||
luaL_unref(this->L, LUA_REGISTRYINDEX, dlgStateRef);
|
||||
showRef = LUA_REFNIL;
|
||||
dlgStateRef = LUA_REFNIL;
|
||||
L = nullptr;
|
||||
}
|
||||
}
|
||||
@ -301,6 +314,7 @@ struct Dialog {
|
||||
template<typename... Args, typename Callback>
|
||||
void Dialog_connect_signal(lua_State* L,
|
||||
int dlgIdx,
|
||||
std::string signalName,
|
||||
obs::signal<void(Args...)>& signal,
|
||||
Callback callback)
|
||||
{
|
||||
@ -336,18 +350,7 @@ void Dialog_connect_signal(lua_State* L,
|
||||
callback(L, std::forward<Args>(args)...);
|
||||
|
||||
if (lua_isfunction(L, -2)) {
|
||||
try {
|
||||
if (lua_pcall(L, 1, 0, 0)) {
|
||||
if (const char* s = lua_tostring(L, -1))
|
||||
App::instance()->scriptEngine()->consolePrint(s);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
// This is used to catch unhandled exception or for
|
||||
// example, std::runtime_error exceptions when a Tx() is
|
||||
// created without an active sprite.
|
||||
App::instance()->scriptEngine()->handleException(ex);
|
||||
}
|
||||
App::instance()->scriptEngine()->callInTask(L, 1, signalName);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1); // Pop the value which should have been a function
|
||||
@ -358,63 +361,73 @@ void Dialog_connect_signal(lua_State* L,
|
||||
|
||||
int Dialog_new(lua_State* L)
|
||||
{
|
||||
// If we don't have UI, just return nil
|
||||
if (!App::instance()->isGui())
|
||||
try {
|
||||
return ui::await<int>(
|
||||
[L]() -> int {
|
||||
// If we don't have UI, just return nil
|
||||
if (!App::instance()->isGui())
|
||||
return 0;
|
||||
|
||||
// Get the title and the type of window (with or without title bar)
|
||||
ui::Window::Type windowType = ui::Window::WithTitleBar;
|
||||
std::string title = "Script";
|
||||
if (lua_isstring(L, 1)) {
|
||||
title = lua_tostring(L, 1);
|
||||
}
|
||||
else if (lua_istable(L, 1)) {
|
||||
int type = lua_getfield(L, 1, "title");
|
||||
if (type != LUA_TNIL)
|
||||
title = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 1, "notitlebar");
|
||||
if (type != LUA_TNIL && lua_toboolean(L, -1))
|
||||
windowType = ui::Window::WithoutTitleBar;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
auto dlg = push_new<Dialog>(L, windowType, title);
|
||||
|
||||
// The uservalue of the dialog userdata will contain a table that
|
||||
// stores all the callbacks to handle events. As these callbacks can
|
||||
// reference the dialog itself, it's important to store callbacks in
|
||||
// this table that depends on the dialog lifetime itself
|
||||
// (i.e. uservalue) and in the global registry, because in that case
|
||||
// we could create a cyclic reference that would be not GC'd.
|
||||
lua_newtable(L);
|
||||
lua_setuservalue(L, -2);
|
||||
|
||||
if (lua_istable(L, 1)) {
|
||||
int type = lua_getfield(L, 1, "parent");
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto parentDlg = may_get_obj<Dialog>(L, -1))
|
||||
dlg->window.setParentDisplay(parentDlg->window.display());
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 1, "onclose");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, -2, "onclose", dlg->window.Close, [](lua_State*, CloseEvent&) {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// The showRef must be the last reference to the dialog to be
|
||||
// unreferenced after the window is closed (that's why this is the
|
||||
// last connection to ui::Window::Close)
|
||||
dlg->unrefShowOnClose();
|
||||
|
||||
TRACE_DIALOG("Dialog_new", dlg);
|
||||
return 1;
|
||||
},
|
||||
500);
|
||||
}
|
||||
catch (AwaitTimeoutException& ex) {
|
||||
luaL_error(L, "Could not create Dialog");
|
||||
return 0;
|
||||
|
||||
// Get the title and the type of window (with or without title bar)
|
||||
ui::Window::Type windowType = ui::Window::WithTitleBar;
|
||||
std::string title = "Script";
|
||||
if (lua_isstring(L, 1)) {
|
||||
title = lua_tostring(L, 1);
|
||||
}
|
||||
else if (lua_istable(L, 1)) {
|
||||
int type = lua_getfield(L, 1, "title");
|
||||
if (type != LUA_TNIL)
|
||||
title = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 1, "notitlebar");
|
||||
if (type != LUA_TNIL && lua_toboolean(L, -1))
|
||||
windowType = ui::Window::WithoutTitleBar;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
auto dlg = push_new<Dialog>(L, windowType, title);
|
||||
|
||||
// The uservalue of the dialog userdata will contain a table that
|
||||
// stores all the callbacks to handle events. As these callbacks can
|
||||
// reference the dialog itself, it's important to store callbacks in
|
||||
// this table that depends on the dialog lifetime itself
|
||||
// (i.e. uservalue) and in the global registry, because in that case
|
||||
// we could create a cyclic reference that would be not GC'd.
|
||||
lua_newtable(L);
|
||||
lua_setuservalue(L, -2);
|
||||
|
||||
if (lua_istable(L, 1)) {
|
||||
int type = lua_getfield(L, 1, "parent");
|
||||
if (type != LUA_TNIL) {
|
||||
if (auto parentDlg = may_get_obj<Dialog>(L, -1))
|
||||
dlg->window.setParentDisplay(parentDlg->window.display());
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 1, "onclose");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, -2, dlg->window.Close, [](lua_State*, CloseEvent&) {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// The showRef must be the last reference to the dialog to be
|
||||
// unreferenced after the window is closed (that's why this is the
|
||||
// last connection to ui::Window::Close)
|
||||
dlg->unrefShowOnClose();
|
||||
|
||||
TRACE_DIALOG("Dialog_new", dlg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Dialog_gc(lua_State* L)
|
||||
@ -764,7 +777,7 @@ int Dialog_button_base(lua_State* L, T** outputWidget = nullptr)
|
||||
|
||||
type = lua_getfield(L, 2, "onclick");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Click, [](lua_State*) {
|
||||
Dialog_connect_signal(L, 1, "onclick", widget->Click, [](lua_State*) {
|
||||
// Do nothing
|
||||
});
|
||||
closeWindowByDefault = false;
|
||||
@ -838,7 +851,7 @@ int Dialog_entry(lua_State* L)
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
||||
Dialog_connect_signal(L, 1, "onchange", widget->Change, [](lua_State* L) {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
@ -868,7 +881,7 @@ int Dialog_number(lua_State* L)
|
||||
|
||||
type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
||||
Dialog_connect_signal(L, 1, "onchange", widget->Change, [](lua_State* L) {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
@ -909,7 +922,7 @@ int Dialog_slider(lua_State* L)
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
||||
Dialog_connect_signal(L, 1, "onchange", widget->Change, [](lua_State* L) {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
@ -917,7 +930,7 @@ int Dialog_slider(lua_State* L)
|
||||
|
||||
type = lua_getfield(L, 2, "onrelease");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->SliderReleased, [](lua_State* L) {
|
||||
Dialog_connect_signal(L, 1, "onrelease", widget->SliderReleased, [](lua_State* L) {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
@ -955,7 +968,7 @@ int Dialog_combobox(lua_State* L)
|
||||
|
||||
type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
||||
Dialog_connect_signal(L, 1, "onchange", widget->Change, [](lua_State* L) {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
@ -979,10 +992,14 @@ int Dialog_color(lua_State* L)
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L, const app::Color& color) {
|
||||
push_obj<app::Color>(L, color);
|
||||
lua_setfield(L, -2, "color");
|
||||
});
|
||||
Dialog_connect_signal(L,
|
||||
1,
|
||||
"onchange",
|
||||
widget->Change,
|
||||
[](lua_State* L, const app::Color& color) {
|
||||
push_obj<app::Color>(L, color);
|
||||
lua_setfield(L, -2, "color");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1026,6 +1043,7 @@ int Dialog_shades(lua_State* L)
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L,
|
||||
1,
|
||||
"onclick",
|
||||
widget->Click,
|
||||
[widget](lua_State* L, ColorShades::ClickEvent& ev) {
|
||||
lua_pushinteger(L, (int)ev.button());
|
||||
@ -1101,7 +1119,7 @@ int Dialog_file(lua_State* L)
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
||||
Dialog_connect_signal(L, 1, "onchange", widget->Change, [](lua_State* L) {
|
||||
// Do nothing
|
||||
});
|
||||
}
|
||||
@ -1276,7 +1294,7 @@ int Dialog_canvas(lua_State* L)
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onpaint");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Paint, [](lua_State* L, GraphicsContext& gc) {
|
||||
Dialog_connect_signal(L, 1, "onpaint", widget->Paint, [](lua_State* L, GraphicsContext& gc) {
|
||||
push_new<GraphicsContext>(L, std::move(gc));
|
||||
lua_setfield(L, -2, "context");
|
||||
});
|
||||
@ -1285,51 +1303,55 @@ int Dialog_canvas(lua_State* L)
|
||||
|
||||
type = lua_getfield(L, 2, "onkeydown");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->KeyDown, fill_keymessage_values);
|
||||
Dialog_connect_signal(L, 1, "onkeydown", widget->KeyDown, fill_keymessage_values);
|
||||
handleKeyEvents = true;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "onkeyup");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->KeyUp, fill_keymessage_values);
|
||||
Dialog_connect_signal(L, 1, "onkeyup", widget->KeyUp, fill_keymessage_values);
|
||||
handleKeyEvents = true;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "onmousemove");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->MouseMove, fill_mousemessage_values);
|
||||
Dialog_connect_signal(L, 1, "onmousemove", widget->MouseMove, fill_mousemessage_values);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "onmousedown");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->MouseDown, fill_mousemessage_values);
|
||||
Dialog_connect_signal(L, 1, "onmousedown", widget->MouseDown, fill_mousemessage_values);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "onmouseup");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->MouseUp, fill_mousemessage_values);
|
||||
Dialog_connect_signal(L, 1, "onmouseup", widget->MouseUp, fill_mousemessage_values);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "ondblclick");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->DoubleClick, fill_mousemessage_values);
|
||||
Dialog_connect_signal(L, 1, "ondblclick", widget->DoubleClick, fill_mousemessage_values);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "onwheel");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->Wheel, fill_mousemessage_values);
|
||||
Dialog_connect_signal(L, 1, "onwheel", widget->Wheel, fill_mousemessage_values);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 2, "ontouchmagnify");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, widget->TouchMagnify, fill_touchmessage_values);
|
||||
Dialog_connect_signal(L,
|
||||
1,
|
||||
"ontouchmagnify",
|
||||
widget->TouchMagnify,
|
||||
fill_touchmessage_values);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
@ -1382,7 +1404,7 @@ int Dialog_tab(lua_State* L)
|
||||
if (lua_istable(L, 2)) {
|
||||
int type = lua_getfield(L, 2, "onclick");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, 1, tab->Click, [id](lua_State* L) {
|
||||
Dialog_connect_signal(L, 1, "onclick", tab->Click, [id](lua_State* L) {
|
||||
lua_pushstring(L, id.c_str());
|
||||
lua_setfield(L, -2, "tab");
|
||||
});
|
||||
@ -1436,7 +1458,7 @@ int Dialog_endtabs(lua_State* L)
|
||||
type = lua_getfield(L, 2, "onchange");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
auto tab = dlg->wipTab;
|
||||
Dialog_connect_signal(L, 1, dlg->wipTab->TabChanged, [tab](lua_State* L) {
|
||||
Dialog_connect_signal(L, 1, "onchange", dlg->wipTab->TabChanged, [tab](lua_State* L) {
|
||||
lua_pushstring(L, tab->tabId(tab->selectedTab()).c_str());
|
||||
lua_setfield(L, -2, "tab");
|
||||
});
|
||||
@ -1879,31 +1901,42 @@ int Dialog_set_bounds(lua_State* L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define wrap(func) \
|
||||
[](lua_State* L) -> int { \
|
||||
try { \
|
||||
return ui::await<int>([L]() -> int { return func(L); }, 500); \
|
||||
} \
|
||||
catch (AwaitTimeoutException & ex) { \
|
||||
luaL_error(L, "Could not execute %s", #func); \
|
||||
return 0; \
|
||||
} \
|
||||
}
|
||||
|
||||
const luaL_Reg Dialog_methods[] = {
|
||||
{ "__gc", Dialog_gc },
|
||||
{ "show", Dialog_show },
|
||||
{ "showMenu", Dialog_showMenu },
|
||||
{ "close", Dialog_close },
|
||||
{ "newrow", Dialog_newrow },
|
||||
{ "separator", Dialog_separator },
|
||||
{ "label", Dialog_label },
|
||||
{ "button", Dialog_button },
|
||||
{ "check", Dialog_check },
|
||||
{ "radio", Dialog_radio },
|
||||
{ "menuItem", Dialog_menuItem },
|
||||
{ "entry", Dialog_entry },
|
||||
{ "number", Dialog_number },
|
||||
{ "slider", Dialog_slider },
|
||||
{ "combobox", Dialog_combobox },
|
||||
{ "color", Dialog_color },
|
||||
{ "shades", Dialog_shades },
|
||||
{ "file", Dialog_file },
|
||||
{ "canvas", Dialog_canvas },
|
||||
{ "tab", Dialog_tab },
|
||||
{ "endtabs", Dialog_endtabs },
|
||||
{ "modify", Dialog_modify },
|
||||
{ "repaint", Dialog_repaint },
|
||||
{ nullptr, nullptr }
|
||||
{ "__gc", wrap(Dialog_gc) },
|
||||
{ "show", wrap(Dialog_show) },
|
||||
{ "showMenu", Dialog_showMenu },
|
||||
{ "close", Dialog_close },
|
||||
{ "newrow", Dialog_newrow },
|
||||
{ "separator", Dialog_separator },
|
||||
{ "label", Dialog_label },
|
||||
{ "button", wrap(Dialog_button) },
|
||||
{ "check", Dialog_check },
|
||||
{ "radio", Dialog_radio },
|
||||
{ "menuItem", Dialog_menuItem },
|
||||
{ "entry", Dialog_entry },
|
||||
{ "number", Dialog_number },
|
||||
{ "slider", Dialog_slider },
|
||||
{ "combobox", Dialog_combobox },
|
||||
{ "color", Dialog_color },
|
||||
{ "shades", Dialog_shades },
|
||||
{ "file", Dialog_file },
|
||||
{ "canvas", Dialog_canvas },
|
||||
{ "tab", Dialog_tab },
|
||||
{ "endtabs", Dialog_endtabs },
|
||||
{ "modify", Dialog_modify },
|
||||
{ "repaint", Dialog_repaint },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
const Property Dialog_properties[] = {
|
||||
|
@ -35,7 +35,10 @@
|
||||
#include "fmt/format.h"
|
||||
#include "ui/base.h"
|
||||
#include "ui/cursor_type.h"
|
||||
#include "ui/manager.h"
|
||||
#include "ui/message_loop.h"
|
||||
#include "ui/mouse_button.h"
|
||||
#include "ui/system.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@ -213,7 +216,100 @@ void register_websocket_class(lua_State* L);
|
||||
|
||||
void set_app_params(lua_State* L, const Params& params);
|
||||
|
||||
Engine::Engine() : L(luaL_newstate()), m_delegate(nullptr), m_printLastResult(false)
|
||||
#define RUNNING_TASKS "RunningTasks"
|
||||
|
||||
std::mutex runningTasksMutex;
|
||||
|
||||
RunScriptTask::RunScriptTask(lua_State* L, int nelems, const std::string& description, Func&& func)
|
||||
: m_LRef(LUA_REFNIL)
|
||||
, m_description(description)
|
||||
{
|
||||
m_L = lua_newthread(L);
|
||||
// Move the thread object in the parent thread stack to the new thread stack.
|
||||
lua_xmove(L, m_L, 1);
|
||||
// Save a reference to the lua thread in the registry to avoid garbage collection
|
||||
// before it gets executed.
|
||||
m_LRef = luaL_ref(m_L, LUA_REGISTRYINDEX);
|
||||
|
||||
// Move nelems stack elements from main thread to the child lua thread.
|
||||
lua_xmove(L, m_L, nelems);
|
||||
|
||||
// We use a global table to have access to the state of all the currently
|
||||
// running scripts
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(runningTasksMutex);
|
||||
int type = lua_getglobal(m_L, RUNNING_TASKS);
|
||||
if (type == LUA_TNIL) {
|
||||
lua_newtable(m_L);
|
||||
lua_pushvalue(m_L, -1);
|
||||
lua_setglobal(m_L, RUNNING_TASKS);
|
||||
}
|
||||
|
||||
lua_pushlightuserdata(m_L, this);
|
||||
lua_rawsetp(m_L, -2, m_L);
|
||||
lua_pop(m_L, 1);
|
||||
}
|
||||
lua_sethook(
|
||||
m_L,
|
||||
[](lua_State* L, lua_Debug* ar) {
|
||||
if (ar->event == LUA_HOOKCOUNT) {
|
||||
std::lock_guard<std::mutex> lock(runningTasksMutex);
|
||||
int type = lua_getglobal(L, RUNNING_TASKS);
|
||||
if (type == LUA_TTABLE) {
|
||||
lua_rawgetp(L, -1, L);
|
||||
RunScriptTask* task = (RunScriptTask*)lua_topointer(L, -1);
|
||||
lua_pop(L, 2);
|
||||
if (task->wantsToStop()) {
|
||||
luaL_where(L, 0);
|
||||
const char* where = lua_tostring(L, -1);
|
||||
luaL_traceback(L, L, "Script stopped", 0);
|
||||
const char* traceback = lua_tostring(L, -1);
|
||||
std::string msg(fmt::format("{}{}", where, traceback));
|
||||
lua_pop(L, 2);
|
||||
lua_pushstring(L, msg.c_str());
|
||||
lua_error(L);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
LUA_MASKCOUNT,
|
||||
10);
|
||||
|
||||
// TODO: use the token for allowing the script to report progress somehow.
|
||||
m_task.on_execute([this, func](base::task_token& token) { func(m_L); });
|
||||
}
|
||||
|
||||
RunScriptTask::~RunScriptTask()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(runningTasksMutex);
|
||||
int type = lua_getglobal(m_L, RUNNING_TASKS);
|
||||
if (type == LUA_TTABLE) {
|
||||
lua_pushlightuserdata(m_L, nullptr);
|
||||
lua_rawsetp(m_L, -2, m_L);
|
||||
}
|
||||
lua_pop(m_L, 1);
|
||||
|
||||
luaL_unref(m_L, LUA_REGISTRYINDEX, m_LRef);
|
||||
|
||||
// Collect script garbage.
|
||||
lua_gc(m_L, LUA_GCCOLLECT);
|
||||
}
|
||||
|
||||
void RunScriptTask::execute(base::thread_pool& pool)
|
||||
{
|
||||
m_task.start(pool);
|
||||
}
|
||||
|
||||
void RunScriptTask::stop()
|
||||
{
|
||||
m_wantsToStop = true;
|
||||
}
|
||||
|
||||
Engine::Engine()
|
||||
: L(luaL_newstate())
|
||||
, m_delegate(nullptr)
|
||||
, m_threadPool(3)
|
||||
, m_printLastResult(false)
|
||||
{
|
||||
#if _DEBUG
|
||||
int top = lua_gettop(L);
|
||||
@ -525,6 +621,15 @@ Engine::~Engine()
|
||||
|
||||
void Engine::destroy()
|
||||
{
|
||||
// Stop all running tasks.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for (auto& task : m_tasks) {
|
||||
task->stop();
|
||||
}
|
||||
}
|
||||
m_threadPool.wait_all();
|
||||
|
||||
close_all_dialogs();
|
||||
lua_close(L);
|
||||
L = nullptr;
|
||||
@ -594,6 +699,11 @@ bool Engine::evalCode(const std::string& code, const std::string& filename)
|
||||
}
|
||||
|
||||
void Engine::handleException(const std::exception& ex)
|
||||
{
|
||||
handleException(L, ex);
|
||||
}
|
||||
|
||||
void Engine::handleException(lua_State* L, const std::exception& ex)
|
||||
{
|
||||
luaL_where(L, 1);
|
||||
const char* where = lua_tostring(L, -1);
|
||||
@ -631,6 +741,122 @@ bool Engine::evalFile(const std::string& filename, const Params& params)
|
||||
return result;
|
||||
}
|
||||
|
||||
void Engine::callInTask(lua_State* parentL, int nargs, const std::string& description)
|
||||
{
|
||||
executeTask(parentL, nargs + 1, description, [this, nargs](lua_State* L) {
|
||||
try {
|
||||
if (lua_pcall(L, nargs, 0, 0)) {
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
std::string error = std::string(s);
|
||||
ui::execute_from_ui_thread([this, error]() { consolePrint(error.c_str()); });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
// This is used to catch unhandled exception or for
|
||||
// example, std::runtime_error exceptions when a Tx() is
|
||||
// created without an active sprite.
|
||||
handleException(L, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Engine::executeTask(lua_State* parentL,
|
||||
int nelems,
|
||||
const std::string& description,
|
||||
RunScriptTask::Func&& func)
|
||||
{
|
||||
auto task = std::make_unique<RunScriptTask>(parentL, nelems, description, std::move(func));
|
||||
auto* taskPtr = task.get();
|
||||
task->onDone([this, taskPtr](base::task_token&) { onTaskDone(taskPtr); });
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_tasks.push_back(std::move(task));
|
||||
}
|
||||
TaskStart(taskPtr);
|
||||
taskPtr->execute(m_threadPool);
|
||||
}
|
||||
|
||||
void Engine::onTaskDone(const RunScriptTask* task)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
TaskDone(task);
|
||||
for (auto it = m_tasks.begin(); it != m_tasks.end(); ++it) {
|
||||
if ((*it).get() == task) {
|
||||
m_tasks.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::evalUserFileInTask(const std::string& filename, const Params& params)
|
||||
{
|
||||
executeTask(L, 0, filename, [filename, params, this](lua_State* L) {
|
||||
std::string absFilename = base::get_absolute_path(filename);
|
||||
// Set the _SCRIPT_PATH global so require() can find .lua files from
|
||||
// the script path.
|
||||
std::string path = base::get_file_path(absFilename);
|
||||
SetScriptForRequire setScript(L, path.c_str());
|
||||
|
||||
bool ok = true;
|
||||
std::stringstream buf;
|
||||
{
|
||||
std::ifstream s(FSTREAM_PATH(filename));
|
||||
// Returns false if we cannot open the file
|
||||
if (!s) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
buf << s.rdbuf();
|
||||
}
|
||||
|
||||
AddScriptFilename addScript(absFilename);
|
||||
set_app_params(L, params);
|
||||
|
||||
const std::string& code = buf.str();
|
||||
const std::string& atFilename = "@" + absFilename;
|
||||
|
||||
int returnCode;
|
||||
try {
|
||||
if (luaL_loadbuffer(L, code.c_str(), code.size(), atFilename.c_str()) ||
|
||||
lua_pcall(L, 0, 1, 0)) {
|
||||
const char* s = lua_tostring(L, -1);
|
||||
if (s) {
|
||||
std::string error = std::string(s);
|
||||
ui::execute_from_ui_thread([this, error]() { onConsoleError(error.c_str()); });
|
||||
}
|
||||
ok = false;
|
||||
returnCode = -1;
|
||||
}
|
||||
else {
|
||||
// Return code
|
||||
if (lua_isinteger(L, -1))
|
||||
returnCode = lua_tointeger(L, -1);
|
||||
else
|
||||
returnCode = 0;
|
||||
|
||||
// Code was executed correctly
|
||||
if (m_printLastResult) {
|
||||
if (!lua_isnone(L, -1)) {
|
||||
const char* result = lua_tostring(L, -1);
|
||||
if (result)
|
||||
TRACE("Result: %s\n", result);
|
||||
// onConsolePrint(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
// handleException(ex);
|
||||
TRACE("Exception: %s\n", ex.what());
|
||||
ok = false;
|
||||
returnCode = -1;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Engine::evalUserFile(const std::string& filename, const Params& params)
|
||||
{
|
||||
// Set the _SCRIPT_PATH global so require() can find .lua files from
|
||||
@ -660,6 +886,16 @@ void Engine::stopDebugger()
|
||||
lua_sethook(L, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
void Engine::stopTask(const RunScriptTask* task)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for (const auto& t : m_tasks) {
|
||||
if (t.get() == task) {
|
||||
t->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::onConsoleError(const char* text)
|
||||
{
|
||||
if (text && m_delegate)
|
||||
|
@ -16,6 +16,8 @@
|
||||
#include "app/color.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/extensions.h"
|
||||
#include "base/task.h"
|
||||
#include "base/thread_pool.h"
|
||||
#include "base/uuid.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/frame.h"
|
||||
@ -84,8 +86,36 @@ public:
|
||||
virtual void endFile(const std::string& file) = 0;
|
||||
};
|
||||
|
||||
class RunScriptTask {
|
||||
public:
|
||||
typedef std::function<void(lua_State* L)> Func;
|
||||
|
||||
RunScriptTask(lua_State* L, int nelems, const std::string& description, Func&& func);
|
||||
~RunScriptTask();
|
||||
|
||||
void onDone(base::task::func_t&& funcDone) { m_task.on_done(std::move(funcDone)); }
|
||||
void execute(base::thread_pool& pool);
|
||||
void stop();
|
||||
bool wantsToStop() const { return m_wantsToStop; }
|
||||
|
||||
int ref() const { return m_LRef; }
|
||||
const std::string& description() const { return m_description; }
|
||||
bool isRunning() const { return m_task.running(); }
|
||||
bool isEnqueued() const { return m_task.enqueued(); }
|
||||
|
||||
private:
|
||||
// Lua's thread state.
|
||||
lua_State* m_L;
|
||||
int m_LRef;
|
||||
base::task m_task;
|
||||
bool m_wantsToStop = false;
|
||||
std::string m_description;
|
||||
};
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
typedef std::vector<std::unique_ptr<RunScriptTask>> Tasks;
|
||||
|
||||
Engine();
|
||||
~Engine();
|
||||
|
||||
@ -101,8 +131,22 @@ public:
|
||||
bool evalCode(const std::string& code, const std::string& filename = std::string());
|
||||
bool evalFile(const std::string& filename, const Params& params = Params());
|
||||
bool evalUserFile(const std::string& filename, const Params& params = Params());
|
||||
bool evalUserFileInTask(const std::string& filename, const Params& params = Params());
|
||||
// Calls the function in the stack with the number of arguments specified by nargs in
|
||||
// a new RunScriptTask.
|
||||
void callInTask(lua_State* L, int nargs, const std::string& description);
|
||||
std::vector<const RunScriptTask*> tasks() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::vector<const RunScriptTask*> tasks;
|
||||
tasks.reserve(m_tasks.size());
|
||||
for (const auto& task : m_tasks)
|
||||
tasks.push_back(task.get());
|
||||
return tasks;
|
||||
}
|
||||
|
||||
void handleException(const std::exception& ex);
|
||||
void handleException(lua_State* L, const std::exception& ex);
|
||||
|
||||
void consolePrint(const char* text) { onConsolePrint(text); }
|
||||
|
||||
@ -112,13 +156,29 @@ public:
|
||||
|
||||
void startDebugger(DebuggerDelegate* debuggerDelegate);
|
||||
void stopDebugger();
|
||||
// Stops the specified task
|
||||
void stopTask(const RunScriptTask* task);
|
||||
|
||||
obs::signal<void(const RunScriptTask*)> TaskStart;
|
||||
obs::signal<void(const RunScriptTask*)> TaskDone;
|
||||
|
||||
private:
|
||||
void onConsoleError(const char* text);
|
||||
void onConsolePrint(const char* text);
|
||||
// Creates a new RunScriptTask based on parentL and moving nelems from parentL's stack
|
||||
// to the child lua thread stack
|
||||
void executeTask(lua_State* parentL,
|
||||
int nelems,
|
||||
const std::string& description,
|
||||
RunScriptTask::Func&& func);
|
||||
void onTaskDone(const RunScriptTask* task);
|
||||
static void checkProgress(lua_State* L, lua_Debug* ar);
|
||||
|
||||
lua_State* L;
|
||||
EngineDelegate* m_delegate;
|
||||
base::thread_pool m_threadPool;
|
||||
mutable std::mutex m_mutex;
|
||||
Tasks m_tasks;
|
||||
bool m_printLastResult;
|
||||
int m_returnCode;
|
||||
};
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/modules/palettes.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/tools/active_tool.h"
|
||||
#include "app/tools/tool.h"
|
||||
#include "app/ui/button_set.h"
|
||||
@ -44,8 +45,16 @@
|
||||
#include "fmt/format.h"
|
||||
#include "gfx/size.h"
|
||||
#include "os/surface.h"
|
||||
#include "os/system.h"
|
||||
#include "text/font.h"
|
||||
#include "ui/box.h"
|
||||
#include "ui/button.h"
|
||||
#include "ui/label.h"
|
||||
#include "ui/listbox.h"
|
||||
#include "ui/listitem.h"
|
||||
#include "ui/popup_window.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/view.h"
|
||||
#include "ver/info.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -607,6 +616,70 @@ private:
|
||||
ui::Button m_button;
|
||||
};
|
||||
|
||||
class StatusBar::RunningScriptsWindow : public ui::Window {
|
||||
public:
|
||||
// TODO: Replace the title by a string
|
||||
RunningScriptsWindow() : ui::Window(Type::WithTitleBar, "Running scripts")
|
||||
{
|
||||
setMinSize({ 500, 200 });
|
||||
m_runningScripts.setExpansive(true);
|
||||
m_view.setExpansive(true);
|
||||
m_view.attachToView(&m_runningScripts);
|
||||
addChild(&m_view);
|
||||
|
||||
initTheme();
|
||||
|
||||
refreshList();
|
||||
|
||||
App::instance()->scriptEngine()->TaskStart.connect([this](const script::RunScriptTask*) {
|
||||
ui::execute_from_ui_thread([this] { refreshList(); });
|
||||
});
|
||||
App::instance()->scriptEngine()->TaskDone.connect([this](const script::RunScriptTask*) {
|
||||
ui::execute_from_ui_thread([this] { refreshList(); });
|
||||
});
|
||||
}
|
||||
|
||||
void refreshList()
|
||||
{
|
||||
m_runningScripts.removeAllChildren();
|
||||
for (const auto* task : App::instance()->scriptEngine()->tasks()) {
|
||||
auto* item = new TaskItem(task);
|
||||
m_runningScripts.addChild(item);
|
||||
}
|
||||
m_runningScripts.layout();
|
||||
flushRedraw();
|
||||
}
|
||||
|
||||
private:
|
||||
class TaskItem : public ListItem {
|
||||
public:
|
||||
TaskItem(const script::RunScriptTask* task) : m_label(""), m_stop("Stop")
|
||||
{
|
||||
m_task = task;
|
||||
m_label.setText(
|
||||
fmt::format("{} ({})", task->description(), (task->isEnqueued() ? "enqueued" : "running")));
|
||||
|
||||
m_label.setExpansive(true);
|
||||
m_row.setExpansive(true);
|
||||
m_row.addChild(&m_label);
|
||||
if (!task->isEnqueued())
|
||||
m_row.addChild(&m_stop);
|
||||
addChild(&m_row);
|
||||
|
||||
m_stop.Click.connect([this]() { App::instance()->scriptEngine()->stopTask(m_task); });
|
||||
}
|
||||
|
||||
private:
|
||||
const script::RunScriptTask* m_task;
|
||||
ui::HBox m_row;
|
||||
ui::Label m_label;
|
||||
ui::Button m_stop;
|
||||
};
|
||||
|
||||
ui::View m_view;
|
||||
ui::ListBox m_runningScripts;
|
||||
};
|
||||
|
||||
// This widget is used to show the current frame.
|
||||
class GotoFrameEntry : public Entry {
|
||||
public:
|
||||
@ -886,6 +959,25 @@ void StatusBar::showSnapToGridWarning(bool state)
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBar::showRunningScriptsWindow(bool state)
|
||||
{
|
||||
if (state) {
|
||||
if (!m_runningScriptsWindow)
|
||||
m_runningScriptsWindow = new RunningScriptsWindow;
|
||||
|
||||
m_runningScriptsWindow->setDisplay(display(), false);
|
||||
|
||||
if (!m_runningScriptsWindow->isVisible()) {
|
||||
m_runningScriptsWindow->openWindow();
|
||||
m_runningScriptsWindow->remapWindow();
|
||||
// updateRunningScriptsWindowPosition();
|
||||
}
|
||||
}
|
||||
else if (m_runningScriptsWindow) {
|
||||
m_runningScriptsWindow->closeWindow(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// StatusBar message handler
|
||||
|
||||
|
@ -66,6 +66,7 @@ public:
|
||||
void showTile(int msecs, doc::tile_t tile, const std::string& text = std::string());
|
||||
void showTool(int msecs, tools::Tool* tool);
|
||||
void showSnapToGridWarning(bool state);
|
||||
void showRunningScriptsWindow(bool state);
|
||||
|
||||
// Used by AppEditor to update the zoom level in the status bar.
|
||||
void updateFromEditor(Editor* editor);
|
||||
@ -119,6 +120,9 @@ private:
|
||||
// Snap to grid window
|
||||
class SnapToGridWindow;
|
||||
SnapToGridWindow* m_snapToGridWindow;
|
||||
|
||||
class RunningScriptsWindow;
|
||||
RunningScriptsWindow* m_runningScriptsWindow;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
68
src/ui/await.h
Normal file
68
src/ui/await.h
Normal file
@ -0,0 +1,68 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef UI_AWAIT_H_INCLUDED
|
||||
#define UI_AWAIT_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "os/event.h"
|
||||
#include "os/event_queue.h"
|
||||
#include "ui/system.h"
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class AwaitTimeoutException : std::runtime_error {
|
||||
public:
|
||||
AwaitTimeoutException() : std::runtime_error("Await timed out") {}
|
||||
};
|
||||
|
||||
// Awaits for the future result of the function passed.
|
||||
// It is meant to be used from background threads to ask something to the main
|
||||
// thread and wait for its result.
|
||||
// If await is called from the main thread it just executes the function
|
||||
// and returns the result.
|
||||
template<typename T>
|
||||
T await(std::function<T()>&& func, int timeout = 0)
|
||||
{
|
||||
if (ui::is_ui_thread())
|
||||
return func();
|
||||
|
||||
std::promise<T> promise;
|
||||
std::future<T> future = promise.get_future();
|
||||
|
||||
// Queue the event
|
||||
os::Event ev;
|
||||
ev.setType(os::Event::Callback);
|
||||
ev.setCallback([func, &promise]() {
|
||||
try {
|
||||
if constexpr (std::is_void<T>())
|
||||
func();
|
||||
else
|
||||
promise.set_value(func());
|
||||
}
|
||||
catch (...) {
|
||||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
});
|
||||
os::queue_event(ev);
|
||||
|
||||
// Wait for the future
|
||||
if (timeout > 0) {
|
||||
auto status = future.wait_for(std::chrono::milliseconds(timeout));
|
||||
if (status != std::future_status::ready) {
|
||||
throw AwaitTimeoutException();
|
||||
}
|
||||
}
|
||||
return future.get();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user