Merge branch 'master' into beta

This commit is contained in:
David Capello 2020-07-30 16:48:43 -03:00
commit 20d763ca4b
33 changed files with 475 additions and 143 deletions

2
laf

@ -1 +1 @@
Subproject commit c8e6b958fdcca7809ab2b50f9cfed7d9387a2c86
Subproject commit ab08b047defdd3390e3aaa55a02c6a10225496ac

View File

@ -347,6 +347,7 @@ int App::initialize(const AppOptions& options)
return code;
}
LOG("APP: Finish launching...\n");
system->finishLaunching();
return 0;
}

View File

@ -33,6 +33,7 @@
#include "doc/slice.h"
#include "doc/tag.h"
#include "doc/tags.h"
#include "os/system.h"
#include "render/dithering_algorithm.h"
#include <algorithm>
@ -576,8 +577,14 @@ int CliProcessor::process(Context* ctx)
else {
cof.document = nullptr;
cof.filename = base::normalize_path(value.value());
if (openFile(ctx, cof))
if (// Check that the filename wasn't used loading a sequence
// of images as one sprite
m_usedFiles.find(cof.filename) == m_usedFiles.end() &&
// Open sprite
openFile(ctx, cof)) {
lastDoc = cof.document;
}
}
}
@ -610,12 +617,19 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
m_delegate->beforeOpenFile(cof);
Doc* oldDoc = ctx->activeDocument();
Command* openCommand = Commands::instance()->byId(CommandId::OpenFile());
Params params;
params.set("filename", cof.filename.c_str());
if (cof.oneFrame)
params.set("oneframe", "true");
ctx->executeCommand(openCommand, params);
m_batch.open(ctx,
cof.filename,
cof.oneFrame);
// Mark used file names as "already processed" so we don't try to
// open then again
for (const auto& usedFn : m_batch.usedFiles()) {
auto fn = base::normalize_path(usedFn);
m_usedFiles.insert(fn);
os::instance()->markCliFileAsProcessed(fn);
}
Doc* doc = ctx->activeDocument();
// If the active document is equal to the previous one, it

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello
//
// This program is distributed under the terms of
@ -12,9 +12,11 @@
#include "app/cli/cli_delegate.h"
#include "app/cli/cli_open_file.h"
#include "app/doc_exporter.h"
#include "app/util/open_batch.h"
#include "doc/selected_layers.h"
#include <memory>
#include <set>
#include <string>
#include <vector>
@ -58,6 +60,11 @@ namespace app {
CliDelegate* m_delegate;
const AppOptions& m_options;
std::unique_ptr<DocExporter> m_exporter;
// Files already used in the CLI processing (e.g. when used to
// load a sequence of files) so we don't ask for them again.
std::set<std::string> m_usedFiles;
OpenBatchOfFiles m_batch;
};
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -11,7 +11,11 @@
#include "app/cmd/set_tag_range.h"
#include "app/doc.h"
#include "app/doc_event.h"
#include "doc/sprite.h"
#include "doc/tag.h"
#include "doc/tags.h"
namespace app {
namespace cmd {
@ -37,5 +41,16 @@ void SetTagRange::onUndo()
tag()->incrementVersion();
}
void SetTagRange::onFireNotifications()
{
Tag* tag = this->tag();
Sprite* sprite = tag->owner()->sprite();
Doc* doc = static_cast<Doc*>(sprite->document());
DocEvent ev(doc);
ev.sprite(sprite);
ev.tag(tag);
doc->notify_observers<DocEvent&>(&DocObserver::onTagChange, ev);
}
} // namespace cmd
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -25,6 +25,7 @@ namespace cmd {
protected:
void onExecute() override;
void onUndo() override;
void onFireNotifications() override;
size_t onMemSize() const override {
return sizeof(*this);
}

View File

@ -42,10 +42,12 @@ using namespace app::skin;
#endif
struct CanvasSizeParams : public NewParams {
Param<bool> ui { this, true, "ui" };
Param<int> left { this, 0, "left" };
Param<int> right { this, 0, "right" };
Param<int> top { this, 0, "top" };
Param<int> bottom { this, 0, "bottom" };
Param<gfx::Rect> bounds { this, gfx::Rect(0, 0, 0, 0), "bounds" };
Param<bool> trimOutside { this, false, "trimOutside" };
};
@ -58,9 +60,10 @@ class CanvasSizeWindow : public app::gen::CanvasSize
public:
enum class Dir { NW, N, NE, W, C, E, SW, S, SE };
CanvasSizeWindow(const CanvasSizeParams& params)
CanvasSizeWindow(const CanvasSizeParams& params,
const gfx::Rect& bounds)
: m_editor(current_editor)
, m_rect(0, 0, current_editor->sprite()->width(), current_editor->sprite()->height())
, m_rect(bounds)
, m_selectBoxState(
new SelectBoxState(
this, m_rect,
@ -73,6 +76,7 @@ public:
setRight(0);
setTop(0);
setBottom(0);
updateBorderFromRect();
width() ->Change.connect([this]{ onSizeChange(); });
height()->Change.connect([this]{ onSizeChange(); });
@ -307,18 +311,31 @@ bool CanvasSizeCommand::onEnabled(Context* context)
void CanvasSizeCommand::onExecute(Context* context)
{
#ifdef ENABLE_UI
const bool ui = (params().ui() && context->isUIAvailable());
#endif
const ContextReader reader(context);
const Sprite* sprite(reader.sprite());
auto& params = this->params();
gfx::Rect bounds(0, 0, sprite->width(), sprite->height());
if (params.bounds.isSet()) {
bounds = params.bounds();
}
else {
bounds.enlarge(
gfx::Border(params.left(), params.top(),
params.right(), params.bottom()));
}
#ifdef ENABLE_UI
if (context->isUIAvailable()) {
if (ui) {
if (!params.trimOutside.isSet()) {
params.trimOutside(Preferences::instance().canvasSize.trimOutside());
}
// load the window widget
std::unique_ptr<CanvasSizeWindow> window(new CanvasSizeWindow(params));
std::unique_ptr<CanvasSizeWindow> window(new CanvasSizeWindow(params, bounds));
window->remapWindow();
@ -346,18 +363,15 @@ void CanvasSizeCommand::onExecute(Context* context)
params.trimOutside(window->getTrimOutside());
Preferences::instance().canvasSize.trimOutside(params.trimOutside());
bounds.enlarge(
gfx::Border(params.left(), params.top(),
params.right(), params.bottom()));
}
#endif
// Resize canvas
int x1 = -params.left();
int y1 = -params.top();
int x2 = sprite->width() + params.right();
int y2 = sprite->height() + params.bottom();
if (x2 <= x1) x2 = x1+1;
if (y2 <= y1) y2 = y1+1;
if (bounds.w < 1) bounds.w = 1;
if (bounds.h < 1) bounds.h = 1;
{
ContextWriter writer(reader);
@ -365,12 +379,7 @@ void CanvasSizeCommand::onExecute(Context* context)
Sprite* sprite = writer.sprite();
Tx tx(writer.context(), "Canvas Size");
DocApi api = doc->getApi(tx);
api.cropSprite(sprite,
gfx::Rect(x1, y1,
base::clamp(x2-x1, 1, DOC_SPRITE_MAX_WIDTH),
base::clamp(y2-y1, 1, DOC_SPRITE_MAX_HEIGHT)),
params.trimOutside());
api.cropSprite(sprite, bounds, params.trimOutside());
tx.commit();
#ifdef ENABLE_UI

View File

@ -122,6 +122,12 @@ void OpenFileCommand::onExecute(Context* context)
// The user cancelled the operation through UI
return;
}
// If the user selected several files, show the checkbox to repeat
// the action for future filenames in the batch of selected files
// to open.
if (filenames.size() > 1)
m_repeatCheckbox = true;
}
else
#endif // ENABLE_UI
@ -152,7 +158,11 @@ void OpenFileCommand::onExecute(Context* context)
if (m_oneFrame)
flags |= FILE_LOAD_ONE_FRAME;
for (const auto& filename : filenames) {
std::string filename;
while (!filenames.empty()) {
filename = filenames[0];
filenames.erase(filenames.begin());
std::unique_ptr<FileOp> fop(
FileOp::createLoadDocumentOperation(
context, filename, flags));
@ -168,18 +178,29 @@ void OpenFileCommand::onExecute(Context* context)
}
else {
if (fop->isSequence()) {
if (fop->sequenceFlags() & FILE_LOAD_SEQUENCE_YES) {
m_seqDecision = SequenceDecision::Agree;
flags &= ~FILE_LOAD_SEQUENCE_ASK;
flags |= FILE_LOAD_SEQUENCE_YES;
}
else if (fop->sequenceFlags() & FILE_LOAD_SEQUENCE_NONE) {
m_seqDecision = SequenceDecision::Skip;
flags &= ~FILE_LOAD_SEQUENCE_ASK;
flags |= FILE_LOAD_SEQUENCE_NONE;
}
m_usedFiles = fop->filenames();
for (std::string fn : fop->filenames()) {
fn = base::normalize_path(fn);
m_usedFiles.push_back(fn);
auto it = std::find(filenames.begin(), filenames.end(), fn);
if (it != filenames.end())
filenames.erase(it);
}
}
else {
m_usedFiles.push_back(fop->filename());
auto fn = base::normalize_path(fop->filename());
m_usedFiles.push_back(fn);
}
OpenFileJob task(fop.get());

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello
//
// This program is distributed under the terms of
@ -27,6 +28,10 @@ namespace app {
return m_usedFiles;
}
SequenceDecision seqDecision() const {
return m_seqDecision;
}
protected:
void onLoadParams(const Params& params) override;
void onExecute(Context* context) override;

View File

@ -24,6 +24,7 @@
#include "filters/hue_saturation_filter.h"
#include "filters/outline_filter.h"
#include "filters/tiled_mode.h"
#include "gfx/rect.h"
#ifdef ENABLE_SCRIPTING
#include "app/script/engine.h"
@ -61,6 +62,21 @@ void Param<std::string>::fromString(const std::string& value)
setValue(value);
}
template<>
void Param<gfx::Rect>::fromString(const std::string& value)
{
gfx::Rect rect;
std::vector<std::string> parts;
base::split_string(value, parts, ",");
if (parts.size() == 4) {
rect.x = base::convert_to<int>(parts[0]);
rect.y = base::convert_to<int>(parts[1]);
rect.w = base::convert_to<int>(parts[2]);
rect.h = base::convert_to<int>(parts[3]);
}
setValue(rect);
}
template<>
void Param<doc::algorithm::ResizeMethod>::fromString(const std::string& value)
{
@ -237,6 +253,12 @@ void Param<std::string>::fromLua(lua_State* L, int index)
setValue(std::string());
}
template<>
void Param<gfx::Rect>::fromLua(lua_State* L, int index)
{
setValue(script::convert_args_into_rect(L, index));
}
template<>
void Param<doc::algorithm::ResizeMethod>::fromLua(lua_State* L, int index)
{

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -73,6 +73,9 @@ namespace app {
virtual void onSelectionChanged(DocEvent& ev) { }
virtual void onSelectionBoundariesChanged(DocEvent& ev) { }
// Tags
virtual void onTagChange(DocEvent& ev) { }
// Slices
virtual void onSliceNameChange(DocEvent& ev) { }

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -17,9 +17,13 @@
#include "base/cfile.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "fmt/format.h"
namespace app {
// Max supported .bmp size (to filter out invalid image sizes)
const uint32_t kMaxBmpSize = 1024*1024*128; // 128 MB
using namespace base;
class BmpFormat : public FileFormat {
@ -212,6 +216,9 @@ static void read_bmicolors(FileOp* fop, int bytes, FILE *f, bool win_flag)
}
}
// Set the number of colors in the palette
fop->sequenceSetNColors(j);
for (; i<bytes; i++)
fgetc(f);
}
@ -643,6 +650,28 @@ bool BmpFormat::onLoad(FileOp *fop)
return false;
}
// Check image size is valid
{
if (int(infoheader.biWidth) < 1 ||
ABS(int(infoheader.biHeight)) == 0) {
fop->setError("Invalid BMP size.\n");
return false;
}
uint32_t size = infoheader.biWidth * uint32_t(ABS(int(infoheader.biHeight)));
if (infoheader.biBitCount >= 8)
size *= (infoheader.biBitCount / 8);
else if (8 / infoheader.biBitCount > 0)
size /= (8 / infoheader.biBitCount);
if (size > kMaxBmpSize) {
fop->setError(fmt::format("BMP size unsupported ({:.2f} MB > {:.2f} MB).\n",
size / 1024.0 / 1024.0,
kMaxBmpSize / 1024.0 / 1024.0).c_str());
return false;
}
}
if ((infoheader.biBitCount == 32) ||
(infoheader.biBitCount == 24) ||
(infoheader.biBitCount == 16))
@ -723,21 +752,48 @@ bool BmpFormat::onLoad(FileOp *fop)
bool BmpFormat::onSave(FileOp *fop)
{
const Image* image = fop->sequenceImage();
const int w = image->width();
const int h = image->height();
int bfSize;
int biSizeImage;
int bpp = (image->pixelFormat() == IMAGE_RGB) ? 24 : 8;
int filler = 3 - ((image->width()*(bpp/8)-1) & 3);
int ncolors = fop->sequenceGetNColors();
int bpp = 0;
switch (image->pixelFormat()) {
case IMAGE_RGB:
bpp = 24;
break;
case IMAGE_GRAYSCALE:
bpp = 8;
break;
case IMAGE_INDEXED: {
if (ncolors > 16)
bpp = 8;
else if (ncolors > 2)
bpp = 4;
else
bpp = 1;
ncolors = (1 << bpp);
break;
}
default:
// TODO save IMAGE_BITMAP as 1bpp bmp?
// Invalid image format
fop->setError("Unsupported color mode.\n");
return false;
}
int filler = int((32 - ((w*bpp-1) & 31)-1) / 8);
int c, i, j, r, g, b;
if (bpp == 8) {
biSizeImage = (image->width() + filler) * image->height();
bfSize = (54 /* header */
+ 256*4 /* palette */
+ biSizeImage); /* image data */
if (bpp <= 8) {
biSizeImage = (w + filler)*bpp/8 * h;
bfSize = (54 // header
+ ncolors*4 // palette
+ biSizeImage); // image data
}
else {
biSizeImage = (image->width()*3 + filler) * image->height();
bfSize = 54 + biSizeImage; /* header + image data */
biSizeImage = (w*3 + filler) * h;
bfSize = 54 + biSizeImage; // header + image data
}
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
@ -749,15 +805,15 @@ bool BmpFormat::onSave(FileOp *fop)
fputw(0, f); /* bfReserved1 */
fputw(0, f); /* bfReserved2 */
if (bpp == 8) /* bfOffBits */
fputl(54+256*4, f);
if (bpp <= 8) /* bfOffBits */
fputl(54+ncolors*4, f);
else
fputl(54, f);
/* info_header */
fputl(40, f); /* biSize */
fputl(image->width(), f); /* biWidth */
fputl(image->height(), f); /* biHeight */
fputl(w, f); /* biWidth */
fputl(h, f); /* biHeight */
fputw(1, f); /* biPlanes */
fputw(bpp, f); /* biBitCount */
fputl(0, f); /* biCompression */
@ -765,12 +821,12 @@ bool BmpFormat::onSave(FileOp *fop)
fputl(0xB12, f); /* biXPelsPerMeter (0xB12 = 72 dpi) */
fputl(0xB12, f); /* biYPelsPerMeter */
if (bpp == 8) {
fputl(256, f); /* biClrUsed */
fputl(256, f); /* biClrImportant */
if (bpp <= 8) {
fputl(ncolors, f); /* biClrUsed */
fputl(ncolors, f); /* biClrImportant */
/* palette */
for (i=0; i<256; i++) {
// Save the palette
for (i=0; i<ncolors; i++) {
fop->sequenceGetColor(i, &r, &g, &b);
fputc(b, f);
fputc(g, f);
@ -783,27 +839,49 @@ bool BmpFormat::onSave(FileOp *fop)
fputl(0, f); /* biClrImportant */
}
/* image data */
for (i=image->height()-1; i>=0; i--) {
for (j=0; j<image->width(); j++) {
if (bpp == 8) {
if (image->pixelFormat() == IMAGE_INDEXED)
fputc(get_pixel_fast<IndexedTraits>(image, j, i), f);
else if (image->pixelFormat() == IMAGE_GRAYSCALE)
fputc(graya_getv(get_pixel_fast<GrayscaleTraits>(image, j, i)), f);
}
else {
c = get_pixel_fast<RgbTraits>(image, j, i);
fputc(rgba_getb(c), f);
fputc(rgba_getg(c), f);
fputc(rgba_getr(c), f);
}
// Only used in indexed mode
int colorsPerByte = std::max(1, 8/bpp);
int colorMask;
switch (bpp) {
case 8: colorMask = 0xFF; break;
case 4: colorMask = 0x0F; break;
case 1: colorMask = 0x01; break;
default: colorMask = 0; break;
}
// Save image pixels (from bottom to top)
for (i=h-1; i>=0; i--) {
switch (image->pixelFormat()) {
case IMAGE_RGB:
for (j=0; j<w; ++j) {
c = get_pixel_fast<RgbTraits>(image, j, i);
fputc(rgba_getb(c), f);
fputc(rgba_getg(c), f);
fputc(rgba_getr(c), f);
}
break;
case IMAGE_GRAYSCALE:
for (j=0; j<w; ++j) {
c = get_pixel_fast<GrayscaleTraits>(image, j, i);
fputc(graya_getv(c), f);
}
break;
case IMAGE_INDEXED:
for (j=0; j<w; ) {
uint8_t value = 0;
for (int k=colorsPerByte-1; k>=0 && j<w; --k, ++j) {
c = get_pixel_fast<IndexedTraits>(image, j, i);
value |= (c & colorMask) << (bpp*k);
}
fputc(value, f);
}
break;
}
for (j=0; j<filler; j++)
fputc(0, f);
fop->setProgress((float)(image->height()-i) / (float)image->height());
fop->setProgress((float)(h-i) / (float)h);
}
if (ferror(f)) {

View File

@ -36,6 +36,7 @@
#include "app/ui/status_bar.h"
#include "app/ui/toolbar.h"
#include "app/ui_context.h"
#include "app/util/open_batch.h"
#include "base/clamp.h"
#include "base/fs.h"
#include "base/memory.h"
@ -368,7 +369,9 @@ void defer_invalid_rect(const gfx::Rect& rc)
defered_invalid_region.createUnion(defered_invalid_region, gfx::Region(rc));
}
//////////////////////////////////////////////////////////////////////
// Manager event handler.
bool CustomizedGuiManager::onProcessMessage(Message* msg)
{
#ifdef ENABLE_STEAM
@ -393,6 +396,7 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
if (getForegroundWindow() == App::instance()->mainWindow()) {
base::paths files = static_cast<DropFilesMessage*>(msg)->files();
UIContext* ctx = UIContext::instance();
OpenBatchOfFiles batch;
while (!files.empty()) {
auto fn = files.front();
@ -423,14 +427,11 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
}
// Other extensions will be handled as an image/sprite
else {
OpenFileCommand cmd;
Params params;
params.set("filename", fn.c_str());
params.set("repeat_checkbox", "true");
ctx->executeCommandFromMenuOrShortcut(&cmd, params);
batch.open(ctx, fn,
false); // Open all frames
// Remove all used file names from the "dropped files"
for (const auto& usedFn : cmd.usedFiles()) {
for (const auto& usedFn : batch.usedFiles()) {
auto it = std::find(files.begin(), files.end(), usedFn);
if (it != files.end())
files.erase(it);

View File

@ -10,6 +10,6 @@
// Increment this value if the scripting API is modified between two
// released Aseprite versions.
#define API_VERSION 11
#define API_VERSION 12
#endif

View File

@ -68,7 +68,8 @@ int load_sprite_from_file(lua_State* L, const char* filename,
Commands::instance()->byId(CommandId::OpenFile());
Params params;
params.set("filename", absFn.c_str());
if (param == LoadSpriteFromFileParam::OneFrameAsImage)
if (param == LoadSpriteFromFileParam::OneFrameAsSprite ||
param == LoadSpriteFromFileParam::OneFrameAsImage)
params.set("oneframe", "true");
ctx->executeCommand(openCommand, params);

View File

@ -40,7 +40,7 @@ int ColorSpace_new(lua_State* L)
lua_pop(L, 1);
// Create sRGB profile with ColorSpace{ sRGB }
if (lua_getfield(L, 1, "sRGB") != LUA_TNONE) {
if (lua_is_key_true(L, 1, "sRGB")) {
lua_pop(L, 1);
push_new<gfx::ColorSpace>(L, *gfx::ColorSpace::MakeSRGB());
return 1;

View File

@ -20,6 +20,7 @@
#include "app/ui/expr_entry.h"
#include "app/ui/filename_field.h"
#include "base/paths.h"
#include "base/remove_from_container.h"
#include "ui/box.h"
#include "ui/button.h"
#include "ui/combobox.h"
@ -33,18 +34,22 @@
#include <map>
#include <string>
#include <vector>
#define TRACE_DIALOG(...) // TRACEARGS
#ifdef ENABLE_UI
#define TRACE_DIALOG(...) // TRACEARGS(__VA_ARGS__)
namespace app {
namespace script {
#ifdef ENABLE_UI
using namespace ui;
namespace {
struct Dialog;
std::vector<Dialog*> all_dialogs;
struct Dialog {
ui::Window window;
ui::VBox vbox;
@ -73,6 +78,11 @@ struct Dialog {
: window(ui::Window::WithTitleBar, "Script"),
grid(2, false) {
window.addChild(&grid);
all_dialogs.push_back(this);
}
~Dialog() {
base::remove_from_container(all_dialogs, this);
}
void unrefShowOnClose() {
@ -368,14 +378,9 @@ int Dialog_newrow(lua_State* L)
dlg->autoNewRow = false;
if (lua_istable(L, 2)) {
// Dialog:newrow{ always }
const int type = lua_getfield(L, 2, "always");
if (type != LUA_TNONE) {
if (type == LUA_TNIL ||
lua_toboolean(L, -1)) {
dlg->autoNewRow = true;
}
lua_pop(L, 1);
}
if (lua_is_key_true(L, 2, "always"))
dlg->autoNewRow = true;
lua_pop(L, 1);
}
lua_pushvalue(L, 1);
@ -1224,16 +1229,24 @@ const Property Dialog_properties[] = {
DEF_MTNAME(Dialog);
#endif // ENABLE_UI
void register_dialog_class(lua_State* L)
{
#ifdef ENABLE_UI
REG_CLASS(L, Dialog);
REG_CLASS_NEW(L, Dialog);
REG_CLASS_PROPERTIES(L, Dialog);
#endif
}
// close all opened Dialogs before closing the UI
void close_all_dialogs()
{
for (Dialog* dlg : all_dialogs) {
ASSERT(dlg);
if (dlg)
dlg->window.closeWindow(nullptr);
}
}
} // namespace script
} // namespace app
#endif // ENABLE_UI

View File

@ -151,7 +151,9 @@ void register_cel_class(lua_State* L);
void register_cels_class(lua_State* L);
void register_color_class(lua_State* L);
void register_color_space_class(lua_State* L);
#ifdef ENABLE_UI
void register_dialog_class(lua_State* L);
#endif
void register_frame_class(lua_State* L);
void register_frames_class(lua_State* L);
void register_image_class(lua_State* L);
@ -371,7 +373,9 @@ Engine::Engine()
register_cels_class(L);
register_color_class(L);
register_color_space_class(L);
#ifdef ENABLE_UI
register_dialog_class(L);
#endif
register_frame_class(L);
register_frames_class(L);
register_image_class(L);
@ -404,6 +408,9 @@ Engine::Engine()
Engine::~Engine()
{
#ifdef ENABLE_UI
close_all_dialogs();
#endif
lua_close(L);
}

View File

@ -167,10 +167,16 @@ namespace app {
// Used by App.open(), Sprite{ fromFile }, and Image{ fromFile }
enum class LoadSpriteFromFileParam { FullAniAsSprite,
OneFrameAsSprite,
OneFrameAsImage };
int load_sprite_from_file(lua_State* L, const char* filename,
const LoadSpriteFromFileParam param);
#ifdef ENABLE_UI
// close all opened Dialogs before closing the UI
void close_all_dialogs();
#endif
} // namespace script
} // namespace app

View File

@ -93,5 +93,15 @@ void create_mt_getters_setters(lua_State* L,
ASSERT(lua_gettop(L) == top);
}
bool lua_is_key_true(lua_State* L, int tableIndex, const char* keyName)
{
bool result = false;
int type = lua_getfield(L, tableIndex, keyName);
if (type != LUA_TNIL && lua_toboolean(L, -1))
result = true;
lua_pop(L, 1);
return result;
}
} // namespace script
} // namespace app

View File

@ -128,6 +128,8 @@ void create_mt_getters_setters(lua_State* L,
const char* tname,
const Property* properties);
bool lua_is_key_true(lua_State* L, int tableIndex, const char* keyName);
#define REG_CLASS_PROPERTIES(L, T) { \
luaL_getmetatable(L, get_mtname<T>()); \
create_mt_getters_setters(L, get_mtname<T>(), T##_properties); \

View File

@ -30,8 +30,7 @@ gfx::Point Point_new(lua_State* L, int index)
// Convert {x=int,y=int} or {int,int} into a Point
else if (lua_istable(L, index)) {
const int type = lua_getfield(L, index, "x");
if (type != LUA_TNONE &&
type != LUA_TNIL) {
if (VALID_LUATYPE(type)) {
lua_getfield(L, index, "y");
pt.x = lua_tointeger(L, -2);
pt.y = lua_tointeger(L, -1);

View File

@ -29,8 +29,7 @@ gfx::Rect Rectangle_new(lua_State* L, int index)
// Convert { x, y, width, height } into a Rectangle
else if (lua_istable(L, index)) {
const int type = lua_getfield(L, index, "x");
if (type != LUA_TNONE &&
type != LUA_TNIL) {
if (VALID_LUATYPE(type)) {
lua_getfield(L, index, "y");
lua_getfield(L, index, "width");
lua_getfield(L, index, "height");

View File

@ -30,8 +30,7 @@ gfx::Size Size_new(lua_State* L, int index)
// Convert {x=int,y=int} or {int,int} into a Size
else if (lua_istable(L, index)) {
const int type = lua_getfield(L, index, "width");
if (type != LUA_TNONE &&
type != LUA_TNIL) {
if (VALID_LUATYPE(type)) {
lua_getfield(L, index, "height");
sz.w = lua_tointeger(L, -2);
sz.h = lua_tointeger(L, -1);

View File

@ -14,6 +14,7 @@
#include "app/cmd/assign_color_profile.h"
#include "app/cmd/clear_cel.h"
#include "app/cmd/convert_color_profile.h"
#include "app/cmd/copy_region.h"
#include "app/cmd/flatten_layers.h"
#include "app/cmd/remove_layer.h"
#include "app/cmd/remove_slice.h"
@ -77,9 +78,13 @@ int Sprite_new(lua_State* L)
if (const char* fromFile = lua_tostring(L, -1)) {
std::string fn = fromFile;
lua_pop(L, 1);
bool oneFrame = (lua_is_key_true(L, -1, "oneFrame"));
return load_sprite_from_file(
L, fn.c_str(),
LoadSpriteFromFileParam::FullAniAsSprite);
(oneFrame ? LoadSpriteFromFileParam::OneFrameAsSprite:
LoadSpriteFromFileParam::FullAniAsSprite));
}
}
lua_pop(L, 1);
@ -444,30 +449,46 @@ int Sprite_newCel(lua_State* L)
if (frame < 0 || frame > sprite->lastFrame())
return luaL_error(L, "frame index out of bounds %d", frame+1);
Doc* doc = static_cast<Doc*>(sprite->document());
LayerImage* layer = static_cast<LayerImage*>(layerBase);
ImageRef image(nullptr);
gfx::Point pos(0, 0);
Image* srcImage = may_get_image_from_arg(L, 4);
if (srcImage) {
image.reset(Image::createCopy(srcImage));
pos = convert_args_into_point(L, 5);
}
else {
image.reset(Image::create(sprite->spec()));
}
gfx::Point pos = convert_args_into_point(L, 5);
Cel* cel = nullptr;
auto cel = new Cel(frame, image);
cel->setPosition(pos);
// For background layers we just draw the image in the existent cel
if (layer->isBackground()) {
cel = layer->cel(frame);
ASSERT(cel);
Doc* doc = static_cast<Doc*>(sprite->document());
Tx tx;
DocApi api = doc->getApi(tx);
if (layer->cel(frame))
Tx tx;
DocApi api = doc->getApi(tx);
api.clearCel(layer, frame);
api.addCel(layer, cel);
tx.commit();
if (srcImage) {
tx(new cmd::CopyRegion(cel->image(), srcImage,
gfx::Region(srcImage->bounds()),
pos, false));
}
tx.commit();
}
// For transparent layers we just draw the image in the existent cel
else {
if (srcImage)
image.reset(Image::createCopy(srcImage));
else
image.reset(Image::create(sprite->spec()));
cel = new Cel(frame, image);
cel->setPosition(pos);
Tx tx;
DocApi api = doc->getApi(tx);
if (layer->cel(frame))
api.clearCel(layer, frame);
api.addCel(layer, cel);
tx.commit();
}
push_docobj(L, cel);
return 1;

View File

@ -752,7 +752,7 @@ void ColorBar::onFgColorButtonBeforeChange(app::Color& color)
if (m_fromPalView)
return;
if (!inEditMode()) {
if (!inEditMode() || color.getType() == app::Color::IndexType) {
m_paletteView.deselect();
return;
}
@ -814,8 +814,7 @@ void ColorBar::onColorButtonChange(const app::Color& color)
{
COLOR_BAR_TRACE("ColorBar::onColorButtonChange(%s)\n", color.toString().c_str());
if (!inEditMode() ||
m_fromPref) {
if (!inEditMode() || color.getType() == app::Color::IndexType || m_fromPref) {
if (color.getType() == app::Color::IndexType)
m_paletteView.selectColor(color.getIndex());
else {

View File

@ -417,6 +417,12 @@ void DocView::onRemoveFrame(DocEvent& ev)
}
}
void DocView::onTagChange(DocEvent& ev)
{
if (m_previewDelegate)
m_previewDelegate->onTagChangeEditor(m_editor, ev);
}
void DocView::onAddCel(DocEvent& ev)
{
UIContext::instance()->notifyActiveSiteChanged();

View File

@ -30,6 +30,7 @@ namespace app {
virtual void onScrollOtherEditor(Editor* editor) = 0;
virtual void onDisposeOtherEditor(Editor* editor) = 0;
virtual void onPreviewOtherEditor(Editor* editor) = 0;
virtual void onTagChangeEditor(Editor* editor, DocEvent& ev) = 0;
};
class DocView : public ui::Box,
@ -75,6 +76,7 @@ namespace app {
void onAddLayer(DocEvent& ev) override;
void onAddFrame(DocEvent& ev) override;
void onRemoveFrame(DocEvent& ev) override;
void onTagChange(DocEvent& ev) override;
void onAddCel(DocEvent& ev) override;
void onRemoveCel(DocEvent& ev) override;
void onTotalFramesChanged(DocEvent& ev) override;

View File

@ -51,6 +51,11 @@ PlayState::PlayState(const bool playOnce,
&PlayState::onBeforeCommandExecution, this);
}
Tag* PlayState::playingTag() const
{
return m_tag;
}
void PlayState::onEnterState(Editor* editor)
{
StateWithWheelBehavior::onEnterState(editor);

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -27,6 +28,8 @@ namespace app {
PlayState(const bool playOnce,
const bool playAll);
doc::Tag* playingTag() const;
void onEnterState(Editor* editor) override;
LeaveAction onLeaveState(Editor* editor, EditorState* newState) override;
bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override;

View File

@ -13,6 +13,7 @@
#include "app/app.h"
#include "app/doc.h"
#include "app/doc_event.h"
#include "app/ini_file.h"
#include "app/loop_tag.h"
#include "app/modules/editors.h"
@ -22,6 +23,7 @@
#include "app/ui/editor/editor_customization_delegate.h"
#include "app/ui/editor/editor_view.h"
#include "app/ui/editor/navigate_state.h"
#include "app/ui/editor/play_state.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h"
#include "app/ui/toolbar.h"
@ -395,28 +397,7 @@ void PreviewEditorWindow::updateUsingEditor(Editor* editor)
miniEditor->setFrame(editor->frame());
}
else {
if (miniEditor->isPlaying()) {
doc::Tag* tag = editor
->getCustomizationDelegate()
->getTagProvider()
->getTagByFrame(editor->frame(), true);
doc::Tag* playingTag = editor
->getCustomizationDelegate()
->getTagProvider()
->getTagByFrame(m_refFrame, true);
if (tag == playingTag)
return;
miniEditor->stop();
}
if (!miniEditor->isPlaying())
miniEditor->setFrame(m_refFrame = editor->frame());
miniEditor->play(Preferences::instance().preview.playOnce(),
Preferences::instance().preview.playAll());
adjustPlayingTag();
}
}
@ -477,6 +458,11 @@ void PreviewEditorWindow::onPreviewOtherEditor(Editor* editor)
updateUsingEditor(editor);
}
void PreviewEditorWindow::onTagChangeEditor(Editor* editor, DocEvent& ev)
{
adjustPlayingTag();
}
void PreviewEditorWindow::hideWindow()
{
destroyDocView();
@ -494,4 +480,34 @@ void PreviewEditorWindow::destroyDocView()
}
}
void PreviewEditorWindow::adjustPlayingTag()
{
Editor* editor = m_relatedEditor;
Editor* miniEditor = m_docView->editor();
ASSERT(editor);
ASSERT(miniEditor);
if (miniEditor->isPlaying()) {
doc::Tag* tag = editor
->getCustomizationDelegate()
->getTagProvider()
->getTagByFrame(editor->frame(), true);
auto playState = dynamic_cast<PlayState*>(miniEditor->getState().get());
doc::Tag* playingTag = (playState ? playState->playingTag(): nullptr);
if (tag == playingTag)
return;
miniEditor->stop();
}
if (!miniEditor->isPlaying())
miniEditor->setFrame(m_refFrame = editor->frame());
miniEditor->play(Preferences::instance().preview.playOnce(),
Preferences::instance().preview.playAll());
}
} // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -42,6 +43,7 @@ namespace app {
void onScrollOtherEditor(Editor* editor) override;
void onDisposeOtherEditor(Editor* editor) override;
void onPreviewOtherEditor(Editor* editor) override;
void onTagChangeEditor(Editor* editor, DocEvent& ev) override;
protected:
bool onProcessMessage(ui::Message* msg) override;
@ -59,6 +61,7 @@ namespace app {
void hideWindow();
void destroyDocView();
void saveScrollPref();
void adjustPlayingTag();
bool m_isEnabled;
DocView* m_docView;

64
src/app/util/open_batch.h Normal file
View File

@ -0,0 +1,64 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UTIL_OPEN_BATCH_H_INCLUDED
#define APP_UTIL_OPEN_BATCH_H_INCLUDED
#pragma once
#include "app/commands/cmd_open_file.h"
#include "app/context.h"
namespace app {
// Helper class to a batch of files and handle the loading of image
// sequences and repeat the selected action of the user (Agree/Skip
// to open the sequence, and Repeat the action for all other
// elements)
class OpenBatchOfFiles {
public:
void open(Context* ctx,
const std::string& fn,
const bool oneFrame) {
Params params;
params.set("filename", fn.c_str());
if (oneFrame)
params.set("oneframe", "true");
else {
switch (m_lastDecision) {
case OpenFileCommand::SequenceDecision::Ask:
params.set("sequence", "ask");
params.set("repeat_checkbox", "true");
break;
case OpenFileCommand::SequenceDecision::Skip:
params.set("sequence", "skip");
break;
case OpenFileCommand::SequenceDecision::Agree:
params.set("sequence", "agree");
break;
}
}
ctx->executeCommandFromMenuOrShortcut(&m_cmd, params);
// Future decision for other files in the CLI
auto d = m_cmd.seqDecision();
if (d != OpenFileCommand::SequenceDecision::Ask)
m_lastDecision = d;
}
const base::paths& usedFiles() const {
return m_cmd.usedFiles();
}
private:
OpenFileCommand m_cmd;
OpenFileCommand::SequenceDecision m_lastDecision = OpenFileCommand::SequenceDecision::Ask;
};
} // namespace app
#endif