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/rectangle_class.cpp
|
||||
script/require.cpp
|
||||
script/script_input_chain.cpp
|
||||
script/security.cpp
|
||||
script/selection_class.cpp
|
||||
script/site_class.cpp
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -31,6 +31,8 @@
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
#include "app/app.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/script_input_chain.h"
|
||||
#include "app/ui/input_chain.h"
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
@ -143,6 +145,10 @@ void DefaultCliDelegate::exportFiles(Context* ctx, DocExporter& exporter)
|
||||
int DefaultCliDelegate::execScript(const std::string& filename,
|
||||
const Params& params)
|
||||
{
|
||||
ScriptInputChain scriptInputChain;
|
||||
if (!App::instance()->isGui()) {
|
||||
App::instance()->inputChain().prioritize(&scriptInputChain, nullptr);
|
||||
}
|
||||
auto engine = App::instance()->scriptEngine();
|
||||
if (!engine->evalUserFile(filename, params))
|
||||
throw base::Exception("Error executing script %s", filename.c_str());
|
||||
|
@ -63,7 +63,7 @@ void CancelCommand::onExecute(Context* context)
|
||||
case All:
|
||||
// TODO should the ContextBar be a InputChainElement to intercept onCancel()?
|
||||
// Discard brush
|
||||
{
|
||||
if (context->isUIAvailable()) {
|
||||
Command* discardBrush = Commands::instance()->byId(
|
||||
CommandId::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() {
|
||||
if (os::instance()->defaultWindow())
|
||||
return os::instance()->defaultWindow()->nativeHandle();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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