mirror of https://github.com/aseprite/aseprite.git
Add slices copy&paste and duplication (fix #4466)
This commit is contained in:
parent
5f7cc42333
commit
50ede4f062
|
@ -1203,6 +1203,7 @@
|
||||||
|
|
||||||
<menu id="slice_popup_menu">
|
<menu id="slice_popup_menu">
|
||||||
<item command="SliceProperties" text="@.properties" group="slice_popup_properties" />
|
<item command="SliceProperties" text="@.properties" group="slice_popup_properties" />
|
||||||
|
<item command="DuplicateSlice" text="@.duplicate" group="slice_popup_duplicate" />
|
||||||
<item command="RemoveSlice" text="@.delete" group="slice_popup_delete" />
|
<item command="RemoveSlice" text="@.delete" group="slice_popup_delete" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,7 @@ Despeckle = Despeckle
|
||||||
DeveloperConsole = Developer Console
|
DeveloperConsole = Developer Console
|
||||||
DiscardBrush = Discard Brush
|
DiscardBrush = Discard Brush
|
||||||
DuplicateLayer = Duplicate Layer
|
DuplicateLayer = Duplicate Layer
|
||||||
|
DuplicateSlice = Duplicate Slice
|
||||||
DuplicateSprite = Duplicate Sprite
|
DuplicateSprite = Duplicate Sprite
|
||||||
DuplicateView = Duplicate View
|
DuplicateView = Duplicate View
|
||||||
Exit = Exit
|
Exit = Exit
|
||||||
|
@ -1674,6 +1675,10 @@ from = From:
|
||||||
to = To:
|
to = To:
|
||||||
tolerance = Tolerance:
|
tolerance = Tolerance:
|
||||||
|
|
||||||
|
[duplicate_slice]
|
||||||
|
x_duplicated = Slice "{}" duplicated
|
||||||
|
n_slices_duplicated = {} slice(s) duplicated
|
||||||
|
|
||||||
[remove_slice]
|
[remove_slice]
|
||||||
x_removed = Slice "{}" removed
|
x_removed = Slice "{}" removed
|
||||||
n_slices_removed = {} slice(s) removed
|
n_slices_removed = {} slice(s) removed
|
||||||
|
@ -1758,6 +1763,7 @@ delete_file = Delete file, I've already sent it
|
||||||
|
|
||||||
[slice_popup_menu]
|
[slice_popup_menu]
|
||||||
properties = Slice &Properties...
|
properties = Slice &Properties...
|
||||||
|
duplicate = D&uplicate Slice
|
||||||
delete = &Delete Slice
|
delete = &Delete Slice
|
||||||
|
|
||||||
[slice_properties]
|
[slice_properties]
|
||||||
|
|
|
@ -393,6 +393,7 @@ target_sources(app-lib PRIVATE
|
||||||
commands/cmd_deselect_mask.cpp
|
commands/cmd_deselect_mask.cpp
|
||||||
commands/cmd_discard_brush.cpp
|
commands/cmd_discard_brush.cpp
|
||||||
commands/cmd_duplicate_layer.cpp
|
commands/cmd_duplicate_layer.cpp
|
||||||
|
commands/cmd_duplicate_slice.cpp
|
||||||
commands/cmd_duplicate_sprite.cpp
|
commands/cmd_duplicate_sprite.cpp
|
||||||
commands/cmd_duplicate_view.cpp
|
commands/cmd_duplicate_view.cpp
|
||||||
commands/cmd_enter_license.cpp
|
commands/cmd_enter_license.cpp
|
||||||
|
@ -715,6 +716,7 @@ target_sources(app-lib PRIVATE
|
||||||
util/render_text.cpp
|
util/render_text.cpp
|
||||||
util/resize_image.cpp
|
util/resize_image.cpp
|
||||||
util/shader_helpers.cpp
|
util/shader_helpers.cpp
|
||||||
|
util/slice_utils.cpp
|
||||||
util/tile_flags_utils.cpp
|
util/tile_flags_utils.cpp
|
||||||
util/tileset_utils.cpp
|
util/tileset_utils.cpp
|
||||||
util/wrap_point.cpp
|
util/wrap_point.cpp
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2025 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/cmd/add_slice.h"
|
||||||
|
#include "app/commands/command.h"
|
||||||
|
#include "app/context.h"
|
||||||
|
#include "app/context_access.h"
|
||||||
|
#include "app/context_flags.h"
|
||||||
|
#include "app/i18n/strings.h"
|
||||||
|
#include "app/site.h"
|
||||||
|
#include "app/tx.h"
|
||||||
|
#include "app/ui/status_bar.h"
|
||||||
|
#include "app/util/slice_utils.h"
|
||||||
|
#include "base/convert_to.h"
|
||||||
|
#include "doc/object_id.h"
|
||||||
|
#include "doc/slice.h"
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
class DuplicateSliceCommand : public Command {
|
||||||
|
public:
|
||||||
|
DuplicateSliceCommand();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onLoadParams(const Params& params) override;
|
||||||
|
bool onEnabled(Context* context) override;
|
||||||
|
void onExecute(Context* context) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectId m_sliceId;
|
||||||
|
};
|
||||||
|
|
||||||
|
DuplicateSliceCommand::DuplicateSliceCommand()
|
||||||
|
: Command(CommandId::DuplicateSlice(), CmdRecordableFlag)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DuplicateSliceCommand::onLoadParams(const Params& params)
|
||||||
|
{
|
||||||
|
std::string id = params.get("id");
|
||||||
|
if (!id.empty())
|
||||||
|
m_sliceId = ObjectId(base::convert_to<doc::ObjectId>(id));
|
||||||
|
else
|
||||||
|
m_sliceId = NullId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DuplicateSliceCommand::onEnabled(Context* context)
|
||||||
|
{
|
||||||
|
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||||
|
ContextFlags::HasActiveSprite | ContextFlags::HasActiveLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DuplicateSliceCommand::onExecute(Context* context)
|
||||||
|
{
|
||||||
|
std::vector<Slice*> selectedSlices;
|
||||||
|
{
|
||||||
|
const ContextReader reader(context);
|
||||||
|
if (m_sliceId == NullId) {
|
||||||
|
selectedSlices = get_selected_slices(reader.site());
|
||||||
|
if (selectedSlices.empty())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
selectedSlices.push_back(reader.sprite()->slices().getById(m_sliceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextWriter writer(context);
|
||||||
|
Tx tx(writer, "Duplicate Slice");
|
||||||
|
Sprite* sprite = writer.site().sprite();
|
||||||
|
|
||||||
|
Doc* doc = static_cast<Doc*>(sprite->document());
|
||||||
|
doc->notifyBeforeSlicesDuplication();
|
||||||
|
for (auto* s : selectedSlices) {
|
||||||
|
Slice* slice = new Slice(*s);
|
||||||
|
slice->setName(get_unique_slice_name(sprite, s->name()));
|
||||||
|
tx(new cmd::AddSlice(sprite, slice));
|
||||||
|
doc->notifySliceDuplicated(slice);
|
||||||
|
}
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
|
std::string sliceName;
|
||||||
|
if (selectedSlices.size() == 1)
|
||||||
|
sliceName = selectedSlices[0]->name();
|
||||||
|
|
||||||
|
StatusBar::instance()->invalidate();
|
||||||
|
if (!sliceName.empty()) {
|
||||||
|
StatusBar::instance()->showTip(1000, Strings::duplicate_slice_x_duplicated(sliceName));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
StatusBar::instance()->showTip(
|
||||||
|
1000,
|
||||||
|
Strings::duplicate_slice_n_slices_duplicated(selectedSlices.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Command* CommandFactory::createDuplicateSliceCommand()
|
||||||
|
{
|
||||||
|
return new DuplicateSliceCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace app
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -40,6 +40,7 @@ FOR_EACH_COMMAND(DeselectMask)
|
||||||
FOR_EACH_COMMAND(Despeckle)
|
FOR_EACH_COMMAND(Despeckle)
|
||||||
FOR_EACH_COMMAND(DiscardBrush)
|
FOR_EACH_COMMAND(DiscardBrush)
|
||||||
FOR_EACH_COMMAND(DuplicateLayer)
|
FOR_EACH_COMMAND(DuplicateLayer)
|
||||||
|
FOR_EACH_COMMAND(DuplicateSlice)
|
||||||
FOR_EACH_COMMAND(DuplicateSprite)
|
FOR_EACH_COMMAND(DuplicateSprite)
|
||||||
FOR_EACH_COMMAND(DuplicateView)
|
FOR_EACH_COMMAND(DuplicateView)
|
||||||
FOR_EACH_COMMAND(Exit)
|
FOR_EACH_COMMAND(Exit)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -338,6 +338,19 @@ void Doc::notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti)
|
||||||
notify_observers<DocEvent&>(&DocObserver::onAfterAddTile, ev);
|
notify_observers<DocEvent&>(&DocObserver::onAfterAddTile, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Doc::notifyBeforeSlicesDuplication()
|
||||||
|
{
|
||||||
|
DocEvent ev(this);
|
||||||
|
notify_observers<DocEvent&>(&DocObserver::onBeforeSlicesDuplication, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Doc::notifySliceDuplicated(Slice* slice)
|
||||||
|
{
|
||||||
|
DocEvent ev(this);
|
||||||
|
ev.slice(slice);
|
||||||
|
notify_observers<DocEvent&>(&DocObserver::onSliceDuplicated, ev);
|
||||||
|
}
|
||||||
|
|
||||||
bool Doc::isModified() const
|
bool Doc::isModified() const
|
||||||
{
|
{
|
||||||
return !m_undo->isInSavedStateOrSimilar();
|
return !m_undo->isInSavedStateOrSimilar();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -141,6 +141,8 @@ public:
|
||||||
void notifyTilesetChanged(Tileset* tileset);
|
void notifyTilesetChanged(Tileset* tileset);
|
||||||
void notifyLayerGroupCollapseChange(Layer* layer);
|
void notifyLayerGroupCollapseChange(Layer* layer);
|
||||||
void notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti);
|
void notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti);
|
||||||
|
void notifyBeforeSlicesDuplication();
|
||||||
|
void notifySliceDuplicated(Slice* slice);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// File related properties
|
// File related properties
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -90,6 +90,8 @@ public:
|
||||||
|
|
||||||
// Slices
|
// Slices
|
||||||
virtual void onSliceNameChange(DocEvent& ev) {}
|
virtual void onSliceNameChange(DocEvent& ev) {}
|
||||||
|
virtual void onBeforeSlicesDuplication(DocEvent& ev) {}
|
||||||
|
virtual void onSliceDuplicated(DocEvent& ev) {}
|
||||||
|
|
||||||
// The tileset has changed.
|
// The tileset has changed.
|
||||||
virtual void onTilesetChanged(DocEvent& ev) {}
|
virtual void onTilesetChanged(DocEvent& ev) {}
|
||||||
|
|
|
@ -36,9 +36,11 @@
|
||||||
#include "app/ui/workspace.h"
|
#include "app/ui/workspace.h"
|
||||||
#include "app/ui_context.h"
|
#include "app/ui_context.h"
|
||||||
#include "app/util/clipboard.h"
|
#include "app/util/clipboard.h"
|
||||||
|
#include "app/util/slice_utils.h"
|
||||||
#include "base/fs.h"
|
#include "base/fs.h"
|
||||||
#include "doc/color.h"
|
#include "doc/color.h"
|
||||||
#include "doc/layer.h"
|
#include "doc/layer.h"
|
||||||
|
#include "doc/slice.h"
|
||||||
#include "doc/sprite.h"
|
#include "doc/sprite.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "ui/alert.h"
|
#include "ui/alert.h"
|
||||||
|
@ -516,6 +518,8 @@ bool DocView::onCanCopy(Context* ctx)
|
||||||
return true;
|
return true;
|
||||||
else if (m_editor->isMovingPixels())
|
else if (m_editor->isMovingPixels())
|
||||||
return true;
|
return true;
|
||||||
|
else if (m_editor->hasSelectedSlices())
|
||||||
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -534,6 +538,11 @@ bool DocView::onCanPaste(Context* ctx)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable) &&
|
||||||
|
ctx->clipboard()->format() == ClipboardFormat::Slices) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,15 +575,22 @@ bool DocView::onCopy(Context* ctx)
|
||||||
ctx->clipboard()->copy(reader);
|
ctx->clipboard()->copy(reader);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return false;
|
std::vector<Slice*> selectedSlices = get_selected_slices(reader.site());
|
||||||
|
if (!selectedSlices.empty()) {
|
||||||
|
ctx->clipboard()->copySlices(selectedSlices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocView::onPaste(Context* ctx, const gfx::Point* position)
|
bool DocView::onPaste(Context* ctx, const gfx::Point* position)
|
||||||
{
|
{
|
||||||
auto clipboard = ctx->clipboard();
|
auto clipboard = ctx->clipboard();
|
||||||
if (clipboard->format() == ClipboardFormat::Image ||
|
if (clipboard->format() == ClipboardFormat::Image ||
|
||||||
clipboard->format() == ClipboardFormat::Tilemap) {
|
clipboard->format() == ClipboardFormat::Tilemap ||
|
||||||
|
clipboard->format() == ClipboardFormat::Slices) {
|
||||||
clipboard->paste(ctx, true, position);
|
clipboard->paste(ctx, true, position);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2555,6 +2555,16 @@ void Editor::onBeforeLayerEditableChange(DocEvent& ev, bool newState)
|
||||||
m_state->onBeforeLayerEditableChange(this, ev.layer(), newState);
|
m_state->onBeforeLayerEditableChange(this, ev.layer(), newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Editor::onBeforeSlicesDuplication(DocEvent& ev)
|
||||||
|
{
|
||||||
|
clearSlicesSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Editor::onSliceDuplicated(DocEvent& ev)
|
||||||
|
{
|
||||||
|
selectSlice(ev.slice());
|
||||||
|
}
|
||||||
|
|
||||||
void Editor::setCursor(const gfx::Point& mouseDisplayPos)
|
void Editor::setCursor(const gfx::Point& mouseDisplayPos)
|
||||||
{
|
{
|
||||||
bool used = false;
|
bool used = false;
|
||||||
|
|
|
@ -343,6 +343,8 @@ protected:
|
||||||
void onRemoveSlice(DocEvent& ev) override;
|
void onRemoveSlice(DocEvent& ev) override;
|
||||||
void onBeforeLayerVisibilityChange(DocEvent& ev, bool newState) override;
|
void onBeforeLayerVisibilityChange(DocEvent& ev, bool newState) override;
|
||||||
void onBeforeLayerEditableChange(DocEvent& ev, bool newState) override;
|
void onBeforeLayerEditableChange(DocEvent& ev, bool newState) override;
|
||||||
|
void onBeforeSlicesDuplication(DocEvent& ev) override;
|
||||||
|
void onSliceDuplicated(DocEvent& ev) override;
|
||||||
|
|
||||||
// ActiveToolObserver impl
|
// ActiveToolObserver impl
|
||||||
void onActiveToolChange(tools::Tool* tool) override;
|
void onActiveToolChange(tools::Tool* tool) override;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -45,6 +45,7 @@
|
||||||
#include "app/ui_context.h"
|
#include "app/ui_context.h"
|
||||||
#include "app/util/expand_cel_canvas.h"
|
#include "app/util/expand_cel_canvas.h"
|
||||||
#include "app/util/layer_utils.h"
|
#include "app/util/layer_utils.h"
|
||||||
|
#include "app/util/slice_utils.h"
|
||||||
#include "doc/brush.h"
|
#include "doc/brush.h"
|
||||||
#include "doc/cel.h"
|
#include "doc/cel.h"
|
||||||
#include "doc/image.h"
|
#include "doc/image.h"
|
||||||
|
@ -693,7 +694,7 @@ public:
|
||||||
// popup menu to create a new one.
|
// popup menu to create a new one.
|
||||||
if (!m_editor->selectSliceBox(bounds) && (bounds.w > 1 || bounds.h > 1)) {
|
if (!m_editor->selectSliceBox(bounds) && (bounds.w > 1 || bounds.h > 1)) {
|
||||||
Slice* slice = new Slice;
|
Slice* slice = new Slice;
|
||||||
slice->setName(getUniqueSliceName());
|
slice->setName(get_unique_slice_name(m_sprite));
|
||||||
|
|
||||||
SliceKey key(bounds);
|
SliceKey key(bounds);
|
||||||
slice->insert(getFrame(), key);
|
slice->insert(getFrame(), key);
|
||||||
|
@ -716,18 +717,6 @@ private:
|
||||||
// EditorObserver impl
|
// EditorObserver impl
|
||||||
void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
||||||
void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
||||||
|
|
||||||
std::string getUniqueSliceName() const
|
|
||||||
{
|
|
||||||
std::string prefix = "Slice";
|
|
||||||
int max = 0;
|
|
||||||
|
|
||||||
for (Slice* slice : m_sprite->slices())
|
|
||||||
if (std::strncmp(slice->name().c_str(), prefix.c_str(), prefix.size()) == 0)
|
|
||||||
max = std::max(max, (int)std::strtol(slice->name().c_str() + prefix.size(), nullptr, 10));
|
|
||||||
|
|
||||||
return fmt::format("{} {}", prefix, max + 1);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
|
#include "app/cmd/add_slice.h"
|
||||||
#include "app/cmd/clear_mask.h"
|
#include "app/cmd/clear_mask.h"
|
||||||
#include "app/cmd/deselect_mask.h"
|
#include "app/cmd/deselect_mask.h"
|
||||||
#include "app/cmd/set_mask.h"
|
#include "app/cmd/set_mask.h"
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
#include "app/util/cel_ops.h"
|
#include "app/util/cel_ops.h"
|
||||||
#include "app/util/clipboard.h"
|
#include "app/util/clipboard.h"
|
||||||
#include "app/util/new_image_from_mask.h"
|
#include "app/util/new_image_from_mask.h"
|
||||||
|
#include "app/util/slice_utils.h"
|
||||||
#include "clip/clip.h"
|
#include "clip/clip.h"
|
||||||
#include "doc/algorithm/shrink_bounds.h"
|
#include "doc/algorithm/shrink_bounds.h"
|
||||||
#include "doc/blend_image.h"
|
#include "doc/blend_image.h"
|
||||||
|
@ -114,6 +116,9 @@ struct Clipboard::Data {
|
||||||
// Selected set of layers/layers/cels
|
// Selected set of layers/layers/cels
|
||||||
ClipboardRange range;
|
ClipboardRange range;
|
||||||
|
|
||||||
|
// Selected slices
|
||||||
|
std::vector<Slice> slices;
|
||||||
|
|
||||||
Data() { range.observeUIContext(); }
|
Data() { range.observeUIContext(); }
|
||||||
|
|
||||||
~Data()
|
~Data()
|
||||||
|
@ -132,6 +137,7 @@ struct Clipboard::Data {
|
||||||
picks.clear();
|
picks.clear();
|
||||||
mask.reset();
|
mask.reset();
|
||||||
range.invalidate();
|
range.invalidate();
|
||||||
|
slices.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipboardFormat format() const
|
ClipboardFormat format() const
|
||||||
|
@ -146,6 +152,8 @@ struct Clipboard::Data {
|
||||||
return ClipboardFormat::PaletteEntries;
|
return ClipboardFormat::PaletteEntries;
|
||||||
else if (tileset && picks.picks())
|
else if (tileset && picks.picks())
|
||||||
return ClipboardFormat::Tileset;
|
return ClipboardFormat::Tileset;
|
||||||
|
else if (!slices.empty())
|
||||||
|
return ClipboardFormat::Slices;
|
||||||
else
|
else
|
||||||
return ClipboardFormat::None;
|
return ClipboardFormat::None;
|
||||||
}
|
}
|
||||||
|
@ -212,6 +220,7 @@ void Clipboard::setData(Image* image,
|
||||||
Mask* mask,
|
Mask* mask,
|
||||||
Palette* palette,
|
Palette* palette,
|
||||||
Tileset* tileset,
|
Tileset* tileset,
|
||||||
|
const std::vector<Slice*>* slices,
|
||||||
bool set_native_clipboard,
|
bool set_native_clipboard,
|
||||||
bool image_source_is_transparent)
|
bool image_source_is_transparent)
|
||||||
{
|
{
|
||||||
|
@ -226,6 +235,11 @@ void Clipboard::setData(Image* image,
|
||||||
else
|
else
|
||||||
m_data->image.reset(image);
|
m_data->image.reset(image);
|
||||||
|
|
||||||
|
if (slices) {
|
||||||
|
for (auto* slice : *slices)
|
||||||
|
m_data->slices.push_back(*slice);
|
||||||
|
}
|
||||||
|
|
||||||
if (set_native_clipboard && use_native_clipboard()) {
|
if (set_native_clipboard && use_native_clipboard()) {
|
||||||
// Copy tilemap to the native clipboard
|
// Copy tilemap to the native clipboard
|
||||||
if (isTilemap) {
|
if (isTilemap) {
|
||||||
|
@ -262,6 +276,7 @@ bool Clipboard::copyFromDocument(const Site& site, bool merged)
|
||||||
(mask ? new Mask(*mask) : nullptr),
|
(mask ? new Mask(*mask) : nullptr),
|
||||||
(pal ? new Palette(*pal) : nullptr),
|
(pal ? new Palette(*pal) : nullptr),
|
||||||
Tileset::MakeCopyCopyingImages(ts),
|
Tileset::MakeCopyCopyingImages(ts),
|
||||||
|
nullptr,
|
||||||
true, // set native clipboard
|
true, // set native clipboard
|
||||||
site.layer() && !site.layer()->isBackground());
|
site.layer() && !site.layer()->isBackground());
|
||||||
|
|
||||||
|
@ -277,6 +292,7 @@ bool Clipboard::copyFromDocument(const Site& site, bool merged)
|
||||||
(mask ? new Mask(*mask) : nullptr),
|
(mask ? new Mask(*mask) : nullptr),
|
||||||
(pal ? new Palette(*pal) : nullptr),
|
(pal ? new Palette(*pal) : nullptr),
|
||||||
nullptr,
|
nullptr,
|
||||||
|
nullptr,
|
||||||
true, // set native clipboard
|
true, // set native clipboard
|
||||||
site.layer() && !site.layer()->isBackground());
|
site.layer() && !site.layer()->isBackground());
|
||||||
|
|
||||||
|
@ -401,6 +417,7 @@ void Clipboard::copyImage(const Image* image, const Mask* mask, const Palette* p
|
||||||
(mask ? new Mask(*mask) : nullptr),
|
(mask ? new Mask(*mask) : nullptr),
|
||||||
(pal ? new Palette(*pal) : nullptr),
|
(pal ? new Palette(*pal) : nullptr),
|
||||||
nullptr,
|
nullptr,
|
||||||
|
nullptr,
|
||||||
App::instance()->isGui(),
|
App::instance()->isGui(),
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
@ -415,6 +432,7 @@ void Clipboard::copyTilemap(const Image* image,
|
||||||
(mask ? new Mask(*mask) : nullptr),
|
(mask ? new Mask(*mask) : nullptr),
|
||||||
(pal ? new Palette(*pal) : nullptr),
|
(pal ? new Palette(*pal) : nullptr),
|
||||||
Tileset::MakeCopyCopyingImages(tileset),
|
Tileset::MakeCopyCopyingImages(tileset),
|
||||||
|
nullptr,
|
||||||
true,
|
true,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
@ -428,6 +446,7 @@ void Clipboard::copyPalette(const Palette* palette, const PalettePicks& picks)
|
||||||
nullptr,
|
nullptr,
|
||||||
new Palette(*palette),
|
new Palette(*palette),
|
||||||
nullptr,
|
nullptr,
|
||||||
|
nullptr,
|
||||||
false, // Don't touch the native clipboard now
|
false, // Don't touch the native clipboard now
|
||||||
false);
|
false);
|
||||||
|
|
||||||
|
@ -438,6 +457,20 @@ void Clipboard::copyPalette(const Palette* palette, const PalettePicks& picks)
|
||||||
m_data->picks = picks;
|
m_data->picks = picks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Clipboard::copySlices(const std::vector<Slice*> slices)
|
||||||
|
{
|
||||||
|
if (slices.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setData(nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
&slices,
|
||||||
|
false, // Don't touch the native clipboard now
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* position)
|
void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* position)
|
||||||
{
|
{
|
||||||
const Site site = ctx->activeSite();
|
const Site site = ctx->activeSite();
|
||||||
|
@ -782,6 +815,26 @@ void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* po
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ClipboardFormat::Slices: {
|
||||||
|
auto& slices = m_data->slices;
|
||||||
|
|
||||||
|
if (slices.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ContextWriter writer(ctx);
|
||||||
|
Tx tx(writer, "Paste Slices");
|
||||||
|
editor->clearSlicesSelection();
|
||||||
|
for (auto& s : slices) {
|
||||||
|
Slice* slice = new Slice(s);
|
||||||
|
slice->setName(get_unique_slice_name(dstSpr, s.name()));
|
||||||
|
tx(new cmd::AddSlice(dstSpr, slice));
|
||||||
|
editor->selectSlice(slice);
|
||||||
|
}
|
||||||
|
tx.commit();
|
||||||
|
updateDstDoc = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all editors/views showing this document
|
// Update all editors/views showing this document
|
||||||
|
@ -799,7 +852,7 @@ ImageRef Clipboard::getImage(Palette* palette)
|
||||||
Tileset* native_tileset = nullptr;
|
Tileset* native_tileset = nullptr;
|
||||||
getNativeBitmap(&native_image, &native_mask, &native_palette, &native_tileset);
|
getNativeBitmap(&native_image, &native_mask, &native_palette, &native_tileset);
|
||||||
if (native_image) {
|
if (native_image) {
|
||||||
setData(native_image, native_mask, native_palette, native_tileset, false, false);
|
setData(native_image, native_mask, native_palette, native_tileset, nullptr, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_data->palette && palette)
|
if (m_data->palette && palette)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -23,6 +23,7 @@ class Image;
|
||||||
class Mask;
|
class Mask;
|
||||||
class Palette;
|
class Palette;
|
||||||
class PalettePicks;
|
class PalettePicks;
|
||||||
|
class Slice;
|
||||||
class Tileset;
|
class Tileset;
|
||||||
} // namespace doc
|
} // namespace doc
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ enum class ClipboardFormat {
|
||||||
PaletteEntries,
|
PaletteEntries,
|
||||||
Tilemap,
|
Tilemap,
|
||||||
Tileset,
|
Tileset,
|
||||||
|
Slices,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Clipboard : public ui::ClipboardDelegate {
|
class Clipboard : public ui::ClipboardDelegate {
|
||||||
|
@ -74,6 +76,7 @@ public:
|
||||||
const doc::Palette* pal,
|
const doc::Palette* pal,
|
||||||
const doc::Tileset* tileset);
|
const doc::Tileset* tileset);
|
||||||
void copyPalette(const doc::Palette* palette, const doc::PalettePicks& picks);
|
void copyPalette(const doc::Palette* palette, const doc::PalettePicks& picks);
|
||||||
|
void copySlices(const std::vector<doc::Slice*> slices);
|
||||||
void paste(Context* ctx, const bool interactive, const gfx::Point* position = nullptr);
|
void paste(Context* ctx, const bool interactive, const gfx::Point* position = nullptr);
|
||||||
|
|
||||||
doc::ImageRef getImage(doc::Palette* palette);
|
doc::ImageRef getImage(doc::Palette* palette);
|
||||||
|
@ -106,6 +109,7 @@ private:
|
||||||
doc::Mask* mask,
|
doc::Mask* mask,
|
||||||
doc::Palette* palette,
|
doc::Palette* palette,
|
||||||
doc::Tileset* tileset,
|
doc::Tileset* tileset,
|
||||||
|
const std::vector<doc::Slice*>* slices,
|
||||||
bool set_native_clipboard,
|
bool set_native_clipboard,
|
||||||
bool image_source_is_transparent);
|
bool image_source_is_transparent);
|
||||||
bool copyFromDocument(const Site& site, bool merged = false);
|
bool copyFromDocument(const Site& site, bool merged = false);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2025 Igara Studio S.A.
|
||||||
|
//
|
||||||
|
// This program is distributed under the terms of
|
||||||
|
// the End-User License Agreement for Aseprite.
|
||||||
|
|
||||||
|
#include "app/util/slice_utils.h"
|
||||||
|
|
||||||
|
#include "app/context_access.h"
|
||||||
|
#include "app/site.h"
|
||||||
|
#include "doc/slice.h"
|
||||||
|
#include "doc/sprite.h"
|
||||||
|
#include "fmt/format.h"
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
std::string get_unique_slice_name(const doc::Sprite* sprite, const std::string& namePrefix)
|
||||||
|
{
|
||||||
|
std::string prefix = namePrefix.empty() ? "Slice" : namePrefix;
|
||||||
|
int max = 0;
|
||||||
|
|
||||||
|
for (doc::Slice* slice : sprite->slices())
|
||||||
|
if (std::strncmp(slice->name().c_str(), prefix.c_str(), prefix.size()) == 0)
|
||||||
|
max = std::max(max, (int)std::strtol(slice->name().c_str() + prefix.size(), nullptr, 10));
|
||||||
|
|
||||||
|
return fmt::format("{} {}", prefix, max + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<doc::Slice*> get_selected_slices(const Site& site)
|
||||||
|
{
|
||||||
|
std::vector<Slice*> selectedSlices;
|
||||||
|
if (site.sprite() && !site.selectedSlices().empty()) {
|
||||||
|
for (auto* slice : site.sprite()->slices()) {
|
||||||
|
if (site.selectedSlices().contains(slice->id())) {
|
||||||
|
selectedSlices.push_back(slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedSlices;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace app
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2025 Igara Studio S.A.
|
||||||
|
//
|
||||||
|
// This program is distributed under the terms of
|
||||||
|
// the End-User License Agreement for Aseprite.
|
||||||
|
|
||||||
|
#ifndef APP_SLICE_UTILS_H_INCLUDED
|
||||||
|
#define APP_SLICE_UTILS_H_INCLUDED
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace doc {
|
||||||
|
class Slice;
|
||||||
|
class Sprite;
|
||||||
|
} // namespace doc
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
class Site;
|
||||||
|
|
||||||
|
std::string get_unique_slice_name(const doc::Sprite* sprite,
|
||||||
|
const std::string& namePrefix = std::string());
|
||||||
|
|
||||||
|
std::vector<doc::Slice*> get_selected_slices(const Site& site);
|
||||||
|
|
||||||
|
} // namespace app
|
||||||
|
|
||||||
|
#endif
|
|
@ -31,7 +31,10 @@ Slices::~Slices()
|
||||||
|
|
||||||
void Slices::add(Slice* slice)
|
void Slices::add(Slice* slice)
|
||||||
{
|
{
|
||||||
m_slices.push_back(slice);
|
// Insert the slice at the begining to display it at the front of the others.
|
||||||
|
// This is useful when duplicating (or copy & pasting) slices, because the
|
||||||
|
// user can drag the new slices instead of the originally selected ones.
|
||||||
|
m_slices.insert(m_slices.begin(), slice);
|
||||||
slice->setOwner(this);
|
slice->setOwner(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ do
|
||||||
assert(b.bounds == Rectangle(0, 2, 8, 10))
|
assert(b.bounds == Rectangle(0, 2, 8, 10))
|
||||||
assert(c.bounds == Rectangle(0, 0, 32, 32))
|
assert(c.bounds == Rectangle(0, 0, 32, 32))
|
||||||
|
|
||||||
local bounds = { nil, Rectangle(0, 2, 8, 10), Rectangle(0, 0, 32, 32) }
|
local bounds = { Rectangle(0, 0, 32, 32), Rectangle(0, 2, 8, 10), nil }
|
||||||
|
|
||||||
local i = 1
|
local i = 1
|
||||||
for k,v in ipairs(s.slices) do
|
for k,v in ipairs(s.slices) do
|
||||||
|
@ -25,8 +25,8 @@ do
|
||||||
end
|
end
|
||||||
|
|
||||||
s:deleteSlice(b)
|
s:deleteSlice(b)
|
||||||
assert(a == s.slices[1])
|
assert(c == s.slices[1])
|
||||||
assert(c == s.slices[2])
|
assert(a == s.slices[2])
|
||||||
|
|
||||||
assert(2 == #s.slices)
|
assert(2 == #s.slices)
|
||||||
app.undo()
|
app.undo()
|
||||||
|
|
Loading…
Reference in New Issue