Restore selected Timeline range after undo/redo

This commit is contained in:
David Capello 2018-06-08 14:40:02 -03:00
parent db097d3e67
commit b8445956ae
16 changed files with 316 additions and 28 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -13,19 +13,61 @@
#include "app/context.h"
#include "doc/site.h"
#ifdef ENABLE_UI
#include "app/app.h"
#include "app/ui/timeline/timeline.h"
#endif
namespace app {
CmdTransaction::CmdTransaction(const std::string& label,
bool changeSavedState, int* savedCounter)
: m_label(label)
bool changeSavedState,
int* savedCounter)
: m_ranges(nullptr)
, m_label(label)
, m_changeSavedState(changeSavedState)
, m_savedCounter(savedCounter)
{
}
void CmdTransaction::setNewDocumentRange(const DocumentRange& range)
{
#ifdef ENABLE_UI
if (m_ranges)
range.write(m_ranges->m_after);
#endif
}
void CmdTransaction::commit()
{
m_spritePositionAfter = calcSpritePosition();
// We cannot capture m_ranges->m_after from the Timeline here
// because the document range in the Timeline is updated after the
// commit/command (on Timeline::onAfterCommandExecution).
//
// So m_ranges->m_after is captured explicitly in
// setNewDocumentRange().
}
std::istream* CmdTransaction::documentRangeBeforeExecute() const
{
if (m_ranges && m_ranges->m_before.tellp() > 0) {
m_ranges->m_before.seekg(0);
return &m_ranges->m_before;
}
else
return nullptr;
}
std::istream* CmdTransaction::documentRangeAfterExecute() const
{
if (m_ranges && m_ranges->m_after.tellp() > 0) {
m_ranges->m_after.seekg(0);
return &m_ranges->m_after;
}
else
return nullptr;
}
void CmdTransaction::onExecute()
@ -35,6 +77,12 @@ void CmdTransaction::onExecute()
// The execution of CmdTransaction is called by Transaction at the
// very beginning, just to save the current sprite position.
m_spritePositionBefore = calcSpritePosition();
#ifdef ENABLE_UI
if (isDocumentRangeEnabled()) {
m_ranges.reset(new Ranges);
calcDocumentRange().write(m_ranges->m_before);
}
#endif
if (m_changeSavedState)
++(*m_savedCounter);
@ -61,10 +109,47 @@ std::string CmdTransaction::onLabel() const
return m_label;
}
doc::SpritePosition CmdTransaction::calcSpritePosition()
size_t CmdTransaction::onMemSize() const
{
size_t size = CmdSequence::onMemSize();
if (m_ranges) {
size += (m_ranges->m_before.tellp() +
m_ranges->m_after.tellp());
}
return size;
}
doc::SpritePosition CmdTransaction::calcSpritePosition() const
{
doc::Site site = context()->activeSite();
return doc::SpritePosition(site.layer(), site.frame());
}
bool CmdTransaction::isDocumentRangeEnabled() const
{
#ifdef ENABLE_UI
if (App::instance()) {
Timeline* timeline = App::instance()->timeline();
if (timeline && timeline->range().enabled())
return true;
}
#endif
return false;
}
DocumentRange CmdTransaction::calcDocumentRange() const
{
#ifdef ENABLE_UI
// TODO We cannot use Context::activeSite() because it losts
// important information about the DocumentRange() (type and
// flags).
if (App::instance()) {
Timeline* timeline = App::instance()->timeline();
if (timeline)
return timeline->range();
}
#endif
return DocumentRange();
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -9,6 +9,10 @@
#pragma once
#include "app/cmd_sequence.h"
#include "app/document_range.h"
#include <memory>
#include <sstream>
namespace app {
@ -19,22 +23,35 @@ namespace app {
CmdTransaction(const std::string& label,
bool changeSavedState, int* savedCounter);
void setNewDocumentRange(const DocumentRange& range);
void commit();
doc::SpritePosition spritePositionBeforeExecute() const { return m_spritePositionBefore; }
doc::SpritePosition spritePositionAfterExecute() const { return m_spritePositionAfter; }
std::istream* documentRangeBeforeExecute() const;
std::istream* documentRangeAfterExecute() const;
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
std::string onLabel() const override;
size_t onMemSize() const override;
private:
doc::SpritePosition calcSpritePosition();
doc::SpritePosition calcSpritePosition() const;
bool isDocumentRangeEnabled() const;
DocumentRange calcDocumentRange() const;
struct Ranges {
std::stringstream m_before;
std::stringstream m_after;
};
doc::SpritePosition m_spritePositionBefore;
doc::SpritePosition m_spritePositionAfter;
std::unique_ptr<Ranges> m_ranges;
std::string m_label;
bool m_changeSavedState;
int* m_savedCounter;

View File

@ -24,6 +24,10 @@
#include "ui/manager.h"
#include "ui/system.h"
#ifdef ENABLE_UI
#include "app/ui/timeline/timeline.h"
#endif
namespace app {
class UndoCommand : public Command {
@ -93,13 +97,23 @@ void UndoCommand::onExecute(Context* context)
}
}
// Get the stream to deserialize the document range after executing
// the undo/redo action. We cannot yet deserialize the document
// range because there could be inexistent layers.
std::istream* docRangeStream;
if (m_type == Undo)
docRangeStream = undo->nextUndoDocumentRange();
else
docRangeStream = undo->nextRedoDocumentRange();
StatusBar* statusbar = StatusBar::instance();
if (statusbar)
if (statusbar) {
statusbar->showTip(1000, "%s %s",
(m_type == Undo ? "Undid": "Redid"),
(m_type == Undo ?
undo->nextUndoLabel().c_str():
undo->nextRedoLabel().c_str()));
}
#endif // ENABLE_UI
// Effectively undo/redo.
@ -124,6 +138,18 @@ void UndoCommand::onExecute(Context* context)
current_editor->setFrame(spritePosition.frame());
}
}
// Update timeline range. We've to deserialize the DocumentRange at
// this point when objects (possible layers) are re-created after
// the undo and we can deserialize them.
if (docRangeStream) {
Timeline* timeline = App::instance()->timeline();
if (timeline) {
DocumentRange docRange;
if (docRange.read(*docRangeStream))
timeline->setRange(docRange);
}
}
#endif // ENABLE_UI
document->generateMaskBoundaries();

View File

@ -10,13 +10,18 @@
#include "app/document_range.h"
#include "base/serialization.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include <iostream>
namespace app {
using namespace base::serialization;
using namespace base::serialization::little_endian;
using namespace doc;
DocumentRange::DocumentRange()
@ -154,6 +159,35 @@ bool DocumentRange::convertToCels(const Sprite* sprite)
return true;
}
bool DocumentRange::write(std::ostream& os) const
{
write32(os, m_type);
write32(os, m_flags);
if (!m_selectedLayers.write(os)) return false;
if (!m_selectedFrames.write(os)) return false;
write32(os, m_selectingFromLayer ? m_selectingFromLayer->id(): 0);
write32(os, m_selectingFromFrame);
return os.good();
}
bool DocumentRange::read(std::istream& is)
{
clearRange();
m_type = (Type)read32(is);
m_flags = read32(is);
if (!m_selectedLayers.read(is)) return false;
if (!m_selectedFrames.read(is)) return false;
ObjectId id = read32(is);
m_selectingFromLayer = doc::get<Layer>(id);
m_selectingFromFrame = read32(is);
return is.good();
}
void DocumentRange::selectLayerRange(Layer* fromLayer, Layer* toLayer)
{
ASSERT(fromLayer);

View File

@ -12,6 +12,8 @@
#include "doc/selected_frames.h"
#include "doc/selected_layers.h"
#include <iosfwd>
namespace doc {
class Cel;
class Sprite;
@ -64,6 +66,9 @@ namespace app {
bool convertToCels(const Sprite* sprite);
bool write(std::ostream& os) const;
bool read(std::istream& is);
private:
void selectLayerRange(Layer* fromLayer, Layer* toLayer);
void selectFrameRange(frame_t fromFrame, frame_t toFrame);

View File

@ -13,10 +13,12 @@
#include "app/document_range_ops.h"
#include "app/app.h"
#include "app/context_access.h"
#include "app/document_api.h"
#include "app/document_range.h"
#include "app/transaction.h"
#include "app/ui/timeline/timeline.h"
#include "doc/layer.h"
#include "doc/sprite.h"
@ -30,13 +32,12 @@ namespace app {
enum Op { Move, Copy };
template<typename T>
static void move_or_copy_cels(
DocumentApi& api, Op op,
LayerList& srcLayers,
LayerList& dstLayers,
T& srcFrames,
T& dstFrames)
const LayerList& srcLayers,
const LayerList& dstLayers,
const SelectedFrames& srcFrames,
const SelectedFrames& dstFrames)
{
ASSERT(srcLayers.size() == dstLayers.size());
@ -74,13 +75,14 @@ static void move_or_copy_cels(
}
}
template<typename T>
static DocumentRange move_or_copy_frames(
DocumentApi& api, Op op,
Sprite* sprite,
T& srcFrames,
const DocumentRange& srcRange,
frame_t dstFrame)
{
const SelectedFrames& srcFrames = srcRange.selectedFrames();
#ifdef TRACE_RANGE_OPS
std::clog << "move_or_copy_frames frames[";
for (auto srcFrame : srcFrames) {
@ -161,6 +163,8 @@ static DocumentRange move_or_copy_frames(
}
DocumentRange result;
if (!srcRange.selectedLayers().empty())
result.selectLayers(srcRange.selectedLayers());
result.startRange(nullptr, dstFrame-srcFrames.size(), DocumentRange::kFrames);
result.endRange(nullptr, dstFrame-1);
return result;
@ -327,8 +331,8 @@ static DocumentRange drop_range_op(
}
if (from.firstFrame() < to.firstFrame()) {
auto srcFrames = from.selectedFrames().reversed();
auto dstFrames = to.selectedFrames().reversed();
auto srcFrames = from.selectedFrames().makeReverse();
auto dstFrames = to.selectedFrames().makeReverse();
move_or_copy_cels(api, op, srcLayers, dstLayers, srcFrames, dstFrames);
}
@ -350,9 +354,7 @@ static DocumentRange drop_range_op(
else
dstFrame = to.lastFrame()+1;
resultRange =
move_or_copy_frames(api, op, sprite,
from.selectedFrames(), dstFrame);
resultRange = move_or_copy_frames(api, op, sprite, from, dstFrame);
break;
}
@ -423,6 +425,9 @@ static DocumentRange drop_range_op(
}
}
if (resultRange.type() != DocumentRange::kNone)
transaction.setNewDocumentRange(resultRange);
transaction.commit();
}
@ -498,6 +503,7 @@ void reverse_frames(Document* doc, const DocumentRange& range)
}
}
transaction.setNewDocumentRange(range);
transaction.commit();
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -187,6 +187,24 @@ SpritePosition DocumentUndo::nextRedoSpritePosition() const
return SpritePosition();
}
std::istream* DocumentUndo::nextUndoDocumentRange() const
{
const undo::UndoState* state = nextUndo();
if (state)
return STATE_CMD(state)->documentRangeBeforeExecute();
else
return nullptr;
}
std::istream* DocumentUndo::nextRedoDocumentRange() const
{
const undo::UndoState* state = nextRedo();
if (state)
return STATE_CMD(state)->documentRangeAfterExecute();
else
return nullptr;
}
Cmd* DocumentUndo::lastExecutedCmd() const
{
const undo::UndoState* state = m_undoHistory.currentState();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -8,12 +8,14 @@
#define APP_DOCUMENT_UNDO_H_INCLUDED
#pragma once
#include "app/document_range.h"
#include "base/disable_copying.h"
#include "base/unique_ptr.h"
#include "doc/sprite_position.h"
#include "obs/observable.h"
#include "undo/undo_history.h"
#include <iosfwd>
#include <string>
namespace doc {
@ -54,6 +56,8 @@ namespace app {
SpritePosition nextUndoSpritePosition() const;
SpritePosition nextRedoSpritePosition() const;
std::istream* nextUndoDocumentRange() const;
std::istream* nextRedoDocumentRange() const;
Cmd* lastExecutedCmd() const;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -50,6 +50,15 @@ Transaction::~Transaction()
}
}
// Used to set the document range after all the transaction is
// executed and before the commit. This range is stored in
// CmdTransaction to recover it on Edit > Redo.
void Transaction::setNewDocumentRange(const DocumentRange& range)
{
ASSERT(m_cmds);
m_cmds->setNewDocumentRange(range);
}
void Transaction::commit()
{
ASSERT(m_cmds);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -15,6 +15,7 @@ namespace app {
class Cmd;
class CmdTransaction;
class Context;
class DocumentRange;
class DocumentUndo;
enum Modification {
@ -45,6 +46,11 @@ namespace app {
Transaction(Context* ctx, const std::string& label, Modification mod = ModifyDocument);
virtual ~Transaction();
// Can be used to change the new document range resulting from
// executing this transaction. This range can be used then in
// undo/redo operations to restore the Timeline selection/range.
void setNewDocumentRange(const DocumentRange& range);
// This must be called to commit all the changes, so the undo will
// be finally added in the sprite.
//

View File

@ -503,7 +503,7 @@ void Timeline::prepareToMoveRange()
m_moveRangeData.activeRelativeFrame = j;
}
void Timeline::moveRange(Range& range)
void Timeline::moveRange(const Range& range)
{
regenerateRows();
@ -535,6 +535,12 @@ void Timeline::moveRange(Range& range)
m_range = range;
}
void Timeline::setRange(const Range& range)
{
m_range = range;
invalidate();
}
void Timeline::activateClipboardRange()
{
m_clipboard_timer.start();

View File

@ -93,7 +93,8 @@ namespace app {
const SelectedFrames& selectedFrames() const { return m_range.selectedFrames(); }
void prepareToMoveRange();
void moveRange(Range& range);
void moveRange(const Range& range);
void setRange(const Range& range);
void activateClipboardRange();

View File

@ -12,11 +12,16 @@
#include "base/base.h"
#include "base/debug.h"
#include "base/serialization.h"
#include <algorithm>
#include <iostream>
namespace doc {
using namespace base::serialization;
using namespace base::serialization::little_endian;
std::size_t SelectedFrames::size() const
{
std::size_t size = 0;
@ -172,4 +177,25 @@ SelectedFrames SelectedFrames::makePingPong() const
return newFrames;
}
bool SelectedFrames::write(std::ostream& os) const
{
write32(os, size());
for (const frame_t frame : *this) {
write32(os, frame);
}
return os.good();
}
bool SelectedFrames::read(std::istream& is)
{
clear();
int nframes = read32(is);
for (int i=0; i<nframes && is; ++i) {
frame_t frame = read32(is);
insert(frame);
}
return is.good();
}
} // namespace doc

View File

@ -10,6 +10,7 @@
#include "doc/frame_range.h"
#include <iosfwd>
#include <iterator>
#include <vector>
@ -156,6 +157,9 @@ namespace doc {
return !operator==(o);
}
bool write(std::ostream& os) const;
bool read(std::istream& is);
private:
Ranges m_ranges;
};

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2016 David Capello
// Copyright (c) 2016, 2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -12,11 +12,17 @@
#include "base/base.h"
#include "base/debug.h"
#include "base/serialization.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include <iostream>
namespace doc {
using namespace base::serialization;
using namespace base::serialization::little_endian;
void SelectedLayers::clear()
{
m_set.clear();
@ -184,4 +190,34 @@ void SelectedLayers::propagateSelection()
insert(layer);
}
bool SelectedLayers::write(std::ostream& os) const
{
write32(os, size());
for (const Layer* layer : *this)
write32(os, layer->id());
return os.good();
}
bool SelectedLayers::read(std::istream& is)
{
clear();
int nlayers = read32(is);
for (int i=0; i<nlayers && is; ++i) {
ObjectId id = read32(is);
Layer* layer = doc::get<Layer>(id);
// Check that the layer does exist. You will see a little trick in
// UndoCommand::onExecute() deserializing the DocumentRange stream
// after the undo/redo is executed so layers exist at this point.
// TODO This should be an assert, but there is a bug that make this fail
//ASSERT(layer);
if (layer)
insert(layer);
}
return is.good();
}
} // namespace doc

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2016-2017 David Capello
// Copyright (c) 2016-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -8,9 +8,11 @@
#define DOC_SELECTED_LAYERS_H_INCLUDED
#pragma once
#include <set>
#include "layer_list.h"
#include <iosfwd>
#include <set>
namespace doc {
class Layer;
@ -52,6 +54,9 @@ namespace doc {
return !operator==(o);
}
bool write(std::ostream& os) const;
bool read(std::istream& is);
private:
Set m_set;
};