Masks as Photoshop's: the button reveals the selection, Alt-click shows a mask alone (13.12)
This commit is contained in:
1 parent
cc9bf95722
commit
0b0d36a7df
28 files changed
+554
-72
No files matched your search
@@ -27,7 +27,7 @@ std::optional<ProjectSnapshot> EditorSession::projectSnapshot() const
|
||||
void EditorSession::installProject(const ProjectSnapshot &snapshot, const QString &path)
|
||||
{
|
||||
m_collapsedGroupIDs.clear();
|
||||
m_isMaskSelected = false;
|
||||
setIsMaskSelected(false);
|
||||
m_cropRect.reset();
|
||||
m_guideDrag = std::nullopt;
|
||||
m_transformEdit = std::nullopt;
|
||||
@@ -78,7 +78,7 @@ void EditorSession::setProjectPath(std::optional<QString> path)
|
||||
void EditorSession::clearProject()
|
||||
{
|
||||
m_collapsedGroupIDs.clear();
|
||||
m_isMaskSelected = false;
|
||||
setIsMaskSelected(false);
|
||||
m_cropRect.reset();
|
||||
m_transformEdit = std::nullopt;
|
||||
m_guideDrag = std::nullopt;
|
||||
|
||||
@@ -35,7 +35,7 @@ void EditorSession::setActiveLayerID(std::optional<QUuid> id)
|
||||
{
|
||||
// Another layer: its pixels are the target again.
|
||||
if (id != m_activeLayerID)
|
||||
m_isMaskSelected = false;
|
||||
setIsMaskSelected(false);
|
||||
m_activeLayerID = id;
|
||||
m_selectedLayerIDs = id ? QSet<QUuid>{*id} : QSet<QUuid>();
|
||||
notify();
|
||||
@@ -234,7 +234,7 @@ void EditorSession::restore(const DocumentHistory::Snapshot &snapshot)
|
||||
m_document = snapshot.document;
|
||||
setActiveLayerID(snapshot.activeLayerID);
|
||||
const std::optional<ImageLayer> active = activeLayer();
|
||||
m_isMaskSelected = keepsMaskTarget && active && active->mask;
|
||||
setIsMaskSelected(keepsMaskTarget && active && active->mask);
|
||||
if (changedCanvas && m_document)
|
||||
viewport.fit(m_document->size());
|
||||
qCInfo(lcApp) << "restored a history snapshot of" << (m_document ? int(m_document->layers.size()) : 0) << "layers";
|
||||
@@ -381,3 +381,18 @@ void EditorSession::requestCanvasFocus()
|
||||
++m_canvasFocusRequest;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setIsMaskSelected(bool value)
|
||||
{
|
||||
m_isMaskSelected = value;
|
||||
if (!value)
|
||||
m_viewsMaskAlone = false;
|
||||
}
|
||||
|
||||
void EditorSession::setViewsMaskAlone(bool value)
|
||||
{
|
||||
if (m_viewsMaskAlone == value)
|
||||
return;
|
||||
m_viewsMaskAlone = value;
|
||||
notify();
|
||||
}
|
||||
@@ -93,10 +93,11 @@ public:
|
||||
void selectTool(NavigationTool value);
|
||||
// Tab: the current tool's next mode.
|
||||
void cycleToolMode();
|
||||
|
||||
// Whether the mask thumbnail, not the layer's, is the target.
|
||||
bool isMaskSelected() const { return m_isMaskSelected; }
|
||||
|
||||
// Alt-click: the targeted mask alone, gray, on the canvas.
|
||||
bool viewsMaskAlone() const { return m_viewsMaskAlone; }
|
||||
void setViewsMaskAlone(bool value);
|
||||
// A blend mode tried on by hovering; nothing is saved.
|
||||
struct BlendPreview {
|
||||
QUuid layerID;
|
||||
@@ -132,7 +133,6 @@ public:
|
||||
// An unlinked mask, selected, transforms without its layer.
|
||||
bool transformTargetsMask() const;
|
||||
void nudgeLayer(double dx, double dy);
|
||||
|
||||
const std::optional<LassoDraft> &lassoDraft() const { return m_lassoDraft; }
|
||||
LassoKind lassoKind() const { return m_lassoKind; }
|
||||
void setLassoKind(LassoKind kind);
|
||||
@@ -350,6 +350,7 @@ private:
|
||||
std::optional<BlendPreview> m_blendPreview;
|
||||
std::optional<QUuid> m_opacityEditLayerID;
|
||||
bool m_isMaskSelected = false;
|
||||
bool m_viewsMaskAlone = false;
|
||||
std::optional<QUuid> m_activeLayerID;
|
||||
bool m_isProjectBusy = false;
|
||||
bool m_showsNewDocument = false;
|
||||
|
||||
@@ -351,7 +351,7 @@ void EditorSession::finishFilterCommit()
|
||||
mask.isEnabled = true;
|
||||
mask.placement = std::nullopt;
|
||||
m_document->layers[index].mask = mask;
|
||||
m_isMaskSelected = true;
|
||||
setIsMaskSelected(true);
|
||||
endEdit();
|
||||
finish();
|
||||
return;
|
||||
|
||||
@@ -97,7 +97,7 @@ void EditorSession::selectEffect(LayerEffectKind kind, QUuid id, bool editing)
|
||||
finishEffectsEditing(false);
|
||||
// Swift's `selectedLayerIDs = [id]`: selectLayer sets it, unrefused here.
|
||||
selectLayer(id);
|
||||
m_isMaskSelected = false;
|
||||
setIsMaskSelected(false);
|
||||
m_effectSelection = selection;
|
||||
if (editing && m_effectsEditing != selection) {
|
||||
m_effectsEditingOriginal = m_document->layers[size_t(indexOf(m_document->layers, id))].effects.value_or(LayerEffects());
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "Document/EditorSession.h"
|
||||
|
||||
// Swift's mask shown alone, an Alt-click on its thumbnail.
|
||||
std::optional<ImageLayer> EditorSession::maskAloneLayer() const
|
||||
{
|
||||
const std::optional<ImageLayer> layer = activeLayer();
|
||||
if (!m_viewsMaskAlone || !m_isMaskSelected || !layer || !layer->mask)
|
||||
return std::nullopt;
|
||||
return layer;
|
||||
}
|
||||
|
||||
void EditorSession::toggleMaskAlone(QUuid id)
|
||||
{
|
||||
const std::optional<ImageLayer> shown = maskAloneLayer();
|
||||
const bool showing = shown && shown->id == id;
|
||||
selectLayerTarget(id, true);
|
||||
if (m_activeLayerID != id || !m_isMaskSelected)
|
||||
return;
|
||||
m_viewsMaskAlone = !showing;
|
||||
notify();
|
||||
}
|
||||
@@ -15,6 +15,12 @@ public:
|
||||
std::optional<QImage> maskDistortPreview(const ImageLayer &layer) const;
|
||||
// The selection made a mask; without one, a solid mask.
|
||||
void addMask(bool revealing = true);
|
||||
// The layer whose mask the canvas shows alone, if any.
|
||||
std::optional<ImageLayer> maskAloneLayer() const;
|
||||
// Alt-click on a mask thumbnail: alone, or the composite again.
|
||||
void toggleMaskAlone(QUuid id);
|
||||
|
||||
private:
|
||||
// Swift's didSet: the pixels targeted end the mask view.
|
||||
void setIsMaskSelected(bool value);
|
||||
void commitMaskTransform();
|
||||
@@ -262,7 +262,7 @@ void EditorSession::selectLayerTarget(QUuid id, bool mask)
|
||||
resolveGradient();
|
||||
selectLayer(id);
|
||||
const std::optional<ImageLayer> active = activeLayer();
|
||||
m_isMaskSelected = mask && active && active->mask;
|
||||
setIsMaskSelected(mask && active && active->mask);
|
||||
notify();
|
||||
}
|
||||
|
||||
@@ -286,14 +286,15 @@ void EditorSession::addMask(bool revealing)
|
||||
// The selection's coverage, soft where it is feathered.
|
||||
QImage image = PixelAdjust::coverage(current->clip(m_document->size()), width, height,
|
||||
BrushRaster::pixelToDocument(active->transform, width, height));
|
||||
if (revealing)
|
||||
// Revealing, white inside; hiding, black inside.
|
||||
if (!revealing)
|
||||
image.invertPixels();
|
||||
const LayerMask mask(LayerMask::assetFrom(image));
|
||||
finishOpacityEdit();
|
||||
beginEdit(QStringLiteral("Add Mask from Selection"));
|
||||
beginEdit(revealing ? QStringLiteral("Reveal Selection") : QStringLiteral("Hide Selection"));
|
||||
m_document->layers[size_t(index)].mask = mask;
|
||||
m_document->selection = std::nullopt;
|
||||
m_isMaskSelected = true;
|
||||
setIsMaskSelected(true);
|
||||
endEdit();
|
||||
} catch (const std::runtime_error &error) {
|
||||
qCWarning(lcApp).noquote() << "cannot make a mask from the selection:" << error.what();
|
||||
@@ -312,7 +313,7 @@ void EditorSession::addLayerMask(bool revealing)
|
||||
if (layer.id == active->id)
|
||||
layer.mask = LayerMask::solid(revealing);
|
||||
}
|
||||
m_isMaskSelected = true;
|
||||
setIsMaskSelected(true);
|
||||
endEdit();
|
||||
}
|
||||
|
||||
@@ -341,7 +342,7 @@ void EditorSession::deleteLayerMask()
|
||||
if (layer.id == active->id)
|
||||
layer.mask = std::nullopt;
|
||||
}
|
||||
m_isMaskSelected = false;
|
||||
setIsMaskSelected(false);
|
||||
endEdit();
|
||||
}
|
||||
|
||||
@@ -371,7 +372,7 @@ void EditorSession::copyMask(QUuid source, QUuid target)
|
||||
beginEdit(find(target)->mask ? QStringLiteral("Replace Layer Mask") : QStringLiteral("Copy Layer Mask"));
|
||||
find(target)->mask = mask;
|
||||
selectLayer(target);
|
||||
m_isMaskSelected = true;
|
||||
setIsMaskSelected(true);
|
||||
endEdit();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user