mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-03 05:22:23 +00:00
Fix app.command.Cut/Paste does not work in --batch mode (fix #4354)
This fix adds support for cut/copy/paste selected areas during script execution when there isn't UI.
This commit is contained in:
parent
09bb5cc3d3
commit
de1fc581f2
@ -220,6 +220,7 @@ if(ENABLE_SCRIPTING)
|
|||||||
script/range_class.cpp
|
script/range_class.cpp
|
||||||
script/rectangle_class.cpp
|
script/rectangle_class.cpp
|
||||||
script/require.cpp
|
script/require.cpp
|
||||||
|
script/script_input_chain.cpp
|
||||||
script/security.cpp
|
script/security.cpp
|
||||||
script/selection_class.cpp
|
script/selection_class.cpp
|
||||||
script/site_class.cpp
|
script/site_class.cpp
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||||
// Copyright (C) 2016-2018 David Capello
|
// Copyright (C) 2016-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -31,6 +31,8 @@
|
|||||||
#ifdef ENABLE_SCRIPTING
|
#ifdef ENABLE_SCRIPTING
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
#include "app/script/engine.h"
|
#include "app/script/engine.h"
|
||||||
|
#include "app/script/script_input_chain.h"
|
||||||
|
#include "app/ui/input_chain.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -143,6 +145,10 @@ void DefaultCliDelegate::exportFiles(Context* ctx, DocExporter& exporter)
|
|||||||
int DefaultCliDelegate::execScript(const std::string& filename,
|
int DefaultCliDelegate::execScript(const std::string& filename,
|
||||||
const Params& params)
|
const Params& params)
|
||||||
{
|
{
|
||||||
|
ScriptInputChain scriptInputChain;
|
||||||
|
if (!App::instance()->isGui()) {
|
||||||
|
App::instance()->inputChain().prioritize(&scriptInputChain, nullptr);
|
||||||
|
}
|
||||||
auto engine = App::instance()->scriptEngine();
|
auto engine = App::instance()->scriptEngine();
|
||||||
if (!engine->evalUserFile(filename, params))
|
if (!engine->evalUserFile(filename, params))
|
||||||
throw base::Exception("Error executing script %s", filename.c_str());
|
throw base::Exception("Error executing script %s", filename.c_str());
|
||||||
|
@ -63,7 +63,7 @@ void CancelCommand::onExecute(Context* context)
|
|||||||
case All:
|
case All:
|
||||||
// TODO should the ContextBar be a InputChainElement to intercept onCancel()?
|
// TODO should the ContextBar be a InputChainElement to intercept onCancel()?
|
||||||
// Discard brush
|
// Discard brush
|
||||||
{
|
if (context->isUIAvailable()) {
|
||||||
Command* discardBrush = Commands::instance()->byId(
|
Command* discardBrush = Commands::instance()->byId(
|
||||||
CommandId::DiscardBrush());
|
CommandId::DiscardBrush());
|
||||||
context->executeCommand(discardBrush);
|
context->executeCommand(discardBrush);
|
||||||
|
131
src/app/script/script_input_chain.cpp
Normal file
131
src/app/script/script_input_chain.cpp
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2024 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
|
||||||
|
|
||||||
|
#include "app/app.h"
|
||||||
|
#include "app/cmd/deselect_mask.h"
|
||||||
|
#include "app/cmd/remap_colors.h"
|
||||||
|
#include "app/commands/commands.h"
|
||||||
|
#include "app/context_access.h"
|
||||||
|
#include "app/script/script_input_chain.h"
|
||||||
|
#include "app/site.h"
|
||||||
|
#include "app/tx.h"
|
||||||
|
#include "app/util/clipboard.h"
|
||||||
|
#include "doc/mask.h"
|
||||||
|
#include "doc/layer.h"
|
||||||
|
#include "doc/primitives.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
ScriptInputChain::~ScriptInputChain() { }
|
||||||
|
|
||||||
|
void ScriptInputChain::onNewInputPriority(InputChainElement* element,
|
||||||
|
const ui::Message* msg) { }
|
||||||
|
|
||||||
|
bool ScriptInputChain::onCanCut(Context* ctx)
|
||||||
|
{
|
||||||
|
return ctx->activeDocument() &&
|
||||||
|
ctx->activeDocument()->isMaskVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptInputChain::onCanCopy(Context* ctx)
|
||||||
|
{
|
||||||
|
return onCanCut(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptInputChain::onCanPaste(Context* ctx)
|
||||||
|
{
|
||||||
|
const Clipboard* clipboard(ctx->clipboard());
|
||||||
|
if (!clipboard)
|
||||||
|
return false;
|
||||||
|
return clipboard->format() == ClipboardFormat::Image &&
|
||||||
|
ctx->activeSite().layer() &&
|
||||||
|
ctx->activeSite().layer()->type() == ObjectType::LayerImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptInputChain::onCanClear(Context* ctx)
|
||||||
|
{
|
||||||
|
return onCanCut(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptInputChain::onCut(Context* ctx)
|
||||||
|
{
|
||||||
|
ContextWriter writer(ctx);
|
||||||
|
Clipboard* clipboard = ctx->clipboard();
|
||||||
|
if (!clipboard)
|
||||||
|
return false;
|
||||||
|
if (writer.document()) {
|
||||||
|
clipboard->cut(writer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptInputChain::onCopy(Context* ctx)
|
||||||
|
{
|
||||||
|
ContextReader reader(ctx);
|
||||||
|
Clipboard* clipboard = ctx->clipboard();
|
||||||
|
if (!clipboard)
|
||||||
|
return false;
|
||||||
|
if (reader.document()) {
|
||||||
|
clipboard->copy(reader);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptInputChain::onPaste(Context* ctx)
|
||||||
|
{
|
||||||
|
Clipboard* clipboard = ctx->clipboard();
|
||||||
|
if (!clipboard)
|
||||||
|
return false;
|
||||||
|
if (clipboard->format() == ClipboardFormat::Image) {
|
||||||
|
clipboard->paste(ctx, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptInputChain::onClear(Context* ctx)
|
||||||
|
{
|
||||||
|
// TODO This code is similar to DocView::onClear() and Clipboard::cut()
|
||||||
|
ContextWriter writer(ctx);
|
||||||
|
Doc* document = ctx->activeDocument();
|
||||||
|
if (writer.document()) {
|
||||||
|
ctx->clipboard()->clearContent();
|
||||||
|
CelList cels;
|
||||||
|
const Site site = ctx->activeSite();
|
||||||
|
cels.push_back(site.cel());
|
||||||
|
if (cels.empty()) // No cels to modify
|
||||||
|
return false;
|
||||||
|
Tx tx(writer, "Clear");
|
||||||
|
ctx->clipboard()->clearMaskFromCels(
|
||||||
|
tx, document, site, cels, true);
|
||||||
|
tx.commit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptInputChain::onCancel(Context* ctx)
|
||||||
|
{
|
||||||
|
// Deselect mask
|
||||||
|
if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||||
|
ContextFlags::HasVisibleMask)) {
|
||||||
|
Command* deselectMask = Commands::instance()->byId(CommandId::DeselectMask());
|
||||||
|
ctx->executeCommand(deselectMask);
|
||||||
|
ctx->activeDocument()->setMaskVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace app
|
37
src/app/script/script_input_chain.h
Normal file
37
src/app/script/script_input_chain.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2024 Igara Studio S.A.
|
||||||
|
//
|
||||||
|
// This program is distributed under the terms of
|
||||||
|
// the End-User License Agreement for Aseprite.
|
||||||
|
|
||||||
|
#ifdef ENABLE_SCRIPTING
|
||||||
|
|
||||||
|
#ifndef APP_SCRIPT_SCRIPT_INPUT_CHAIN_H_INCLUDED
|
||||||
|
#define APP_SCRIPT_SCRIPT_INPUT_CHAIN_H_INCLUDED
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "app/ui/input_chain_element.h"
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
class ScriptInputChain : public InputChainElement {
|
||||||
|
public:
|
||||||
|
|
||||||
|
// InputChainElement impl
|
||||||
|
~ScriptInputChain() override;
|
||||||
|
void onNewInputPriority(InputChainElement* element,
|
||||||
|
const ui::Message* msg) override;
|
||||||
|
bool onCanCut(Context* ctx) override;
|
||||||
|
bool onCanCopy(Context* ctx) override;
|
||||||
|
bool onCanPaste(Context* ctx) override;
|
||||||
|
bool onCanClear(Context* ctx) override;
|
||||||
|
bool onCut(Context* ctx) override;
|
||||||
|
bool onCopy(Context* ctx) override;
|
||||||
|
bool onPaste(Context* ctx) override;
|
||||||
|
bool onClear(Context* ctx) override;
|
||||||
|
void onCancel(Context* ctx) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace app
|
||||||
|
#endif
|
||||||
|
#endif
|
@ -53,7 +53,9 @@ namespace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void* native_window_handle() {
|
void* native_window_handle() {
|
||||||
return os::instance()->defaultWindow()->nativeHandle();
|
if (os::instance()->defaultWindow())
|
||||||
|
return os::instance()->defaultWindow()->nativeHandle();
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void custom_error_handler(clip::ErrorCode code) {
|
void custom_error_handler(clip::ErrorCode code) {
|
||||||
|
164
tests/scripts/app_cut_paste.lua
Normal file
164
tests/scripts/app_cut_paste.lua
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
-- Copyright (C) 2024 Igara Studio S.A.
|
||||||
|
--
|
||||||
|
-- This file is released under the terms of the MIT license.
|
||||||
|
-- Read LICENSE.txt for more information.
|
||||||
|
|
||||||
|
dofile('./test_utils.lua')
|
||||||
|
|
||||||
|
do
|
||||||
|
local sprite = Sprite{ fromFile="sprites/cut_paste.aseprite" }
|
||||||
|
|
||||||
|
app.layer = sprite.layers[1]
|
||||||
|
app.useTool {
|
||||||
|
tool = "rectangular_marquee",
|
||||||
|
points = {Point(0,1), Point(4,1)},
|
||||||
|
selection = SelectionMode.REPLACE
|
||||||
|
}
|
||||||
|
app.command.Cut()
|
||||||
|
sprite:newLayer()
|
||||||
|
app.command.Paste()
|
||||||
|
|
||||||
|
app.layer = sprite.layers[1]
|
||||||
|
assert(app.cel.position == Point(1, 2))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 1 })
|
||||||
|
app.layer = sprite.layers[2]
|
||||||
|
assert(app.cel.position == Point(2, 2))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 2, 2,
|
||||||
|
2, 2 })
|
||||||
|
|
||||||
|
-- TO DO: Fix this difference between running this script with
|
||||||
|
-- 'UI Available' versus 'UI Not Available'
|
||||||
|
app.layer = sprite.layers[3]
|
||||||
|
if (app.isUIAvailable) then
|
||||||
|
assert(app.cel.position == Point(1, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 1 })
|
||||||
|
else
|
||||||
|
assert(app.cel.position == Point(0, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 0, 1, 1, 0, 0 })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.command.FlattenLayers()
|
||||||
|
assert(app.cel.position == Point(1, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 1, 0,
|
||||||
|
1, 2, 2,
|
||||||
|
0, 2, 2 })
|
||||||
|
|
||||||
|
app.undo() -- Flatten
|
||||||
|
app.undo() -- Paste
|
||||||
|
app.undo() -- New Layer
|
||||||
|
app.undo() -- Cut
|
||||||
|
|
||||||
|
-- Another test
|
||||||
|
app.layer = sprite.layers[1]
|
||||||
|
app.useTool {
|
||||||
|
tool = "rectangular_marquee",
|
||||||
|
points = {Point(2,2), Point(4,2)},
|
||||||
|
selection = SelectionMode.REPLACE
|
||||||
|
}
|
||||||
|
|
||||||
|
app.command.Cut()
|
||||||
|
sprite:newLayer()
|
||||||
|
app.command.Paste()
|
||||||
|
|
||||||
|
-- TO DO: Fix this difference between running this script with
|
||||||
|
-- 'UI Available' versus 'UI Not Available'
|
||||||
|
app.layer = sprite.layers[3]
|
||||||
|
if (app.isUIAvailable) then
|
||||||
|
assert(app.cel.position == Point(2, 2))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1 })
|
||||||
|
else
|
||||||
|
assert(app.cel.position == Point(2, 2))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 0, 0 })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.undo() -- Paste
|
||||||
|
app.undo() -- New Layer
|
||||||
|
app.undo() -- Cut
|
||||||
|
app.undo() -- MoveMask
|
||||||
|
-- TO DO: at the moment useTool requires double undo to undo
|
||||||
|
-- the selection action (Just one undo should be enough).
|
||||||
|
app.undo() -- useTool
|
||||||
|
app.undo() -- useTool
|
||||||
|
|
||||||
|
-- Test app.command.Copy
|
||||||
|
app.layer = sprite.layers[1]
|
||||||
|
app.useTool {
|
||||||
|
tool = "rectangular_marquee",
|
||||||
|
points = {Point(0,1), Point(4,1)},
|
||||||
|
selection = SelectionMode.REPLACE
|
||||||
|
}
|
||||||
|
app.command.Copy()
|
||||||
|
sprite:newLayer()
|
||||||
|
app.command.Paste()
|
||||||
|
|
||||||
|
app.layer = sprite.layers[1]
|
||||||
|
assert(app.cel.position == Point(1, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 1,
|
||||||
|
1, 1})
|
||||||
|
app.layer = sprite.layers[2]
|
||||||
|
assert(app.cel.position == Point(2, 2))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 2, 2,
|
||||||
|
2, 2 })
|
||||||
|
|
||||||
|
-- TO DO: Fix this difference between running this script with
|
||||||
|
-- 'UI Available' versus 'UI Not Available'
|
||||||
|
app.layer = sprite.layers[3]
|
||||||
|
if (app.isUIAvailable) then
|
||||||
|
assert(app.cel.position == Point(1, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 1 })
|
||||||
|
else
|
||||||
|
assert(app.cel.position == Point(0, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 0, 1, 1, 0, 0 })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.command.FlattenLayers()
|
||||||
|
assert(app.cel.position == Point(1, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 1, 0,
|
||||||
|
1, 2, 2,
|
||||||
|
0, 2, 2 })
|
||||||
|
|
||||||
|
-- Test app.command.Clear()
|
||||||
|
app.useTool {
|
||||||
|
tool = "rectangular_marquee",
|
||||||
|
points = {Point(0,1), Point(4,2)},
|
||||||
|
selection = SelectionMode.REPLACE
|
||||||
|
}
|
||||||
|
app.command.Clear()
|
||||||
|
assert(app.cel.position == Point(2, 3))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 2, 2 })
|
||||||
|
|
||||||
|
app.undo()
|
||||||
|
|
||||||
|
assert(app.cel.position == Point(1, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 1, 0,
|
||||||
|
1, 2, 2,
|
||||||
|
0, 2, 2 })
|
||||||
|
|
||||||
|
-- Test app.command.Cancel()
|
||||||
|
app.useTool {
|
||||||
|
tool = "rectangular_marquee",
|
||||||
|
points = {Point(0,1), Point(4,2)},
|
||||||
|
selection = SelectionMode.REPLACE
|
||||||
|
}
|
||||||
|
app.command.Cancel()
|
||||||
|
app.command.Cut()
|
||||||
|
assert(app.cel.position == Point(1, 1))
|
||||||
|
expect_img(app.activeImage,
|
||||||
|
{ 1, 1, 0,
|
||||||
|
1, 2, 2,
|
||||||
|
0, 2, 2 })
|
||||||
|
end
|
BIN
tests/sprites/cut_paste.aseprite
Normal file
BIN
tests/sprites/cut_paste.aseprite
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user