diff --git a/toonz/sources/include/toonz/doubleparamcmd.h b/toonz/sources/include/toonz/doubleparamcmd.h index acbea51..99f15a8 100644 --- a/toonz/sources/include/toonz/doubleparamcmd.h +++ b/toonz/sources/include/toonz/doubleparamcmd.h @@ -18,6 +18,7 @@ #endif class KeyframesUndo; +class TSceneHandle; class DVAPI KeyframeSetter { TDoubleParamP m_param; @@ -101,7 +102,8 @@ public: } static void removeKeyframeAt(TDoubleParam *curve, double frame); - static void enableCycle(TDoubleParam *curve, bool enabled); + static void enableCycle(TDoubleParam *curve, bool enabled, + TSceneHandle *sceneHandle = nullptr); }; #endif diff --git a/toonz/sources/include/toonzqt/functionsheet.h b/toonz/sources/include/toonzqt/functionsheet.h index 5eabfc5..7bf4c0d 100644 --- a/toonz/sources/include/toonzqt/functionsheet.h +++ b/toonz/sources/include/toonzqt/functionsheet.h @@ -124,6 +124,11 @@ public: int getColumnIndexByCurve(TDoubleParam *param) const; bool anyWidgetHasFocus(); + // Obtains a pointer to the stage object containing the + // parameter of specified column. Returns nullptr for + // fx parameter columns. + TStageObject *getStageObject(int column); + protected: void showEvent(QShowEvent *e) override; void hideEvent(QHideEvent *e) override; diff --git a/toonz/sources/include/toonzqt/functiontreeviewer.h b/toonz/sources/include/toonzqt/functiontreeviewer.h index 3996792..4fe2377 100644 --- a/toonz/sources/include/toonzqt/functiontreeviewer.h +++ b/toonz/sources/include/toonzqt/functiontreeviewer.h @@ -336,6 +336,31 @@ public: void refresh() override; }; +//============================================================================= + +class StageObjectChannelGroup final : public FunctionTreeModel::ChannelGroup { +public: + TStageObject *m_stageObject; //!< (not owned) Referenced stage object + FunctionTreeModel::ChannelGroup + *m_plasticGroup; //!< (not owned) Eventual plastic channels group + +public: + StageObjectChannelGroup(TStageObject *pegbar); + ~StageObjectChannelGroup(); + + QString getShortName() const override; + QString getLongName() const override; + + QString getIdName() const override; + + void *getInternalPointer() const override { + return static_cast(m_stageObject); + } + + TStageObject *getStageObject() const { return m_stageObject; } + QVariant data(int role) const override; +}; + //***************************************************************************************** // FunctionTreeView declaration //***************************************************************************************** diff --git a/toonz/sources/include/toonzqt/functionviewer.h b/toonz/sources/include/toonzqt/functionviewer.h index 413ab58..632cb1e 100644 --- a/toonz/sources/include/toonzqt/functionviewer.h +++ b/toonz/sources/include/toonzqt/functionviewer.h @@ -120,6 +120,8 @@ public: void clearFocusColumnsAndGraph(); bool columnsOrGraphHasFocus(); void setSceneHandle(TSceneHandle *sceneHandle); + TSceneHandle *getSceneHandle() const { return m_sceneHandle; } + // SaveLoadQSettings virtual void save(QSettings &settings) const override; virtual void load(QSettings &settings) override; diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index 745e96f..e47abb6 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -2531,6 +2531,8 @@ public: void undo() const override { m_pegbar->enableCycle(!m_pegbar->isCycleEnabled()); m_area->update(); + TApp::instance()->getCurrentScene()->setDirtyFlag(true); + TApp::instance()->getCurrentObject()->notifyObjectIdChanged(false); } void redo() const override { undo(); } int getSize() const override { return sizeof *this; } @@ -2694,8 +2696,9 @@ void CellArea::mousePressEvent(QMouseEvent *event) { } else if (isKeyframeFrame && row == k1 + 1 && o->rect(PredefinedRect::LOOP_ICON) .contains(mouseInCell)) { // cycle toggle - pegbar->enableCycle(!pegbar->isCycleEnabled()); - TUndoManager::manager()->add(new CycleUndo(pegbar, this)); + CycleUndo *undo = new CycleUndo(pegbar, this); + undo->redo(); + TUndoManager::manager()->add(undo); accept = true; } if (accept) { diff --git a/toonz/sources/toonzlib/doubleparamcmd.cpp b/toonz/sources/toonzlib/doubleparamcmd.cpp index 2fa15db..bd9cc30 100644 --- a/toonz/sources/toonzlib/doubleparamcmd.cpp +++ b/toonz/sources/toonzlib/doubleparamcmd.cpp @@ -2,6 +2,7 @@ #include "toonz/doubleparamcmd.h" #include "toonz/preferences.h" +#include "toonz/tscenehandle.h" #include "tdoubleparam.h" #include "tdoublekeyframe.h" #include "tundo.h" @@ -830,13 +831,23 @@ void KeyframeSetter::removeKeyframeAt(TDoubleParam *curve, double frame) { class EnableCycleUndo final : public TUndo { TDoubleParam *m_param; + TSceneHandle *m_sceneHandle; public: - EnableCycleUndo(TDoubleParam *param) : m_param(param) { m_param->addRef(); } + EnableCycleUndo(TDoubleParam *param, TSceneHandle *sceneHandle) + : m_param(param), m_sceneHandle(sceneHandle) { + m_param->addRef(); + } ~EnableCycleUndo() { m_param->release(); } void invertCycleEnabled() const { bool isEnabled = m_param->isCycleEnabled(); m_param->enableCycle(!isEnabled); + // for now the scene handle is only available when RMB click in function + // sheet + if (m_sceneHandle) { + m_sceneHandle->setDirtyFlag(true); + m_sceneHandle->notifySceneChanged(); + } } void undo() const override { invertCycleEnabled(); } void redo() const override { invertCycleEnabled(); } @@ -847,7 +858,9 @@ public: //============================================================================= -void KeyframeSetter::enableCycle(TDoubleParam *curve, bool enabled) { +void KeyframeSetter::enableCycle(TDoubleParam *curve, bool enabled, + TSceneHandle *sceneHandle) { curve->enableCycle(enabled); - TUndoManager::manager()->add(new EnableCycleUndo(curve)); + sceneHandle->notifySceneChanged(); + TUndoManager::manager()->add(new EnableCycleUndo(curve, sceneHandle)); } diff --git a/toonz/sources/toonzqt/functionsheet.cpp b/toonz/sources/toonzqt/functionsheet.cpp index 3b50a52..7026ef3 100644 --- a/toonz/sources/toonzqt/functionsheet.cpp +++ b/toonz/sources/toonzqt/functionsheet.cpp @@ -11,6 +11,7 @@ #include "toonz/doubleparamcmd.h" #include "toonz/preferences.h" #include "toonz/toonzfolders.h" +#include "toonz/tstageobject.h" // TnzBase includes #include "tunit.h" @@ -579,6 +580,13 @@ void FunctionSheetCellViewer::drawCells(QPainter &painter, int r0, int c0, TMeasure *measure = curve->getMeasure(); const TUnit *unit = measure ? measure->getCurrentUnit() : 0; + bool isStageObjectCycled = false; + TStageObject *obj = m_sheet->getStageObject(c); + if (obj && obj->isCycleEnabled()) isStageObjectCycled = true; + + bool isParamCycled = curve->isCycleEnabled(); + int rowCount = getViewer()->getRowCount(); + // draw each cell for (int row = r0; row <= r1; row++) { int ya = m_sheet->rowToY(row); @@ -586,8 +594,11 @@ void FunctionSheetCellViewer::drawCells(QPainter &painter, int r0, int c0, bool isSelected = getViewer()->isSelectedCell(row, c); - double value = curve->getValue(row); + double value = (isStageObjectCycled) + ? curve->getValue(obj->paramsTime((double)row)) + : curve->getValue(row); if (unit) value = unit->convertTo(value); + enum { None, KeyRange, CycleRange } drawValue = None; QRect cellRect(x0, ya, x1 - x0 + 1, yb - ya + 1); QRect borderRect(x0, ya, 7, yb - ya + 1); @@ -622,8 +633,42 @@ void FunctionSheetCellViewer::drawCells(QPainter &painter, int r0, int c0, } } + drawValue = KeyRange; + + } + // empty cells + else { + // show values for cycled parameter. + // cycle option can be set in two ways; one is as TStageObject, + // the other is as TDoubleParam. + // - TStageObject cycle literally cycles values with no offset. + // Applied to all transformation parameters of the cycled object. + // - TDoubleParam cycle includes value offset so that the curve + // connects smoothly. + // - TStageObject cycle option has a priority to TDoubleParam one. + // (see TStageObject::paramsTime() in tstageobject.cpp) + if (kCount > 0 && row > kr1 && (isStageObjectCycled || isParamCycled) && + (row < rowCount)) { + drawValue = CycleRange; + } + // empty and selected cell + if (isSelected) { + cellColor = (row >= rowCount) ? SelectedEmptyColor + : SelectedSceneRangeEmptyColor; + painter.setPen(Qt::NoPen); + painter.fillRect(cellRect, cellColor); + } + } + + if (drawValue != None) { // draw cell value - painter.setPen(getViewer()->getTextColor()); + if (drawValue == KeyRange) + painter.setPen(getViewer()->getTextColor()); + else { + QColor semiTranspTextColor = getViewer()->getTextColor(); + semiTranspTextColor.setAlpha(128); + painter.setPen(semiTranspTextColor); + } /*--- 整数から小数点以下3桁以内の場合はそれ以降の0000を描かない ---*/ QString text; @@ -653,19 +698,31 @@ void FunctionSheetCellViewer::drawCells(QPainter &painter, int r0, int c0, fontName = "Helvetica"; #endif } - static QFont font(fontName, -1, QFont::Bold); + static QFont font(fontName, -1); + font.setBold(drawValue == KeyRange); font.setPixelSize(12); painter.setFont(font); painter.drawText(cellRect.adjusted(10, 0, 0, 0), - Qt::AlignVCenter | Qt::AlignLeft, text); + Qt::AlignVCenter | Qt::AlignRight, text); } - // empty and selected cell - else if (isSelected) { - int rowCount = getViewer()->getRowCount(); - cellColor = (row >= rowCount) ? SelectedEmptyColor - : SelectedSceneRangeEmptyColor; - painter.setPen(Qt::NoPen); - painter.fillRect(cellRect, cellColor); + } + + if (kCount > 0 && (isStageObjectCycled || isParamCycled)) { + // draw the row zigzag + int ymax = m_sheet->rowToY(r1 + 1); + int qx = x0 + 4; + int qy = m_sheet->rowToY(kr1 + 1); + int zig = 2; + QColor zigzagColor = (isStageObjectCycled) ? getViewer()->getTextColor() + : KeyFrameBorderColor; + painter.setPen(zigzagColor); + painter.drawLine(QPoint(qx, qy), QPoint(qx - zig, qy + zig)); + qy += zig; + while (qy < ymax) { + painter.drawLine(QPoint(qx - zig, qy), QPoint(qx + zig, qy + 2 * zig)); + painter.drawLine(QPoint(qx + zig, qy + 2 * zig), + QPoint(qx - zig, qy + 4 * zig)); + qy += 4 * zig; } } } @@ -835,6 +892,8 @@ void FunctionSheetCellViewer::openContextMenu(QMouseEvent *e) { QAction setStep2Action(tr("Step 2"), 0); QAction setStep3Action(tr("Step 3"), 0); QAction setStep4Action(tr("Step 4"), 0); + QAction activateCycleAction(tr("Activate Cycle"), 0); + QAction deactivateCycleAction(tr("Deactivate Cycle"), 0); CellPosition cellPosition = getViewer()->xyToPosition(e->pos()); int row = cellPosition.frame(); @@ -858,6 +917,15 @@ void FunctionSheetCellViewer::openContextMenu(QMouseEvent *e) { // build menu QMenu menu(0); + + // on clicking after last keyframe + if (kCount > 0 && isEmpty && kIndex == kCount - 1) { + if (curve->isCycleEnabled()) + menu.addAction(&deactivateCycleAction); + else + menu.addAction(&activateCycleAction); + } + if (!isKeyframe) // menu.addAction(&deleteKeyframeAction); else menu.addAction(&insertKeyframeAction); @@ -896,7 +964,7 @@ void FunctionSheetCellViewer::openContextMenu(QMouseEvent *e) { menu.addAction(cmdManager->getAction("MI_Insert")); FunctionSelection *selection = m_sheet->getSelection(); - + TSceneHandle *sceneHandle = m_sheet->getViewer()->getSceneHandle(); // execute menu QAction *action = menu.exec(e->globalPos()); // QCursor::pos()); if (action == &deleteKeyframeAction) { @@ -935,6 +1003,10 @@ void FunctionSheetCellViewer::openContextMenu(QMouseEvent *e) { KeyframeSetter(curve, kIndex).setStep(3); else if (action == &setStep4Action) KeyframeSetter(curve, kIndex).setStep(4); + else if (action == &activateCycleAction) + KeyframeSetter::enableCycle(curve, true, sceneHandle); + else if (action == &deactivateCycleAction) + KeyframeSetter::enableCycle(curve, false, sceneHandle); update(); } @@ -1152,3 +1224,22 @@ void FunctionSheet::onCurrentChannelChanged( } } } + +//----------------------------------------------------------------------------- +/*! Obtains a pointer to the stage object containing the parameter of specified + * column +*/ +TStageObject *FunctionSheet::getStageObject(int column) { + FunctionTreeModel::Channel *channel = getChannel(column); + if (!channel) return nullptr; + + FunctionTreeModel::ChannelGroup *channelGroup = channel->getChannelGroup(); + if (!channelGroup) return nullptr; + + // returns nullptr if the channel is a fx parameter + StageObjectChannelGroup *stageItem = + dynamic_cast(channelGroup); + if (!stageItem) return nullptr; + + return stageItem->getStageObject(); +} \ No newline at end of file diff --git a/toonz/sources/toonzqt/functiontreeviewer.cpp b/toonz/sources/toonzqt/functiontreeviewer.cpp index 4fca61b..eeeb822 100644 --- a/toonz/sources/toonzqt/functiontreeviewer.cpp +++ b/toonz/sources/toonzqt/functiontreeviewer.cpp @@ -64,31 +64,6 @@ public: //============================================================================= -class StageObjectChannelGroup final : public FunctionTreeModel::ChannelGroup { -public: - TStageObject *m_stageObject; //!< (not owned) Referenced stage object - FunctionTreeModel::ChannelGroup - *m_plasticGroup; //!< (not owned) Eventual plastic channels group - -public: - StageObjectChannelGroup(TStageObject *pegbar); - ~StageObjectChannelGroup(); - - QString getShortName() const override; - QString getLongName() const override; - - QString getIdName() const override; - - void *getInternalPointer() const override { - return static_cast(m_stageObject); - } - - TStageObject *getStageObject() const { return m_stageObject; } - QVariant data(int role) const override; -}; - -//============================================================================= - class SkVDChannelGroup final : public FunctionTreeModel::ChannelGroup { public: StageObjectChannelGroup *m_stageObjectGroup; //!< Parent stage object group diff --git a/toonz/sources/toonzqt/functionviewer.cpp b/toonz/sources/toonzqt/functionviewer.cpp index 4148a6c..6d75e43 100644 --- a/toonz/sources/toonzqt/functionviewer.cpp +++ b/toonz/sources/toonzqt/functionviewer.cpp @@ -582,7 +582,10 @@ void FunctionViewer::onStageObjectChanged(bool isDragging) { static_cast(m_treeView->model()) ->setCurrentStageObject(obj); - if (!isDragging) m_treeView->updateAll(); + if (!isDragging) { + m_treeView->updateAll(); + m_numericalColumns->updateAll(); + } m_functionGraph->update(); }