diff --git a/libs/image/kis_base_node.h b/libs/image/kis_base_node.h --- a/libs/image/kis_base_node.h +++ b/libs/image/kis_base_node.h @@ -389,6 +389,14 @@ */ virtual void setUserLocked(bool l); + /** + * @brief setHasPixelData + * @param hasPixelData + */ + void setHasPixelData(bool hasPixelData); + + bool hasPixelData(); + /** * @return true if the node can be edited: * @@ -565,6 +573,11 @@ */ void userLockingChanged(bool); + /** + * @brief This signal emitted when the node either contains data or the content is empty + */ + void pixelDataChanged(bool); + void keyframeChannelAdded(KisKeyframeChannel *channel); private: diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -274,6 +274,23 @@ baseNodeChangedCallback(); } +bool KisBaseNode::hasPixelData() +{ + KisBaseNodeSP parentNode = parentCallback(); + return parentNode->hasPixelData(); +} + +void KisBaseNode::setHasPixelData(bool hasPixelData) +{ + // check for already set data + KisBaseNodeSP parentNode = parentCallback(); + bool hasData = parentNode->hasPixelData(); + + emit pixelDataChanged(hasData); + baseNodeChangedCallback(); +} + + bool KisBaseNode::isEditable(bool checkVisibility) const { bool editable = true; diff --git a/libs/image/kis_keyframe.h b/libs/image/kis_keyframe.h --- a/libs/image/kis_keyframe.h +++ b/libs/image/kis_keyframe.h @@ -64,6 +64,8 @@ int colorLabel() const; void setColorLabel(int label); + bool hasContent(); // does any content exist in keyframe, or is it empty? + void setHasContent(bool content); KisKeyframeChannel *channel() const; diff --git a/libs/image/kis_keyframe.cpp b/libs/image/kis_keyframe.cpp --- a/libs/image/kis_keyframe.cpp +++ b/libs/image/kis_keyframe.cpp @@ -41,6 +41,7 @@ QPointF leftTangent; QPointF rightTangent; int colorLabel{0}; + bool hasContent = false; Private(KisKeyframeChannel *channel, int time) : channel(channel), time(time), interpolationMode(Constant) @@ -62,6 +63,7 @@ m_d->leftTangent = rhs->m_d->leftTangent; m_d->rightTangent = rhs->m_d->rightTangent; m_d->colorLabel = rhs->m_d->colorLabel; + m_d->hasContent = rhs->m_d->hasContent; } KisKeyframe::~KisKeyframe() @@ -123,6 +125,15 @@ m_d->colorLabel = label; } +void KisKeyframe::setHasContent(bool content) +{ + m_d->hasContent = content ; +} + +bool KisKeyframe::hasContent() { + return m_d->hasContent; +} + KisKeyframeChannel *KisKeyframe::channel() const { return m_d->channel; diff --git a/libs/image/kis_keyframe_channel.cpp b/libs/image/kis_keyframe_channel.cpp --- a/libs/image/kis_keyframe_channel.cpp +++ b/libs/image/kis_keyframe_channel.cpp @@ -501,6 +501,7 @@ QDomElement keyframeElement = doc.createElement("keyframe"); keyframeElement.setAttribute("time", keyframe->time()); keyframeElement.setAttribute("color-label", keyframe->colorLabel()); + keyframeElement.setAttribute("has-content", keyframe->hasContent()); saveKeyframe(keyframe, keyframeElement, layerFilename); @@ -521,6 +522,11 @@ keyframe->setColorLabel(keyframeNode.attribute("color-label").toUInt()); } + if (keyframeNode.hasAttribute("has-content")) { + keyframe->setHasContent(keyframeNode.attribute("has-content").toUInt()); + } + + m_d->keys.insert(keyframe->time(), keyframe); } } diff --git a/libs/image/kis_layer_properties_icons.h b/libs/image/kis_layer_properties_icons.h --- a/libs/image/kis_layer_properties_icons.h +++ b/libs/image/kis_layer_properties_icons.h @@ -44,6 +44,7 @@ static const KoID colorizeEditKeyStrokes; static const KoID colorizeShowColoring; static const KoID openFileLayerFile; + static const KoID hasPixelData; // useful for rendering GUI in timeline static KisLayerPropertiesIcons* instance(); diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h --- a/libs/libkis/Node.h +++ b/libs/libkis/Node.h @@ -232,6 +232,13 @@ */ void setLocked(bool value); + /** + * @brief does the node have any content in it? + * @return if node has any content in it + */ + bool hasExtents(); + + /** * @return the user-visible name of this node. */ diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -308,6 +308,17 @@ d->node->setUserLocked(value); } +bool Node::hasExtents() +{ + if (d->node->extent().height() == 0 || d->node->extent().width() == 0 ) { + d->node->setHasPixelData(false); + return false; + } + else { + d->node->setHasPixelData(true); + return true; + } +} QString Node::name() const { diff --git a/plugins/dockers/animation/kis_time_based_item_model.h b/plugins/dockers/animation/kis_time_based_item_model.h --- a/plugins/dockers/animation/kis_time_based_item_model.h +++ b/plugins/dockers/animation/kis_time_based_item_model.h @@ -73,7 +73,8 @@ FrameCachedRole, FrameEditableRole, FramesPerSecondRole, - UserRole + UserRole, + FrameHasContent // is it an empty frame with nothing in it? }; protected: @@ -83,12 +84,11 @@ KUndo2Command* createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, KUndo2Command *parentCommand = 0, bool moveEmptyFrames = true); + private Q_SLOTS: void slotFramerateChanged(); void slotCurrentTimeChanged(int time); - void slotCacheChanged(); - void slotInternalScrubPreviewRequested(int time); void slotPlaybackFrameChanged(); diff --git a/plugins/dockers/animation/timeline_color_scheme.h b/plugins/dockers/animation/timeline_color_scheme.h --- a/plugins/dockers/animation/timeline_color_scheme.h +++ b/plugins/dockers/animation/timeline_color_scheme.h @@ -43,8 +43,6 @@ QBrush headerCachedFrame() const; QBrush headerActive() const; - QColor frameColor(bool present, bool active)const ; - QColor onionSkinsSliderEnabledColor() const; QColor onionSkinsSliderDisabledColor() const; QColor onionSkinsButtonColor() const; diff --git a/plugins/dockers/animation/timeline_color_scheme.cpp b/plugins/dockers/animation/timeline_color_scheme.cpp --- a/plugins/dockers/animation/timeline_color_scheme.cpp +++ b/plugins/dockers/animation/timeline_color_scheme.cpp @@ -87,24 +87,6 @@ return selectorColor(); } -QColor TimelineColorScheme::frameColor(bool present, bool active) const -{ - QColor color = Qt::transparent; - - if (present && !active) { - color = m_d->baseColor; - } else if (present && active) { - QColor bgColor = qApp->palette().color(QPalette::Base); - int darkenCoeff = bgColor.value() > 128 ? 130 : 80; - color = m_d->baseColor.darker(darkenCoeff); - } else if (!present && active) { - QColor bgColor = qApp->palette().color(QPalette::Base); - return KritaUtils::blendColors(m_d->baseColor, bgColor, 0.2); - } - - return color; -} - QColor TimelineColorScheme::onionSkinsSliderEnabledColor() const { return m_d->baseColor; diff --git a/plugins/dockers/animation/timeline_docker.h b/plugins/dockers/animation/timeline_docker.h --- a/plugins/dockers/animation/timeline_docker.h +++ b/plugins/dockers/animation/timeline_docker.h @@ -43,6 +43,8 @@ public Q_SLOTS: void slotUpdateIcons(); + void slotImageUpdated(); + void slotRefreshUI(); private: struct Private; diff --git a/plugins/dockers/animation/timeline_docker.cpp b/plugins/dockers/animation/timeline_docker.cpp --- a/plugins/dockers/animation/timeline_docker.cpp +++ b/plugins/dockers/animation/timeline_docker.cpp @@ -32,10 +32,12 @@ #include "timeline_frames_model.h" #include "timeline_frames_view.h" #include "kis_animation_frame_cache.h" +#include "kis_image_animation_interface.h" #include "kis_signal_auto_connection.h" #include "kis_node_manager.h" #include +#include struct TimelineDocker::Private { @@ -44,6 +46,7 @@ view(new TimelineFramesView(parent)) { view->setModel(model); + refreshTimelineUITimer = new QTimer(parent); } TimelineFramesModel *model; @@ -51,6 +54,10 @@ QPointer canvas; + + QTimer *refreshTimelineUITimer; + + KisSignalAutoConnectionsStore canvasConnections; }; @@ -124,6 +131,12 @@ m_d->canvasConnections.addConnection( m_d->canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons()) ); + + m_d->canvasConnections.addConnection(m_d->canvas->image(), SIGNAL(sigImageUpdated(const QRect &)), + this, SLOT(slotImageUpdated())); + + m_d->canvasConnections.addConnection(m_d->refreshTimelineUITimer, SIGNAL(timeout()), this, SLOT(slotRefreshUI())); + } } @@ -135,6 +148,29 @@ } } +void TimelineDocker::slotImageUpdated() +{ + m_d->refreshTimelineUITimer->setSingleShot(true); + m_d->refreshTimelineUITimer->start(500); +} + +void TimelineDocker::slotRefreshUI() +{ + m_d->refreshTimelineUITimer->stop(); + + + // Potential hack... + // the animation timeline UI updates its display when the frame changes + // this forces that refresh to happen by quickly changing frames back and forth + // just setting the frame to the current frame has no effect + if (m_d->canvas && m_d->canvas->image() ) { + int currentFrame = m_d->model->currentTime(); + m_d->canvas->image()->animationInterface()->sigUiTimeChanged(currentFrame+1); + m_d->canvas->image()->animationInterface()->sigUiTimeChanged(currentFrame-1); + m_d->canvas->image()->animationInterface()->sigUiTimeChanged(currentFrame); + } +} + void TimelineDocker::unsetCanvas() { setCanvas(0); diff --git a/plugins/dockers/animation/timeline_frames_item_delegate.h b/plugins/dockers/animation/timeline_frames_item_delegate.h --- a/plugins/dockers/animation/timeline_frames_item_delegate.h +++ b/plugins/dockers/animation/timeline_frames_item_delegate.h @@ -29,7 +29,10 @@ ~TimelineFramesItemDelegate() override; static void paintActiveFrameSelector(QPainter *painter, const QRect &rc, bool isCurrentFrame); + + /// the opacity keyframe static void paintSpecialKeyframeIndicator(QPainter *painter, const QModelIndex &index, const QRect &rc); + void drawBackground(QPainter *painter, const QModelIndex &index, const QRect &rc) const; void drawFocus(QPainter *painter, const QStyleOptionViewItem &option, diff --git a/plugins/dockers/animation/timeline_frames_item_delegate.cpp b/plugins/dockers/animation/timeline_frames_item_delegate.cpp --- a/plugins/dockers/animation/timeline_frames_item_delegate.cpp +++ b/plugins/dockers/animation/timeline_frames_item_delegate.cpp @@ -21,7 +21,7 @@ #include #include #include - +#include "krita_utils.h" #include "timeline_frames_model.h" #include "timeline_color_scheme.h" @@ -73,11 +73,13 @@ void TimelineFramesItemDelegate::paintSpecialKeyframeIndicator(QPainter *painter, const QModelIndex &index, const QRect &rc) { + bool active = index.data(TimelineFramesModel::ActiveLayerRole).toBool(); bool framePresent = index.data(TimelineFramesModel::FrameExistsRole).toBool(); bool editable = index.data(TimelineFramesModel::FrameEditableRole).toBool(); - QColor color = TimelineColorScheme::instance()->frameColor(!framePresent, active); + QColor color = qApp->palette().color(QPalette::Highlight); + if (!editable && color.alpha() > 0) { const int l = color.lightness(); @@ -105,20 +107,86 @@ void TimelineFramesItemDelegate::drawBackground(QPainter *painter, const QModelIndex &index, const QRect &rc) const { - bool active = index.data(TimelineFramesModel::ActiveLayerRole).toBool(); - bool present = index.data(TimelineFramesModel::FrameExistsRole).toBool(); - bool editable = index.data(TimelineFramesModel::FrameEditableRole).toBool(); + /// is the current layer actively selected (this is not the same as visibility) + bool hasActiveLayerRole = index.data(TimelineFramesModel::ActiveLayerRole).toBool(); + bool doesFrameExist = index.data(TimelineFramesModel::FrameExistsRole).toBool(); /// does keyframe exist + bool isEditable = index.data(TimelineFramesModel::FrameEditableRole).toBool(); /// is layer editable + bool hasContent = index.data(TimelineFramesModel::FrameHasContent).toBool(); /// find out if frame is empty + + QColor color; // will get re-used for determining color + + // pass for filling in the active row with slightly color difference + if (hasActiveLayerRole && !doesFrameExist) { + QColor highlightColor = qApp->palette().color(QPalette::Highlight); + QColor baseColor = qApp->palette().color(QPalette::Base); + + color = KritaUtils::blendColors(baseColor, highlightColor, 0.8); + + painter->fillRect(rc, color); + } + + + + + + + + + // assign background color for frame depending on if the frame has a color label or not QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); + color = labelColors.at(colorLabel.toInt()); - QColor color = colorLabel.isValid() ? labelColors.at(colorLabel.toInt()) : - TimelineColorScheme::instance()->frameColor(present, active); - if (!editable && color.alpha() > 0) { - const int l = color.lightness(); - color = QColor(l, l, l); + // if color label is not valid, calculate the normal value + if (!colorLabel.isValid()) { + QColor color = Qt::transparent; + + QColor bgColor = qApp->palette().color(QPalette::Base); + int darkenCoeff = bgColor.value() > 128 ? 130 : 80; + + color = bgColor.darker(darkenCoeff); } - painter->fillRect(rc, color); + + // how do we fill in a frame that has content + // a keyframe will be totally filled in. A hold frame will have a line running through it + if (hasContent && doesFrameExist) { + painter->fillRect(rc, color); + } + + + + // pass of outline for empty keyframes + if (doesFrameExist && !hasContent) { + + QPen oldPen = painter->pen(); + QBrush oldBrush(painter->brush()); + + painter->setPen(QPen(color, 2)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(rc); + + painter->setBrush(oldBrush); + painter->setPen(oldPen); + } + + + // pass of hold frame line + if (!doesFrameExist && hasContent) { + + QPoint lineStart(rc.x(), (rc.y() + rc.height()/2)); + QPoint lineEnd(rc.x() + rc.width(), (rc.y() + rc.height()/2)); + + color = qApp->palette().color(QPalette::Highlight); + QPen holdFramePen(color); + holdFramePen.setWidth(3); + + painter->setPen(holdFramePen); + painter->drawLine(QLine(lineStart, lineEnd)); + } + + + } void TimelineFramesItemDelegate::drawFocus(QPainter *painter, @@ -146,12 +214,13 @@ void TimelineFramesItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ +{ + // draws background as well as fills normal keyframes drawBackground(painter, index, option.rect); + // creates a semi transparent orange rectangle in the frame that is actively selected on the active row if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { - QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) @@ -165,13 +234,16 @@ painter->setOpacity(oldOpacity); } + // not sure what this is drawing drawFocus(painter, option, option.rect); + // opacity keyframe, but NOT normal keyframes bool specialKeys = index.data(TimelineFramesModel::SpecialKeyframeExists).toBool(); if (specialKeys) { paintSpecialKeyframeIndicator(painter, index, option.rect); } + // creates a border and dot on the selected frame on the active row bool active = index.data(TimelineFramesModel::ActiveFrameRole).toBool(); bool layerIsCurrent = index.data(TimelineFramesModel::ActiveLayerRole).toBool(); if (active) { diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -103,6 +103,20 @@ return (primaryChannel && primaryChannel->keyframeAt(column)); } + bool frameHasContent(int row, int column) { + + KisNodeDummy *dummy = converter->dummyFromRow(row); + + KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); + if (!primaryChannel) return false; + + // first check if we are a key frame + KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); // primaryChannel->keyframeAt(column); + if (!frame) return false; + + return frame->hasContent(); + } + bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; @@ -140,6 +154,27 @@ frame->setColorLabel(color); } + void setHasContent(int row, int column) + { + KisNodeDummy *dummy = converter->dummyFromRow(row); + if (!dummy) return; + + bool valueToSet; + if ( dummy->node()->paintDevice()->extent().height() || dummy->node()->paintDevice()->extent().width()) { + valueToSet = true; + } else { + valueToSet = false; + } + + KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); + if (!primaryChannel) return; + + KisKeyframeSP frame = primaryChannel->currentlyActiveKeyframe(); + if (!frame) return; + + frame->setHasContent(valueToSet); + } + int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; @@ -338,6 +373,9 @@ case FrameEditableRole: { return m_d->layerEditable(index.row()); } + case FrameHasContent: { + return m_d->frameHasContent(index.row(), index.column()); + } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } @@ -407,6 +445,8 @@ break; } + m_d->setHasContent(index.row(), index.column()); + return ModelWithExternalNotifications::setData(index, value, role); } diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp --- a/plugins/dockers/animation/timeline_frames_view.cpp +++ b/plugins/dockers/animation/timeline_frames_view.cpp @@ -756,6 +756,7 @@ option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; //adjustViewOptionsForIndex(&option, current); + q->itemDelegate(current)->paint(&painter, option, current); } return pixmap;