mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 13:21:34 +00:00
Allow dropping files into the timeline
This commit is contained in:
parent
f7eed69d7c
commit
722c74189c
@ -282,6 +282,7 @@ target_sources(app-lib PRIVATE
|
||||
cmd/copy_region.cpp
|
||||
cmd/crop_cel.cpp
|
||||
cmd/deselect_mask.cpp
|
||||
cmd/drop_on_timeline.cpp
|
||||
cmd/flatten_layers.cpp
|
||||
cmd/flip_image.cpp
|
||||
cmd/flip_mask.cpp
|
||||
|
231
src/app/cmd/drop_on_timeline.cpp
Normal file
231
src/app/cmd/drop_on_timeline.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
// 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/cmd/drop_on_timeline.h"
|
||||
|
||||
#include "app/cmd/add_layer.h"
|
||||
#include "app/cmd/set_pixel_format.h"
|
||||
#include "app/context_flags.h"
|
||||
#include "app/console.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_event.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/open_file_job.h"
|
||||
#include "app/tx.h"
|
||||
#include "base/fs.h"
|
||||
#include "doc/layer_list.h"
|
||||
#include "render/dithering.h"
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
|
||||
DropOnTimeline::DropOnTimeline(app::Doc* doc,
|
||||
doc::frame_t frame,
|
||||
doc::layer_t layerIndex,
|
||||
LayerInsertion insert,
|
||||
const base::paths& paths) : WithDocument(doc)
|
||||
, m_size(0)
|
||||
, m_paths(paths)
|
||||
, m_frame(frame)
|
||||
, m_layerIndex(layerIndex)
|
||||
, m_insert(insert)
|
||||
{
|
||||
ASSERT(m_layerIndex >= 0);
|
||||
for(const auto& path : m_paths)
|
||||
m_size += path.size();
|
||||
}
|
||||
|
||||
void DropOnTimeline::setupInsertionLayers(Layer** before, Layer** after, LayerGroup** group)
|
||||
{
|
||||
const LayerList& allLayers = document()->sprite()->allLayers();
|
||||
*after = (m_insert == LayerInsertion::After ? allLayers[m_layerIndex] : nullptr);
|
||||
*before = (m_insert == LayerInsertion::Before ? allLayers[m_layerIndex] : nullptr);
|
||||
if (*before && (*before)->isGroup()) {
|
||||
*group = static_cast<LayerGroup*>(*before);
|
||||
// The user is trying to drop layers into an empty group, so there is no after
|
||||
// nor before layer...
|
||||
if ((*group)->layersCount() == 0) {
|
||||
*after = nullptr;
|
||||
*before = nullptr;
|
||||
return;
|
||||
}
|
||||
*after = static_cast<LayerGroup*>(*before)->lastLayer();
|
||||
*before = nullptr;
|
||||
}
|
||||
|
||||
*group = (*after ? (*after)->parent() : (*before)->parent());
|
||||
}
|
||||
|
||||
void DropOnTimeline::onExecute()
|
||||
{
|
||||
Doc* destDoc = document();
|
||||
Console console;
|
||||
Context* context = document()->context();
|
||||
|
||||
m_previousTotalFrames = destDoc->sprite()->totalFrames();
|
||||
// Layers after/before which the dropped layers will be inserted
|
||||
Layer* afterThis = nullptr;
|
||||
Layer* beforeThis = nullptr;
|
||||
// Parent group of the after/before layers.
|
||||
LayerGroup* group = nullptr;
|
||||
|
||||
int flags =
|
||||
FILE_LOAD_DATA_FILE |
|
||||
FILE_LOAD_CREATE_PALETTE;
|
||||
|
||||
for(const auto& path : m_paths) {
|
||||
std::unique_ptr<FileOp> fop(
|
||||
FileOp::createLoadDocumentOperation(context, path, flags));
|
||||
|
||||
// Do nothing (the user cancelled or something like that)
|
||||
if (!fop)
|
||||
return;
|
||||
|
||||
if (fop->hasError()) {
|
||||
console.printf(fop->error().c_str());
|
||||
}
|
||||
else {
|
||||
OpenFileJob task(fop.get(), true);
|
||||
task.showProgressWindow();
|
||||
|
||||
// Post-load processing, it is called from the GUI because may require user intervention.
|
||||
fop->postLoad();
|
||||
|
||||
// Show any error
|
||||
if (fop->hasError() && !fop->isStop())
|
||||
console.printf(fop->error().c_str());
|
||||
|
||||
Doc* srcDoc = fop->document();
|
||||
if (srcDoc) {
|
||||
// If source document doesn't match the destination document's color
|
||||
// mode, change it.
|
||||
if (srcDoc->colorMode() != destDoc->colorMode()) {
|
||||
Tx tx(srcDoc);
|
||||
tx(new cmd::SetPixelFormat(
|
||||
srcDoc->sprite(), destDoc->sprite()->pixelFormat(),
|
||||
render::Dithering(),
|
||||
Preferences::instance().quantization.rgbmapAlgorithm(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
FitCriteria::DEFAULT));
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
// If there is no room for the source frames, add frames to the
|
||||
// destination sprite.
|
||||
if (m_frame+srcDoc->sprite()->totalFrames() > destDoc->sprite()->totalFrames()) {
|
||||
destDoc->sprite()->setTotalFrames(m_frame+srcDoc->sprite()->totalFrames());
|
||||
}
|
||||
|
||||
setupInsertionLayers(&beforeThis, &afterThis, &group);
|
||||
|
||||
// Insert layers from the source document.
|
||||
auto allLayers = srcDoc->sprite()->allLayers();
|
||||
for (auto it = allLayers.cbegin(); it != allLayers.cend(); ++it) {
|
||||
auto* layer = *it;
|
||||
// TODO: If we could "relocate" a layer from the source document to the
|
||||
// destination document we could avoid making a copy here.
|
||||
auto* layerCopy = Layer::MakeCopyWithSprite(layer, destDoc->sprite());
|
||||
destDoc->copyLayerContent(layer, destDoc, layerCopy);
|
||||
layerCopy->displaceFrames(0, m_frame);
|
||||
|
||||
if (afterThis) {
|
||||
group->insertLayer(layerCopy, afterThis);
|
||||
afterThis = layerCopy;
|
||||
}
|
||||
else if (beforeThis) {
|
||||
group->insertLayerBefore(layerCopy, beforeThis);
|
||||
beforeThis = nullptr;
|
||||
afterThis = layerCopy;
|
||||
}
|
||||
else {
|
||||
group->addLayer(layerCopy);
|
||||
afterThis = layerCopy;
|
||||
}
|
||||
m_droppedLayers.push_back(layerCopy);
|
||||
m_size += layerCopy->getMemSize();
|
||||
}
|
||||
group->incrementVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
destDoc->sprite()->incrementVersion();
|
||||
destDoc->incrementVersion();
|
||||
|
||||
notifyDocObservers(afterThis ? afterThis : beforeThis);
|
||||
}
|
||||
|
||||
void DropOnTimeline::onUndo()
|
||||
{
|
||||
Doc* doc = document();
|
||||
frame_t currentTotalFrames = doc->sprite()->totalFrames();
|
||||
Layer* layerBefore = nullptr;
|
||||
for (auto* layer : m_droppedLayers) {
|
||||
layerBefore = layer->getPrevious();
|
||||
layer->parent()->removeLayer(layer);
|
||||
}
|
||||
doc->sprite()->setTotalFrames(m_previousTotalFrames);
|
||||
m_previousTotalFrames = currentTotalFrames;
|
||||
|
||||
if (!layerBefore)
|
||||
layerBefore = doc->sprite()->firstLayer();
|
||||
|
||||
notifyDocObservers(layerBefore);
|
||||
}
|
||||
|
||||
void DropOnTimeline::onRedo()
|
||||
{
|
||||
Doc* doc = document();
|
||||
frame_t currentTotalFrames = doc->sprite()->totalFrames();
|
||||
doc->sprite()->setTotalFrames(m_previousTotalFrames);
|
||||
m_previousTotalFrames = currentTotalFrames;
|
||||
|
||||
Layer* afterThis = nullptr;
|
||||
Layer* beforeThis = nullptr;
|
||||
LayerGroup* group = nullptr;
|
||||
setupInsertionLayers(&beforeThis, &afterThis, &group);
|
||||
|
||||
for (auto it = m_droppedLayers.cbegin(); it != m_droppedLayers.cend(); ++it) {
|
||||
auto* layer = *it;
|
||||
|
||||
if (afterThis) {
|
||||
group->insertLayer(layer, afterThis);
|
||||
afterThis = layer;
|
||||
}
|
||||
else if (beforeThis) {
|
||||
group->insertLayerBefore(layer, beforeThis);
|
||||
beforeThis = nullptr;
|
||||
afterThis = layer;
|
||||
}
|
||||
else {
|
||||
group->addLayer(layer);
|
||||
afterThis = layer;
|
||||
}
|
||||
}
|
||||
notifyDocObservers(afterThis ? afterThis : beforeThis);
|
||||
}
|
||||
|
||||
void DropOnTimeline::notifyDocObservers(Layer* layer)
|
||||
{
|
||||
Doc* doc = document();
|
||||
if (doc && layer) {
|
||||
DocEvent ev(doc);
|
||||
ev.sprite(doc->sprite());
|
||||
ev.layer(layer);
|
||||
// TODO: This is a hack, we send this notification because the timeline
|
||||
// has the code we need to execute after this command. We tried using
|
||||
// DocObserver::onAddLayer but it makes the redo crash.
|
||||
doc->notify_observers<DocEvent&>(&DocObserver::onAfterRemoveLayer, ev);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
62
src/app/cmd/drop_on_timeline.h
Normal file
62
src/app/cmd/drop_on_timeline.h
Normal file
@ -0,0 +1,62 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_CMD_drop_on_timeline_H_INCLUDED
|
||||
#define APP_CMD_drop_on_timeline_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/cmd.h"
|
||||
#include "app/cmd/with_document.h"
|
||||
#include "app/doc_observer.h"
|
||||
#include "base/paths.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_list.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
|
||||
class DropOnTimeline : public Cmd
|
||||
, public WithDocument {
|
||||
public:
|
||||
enum class LayerInsertion {
|
||||
Before,
|
||||
After,
|
||||
};
|
||||
// Inserts the layers and frames of the documents pointed by the specified
|
||||
// paths, at the specified frame and before or after the specified layer index.
|
||||
DropOnTimeline(app::Doc* doc, doc::frame_t frame, doc::layer_t layerIndex,
|
||||
LayerInsertion insert, const base::paths& paths);
|
||||
protected:
|
||||
void onExecute() override;
|
||||
void onUndo() override;
|
||||
void onRedo() override;
|
||||
size_t onMemSize() const override {
|
||||
return sizeof(*this) + m_size;
|
||||
}
|
||||
|
||||
private:
|
||||
void setupInsertionLayers(doc::Layer** before, doc::Layer** after, doc::LayerGroup** group);
|
||||
void notifyAddLayer(doc::Layer* layer);
|
||||
void notifyDocObservers(doc::Layer* layer);
|
||||
|
||||
size_t m_size;
|
||||
base::paths m_paths;
|
||||
doc::frame_t m_frame;
|
||||
doc::layer_t m_layerIndex;
|
||||
LayerInsertion m_insert;
|
||||
// Holds the list of layers dropped into the document.
|
||||
doc::LayerList m_droppedLayers;
|
||||
// Number of frames the doc had before dropping.
|
||||
doc::frame_t m_previousTotalFrames;
|
||||
};
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/cmd/drop_on_timeline.h"
|
||||
#include "app/cmd/set_tag_range.h"
|
||||
#include "app/cmd_transaction.h"
|
||||
#include "app/color_utils.h"
|
||||
@ -4471,6 +4472,7 @@ void Timeline::onDragLeave(ui::DragEvent& e)
|
||||
{
|
||||
m_state = STATE_STANDBY;
|
||||
m_range.clearRange();
|
||||
m_dropRange.clearRange();
|
||||
invalidate();
|
||||
flushRedraw();
|
||||
os::Event ev;
|
||||
@ -4487,12 +4489,15 @@ void Timeline::onDrag(ui::DragEvent& e)
|
||||
case PART_ROW_CONTINUOUS_ICON:
|
||||
case PART_ROW_PADLOCK_ICON:
|
||||
case PART_ROW_TEXT: {
|
||||
m_range.startRange(nullptr, m_frame, Range::kLayers);
|
||||
m_range.startRange(nullptr, -1, Range::kLayers);
|
||||
break;
|
||||
}
|
||||
case PART_CEL:
|
||||
case PART_HEADER_FRAME:
|
||||
m_range.startRange(nullptr, m_frame, Range::kFrames);
|
||||
m_range.startRange(nullptr, -1, Range::kFrames);
|
||||
break;
|
||||
default:
|
||||
m_range.clearRange();
|
||||
break;
|
||||
}
|
||||
|
||||
@ -4507,9 +4512,43 @@ void Timeline::onDrop(ui::DragEvent& e)
|
||||
{
|
||||
Widget::onDrop(e);
|
||||
|
||||
// Determine at which frame and layer the content was dropped on.
|
||||
frame_t frame = m_frame;
|
||||
layer_t layerIndex = getLayerIndex(m_layer);
|
||||
switch(m_dropRange.type()) {
|
||||
case Range::kFrames:
|
||||
frame = m_dropRange.firstFrame();
|
||||
if (m_dropTarget.hhit == DropTarget::After)
|
||||
frame++;
|
||||
break;
|
||||
case Range::kLayers:
|
||||
layerIndex = getLayerIndex(*m_dropRange.selectedLayers().begin());
|
||||
if (m_dropTarget.vhit == DropTarget::Top)
|
||||
layerIndex++;
|
||||
break;
|
||||
}
|
||||
|
||||
#if _DEBUG
|
||||
LOG(LogLevel::VERBOSE, "Dropped at frame: %d, and layerIndex: %d\n", frame, layerIndex);
|
||||
#endif
|
||||
|
||||
if (e.hasPaths()) {
|
||||
base::paths paths = e.getPaths();
|
||||
execute_from_ui_thread([=]{
|
||||
Tx tx(m_document);
|
||||
tx(new cmd::DropAtTimeline(m_document, frame, layerIndex, paths));
|
||||
tx.commit();
|
||||
regenerateRows();
|
||||
showCurrentCel();
|
||||
clearClipboardRange();
|
||||
m_document->notifyGeneralUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
e.handled(true);
|
||||
m_state = STATE_STANDBY;
|
||||
m_range.clearRange();
|
||||
m_dropRange.clearRange();
|
||||
invalidate();
|
||||
flushRedraw();
|
||||
os::Event ev;
|
||||
|
Loading…
x
Reference in New Issue
Block a user