Generate selection boundaries automatically after transactions

Now Transaction::commit() will regenerate mask boundaries
automatically if in the middle of the transaction the document
selection was modified. This is the first step to finally remove
update_screen_for_document() and any kind of manual screen
refresh.

This will be useful for scripting functions that modify the selection
too, because we wouldn't need to regenerate the selection boundaries
automatically from the script or from app.refresh() Lua function.

Related to #378
This commit is contained in:
David Capello 2019-02-15 17:14:44 -03:00
parent 668b29193a
commit 7594ebf25b
28 changed files with 101 additions and 55 deletions

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -27,6 +28,7 @@ void DeselectMask::onExecute()
Doc* doc = document();
m_oldMask.reset(doc->isMaskVisible() ? new Mask(*doc->mask()): nullptr);
doc->setMaskVisible(false);
doc->notifySelectionChanged();
}
void DeselectMask::onUndo()
@ -35,6 +37,7 @@ void DeselectMask::onUndo()
doc->setMask(m_oldMask.get());
doc->setMaskVisible(true);
doc->notifySelectionChanged();
m_oldMask.reset();
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -37,8 +38,8 @@ void FlipMask::onUndo()
void FlipMask::swap()
{
Doc* document = this->document();
Mask* mask = document->mask();
Doc* doc = this->document();
Mask* mask = doc->mask();
ASSERT(mask->bitmap());
if (!mask->bitmap())
@ -48,6 +49,8 @@ void FlipMask::swap()
doc::algorithm::flip_image(mask->bitmap(),
mask->bitmap()->bounds(), m_flipType);
mask->unfreeze();
doc->notifySelectionChanged();
}
} // namespace cmd

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -32,6 +33,7 @@ void ReselectMask::onExecute()
}
doc->setMaskVisible(true);
doc->notifySelectionChanged();
}
void ReselectMask::onUndo()
@ -41,6 +43,7 @@ void ReselectMask::onUndo()
m_oldMask.reset(doc->isMaskVisible() ? new Mask(*doc->mask()): nullptr);
doc->setMaskVisible(false);
doc->notifySelectionChanged();
}
size_t ReselectMask::onMemSize() const

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -16,14 +17,14 @@
namespace app {
namespace cmd {
SetMask::SetMask(Doc* doc, Mask* newMask)
SetMask::SetMask(Doc* doc, const Mask* newMask)
: WithDocument(doc)
, m_oldMask(doc->isMaskVisible() ? new Mask(*doc->mask()): nullptr)
, m_newMask(newMask && !newMask->isEmpty() ? new Mask(*newMask): nullptr)
{
}
void SetMask::setNewMask(Mask* newMask)
void SetMask::setNewMask(const Mask* newMask)
{
m_newMask.reset(newMask ? new Mask(*newMask): nullptr);
setMask(m_newMask.get());
@ -46,7 +47,7 @@ size_t SetMask::onMemSize() const
(m_newMask ? m_newMask->getMemSize(): 0);
}
void SetMask::setMask(Mask* mask)
void SetMask::setMask(const Mask* mask)
{
Doc* doc = document();
@ -59,6 +60,8 @@ void SetMask::setMask(Mask* mask)
doc->setMask(&empty);
doc->setMaskVisible(false);
}
doc->notifySelectionChanged();
}
} // namespace cmd

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -25,10 +26,10 @@ namespace cmd {
class SetMask : public Cmd
, public WithDocument {
public:
SetMask(Doc* doc, Mask* newMask);
SetMask(Doc* doc, const Mask* newMask);
// Used to change the new mask used in the onRedo()
void setNewMask(Mask* newMask);
void setNewMask(const Mask* newMask);
protected:
void onExecute() override;
@ -36,7 +37,7 @@ namespace cmd {
size_t onMemSize() const override;
private:
void setMask(Mask* mask);
void setMask(const Mask* mask);
std::unique_ptr<Mask> m_oldMask;
std::unique_ptr<Mask> m_newMask;

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -38,6 +39,8 @@ void SetMaskPosition::setMaskPosition(const gfx::Point& pos)
Doc* doc = document();
doc->mask()->setOrigin(pos.x, pos.y);
doc->resetTransformation();
doc->notifySelectionChanged();
}
} // namespace cmd

View File

@ -352,8 +352,6 @@ void CanvasSizeCommand::onExecute(Context* context)
MID(1, y2-y1, DOC_SPRITE_MAX_HEIGHT)));
tx.commit();
doc->generateMaskBoundaries();
#ifdef ENABLE_UI
if (context->isUIAvailable())
update_screen_for_document(doc);

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -74,7 +75,6 @@ void CropSpriteCommand::onExecute(Context* context)
document->getApi(tx).cropSprite(sprite, bounds);
tx.commit();
}
document->generateMaskBoundaries();
#ifdef ENABLE_UI
if (context->isUIAvailable())
@ -112,7 +112,6 @@ void AutocropSpriteCommand::onExecute(Context* context)
document->getApi(tx).trimSprite(sprite);
tx.commit();
}
document->generateMaskBoundaries();
#ifdef ENABLE_UI
if (context->isUIAvailable())

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -47,7 +48,6 @@ void DeselectMaskCommand::onExecute(Context* context)
tx(new cmd::DeselectMask(document));
tx.commit();
}
document->generateMaskBoundaries();
update_screen_for_document(document);
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -222,8 +223,6 @@ void FlipCommand::onExecute(Context* context)
(m_flipType == doc::algorithm::FlipVertical ?
sprite->height() - mask->bounds().y2():
mask->bounds().y))));
document->generateMaskBoundaries();
}
tx.commit();

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -96,7 +97,6 @@ void InvertMaskCommand::onExecute(Context* context)
tx(new cmd::SetMask(document, mask.get()));
tx.commit();
document->generateMaskBoundaries();
update_screen_for_document(document);
}
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -80,7 +81,6 @@ void LoadMaskCommand::onExecute(Context* context)
tx(new cmd::SetMask(document, mask.get()));
tx.commit();
document->generateMaskBoundaries();
update_screen_for_document(document);
}
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -50,10 +51,8 @@ void MaskAllCommand::onExecute(Context* context)
Tx tx(writer.context(), "Select All", DoesntModifyDocument);
tx(new cmd::SetMask(document, &newMask));
tx.commit();
document->resetTransformation();
document->generateMaskBoundaries();
tx.commit();
if (Preferences::instance().selection.autoShowSelectionEdges()) {
DocumentPreferences& docPref = Preferences::instance().document(document);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -189,9 +189,11 @@ void MaskByColorCommand::onExecute(Context* context)
set_config_int("MaskColor", "Tolerance", m_sliderTolerance->getValue());
set_config_bool("MaskColor", "Preview", m_checkPreview->isSelected());
}
else {
document->generateMaskBoundaries();
}
// Update boundaries and editors.
document->generateMaskBoundaries();
update_screen_for_document(document);
// Save window configuration.

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -83,10 +84,8 @@ void MaskContentCommand::onExecute(Context* context)
Tx tx(writer.context(), "Select Content", DoesntModifyDocument);
tx(new cmd::SetMask(document, &newMask));
tx.commit();
document->resetTransformation();
document->generateMaskBoundaries();
tx.commit();
}
// Select marquee tool

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
//
// This program is distributed under the terms of
@ -143,7 +144,6 @@ void ModifySelectionCommand::onExecute(Context* context)
tx(new cmd::SetMask(document, mask.get()));
tx.commit();
document->generateMaskBoundaries();
update_screen_for_document(document);
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -88,7 +89,6 @@ void MoveMaskCommand::onExecute(Context* context)
tx.commit();
}
document->generateMaskBoundaries();
update_screen_for_document(document);
break;
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -53,7 +54,6 @@ void ReselectMaskCommand::onExecute(Context* context)
tx.commit();
}
document->generateMaskBoundaries();
update_screen_for_document(document);
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -243,7 +244,6 @@ void RotateCommand::onExecute(Context* context)
job.startJob();
job.waitJob();
}
reader.document()->generateMaskBoundaries();
update_screen_for_document(reader.document());
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
//
// This program is distributed under the terms of
@ -109,7 +109,6 @@ void SelectTileCommand::onExecute(Context* ctx)
tx(new cmd::SetMask(doc, mask.get()));
tx.commit();
doc->generateMaskBoundaries();
update_screen_for_document(doc);
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -180,6 +180,12 @@ void Doc::notifySelectionChanged()
notify_observers<DocEvent&>(&DocObserver::onSelectionChanged, ev);
}
void Doc::notifySelectionBoundariesChanged()
{
DocEvent ev(this);
notify_observers<DocEvent&>(&DocObserver::onSelectionBoundariesChanged, ev);
}
bool Doc::isModified() const
{
return !m_undo->isSavedState();
@ -252,8 +258,7 @@ void Doc::generateMaskBoundaries(const Mask* mask)
mask->bounds().y);
}
// TODO move this to the exact place where selection is modified.
notifySelectionChanged();
notifySelectionBoundariesChanged();
}
//////////////////////////////////////////////////////////////////////
@ -261,7 +266,9 @@ void Doc::generateMaskBoundaries(const Mask* mask)
void Doc::setMask(const Mask* mask)
{
m_mask.reset(new Mask(*mask));
ASSERT(mask);
m_mask->copyFrom(mask);
m_flags |= kMaskVisible;
resetTransformation();
@ -271,7 +278,6 @@ bool Doc::isMaskVisible() const
{
return
(m_flags & kMaskVisible) && // The mask was not hidden by the user explicitly
m_mask && // The mask does exist
!m_mask->isEmpty(); // The mask is not empty
}
@ -298,10 +304,7 @@ void Doc::setTransformation(const Transformation& transform)
void Doc::resetTransformation()
{
if (m_mask)
m_transformation = Transformation(gfx::RectF(m_mask->bounds()));
else
m_transformation = Transformation();
m_transformation = Transformation(gfx::RectF(m_mask->bounds()));
}
//////////////////////////////////////////////////////////////////////

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -96,6 +96,7 @@ namespace app {
void notifyCelMoved(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame);
void notifyCelCopied(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame);
void notifySelectionChanged();
void notifySelectionBoundariesChanged();
//////////////////////////////////////////////////////////////////////
// File related properties
@ -211,7 +212,7 @@ namespace app {
ExtraCelRef m_extraCel;
// Current mask.
std::unique_ptr<Mask> m_mask;
std::unique_ptr<doc::Mask> m_mask;
// Current transformation.
Transformation m_transformation;

View File

@ -68,6 +68,7 @@ namespace app {
// The selection has changed.
virtual void onSelectionChanged(DocEvent& ev) { }
virtual void onSelectionBoundariesChanged(DocEvent& ev) { }
// Called to destroy the observable. (Here you could call "delete this".)
virtual void dispose() { }

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -16,6 +16,7 @@
#include "app/doc.h"
#include "app/doc_undo.h"
#include "doc/sprite.h"
#include "ui/system.h"
#define TX_TRACE(...)
@ -25,17 +26,22 @@ using namespace doc;
Transaction::Transaction(Context* ctx, const std::string& label, Modification modification)
: m_ctx(ctx)
, m_cmds(NULL)
, m_doc(nullptr)
, m_undo(nullptr)
, m_cmds(nullptr)
, m_changes(Changes::kNone)
{
TX_TRACE("TX: Start <%s> (%s)\n",
label.c_str(),
modification == ModifyDocument ? "modifies document":
"doesn't modify document");
Doc* doc = m_ctx->activeDocument();
if (!doc)
m_doc = m_ctx->activeDocument();
if (!m_doc)
throw std::runtime_error("No active document to execute a transaction");
m_undo = doc->undoHistory();
m_doc->add_observer(this);
m_undo = m_doc->undoHistory();
m_cmds = new CmdTransaction(label,
modification == Modification::ModifyDocument,
@ -60,6 +66,8 @@ Transaction::~Transaction()
// TODO logging error
}
m_doc->remove_observer(this);
}
// Used to set the document range after all the transaction is
@ -73,12 +81,17 @@ void Transaction::setNewDocRange(const DocRange& range)
void Transaction::commit()
{
ui::assert_ui_thread();
ASSERT(m_cmds);
TX_TRACE("TX: Commit <%s>\n", m_cmds->label().c_str());
m_cmds->commit();
m_undo->add(m_cmds);
m_cmds = NULL;
m_cmds = nullptr;
// Process changes
if (int(m_changes) & int(Changes::kSelection))
m_doc->generateMaskBoundaries();
}
void Transaction::rollback()
@ -89,7 +102,7 @@ void Transaction::rollback()
m_cmds->undo();
delete m_cmds;
m_cmds = NULL;
m_cmds = nullptr;
}
void Transaction::execute(Cmd* cmd)
@ -112,4 +125,9 @@ void Transaction::execute(Cmd* cmd)
}
}
void Transaction::onSelectionChanged(DocEvent& ev)
{
m_changes = Changes(int(m_changes) | int(Changes::kSelection));
}
} // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -8,6 +9,8 @@
#define APP_TRANSACTION_H_INCLUDED
#pragma once
#include "app/doc_observer.h"
#include <string>
namespace app {
@ -38,7 +41,7 @@ namespace app {
// transaction.commit();
// }
//
class Transaction {
class Transaction : public DocObserver {
public:
// Starts a undoable sequence of operations in a transaction that
// can be committed or rollbacked. All the operations will be
@ -67,11 +70,20 @@ namespace app {
void execute(Cmd* cmd);
private:
// List of changes during the execution of this transaction
enum class Changes { kNone = 0,
kSelection = 1 };
void rollback();
// DocObserver impl
void onSelectionChanged(DocEvent& ev) override;
Context* m_ctx;
Doc* m_doc;
DocUndo* m_undo;
CmdTransaction* m_cmds;
Changes m_changes;
};
} // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -463,7 +464,6 @@ public:
}
// Selection ink
else if (getInk()->isSelection()) {
m_document->generateMaskBoundaries();
redraw = true;
// Show selection edges

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -1784,7 +1784,7 @@ void Timeline::onRemoveFrame(DocEvent& ev)
invalidate();
}
void Timeline::onSelectionChanged(DocEvent& ev)
void Timeline::onSelectionBoundariesChanged(DocEvent& ev)
{
if (m_rangeLocks == 0)
clearAndInvalidateRange();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -144,7 +144,7 @@ namespace app {
void onAfterRemoveLayer(DocEvent& ev) override;
void onAddFrame(DocEvent& ev) override;
void onRemoveFrame(DocEvent& ev) override;
void onSelectionChanged(DocEvent& ev) override;
void onSelectionBoundariesChanged(DocEvent& ev) override;
void onLayerNameChange(DocEvent& ev) override;
void onAddFrameTag(DocEvent& ev) override;
void onRemoveFrameTag(DocEvent& ev) override;