mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-17 13:20:45 +00:00
Requested in twitter, the forum, and in several other places and frequently over all these years: https://community.aseprite.org/t/scaling-multiple-frames-simultaneously/240
This commit is contained in:
parent
2d979c521c
commit
3e478d3efa
@ -31,6 +31,21 @@ CmdTransaction::CmdTransaction(const std::string& label,
|
||||
{
|
||||
}
|
||||
|
||||
CmdTransaction* CmdTransaction::moveToEmptyCopy()
|
||||
{
|
||||
CmdTransaction* copy = new CmdTransaction(m_label,
|
||||
m_changeSavedState,
|
||||
m_savedCounter);
|
||||
copy->m_spritePositionBefore = m_spritePositionBefore;
|
||||
copy->m_spritePositionAfter = m_spritePositionAfter;
|
||||
if (m_ranges) {
|
||||
copy->m_ranges.reset(new Ranges);
|
||||
copy->m_ranges->m_before = std::move(m_ranges->m_before);
|
||||
copy->m_ranges->m_after = std::move(m_ranges->m_after);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
void CmdTransaction::setNewDocRange(const DocRange& range)
|
||||
{
|
||||
#ifdef ENABLE_UI
|
||||
|
@ -25,6 +25,11 @@ namespace app {
|
||||
CmdTransaction(const std::string& label,
|
||||
bool changeSavedState, int* savedCounter);
|
||||
|
||||
// Moves the CmdTransaction internals to a new copy in case that
|
||||
// we want to rollback this CmdTransaction and start again with
|
||||
// the new CmdTransaction.
|
||||
CmdTransaction* moveToEmptyCopy();
|
||||
|
||||
void setNewDocRange(const DocRange& range);
|
||||
void updateSpritePositionAfter();
|
||||
|
||||
|
@ -76,6 +76,19 @@ void FlipCommand::onExecute(Context* context)
|
||||
|
||||
CelList cels;
|
||||
if (m_flipMask) {
|
||||
// If we want to flip the visible mask we can go to
|
||||
// MovingPixelsState (even when the range is enabled, because now
|
||||
// PixelsMovement support ranges).
|
||||
if (site.document()->isMaskVisible()) {
|
||||
// Select marquee tool
|
||||
if (tools::Tool* tool = App::instance()->toolBox()
|
||||
->getToolById(tools::WellKnownTools::RectangularMarquee)) {
|
||||
ToolBar::instance()->selectTool(tool);
|
||||
current_editor->startFlipTransformation(m_flipType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto range = timeline->range();
|
||||
if (range.enabled()) {
|
||||
cels = get_unlocked_unique_cels(site.sprite(), range);
|
||||
@ -83,17 +96,6 @@ void FlipCommand::onExecute(Context* context)
|
||||
else if (site.cel() &&
|
||||
site.layer() &&
|
||||
site.layer()->isEditable()) {
|
||||
// If we want to flip the visible mask for the current cel,
|
||||
// we can go to MovingPixelsState.
|
||||
if (site.document()->isMaskVisible()) {
|
||||
// Select marquee tool
|
||||
if (tools::Tool* tool = App::instance()->toolBox()
|
||||
->getToolById(tools::WellKnownTools::RectangularMarquee)) {
|
||||
ToolBar::instance()->selectTool(tool);
|
||||
current_editor->startFlipTransformation(m_flipType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
cels.push_back(site.cel());
|
||||
}
|
||||
|
||||
|
@ -203,24 +203,25 @@ void RotateCommand::onExecute(Context* context)
|
||||
|
||||
// Flip the mask or current cel
|
||||
if (m_flipMask) {
|
||||
// If we want to rotate the visible mask, we can go to
|
||||
// MovingPixelsState (even when the range is enabled, because
|
||||
// now PixelsMovement support ranges).
|
||||
if (site.document()->isMaskVisible()) {
|
||||
// Select marquee tool
|
||||
if (tools::Tool* tool = App::instance()->toolBox()
|
||||
->getToolById(tools::WellKnownTools::RectangularMarquee)) {
|
||||
ToolBar::instance()->selectTool(tool);
|
||||
current_editor->startSelectionTransformation(gfx::Point(0, 0), m_angle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto range = App::instance()->timeline()->range();
|
||||
if (range.enabled())
|
||||
cels = get_unlocked_unique_cels(site.sprite(), range);
|
||||
else if (site.cel() &&
|
||||
site.layer() &&
|
||||
site.layer()->isEditable()) {
|
||||
// If we want to rotate the visible mask for the current cel,
|
||||
// we can go to MovingPixelsState.
|
||||
if (site.document()->isMaskVisible()) {
|
||||
// Select marquee tool
|
||||
if (tools::Tool* tool = App::instance()->toolBox()
|
||||
->getToolById(tools::WellKnownTools::RectangularMarquee)) {
|
||||
ToolBar::instance()->selectTool(tool);
|
||||
current_editor->startSelectionTransformation(gfx::Point(0, 0), m_angle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cels.push_back(site.cel());
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ Transaction::~Transaction()
|
||||
try {
|
||||
// If it isn't committed, we have to rollback all changes.
|
||||
if (m_cmds)
|
||||
rollback();
|
||||
rollback(nullptr);
|
||||
}
|
||||
catch (...) {
|
||||
// Just avoid throwing an exception in the dtor (just in case
|
||||
@ -96,7 +96,14 @@ void Transaction::commit()
|
||||
m_doc->generateMaskBoundaries();
|
||||
}
|
||||
|
||||
void Transaction::rollback()
|
||||
void Transaction::rollbackAndStartAgain()
|
||||
{
|
||||
auto newCmds = m_cmds->moveToEmptyCopy();
|
||||
rollback(newCmds);
|
||||
newCmds->execute(m_ctx);
|
||||
}
|
||||
|
||||
void Transaction::rollback(CmdTransaction* newCmds)
|
||||
{
|
||||
ASSERT(m_cmds);
|
||||
TX_TRACE("TX: Rollback <%s>\n", m_cmds->label().c_str());
|
||||
@ -104,7 +111,7 @@ void Transaction::rollback()
|
||||
m_cmds->undo();
|
||||
|
||||
delete m_cmds;
|
||||
m_cmds = nullptr;
|
||||
m_cmds = newCmds;
|
||||
}
|
||||
|
||||
void Transaction::execute(Cmd* cmd)
|
||||
|
@ -67,6 +67,10 @@ namespace app {
|
||||
// updates the Undo History window UI.
|
||||
void commit();
|
||||
|
||||
// Discard everything that was added so far. We can start
|
||||
// executing new Cmds again.
|
||||
void rollbackAndStartAgain();
|
||||
|
||||
void execute(Cmd* cmd);
|
||||
|
||||
private:
|
||||
@ -74,7 +78,7 @@ namespace app {
|
||||
enum class Changes { kNone = 0,
|
||||
kSelection = 1 };
|
||||
|
||||
void rollback();
|
||||
void rollback(CmdTransaction* newCmds);
|
||||
|
||||
// DocObserver impl
|
||||
void onSelectionChanged(DocEvent& ev) override;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -47,6 +48,10 @@ namespace app {
|
||||
m_transaction->commit();
|
||||
}
|
||||
|
||||
void rollbackAndStartAgain() {
|
||||
m_transaction->rollbackAndStartAgain();
|
||||
}
|
||||
|
||||
void operator()(Cmd* cmd) {
|
||||
m_transaction->execute(cmd);
|
||||
}
|
||||
|
@ -19,8 +19,8 @@
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc_event.h"
|
||||
#include "app/doc_access.h"
|
||||
#include "app/doc_event.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/modules/editors.h"
|
||||
#include "app/modules/palettes.h"
|
||||
@ -36,6 +36,7 @@
|
||||
#include "app/ui/workspace.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/range_utils.h"
|
||||
#include "base/fs.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/sprite.h"
|
||||
@ -572,24 +573,29 @@ bool DocView::onClear(Context* ctx)
|
||||
|
||||
// In other case we delete the mask or the cel.
|
||||
ContextWriter writer(ctx);
|
||||
Doc* document = writer.document();
|
||||
Doc* document = site.document();
|
||||
bool visibleMask = document->isMaskVisible();
|
||||
|
||||
if (!writer.cel())
|
||||
CelList cels;
|
||||
if (site.range().enabled()) {
|
||||
cels = get_unlocked_unique_cels(site.sprite(), site.range());
|
||||
}
|
||||
else if (site.cel()) {
|
||||
cels.push_back(site.cel());
|
||||
}
|
||||
|
||||
if (cels.empty()) // No cels to modify
|
||||
return false;
|
||||
|
||||
{
|
||||
Tx tx(writer.context(), "Clear");
|
||||
tx(new cmd::ClearMask(writer.cel()));
|
||||
const bool deselectMask =
|
||||
(visibleMask &&
|
||||
!Preferences::instance().selection.keepSelectionAfterClear());
|
||||
|
||||
// If the cel wasn't deleted by cmd::ClearMask, we trim it.
|
||||
if (writer.cel() &&
|
||||
writer.cel()->layer()->isTransparent())
|
||||
tx(new cmd::TrimCel(writer.cel()));
|
||||
|
||||
if (visibleMask &&
|
||||
!Preferences::instance().selection.keepSelectionAfterClear())
|
||||
tx(new cmd::DeselectMask(document));
|
||||
clipboard::clear_mask_from_cels(
|
||||
tx, document, cels,
|
||||
deselectMask);
|
||||
|
||||
tx.commit();
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "app/ui/main_window.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/ui/timeline/timeline.h"
|
||||
#include "app/ui/toolbar.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "base/bind.h"
|
||||
@ -384,6 +385,13 @@ void Editor::getSite(Site* site) const
|
||||
getCurrentEditorInk()->isSlice()) {
|
||||
site->selectedSlices(m_selectedSlices);
|
||||
}
|
||||
|
||||
// TODO we should not access timeline directly here
|
||||
Timeline* timeline = App::instance()->timeline();
|
||||
if (timeline &&
|
||||
timeline->range().enabled()) {
|
||||
site->range(timeline->range());
|
||||
}
|
||||
}
|
||||
|
||||
Site Editor::getSite() const
|
||||
@ -2182,6 +2190,15 @@ bool Editor::canStartMovingSelectionPixels()
|
||||
int(m_customizationDelegate->getPressedKeyAction(KeyContext::TranslatingSelection) & KeyAction::CopySelection)));
|
||||
}
|
||||
|
||||
bool Editor::keepTimelineRange()
|
||||
{
|
||||
if (MovingPixelsState* movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get())) {
|
||||
if (movingPixels->canHandleFrameChange())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
EditorHit Editor::calcHit(const gfx::Point& mouseScreenPos)
|
||||
{
|
||||
tools::Ink* ink = getCurrentEditorInk();
|
||||
|
@ -218,6 +218,13 @@ namespace app {
|
||||
// way to move the selection.
|
||||
bool canStartMovingSelectionPixels();
|
||||
|
||||
// Returns true if the range selected in the timeline should be
|
||||
// kept. E.g. When we are moving/transforming pixels on multiple
|
||||
// cels, the MovingPixelsState can handle previous/next frame
|
||||
// commands, so it's nice to keep the timeline range intact while
|
||||
// we are in the MovingPixelsState.
|
||||
bool keepTimelineRange();
|
||||
|
||||
// Returns the element that will be modified if the mouse is used
|
||||
// in the given position.
|
||||
EditorHit calcHit(const gfx::Point& mouseScreenPos);
|
||||
|
@ -597,6 +597,22 @@ void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
// We can use previous/next frames while transforming the selection
|
||||
// to switch between frames
|
||||
else if (command->id() == CommandId::GotoPreviousFrame() ||
|
||||
command->id() == CommandId::GotoPreviousFrameWithSameTag()) {
|
||||
if (m_pixelsMovement->gotoFrame(-1)) {
|
||||
ev.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (command->id() == CommandId::GotoNextFrame() ||
|
||||
command->id() == CommandId::GotoNextFrameWithSameTag()) {
|
||||
if (m_pixelsMovement->gotoFrame(+1)) {
|
||||
ev.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pixelsMovement)
|
||||
dropPixels();
|
||||
@ -614,8 +630,10 @@ void MovingPixelsState::onBeforeFrameChanged(Editor* editor)
|
||||
if (!isActiveDocument())
|
||||
return;
|
||||
|
||||
if (m_pixelsMovement)
|
||||
if (m_pixelsMovement &&
|
||||
!m_pixelsMovement->canHandleFrameChange()) {
|
||||
dropPixels();
|
||||
}
|
||||
}
|
||||
|
||||
void MovingPixelsState::onBeforeLayerChanged(Editor* editor)
|
||||
|
@ -33,6 +33,10 @@ namespace app {
|
||||
MovingPixelsState(Editor* editor, ui::MouseMessage* msg, PixelsMovementPtr pixelsMovement, HandleType handle);
|
||||
virtual ~MovingPixelsState();
|
||||
|
||||
bool canHandleFrameChange() const {
|
||||
return m_pixelsMovement->canHandleFrameChange();
|
||||
}
|
||||
|
||||
void translate(const gfx::Point& delta);
|
||||
void rotate(double angle);
|
||||
void flip(doc::algorithm::FlipType flipType);
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/expand_cel_canvas.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "app/util/range_utils.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/pi.h"
|
||||
#include "base/vector2d.h"
|
||||
@ -43,6 +45,14 @@
|
||||
#include "gfx/region.h"
|
||||
#include "render/render.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#if _DEBUG
|
||||
#define DUMP_INNER_CMDS() dumpInnerCmds()
|
||||
#else
|
||||
#define DUMP_INNER_CMDS()
|
||||
#endif
|
||||
|
||||
namespace app {
|
||||
|
||||
template<typename T>
|
||||
@ -50,6 +60,60 @@ static inline const base::Vector2d<double> point2Vector(const gfx::PointT<T>& pt
|
||||
return base::Vector2d<double>(pt.x, pt.y);
|
||||
}
|
||||
|
||||
PixelsMovement::InnerCmd::InnerCmd(InnerCmd&& c)
|
||||
: type(None)
|
||||
{
|
||||
std::swap(type, c.type);
|
||||
std::swap(data, c.data);
|
||||
}
|
||||
|
||||
PixelsMovement::InnerCmd::~InnerCmd()
|
||||
{
|
||||
if (type == InnerCmd::Stamp)
|
||||
delete data.stamp.transformation;
|
||||
}
|
||||
|
||||
// static
|
||||
PixelsMovement::InnerCmd
|
||||
PixelsMovement::InnerCmd::MakeClear()
|
||||
{
|
||||
InnerCmd c;
|
||||
c.type = InnerCmd::Clear;
|
||||
return c;
|
||||
}
|
||||
|
||||
// static
|
||||
PixelsMovement::InnerCmd
|
||||
PixelsMovement::InnerCmd::MakeFlip(const doc::algorithm::FlipType flipType)
|
||||
{
|
||||
InnerCmd c;
|
||||
c.type = InnerCmd::Flip;
|
||||
c.data.flip.type = flipType;
|
||||
return c;
|
||||
}
|
||||
|
||||
// static
|
||||
PixelsMovement::InnerCmd
|
||||
PixelsMovement::InnerCmd::MakeShift(const int dx, const int dy, const double angle)
|
||||
{
|
||||
InnerCmd c;
|
||||
c.type = InnerCmd::Shift;
|
||||
c.data.shift.dx = dx;
|
||||
c.data.shift.dy = dy;
|
||||
c.data.shift.angle = angle;
|
||||
return c;
|
||||
}
|
||||
|
||||
// static
|
||||
PixelsMovement::InnerCmd
|
||||
PixelsMovement::InnerCmd::MakeStamp(const Transformation& t)
|
||||
{
|
||||
InnerCmd c;
|
||||
c.type = InnerCmd::Stamp;
|
||||
c.data.stamp.transformation = new Transformation(t);
|
||||
return c;
|
||||
}
|
||||
|
||||
PixelsMovement::PixelsMovement(
|
||||
Context* context,
|
||||
Site site,
|
||||
@ -59,16 +123,15 @@ PixelsMovement::PixelsMovement(
|
||||
: m_reader(context)
|
||||
, m_site(site)
|
||||
, m_document(site.document())
|
||||
, m_sprite(site.sprite())
|
||||
, m_layer(site.layer())
|
||||
, m_tx(context, operationName)
|
||||
, m_setMaskCmd(nullptr)
|
||||
// , m_setMaskCmd(nullptr)
|
||||
, m_isDragging(false)
|
||||
, m_adjustPivot(false)
|
||||
, m_handle(NoHandle)
|
||||
, m_originalImage(Image::createCopy(moveThis))
|
||||
, m_opaque(false)
|
||||
, m_maskColor(m_sprite->transparentColor())
|
||||
, m_maskColor(m_site.sprite()->transparentColor())
|
||||
, m_canHandleFrameChange(false)
|
||||
{
|
||||
Transformation transform(mask->bounds());
|
||||
set_pivot_from_preferences(transform);
|
||||
@ -76,8 +139,9 @@ PixelsMovement::PixelsMovement(
|
||||
m_initialData = transform;
|
||||
m_currentData = transform;
|
||||
|
||||
m_initialMask = new Mask(*mask);
|
||||
m_currentMask = new Mask(*mask);
|
||||
m_initialMask.reset(new Mask(*mask));
|
||||
m_initialMask0.reset(new Mask(*mask));
|
||||
m_currentMask.reset(new Mask(*mask));
|
||||
|
||||
m_pivotVisConn =
|
||||
Preferences::instance().selection.pivotVisibility.AfterChange.connect(
|
||||
@ -96,57 +160,22 @@ PixelsMovement::PixelsMovement(
|
||||
redrawExtraImage();
|
||||
redrawCurrentMask();
|
||||
|
||||
// If the mask isn't in the document (e.g. it's from Paste command),
|
||||
// we've to replace the document mask and generate its boundaries.
|
||||
//
|
||||
// This is really tricky. PixelsMovement is used in two situations:
|
||||
// 1) When the current selection is transformed, and
|
||||
// 2) when the user pastes the clipboard content.
|
||||
//
|
||||
// In the first case, the current document selection is used. And a
|
||||
// cutMask() command could be called after PixelsMovement ctor. We
|
||||
// need the following stack of Cmd instances in the Transaction:
|
||||
// - cmd::ClearMask: clears the old mask)
|
||||
// - cmd::SetMask (m_setMaskCmd): replaces the old mask with a new mask
|
||||
// The new mask in m_setMaskCmd is replaced each time the mask is modified.
|
||||
//
|
||||
// In the second case, the mask isn't in the document, is a new mask
|
||||
// used to paste the pixels, so we've to replace the document mask.
|
||||
// The Transaction contains just a:
|
||||
// - cmd::SetMask
|
||||
//
|
||||
// The main point here is that cmd::SetMask must be the last item in
|
||||
// the Transaction using the mask (because we use cmd::SetMask::setNewMask()).
|
||||
//
|
||||
// TODO Simplify this code in some way or make explicit both usages
|
||||
// If the mask is different than the mask from the document
|
||||
// (e.g. it's from Paste command), we've to replace the document
|
||||
// mask and generate its boundaries.
|
||||
if (mask != m_document->mask()) {
|
||||
updateDocumentMask();
|
||||
// Update document mask
|
||||
m_tx(new cmd::SetMask(m_document, m_currentMask.get()));
|
||||
m_document->generateMaskBoundaries(m_currentMask.get());
|
||||
update_screen_for_document(m_document);
|
||||
}
|
||||
}
|
||||
|
||||
PixelsMovement::~PixelsMovement()
|
||||
{
|
||||
delete m_originalImage;
|
||||
delete m_initialMask;
|
||||
delete m_currentMask;
|
||||
}
|
||||
|
||||
void PixelsMovement::flipImage(doc::algorithm::FlipType flipType)
|
||||
{
|
||||
// Flip the image.
|
||||
doc::algorithm::flip_image(
|
||||
m_originalImage,
|
||||
gfx::Rect(gfx::Point(0, 0),
|
||||
gfx::Size(m_originalImage->width(),
|
||||
m_originalImage->height())),
|
||||
flipType);
|
||||
m_innerCmds.push_back(InnerCmd::MakeFlip(flipType));
|
||||
|
||||
// Flip the mask.
|
||||
doc::algorithm::flip_image(
|
||||
m_initialMask->bitmap(),
|
||||
gfx::Rect(gfx::Point(0, 0), m_initialMask->bounds().size()),
|
||||
flipType);
|
||||
flipOriginalImage(flipType);
|
||||
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
@ -179,7 +208,10 @@ void PixelsMovement::rotate(double angle)
|
||||
|
||||
void PixelsMovement::shift(int dx, int dy)
|
||||
{
|
||||
doc::algorithm::shift_image(m_originalImage, dx, dy, m_currentData.angle());
|
||||
const double angle = m_currentData.angle();
|
||||
m_innerCmds.push_back(InnerCmd::MakeShift(dx, dy, angle));
|
||||
shiftOriginalImage(dx, dy, angle);
|
||||
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
|
||||
@ -194,16 +226,31 @@ void PixelsMovement::shift(int dx, int dy)
|
||||
void PixelsMovement::trim()
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
Cel* activeCel = m_site.cel();
|
||||
bool restoreMask = false;
|
||||
|
||||
// writer.cel() can be nullptr when we paste in an empty cel
|
||||
// (Ctrl+V) and cut (Ctrl+X) the floating pixels.
|
||||
if (writer.cel() &&
|
||||
writer.cel()->layer()->isTransparent())
|
||||
m_tx(new cmd::TrimCel(writer.cel()));
|
||||
// TODO this is similar to clear_mask_from_cels()
|
||||
|
||||
for (Cel* cel : getEditableCels()) {
|
||||
if (cel != activeCel) {
|
||||
if (!restoreMask) {
|
||||
m_document->setMask(m_initialMask0.get());
|
||||
restoreMask = true;
|
||||
}
|
||||
m_tx(new cmd::ClearMask(cel));
|
||||
}
|
||||
if (cel->layer()->isTransparent())
|
||||
m_tx(new cmd::TrimCel(cel));
|
||||
}
|
||||
|
||||
if (restoreMask)
|
||||
updateDocumentMask();
|
||||
}
|
||||
|
||||
void PixelsMovement::cutMask()
|
||||
{
|
||||
m_innerCmds.push_back(InnerCmd::MakeClear());
|
||||
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
if (writer.cel()) {
|
||||
@ -465,8 +512,10 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
||||
// If "fullBounds" is empty is because the cel was not moved
|
||||
if (!fullBounds.isEmpty()) {
|
||||
// Notify the modified region.
|
||||
m_document->notifySpritePixelsModified(m_sprite, gfx::Region(fullBounds),
|
||||
m_site.frame());
|
||||
m_document->notifySpritePixelsModified(
|
||||
m_site.sprite(),
|
||||
gfx::Region(fullBounds),
|
||||
m_site.frame());
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,9 +523,11 @@ void PixelsMovement::getDraggedImageCopy(std::unique_ptr<Image>& outputImage,
|
||||
std::unique_ptr<Mask>& outputMask)
|
||||
{
|
||||
gfx::Rect bounds = m_currentData.transformedBounds();
|
||||
std::unique_ptr<Image> image(Image::create(m_sprite->pixelFormat(), bounds.w, bounds.h));
|
||||
std::unique_ptr<Image> image(
|
||||
Image::create(
|
||||
m_site.sprite()->pixelFormat(), bounds.w, bounds.h));
|
||||
|
||||
drawImage(image.get(), bounds.origin(), false);
|
||||
drawImage(m_currentData, image.get(), bounds.origin(), false);
|
||||
|
||||
// Draw mask without shrinking it, so the mask size is equal to the
|
||||
// "image" render.
|
||||
@ -502,41 +553,83 @@ void PixelsMovement::getDraggedImageCopy(std::unique_ptr<Image>& outputImage,
|
||||
}
|
||||
|
||||
void PixelsMovement::stampImage()
|
||||
{
|
||||
stampImage(false);
|
||||
m_innerCmds.push_back(InnerCmd::MakeStamp(m_currentData));
|
||||
}
|
||||
|
||||
// finalStamp: true if we have to stamp the current transformation
|
||||
// (m_currentData) in all cels of the active range, or false if we
|
||||
// have to stamp the image only in the current cel.
|
||||
void PixelsMovement::stampImage(bool finalStamp)
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
Cel* currentCel = m_site.cel();
|
||||
|
||||
CelList cels;
|
||||
if (finalStamp) {
|
||||
cels = getEditableCels();
|
||||
}
|
||||
// Current cel (m_site.cel()) can be nullptr when we paste in an
|
||||
// empty cel (Ctrl+V) and cut (Ctrl+X) the floating pixels.
|
||||
else {
|
||||
cels.push_back(currentCel);
|
||||
}
|
||||
|
||||
for (Cel* target : cels) {
|
||||
// We'll re-create the transformation for the other cels
|
||||
if (target != currentCel) {
|
||||
ASSERT(target);
|
||||
m_site.layer(target->layer());
|
||||
m_site.frame(target->frame());
|
||||
ASSERT(m_site.cel() == target);
|
||||
|
||||
reproduceAllTransformationsWithInnerCmds();
|
||||
}
|
||||
|
||||
redrawExtraImage();
|
||||
stampExtraCelImage();
|
||||
}
|
||||
|
||||
currentCel = m_site.cel();
|
||||
if (currentCel &&
|
||||
(m_site.layer() != currentCel->layer() ||
|
||||
m_site.frame() != currentCel->frame())) {
|
||||
m_site.layer(currentCel->layer());
|
||||
m_site.frame(currentCel->frame());
|
||||
redrawExtraImage();
|
||||
}
|
||||
}
|
||||
|
||||
void PixelsMovement::stampExtraCelImage()
|
||||
{
|
||||
const Cel* cel = m_extraCel->cel();
|
||||
const Image* image = m_extraCel->image();
|
||||
|
||||
ASSERT(cel && image);
|
||||
// Expand the canvas to paste the image in the fully visible
|
||||
// portion of sprite.
|
||||
ExpandCelCanvas expand(
|
||||
m_site, m_site.layer(),
|
||||
TiledMode::NONE, m_tx,
|
||||
ExpandCelCanvas::None);
|
||||
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
{
|
||||
// Expand the canvas to paste the image in the fully visible
|
||||
// portion of sprite.
|
||||
ExpandCelCanvas expand(
|
||||
m_site, m_site.layer(),
|
||||
TiledMode::NONE, m_tx,
|
||||
ExpandCelCanvas::None);
|
||||
// We cannot use cel->bounds() because cel->image() is nullptr
|
||||
gfx::Rect modifiedRect(
|
||||
cel->x(),
|
||||
cel->y(),
|
||||
image->width(),
|
||||
image->height());
|
||||
|
||||
// We cannot use cel->bounds() because cel->image() is nullptr
|
||||
gfx::Rect modifiedRect(
|
||||
cel->x(),
|
||||
cel->y(),
|
||||
image->width(),
|
||||
image->height());
|
||||
gfx::Region modifiedRegion(modifiedRect);
|
||||
expand.validateDestCanvas(modifiedRegion);
|
||||
|
||||
gfx::Region modifiedRegion(modifiedRect);
|
||||
expand.validateDestCanvas(modifiedRegion);
|
||||
expand.getDestCanvas()->copy(
|
||||
image, gfx::Clip(
|
||||
cel->x()-expand.getCel()->x(),
|
||||
cel->y()-expand.getCel()->y(),
|
||||
image->bounds()));
|
||||
|
||||
expand.getDestCanvas()->copy(
|
||||
image, gfx::Clip(
|
||||
cel->x()-expand.getCel()->x(),
|
||||
cel->y()-expand.getCel()->y(),
|
||||
image->bounds()));
|
||||
|
||||
expand.commit();
|
||||
}
|
||||
}
|
||||
expand.commit();
|
||||
}
|
||||
|
||||
void PixelsMovement::dropImageTemporarily()
|
||||
@ -586,7 +679,11 @@ void PixelsMovement::dropImage()
|
||||
m_isDragging = false;
|
||||
|
||||
// Stamp the image in the current layer.
|
||||
stampImage();
|
||||
stampImage(true);
|
||||
|
||||
// Put the new mask
|
||||
m_document->setMask(m_initialMask0.get());
|
||||
m_tx(new cmd::SetMask(m_document, m_currentMask.get()));
|
||||
|
||||
// This is the end of the whole undo transaction.
|
||||
m_tx.commit();
|
||||
@ -603,8 +700,13 @@ void PixelsMovement::discardImage(const CommitChangesOption commit,
|
||||
m_isDragging = false;
|
||||
|
||||
// Deselect the mask (here we don't stamp the image)
|
||||
if (keepMask == DontKeepMask)
|
||||
m_document->setMask(m_initialMask0.get());
|
||||
if (keepMask == DontKeepMask) {
|
||||
m_tx(new cmd::DeselectMask(m_document));
|
||||
}
|
||||
else {
|
||||
m_tx(new cmd::SetMask(m_document, m_currentMask.get()));
|
||||
}
|
||||
|
||||
if (commit == CommitChanges)
|
||||
m_tx.commit();
|
||||
@ -648,47 +750,56 @@ void PixelsMovement::setMaskColor(bool opaque, color_t mask_color)
|
||||
update_screen_for_document(m_document);
|
||||
}
|
||||
|
||||
void PixelsMovement::redrawExtraImage()
|
||||
void PixelsMovement::redrawExtraImage(Transformation* transformation)
|
||||
{
|
||||
int t, opacity = (m_layer->isImage() ? static_cast<LayerImage*>(m_layer)->opacity(): 255);
|
||||
if (!transformation)
|
||||
transformation = &m_currentData;
|
||||
|
||||
int t, opacity = (m_site.layer()->isImage() ?
|
||||
static_cast<LayerImage*>(m_site.layer())->opacity(): 255);
|
||||
Cel* cel = m_site.cel();
|
||||
if (cel) opacity = MUL_UN8(opacity, cel->opacity(), t);
|
||||
|
||||
gfx::Rect bounds = m_currentData.transformedBounds();
|
||||
gfx::Rect bounds = transformation->transformedBounds();
|
||||
|
||||
if (!m_extraCel)
|
||||
m_extraCel.reset(new ExtraCel);
|
||||
|
||||
m_extraCel.reset(new ExtraCel);
|
||||
m_extraCel->create(m_document->sprite(), bounds, m_site.frame(), opacity);
|
||||
m_extraCel->setType(render::ExtraType::PATCH);
|
||||
m_extraCel->setBlendMode(m_layer->isImage() ?
|
||||
static_cast<LayerImage*>(m_layer)->blendMode():
|
||||
m_extraCel->setBlendMode(m_site.layer()->isImage() ?
|
||||
static_cast<LayerImage*>(m_site.layer())->blendMode():
|
||||
BlendMode::NORMAL);
|
||||
m_document->setExtraCel(m_extraCel);
|
||||
|
||||
// Draw the transformed pixels in the extra-cel which is the chunk
|
||||
// of pixels that the user is moving.
|
||||
drawImage(m_extraCel->image(), bounds.origin(), true);
|
||||
drawImage(*transformation, m_extraCel->image(), bounds.origin(), true);
|
||||
}
|
||||
|
||||
void PixelsMovement::redrawCurrentMask()
|
||||
{
|
||||
drawMask(m_currentMask, true);
|
||||
drawMask(m_currentMask.get(), true);
|
||||
}
|
||||
|
||||
void PixelsMovement::drawImage(doc::Image* dst, const gfx::Point& pt, bool renderOriginalLayer)
|
||||
void PixelsMovement::drawImage(
|
||||
const Transformation& transformation,
|
||||
doc::Image* dst, const gfx::Point& pt,
|
||||
const bool renderOriginalLayer)
|
||||
{
|
||||
ASSERT(dst);
|
||||
|
||||
Transformation::Corners corners;
|
||||
m_currentData.transformBox(corners);
|
||||
transformation.transformBox(corners);
|
||||
gfx::Rect bounds = corners.bounds();
|
||||
|
||||
dst->setMaskColor(m_sprite->transparentColor());
|
||||
dst->setMaskColor(m_site.sprite()->transparentColor());
|
||||
dst->clear(dst->maskColor());
|
||||
|
||||
if (renderOriginalLayer) {
|
||||
render::Render render;
|
||||
render.renderLayer(
|
||||
dst, m_layer, m_site.frame(),
|
||||
dst, m_site.layer(), m_site.frame(),
|
||||
gfx::Clip(bounds.x-pt.x, bounds.y-pt.y, bounds),
|
||||
BlendMode::SRC);
|
||||
}
|
||||
@ -708,7 +819,10 @@ void PixelsMovement::drawImage(doc::Image* dst, const gfx::Point& pt, bool rende
|
||||
}
|
||||
m_originalImage->setMaskColor(maskColor);
|
||||
|
||||
drawParallelogram(dst, m_originalImage, m_initialMask, corners, pt);
|
||||
drawParallelogram(
|
||||
transformation,
|
||||
dst, m_originalImage.get(),
|
||||
m_initialMask.get(), corners, pt);
|
||||
}
|
||||
|
||||
void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
|
||||
@ -721,7 +835,8 @@ void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
|
||||
if (shrink)
|
||||
mask->freeze();
|
||||
clear_image(mask->bitmap(), 0);
|
||||
drawParallelogram(mask->bitmap(),
|
||||
drawParallelogram(m_currentData,
|
||||
mask->bitmap(),
|
||||
m_initialMask->bitmap(),
|
||||
nullptr,
|
||||
corners, bounds.origin());
|
||||
@ -730,6 +845,7 @@ void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
|
||||
}
|
||||
|
||||
void PixelsMovement::drawParallelogram(
|
||||
const Transformation& transformation,
|
||||
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
||||
const Transformation::Corners& corners,
|
||||
const gfx::Point& leftTop)
|
||||
@ -740,7 +856,7 @@ void PixelsMovement::drawParallelogram(
|
||||
// right/straight-angle, we should use the fast rotation algorithm,
|
||||
// as it's pixel-perfect match with the original selection when just
|
||||
// a translation is applied.
|
||||
double angle = 180.0*m_currentData.angle()/PI;
|
||||
double angle = 180.0*transformation.angle()/PI;
|
||||
if (std::fabs(std::fmod(std::fabs(angle), 90.0)) < 0.01 ||
|
||||
std::fabs(std::fmod(std::fabs(angle), 90.0)-90.0) < 0.01) {
|
||||
rotAlgo = tools::RotationAlgorithm::FAST;
|
||||
@ -811,14 +927,198 @@ void PixelsMovement::onRotationAlgorithmChange()
|
||||
|
||||
void PixelsMovement::updateDocumentMask()
|
||||
{
|
||||
if (!m_setMaskCmd) {
|
||||
m_setMaskCmd = new cmd::SetMask(m_document, m_currentMask);
|
||||
m_tx(m_setMaskCmd);
|
||||
}
|
||||
else
|
||||
m_setMaskCmd->setNewMask(m_currentMask);
|
||||
|
||||
m_document->generateMaskBoundaries(m_currentMask);
|
||||
m_document->setMask(m_currentMask.get());
|
||||
m_document->generateMaskBoundaries(m_currentMask.get());
|
||||
}
|
||||
|
||||
void PixelsMovement::flipOriginalImage(const doc::algorithm::FlipType flipType)
|
||||
{
|
||||
// Flip the image.
|
||||
doc::algorithm::flip_image(
|
||||
m_originalImage.get(),
|
||||
gfx::Rect(gfx::Point(0, 0),
|
||||
gfx::Size(m_originalImage->width(),
|
||||
m_originalImage->height())),
|
||||
flipType);
|
||||
|
||||
// Flip the mask.
|
||||
doc::algorithm::flip_image(
|
||||
m_initialMask->bitmap(),
|
||||
gfx::Rect(gfx::Point(0, 0), m_initialMask->bounds().size()),
|
||||
flipType);
|
||||
}
|
||||
|
||||
void PixelsMovement::shiftOriginalImage(const int dx, const int dy,
|
||||
const double angle)
|
||||
{
|
||||
doc::algorithm::shift_image(
|
||||
m_originalImage.get(), dx, dy, angle);
|
||||
}
|
||||
|
||||
// Returns the list of cels that will be transformed (the first item
|
||||
// in the list must be the current cel that was transformed if the cel
|
||||
// wasn't nullptr).
|
||||
CelList PixelsMovement::getEditableCels()
|
||||
{
|
||||
CelList cels;
|
||||
|
||||
if (m_site.range().enabled()) {
|
||||
cels = get_unlocked_unique_cels(
|
||||
m_site.sprite(), m_site.range());
|
||||
}
|
||||
else {
|
||||
// TODO This case is used in paste too, where the cel() can be
|
||||
// nullptr (e.g. we paste the clipboard image into an empty
|
||||
// cel).
|
||||
cels.push_back(m_site.cel());
|
||||
return cels;
|
||||
}
|
||||
|
||||
// Current cel (m_site.cel()) can be nullptr when we paste in an
|
||||
// empty cel (Ctrl+V) and cut (Ctrl+X) the floating pixels.
|
||||
if (m_site.cel() &&
|
||||
m_site.cel()->layer()->isEditable()) {
|
||||
auto it = std::find(cels.begin(), cels.end(), m_site.cel());
|
||||
if (it != cels.end())
|
||||
cels.erase(it);
|
||||
cels.insert(cels.begin(), m_site.cel());
|
||||
}
|
||||
|
||||
return cels;
|
||||
}
|
||||
|
||||
bool PixelsMovement::gotoFrame(const doc::frame_t deltaFrame)
|
||||
{
|
||||
if (m_site.range().enabled()) {
|
||||
Layer* layer = m_site.layer();
|
||||
ASSERT(layer);
|
||||
|
||||
const doc::SelectedFrames frames = m_site.range().selectedFrames();
|
||||
doc::frame_t initialFrame = m_site.frame();
|
||||
doc::frame_t frame = initialFrame + deltaFrame;
|
||||
|
||||
if (frames.size() >= 2) {
|
||||
for (; !frames.contains(frame) &&
|
||||
!layer->cel(frame); frame+=deltaFrame) {
|
||||
if (deltaFrame > 0 && frame > frames.lastFrame()) {
|
||||
frame = frames.firstFrame();
|
||||
break;
|
||||
}
|
||||
else if (deltaFrame < 0 && frame < frames.firstFrame()) {
|
||||
frame = frames.lastFrame();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame == initialFrame ||
|
||||
!frames.contains(frame) ||
|
||||
// TODO At the moment we don't support going to an empty cel,
|
||||
// so we don't handle these cases
|
||||
!layer->cel(frame)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rollback all the actions, go to the next/previous frame and
|
||||
// reproduce all transformation again so the new frame is the
|
||||
// preview for the transformation.
|
||||
m_tx.rollbackAndStartAgain();
|
||||
|
||||
// Re-create the cmd::SetMask()
|
||||
//m_setMaskCmd = nullptr;
|
||||
|
||||
{
|
||||
m_canHandleFrameChange = true;
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
writer.context()->setActiveFrame(frame);
|
||||
m_site.frame(frame);
|
||||
}
|
||||
m_canHandleFrameChange = false;
|
||||
}
|
||||
|
||||
reproduceAllTransformationsWithInnerCmds();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reproduces all the inner commands in the active m_site
|
||||
void PixelsMovement::reproduceAllTransformationsWithInnerCmds()
|
||||
{
|
||||
TRACEARGS("MOVPIXS: reproduceAllTransformationsWithInnerCmds",
|
||||
"layer", m_site.layer()->name(),
|
||||
"frame", m_site.frame());
|
||||
DUMP_INNER_CMDS();
|
||||
|
||||
m_document->setMask(m_initialMask0.get());
|
||||
m_initialMask->copyFrom(m_initialMask0.get());
|
||||
m_originalImage.reset(
|
||||
new_image_from_mask(
|
||||
m_site, m_initialMask.get(),
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
|
||||
for (const InnerCmd& c : m_innerCmds) {
|
||||
switch (c.type) {
|
||||
case InnerCmd::Clear:
|
||||
m_tx(new cmd::ClearMask(m_site.cel()));
|
||||
break;
|
||||
case InnerCmd::Flip:
|
||||
flipOriginalImage(c.data.flip.type);
|
||||
break;
|
||||
case InnerCmd::Shift:
|
||||
shiftOriginalImage(c.data.shift.dx,
|
||||
c.data.shift.dy,
|
||||
c.data.shift.angle);
|
||||
break;
|
||||
case InnerCmd::Stamp:
|
||||
redrawExtraImage(c.data.stamp.transformation);
|
||||
stampExtraCelImage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
redrawExtraImage();
|
||||
redrawCurrentMask();
|
||||
updateDocumentMask();
|
||||
}
|
||||
|
||||
#if _DEBUG
|
||||
void PixelsMovement::dumpInnerCmds()
|
||||
{
|
||||
TRACEARGS("MOVPIXS: InnerCmds size=", m_innerCmds.size());
|
||||
for (auto& c : m_innerCmds) {
|
||||
switch (c.type) {
|
||||
case InnerCmd::None:
|
||||
TRACEARGS("MOVPIXS: - None");
|
||||
break;
|
||||
case InnerCmd::Clear:
|
||||
TRACEARGS("MOVPIXS: - Clear");
|
||||
break;
|
||||
case InnerCmd::Flip:
|
||||
TRACEARGS("MOVPIXS: - Flip",
|
||||
(c.data.flip.type == doc::algorithm::FlipHorizontal ? "Horizontal":
|
||||
"Vertical"));
|
||||
break;
|
||||
case InnerCmd::Shift:
|
||||
TRACEARGS("MOVPIXS: - Shift",
|
||||
"dx=", c.data.shift.dx,
|
||||
"dy=", c.data.shift.dy,
|
||||
"angle=", c.data.shift.angle);
|
||||
break;
|
||||
case InnerCmd::Stamp:
|
||||
TRACEARGS("MOVPIXS: - Stamp",
|
||||
"angle=", c.data.stamp.transformation->angle(),
|
||||
"pivot=", c.data.stamp.transformation->pivot().x,
|
||||
c.data.stamp.transformation->pivot().y,
|
||||
"bounds=", c.data.stamp.transformation->bounds().x,
|
||||
c.data.stamp.transformation->bounds().y,
|
||||
c.data.stamp.transformation->bounds().w,
|
||||
c.data.stamp.transformation->bounds().h);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // _DEBUG
|
||||
|
||||
} // namespace app
|
||||
|
@ -12,9 +12,12 @@
|
||||
#include "app/context_access.h"
|
||||
#include "app/extra_cel.h"
|
||||
#include "app/site.h"
|
||||
#include "app/transformation.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/editor/handle_type.h"
|
||||
#include "doc/algorithm/flip_type.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "gfx/size.h"
|
||||
#include "obs/connection.h"
|
||||
|
||||
@ -64,9 +67,9 @@ namespace app {
|
||||
const Image* moveThis,
|
||||
const Mask* mask,
|
||||
const char* operationName);
|
||||
~PixelsMovement();
|
||||
|
||||
HandleType handle() const { return m_handle; }
|
||||
bool canHandleFrameChange() const { return m_canHandleFrameChange; }
|
||||
|
||||
void trim();
|
||||
void cutMask();
|
||||
@ -109,42 +112,89 @@ namespace app {
|
||||
|
||||
void shift(int dx, int dy);
|
||||
|
||||
// Navigate frames
|
||||
bool gotoFrame(const doc::frame_t deltaFrame);
|
||||
|
||||
const Transformation& getTransformation() const { return m_currentData; }
|
||||
|
||||
private:
|
||||
void stampImage(bool finalStamp);
|
||||
void stampExtraCelImage();
|
||||
void onPivotChange();
|
||||
void onRotationAlgorithmChange();
|
||||
void redrawExtraImage();
|
||||
void redrawExtraImage(Transformation* transformation = nullptr);
|
||||
void redrawCurrentMask();
|
||||
void drawImage(doc::Image* dst, const gfx::Point& pos, bool renderOriginalLayer);
|
||||
void drawImage(
|
||||
const Transformation& transformation,
|
||||
doc::Image* dst, const gfx::Point& pos,
|
||||
const bool renderOriginalLayer);
|
||||
void drawMask(doc::Mask* dst, bool shrink);
|
||||
void drawParallelogram(doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
||||
void drawParallelogram(
|
||||
const Transformation& transformation,
|
||||
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
||||
const Transformation::Corners& corners,
|
||||
const gfx::Point& leftTop);
|
||||
void updateDocumentMask();
|
||||
|
||||
void flipOriginalImage(const doc::algorithm::FlipType flipType);
|
||||
void shiftOriginalImage(const int dx, const int dy,
|
||||
const double angle);
|
||||
CelList getEditableCels();
|
||||
void reproduceAllTransformationsWithInnerCmds();
|
||||
#if _DEBUG
|
||||
void dumpInnerCmds();
|
||||
#endif
|
||||
|
||||
const ContextReader m_reader;
|
||||
Site m_site;
|
||||
Doc* m_document;
|
||||
Sprite* m_sprite;
|
||||
Layer* m_layer;
|
||||
Tx m_tx;
|
||||
cmd::SetMask* m_setMaskCmd;
|
||||
bool m_isDragging;
|
||||
bool m_adjustPivot;
|
||||
HandleType m_handle;
|
||||
Image* m_originalImage;
|
||||
doc::ImageRef m_originalImage;
|
||||
gfx::Point m_catchPos;
|
||||
Transformation m_initialData;
|
||||
Transformation m_currentData;
|
||||
Mask* m_initialMask;
|
||||
Mask* m_currentMask;
|
||||
std::unique_ptr<Mask> m_initialMask, m_initialMask0;
|
||||
std::unique_ptr<Mask> m_currentMask;
|
||||
bool m_opaque;
|
||||
color_t m_maskColor;
|
||||
obs::scoped_connection m_pivotVisConn;
|
||||
obs::scoped_connection m_pivotPosConn;
|
||||
obs::scoped_connection m_rotAlgoConn;
|
||||
ExtraCelRef m_extraCel;
|
||||
bool m_canHandleFrameChange;
|
||||
|
||||
// Commands used in the interaction with the transformed pixels.
|
||||
// This is used to re-create the whole interaction on each
|
||||
// modified cel when we are modifying multiples cels at the same
|
||||
// time, or also to re-create it when we switch to another frame.
|
||||
struct InnerCmd {
|
||||
enum Type { None, Clear, Flip, Shift, Stamp } type;
|
||||
union {
|
||||
struct {
|
||||
doc::algorithm::FlipType type;
|
||||
} flip;
|
||||
struct {
|
||||
int dx, dy;
|
||||
double angle;
|
||||
} shift;
|
||||
struct {
|
||||
Transformation* transformation;
|
||||
} stamp;
|
||||
} data;
|
||||
InnerCmd() : type(None) { }
|
||||
InnerCmd(InnerCmd&&);
|
||||
~InnerCmd();
|
||||
InnerCmd(const InnerCmd&) = delete;
|
||||
InnerCmd& operator=(const InnerCmd&) = delete;
|
||||
static InnerCmd MakeClear();
|
||||
static InnerCmd MakeFlip(const doc::algorithm::FlipType flipType);
|
||||
static InnerCmd MakeShift(const int dx, const int dy, const double angle);
|
||||
static InnerCmd MakeStamp(const Transformation& t);
|
||||
};
|
||||
std::vector<InnerCmd> m_innerCmds;
|
||||
};
|
||||
|
||||
inline PixelsMovement::MoveModifier& operator|=(PixelsMovement::MoveModifier& a,
|
||||
|
@ -1793,12 +1793,6 @@ void Timeline::onRemoveFrame(DocEvent& ev)
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void Timeline::onSelectionBoundariesChanged(DocEvent& ev)
|
||||
{
|
||||
if (m_rangeLocks == 0)
|
||||
clearAndInvalidateRange();
|
||||
}
|
||||
|
||||
void Timeline::onLayerNameChange(DocEvent& ev)
|
||||
{
|
||||
invalidate();
|
||||
@ -1830,7 +1824,7 @@ void Timeline::onAfterFrameChanged(Editor* editor)
|
||||
|
||||
setFrame(editor->frame(), false);
|
||||
|
||||
if (!hasCapture())
|
||||
if (!hasCapture() && !editor->keepTimelineRange())
|
||||
clearAndInvalidateRange();
|
||||
|
||||
showCurrentCel();
|
||||
@ -4027,7 +4021,12 @@ bool Timeline::onPaste(Context* ctx)
|
||||
|
||||
bool Timeline::onClear(Context* ctx)
|
||||
{
|
||||
if (!m_document || !m_sprite || !m_range.enabled())
|
||||
if (!m_document ||
|
||||
!m_sprite ||
|
||||
!m_range.enabled() ||
|
||||
// If the mask is visible the delete command will be handled by
|
||||
// the Editor
|
||||
m_document->isMaskVisible())
|
||||
return false;
|
||||
|
||||
Command* cmd = nullptr;
|
||||
|
@ -130,6 +130,8 @@ namespace app {
|
||||
void lockRange();
|
||||
void unlockRange();
|
||||
|
||||
void clearAndInvalidateRange();
|
||||
|
||||
protected:
|
||||
bool onProcessMessage(ui::Message* msg) override;
|
||||
void onInitTheme(ui::InitThemeEvent& ev) override;
|
||||
@ -145,7 +147,6 @@ namespace app {
|
||||
void onAfterRemoveLayer(DocEvent& ev) override;
|
||||
void onAddFrame(DocEvent& ev) override;
|
||||
void onRemoveFrame(DocEvent& ev) override;
|
||||
void onSelectionBoundariesChanged(DocEvent& ev) override;
|
||||
void onLayerNameChange(DocEvent& ev) override;
|
||||
void onAddFrameTag(DocEvent& ev) override;
|
||||
void onRemoveFrameTag(DocEvent& ev) override;
|
||||
@ -312,7 +313,6 @@ namespace app {
|
||||
const Cel* cel);
|
||||
void updateDropRange(const gfx::Point& pt);
|
||||
void clearClipboardRange();
|
||||
void clearAndInvalidateRange();
|
||||
|
||||
// The layer of the bottom (e.g. Background layer)
|
||||
layer_t firstLayer() const { return 0; }
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -33,6 +33,7 @@
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/clipboard_native.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "app/util/range_utils.h"
|
||||
#include "clip/clip.h"
|
||||
#include "doc/doc.h"
|
||||
#include "render/dithering.h"
|
||||
@ -239,6 +240,28 @@ void get_document_range_info(Doc** document, DocRange* range)
|
||||
}
|
||||
}
|
||||
|
||||
void clear_mask_from_cels(Tx& tx,
|
||||
Doc* doc,
|
||||
const CelList& cels,
|
||||
const bool deselectMask)
|
||||
{
|
||||
for (Cel* cel : cels) {
|
||||
ObjectId celId = cel->id();
|
||||
|
||||
tx(new cmd::ClearMask(cel));
|
||||
|
||||
// Get cel again just in case the cmd::ClearMask() called cmd::ClearCel()
|
||||
cel = doc::get<Cel>(celId);
|
||||
if (cel &&
|
||||
cel->layer()->isTransparent()) {
|
||||
tx(new cmd::TrimCel(cel));
|
||||
}
|
||||
}
|
||||
|
||||
if (deselectMask)
|
||||
tx(new cmd::DeselectMask(doc));
|
||||
}
|
||||
|
||||
void clear_content()
|
||||
{
|
||||
set_clipboard_image(nullptr, nullptr, nullptr, true, false);
|
||||
@ -257,14 +280,18 @@ void cut(ContextWriter& writer)
|
||||
else {
|
||||
{
|
||||
Tx tx(writer.context(), "Cut");
|
||||
tx(new cmd::ClearMask(writer.cel()));
|
||||
|
||||
ASSERT(writer.cel());
|
||||
if (writer.cel() &&
|
||||
writer.cel()->layer()->isTransparent())
|
||||
tx(new cmd::TrimCel(writer.cel()));
|
||||
|
||||
tx(new cmd::DeselectMask(writer.document()));
|
||||
Site site = writer.context()->activeSite();
|
||||
CelList cels;
|
||||
if (site.range().enabled()) {
|
||||
cels = get_unlocked_unique_cels(site.sprite(), site.range());
|
||||
}
|
||||
else if (site.cel()) {
|
||||
cels.push_back(site.cel());
|
||||
}
|
||||
clear_mask_from_cels(tx,
|
||||
writer.document(),
|
||||
cels,
|
||||
true); // Deselect mask
|
||||
tx.commit();
|
||||
}
|
||||
writer.document()->generateMaskBoundaries();
|
||||
@ -369,6 +396,10 @@ void paste(Context* ctx, const bool interactive)
|
||||
}
|
||||
|
||||
if (current_editor && interactive) {
|
||||
// TODO we don't support pasting in multiple cels at the moment,
|
||||
// so we clear the range here.
|
||||
App::instance()->timeline()->clearAndInvalidateRange();
|
||||
|
||||
// Change to MovingPixelsState
|
||||
current_editor->pasteImage(src_image.get(),
|
||||
clipboard_mask.get());
|
||||
|
@ -9,6 +9,7 @@
|
||||
#define APP_UTIL_CLIPBOARD_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "doc/cel_list.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/size.h"
|
||||
#include "ui/base.h"
|
||||
@ -22,11 +23,12 @@ namespace doc {
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class Doc;
|
||||
class Context;
|
||||
class ContextReader;
|
||||
class ContextWriter;
|
||||
class Doc;
|
||||
class DocRange;
|
||||
class Tx;
|
||||
|
||||
namespace clipboard {
|
||||
using namespace doc;
|
||||
@ -56,6 +58,11 @@ namespace app {
|
||||
ClipboardFormat get_current_format();
|
||||
void get_document_range_info(Doc** document, DocRange* range);
|
||||
|
||||
void clear_mask_from_cels(Tx& tx,
|
||||
Doc* doc,
|
||||
const doc::CelList& cels,
|
||||
const bool deselectMask);
|
||||
|
||||
void clear_content();
|
||||
void cut(ContextWriter& context);
|
||||
void copy(const ContextReader& context);
|
||||
|
Loading…
x
Reference in New Issue
Block a user