From e73584dbafbd59e6780b028ca80e1fb6becf062d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= <d_muel20@uni-muenster.de> Date: Fri, 15 Jun 2018 20:16:03 +0200 Subject: [PATCH] * The original input images are now shown as special layers in the IE * Layers now have an opacity value --- Grinder/Grinder.pro | 10 ++- Grinder/Version.h | 4 +- .../serialization/SettingsContainer.cpp | 16 +++++ .../common/serialization/SettingsContainer.h | 9 ++- Grinder/controller/ImageEditorController.cpp | 55 ++++++++++----- Grinder/controller/ImageEditorController.h | 8 ++- Grinder/image/ImageBuild.cpp | 30 +++++++-- Grinder/image/ImageBuild.h | 2 +- Grinder/image/ImageBuildItem.cpp | 3 +- Grinder/image/ImageBuildPool.cpp | 2 +- Grinder/image/Layer.cpp | 34 +++++++++- Grinder/image/Layer.h | 26 ++++++- Grinder/image/LayerPixelsData.cpp | 2 + Grinder/image/LayerVector.cpp | 5 ++ Grinder/image/LayerVector.h | 1 + Grinder/ui/image/DraftItemNode.cpp | 16 ++--- Grinder/ui/image/DraftItemNode.h | 9 ++- Grinder/ui/image/ImageEditorScene.cpp | 61 ++++++++++++----- Grinder/ui/image/ImageEditorScene.h | 36 +++++----- Grinder/ui/image/ImageEditorScene.impl.h | 32 ++------- Grinder/ui/image/LayerBackgroundNode.cpp | 67 +++++++++++++++++++ Grinder/ui/image/LayerBackgroundNode.h | 36 ++++++++++ Grinder/ui/image/LayerItemNode.cpp | 38 +++++++++++ Grinder/ui/image/LayerItemNode.h | 38 +++++++++++ Grinder/ui/image/LayerPixelsNode.cpp | 36 +++------- Grinder/ui/image/LayerPixelsNode.h | 19 ++---- Grinder/ui/image/LayersListItem.cpp | 12 ++++ Grinder/ui/image/LayersListItem.h | 6 ++ Grinder/ui/image/LayersListWidget.cpp | 47 +++++++++++-- Grinder/ui/image/LayersListWidget.h | 5 ++ Grinder/ui/widgets/SliderValueWidget.cpp | 56 ++++++++++++++++ Grinder/ui/widgets/SliderValueWidget.h | 41 ++++++++++++ 32 files changed, 599 insertions(+), 163 deletions(-) create mode 100644 Grinder/ui/image/LayerBackgroundNode.cpp create mode 100644 Grinder/ui/image/LayerBackgroundNode.h create mode 100644 Grinder/ui/image/LayerItemNode.cpp create mode 100644 Grinder/ui/image/LayerItemNode.h create mode 100644 Grinder/ui/widgets/SliderValueWidget.cpp create mode 100644 Grinder/ui/widgets/SliderValueWidget.h diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro index 8c0f367..8dd84fd 100644 --- a/Grinder/Grinder.pro +++ b/Grinder/Grinder.pro @@ -249,7 +249,10 @@ SOURCES += \ pipeline/blocks/MergeChannelsBlock.cpp \ pipeline/blocks/SplitChannelsBlock.cpp \ engine/processors/SplitChannelsProcessor.cpp \ - engine/processors/MergeChannelsProcessor.cpp + engine/processors/MergeChannelsProcessor.cpp \ + ui/image/LayerItemNode.cpp \ + ui/image/LayerBackgroundNode.cpp \ + ui/widgets/SliderValueWidget.cpp HEADERS += \ ui/mainwnd/GrinderWindow.h \ @@ -547,7 +550,10 @@ HEADERS += \ pipeline/blocks/MergeChannelsBlock.h \ pipeline/blocks/SplitChannelsBlock.h \ engine/processors/SplitChannelsProcessor.h \ - engine/processors/MergeChannelsProcessor.h + engine/processors/MergeChannelsProcessor.h \ + ui/image/LayerItemNode.h \ + ui/image/LayerBackgroundNode.h \ + ui/widgets/SliderValueWidget.h FORMS += \ ui/mainwnd/GrinderWindow.ui \ diff --git a/Grinder/Version.h b/Grinder/Version.h index 0ddd137..1423d8e 100644 --- a/Grinder/Version.h +++ b/Grinder/Version.h @@ -10,14 +10,14 @@ #define GRNDR_INFO_TITLE "Grinder" #define GRNDR_INFO_COPYRIGHT "Copyright (c) WWU Muenster" -#define GRNDR_INFO_DATE "14.06.2018" +#define GRNDR_INFO_DATE "15.06.2018" #define GRNDR_INFO_COMPANY "WWU Muenster" #define GRNDR_INFO_WEBSITE "http://www.uni-muenster.de" #define GRNDR_VERSION_MAJOR 0 #define GRNDR_VERSION_MINOR 5 #define GRNDR_VERSION_REVISION 0 -#define GRNDR_VERSION_BUILD 185 +#define GRNDR_VERSION_BUILD 189 namespace grndr { diff --git a/Grinder/common/serialization/SettingsContainer.cpp b/Grinder/common/serialization/SettingsContainer.cpp index e58a0a3..8fa898d 100644 --- a/Grinder/common/serialization/SettingsContainer.cpp +++ b/Grinder/common/serialization/SettingsContainer.cpp @@ -67,3 +67,19 @@ void SettingsContainer::removeChildren(QString name) std::remove_if(_childContainers.begin(), _childContainers.end(), [name](const auto& child) { return child->_name.compare(name, Qt::CaseInsensitive) == 0; }); } + +QVariant& SettingsContainer::value(QString name, QVariant defaultValue) +{ + if (!contains(name)) + _values[name] = defaultValue; + + return _values[name]; +} + +QVariant SettingsContainer::value(QString name, QVariant defaultValue) const +{ + if (contains(name)) + return _values[name]; + else + return defaultValue; +} diff --git a/Grinder/common/serialization/SettingsContainer.h b/Grinder/common/serialization/SettingsContainer.h index 9578bab..d976df5 100644 --- a/Grinder/common/serialization/SettingsContainer.h +++ b/Grinder/common/serialization/SettingsContainer.h @@ -50,13 +50,18 @@ namespace grndr void clearChildren() { _childContainers.clear(); } public: + QVariant& value(QString name, QVariant defaultValue = QVariant{}); + QVariant value(QString name, QVariant defaultValue = QVariant{}) const; + QVariant& operator[](QString name) { return _values[name]; } - const QVariant operator[](QString name) const { return _values[name]; } + QVariant operator[](QString name) const { return _values[name]; } + QVariant& operator()(QString name, QVariant defaultValue = QVariant{}) { return value(name, defaultValue); } + QVariant operator()(QString name, QVariant defaultValue = QVariant{}) const { return value(name, defaultValue); } const QVariantMap& values() const { return _values; } QStringList keys() const { return _values.keys(); } - bool contains(QString name) { return _values.find(name) != _values.end(); } + bool contains(QString name) const { return _values.find(name) != _values.cend(); } void removeValue(QString name) { _values.remove(name); } void clearValues() { _values.clear(); } diff --git a/Grinder/controller/ImageEditorController.cpp b/Grinder/controller/ImageEditorController.cpp index b88b393..4c86f17 100644 --- a/Grinder/controller/ImageEditorController.cpp +++ b/Grinder/controller/ImageEditorController.cpp @@ -208,7 +208,7 @@ void ImageEditorController::pasteLayer() auto layers = grinder()->clipboardManager().deserialize<Layer>(LayerVector::Serialization_Element, [this](const SettingsContainer& settings) { QString name = settings[Layer::Serialization_Value_Name].toString(); - if (_activeImageBuild->layers().selectByName(name)) + while (_activeImageBuild->layers().selectByName(name)) name += " - Copy"; return createLayer(name).get(); @@ -228,9 +228,9 @@ void ImageEditorController::cutLayer(const Layer* layer) removeLayer(layer); } -void ImageEditorController::removeLayer(const Layer* layer) +void ImageEditorController::removeLayer(const Layer* layer, bool enforceRemoval) { - if (layer) + if (layer && (layer->isRemovable() || enforceRemoval)) { // If removing the active layer, unset it first if (_activeLayer == layer) @@ -243,10 +243,27 @@ void ImageEditorController::removeLayer(const Layer* layer) } } -void ImageEditorController::removeAllLayers() +void ImageEditorController::removeAllLayers(bool enforceRemoval) { - if (_activeImageBuild) - _activeImageBuild->removeAllLayers(); + if (_activeImageBuild) + { + if (!enforceRemoval) + { + // Only remove all removable layers + std::vector<Layer*> layersToRemove; + + for (auto layer : _activeImageBuild->layers()) + { + if (layer->isRemovable()) + layersToRemove.push_back(layer.get()); + } + + for (auto layer : layersToRemove) + removeLayer(layer, true); + } + else + _activeImageBuild->removeAllLayers(); + } } std::shared_ptr<DraftItem> ImageEditorController::createDraftItem(DraftItemType type, Layer* layer, bool autoCreateLayer) @@ -286,6 +303,11 @@ void ImageEditorController::setLayerVisibility(Layer* layer, bool visible) const layer->setVisible(visible); } +void ImageEditorController::setLayerAlpha(Layer* layer, float alpha) const +{ + layer->setAlpha(alpha); +} + void ImageEditorController::moveLayer(Layer* layer, bool up) const { callControllerFunction("Moving a layer", [this](Layer* layer, bool up) { @@ -612,6 +634,7 @@ void ImageEditorController::connectLayerSignals(Layer* layer, bool connectSignal connect(layer, &Layer::draftItemRemoved, this, &ImageEditorController::draftItemRemoved); connect(layer, &Layer::layerShown, this, &ImageEditorController::layerVisibilityChanged); connect(layer, &Layer::layerHidden, this, &ImageEditorController::layerVisibilityChanged); + connect(layer, &Layer::layerAlphaChanged, this, &ImageEditorController::layerAlphaChanged); } else disconnect(layer, nullptr, this, nullptr); @@ -662,10 +685,7 @@ void ImageEditorController::layerMoved(const std::shared_ptr<Layer>& layer, int // Reflect the new z-order in the scene if (_activeScene) - { - _activeScene->updateDraftItemsOrder(); - _activeScene->updateLayerPixelsOrder(); - } + _activeScene->updateLayerItemsOrder(); if (_layersList) _layersList->swapLayers(indexOld, indexNew); @@ -675,11 +695,16 @@ void ImageEditorController::layerVisibilityChanged() const { // Update the visibility of all items in the layer if (_activeScene) - { - auto layer = dynamic_cast<Layer*>(sender()); - _activeScene->updateDraftItemsVisibility(layer); - _activeScene->updateLayerPixelsVisibility(layer); - } + _activeScene->updateLayerItemsVisibility(dynamic_cast<Layer*>(sender())); +} + +void ImageEditorController::layerAlphaChanged(float alpha) const +{ + Q_UNUSED(alpha); + + // Update the alpha value of all items in the layer + if (_activeScene) + _activeScene->updateLayerItemsAlpha(dynamic_cast<Layer*>(sender())); } void ImageEditorController::draftItemCreated(const std::shared_ptr<DraftItem>& item) const diff --git a/Grinder/controller/ImageEditorController.h b/Grinder/controller/ImageEditorController.h index 992d597..e215aef 100644 --- a/Grinder/controller/ImageEditorController.h +++ b/Grinder/controller/ImageEditorController.h @@ -51,13 +51,14 @@ namespace grndr void copyLayer(const Layer* layer) const; void pasteLayer(); void cutLayer(const Layer* layer); - void removeLayer(const Layer* layer); - void removeAllLayers(); + void removeLayer(const Layer* layer, bool enforceRemoval = false); + void removeAllLayers(bool enforceRemoval = false); std::shared_ptr<DraftItem> createDraftItem(DraftItemType type, Layer* layer = nullptr, bool autoCreateLayer = true); void removeDraftItem(const DraftItem* item) const; - void setLayerVisibility(Layer* layer, bool visible) const; + void setLayerVisibility(Layer* layer, bool visible) const; + void setLayerAlpha(Layer* layer, float alpha) const; void moveLayer(Layer* layer, bool up) const; void renameLayer(Layer* layer, QString newName) const; void renameImageBuildItem(ImageBuildItem* item, QString newName) const; @@ -103,6 +104,7 @@ namespace grndr void layerRemoved(const std::shared_ptr<Layer>& layer) const; void layerMoved(const std::shared_ptr<Layer>& layer, int indexOld, int indexNew) const; void layerVisibilityChanged() const; + void layerAlphaChanged(float alpha) const; void draftItemCreated(const std::shared_ptr<DraftItem>& item) const; void draftItemRemoved(const std::shared_ptr<DraftItem>& item) const; diff --git a/Grinder/image/ImageBuild.cpp b/Grinder/image/ImageBuild.cpp index ff31fc4..27c817d 100644 --- a/Grinder/image/ImageBuild.cpp +++ b/Grinder/image/ImageBuild.cpp @@ -7,6 +7,7 @@ #include "ImageBuild.h" #include "ImageExceptions.h" #include "pipeline/Block.h" +#include "project/ImageReference.h" #include "properties/ImageTagsProperty.h" const char* ImageBuild::Serialization_Value_ImageReferences = "ImageReferences"; @@ -31,12 +32,19 @@ ImageBuild::ImageBuild(const Block* block, const std::vector<const ImageReferenc void ImageBuild::initImageBuild() { createProperties(); + + // Create background image layers for all assigned image references + for (const auto imageRef : _imageReferences) + { + if (auto backgroundLayer = createLayer(QString{"Bg: %1"}.arg(imageRef->getImageFileName()), Layer::Type::BackgroundImage, imageRef)) + backgroundLayer->setVisible(false); + } } -std::shared_ptr<Layer> ImageBuild::createLayer(QString name) +std::shared_ptr<Layer> ImageBuild::createLayer(QString name, Layer::Type layerType, const ImageReference* backgroundImage) { // Create new layer - auto layer = std::make_shared<Layer>(this, name); + auto layer = std::make_shared<Layer>(this, name, layerType, backgroundImage); try { // Propagate initialization errors to the caller layer->initLayer(); @@ -138,9 +146,23 @@ void ImageBuild::deserialize(DeserializationContext& ctx) // Deserialize all layers if (ctx.beginGroup(LayerVector::Serialization_Group)) { - _layers.deserialize(LayerVector::Serialization_Element, ctx, [this](const SettingsContainer& settings) { + _layers.deserialize(LayerVector::Serialization_Element, ctx, [this, ctx](const SettingsContainer& settings) { + Layer::Type type = static_cast<Layer::Type>(settings[Layer::Serialization_Value_Type].toInt()); + int backgroundImageIndex = settings[Layer::Serialization_Value_BackgroundImage].toInt(); + const ImageReference* backgroundImage = nullptr; + + // Search for an existing background image layer (as they are created during initialization) + if (backgroundImageIndex != -1 && type != Layer::Type::Standard) + { + if ((backgroundImage = ctx.getImageReference(backgroundImageIndex))) + { + if (auto backgroundLayer = _layers.selectByBackgroundImage(backgroundImage)) + return backgroundLayer; + } + } + QString name = settings[Layer::Serialization_Value_Name].toString(); - return createLayer(name); + return createLayer(name, type, backgroundImage); }); ctx.endGroup(); diff --git a/Grinder/image/ImageBuild.h b/Grinder/image/ImageBuild.h index e740f66..b9e8057 100644 --- a/Grinder/image/ImageBuild.h +++ b/Grinder/image/ImageBuild.h @@ -31,7 +31,7 @@ namespace grndr void initImageBuild(); public: - std::shared_ptr<Layer> createLayer(QString name = ""); + std::shared_ptr<Layer> createLayer(QString name = "", Layer::Type layerType = Layer::Type::Standard, const ImageReference* backgroundImage = nullptr); void removeLayer(const Layer* layer); void removeAllLayers(); diff --git a/Grinder/image/ImageBuildItem.cpp b/Grinder/image/ImageBuildItem.cpp index fe785e4..f4a5066 100644 --- a/Grinder/image/ImageBuildItem.cpp +++ b/Grinder/image/ImageBuildItem.cpp @@ -28,5 +28,6 @@ void ImageBuildItem::serialize(SerializationContext& ctx) const void ImageBuildItem::deserialize(DeserializationContext& ctx) { // Deserialize values - _name = ctx.settings()[Serialization_Value_Name].toString(); + if (ctx.getMode() != DeserializationContext::Mode::ClipboardSerialization) + _name = ctx.settings()[Serialization_Value_Name].toString(); } diff --git a/Grinder/image/ImageBuildPool.cpp b/Grinder/image/ImageBuildPool.cpp index 527c355..6983c61 100644 --- a/Grinder/image/ImageBuildPool.cpp +++ b/Grinder/image/ImageBuildPool.cpp @@ -67,7 +67,7 @@ std::shared_ptr<ImageBuild> ImageBuildPool::imageBuild(const Block* block, const std::shared_ptr<ImageBuild> ImageBuildPool::imageBuild(const Block* block, const ImageReference* activeImageReference) { // Get the image build for the given block and all its used image references - std::set<const ImageReference*> imageReferences = block->assembleInputImageReferences(activeImageReference); + auto imageReferences = block->assembleInputImageReferences(activeImageReference); return imageBuild(block, std::vector<const ImageReference*>{imageReferences.cbegin(), imageReferences.cend()}); } diff --git a/Grinder/image/Layer.cpp b/Grinder/image/Layer.cpp index 9a13a09..6b3310d 100644 --- a/Grinder/image/Layer.cpp +++ b/Grinder/image/Layer.cpp @@ -9,12 +9,16 @@ #include "ImageExceptions.h" #include "DraftItemCatalog.h" +const char* Layer::Serialization_Value_Type = "Type"; const char* Layer::Serialization_Value_Visible = "Visible"; +const char* Layer::Serialization_Value_Alpha = "Alpha"; +const char* Layer::Serialization_Value_BackgroundImage = "BackgroundImage"; -Layer::Layer(ImageBuild* imageBuild, QString name) : ImageBuildItem(imageBuild, name), - _draftItems{this}, _layerPixels{this} +Layer::Layer(ImageBuild* imageBuild, QString name, Type type, const ImageReference* backgroundImage) : ImageBuildItem(imageBuild, name), + _type{type}, _draftItems{this}, _layerPixels{this}, _backgroundImage{backgroundImage} { - + if (type == Type::Standard) // Standard layers don't have a background + _backgroundImage = nullptr; } void Layer::initLayer() @@ -81,12 +85,27 @@ void Layer::setVisible(bool visible) } } +void Layer::setAlpha(unsigned int alpha) +{ + if (alpha > 100) + alpha = 100; + + if (alpha != _alpha) + { + _alpha = alpha; + emit layerAlphaChanged(_alpha); + } +} + void Layer::serialize(SerializationContext& ctx) const { ImageBuildItem::serialize(ctx); // Serialize values + ctx.settings()[Serialization_Value_Type] = static_cast<int>(_type); ctx.settings()[Serialization_Value_Visible] = _isVisible; + ctx.settings()[Serialization_Value_Alpha] = _alpha; + ctx.settings()[Serialization_Value_BackgroundImage] = _backgroundImage ? ctx.getImageReferenceIndex(_backgroundImage) : -1; // Serialize all draft items ctx.beginGroup(DraftItemVector::Serialization_Group, true); @@ -104,8 +123,17 @@ void Layer::deserialize(DeserializationContext& ctx) ImageBuildItem::deserialize(ctx); // Deserialize values + _type = static_cast<Type>(ctx.settings()[Serialization_Value_Type].toInt()); + _alpha = ctx.settings()(Serialization_Value_Alpha, 100).toUInt(); _isVisible = ctx.settings()[Serialization_Value_Visible].toBool(); + int backgroundImageIndex = ctx.settings()(Serialization_Value_BackgroundImage, -1).toInt(); + + if (backgroundImageIndex != -1) + _backgroundImage = ctx.getImageReference(backgroundImageIndex); + else + _backgroundImage = nullptr; + // Deserialize all draft items if (ctx.beginGroup(DraftItemVector::Serialization_Group)) { diff --git a/Grinder/image/Layer.h b/Grinder/image/Layer.h index 0354293..d4c8b16 100644 --- a/Grinder/image/Layer.h +++ b/Grinder/image/Layer.h @@ -17,10 +17,19 @@ namespace grndr Q_OBJECT public: + static const char* Serialization_Value_Type; static const char* Serialization_Value_Visible; + static const char* Serialization_Value_Alpha; + static const char* Serialization_Value_BackgroundImage; + + enum class Type + { + Standard, + BackgroundImage, + }; public: - Layer(ImageBuild* imageBuild, QString name = ""); + Layer(ImageBuild* imageBuild, QString name = "", Type type = Type::Standard, const ImageReference* backgroundImage = nullptr); public: void initLayer(); @@ -30,15 +39,24 @@ namespace grndr void removeDraftItem(const DraftItem* item); public: + Type getType() const { return _type; } + int getZOrder() const; bool isVisible() const { return _isVisible; } void setVisible(bool visible = true); + unsigned int getAlpha() const { return _alpha; } + void setAlpha(unsigned int alpha); + + bool isRemovable() const { return _type == Type::Standard; } const DraftItemVector& draftItems() const { return _draftItems; } LayerPixels& layerPixels() { return _layerPixels; } const LayerPixels& layerPixels() const { return _layerPixels; } + const ImageReference* backgroundImage() const { return _backgroundImage; } + void setBackgroundImage(const ImageReference* imageRef) { _backgroundImage = imageRef; } + public: virtual void serialize(SerializationContext& ctx) const override; virtual void deserialize(DeserializationContext& ctx) override; @@ -46,15 +64,21 @@ namespace grndr signals: void layerShown(); void layerHidden(); + void layerAlphaChanged(unsigned int); void draftItemCreated(const std::shared_ptr<DraftItem>&); void draftItemRemoved(const std::shared_ptr<DraftItem>&); private: + Type _type{Type::Standard}; + bool _isVisible{true}; + unsigned int _alpha{100}; DraftItemVector _draftItems; LayerPixels _layerPixels; + + const ImageReference* _backgroundImage{nullptr}; }; } diff --git a/Grinder/image/LayerPixelsData.cpp b/Grinder/image/LayerPixelsData.cpp index 4c79d31..e358ae6 100644 --- a/Grinder/image/LayerPixelsData.cpp +++ b/Grinder/image/LayerPixelsData.cpp @@ -110,4 +110,6 @@ void LayerPixelsData::deserialize(DeserializationContext& ctx) // Deserialize all color map entries for (auto it = ctx.settings().values().begin(); it != ctx.settings().values().end(); ++it) _colorMap[QColor{it.key()}].fromString(it->toString()); + + emit dataModified(); } diff --git a/Grinder/image/LayerVector.cpp b/Grinder/image/LayerVector.cpp index 9a989af..395fb05 100644 --- a/Grinder/image/LayerVector.cpp +++ b/Grinder/image/LayerVector.cpp @@ -20,3 +20,8 @@ LayerVector::pointer_type LayerVector::selectByName(QString name, bool caseSensi { return selectFirst([name, caseSensitive](auto layer) { return layer->getName().compare(name, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive) == 0; }); } + +LayerVector::pointer_type LayerVector::selectByBackgroundImage(const ImageReference* imageRef) const +{ + return selectFirst([imageRef](auto layer) { return layer->backgroundImage() == imageRef; }); +} diff --git a/Grinder/image/LayerVector.h b/Grinder/image/LayerVector.h index 678bb0d..65d9554 100644 --- a/Grinder/image/LayerVector.h +++ b/Grinder/image/LayerVector.h @@ -22,6 +22,7 @@ namespace grndr public: pointer_type selectByName(QString name, bool caseSensitive = false) const; + pointer_type selectByBackgroundImage(const ImageReference* imageRef) const; private: const ImageBuild* _imageBuild{nullptr}; diff --git a/Grinder/ui/image/DraftItemNode.cpp b/Grinder/ui/image/DraftItemNode.cpp index 924c090..b6c75b1 100644 --- a/Grinder/ui/image/DraftItemNode.cpp +++ b/Grinder/ui/image/DraftItemNode.cpp @@ -13,7 +13,7 @@ #include "util/MathUtils.h" #include "res/Resources.h" -DraftItemNode::DraftItemNode(ImageEditorScene* scene, const std::shared_ptr<DraftItem>& item, QGraphicsItem* parent) : ImageEditorNode(scene, parent), +DraftItemNode::DraftItemNode(ImageEditorScene* scene, const std::shared_ptr<DraftItem>& item, QGraphicsItem* parent) : LayerItemNode(scene, item->layer(), parent), _draftItem{item}, _draftItemStyle{_style.getItemNodeStyle()} { if (!item) @@ -37,6 +37,7 @@ DraftItemNode::DraftItemNode(ImageEditorScene* scene, const std::shared_ptr<Draf updateZOrder(); updateVisibility(); + updateAlpha(); // Create node actions _deleteAction = createNodeAction("&Delete item(s)", FILE_ICON_DELETE, SLOT(deleteNode()), "Remove the selected items", "Del"); @@ -67,17 +68,14 @@ void DraftItemNode::updateNode() for (const auto& imageTag : draftItem->imageTagsAllotment()->object().allottedTags()) imageTags << imageTag->getName(); - setToolTip(QString{"<b>Tags:</b> %1"}.arg(!imageTags.isEmpty() ? imageTags.join(", ") : "None")); + setToolTip(QString{"<b>Layer:</b> %2<br><b>Tags:</b> %1"}.arg(!imageTags.isEmpty() ? imageTags.join(", ") : "None").arg(draftItem->layer()->getName())); } - prepareGeometryChange(); - updateGeometry(); + LayerItemNode::updateNode(); // Also update the in-place editor if (_inPlaceEditor) _inPlaceEditor->updateEditor(); - - update(); } void DraftItemNode::updateZOrder() @@ -93,12 +91,6 @@ void DraftItemNode::updateZOrder() } } -void DraftItemNode::updateVisibility() -{ - if (auto draftItem = _draftItem.lock()) // Make sure that the underlying draft item still exists - setVisible(draftItem->layer()->isVisible()); // Use the visibility flag of the layer -} - QVariant DraftItemNode::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value) { if (change == QGraphicsItem::ItemPositionHasChanged) diff --git a/Grinder/ui/image/DraftItemNode.h b/Grinder/ui/image/DraftItemNode.h index 13d68d2..14ca5d3 100644 --- a/Grinder/ui/image/DraftItemNode.h +++ b/Grinder/ui/image/DraftItemNode.h @@ -6,13 +6,13 @@ #ifndef DRAFTITEMNODE_H #define DRAFTITEMNODE_H -#include "ImageEditorNode.h" +#include "LayerItemNode.h" #include "ImageEditorStyle.h" #include "InPlaceEditor.h" namespace grndr { - class DraftItemNode : public ImageEditorNode + class DraftItemNode : public LayerItemNode { Q_OBJECT @@ -27,10 +27,9 @@ namespace grndr const std::weak_ptr<DraftItem>& draftItem() const { return _draftItem; } public slots: - void updateNode(); + virtual void updateNode() override; - void updateZOrder(); - void updateVisibility(); + virtual void updateZOrder() override; protected: virtual std::unique_ptr<InPlaceEditor> createInPlaceEditor() { return std::unique_ptr<InPlaceEditor>{}; } diff --git a/Grinder/ui/image/ImageEditorScene.cpp b/Grinder/ui/image/ImageEditorScene.cpp index 580c95e..e20434c 100644 --- a/Grinder/ui/image/ImageEditorScene.cpp +++ b/Grinder/ui/image/ImageEditorScene.cpp @@ -7,6 +7,7 @@ #include "ImageEditorScene.h" #include "ImageEditor.h" #include "DraftItemNode.h" +#include "LayerBackgroundNode.h" #include "LayerPixelsNode.h" #include "image/ImageExceptions.h" #include "controller/ImageEditorController.h" @@ -39,6 +40,7 @@ void ImageEditorScene::buildScene() for (const auto& item : layer->draftItems()) createDraftItemNode(item); + createLayerBackgroundNode(layer); createLayerPixelsNode(layer); } } @@ -100,16 +102,22 @@ void ImageEditorScene::removeDraftItemNode(const std::shared_ptr<DraftItem>& ite removeNode(findDraftItemNode(item.get())); } -void ImageEditorScene::updateDraftItemsOrder() +void ImageEditorScene::createLayerBackgroundNode(const std::shared_ptr<Layer>& layer) { - for (auto& draftItem : findDraftItemNodes()) - draftItem->updateZOrder(); + if (!layer) + throw std::invalid_argument{_EXCPT("layer may not be null")}; + + if (auto backgroundImage = layer->backgroundImage()) + { + if (!findLayerBackgroundNode(backgroundImage)) + addItem(new LayerBackgroundNode{this, layer.get()}); + } } -void ImageEditorScene::updateDraftItemsVisibility(const Layer* layer) +void ImageEditorScene::removeLayerBackgroundNode(const std::shared_ptr<Layer>& layer) { - for (auto& draftItem : findDraftItemNodes(layer)) - draftItem->updateVisibility(); + if (auto backgroundImage = layer->backgroundImage()) + removeNode(findLayerBackgroundNode(backgroundImage)); } void ImageEditorScene::createLayerPixelsNode(const std::shared_ptr<Layer>& layer) @@ -118,7 +126,7 @@ void ImageEditorScene::createLayerPixelsNode(const std::shared_ptr<Layer>& layer throw std::invalid_argument{_EXCPT("layer may not be null")}; if (!findLayerPixelsNode(&layer->layerPixels())) - addItem(new LayerPixelsNode{this, layer}); + addItem(new LayerPixelsNode{this, layer.get()}); } void ImageEditorScene::removeLayerPixelsNode(const std::shared_ptr<Layer>& layer) @@ -126,16 +134,22 @@ void ImageEditorScene::removeLayerPixelsNode(const std::shared_ptr<Layer>& layer removeNode(findLayerPixelsNode(&layer->layerPixels())); } -void ImageEditorScene::updateLayerPixelsOrder() +void ImageEditorScene::updateLayerItemsOrder() { - for (auto& layerPixels : findLayerPixelsNodes()) - layerPixels->updateZOrder(); + for (auto& layerItem : findLayerItemNodes()) + layerItem->updateZOrder(); } -void ImageEditorScene::updateLayerPixelsVisibility(const Layer* layer) +void ImageEditorScene::updateLayerItemsVisibility(const Layer* layer) { - for (auto& layerPixels : findLayerPixelsNodes(layer)) - layerPixels->updateVisibility(); + for (auto& layerItem : findLayerItemNodes(layer)) + layerItem->updateVisibility(); +} + +void ImageEditorScene::updateLayerItemsAlpha(const Layer* layer) +{ + for (auto& layerItem : findLayerItemNodes(layer)) + layerItem->updateAlpha(); } void ImageEditorScene::mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent) @@ -219,17 +233,28 @@ DraftItemNode* ImageEditorScene::_findDraftItemNode(const DraftItem* item) const return nullptr; } +LayerBackgroundNode* ImageEditorScene::_findLayerBackgroundNode(const ImageReference* imageRef) const +{ + for (const auto& node : items()) + { + if (auto itemNode = dynamic_cast<LayerBackgroundNode*>(node)) + { + if (itemNode->layer()->backgroundImage() == imageRef) + return itemNode; + } + } + + return nullptr; +} + LayerPixelsNode* ImageEditorScene::_findLayerPixelsNode(const LayerPixels* layerPixels) const { for (const auto& node : items()) { if (auto itemNode = dynamic_cast<LayerPixelsNode*>(node)) { - if (auto layer = itemNode->layer().lock()) - { - if (&layer->layerPixels() == layerPixels) - return itemNode; - } + if (&itemNode->layer()->layerPixels() == layerPixels) + return itemNode; } } diff --git a/Grinder/ui/image/ImageEditorScene.h b/Grinder/ui/image/ImageEditorScene.h index e6e2a35..bd658ef 100644 --- a/Grinder/ui/image/ImageEditorScene.h +++ b/Grinder/ui/image/ImageEditorScene.h @@ -16,7 +16,8 @@ namespace grndr { class ImageEditor; - class ImageEditorNode; + class LayerItemNode; + class LayerBackgroundNode; class LayerPixelsNode; class ImageEditorScene : public VisualScene<ImageEditorView>, public ImageEditorComponent @@ -35,27 +36,30 @@ namespace grndr void createDraftItemNode(const std::shared_ptr<DraftItem>& item); void removeDraftItemNode(const std::shared_ptr<DraftItem>& item); - void updateDraftItemsOrder(); - void updateDraftItemsVisibility(const Layer* layer = nullptr); - - void createLayerPixelsNode(const std::shared_ptr<Layer>& layer); + void createLayerBackgroundNode(const std::shared_ptr<Layer>& layer); + void removeLayerBackgroundNode(const std::shared_ptr<Layer>& layer); + void createLayerPixelsNode(const std::shared_ptr<Layer>& layer); void removeLayerPixelsNode(const std::shared_ptr<Layer>& layer); - void updateLayerPixelsOrder(); - void updateLayerPixelsVisibility(const Layer* layer = nullptr); + void updateLayerItemsOrder(); + void updateLayerItemsVisibility(const Layer* layer); + void updateLayerItemsAlpha(const Layer* layer); void removeNode(QGraphicsItem* node) { if (node) delete node; } public: + template<typename ValType = LayerItemNode> + std::vector<ValType*> findLayerItemNodes(const Layer* layer = nullptr) { return _findLayerItemNodes<ValType>(layer); } + template<typename ValType = LayerItemNode> + std::vector<const ValType*> findLayerItemNodes(const Layer* layer = nullptr) const { return _findLayerItemNodes<const ValType>(layer); } + DraftItemNode* findDraftItemNode(const DraftItem* item) { return _findDraftItemNode(item); } const DraftItemNode* findDraftItemNode(const DraftItem* item) const { return _findDraftItemNode(item); } - std::vector<DraftItemNode*> findDraftItemNodes(const Layer* layer = nullptr) { return _findDraftItemNodes<DraftItemNode*>(layer); } - std::vector<const DraftItemNode*> findDraftItemNodes(const Layer* layer = nullptr) const { return _findDraftItemNodes<const DraftItemNode*>(layer); } - LayerPixelsNode* findLayerPixelsNode(const LayerPixels* layerPixels) { return _findLayerPixelsNode(layerPixels); } + LayerBackgroundNode* findLayerBackgroundNode(const ImageReference* imageRef) { return _findLayerBackgroundNode(imageRef); } + const LayerBackgroundNode* findLayerBackgroundNode(const ImageReference* imageRef) const { return _findLayerBackgroundNode(imageRef); } + LayerPixelsNode* findLayerPixelsNode(const LayerPixels* layerPixels) { return _findLayerPixelsNode(layerPixels); } const LayerPixelsNode* findLayerPixelsNode(const LayerPixels* layerPixels) const { return _findLayerPixelsNode(layerPixels); } - std::vector<LayerPixelsNode*> findLayerPixelsNodes(const Layer* layer = nullptr) { return _findLayerPixelsNodes<LayerPixelsNode*>(layer); } - std::vector<const LayerPixelsNode*> findLayerPixelsNodes(const Layer* layer = nullptr) const { return _findLayerPixelsNodes<const LayerPixelsNode*>(layer); } public: ImageBuild* imageBuild() { return _imageBuild; } @@ -78,13 +82,13 @@ namespace grndr ImageEditorTool::InputEventResult handleKeyEvent(QKeyEvent* keyEvent, std::function<ImageEditorTool::InputEventResult(ImageEditorTool*, QKeyEvent*)> handler); private: - DraftItemNode* _findDraftItemNode(const DraftItem* item) const; template<typename ValType> - std::vector<ValType> _findDraftItemNodes(const Layer* layer = nullptr) const; + std::vector<ValType*> _findLayerItemNodes(const Layer* layer = nullptr) const; + DraftItemNode* _findDraftItemNode(const DraftItem* item) const; + + LayerBackgroundNode* _findLayerBackgroundNode(const ImageReference* imageRef) const; LayerPixelsNode* _findLayerPixelsNode(const LayerPixels* layerPixels) const; - template<typename ValType> - std::vector<ValType> _findLayerPixelsNodes(const Layer* layer = nullptr) const; private: ImageBuild* _imageBuild{nullptr}; diff --git a/Grinder/ui/image/ImageEditorScene.impl.h b/Grinder/ui/image/ImageEditorScene.impl.h index 6d5fc97..9bc504f 100644 --- a/Grinder/ui/image/ImageEditorScene.impl.h +++ b/Grinder/ui/image/ImageEditorScene.impl.h @@ -9,38 +9,16 @@ #include "LayerPixelsNode.h" template<typename ValType> -std::vector<ValType> ImageEditorScene::_findDraftItemNodes(const Layer* layer) const +std::vector<ValType*> ImageEditorScene::_findLayerItemNodes(const Layer* layer) const { - std::vector<ValType> nodes; + std::vector<ValType*> nodes; for (const auto& node : items()) { - if (auto itemNode = dynamic_cast<DraftItemNode*>(node)) + if (auto itemNode = dynamic_cast<ValType*>(node)) { - if (auto item = itemNode->draftItem().lock()) - { - if (!layer || item->layer() == layer) - nodes.push_back(itemNode); - } - } - } - - return nodes; -} -template<typename ValType> -std::vector<ValType> ImageEditorScene::_findLayerPixelsNodes(const Layer* layer) const -{ - std::vector<ValType> nodes; - - for (const auto& node : items()) - { - if (auto pixelsNode = dynamic_cast<LayerPixelsNode*>(node)) - { - if (auto pixelsLayer = pixelsNode->layer().lock()) - { - if (!layer || pixelsLayer.get() == layer) - nodes.push_back(pixelsNode); - } + if (!layer || itemNode->layer() == layer) + nodes.push_back(itemNode); } } diff --git a/Grinder/ui/image/LayerBackgroundNode.cpp b/Grinder/ui/image/LayerBackgroundNode.cpp new file mode 100644 index 0000000..be9e535 --- /dev/null +++ b/Grinder/ui/image/LayerBackgroundNode.cpp @@ -0,0 +1,67 @@ +/****************************************************************************** + * File: LayerBackgroundNode.cpp + * Date: 15.6.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "LayerBackgroundNode.h" +#include "image/Layer.h" +#include "project/ImageReference.h" +#include "util/CVUtils.h" +#include "res/Resources.h" + +LayerBackgroundNode::LayerBackgroundNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent) : LayerItemNode(scene, layer, parent), + _imageNode{new QGraphicsPixmapItem{this}} +{ + setFlags(0); // This item doesn't behave like an item at all + + // Set up the background node + _imageNode->setPos(QPointF{0.0, 0.0}); + _imageNode->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); + _imageNode->setTransformationMode(Qt::SmoothTransformation); + + refreshImage(); + + updateZOrder(); + updateVisibility(); + updateAlpha(); + + updateNode(); +} + +void LayerBackgroundNode::refreshImage() +{ + bool resetPixmap = true; + + if (auto backgroundImage = _layer->backgroundImage()) + { + try { + auto imageData = backgroundImage->loadImage(); + auto image = CVUtils::matrixToPixmap(imageData); + + if (!image.isNull()) + { + _imageNode->setPixmap(image); + resetPixmap = false; + } + } catch (...) { + // Just ignore any errors + } + } + + if (resetPixmap) + _imageNode->setPixmap(QPixmap{FILE_ICON_EDITOR_NOIMAGE}); // If the image is invalid, show a question mark indicating an error + + updateNode(); +} + +void LayerBackgroundNode::updateZOrder() +{ + setZValue(_layer->getZOrder() - 0.5); // Place the background just below the layer pixels +} + +void LayerBackgroundNode::updateGeometry() +{ + _nodeRect = _nodeRectSelected = _imageNode->boundingRect(); + ImageEditorNode::updateGeometry(); +} diff --git a/Grinder/ui/image/LayerBackgroundNode.h b/Grinder/ui/image/LayerBackgroundNode.h new file mode 100644 index 0000000..d2cab9c --- /dev/null +++ b/Grinder/ui/image/LayerBackgroundNode.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * File: LayerBackgroundNode.h + * Date: 15.6.2018 + *****************************************************************************/ + +#ifndef LAYERBACKGROUNDNODE_H +#define LAYERBACKGROUNDNODE_H + +#include "LayerItemNode.h" + +namespace grndr +{ + class LayerPixels; + + class LayerBackgroundNode : public LayerItemNode + { + Q_OBJECT + + public: + LayerBackgroundNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent = nullptr); + + public: + void refreshImage(); + + public slots: + virtual void updateZOrder() override; + + protected: + virtual void updateGeometry() override; + + private: + QGraphicsPixmapItem* _imageNode{nullptr}; + }; +} + +#endif diff --git a/Grinder/ui/image/LayerItemNode.cpp b/Grinder/ui/image/LayerItemNode.cpp new file mode 100644 index 0000000..9714418 --- /dev/null +++ b/Grinder/ui/image/LayerItemNode.cpp @@ -0,0 +1,38 @@ +/****************************************************************************** + * File: LayerItemNode.cpp + * Date: 15.6.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "LayerItemNode.h" +#include "image/Layer.h" + +LayerItemNode::LayerItemNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent) : ImageEditorNode(scene, parent), + _layer{layer} +{ + if (!layer) + throw std::invalid_argument{_EXCPT("layer may not be null")}; +} + +void LayerItemNode::updateNode() +{ + prepareGeometryChange(); + updateGeometry(); + + update(); +} + +void LayerItemNode::updateZOrder() +{ + setZValue(_layer->getZOrder()); // Use the z-order of the layer +} + +void LayerItemNode::updateVisibility() +{ + setVisible(_layer->isVisible()); // Use the visibility flag of the layer +} + +void LayerItemNode::updateAlpha() +{ + setOpacity(_layer->getAlpha() / 100.0f); // Use the alpha value of the layer +} diff --git a/Grinder/ui/image/LayerItemNode.h b/Grinder/ui/image/LayerItemNode.h new file mode 100644 index 0000000..d5ab0e9 --- /dev/null +++ b/Grinder/ui/image/LayerItemNode.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * File: LayerItemNode.h + * Date: 15.6.2018 + *****************************************************************************/ + +#ifndef LAYERITEMNODE_H +#define LAYERITEMNODE_H + +#include "ImageEditorNode.h" + +namespace grndr +{ + class Layer; + + class LayerItemNode : public ImageEditorNode + { + Q_OBJECT + + public: + LayerItemNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent = nullptr); + + public: + Layer* layer() { return _layer; } + const Layer* layer() const { return _layer; } + + public slots: + virtual void updateNode(); + + virtual void updateZOrder(); + virtual void updateVisibility(); + virtual void updateAlpha(); + + protected: + Layer* _layer{nullptr}; + }; +} + +#endif diff --git a/Grinder/ui/image/LayerPixelsNode.cpp b/Grinder/ui/image/LayerPixelsNode.cpp index 1cf793a..ca87a44 100644 --- a/Grinder/ui/image/LayerPixelsNode.cpp +++ b/Grinder/ui/image/LayerPixelsNode.cpp @@ -8,50 +8,30 @@ #include "ImageEditorStyle.h" #include "image/Layer.h" -LayerPixelsNode::LayerPixelsNode(ImageEditorScene* scene, const std::shared_ptr<Layer>& layer, QGraphicsItem* parent) : ImageEditorNode(scene, parent), - _layer{layer} +LayerPixelsNode::LayerPixelsNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent) : LayerItemNode(scene, layer, parent) { setFlags(0); // This item doesn't behave like an item at all - if (auto layer = _layer.lock()) // Make sure that the underlying layer still exists - { - // Create the renderer for the layer pixels - _renderer = layer->layerPixels().createRenderer(_style.getItemNodeStyle()); + // Create the renderer for the layer pixels + _renderer = layer->layerPixels().createRenderer(_style.getItemNodeStyle()); - // Update the node whenever the pixels data has changed - connect(&layer->layerPixels().data(), &LayerPixelsData::dataModified, this, &LayerPixelsNode::updateNode); - } + // Update the node whenever the pixels data has changed + connect(&layer->layerPixels().data(), &LayerPixelsData::dataModified, this, &LayerPixelsNode::updateNode); updateZOrder(); updateVisibility(); + updateAlpha(); updateNode(); } -void LayerPixelsNode::updateNode() -{ - prepareGeometryChange(); - updateGeometry(); - - update(); -} - void LayerPixelsNode::updateZOrder() { - if (auto layer = _layer.lock()) // Make sure that the underlying layer still exists - setZValue(layer->getZOrder() - 0.1); // Place the pixels just below all other items -} - -void LayerPixelsNode::updateVisibility() -{ - if (auto layer = _layer.lock()) // Make sure that the underlying layer still exists - setVisible(layer->isVisible()); // Use the visibility flag of the layer + setZValue(_layer->getZOrder() - 0.1); // Place the pixels just below all other items } void LayerPixelsNode::updateGeometry() { - if (auto layer = _layer.lock()) // Make sure that the underlying layer still exists - _nodeRect = _nodeRectSelected = layer->layerPixels().data().bounds(); - + _nodeRect = _nodeRectSelected = _layer->layerPixels().data().bounds(); ImageEditorNode::updateGeometry(); } diff --git a/Grinder/ui/image/LayerPixelsNode.h b/Grinder/ui/image/LayerPixelsNode.h index b1fe997..d6f176b 100644 --- a/Grinder/ui/image/LayerPixelsNode.h +++ b/Grinder/ui/image/LayerPixelsNode.h @@ -6,35 +6,24 @@ #ifndef LAYERPIXELSNODE_H #define LAYERPIXELSNODE_H -#include "ImageEditorNode.h" +#include "LayerItemNode.h" namespace grndr { - class Layer; class LayerPixels; - class LayerPixelsNode : public ImageEditorNode + class LayerPixelsNode : public LayerItemNode { Q_OBJECT public: - LayerPixelsNode(ImageEditorScene* scene, const std::shared_ptr<Layer>& layer, QGraphicsItem* parent = nullptr); - - public: - std::weak_ptr<Layer>& layer() { return _layer; } - const std::weak_ptr<Layer>& layer() const { return _layer; } + LayerPixelsNode(ImageEditorScene* scene, Layer* layer, QGraphicsItem* parent = nullptr); public slots: - void updateNode(); - - void updateZOrder(); - void updateVisibility(); + virtual void updateZOrder() override; protected: virtual void updateGeometry() override; - - private: - std::weak_ptr<Layer> _layer; }; } diff --git a/Grinder/ui/image/LayersListItem.cpp b/Grinder/ui/image/LayersListItem.cpp index f9acda4..eef43f9 100644 --- a/Grinder/ui/image/LayersListItem.cpp +++ b/Grinder/ui/image/LayersListItem.cpp @@ -9,6 +9,10 @@ LayersListItem::LayersListItem(Layer* layer) : ActiveObjectListItem(layer) { + _specialFont.setItalic(true); + _specialActiveFont.setItalic(true); + _specialActiveFont.setBold(true); + setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsUserCheckable); setIcon(QIcon{FILE_ICON_LAYER}); @@ -19,6 +23,14 @@ void LayersListItem::updateItem() { ActiveObjectListItem::updateItem(); + if (getType() != Layer::Type::Standard) + { + if (_isActive) + this->setFont(_specialActiveFont); + else + this->setFont(_specialFont); + } + setText(getName()); setCheckState(isVisible() ? Qt::Checked : Qt::Unchecked); } diff --git a/Grinder/ui/image/LayersListItem.h b/Grinder/ui/image/LayersListItem.h index 7386912..0e6bd3d 100644 --- a/Grinder/ui/image/LayersListItem.h +++ b/Grinder/ui/image/LayersListItem.h @@ -21,7 +21,13 @@ namespace grndr public: QString getName() const { return _object->getName(); } + Layer::Type getType() const { return _object->getType(); } bool isVisible() const { return _object->isVisible(); } + bool isRemovable() const { return _object->isRemovable(); } + + private: + QFont _specialFont; + QFont _specialActiveFont; }; } diff --git a/Grinder/ui/image/LayersListWidget.cpp b/Grinder/ui/image/LayersListWidget.cpp index 5599081..73e6b2f 100644 --- a/Grinder/ui/image/LayersListWidget.cpp +++ b/Grinder/ui/image/LayersListWidget.cpp @@ -9,13 +9,15 @@ #include "image/ImageBuild.h" #include "core/GrinderApplication.h" #include "ui/widgets/ControlBar.h" +#include "ui/widgets/SliderValueWidget.h" #include "util/UIUtils.h" #include "util/StringUtils.h" #include "res/Resources.h" -LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent) +LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent), + _sliderValueWidget{new SliderValueWidget{"Opacity:", 0, 100, "%"}} { - // Create labels actions + // Create layers actions _renameLayerAction = UIUtils::createAction(this, "Rename &layer", FILE_ICON_EDIT, SLOT(renameLayer()), "Rename the selected layer", "F2"); _moveUpAction = UIUtils::createAction(this, "Move &up", FILE_ICON_MOVEUP, SLOT(moveLayerUp()), "Move the selected layer up", "Ctrl+Shift+Up"); _moveDownAction = UIUtils::createAction(this, "Move &down", FILE_ICON_MOVEDOWN, SLOT(moveLayerDown()), "Move the selected layer down", "Ctrl+Shift+Down"); @@ -29,6 +31,14 @@ LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent) _removeLayerAction = UIUtils::createAction(this, "&Remove layer", FILE_ICON_DELETE, SLOT(removeLayer()), "Remove the selected layer", "Del"); _removeAllLayersAction = UIUtils::createAction(this, "Remove all layers", "", SLOT(removeAllLayers()), "Remove all layers"); + // Create the alpha slider action + auto sliderAction = new QWidgetAction{this}; + sliderAction->setDefaultWidget(_sliderValueWidget); + _alphaAction = sliderAction; + + // Update the layer opacity (alpha) if the value has been changed + connect(_sliderValueWidget, &SliderValueWidget::valueChanged, this, &LayersListWidget::layerOpacityChanged); + _moveUpAction->setData(Qt::ToolButtonIconOnly); _moveDownAction->setData(Qt::ToolButtonIconOnly); _convertPixelsToItemsAction->setData(Qt::ToolButtonIconOnly); @@ -101,16 +111,24 @@ std::vector<QAction*> LayersListWidget::getActions(MetaWidget::AddActionsMode mo actions.push_back(_newLayerAction); actions.push_back(_removeLayerAction); - actions.push_back(nullptr); + actions.push_back(nullptr); + + if (mode == AddActionsMode::ContextMenu) + { + actions.push_back(_alphaAction); + actions.push_back(nullptr); + } + actions.push_back(_convertPixelsToItemsAction); actions.push_back(nullptr); + actions.push_back(_moveUpAction); actions.push_back(_moveDownAction); if (mode == AddActionsMode::ContextMenu) { actions.push_back(nullptr); - actions.push_back(_removeAllLayersAction); + actions.push_back(_removeAllLayersAction); } return actions; @@ -219,6 +237,12 @@ void LayersListWidget::layerCheckChanged(QListWidgetItem* item) const _imageEditor->controller().setLayerVisibility(layerItem->object(), item->checkState() == Qt::Checked); } +void LayersListWidget::layerOpacityChanged(int value) const +{ + if (auto layer = currentObject()) + _imageEditor->controller().setLayerAlpha(layer, value); +} + void LayersListWidget::layerSwitching(Layer* layer) { if (layer) @@ -262,6 +286,13 @@ void LayersListWidget::updateActions() { auto currentLayer = currentObject(); bool layerSelected = (currentLayer != nullptr); + int removableLayersCount = 0; + + for (int i = 0; i < count(); ++i) + { + if (objectItem(i)->isRemovable()) + ++removableLayersCount; + } _renameLayerAction->setEnabled(layerSelected); _moveUpAction->setEnabled(layerSelected && currentRow() > 0); @@ -269,7 +300,9 @@ void LayersListWidget::updateActions() _convertPixelsToItemsAction->setEnabled(layerSelected && !currentLayer->layerPixels().data().empty()); _copyLayer->setEnabled(layerSelected); _pasteLayer->setEnabled(grinder()->clipboardManager().hasData(LayerVector::Serialization_Element)); - _cutLayer->setEnabled(layerSelected); - _removeLayerAction->setEnabled(layerSelected); - _removeAllLayersAction->setEnabled(count() > 0); + _cutLayer->setEnabled(layerSelected && currentLayer->isRemovable()); + _removeLayerAction->setEnabled(layerSelected && currentLayer->isRemovable()); + _removeAllLayersAction->setEnabled(removableLayersCount > 0); + + _sliderValueWidget->setValue(currentLayer ? currentLayer->getAlpha() : 0); } diff --git a/Grinder/ui/image/LayersListWidget.h b/Grinder/ui/image/LayersListWidget.h index 3094b6b..3cae37b 100644 --- a/Grinder/ui/image/LayersListWidget.h +++ b/Grinder/ui/image/LayersListWidget.h @@ -14,6 +14,7 @@ namespace grndr { class Layer; + class SliderValueWidget; using LayerObjectListWidget = ActiveObjectListWidget<Layer, LayersListItem>; @@ -53,6 +54,7 @@ namespace grndr void imageBuildSwitched(ImageBuild* imageBuild); void layerCheckChanged(QListWidgetItem* item) const; + void layerOpacityChanged(int value) const; void layerSwitching(Layer* layer); void layerSwitched(Layer* layer); void layerRenamed(QWidget* editor) const; @@ -61,6 +63,8 @@ namespace grndr void updateActions(); private: + SliderValueWidget* _sliderValueWidget{nullptr}; + QAction* _renameLayerAction{nullptr}; QAction* _newLayerAction{nullptr}; QAction* _moveUpAction{nullptr}; @@ -71,6 +75,7 @@ namespace grndr QAction* _cutLayer{nullptr}; QAction* _removeLayerAction{nullptr}; QAction* _removeAllLayersAction{nullptr}; + QAction* _alphaAction{nullptr}; }; } diff --git a/Grinder/ui/widgets/SliderValueWidget.cpp b/Grinder/ui/widgets/SliderValueWidget.cpp new file mode 100644 index 0000000..ea216df --- /dev/null +++ b/Grinder/ui/widgets/SliderValueWidget.cpp @@ -0,0 +1,56 @@ +/****************************************************************************** + * File: SliderValueWidget.cpp + * Date: 15.6.2018 + *****************************************************************************/ + +#include "Grinder.h" +#include "SliderValueWidget.h" +#include "core/GrinderApplication.h" + +SliderValueWidget::SliderValueWidget(QString label, int minValue, int maxValue, QString valueSuffix, QWidget* parent) : QWidget(parent), + _layout{new QHBoxLayout{this}} +{ + _layout->setContentsMargins(4, 2, 4, 2); + _layout->setSpacing(6); + + _slider = new QSlider{Qt::Horizontal}; + _slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + _slider->setRange(minValue, maxValue); + _slider->setTickInterval((maxValue - minValue) / 20); + _slider->setTickPosition(QSlider::TicksBelow); + + _spinBox = new QSpinBox{}; + _spinBox->setRange(minValue, maxValue); + _spinBox->setSuffix(valueSuffix); + + connect(_slider, &QSlider::valueChanged, this, &SliderValueWidget::sliderValueChanged); + connect(_spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &SliderValueWidget::spinBoxValueChanged); + + if (!label.isEmpty()) + { + auto labelWidget = new QLabel{label}; + labelWidget->setFont(QApplication::font("QMenu")); + _layout->addWidget(labelWidget); + } + + _layout->addWidget(_slider); + _layout->addWidget(_spinBox); +} + +void SliderValueWidget::sliderValueChanged(int value) +{ + if (_spinBox->value() != value) + { + _spinBox->setValue(value); + emit valueChanged(value); + } +} + +void SliderValueWidget::spinBoxValueChanged(int value) +{ + if (_slider->value() != value) + { + _slider->setValue(value); + emit valueChanged(value); + } +} diff --git a/Grinder/ui/widgets/SliderValueWidget.h b/Grinder/ui/widgets/SliderValueWidget.h new file mode 100644 index 0000000..b59949c --- /dev/null +++ b/Grinder/ui/widgets/SliderValueWidget.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * File: SliderValueWidget.h + * Date: 15.6.2018 + *****************************************************************************/ + +#ifndef SLIDERVALUEWIDGET_H +#define SLIDERVALUEWIDGET_H + +#include <QHBoxLayout> +#include <QSlider> +#include <QSpinBox> + +namespace grndr +{ + class SliderValueWidget : public QWidget + { + Q_OBJECT + + public: + SliderValueWidget(QString label, int minValue, int maxValue, QString valueSuffix = "", QWidget* parent = nullptr); + + public: + int getValue() const { return _slider->value(); } + void setValue(int value) { _slider->setValue(value); } + + signals: + void valueChanged(int); + + private slots: + void sliderValueChanged(int value); + void spinBoxValueChanged(int value); + + private: + QHBoxLayout* _layout{nullptr}; + + QSlider* _slider{nullptr}; + QSpinBox* _spinBox{nullptr}; + }; +} + +#endif -- GitLab