mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 13:21:34 +00:00
Add slices copy&paste and duplication (fix #4466)
This commit is contained in:
parent
53c415c933
commit
28a22c06cb
@ -1203,6 +1203,7 @@
|
||||
|
||||
<menu id="slice_popup_menu">
|
||||
<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" />
|
||||
</menu>
|
||||
|
||||
|
@ -265,6 +265,7 @@ Despeckle = Despeckle
|
||||
DeveloperConsole = Developer Console
|
||||
DiscardBrush = Discard Brush
|
||||
DuplicateLayer = Duplicate Layer
|
||||
DuplicateSlice = Duplicate Slice
|
||||
DuplicateSprite = Duplicate Sprite
|
||||
DuplicateView = Duplicate View
|
||||
Exit = Exit
|
||||
@ -1658,6 +1659,10 @@ from = From:
|
||||
to = To:
|
||||
tolerance = Tolerance:
|
||||
|
||||
[duplicate_slice]
|
||||
x_duplicated = Slice "{}" duplicated
|
||||
n_slices_duplicated = {} slice(s) duplicated
|
||||
|
||||
[remove_slice]
|
||||
x_removed = Slice "{}" removed
|
||||
n_slices_removed = {} slice(s) removed
|
||||
@ -1736,6 +1741,7 @@ delete_file = Delete file, I've already sent it
|
||||
|
||||
[slice_popup_menu]
|
||||
properties = Slice &Properties...
|
||||
duplicate = D&uplicate Slice
|
||||
delete = &Delete Slice
|
||||
|
||||
[slice_properties]
|
||||
|
@ -394,6 +394,7 @@ target_sources(app-lib PRIVATE
|
||||
commands/cmd_deselect_mask.cpp
|
||||
commands/cmd_discard_brush.cpp
|
||||
commands/cmd_duplicate_layer.cpp
|
||||
commands/cmd_duplicate_slice.cpp
|
||||
commands/cmd_duplicate_sprite.cpp
|
||||
commands/cmd_duplicate_view.cpp
|
||||
commands/cmd_enter_license.cpp
|
||||
@ -713,6 +714,7 @@ target_sources(app-lib PRIVATE
|
||||
util/render_text.cpp
|
||||
util/resize_image.cpp
|
||||
util/shader_helpers.cpp
|
||||
util/slice_utils.cpp
|
||||
util/tile_flags_utils.cpp
|
||||
util/tileset_utils.cpp
|
||||
util/wrap_point.cpp
|
||||
|
108
src/app/commands/cmd_duplicate_slice.cpp
Normal file
108
src/app/commands/cmd_duplicate_slice.cpp
Normal file
@ -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
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -40,6 +40,7 @@ FOR_EACH_COMMAND(DeselectMask)
|
||||
FOR_EACH_COMMAND(Despeckle)
|
||||
FOR_EACH_COMMAND(DiscardBrush)
|
||||
FOR_EACH_COMMAND(DuplicateLayer)
|
||||
FOR_EACH_COMMAND(DuplicateSlice)
|
||||
FOR_EACH_COMMAND(DuplicateSprite)
|
||||
FOR_EACH_COMMAND(DuplicateView)
|
||||
FOR_EACH_COMMAND(Exit)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return !m_undo->isInSavedStateOrSimilar();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -141,6 +141,8 @@ public:
|
||||
void notifyTilesetChanged(Tileset* tileset);
|
||||
void notifyLayerGroupCollapseChange(Layer* layer);
|
||||
void notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti);
|
||||
void notifyBeforeSlicesDuplication();
|
||||
void notifySliceDuplicated(Slice* slice);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// File related properties
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -90,6 +90,8 @@ public:
|
||||
|
||||
// Slices
|
||||
virtual void onSliceNameChange(DocEvent& ev) {}
|
||||
virtual void onBeforeSlicesDuplication(DocEvent& ev) {}
|
||||
virtual void onSliceDuplicated(DocEvent& ev) {}
|
||||
|
||||
// The tileset has changed.
|
||||
virtual void onTilesetChanged(DocEvent& ev) {}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -36,9 +36,11 @@
|
||||
#include "app/ui/workspace.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/slice_utils.h"
|
||||
#include "base/fs.h"
|
||||
#include "doc/color.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/slice.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ui/accelerator.h"
|
||||
@ -510,6 +512,8 @@ bool DocView::onCanCopy(Context* ctx)
|
||||
return true;
|
||||
else if (m_editor->isMovingPixels())
|
||||
return true;
|
||||
else if (m_editor->hasSelectedSlices())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
@ -528,6 +532,11 @@ bool DocView::onCanPaste(Context* ctx)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable) &&
|
||||
ctx->clipboard()->format() == ClipboardFormat::Slices) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -560,15 +569,22 @@ bool DocView::onCopy(Context* ctx)
|
||||
ctx->clipboard()->copy(reader);
|
||||
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)
|
||||
{
|
||||
auto clipboard = ctx->clipboard();
|
||||
if (clipboard->format() == ClipboardFormat::Image ||
|
||||
clipboard->format() == ClipboardFormat::Tilemap) {
|
||||
clipboard->format() == ClipboardFormat::Tilemap ||
|
||||
clipboard->format() == ClipboardFormat::Slices) {
|
||||
clipboard->paste(ctx, true, position);
|
||||
return true;
|
||||
}
|
||||
|
@ -2534,6 +2534,16 @@ void Editor::onBeforeLayerEditableChange(DocEvent& ev, bool 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)
|
||||
{
|
||||
Rect vp = View::getView(this)->viewportBounds();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -343,6 +343,8 @@ protected:
|
||||
void onRemoveSlice(DocEvent& ev) override;
|
||||
void onBeforeLayerVisibilityChange(DocEvent& ev, bool newState) override;
|
||||
void onBeforeLayerEditableChange(DocEvent& ev, bool newState) override;
|
||||
void onBeforeSlicesDuplication(DocEvent& ev) override;
|
||||
void onSliceDuplicated(DocEvent& ev) override;
|
||||
|
||||
// ActiveToolObserver impl
|
||||
void onActiveToolChange(tools::Tool* tool) override;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -45,6 +45,7 @@
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/expand_cel_canvas.h"
|
||||
#include "app/util/layer_utils.h"
|
||||
#include "app/util/slice_utils.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
@ -693,7 +694,7 @@ public:
|
||||
// popup menu to create a new one.
|
||||
if (!m_editor->selectSliceBox(bounds) && (bounds.w > 1 || bounds.h > 1)) {
|
||||
Slice* slice = new Slice;
|
||||
slice->setName(getUniqueSliceName());
|
||||
slice->setName(get_unique_slice_name(m_sprite));
|
||||
|
||||
SliceKey key(bounds);
|
||||
slice->insert(getFrame(), key);
|
||||
@ -716,18 +717,6 @@ private:
|
||||
// EditorObserver impl
|
||||
void onScrollChanged(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
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/cmd/add_slice.h"
|
||||
#include "app/cmd/clear_mask.h"
|
||||
#include "app/cmd/deselect_mask.h"
|
||||
#include "app/cmd/set_mask.h"
|
||||
@ -32,6 +33,7 @@
|
||||
#include "app/util/cel_ops.h"
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "app/util/slice_utils.h"
|
||||
#include "clip/clip.h"
|
||||
#include "doc/algorithm/shrink_bounds.h"
|
||||
#include "doc/blend_image.h"
|
||||
@ -114,6 +116,9 @@ struct Clipboard::Data {
|
||||
// Selected set of layers/layers/cels
|
||||
ClipboardRange range;
|
||||
|
||||
// Selected slices
|
||||
std::vector<Slice> slices;
|
||||
|
||||
Data() { range.observeUIContext(); }
|
||||
|
||||
~Data()
|
||||
@ -132,6 +137,7 @@ struct Clipboard::Data {
|
||||
picks.clear();
|
||||
mask.reset();
|
||||
range.invalidate();
|
||||
slices.clear();
|
||||
}
|
||||
|
||||
ClipboardFormat format() const
|
||||
@ -146,6 +152,8 @@ struct Clipboard::Data {
|
||||
return ClipboardFormat::PaletteEntries;
|
||||
else if (tileset && picks.picks())
|
||||
return ClipboardFormat::Tileset;
|
||||
else if (!slices.empty())
|
||||
return ClipboardFormat::Slices;
|
||||
else
|
||||
return ClipboardFormat::None;
|
||||
}
|
||||
@ -212,6 +220,7 @@ void Clipboard::setData(Image* image,
|
||||
Mask* mask,
|
||||
Palette* palette,
|
||||
Tileset* tileset,
|
||||
const std::vector<Slice*>* slices,
|
||||
bool set_native_clipboard,
|
||||
bool image_source_is_transparent)
|
||||
{
|
||||
@ -226,6 +235,11 @@ void Clipboard::setData(Image* image,
|
||||
else
|
||||
m_data->image.reset(image);
|
||||
|
||||
if (slices) {
|
||||
for (auto* slice : *slices)
|
||||
m_data->slices.push_back(*slice);
|
||||
}
|
||||
|
||||
if (set_native_clipboard && use_native_clipboard()) {
|
||||
// Copy tilemap to the native clipboard
|
||||
if (isTilemap) {
|
||||
@ -262,6 +276,7 @@ bool Clipboard::copyFromDocument(const Site& site, bool merged)
|
||||
(mask ? new Mask(*mask) : nullptr),
|
||||
(pal ? new Palette(*pal) : nullptr),
|
||||
Tileset::MakeCopyCopyingImages(ts),
|
||||
nullptr,
|
||||
true, // set native clipboard
|
||||
site.layer() && !site.layer()->isBackground());
|
||||
|
||||
@ -277,6 +292,7 @@ bool Clipboard::copyFromDocument(const Site& site, bool merged)
|
||||
(mask ? new Mask(*mask) : nullptr),
|
||||
(pal ? new Palette(*pal) : nullptr),
|
||||
nullptr,
|
||||
nullptr,
|
||||
true, // set native clipboard
|
||||
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),
|
||||
(pal ? new Palette(*pal) : nullptr),
|
||||
nullptr,
|
||||
nullptr,
|
||||
App::instance()->isGui(),
|
||||
false);
|
||||
}
|
||||
@ -415,6 +432,7 @@ void Clipboard::copyTilemap(const Image* image,
|
||||
(mask ? new Mask(*mask) : nullptr),
|
||||
(pal ? new Palette(*pal) : nullptr),
|
||||
Tileset::MakeCopyCopyingImages(tileset),
|
||||
nullptr,
|
||||
true,
|
||||
false);
|
||||
}
|
||||
@ -428,6 +446,7 @@ void Clipboard::copyPalette(const Palette* palette, const PalettePicks& picks)
|
||||
nullptr,
|
||||
new Palette(*palette),
|
||||
nullptr,
|
||||
nullptr,
|
||||
false, // Don't touch the native clipboard now
|
||||
false);
|
||||
|
||||
@ -438,6 +457,20 @@ void Clipboard::copyPalette(const Palette* palette, const PalettePicks& 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)
|
||||
{
|
||||
const Site site = ctx->activeSite();
|
||||
@ -782,6 +815,26 @@ void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* po
|
||||
}
|
||||
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
|
||||
@ -799,7 +852,7 @@ ImageRef Clipboard::getImage(Palette* palette)
|
||||
Tileset* native_tileset = nullptr;
|
||||
getNativeBitmap(&native_image, &native_mask, &native_palette, &native_tileset);
|
||||
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)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -23,6 +23,7 @@ class Image;
|
||||
class Mask;
|
||||
class Palette;
|
||||
class PalettePicks;
|
||||
class Slice;
|
||||
class Tileset;
|
||||
} // namespace doc
|
||||
|
||||
@ -45,6 +46,7 @@ enum class ClipboardFormat {
|
||||
PaletteEntries,
|
||||
Tilemap,
|
||||
Tileset,
|
||||
Slices,
|
||||
};
|
||||
|
||||
class Clipboard : public ui::ClipboardDelegate {
|
||||
@ -74,6 +76,7 @@ public:
|
||||
const doc::Palette* pal,
|
||||
const doc::Tileset* tileset);
|
||||
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);
|
||||
|
||||
doc::ImageRef getImage(doc::Palette* palette);
|
||||
@ -106,6 +109,7 @@ private:
|
||||
doc::Mask* mask,
|
||||
doc::Palette* palette,
|
||||
doc::Tileset* tileset,
|
||||
const std::vector<doc::Slice*>* slices,
|
||||
bool set_native_clipboard,
|
||||
bool image_source_is_transparent);
|
||||
bool copyFromDocument(const Site& site, bool merged = false);
|
||||
|
42
src/app/util/slice_utils.cpp
Normal file
42
src/app/util/slice_utils.cpp
Normal file
@ -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
|
30
src/app/util/slice_utils.h
Normal file
30
src/app/util/slice_utils.h
Normal file
@ -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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ do
|
||||
assert(b.bounds == Rectangle(0, 2, 8, 10))
|
||||
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
|
||||
for k,v in ipairs(s.slices) do
|
||||
@ -25,8 +25,8 @@ do
|
||||
end
|
||||
|
||||
s:deleteSlice(b)
|
||||
assert(a == s.slices[1])
|
||||
assert(c == s.slices[2])
|
||||
assert(c == s.slices[1])
|
||||
assert(a == s.slices[2])
|
||||
|
||||
assert(2 == #s.slices)
|
||||
app.undo()
|
||||
|
Loading…
x
Reference in New Issue
Block a user