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,7 @@ int colorLabel() const; void setColorLabel(int label); + virtual bool hasContent() const; // does any content exist in keyframe, or is it empty? 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 @@ -123,6 +123,10 @@ m_d->colorLabel = label; } +bool KisKeyframe::hasContent() const { + return true; +} + KisKeyframeChannel *KisKeyframe::channel() const { return m_d->channel; diff --git a/libs/image/kis_raster_keyframe_channel.h b/libs/image/kis_raster_keyframe_channel.h --- a/libs/image/kis_raster_keyframe_channel.h +++ b/libs/image/kis_raster_keyframe_channel.h @@ -79,10 +79,14 @@ void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) override; KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) override; + friend class KisRasterKeyframe; + bool keyframeHasContent(const KisKeyframe *keyframe) const; + private: void setFrameFilename(int frameId, const QString &filename); QString chooseFrameFilename(int frameId, const QString &layerFilename); int frameId(KisKeyframeSP keyframe) const; + int frameId(const KisKeyframe *keyframe) const; struct Private; QScopedPointer m_d; diff --git a/libs/image/kis_raster_keyframe_channel.cpp b/libs/image/kis_raster_keyframe_channel.cpp --- a/libs/image/kis_raster_keyframe_channel.cpp +++ b/libs/image/kis_raster_keyframe_channel.cpp @@ -45,6 +45,12 @@ return toQShared(new KisRasterKeyframe(this, channel)); } + bool hasContent() const override { + KisRasterKeyframeChannel *channel = dynamic_cast(this->channel()); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(channel, true); + + return channel->keyframeHasContent(this); + } }; struct KisRasterKeyframeChannel::Private @@ -83,7 +89,12 @@ int KisRasterKeyframeChannel::frameId(KisKeyframeSP keyframe) const { - KisRasterKeyframe *key = dynamic_cast(keyframe.data()); + return frameId(keyframe.data()); +} + +int KisRasterKeyframeChannel::frameId(const KisKeyframe *keyframe) const +{ + const KisRasterKeyframe *key = dynamic_cast(keyframe); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(key, -1); return key->frameId; } @@ -280,6 +291,11 @@ return keyframe; } +bool KisRasterKeyframeChannel::keyframeHasContent(const KisKeyframe *keyframe) const +{ + return !m_d->paintDevice->framesInterface()->frameBounds(frameId(keyframe)).isEmpty(); +} + bool KisRasterKeyframeChannel::hasScalarValue() const { return false; 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,10 @@ d->node->setUserLocked(value); } +bool Node::hasExtents() +{ + return !d->node->extent().isEmpty(); +} 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 @@ -72,7 +72,8 @@ FrameCachedRole, FrameEditableRole, FramesPerSecondRole, - UserRole + UserRole, + FrameHasContent // is it an empty frame with nothing in it? }; protected: @@ -82,12 +83,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.cpp b/plugins/dockers/animation/timeline_docker.cpp --- a/plugins/dockers/animation/timeline_docker.cpp +++ b/plugins/dockers/animation/timeline_docker.cpp @@ -32,6 +32,7 @@ #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" 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); - static void paintSpecialKeyframeIndicator(QPainter *painter, const QModelIndex &index, const QRect &rc); + + /// the opacity keyframe + void paintSpecialKeyframeIndicator(QPainter *painter, const QModelIndex &index, const QRect &rc) const; + 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" @@ -71,17 +71,30 @@ } } -void TimelineFramesItemDelegate::paintSpecialKeyframeIndicator(QPainter *painter, const QModelIndex &index, const QRect &rc) +void TimelineFramesItemDelegate::paintSpecialKeyframeIndicator(QPainter *painter, const QModelIndex &index, const QRect &rc) const { - bool active = index.data(TimelineFramesModel::ActiveLayerRole).toBool(); - bool framePresent = index.data(TimelineFramesModel::FrameExistsRole).toBool(); - bool editable = index.data(TimelineFramesModel::FrameEditableRole).toBool(); + bool doesFrameExist = index.data(TimelineFramesModel::FrameExistsRole).toBool(); /// does normal keyframe exist + bool isEditable = index.data(TimelineFramesModel::FrameEditableRole).toBool(); + bool hasContent = index.data(TimelineFramesModel::FrameHasContent).toBool(); /// find out if frame is empty - QColor color = TimelineColorScheme::instance()->frameColor(!framePresent, active); + QColor color = qApp->palette().color(QPalette::Highlight); + QColor baseColor = qApp->palette().color(QPalette::Base); + QColor noLabelSetColor = qApp->palette().color(QPalette::Highlight); // if no color label is used - if (!editable && color.alpha() > 0) { - const int l = color.lightness(); - color = QColor(l, l, l); + // use color label if it exists. coloring follows very similar logic to the drawBackground() function except this is a bit simpler + QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); + if (colorLabel.isValid()) { + color = labelColors.at(colorLabel.toInt()); + } else { + color = noLabelSetColor; + } + + if (!isEditable) { + color = KritaUtils::blendColors(baseColor, color, 0.5); + } + + if (doesFrameExist && hasContent) { + color = baseColor; // special keyframe will be over filled in frame, so switch color so it is shown } QPen oldPen = painter->pen(); @@ -105,20 +118,84 @@ 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 + QColor noLabelSetColor = qApp->palette().color(QPalette::Highlight); // if no color label is used + QColor highlightColor = qApp->palette().color(QPalette::Highlight); + QColor baseColor = qApp->palette().color(QPalette::Base); + + + // pass for filling in the active row with slightly color difference + if (hasActiveLayerRole) { + 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); + if (colorLabel.isValid()) { + color = labelColors.at(colorLabel.toInt()); + } else { + color = noLabelSetColor; + } - 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 layer is hidden, make the entire color more subtle. + // this should be in effect for both fill color and empty outline color + if (!isEditable) { + color = KritaUtils::blendColors(baseColor, color, 0.5); } - 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) { + + // pretty much the same check as "isValid" above, but that isn't working with hold frames + if (colorLabel.toInt() == 0) { + color = noLabelSetColor; + } + + + QPoint lineStart(rc.x(), (rc.y() + rc.height()/2)); + QPoint lineEnd(rc.x() + rc.width()-2, (rc.y() + rc.height()/2)); + + QPen holdFramePen(color); + holdFramePen.setWidth(3); + + painter->setPen(holdFramePen); + painter->drawLine(QLine(lineStart, lineEnd)); + } + + + } void TimelineFramesItemDelegate::drawFocus(QPainter *painter, @@ -146,12 +223,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 +243,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.h b/plugins/dockers/animation/timeline_frames_model.h --- a/plugins/dockers/animation/timeline_frames_model.h +++ b/plugins/dockers/animation/timeline_frames_model.h @@ -132,6 +132,7 @@ private Q_SLOTS: void slotDummyChanged(KisNodeDummy *dummy); + void slotImageContentChanged(); void processUpdateQueue(); public Q_SLOTS: 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 @@ -104,6 +104,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); + if (!frame) return false; + + return frame->hasContent(); + } + bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; @@ -122,7 +136,7 @@ KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; - KisKeyframeSP frame = primaryChannel->keyframeAt(column); + KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return -1; return frame->colorLabel(); @@ -260,6 +274,7 @@ SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); + connect(m_d->image, SIGNAL(sigImageModified()), SLOT(slotImageContentChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { @@ -281,6 +296,16 @@ m_d->updateTimer.start(); } +void TimelineFramesModel::slotImageContentChanged() +{ + if (m_d->activeLayerIndex < 0) return; + + KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); + if (!dummy) return; + + slotDummyChanged(dummy); +} + void TimelineFramesModel::processUpdateQueue() { Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { @@ -339,6 +364,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()); } 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 @@ -770,6 +770,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;