mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 13:21:34 +00:00
Add support to drop a file into a timeline's cel
This commit is contained in:
parent
84b56f4b2d
commit
9ae0416627
@ -12,15 +12,14 @@
|
||||
|
||||
#include "app/cmd/add_layer.h"
|
||||
#include "app/cmd/set_pixel_format.h"
|
||||
#include "app/cmd/move_cel.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"
|
||||
|
||||
@ -32,13 +31,15 @@ namespace cmd {
|
||||
DropOnTimeline::DropOnTimeline(app::Doc* doc,
|
||||
doc::frame_t frame,
|
||||
doc::layer_t layerIndex,
|
||||
LayerInsertion insert,
|
||||
InsertionPoint insert,
|
||||
DroppedOn droppedOn,
|
||||
const base::paths& paths) : WithDocument(doc)
|
||||
, m_size(0)
|
||||
, m_paths(paths)
|
||||
, m_frame(frame)
|
||||
, m_layerIndex(layerIndex)
|
||||
, m_insert(insert)
|
||||
, m_droppedOn(droppedOn)
|
||||
{
|
||||
ASSERT(m_layerIndex >= 0);
|
||||
for(const auto& path : m_paths)
|
||||
@ -48,8 +49,8 @@ DropOnTimeline::DropOnTimeline(app::Doc* doc,
|
||||
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);
|
||||
*after = (m_insert == InsertionPoint::AfterLayer ? allLayers[m_layerIndex] : nullptr);
|
||||
*before = (m_insert == InsertionPoint::BeforeLayer ? 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
|
||||
@ -83,13 +84,20 @@ void DropOnTimeline::onExecute()
|
||||
FILE_LOAD_DATA_FILE |
|
||||
FILE_LOAD_CREATE_PALETTE | FILE_LOAD_SEQUENCE_YES;
|
||||
|
||||
int fopCount = 0;
|
||||
while(!m_paths.empty()) {
|
||||
std::unique_ptr<FileOp> fop(
|
||||
FileOp::createLoadDocumentOperation(context, m_paths.front(), flags));
|
||||
fopCount++;
|
||||
|
||||
base::paths fopFilenames;
|
||||
fop->getFilenameList(fopFilenames);
|
||||
// Remove paths that will be loaded by the current file operation.
|
||||
for (const auto& filename : fop->filenames())
|
||||
m_paths.erase(std::find(m_paths.begin(), m_paths.end(), filename));
|
||||
for (const auto& filename : fopFilenames) {
|
||||
auto it = std::find(m_paths.begin(), m_paths.end(), filename);
|
||||
if (it != m_paths.end())
|
||||
m_paths.erase(it);
|
||||
}
|
||||
|
||||
// Do nothing (the user cancelled or something like that)
|
||||
if (!fop)
|
||||
@ -114,6 +122,8 @@ void DropOnTimeline::onExecute()
|
||||
// If source document doesn't match the destination document's color
|
||||
// mode, change it.
|
||||
if (srcDoc->colorMode() != destDoc->colorMode()) {
|
||||
// Execute in a source doc transaction because we don't need undo/redo
|
||||
// this.
|
||||
Tx tx(srcDoc);
|
||||
tx(new cmd::SetPixelFormat(
|
||||
srcDoc->sprite(), destDoc->sprite()->pixelFormat(),
|
||||
@ -125,6 +135,16 @@ void DropOnTimeline::onExecute()
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
// If there is only one file operation and we can move the cel from the
|
||||
// source document then move the cel into the destination frame.
|
||||
const bool isOnlyFileOperation = (fopCount == 1 && m_paths.empty());
|
||||
if (isOnlyFileOperation && canMoveCelFrom(srcDoc)) {
|
||||
auto* srcLayer = static_cast<LayerImage*>(srcDoc->sprite()->firstLayer());
|
||||
auto* destLayer = static_cast<LayerImage*>(destDoc->sprite()->allLayers()[m_layerIndex]);
|
||||
executeAndAdd(new MoveCel(srcLayer, 0, destLayer, m_frame, false));
|
||||
break;
|
||||
}
|
||||
|
||||
// If there is no room for the source frames, add frames to the
|
||||
// destination sprite.
|
||||
if (m_frame+srcDoc->sprite()->totalFrames() > destDoc->sprite()->totalFrames()) {
|
||||
@ -171,6 +191,13 @@ void DropOnTimeline::onExecute()
|
||||
|
||||
void DropOnTimeline::onUndo()
|
||||
{
|
||||
CmdSequence::onUndo();
|
||||
|
||||
if (m_droppedLayers.empty()) {
|
||||
notifyDocObservers(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
Doc* doc = document();
|
||||
frame_t currentTotalFrames = doc->sprite()->totalFrames();
|
||||
Layer* layerBefore = nullptr;
|
||||
@ -189,6 +216,13 @@ void DropOnTimeline::onUndo()
|
||||
|
||||
void DropOnTimeline::onRedo()
|
||||
{
|
||||
CmdSequence::onRedo();
|
||||
|
||||
if (m_droppedLayers.empty()) {
|
||||
notifyDocObservers(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
Doc* doc = document();
|
||||
frame_t currentTotalFrames = doc->sprite()->totalFrames();
|
||||
doc->sprite()->setTotalFrames(m_previousTotalFrames);
|
||||
@ -219,18 +253,41 @@ void DropOnTimeline::onRedo()
|
||||
notifyDocObservers(afterThis ? afterThis : beforeThis);
|
||||
}
|
||||
|
||||
bool DropOnTimeline::canMoveCelFrom(app::Doc* srcDoc)
|
||||
{
|
||||
// The cel from the source doc can be moved only when the all of the following
|
||||
// conditions are met:
|
||||
// * Drop took place in a cel.
|
||||
// * Source doc has only one layer with just one frame.
|
||||
// * The layer from the source doc and the destination cel's layer are both
|
||||
// Image layers.
|
||||
auto* srcLayer = srcDoc->sprite()->firstLayer();
|
||||
auto* destLayer = document()->sprite()->allLayers()[m_layerIndex];
|
||||
return m_droppedOn == DroppedOn::Cel &&
|
||||
srcDoc->sprite()->allLayersCount() == 1 &&
|
||||
srcDoc->sprite()->totalFrames() == 1 &&
|
||||
srcLayer->isImage() &&
|
||||
destLayer->isImage();
|
||||
}
|
||||
|
||||
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);
|
||||
if (!doc)
|
||||
return;
|
||||
|
||||
if (!layer) {
|
||||
doc->notifyGeneralUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -8,7 +8,7 @@
|
||||
#define APP_CMD_drop_on_timeline_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/cmd.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
#include "app/cmd/with_document.h"
|
||||
#include "app/doc_observer.h"
|
||||
#include "base/paths.h"
|
||||
@ -16,22 +16,28 @@
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_list.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
|
||||
class DropOnTimeline : public Cmd
|
||||
class DropOnTimeline : public CmdSequence
|
||||
, public WithDocument {
|
||||
public:
|
||||
enum class LayerInsertion {
|
||||
Before,
|
||||
After,
|
||||
enum class InsertionPoint {
|
||||
BeforeLayer,
|
||||
AfterLayer,
|
||||
};
|
||||
|
||||
enum class DroppedOn {
|
||||
Unspecified,
|
||||
Frame,
|
||||
Layer,
|
||||
Cel,
|
||||
};
|
||||
|
||||
// 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);
|
||||
InsertionPoint insert, DroppedOn droppedOn, const base::paths& paths);
|
||||
protected:
|
||||
void onExecute() override;
|
||||
void onUndo() override;
|
||||
@ -42,6 +48,7 @@ namespace cmd {
|
||||
|
||||
private:
|
||||
void setupInsertionLayers(doc::Layer** before, doc::Layer** after, doc::LayerGroup** group);
|
||||
bool canMoveCelFrom(app::Doc* srcDoc);
|
||||
void notifyAddLayer(doc::Layer* layer);
|
||||
void notifyDocObservers(doc::Layer* layer);
|
||||
|
||||
@ -49,8 +56,10 @@ namespace cmd {
|
||||
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.
|
||||
InsertionPoint m_insert;
|
||||
DroppedOn m_droppedOn;
|
||||
// Holds the list of layers dropped into the document. Used to support
|
||||
// undo/redo without having to read all the files again.
|
||||
doc::LayerList m_droppedLayers;
|
||||
// Number of frames the doc had before dropping.
|
||||
doc::frame_t m_previousTotalFrames;
|
||||
|
@ -4515,32 +4515,37 @@ void Timeline::onDrag(ui::DragEvent& e)
|
||||
|
||||
void Timeline::onDrop(ui::DragEvent& e)
|
||||
{
|
||||
using LayerInsertion = cmd::DropOnTimeline::LayerInsertion;
|
||||
using InsertionPoint = cmd::DropOnTimeline::InsertionPoint;
|
||||
using DroppedOn = cmd::DropOnTimeline::DroppedOn;
|
||||
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);
|
||||
LayerInsertion insert = LayerInsertion::Before;
|
||||
InsertionPoint insert = InsertionPoint::BeforeLayer;
|
||||
DroppedOn droppedOn = DroppedOn::Unspecified;
|
||||
switch(m_dropRange.type()) {
|
||||
case Range::kCels:
|
||||
frame = m_hot.frame;
|
||||
layerIndex = m_hot.layer;
|
||||
insert = (m_dropTarget.vhit == DropTarget::Top ? LayerInsertion::After
|
||||
: LayerInsertion::Before);
|
||||
droppedOn = DroppedOn::Cel;
|
||||
insert = (m_dropTarget.vhit == DropTarget::Top ? InsertionPoint::AfterLayer
|
||||
: InsertionPoint::BeforeLayer);
|
||||
break;
|
||||
case Range::kFrames:
|
||||
frame = m_dropRange.firstFrame();
|
||||
droppedOn = DroppedOn::Frame;
|
||||
if (m_dropTarget.hhit == DropTarget::After)
|
||||
frame++;
|
||||
break;
|
||||
case Range::kLayers:
|
||||
droppedOn = DroppedOn::Layer;
|
||||
if (m_dropTarget.vhit != DropTarget::VeryBottom) {
|
||||
auto* selectedLayer = *m_dropRange.selectedLayers().begin();
|
||||
layerIndex = getLayerIndex(selectedLayer);
|
||||
}
|
||||
insert = (m_dropTarget.vhit == DropTarget::Top ? LayerInsertion::After
|
||||
: LayerInsertion::Before);
|
||||
insert = (m_dropTarget.vhit == DropTarget::Top ? InsertionPoint::AfterLayer
|
||||
: InsertionPoint::BeforeLayer);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -4551,8 +4556,8 @@ void Timeline::onDrop(ui::DragEvent& e)
|
||||
if (e.hasPaths()) {
|
||||
base::paths paths = e.getPaths();
|
||||
execute_from_ui_thread([=]{
|
||||
Tx tx(m_document);
|
||||
tx(new cmd::DropOnTimeline(m_document, frame, layerIndex, insert, paths));
|
||||
Tx tx(m_document, "Drop on timeline");
|
||||
tx(new cmd::DropOnTimeline(m_document, frame, layerIndex, insert, droppedOn, paths));
|
||||
tx.commit();
|
||||
m_document->notifyGeneralUpdate();
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user