#pragma once #include "Document/BrushStroke.h" #include "Document/CloneStamp.h" #include "Document/ColorPalette.h" #include "Document/Gradient.h" #include "Document/Distort.h" #include "Document/Filters.h" #include "Document/LevelsAutomatic.h" #include "Document/FloatingSelection.h" #include "Document/MagicWand.h" #include "Document/ObjectSelection.h" #include "Document/SelectionClipboard.h" #include "Document/SelectionEdits.h" #include "Document/SmudgeLiquify.h" #include "Document/ToolDefaults.h" #include "Document/DocumentHistory.h" #include "Document/EditorSession+Jobs.h" #include "Document/EditorSession+Model.h" #include "IO/ProjectStore.h" #include "IO/PSD/PSDDocumentBuilder.h" #include "IO/RawImporter.h" #include "UI/PSDConversionSheet.h" #include "Rendering/CanvasViewport.h" #include "Rendering/EffectsPreviewCache.h" #include #include #include #include #include #include #include #include #include class QPainter; // The one editor state; `changed` stands in for Swift's observation. class EditorSession : public QObject, private SessionJobs { Q_OBJECT public: explicit EditorSession(QObject *parent = nullptr); // The canvas edits this in place, then calls notify(). CanvasViewport viewport; DocumentHistory history; // Swift's @ObservationIgnored tuple: the canvas draws it unannounced. SnapGuides snapGuides; // The canvas's effects previews, rendered off the UI thread. EffectsPreviewCache effectsPreviews; const std::optional &document() const { return m_document; } NavigationTool tool() const { return m_tool; } const QSet &collapsedGroupIDs() const { return m_collapsedGroupIDs; } const QSet &selectedLayerIDs() const { return m_selectedLayerIDs; } std::optional activeLayerID() const { return m_activeLayerID; } void setActiveLayerID(std::optional id); std::optional activeLayer() const; // Blocks overlapping edits at once. bool isProjectBusy() const { return m_isProjectBusy; } void setIsProjectBusy(bool busy); // Busy this long, in milliseconds, before the controls dim. static constexpr int busyIndicatorDelay = 250; bool showsBusy() const { return m_showsBusy; } bool showsNewDocument() const { return m_showsNewDocument; } void setShowsNewDocument(bool shows); bool showsImporter() const { return m_showsImporter; } void setShowsImporter(bool shows); bool isImporting() const { return m_isImporting; } void setIsImporting(bool importing); std::optional importError() const { return m_importError; } void setImportError(std::optional error); bool showsConversionSheet() const { return m_showsConversionSheet; } const std::optional &conversionRequest() const { return m_conversionRequest; } // The RAW file the develop sheet shows, while it shows. const std::optional &rawDevelop() const { return m_rawDevelop; } void finishRawDevelop(std::optional settings); std::optional renamingLayerID() const { return m_renamingLayerID; } void setRenamingLayerID(std::optional id); // The first tab's first canvas sheet skips the clipboard. bool skipsInitialClipboardCanvasSize = false; // Where the project lives on disk, once opened or saved. const std::optional &projectPath() const { return m_projectPath; } void setProjectPath(std::optional path); bool canStartProjectOperation() const; // `ready` runs at once, or when the session allows. void waitForFileRequest(std::function ready); void waitForProjectAccess(std::function ready); void selectLayer(std::optional id); 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; } // A blend mode tried on by hovering; nothing is saved. struct BlendPreview { QUuid layerID; LayerBlendMode mode; friend bool operator==(const BlendPreview &, const BlendPreview &) = default; }; const std::optional &blendPreview() const { return m_blendPreview; } // A pending transform; layers change when it is committed. const std::optional &transformEdit() const { return m_transformEdit; } bool canTransform() const; // Several layers, or a folder's contents, move in one box. bool transformsAsGroup() const; std::vector groupTransformMembers() const; std::optional groupTransformBox() const; void beginTransform(bool persistent = true); // Alt-drag: the selection's copies move, one step. void beginDuplicateTransform(); // The Move tool's settings (TransformInspector, Ctrl+H). bool transformAutoSelect() const { return m_transformAutoSelect; } void setTransformAutoSelect(bool picks); bool showsTransformControls() const { return m_showsTransformControls; } void setShowsTransformControls(bool shows); bool locksTransformRatio() const { return m_locksTransformRatio; } void setLocksTransformRatio(bool locks); void previewTransform(const LayerTransform &value); void commitTransform(); void cancelTransform(); std::optional transformPixelSize() const; LayerTransform displayedTransform(const ImageLayer &layer) const; LayerTransform editedTransform(const ImageLayer &layer) const; std::optional pendingTransform(const ImageLayer &layer) const; // An unlinked mask, selected, transforms without its layer. bool transformTargetsMask() const; void nudgeLayer(double dx, double dy); const std::optional &lassoDraft() const { return m_lassoDraft; } LassoKind lassoKind() const { return m_lassoKind; } void setLassoKind(LassoKind kind); LassoKind marqueeKind() const { return m_marqueeKind; } void setMarqueeKind(LassoKind kind); // The options bar's mode; Shift adds, Alt subtracts. SelectionMode selectionModeChoice() const { return m_selectionModeChoice; } void setSelectionModeChoice(SelectionMode mode); std::optional heldSelectionMode() const { return m_heldSelectionMode; } bool selectionAntialiased() const { return m_selectionAntialiased; } void setSelectionAntialiased(bool antialiased); // Pixels Expand and Contract grow or shrink by. int selectionExpandAmount() const { return m_selectionExpandAmount; } void setSelectionExpandAmount(int amount); int selectionContractAmount() const { return m_selectionContractAmount; } void setSelectionContractAmount(int amount); int selectionFeatherAmount() const { return m_selectionFeatherAmount; } void setSelectionFeatherAmount(int amount); // The amount sheet's question, open while set. std::optional selectionAmountOperation() const { return m_selectionAmountOperation; } void setSelectionAmountOperation(std::optional operation); const std::optional &selectionMoveOrigin() const { return m_selectionMoveOrigin; } const WandSettings &wandSettings() const { return m_wandSettings; } void setWandSettings(const WandSettings &settings); const BrushStroke *brushStroke() const { return m_brushStroke.get(); } const BrushSettings &brushSettings() const { return m_brushSettings; } void setBrushSettings(const BrushSettings &settings); BrushToolMode brushMode() const { return m_brushMode; } void setBrushMode(BrushToolMode mode); BlurToolMode blurMode() const { return m_blurMode; } void setBlurMode(BlurToolMode mode); // Smudge or Liquify under way; the canvas shows its copy. const WarpStroke *warpStroke() const { return m_warpStroke.get(); } SpotHealingMode spotHealingMode() const { return m_spotHealingMode; } void setSpotHealingMode(SpotHealingMode mode); bool maskPaintWhite() const { return m_maskPaintWhite; } void setMaskPaintWhite(bool white); const PaletteColor &backgroundColor() const { return m_backgroundColor; } void setBackgroundColor(const PaletteColor &color); // The open colour picker; the session notifies its changes. const std::optional &colorPicker() const { return m_colorPicker; } const GradientSettings &gradientSettings() const { return m_gradientSettings; } void setGradientSettings(const GradientSettings &settings); // A gradient drawn and not yet applied. const std::optional &gradientEdit() const { return m_gradientEdit; } // Clone Stamp's Alt-clicked source, in document pixels, and options. const std::optional &cloneSource() const { return m_cloneSource; } const CloneSettings &cloneSettings() const { return m_cloneSettings; } void setCloneSettings(const CloneSettings &settings); // Counts every change to a stroke, so the canvas redraws. int brushRevision() const { return m_brushRevision; } // Selected pixels on the move, from a Ctrl-drag or Ctrl-arrow. const PixelMove *pixelMove() const { return m_pixelMove.get(); } // The one filter open; its sheet shows while it lasts. const std::optional &filterEdit() const { return m_filterEdit; } // The last committed filter's settings, where the next one starts. const FilterSettings &filterSettings() const { return m_filterSettings; } const std::optional &levels() const { return m_levels; } const std::optional &hueSaturation() const { return m_hueSaturation; } // The newest preview, waiting for the running one. const std::optional &hueSaturationPending() const { return m_hueSaturationPending; } std::optional hueSampleMode() const { return m_hueSampleMode; } bool hueTargeting() const { return m_hueTargeting; } // The adjustment layer the colour editors write to. std::optional adjustmentEditingID() const { return m_adjustmentEditingID; } void setAdjustmentEditingID(std::optional id); // Its settings as its editor opened, for Cancel. const std::optional &adjustmentOriginal() const { return m_adjustmentOriginal; } // The effect whose panel is open, bound to its layer. const std::optional &effectsEditing() const { return m_effectsEditing; } void setEffectsEditing(std::optional selection); // Its layer's effects as the panel opened, for Cancel. const std::optional &effectsEditingOriginal() const { return m_effectsEditingOriginal; } void setEffectsEditingOriginal(std::optional original); // The effect chosen in the list; see selectedEffect. const std::optional &effectSelection() const { return m_effectSelection; } bool isModified() const { return history.isModified(); } bool canUseHistory() const; // Undo's first press discards a pending gradient. bool canUndo() const { return canUseHistory() && (history.canUndo() || m_gradientEdit); } bool canRedo() const { return canUseHistory() && history.canRedo(); } void undo(); void redo(); // A transaction that nests; a whole gesture is one entry. void beginEdit(const QString &name); void endEdit(); bool canEditLayers() const; void addBlankLayer(); void deleteLayer(QUuid id); void deleteActiveLayer(); // Every selected layer, folders with their contents: one step. void deleteSelectedLayers(); void renameLayer(QUuid id, const QString &name); void toggleLayerVisibility(QUuid id); // Pressing an eye, then dragging over others: one undo step. std::optional beginVisibilitySwipe(QUuid id); void setVisibilityInSwipe(QUuid id, bool visible); void endVisibilitySwipe(); // The list runs top down; the document stores bottom up. void reorderLayers(const std::set &offsets, int destination); bool canMoveActiveLayer(int offset) const; void moveActiveLayer(int offset); // Files decode off the UI thread; `done` follows this request. void importImages(const QList &urls, std::optional point = std::nullopt, std::function done = {}); void insert(const ImportedImage &asset, std::optional centeredAt = std::nullopt); // The sheet shows while a Photoshop file is read. void beginPSDReading(const QString &title, const QString &confirmTitle); void finishPSDReading(const std::vector &conversions, std::function answer); void endPSDReading(); void finishConversion(bool confirmed); void decodeRaw(const QString &path, qint64 remaining); void developRaw(const QString &path, std::function)> answer); void insertPhotoshop(const PSDImport &imported, const QString &named, std::optional centeredAt = std::nullopt); void createDocument(int width, int height, bool emptyLayer = false); void fit(); void zoom(double value, std::optional anchor = std::nullopt); // The View menu's grid, shown from 800%. bool showsPixelGrid() const { return m_showsPixelGrid; } void setShowsPixelGrid(bool shows); // View > Snap: moves and crops line up with edges. bool snappingEnabled() const { return m_snappingEnabled; } void setSnappingEnabled(bool enabled); // The Eyedropper's ring: the sampled colour over the original. bool showsSampleRing() const { return m_showsSampleRing; } void setShowsSampleRing(bool shows); // The Crop tool's frame in document pixels, once drawn. const std::optional &cropRect() const { return m_cropRect; } void setCropRect(std::optional rect); // Swift's ratio names: Free, Original, 1:1, 4:3 and 16:9. const QString &cropRatioChoice() const { return m_cropRatioChoice; } void setCropRatioChoice(const QString &choice); const std::optional &cropError() const { return m_cropError; } void setCropError(std::optional error); // Counted requests; the canvas takes focus on each new one. int canvasFocusRequest() const { return m_canvasFocusRequest; } void requestCanvasFocus(); // What Paste puts back in place, until another copy. const std::optional &pixelClipboard() const { return m_pixelClipboard; } // What went wrong in a tool; the window shows it. const std::optional &brushError() const { return m_brushError; } void setBrushError(std::optional error); void notify() { emit changed(); } // Swift's extensions, each declared in its twin's header. #include "Document/EditorSession+Projects.h" #include "IO/ImageResizer+Session.h" #include "Document/LayerMask+Session.h" #include "Document/LayerAppearance+Session.h" #include "Document/Crop+Session.h" #include "Document/Distort+Session.h" #include "Document/LayerMerge+Session.h" #include "Document/LayerFlip+Session.h" #include "Document/Selection+Session.h" #include "Document/MagicWand+Session.h" #include "Document/SubjectRemoval+Session.h" #include "Document/EditorSession+Brush.h" #include "Document/CloneStamp+Session.h" #include "Document/ColorPalette+Session.h" #include "Document/Gradient+Session.h" #include "Document/ShapeTool+Session.h" #include "Document/TypeTool+Session.h" #include "Document/SmudgeLiquify+Session.h" #include "Document/BlurTool+Session.h" #include "Document/SelectionEdits+Session.h" #include "Document/SelectionClipboard+Session.h" #include "Document/FloatingSelection+Session.h" #include "Document/Filters+Session.h" #include "Document/Levels+Session.h" #include "Document/LevelsAutomatic+Session.h" #include "Document/HueSaturation+Session.h" #include "Document/MaskTracing+Session.h" #include "Document/LayerGroups+Session.h" #include "Document/LiveLayerMask+Session.h" #include "Document/LayerAdjustment+Session.h" #include "Document/AdjustmentEditing+Session.h" #include "Document/LayerEffects+Session.h" #include "Document/CameraRaw+Session.h" #include "Document/Guides+Session.h" #include "Document/ObjectSelection+Session.h" signals: void changed(); private: // Copies layers into this document in place, as Swift does. friend class ProjectWorkspace; void restore(const DocumentHistory::Snapshot &snapshot); void resumeFileRequests(); void updateBusyIndicator(); void drainImports(); void decodeNext(); void finishDecode(); void finishPhotoshopRead(); void endDuplicateTransform(); // Clone Stamp is family 1, the Smear 2, others 0. static int tipFamily(NavigationTool tool); std::optional m_document; NavigationTool m_tool = NavigationTool::move; QSet m_collapsedGroupIDs; QSet m_selectedLayerIDs; // The canvas reads previews from a const session: mutable caches. mutable std::map m_distortPreviewCache; mutable std::map m_distortEffectsCache; mutable std::optional m_maskDistortPreviewCache; std::optional m_transformEdit; std::optional m_transformDuplicate; bool m_transformAutoSelect = ToolDefaults::boolean(QStringLiteral("autoSelect"), false); bool m_showsTransformControls = ToolDefaults::boolean(QStringLiteral("transformControls"), true); bool m_locksTransformRatio = true; std::optional m_blendPreview; std::optional m_opacityEditLayerID; bool m_isMaskSelected = false; std::optional m_activeLayerID; bool m_isProjectBusy = false; bool m_showsNewDocument = false; bool m_showsImporter = false; bool m_isImporting = false; std::optional m_importError; std::optional m_renamingLayerID; std::optional m_projectPath; bool m_showsPixelGrid = ToolDefaults::boolean(QStringLiteral("pixelGrid"), true); bool m_snappingEnabled = true; // The layout grid starts off; guides show; rulers hide. bool m_showsGrid = ToolDefaults::boolean(QStringLiteral("grid"), false); bool m_showsGuides = ToolDefaults::boolean(QStringLiteral("guides"), true); bool m_showsRulers = ToolDefaults::boolean(QStringLiteral("rulers"), false); bool m_snapEnabled = ToolDefaults::boolean(QStringLiteral("snap"), true); bool m_snapToGuides = ToolDefaults::boolean(QStringLiteral("snapGuides"), true); bool m_snapToGrid = ToolDefaults::boolean(QStringLiteral("snapGrid"), false); bool m_snapToLayers = ToolDefaults::boolean(QStringLiteral("snapLayers"), true); bool m_snapToDocumentBounds = ToolDefaults::boolean(QStringLiteral("snapBounds"), true); bool m_locksGuides = ToolDefaults::boolean(QStringLiteral("lockGuides"), false); std::optional m_guideDrag; bool m_showsSampleRing = true; std::optional m_cropRect; QString m_cropRatioChoice = QStringLiteral("Free"); std::optional m_cropError; QFutureWatcher m_cropCommit; std::function m_committingCrop; int m_canvasFocusRequest = 0; bool m_showsBusy = false; QTimer m_busyTimer; std::vector> m_projectWaiters; std::vector> m_fileRequestWaiters; std::deque m_pendingImports; // Where the request now decoding stands. qsizetype m_importIndex = 0; std::optional m_importPoint; QStringList m_importFailures; QFutureWatcher m_decoder; QFutureWatcher m_photoshopReader; bool m_showsConversionSheet = false; std::optional m_conversionRequest; std::function m_conversionAnswer; std::optional m_rawDevelop; std::function)> m_rawAnswer; // Cancel pressed while the file was still read. bool m_conversionCancelled = false; std::optional m_brushError; // The layers a bake under way will delete. std::vector m_bakingIDs; QFutureWatcher m_baker; std::optional m_inverting; QFutureWatcher m_inverter; WandSettings m_wandSettings; WandMode m_wandMode = WandMode::wand; ObjectSelectionSettings m_objectSelectionSettings; std::optional m_wanding; QFutureWatcher m_wand; std::optional m_subjecting; QFutureWatcher m_subject; std::unique_ptr m_brushStroke; std::unique_ptr m_pixelMove; BrushSettings m_brushSettings; BrushToolMode m_brushMode = BrushToolMode::paint; BlurToolMode m_blurMode = BlurToolMode::liquify; std::unique_ptr m_warpStroke; SpotHealingMode m_spotHealingMode = SpotHealingMode::contentAware; bool m_maskPaintWhite = false; PaletteColor m_backgroundColor = PaletteColor::white(); std::optional m_colorPicker; GradientSettings m_gradientSettings; std::optional m_gradientEdit; ShapeKind m_shapeKind = ShapeKind::rectangle; double m_shapeCornerRadius = 0; double m_shapeLineWidth = 4; std::optional m_shapeDraft; mutable std::map m_shapeTransformPreviewCache; std::optional m_textDraft; LayerTextStyle m_textDefaults; std::optional m_cloneSource; CloneSettings m_cloneSettings; // The offset aligned strokes keep, fixed as a stroke begins. std::optional m_cloneOffset; // Clone Stamp and the Smear keep their own soft tips. std::map m_parkedBrushTips{{1, {40, 0, 1}}, {2, {40, 0, 1}}}; std::optional m_lastBrushPoint; // Smoothing's brush, and the pointer it trails. std::optional m_brushAnchor; std::optional m_brushPointer; int m_brushRevision = 0; std::optional m_pendingOpacityDigit; std::optional m_committingRaster; QFutureWatcher m_rasterCommit; std::optional m_filterEdit; FilterSettings m_filterSettings; QFutureWatcher m_filterPreview; // Out until its finish runs: setFuture drops a pending signal. bool m_filterRendering = false; // Which edit's preview is being made; a cancelled edit's drops. QUuid m_filterPreviewFor; std::vector m_filterCommitWaiting; std::optional m_committingFilter; QFutureWatcher m_filterCommit; std::optional m_levels; // Which edit a running preview serves; others drop. QUuid m_levelsPreviewFor; QFutureWatcher> m_levelsPreview; bool m_levelsRendering = false; QFutureWatcher> m_levelsHistogram; std::optional m_committingLevels; QFutureWatcher m_levelsCommit; std::optional m_hueSaturation; std::optional m_hueSaturationPending; // Which edit a running preview serves; others drop. QUuid m_hueSaturationPreviewFor; QFutureWatcher m_hueSaturationTask; bool m_hueSaturationRendering = false; std::optional m_committingHueSaturation; QFutureWatcher m_hueSaturationCommit; std::optional m_hueSampleMode; bool m_hueTargeting = false; std::optional m_hueTargetDrag; std::optional m_adjustmentEditingID; std::optional m_adjustmentOriginal; std::optional m_effectsEditing; std::optional m_effectsEditingOriginal; std::optional m_effectSelection; std::optional m_pixelClipboard; // Swift's pasteboard change count, counted from the first copy. int m_clipboardChanges = 0; bool m_watchingClipboard = false; std::optional m_lassoDraft; LassoKind m_lassoKind = LassoKind::freehand; LassoKind m_marqueeKind = LassoKind::rectangle; SelectionMode m_selectionModeChoice = SelectionMode::replace; std::optional m_heldSelectionMode; // The selection as a drag began; one step per drag. std::optional m_selectionMoveOrigin; bool m_selectionAntialiased = true; int m_selectionExpandAmount = 1; int m_selectionContractAmount = 1; int m_selectionFeatherAmount = 2; std::optional m_selectionAmountOperation; };