mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-29 21:33:12 +00:00
Merge branch 'main' into beta
This commit is contained in:
commit
d077900fe5
@ -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 |
@ -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" />
|
||||
|
@ -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" />
|
||||
|
@ -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
43
data/widgets/debugger.xml
Normal 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>
|
@ -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/)
|
||||
|
||||
```
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
827
src/app/commands/debugger.cpp
Normal file
827
src/app/commands/debugger.cpp
Normal 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
|
33
src/app/commands/debugger.h
Normal file
33
src/app/commands/debugger.h
Normal 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
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
6
third_party/CMakeLists.txt
vendored
6
third_party/CMakeLists.txt
vendored
@ -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
2
third_party/lua
vendored
@ -1 +1 @@
|
||||
Subproject commit 9b86693268347da63de3fcbbd48c049e188da145
|
||||
Subproject commit db8284f5fc6f02f8f800c99b84b5e4f640e893ce
|
Loading…
x
Reference in New Issue
Block a user