Merge branch 'main' into beta

This commit is contained in:
David Capello 2021-10-13 10:15:39 -03:00
commit d077900fe5
25 changed files with 1071 additions and 13 deletions

View File

@ -4,9 +4,9 @@
cmake_minimum_required(VERSION 3.4)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # We use -std=c++11 instead of -std=gnu++11 in macOS
set(CMAKE_CXX_EXTENSIONS OFF) # We use -std=c++14 instead of -std=gnu++14 in macOS
if(COMMAND cmake_policy)
# CMP0003: Libraries linked via full path no longer produce linker search paths.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -249,6 +249,12 @@
<part id="pal_presets" x="160" y="200" w="5" h="5" />
<part id="pal_options" x="168" y="200" w="5" h="5" />
<part id="pal_resize" x="176" y="200" w="5" h="5" />
<part id="debug_continue" x="208" y="240" w="7" h="7" />
<part id="debug_pause" x="208" y="247" w="7" h="7" />
<part id="debug_step_into" x="222" y="240" w="7" h="7" />
<part id="debug_step_over" x="215" y="240" w="7" h="7" />
<part id="debug_step_out" x="229" y="240" w="7" h="7" />
<part id="debug_breakpoint" x="236" y="240" w="7" h="7" />
<part id="selection_replace" x="176" y="160" w="7" h="7" />
<part id="selection_add" x="184" y="160" w="7" h="7" />
<part id="selection_subtract" x="192" y="160" w="7" h="7" />

View File

@ -667,6 +667,8 @@
<menu id="scripts_menu" text="@.file_scripts" group="file_scripts">
<item command="OpenScriptFolder" text="@.file_open_script_folder" />
<item command="Refresh" text="@.file_rescan_script_folder" />
<separator />
<item command="Debugger" text="@.file_debugger" />
</menu>
<separator group="file_app" />
<item command="Exit" text="@.file_exit" />

View File

@ -290,6 +290,7 @@ CopyMerged = Copy Merged
CopyTiles = Copy Tiles
CropSprite = Crop Sprite
Cut = Cut
Debugger = Debugger
DeselectMask = Deselect Mask
Despeckle = Despeckle
DeveloperConsole = Developer Console
@ -508,6 +509,17 @@ Zoom_In = Zoom In
Zoom_Out = Zoom Out
Zoom_Set = Zoom {0}%
[debugger]
title = Debugger
continue = Start or Continue Debugging
step_into = Step Into
step_over = Step Over
step_out = Step Out
toggle_breakpoint = Toggle Breakpoint
stacktrace = Stacktrace
console = Console
locals = Locals
[document_tab_popup_menu]
duplicate_view = Duplicate &View
open_with_os = &Open with OS
@ -866,6 +878,7 @@ file_repeat_last_export = Repeat &Last Export
file_scripts = Scri&pts
file_open_script_folder = &Open Scripts Folder
file_rescan_script_folder = &Rescan Scripts Folder
file_debugger = &Debugger
file_exit = E&xit
edit = &Edit
edit_undo = &Undo

43
data/widgets/debugger.xml Normal file
View File

@ -0,0 +1,43 @@
<!-- Aseprite -->
<!-- Copyright (C) 2021 by Igara Studio S.A. -->
<gui>
<window id="debugger" text="@.title">
<vbox childspacing="0">
<hbox>
<buttonset columns="4" id="control">
<item icon="debug_continue" tooltip="@.continue" tooltip_dir="bottom" />
<item icon="debug_step_into" tooltip="@.step_into" tooltip_dir="bottom" />
<item icon="debug_step_over" tooltip="@.step_over" tooltip_dir="bottom" />
<item icon="debug_step_out" tooltip="@.step_out" tooltip_dir="bottom" />
</buttonset>
<buttonset columns="1" id="breakpoint">
<item icon="debug_breakpoint" tooltip="@.toggle_breakpoint" tooltip_dir="bottom" />
</buttonset>
</hbox>
<splitter horizontal="true" noborders="true" childspacing="2" expansive="true">
<view id="source_placeholder" />
<splitter vertical="true" noborders="true" childspacing="2" expansive="true">
<vbox id="stack_part">
<separator text="@.stacktrace" horizontal="true" />
<view id="stack_placeholder" expansive="true" />
</vbox>
<vbox id="output_part">
<hbox>
<buttonset id="output_buttons" columns="2">
<item text="@.console" />
<item text="@.locals" />
</buttonset>
</hbox>
<view id="console_view" expansive="true">
<textbox id="console" />
</view>
<view id="locals_view" expansive="true">
<listbox id="locals" expansive="true" />
</view>
<vbox id="eval_placeholder" noborders="true" />
</vbox>
</splitter>
</splitter>
</vbox>
</window>
</gui>

View File

@ -573,6 +573,40 @@ ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
```
# [IXWebSocket](https://github.com/machinezone/IXWebSocket)
```
Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
# [json11](https://github.com/dropbox/json11/)
```

View File

@ -145,6 +145,7 @@ if(ENABLE_SCRIPTING)
set(scripting_files_ui
commands/cmd_developer_console.cpp
commands/cmd_open_script_folder.cpp
commands/debugger.cpp
ui/devconsole_view.cpp)
endif()
if(ENABLE_WEBSOCKET)

View File

@ -95,6 +95,9 @@ namespace {
class ConsoleEngineDelegate : public script::EngineDelegate {
public:
void onConsoleError(const char* text) override {
onConsolePrint(text);
}
void onConsolePrint(const char* text) override {
m_console.printf("%s\n", text);
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -17,6 +18,10 @@
#include "app/ui/main_window.h"
#include "ui/alert.h"
#ifdef ENABLE_SCRIPTING
#include "app/commands/debugger.h"
#endif
namespace app {
class ExitCommand : public Command {
@ -39,6 +44,13 @@ void ExitCommand::onExecute(Context* ctx)
if (Job::runningJobs() > 0)
return;
#ifdef ENABLE_SCRIPTING
if (auto debuggerCommand = dynamic_cast<DebuggerCommand*>(
Commands::instance()->byId(CommandId::Debugger()))) {
debuggerCommand->closeDebugger(ctx);
}
#endif
if (ctx->hasModifiedDocuments()) {
Command* closeAll = Commands::instance()->byId(CommandId::CloseAllFiles());
Params params;

View File

@ -173,6 +173,7 @@ FOR_EACH_COMMAND(Zoom)
#ifdef ENABLE_SCRIPTING
#ifdef ENABLE_UI
FOR_EACH_COMMAND(Debugger)
FOR_EACH_COMMAND(DeveloperConsole)
FOR_EACH_COMMAND(OpenScriptFolder)
#endif

View File

@ -0,0 +1,827 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef ENABLE_SCRIPTING
#error ENABLE_SCRIPTING must be defined
#endif
#include "app/commands/debugger.h"
#include "app/app.h"
#include "app/context.h"
#include "app/script/engine.h"
#include "app/ui/skin/skin_theme.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "base/file_content.h"
#include "base/fs.h"
#include "base/split_string.h"
#include "base/trim_string.h"
#include "fmt/format.h"
#include "ui/entry.h"
#include "ui/listbox.h"
#include "ui/listitem.h"
#include "ui/manager.h"
#include "ui/message.h"
#include "ui/message_loop.h"
#include "ui/paint_event.h"
#include "ui/size_hint_event.h"
#ifdef ENABLE_SCRIPTING
#include "app/script/luacpp.h"
#else
#error Compile the debugger only when ENABLE_SCRIPTING is defined
#endif
#include "debugger.xml.h"
#include <unordered_map>
namespace app {
using namespace ui;
using namespace app::skin;
namespace {
struct FileContent {
base::buffer content;
std::vector<const uint8_t*> lines;
FileContent() { }
FileContent(const FileContent&) = delete;
FileContent& operator=(const FileContent&) = delete;
void clear() {
content.clear();
lines.clear();
}
void setContent(const std::string& c) {
content = base::buffer(
(const uint8_t*)c.c_str(),
(const uint8_t*)c.c_str() + c.size() + 1); // Include nul char
update();
}
void readContentFromFile(const std::string& filename) {
if (base::is_file(filename))
content = base::read_file_content(filename);
else
content.clear();
// Ensure one last nul char to print as const char* the last line
content.push_back(0);
update();
}
private:
void update() {
ASSERT(content.back() == 0);
// Replace all '\r' to ' ' (for Windows EOL and to avoid to paint
// a square on each newline)
for (auto& chr : content) {
if (chr == '\r')
chr = ' ';
}
// Generate the lines array
lines.clear();
for (size_t i=0; i<content.size(); ++i) {
lines.push_back(&content[i]);
size_t j = i;
for (; j<content.size() && content[j] != '\n'; ++j)
;
if (j < content.size())
content[j] = 0;
i = j;
}
}
};
using FileContentPtr = std::shared_ptr<FileContent>;
// Cached file content of each visited file in the debugger.
// key=filename -> value=FileContent
std::unordered_map<std::string, FileContentPtr> g_fileContent;
class DebuggerSource : public Widget {
public:
DebuggerSource() {
}
void clearFile() {
m_fileContent.reset();
m_maxLineWidth = 0;
if (View* view = View::getView(this))
view->updateView();
}
void setFileContent(const FileContentPtr& fileContent) {
m_fileContent = fileContent;
m_maxLineWidth = 0;
if (View* view = View::getView(this))
view->updateView();
setCurrentLine(1);
}
void setFile(const std::string& filename,
const std::string& content) {
FileContentPtr newFileContent(new FileContent);
if (!content.empty()) {
newFileContent->setContent(content);
}
else {
newFileContent->readContentFromFile(filename);
}
g_fileContent[filename] = newFileContent;
setFileContent(newFileContent);
}
void setCurrentLine(int currentLine) {
m_currentLine = currentLine;
if (m_currentLine > 0) {
if (View* view = View::getView(this)) {
const gfx::Rect vp = view->viewportBounds();
const int th = textHeight();
int y = m_currentLine*th - vp.h/2 + th/2;
if (y < 0)
y = 0;
view->setViewScroll(gfx::Point(0, y));
}
}
invalidate();
}
protected:
bool onProcessMessage(Message* msg) override {
return Widget::onProcessMessage(msg);
}
void onPaint(PaintEvent& ev) override {
auto theme = static_cast<SkinTheme*>(this->theme());
Graphics* g = ev.graphics();
View* view = View::getView(this);
gfx::Color linesBg = theme->colors.textboxCodeFace();
gfx::Color bg = theme->colors.textboxFace();
gfx::Color fg = theme->colors.textboxText();
int nlines = (m_fileContent ? m_fileContent->lines.size(): 0);
gfx::Rect vp;
if (view)
vp = view->viewportBounds().offset(-bounds().origin());
else
vp = clientBounds();
auto f = font();
gfx::Rect linesVp(
vp.x, vp.y,
Graphics::measureUITextLength(base::convert_to<std::string>(nlines), f)
+ 4*guiscale(), // TODO configurable from the theme?
vp.h);
vp.x += linesVp.w;
vp.w -= linesVp.w;
// Fill background
g->fillRect(linesBg, linesVp);
g->fillRect(bg, vp);
if (m_fileContent) {
auto icon = theme->parts.debugContinue()->bitmap(0);
gfx::Point pt = clientBounds().origin();
for (int i = 0; i < nlines; ++i) {
if (i+1 == m_currentLine) {
g->drawRgbaSurface(icon, pt.x+linesVp.w, pt.y);
}
// Draw the line number
{
auto lineNumText = base::convert_to<std::string>(i+1);
int lw = Graphics::measureUITextLength(lineNumText, f);
g->drawText(
lineNumText.c_str(),
fg, linesBg,
gfx::Point(pt.x+linesVp.w-lw-2*guiscale(), pt.y));
}
// Draw the this line of source code
const char* line = (const char*)m_fileContent->lines[i];
g->drawText(line, fg, bg,
gfx::Point(pt.x + icon->width() + linesVp.w, pt.y));
pt.y += textHeight();
}
}
}
void onSizeHint(SizeHintEvent& ev) override {
if (m_fileContent) {
if (m_maxLineWidth == 0) {
auto f = font();
std::string tmp;
for (const uint8_t* line : m_fileContent->lines) {
ASSERT(line);
tmp.assign((const char*)line);
m_maxLineWidth = std::max(m_maxLineWidth, Graphics::measureUITextLength(tmp, f));
}
}
ev.setSizeHint(gfx::Size(m_maxLineWidth,
m_fileContent->lines.size() * textHeight()));
}
}
private:
FileContentPtr m_fileContent;
int m_currentLine = -1;
int m_maxLineWidth = 0;
};
class StacktraceBox : public ListBox {
public:
class Item : public ListItem {
public:
Item(lua_Debug* ar, const int stackLevel)
: m_fn(ar->short_src)
, m_ln(ar->currentline)
, m_stackLevel(stackLevel) {
std::string lineContent;
auto it = g_fileContent.find(m_fn);
if (it != g_fileContent.end()) {
const int i = ar->currentline - 1;
if (i >= 0 && i < it->second->lines.size())
lineContent.assign((const char*)it->second->lines[i]);
}
base::trim_string(lineContent, lineContent);
setText(fmt::format(
"{}:{}: {}", base::get_file_name(m_fn), m_ln, lineContent));
}
const std::string& filename() const { return m_fn; }
const int lineNumber() const { return m_ln; }
const int stackLevel() const { return m_stackLevel; }
private:
std::string m_fn;
int m_ln;
int m_stackLevel;
};
StacktraceBox() {
}
void clear() {
while (auto item = firstChild()) {
removeChild(item);
item->deferDelete();
}
}
void update(lua_State* L) {
clear();
lua_Debug ar;
int level = 0;
while (lua_getstack(L, level, &ar)) {
lua_getinfo(L, "lnS", &ar);
if (ar.currentline > 0)
addChild(new Item(&ar, level));
++level;
}
}
};
// TODO similar to DevConsoleView::CommmandEntry, merge both widgets
// or remove the DevConsoleView
class EvalEntry : public Entry {
public:
EvalEntry() : Entry(2048, "") {
setFocusStop(true);
}
obs::signal<void(const std::string&)> Execute;
protected:
bool onProcessMessage(Message* msg) override {
switch (msg->type()) {
case kKeyDownMessage:
if (hasFocus()) {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
KeyScancode scancode = keymsg->scancode();
switch (scancode) {
case kKeyEnter:
case kKeyEnterPad: {
std::string cmd = text();
setText("");
Execute(cmd);
return true;
}
}
}
break;
}
return Entry::onProcessMessage(msg);
}
};
} // anonmous namespace
class Debugger : public gen::Debugger
, public script::EngineDelegate
, public script::DebuggerDelegate {
enum State {
Hidden,
WaitingStart,
WaitingHook,
WaitingNextCommand,
ProcessingCommand,
};
enum Button {
None = -1,
Start = 0, // Start/Pause/Continue
StepInto,
StepOver,
StepOut,
Breakpoint,
};
public:
Debugger() {
control()->ItemChange.connect([this] {
auto button = (Button)control()->selectedItem();
control()->deselectItems();
onControl(button);
});
breakpoint()->setVisible(false); // TODO make this visible
breakpoint()->ItemChange.connect([this] {
breakpoint()->deselectItems();
onControl(Button::Breakpoint);
});
Close.connect([this]{
m_state = State::Hidden;
auto app = App::instance();
app->scriptEngine()->setDelegate(m_oldDelegate);
app->scriptEngine()->stopDebugger();
// Clear the cached content of all debugged files
g_fileContent.clear();
// Clear console & locals.
console()->setText(std::string());
clearLocals();
});
m_stacktrace.Change.connect([this]{
onStacktraceChange();
});
m_evalEntry.Execute.connect([this](const std::string& expr){
onEvalExpression(expr);
});
sourcePlaceholder()->attachToView(&m_sourceViewer);
sourcePlaceholder()->setVisible(false);
stackPlaceholder()->attachToView(&m_stacktrace);
stackPlaceholder()->setVisible(false);
outputButtons()->ItemChange.connect([this]{ onOutputButtonChange(); });
outputButtons()->setSelectedItem(0);
onOutputButtonChange();
console()->setVisible(false);
locals()->setVisible(false);
evalPlaceholder()->addChild(&m_evalEntry);
}
void openDebugger() {
m_state = State::WaitingHook;
updateControls();
openWindow();
auto app = App::instance();
m_oldDelegate = app->scriptEngine()->delegate();
app->scriptEngine()->setDelegate(this);
app->scriptEngine()->startDebugger(this);
}
void onControl(Button button) {
ASSERT(m_state != State::Hidden);
m_lastCommand = button;
switch (button) {
case Button::Start:
if (m_state == State::WaitingStart) {
m_state = State::WaitingHook;
m_commandStackLevel = m_stackLevel = 0;
m_sourceViewer.clearFile();
}
else {
m_state = State::WaitingStart;
m_commandStackLevel = m_stackLevel = 0;
m_sourceViewer.clearFile();
}
break;
case Button::StepInto:
case Button::StepOver:
case Button::StepOut:
m_state = State::ProcessingCommand;
m_commandStackLevel = m_stackLevel;
break;
case Button::Breakpoint:
// m_state = State::WaitingNextCommand;
// TODO
break;
}
updateControls();
}
void updateControls() {
bool isRunning = (m_state == State::WaitingHook ||
m_state == State::ProcessingCommand);
bool canRunCommands = (m_state == State::WaitingNextCommand);
auto theme = static_cast<SkinTheme*>(this->theme());
control()->getItem(0)->setIcon(
(isRunning ? theme->parts.debugPause() :
theme->parts.debugContinue()));
control()->getItem(1)->setEnabled(canRunCommands);
control()->getItem(2)->setEnabled(canRunCommands);
control()->getItem(3)->setEnabled(canRunCommands);
breakpoint()->getItem(0)->setEnabled(canRunCommands);
// Calling this we update the mouse widget and we can click the
// same button several times.
//
// TODO why is this needed? shouldn't be this automatic from the
// ui::Manager side?
if (auto man = manager())
man->_updateMouseWidgets();
}
// script::EngineDelegate impl
void onConsoleError(const char* text) override {
m_fileOk = false;
onConsolePrint(text);
// Get error filename and number
{
std::vector<std::string> parts;
base::split_string(text, parts, { ":" });
if (parts.size() >= 3) {
const std::string& fn = parts[0];
const std::string& ln = parts[1];
if (base::is_file(fn)) {
m_sourceViewer.setFile(fn, std::string());
m_sourceViewer.setCurrentLine(
std::strtol(ln.c_str(), nullptr, 10));
sourcePlaceholder()->setVisible(true);
layout();
}
}
}
// Stop debugger
auto app = App::instance();
waitNextCommand(app->scriptEngine()->luaState());
updateControls();
}
void onConsolePrint(const char* text) override {
console()->setVisible(true);
consoleView()->setViewScroll(gfx::Point(0, 0));
if (text) {
std::string stext(console()->text());
stext += text;
stext += '\n';
console()->setText(stext);
}
layout();
consoleView()->setViewScroll(
gfx::Point(0, consoleView()->getScrollableSize().h));
}
// script::DebuggerDelegate impl
void hook(lua_State* L, lua_Debug* ar) override {
lua_getinfo(L, "lnS", ar);
switch (ar->event) {
case LUA_HOOKCALL: ++m_stackLevel; break;
case LUA_HOOKRET: --m_stackLevel; break;
case LUA_HOOKLINE:
case LUA_HOOKCOUNT:
case LUA_HOOKTAILCALL:
break;
}
switch (m_state) {
case State::WaitingStart:
// Do nothing (the execution continues regularly, unexpected
// script being executed)
return;
case State::WaitingHook:
if (ar->event == LUA_HOOKLINE)
waitNextCommand(L);
else
return;
break;
case State::ProcessingCommand:
switch (m_lastCommand) {
case Button::Start:
if (ar->event == LUA_HOOKLINE) {
// TODO Wait next error...
}
else {
return;
}
return;
case Button::StepInto:
if (ar->event == LUA_HOOKLINE) {
waitNextCommand(L);
}
else {
return;
}
break;
case Button::StepOver:
if (ar->event == LUA_HOOKLINE &&
m_stackLevel == m_commandStackLevel) {
waitNextCommand(L);
}
else {
return;
}
break;
case Button::StepOut:
if (ar->event == LUA_HOOKLINE &&
m_stackLevel < m_commandStackLevel) {
waitNextCommand(L);
}
else {
return;
}
break;
case Button::Breakpoint:
if (ar->event != LUA_HOOKLINE) {
// TODO
return;
}
break;
}
break;
}
updateControls();
if (m_state == State::WaitingNextCommand) {
sourcePlaceholder()->setVisible(true);
stackPlaceholder()->setVisible(true);
layout();
if (m_lastFile != ar->short_src &&
base::is_file(ar->short_src)) {
m_lastFile = ar->short_src;
m_sourceViewer.setFile(m_lastFile, std::string());
}
if (m_lastFile == ar->short_src) {
m_sourceViewer.setCurrentLine(ar->currentline);
}
if (!m_expanded) {
m_expanded = true;
gfx::Rect bounds = this->bounds();
if (m_sourceViewer.isVisible()) {
bounds.w = std::max(bounds.w, 256*guiscale());
bounds.h = std::max(bounds.h, 256*guiscale());
}
expandWindow(bounds.size());
invalidate();
}
MessageLoop loop(Manager::getDefault());
while (m_state == State::WaitingNextCommand)
loop.pumpMessages();
}
}
void startFile(const std::string& file,
const std::string& content) override {
m_stackLevel = 0;
m_fileOk = true;
m_sourceViewer.setFile(file, content);
}
void endFile(const std::string& file) override {
if (m_fileOk)
m_sourceViewer.clearFile();
m_stacktrace.clear();
m_lastFile.clear();
m_stackLevel = 0;
}
private:
void waitNextCommand(lua_State* L) {
m_state = State::WaitingNextCommand;
m_stacktrace.update(L);
updateLocals(L, 0);
}
void onOutputButtonChange() {
consoleView()->setVisible(isConsoleSelected());
localsView()->setVisible(isLocalsSelected());
layout();
}
bool isConsoleSelected() const {
return (outputButtons()->selectedItem() == 0);
}
bool isLocalsSelected() const {
return (outputButtons()->selectedItem() == 1);
}
void onStacktraceChange() {
if (auto item = dynamic_cast<StacktraceBox::Item*>(m_stacktrace.getSelectedChild())) {
auto it = g_fileContent.find(item->filename());
if (it != g_fileContent.end())
m_sourceViewer.setFileContent(it->second);
else
m_sourceViewer.setFile(item->filename(), std::string());
m_sourceViewer.setCurrentLine(item->lineNumber());
auto app = App::instance();
updateLocals(app->scriptEngine()->luaState(), item->stackLevel());
}
}
void onEvalExpression(const std::string& expr) {
auto app = App::instance();
app->scriptEngine()->evalCode(expr);
}
void clearLocals() {
while (auto item = locals()->firstChild()) {
locals()->removeChild(item);
item->deferDelete();
}
}
void updateLocals(lua_State* L, int level) {
clearLocals();
lua_Debug ar;
if (lua_getstack(L, level, &ar)) {
for (int n=1; ; ++n) {
const char* name = lua_getlocal(L, &ar, n);
if (!name)
break;
// These special names are returned by luaG_findlocal()
if (strcmp(name, "(temporary)") == 0 ||
strcmp(name, "(C temporary)") == 0) {
lua_pop(L, 1);
continue;
}
std::string v = "?";
switch (lua_type(L, -1)) {
case LUA_TNONE:
v = "none";
break;
case LUA_TNIL:
v = "nil";
break;
case LUA_TBOOLEAN:
v = (lua_toboolean(L, -1) ? "true": "false");
break;
case LUA_TLIGHTUSERDATA:
v = "lightuserdata";
break;
case LUA_TNUMBER:
v = lua_tostring(L, -1);
break;
case LUA_TSTRING:
v = lua_tostring(L, -1);
break;
case LUA_TTABLE:
v = "table";
break;
case LUA_TFUNCTION:
v = "function";
break;
case LUA_TUSERDATA:
v = "userdata";
break;
case LUA_TTHREAD:
v = "thread";
break;
}
std::string itemText = fmt::format("{}={}", name, v);
lua_pop(L, 1);
locals()->addChild(new ListItem(itemText));
}
}
locals()->setVisible(true);
localsView()->updateView();
}
EngineDelegate* m_oldDelegate = nullptr;
bool m_expanded = false;
State m_state = State::Hidden;
Button m_lastCommand = Button::None;
DebuggerSource m_sourceViewer;
StacktraceBox m_stacktrace;
EvalEntry m_evalEntry;
std::string m_lastFile;
int m_commandStackLevel = 0;
int m_stackLevel = 0;
bool m_fileOk = true;
};
DebuggerCommand::DebuggerCommand()
: Command(CommandId::Debugger(), CmdRecordableFlag)
{
}
void DebuggerCommand::closeDebugger(Context* ctx)
{
if (ctx->isUIAvailable()) {
if (m_debugger)
m_debugger->closeWindow(nullptr);
}
}
void DebuggerCommand::onExecute(Context* ctx)
{
if (ctx->isUIAvailable()) {
auto app = App::instance();
// Create the debugger window for the first time
if (!m_debugger) {
m_debugger.reset(new Debugger);
app->Exit.connect([this]{
m_debugger.reset();
});
}
if (!m_debugger->isVisible()) {
m_debugger->openDebugger();
}
else {
m_debugger->closeWindow(nullptr);
}
}
}
Command* CommandFactory::createDebuggerCommand()
{
return new DebuggerCommand;
}
} // namespace app

View File

@ -0,0 +1,33 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_COMMANDS_DEBUGGER_H_INCLUDED
#define APP_COMMANDS_DEBUGGER_H_INCLUDED
#pragma once
#include "app/commands/command.h"
#include <memory>
namespace app {
class Debugger;
class DebuggerCommand : public Command {
public:
DebuggerCommand();
void closeDebugger(Context* ctx);
protected:
void onExecute(Context* context) override;
std::unique_ptr<Debugger> m_debugger;
};
} // namespace app
#endif

View File

@ -48,6 +48,9 @@ base::Chrono luaClock;
// Stack of script filenames that are being executed.
std::stack<std::string> current_script_dirs;
// Just one debugger delegate is possible.
DebuggerDelegate* g_debuggerDelegate = nullptr;
class AddScriptFilename {
public:
AddScriptFilename(const std::string& fn) {
@ -468,7 +471,7 @@ bool Engine::evalCode(const std::string& code,
lua_pcall(L, 0, 1, 0)) {
const char* s = lua_tostring(L, -1);
if (s)
onConsolePrint(s);
onConsoleError(s);
ok = false;
m_returnCode = -1;
}
@ -491,7 +494,7 @@ bool Engine::evalCode(const std::string& code,
lua_pop(L, 1);
}
catch (const std::exception& ex) {
onConsolePrint(ex.what());
onConsoleError(ex.what());
ok = false;
m_returnCode = -1;
}
@ -516,7 +519,44 @@ bool Engine::evalFile(const std::string& filename,
AddScriptFilename add(absFilename);
set_app_params(L, params);
return evalCode(buf.str(), "@" + absFilename);
if (g_debuggerDelegate)
g_debuggerDelegate->startFile(absFilename, buf.str());
bool result = evalCode(buf.str(), "@" + absFilename);
if (g_debuggerDelegate)
g_debuggerDelegate->endFile(absFilename);
return result;
}
void Engine::startDebugger(DebuggerDelegate* debuggerDelegate)
{
g_debuggerDelegate = debuggerDelegate;
lua_Hook hook = [](lua_State* L, lua_Debug* ar) {
int ret = lua_getinfo(L, "l", ar);
if (ret == 0 || ar->currentline < 0)
return;
g_debuggerDelegate->hook(L, ar);
};
lua_sethook(L, hook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT, 1);
}
void Engine::stopDebugger()
{
lua_sethook(L, nullptr, 0, 0);
}
void Engine::onConsoleError(const char* text)
{
if (text && m_delegate)
m_delegate->onConsoleError(text);
else
onConsolePrint(text);
}
void Engine::onConsolePrint(const char* text)

View File

@ -23,10 +23,12 @@
#include "gfx/fwd.h"
#include <cstdio>
#include <string>
#include <functional>
#include <map>
#include <string>
struct lua_State;
struct lua_Debug;
namespace base {
class Version;
@ -63,9 +65,19 @@ namespace app {
class EngineDelegate {
public:
virtual ~EngineDelegate() { }
virtual void onConsoleError(const char* text) = 0;
virtual void onConsolePrint(const char* text) = 0;
};
class DebuggerDelegate {
public:
virtual ~DebuggerDelegate() { }
virtual void hook(lua_State* L, lua_Debug* ar) = 0;
virtual void startFile(const std::string& file,
const std::string& content) = 0;
virtual void endFile(const std::string& file) = 0;
};
class Engine {
public:
Engine();
@ -92,7 +104,11 @@ namespace app {
lua_State* luaState() { return L; }
void startDebugger(DebuggerDelegate* debuggerDelegate);
void stopDebugger();
private:
void onConsoleError(const char* text);
void onConsolePrint(const char* text);
lua_State* L;

View File

@ -148,7 +148,10 @@ void ButtonSet::Item::onPaint(ui::PaintEvent& ev)
if (m_icon) {
os::Surface* bmp = m_icon->bitmap(0);
if (isSelected() && hasCapture())
if (!isEnabled())
g->drawColoredRgbaSurface(bmp, theme->colors.disabled(),
iconRc.x, iconRc.y);
else if (isSelected() && hasCapture())
g->drawColoredRgbaSurface(bmp, theme->colors.buttonSelectedText(),
iconRc.x, iconRc.y);
else if (m_mono)

View File

@ -154,6 +154,11 @@ void DevConsoleView::onExecuteCommand(const std::string& cmd)
m_engine->evalCode(cmd);
}
void DevConsoleView::onConsoleError(const char* text)
{
onConsolePrint(text);
}
void DevConsoleView::onConsolePrint(const char* text)
{
if (text)

View File

@ -43,6 +43,7 @@ namespace app {
void onTabPopup(Workspace* workspace) override;
// EngineDelegate impl
virtual void onConsoleError(const char* text) override;
virtual void onConsolePrint(const char* text) override;
protected:

View File

@ -394,18 +394,19 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
else if (elem_name == "slider") {
const char *min = elem->Attribute("min");
const char *max = elem->Attribute("max");
int min_value = min != NULL ? strtol(min, NULL, 10): 0;
int max_value = max != NULL ? strtol(max, NULL, 10): 0;
int min_value = (min ? strtol(min, nullptr, 10): 0);
int max_value = (max ? strtol(max, nullptr, 10): 0);
widget = new Slider(min_value, max_value, min_value);
}
else if (elem_name == "textbox") {
const char* text = (elem->GetText() ? elem->GetText(): "");
bool wordwrap = bool_attr(elem, "wordwrap", false);
if (!widget)
widget = new TextBox(elem->GetText(), 0);
widget = new TextBox(text, 0);
else
widget->setText(elem->GetText());
widget->setText(text);
if (wordwrap)
widget->setAlign(widget->align() | WORDWRAP);

View File

@ -1,4 +1,5 @@
// Aseprite Code Generator
// Copyright (c) 2021 Igara Studio S.A.
// Copyright (c) 2014-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -153,6 +154,9 @@ static Item convert_to_item(TiXmlElement* elem)
if (name == "splitter")
return item.typeIncl("ui::Splitter",
"ui/splitter.h");
if (name == "textbox")
return item.typeIncl("ui::TextBox",
"ui/textbox.h");
if (name == "tipwindow")
return item.typeIncl("ui::TipWindow",
"ui/tooltips.h");

View File

@ -1532,6 +1532,12 @@ void Manager::_runModalWindow(Window* window)
loop.pumpMessages();
}
void Manager::_updateMouseWidgets()
{
// Update mouse widget.
updateMouseWidgets(ui::get_mouse_position(), nullptr);
}
bool Manager::onProcessMessage(Message* msg)
{
switch (msg->type()) {

View File

@ -99,6 +99,7 @@ namespace ui {
void _openWindow(Window* window, bool center);
void _closeWindow(Window* window, bool redraw_background);
void _runModalWindow(Window* window);
void _updateMouseWidgets();
protected:
bool onProcessMessage(Message* msg) override;

View File

@ -747,7 +747,7 @@ void Theme::drawSlices(Graphics* g, os::Surface* sheet,
void Theme::drawTextBox(Graphics* g, Widget* widget,
int* w, int* h, gfx::Color bg, gfx::Color fg)
{
View* view = View::getView(widget);
View* view = (g ? View::getView(widget): nullptr);
char* text = const_cast<char*>(widget->text().c_str());
char* beg, *end;
int x1, y1;

View File

@ -36,6 +36,12 @@ endif()
if(WITH_WEBP_SUPPORT)
set(WEBP_BUILD_EXTRAS OFF CACHE BOOL "Build extras.")
add_subdirectory(libwebp)
if(NOT USE_SHARED_LIBPNG)
add_dependencies(webp ${PNG_LIBRARY})
add_dependencies(webpdemux ${PNG_LIBRARY})
add_dependencies(libwebpmux ${PNG_LIBRARY})
endif()
endif()
if(NOT USE_SHARED_TINYXML)

2
third_party/lua vendored

@ -1 +1 @@
Subproject commit 9b86693268347da63de3fcbbd48c049e188da145
Subproject commit db8284f5fc6f02f8f800c99b84b5e4f640e893ce