aseprite/src/app/doc.h
David Capello e949a5401d Drop selection when we hide a layer that is being transformed (fix #4179, fix #3254)
This fixes several problems in MovingPixelsState where hidden layers
were transformed anyway when we switched the visibility of a layer in
this state.

Other fix was tried before in #3254 but we needed the onBefore/After
layer visibility change notifications to make this work properly
(i.e. drop pixels when the visiblity of a layer is changed).

The only drawback at this moment is that changing the visibility of
the non-active layer when we are transforming multiple cels/timeline
range can be confused because we don't have #2144/#2865 implemented
yet.

This bug was originally reported here: https://community.aseprite.org/t/20621
2024-02-26 17:36:57 -03:00

300 lines
9.6 KiB
C++

// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_DOC_H_INCLUDED
#define APP_DOC_H_INCLUDED
#pragma once
#include "app/doc_observer.h"
#include "app/extra_cel.h"
#include "app/file/format_options.h"
#include "app/transformation.h"
#include "base/disable_copying.h"
#include "base/rw_lock.h"
#include "doc/blend_mode.h"
#include "doc/color.h"
#include "doc/document.h"
#include "doc/frame.h"
#include "doc/mask_boundaries.h"
#include "doc/pixel_format.h"
#include "gfx/rect.h"
#include "obs/observable.h"
#include "os/color_space.h"
#include <atomic>
#include <memory>
#include <string>
namespace doc {
class Cel;
class Layer;
class LayerTilemap;
class Mask;
class Sprite;
class Tileset;
}
namespace gfx {
class Region;
}
namespace app {
class Context;
class DocApi;
class DocUndo;
class Transaction;
using namespace doc;
enum DuplicateType {
DuplicateExactCopy,
DuplicateWithFlattenLayers,
};
// An application document. It is the class used to contain one file
// opened and being edited by the user (a sprite).
class Doc : public doc::Document,
public obs::observable<DocObserver> {
enum Flags {
kAssociatedToFile = 1, // This sprite is associated to a file in the file-system
kMaskVisible = 2, // The mask wasn't hidden by the user
kInhibitBackup = 4, // Inhibit the backup process
kFullyBackedUp = 8, // Full backup was done
kReadOnly = 16,// This document is read-only
};
public:
using LockResult = base::RWLock::LockResult;
Doc(Sprite* sprite);
~Doc();
Context* context() const { return m_ctx; }
void setContext(Context* ctx);
// Lock/unlock API (RWLock wrapper)
bool canWriteLockFromRead() const;
LockResult readLock(int timeout);
LockResult writeLock(int timeout);
LockResult upgradeToWrite(int timeout);
void downgradeToRead(LockResult lockResult);
void unlock(LockResult lockResult);
bool weakLock(std::atomic<base::RWLock::WeakLock>* weak_lock_flag);
void weakUnlock();
// Sets active/running transaction.
void setTransaction(Transaction* transaction);
Transaction* transaction() { return m_transaction; }
// Returns a high-level API: observable and undoable methods.
DocApi getApi(Transaction& transaction);
//////////////////////////////////////////////////////////////////////
// Main properties
const DocUndo* undoHistory() const { return m_undo.get(); }
DocUndo* undoHistory() { return m_undo.get(); }
bool isUndoing() const;
color_t bgColor() const;
color_t bgColor(Layer* layer) const;
os::ColorSpaceRef osColorSpace() const { return m_osColorSpace; }
//////////////////////////////////////////////////////////////////////
// Modifications with notifications
// Use this function to change the layer visibility and notify all
// DocObservers about this change (e.g. so the Editor can be
// invalidated/redrawn, MovingPixelsState can drop pixels, etc.)
void setLayerVisibilityWithNotifications(Layer* layer, const bool visible);
//////////////////////////////////////////////////////////////////////
// Notifications
void notifyGeneralUpdate();
void notifyColorSpaceChanged();
void notifyPaletteChanged();
void notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region, frame_t frame);
void notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region);
void notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer);
void notifyBeforeLayerVisibilityChange(Layer* layer, bool newState);
void notifyAfterLayerVisibilityChange(Layer* layer);
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();
void notifyTilesetChanged(Tileset* tileset);
void notifyLayerGroupCollapseChange(Layer* layer);
void notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti);
//////////////////////////////////////////////////////////////////////
// File related properties
bool isModified() const;
bool isAssociatedToFile() const;
void markAsSaved();
// You can use this to indicate that we've destroyed (or we cannot
// trust) the file associated with the document (e.g. when we
// cancel a Save operation in the middle). So it's impossible to
// back to the saved state using the UndoHistory.
void impossibleToBackToSavedState();
// Returns true if it does make sense to create a backup in this
// document. For example, it doesn't make sense to create a backup
// for an unmodified document.
bool needsBackup() const;
// Can be used to avoid creating a backup when the file is in a
// unusual temporary state (e.g. when the file is resized to be
// exported with other size)
bool inhibitBackup() const;
void setInhibitBackup(const bool inhibitBackup);
void markAsBackedUp();
bool isFullyBackedUp() const;
// TODO This read-only flag might be confusing because it
// indicates that the file was loaded from an incompatible
// version (future unknown feature) and it's preferable to
// mark the sprite as read-only to avoid overwriting unknown
// data. If in the future we want to add the possibility to
// mark a regular file as read-only, this flag'll need a new
// name.
void markAsReadOnly();
bool isReadOnly() const;
void removeReadOnlyMark();
//////////////////////////////////////////////////////////////////////
// Loaded options from file
void setFormatOptions(const FormatOptionsPtr& format_options);
FormatOptionsPtr formatOptions() const { return m_format_options; }
//////////////////////////////////////////////////////////////////////
// Boundaries
void destroyMaskBoundaries();
void generateMaskBoundaries(const Mask* mask = nullptr);
const MaskBoundaries& maskBoundaries() const {
return m_maskBoundaries;
}
MaskBoundaries& maskBoundaries() {
return m_maskBoundaries;
}
bool hasMaskBoundaries() const {
return !m_maskBoundaries.isEmpty();
}
//////////////////////////////////////////////////////////////////////
// Extra Cel (it is used to draw pen preview, pixels in movement, etc.)
ExtraCelRef extraCel() const { return m_extraCel; }
void setExtraCel(const ExtraCelRef& extraCel) { m_extraCel = extraCel; }
//////////////////////////////////////////////////////////////////////
// Mask
// Returns the current mask, it can be empty. The mask could be not
// empty but hidden to the user if the setMaskVisible(false) was
// used called before.
Mask* mask() const { return m_mask.get(); }
// Sets the current mask. The new mask will be visible by default,
// so you don't need to call setMaskVisible(true).
void setMask(const Mask* mask);
// Returns true only when the mask is not empty, and was not yet
// hidden using setMaskVisible (e.g. when the user "deselect the
// mask").
bool isMaskVisible() const;
// Changes the visibility state of the mask (it is useful only if
// the getMask() is not empty and the user can see that the mask is
// being hidden and shown to him).
void setMaskVisible(bool visible);
//////////////////////////////////////////////////////////////////////
// Transformation
Transformation getTransformation() const;
void setTransformation(const Transformation& transform);
void resetTransformation();
//////////////////////////////////////////////////////////////////////
// Last point used to draw straight lines using freehand tools + Shift key
// (EditorCustomizationDelegate::isStraightLineFromLastPoint() modifier)
static gfx::Point NoLastDrawingPoint();
gfx::Point lastDrawingPoint() const { return m_lastDrawingPoint; }
void setLastDrawingPoint(const gfx::Point& pos) { m_lastDrawingPoint = pos; }
//////////////////////////////////////////////////////////////////////
// Copying
void copyLayerContent(const Layer* sourceLayer, Doc* destDoc, Layer* destLayer) const;
Doc* duplicate(DuplicateType type) const;
void close();
protected:
void onFileNameChange() override;
virtual void onContextChanged();
private:
void removeFromContext();
void updateOSColorSpace(bool appWideSignal);
// The document is in the collection of documents of this context.
Context* m_ctx;
// Internal states of the document.
int m_flags;
// Read-Write locks.
base::RWLock m_rwLock;
// Undo and redo information about the document.
std::unique_ptr<DocUndo> m_undo;
// Current transaction for this document (when this is commit(), a
// new undo command is added to m_undo).
Transaction* m_transaction;
// Selected mask region boundaries
doc::MaskBoundaries m_maskBoundaries;
// Data to save the file in the same format that it was loaded
FormatOptionsPtr m_format_options;
// Extra cel used to draw extra stuff (e.g. editor's pen preview, pixels in movement, etc.)
ExtraCelRef m_extraCel;
// Current mask.
std::unique_ptr<doc::Mask> m_mask;
// Current transformation.
Transformation m_transformation;
gfx::Point m_lastDrawingPoint;
// Last used color space to render a sprite.
os::ColorSpaceRef m_osColorSpace;
DISABLE_COPYING(Doc);
};
} // namespace app
#endif