From 2b24e8396577c0101f3b406f12155d2d2acbcf4d Mon Sep 17 00:00:00 2001 From: shun-iwasawa Date: Aug 07 2018 07:22:49 +0000 Subject: Enhancement of the Shift and Trace Feature (#2212) * shift and trace enhancement * override shortcuts --- diff --git a/toonz/sources/include/orientation.h b/toonz/sources/include/orientation.h index 05b934e..d9ba1c0 100644 --- a/toonz/sources/include/orientation.h +++ b/toonz/sources/include/orientation.h @@ -118,7 +118,9 @@ enum class PredefinedRect { ZOOM_OUT_AREA, ZOOM_OUT, LAYER_FOOTER_PANEL, - PREVIEW_FRAME_AREA + PREVIEW_FRAME_AREA, + SHIFTTRACE_DOT, + SHIFTTRACE_DOT_AREA }; enum class PredefinedLine { LOCKED, //! dotted vertical line when cell is locked diff --git a/toonz/sources/include/tools/tooloptions.h b/toonz/sources/include/tools/tooloptions.h index 2d5db49..7ca81a6 100644 --- a/toonz/sources/include/tools/tooloptions.h +++ b/toonz/sources/include/tools/tooloptions.h @@ -669,6 +669,26 @@ protected slots: void updateRealTimePickLabel(const int, const int, const int); }; +//============================================================================= +// +// ShiftTraceToolOptionBox +// shown only when "Edit Shift" mode is active +// +//============================================================================= + +class ShiftTraceToolOptionBox final : public ToolOptionsBox { + Q_OBJECT + QPushButton *m_resetPrevGhostBtn; + QPushButton *m_resetAfterGhostBtn; + void resetGhost(int index); + +public: + ShiftTraceToolOptionBox(QWidget *parent = 0); +protected slots: + void onResetPrevGhostBtnPressed(); + void onResetAfterGhostBtnPressed(); +}; + //----------------------------------------------------------------------------- class DVAPI ToolOptions final : public QFrame { diff --git a/toonz/sources/include/toonz/onionskinmask.h b/toonz/sources/include/toonz/onionskinmask.h index f0b81b5..19cae20 100644 --- a/toonz/sources/include/toonz/onionskinmask.h +++ b/toonz/sources/include/toonz/onionskinmask.h @@ -7,6 +7,8 @@ #include "tcommon.h" #include "tgeometry.h" +#include + #undef DVAPI #undef DVVAR #ifdef TOONZLIB_EXPORTS @@ -114,6 +116,23 @@ since underlying onion-skinned drawings must be visible. } void setShiftTraceGhostCenter(int index, const TPointD ¢er); + const int getShiftTraceGhostFrameOffset(int index) { + return m_ghostFrame[index]; + } + void setShiftTraceGhostFrameOffset(int index, int offset) { + m_ghostFrame[index] = offset; + } + + const int getGhostFlipKey() { + return (m_ghostFlipKeys.isEmpty()) ? 0 : m_ghostFlipKeys.last(); + } + void appendGhostFlipKey(int key) { + m_ghostFlipKeys.removeAll(key); + m_ghostFlipKeys.append(key); + } + void removeGhostFlipKey(int key) { m_ghostFlipKeys.removeAll(key); } + void clearGhostFlipKey() { m_ghostFlipKeys.clear(); } + private: std::vector m_fos, m_mos; //!< Fixed and Mobile Onion Skin indices bool m_enabled; //!< Whether onion skin is enabled @@ -122,6 +141,9 @@ private: ShiftTraceStatus m_shiftTraceStatus; TAffine m_ghostAff[2]; TPointD m_ghostCenter[2]; + int m_ghostFrame[2]; // relative frame position of the ghosts + QList m_ghostFlipKeys; // If F1, F2 or F3 key is pressed, then only + // display the corresponding ghost }; //*************************************************************************** diff --git a/toonz/sources/tnztools/tooloptions.cpp b/toonz/sources/tnztools/tooloptions.cpp index 7db4e2a..a7c98bc 100644 --- a/toonz/sources/tnztools/tooloptions.cpp +++ b/toonz/sources/tnztools/tooloptions.cpp @@ -36,6 +36,7 @@ #include "toonz/preferences.h" #include "toonz/tstageobjecttree.h" #include "toonz/mypaintbrushstyle.h" +#include "toonz/tonionskinmaskhandle.h" // TnzCore includes #include "tproperty.h" @@ -2497,6 +2498,45 @@ void StylePickerToolOptionsBox::updateRealTimePickLabel(const int ink, } //============================================================================= +// ShiftTraceToolOptionBox +//----------------------------------------------------------------------------- + +ShiftTraceToolOptionBox::ShiftTraceToolOptionBox(QWidget *parent) + : ToolOptionsBox(parent) { + setFrameStyle(QFrame::StyledPanel); + setFixedHeight(26); + + m_resetPrevGhostBtn = + new QPushButton(tr("Reset Shift of Previous Drawing"), this); + m_resetAfterGhostBtn = + new QPushButton(tr("Reset Shift of Forward Drawing"), this); + + m_layout->addWidget(m_resetPrevGhostBtn, 0); + m_layout->addWidget(m_resetAfterGhostBtn, 0); + m_layout->addStretch(1); + + connect(m_resetPrevGhostBtn, SIGNAL(clicked()), this, + SLOT(onResetPrevGhostBtnPressed())); + connect(m_resetAfterGhostBtn, SIGNAL(clicked()), this, + SLOT(onResetAfterGhostBtnPressed())); +} + +void ShiftTraceToolOptionBox::resetGhost(int index) { + TTool::Application *app = TTool::getApplication(); + OnionSkinMask osm = app->getCurrentOnionSkin()->getOnionSkinMask(); + osm.setShiftTraceGhostCenter(index, TPointD()); + osm.setShiftTraceGhostAff(index, TAffine()); + app->getCurrentOnionSkin()->setOnionSkinMask(osm); + app->getCurrentOnionSkin()->notifyOnionSkinMaskChanged(); + TTool *tool = app->getCurrentTool()->getTool(); + if (tool) tool->reset(); +} + +void ShiftTraceToolOptionBox::onResetPrevGhostBtnPressed() { resetGhost(0); } + +void ShiftTraceToolOptionBox::onResetAfterGhostBtnPressed() { resetGhost(1); } + +//============================================================================= // ToolOptions //----------------------------------------------------------------------------- @@ -2604,6 +2644,8 @@ void ToolOptions::onToolSwitched() { } else if (tool->getName() == T_StylePicker) panel = new StylePickerToolOptionsBox(0, tool, currPalette, currTool, app->getPaletteController()); + else if (tool->getName() == "T_ShiftTrace") + panel = new ShiftTraceToolOptionBox(this); else panel = tool->createOptionsBox(); // Only this line should remain out // of that if/else monstrosity diff --git a/toonz/sources/toonz/frameheadgadget.cpp b/toonz/sources/toonz/frameheadgadget.cpp index 87d90e5..c3dedd0 100644 --- a/toonz/sources/toonz/frameheadgadget.cpp +++ b/toonz/sources/toonz/frameheadgadget.cpp @@ -54,8 +54,10 @@ FrameHeadGadget::~FrameHeadGadget() {} void FrameHeadGadget::draw(QPainter &p, const QColor &lightColor, const QColor &darkColor) { // drawPlayingHead(p, lightColor, darkColor); - if (!Preferences::instance()->isOnionSkinEnabled()) return; - drawOnionSkinSelection(p, lightColor, darkColor); + if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked()) + drawShiftTraceMarker(p); + else if (Preferences::instance()->isOnionSkinEnabled()) + drawOnionSkinSelection(p, lightColor, darkColor); } void FrameHeadGadget::drawPlayingHead(QPainter &p, const QColor &lightColor, @@ -294,7 +296,8 @@ void FrameHeadGadget::setMos(int frame, bool on) { FilmstripFrameHeadGadget::FilmstripFrameHeadGadget(FilmstripFrames *filmstrip) : m_filmstrip(filmstrip) , m_dy(m_filmstrip->getIconSize().height() + fs_frameSpacing + - fs_iconMarginTop + fs_iconMarginBottom) {} + fs_iconMarginTop + fs_iconMarginBottom) + , m_highlightedghostFrame(-1) {} int FilmstripFrameHeadGadget::getY() const { return 50; } @@ -442,7 +445,83 @@ void FilmstripFrameHeadGadget::drawOnionSkinSelection(QPainter &p, //----------------------------------------------------------------------------- +void FilmstripFrameHeadGadget::drawShiftTraceMarker(QPainter &p) { + int currentRow = getCurrentFrame(); + + TPixel frontPixel, backPixel; + bool inksOnly; + Preferences::instance()->getOnionData(frontPixel, backPixel, inksOnly); + QColor frontColor((int)frontPixel.r, (int)frontPixel.g, (int)frontPixel.b); + QColor backColor((int)backPixel.r, (int)backPixel.g, (int)backPixel.b); + + OnionSkinMask osMask = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + + // draw lines to ghost frames + int prevOffset = osMask.getShiftTraceGhostFrameOffset(0); + int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1); + + const int shiftTraceDotSize = 12; + const int shiftTraceDotXOffset = 3; + int dotYPos = (m_dy - shiftTraceDotSize) / 2; + + if (currentRow > 0 && prevOffset < 0) // previous ghost + { + p.setPen(backColor); + int y0 = index2y(currentRow + prevOffset) + dotYPos + shiftTraceDotSize; + int y1 = index2y(currentRow); + p.drawLine(shiftTraceDotXOffset + shiftTraceDotSize / 2, y0, + shiftTraceDotXOffset + shiftTraceDotSize / 2, y1); + } + if (forwardOffset > 0) // forward ghost + { + p.setPen(frontColor); + int y0 = index2y(currentRow + 1); + int y1 = index2y(currentRow + forwardOffset) + dotYPos + shiftTraceDotSize; + p.drawLine(shiftTraceDotXOffset + shiftTraceDotSize / 2, y0, + shiftTraceDotXOffset + shiftTraceDotSize / 2, y1); + } + // draw dots + std::vector offsVec = {prevOffset, 0, forwardOffset}; + std::vector colorsVec = {backColor, QColor(0, 162, 232), frontColor}; + QFont currentFont = p.font(); + QFont tmpFont = p.font(); + tmpFont.setPointSize(7); + p.setFont(tmpFont); + + for (int i = 0; i < 3; i++) { + if (i != 1 && offsVec[i] == 0) continue; + p.setPen(colorsVec[i]); + p.setBrush(Qt::gray); + int topPos = index2y(currentRow + offsVec[i]) + dotYPos; + QRect dotRect(shiftTraceDotXOffset, topPos, shiftTraceDotSize, + shiftTraceDotSize); + p.drawRect(dotRect); + + // draw shortcut numbers + p.setPen(Qt::black); + p.drawText(dotRect, Qt::AlignCenter, QString::number(i + 1)); + } + p.setFont(currentFont); + + // highlight on mouse over + if (m_highlightedghostFrame >= 0) { + p.setPen(QColor(255, 255, 0)); + p.setBrush(QColor(255, 255, 0, 180)); + int topPos = index2y(m_highlightedghostFrame) + dotYPos; + QRect dotRect(shiftTraceDotXOffset, topPos, shiftTraceDotSize, + shiftTraceDotSize); + p.drawRect(dotRect); + } +} + +//----------------------------------------------------------------------------- + bool FilmstripFrameHeadGadget::eventFilter(QObject *obj, QEvent *e) { + // shift & trace case + if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked()) + return shiftTraceEventFilter(obj, e); + if (!Preferences::instance()->isOnionSkinEnabled()) return false; QWidget *viewer = dynamic_cast(obj); @@ -650,6 +729,97 @@ bool FilmstripFrameHeadGadget::eventFilter(QObject *obj, QEvent *e) { //----------------------------------------------------------------------------- +bool FilmstripFrameHeadGadget::shiftTraceEventFilter(QObject *obj, QEvent *e) { + QWidget *viewer = dynamic_cast(obj); + + if (e->type() != QEvent::MouseButtonPress && e->type() != QEvent::MouseMove) + return false; + + const int shiftTraceDotSize = 12; + const int shiftTraceDotXOffset = 3; + int dotYPos = (m_dy - shiftTraceDotSize) / 2; + QRect dotRect(shiftTraceDotXOffset, dotYPos, shiftTraceDotSize, + shiftTraceDotSize); + + // reset highlight + if (m_highlightedghostFrame >= 0) { + m_highlightedghostFrame = -1; + viewer->update(); + } + + QMouseEvent *mouseEvent = dynamic_cast(e); + int frame = y2index(mouseEvent->pos().y()); + // position from top-left of the frame + QPoint mousePos = mouseEvent->pos() + QPoint(0, -index2y(frame)); + int currentFrame = getCurrentFrame(); + + if (mousePos.x() < dotRect.left() || mousePos.x() > dotRect.right()) + return false; + + if (e->type() == QEvent::MouseButtonPress) { + if (frame == currentFrame) { + if (dotRect.contains(mousePos)) + OnioniSkinMaskGUI::resetShiftTraceFrameOffset(); + else + return false; + } else { + OnionSkinMask osMask = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + int prevOffset = osMask.getShiftTraceGhostFrameOffset(0); + int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1); + // Hide previous ghost + if (frame == currentFrame + prevOffset) + osMask.setShiftTraceGhostFrameOffset(0, 0); + // Hide forward ghost + else if (frame == currentFrame + forwardOffset) + osMask.setShiftTraceGhostFrameOffset(1, 0); + // Move previous ghost + else if (frame < currentFrame) + osMask.setShiftTraceGhostFrameOffset(0, frame - currentFrame); + // Move forward ghost + else + osMask.setShiftTraceGhostFrameOffset(1, frame - currentFrame); + TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osMask); + } + TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged(); + return true; + } + //---- + else if (e->type() == QEvent::MouseMove) { + if (frame == currentFrame) { + if (dotRect.contains(mousePos)) { + m_highlightedghostFrame = frame; + viewer->setToolTip( + tr("Click to Reset Shift & Trace Markers to Neighbor Frames\nHold " + "F2 Key on the Viewer to Show This Frame Only")); + } else { + viewer->setToolTip(tr("")); + return false; + } + } else { + m_highlightedghostFrame = frame; + OnionSkinMask osMask = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + int prevOffset = osMask.getShiftTraceGhostFrameOffset(0); + int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1); + // Hide ghost + if (frame == currentFrame + prevOffset) + viewer->setToolTip( + tr("Click to Hide This Frame from Shift & Trace\nHold F1 Key on " + "the Viewer to Show This Frame Only")); + else if (frame == currentFrame + forwardOffset) + viewer->setToolTip( + tr("Click to Hide This Frame from Shift & Trace\nHold F3 Key on " + "the Viewer to Show This Frame Only")); + // Move ghost + else + viewer->setToolTip(tr("Click to Move Shift & Trace Marker")); + } + } + return true; +} +//----------------------------------------------------------------------------- + class OnionSkinToggle final : public MenuItemHandler { public: OnionSkinToggle() : MenuItemHandler(MI_OnionSkin) {} diff --git a/toonz/sources/toonz/frameheadgadget.h b/toonz/sources/toonz/frameheadgadget.h index 0255bb2..19fd43e 100644 --- a/toonz/sources/toonz/frameheadgadget.h +++ b/toonz/sources/toonz/frameheadgadget.h @@ -46,6 +46,8 @@ public: virtual void drawOnionSkinSelection(QPainter &p, const QColor &lightColor, const QColor &darkColor); + virtual void drawShiftTraceMarker(QPainter &p) {} + virtual int getY() const = 0; virtual int index2y(int index) const = 0; @@ -70,6 +72,7 @@ public: class FilmstripFrameHeadGadget final : public FrameHeadGadget { FilmstripFrames *m_filmstrip; int m_dy; + int m_highlightedghostFrame; public: FilmstripFrameHeadGadget(FilmstripFrames *filmstrip); @@ -81,10 +84,13 @@ public: void drawOnionSkinSelection(QPainter &p, const QColor &lightColor, const QColor &darkColor) override; + void drawShiftTraceMarker(QPainter &p) override; + void setCurrentFrame(int index) const override; int getCurrentFrame() const override; bool eventFilter(QObject *obj, QEvent *event) override; + bool shiftTraceEventFilter(QObject *obj, QEvent *event); }; #endif diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index 26e4b63..49993c0 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -1960,6 +1960,8 @@ void MainWindow::defineActions() { MenuViewCommandType); createToggle(MI_EditShift, tr("Edit Shift"), "", false, MenuViewCommandType); createToggle(MI_NoShift, tr("No Shift"), "", false, MenuViewCommandType); + CommandManager::instance()->enable(MI_EditShift, false); + CommandManager::instance()->enable(MI_NoShift, false); createAction(MI_ResetShift, tr("Reset Shift"), "", MenuViewCommandType); if (QGLPixelBuffer::hasOpenGLPbuffers()) diff --git a/toonz/sources/toonz/onionskinmaskgui.cpp b/toonz/sources/toonz/onionskinmaskgui.cpp index dc5175f..45e44d7 100644 --- a/toonz/sources/toonz/onionskinmaskgui.cpp +++ b/toonz/sources/toonz/onionskinmaskgui.cpp @@ -3,6 +3,11 @@ #include "onionskinmaskgui.h" #include "tapp.h" #include "toonz/tonionskinmaskhandle.h" +#include "toonz/tframehandle.h" +#include "toonz/txshlevelhandle.h" +#include "toonz/txsheethandle.h" +#include "toonz/tcolumnhandle.h" +#include "toonz/txshsimplelevel.h" #include "toonz/onionskinmask.h" @@ -105,3 +110,66 @@ void OnioniSkinMaskGUI::addOnionSkinCommand(QMenu *menu, bool isFilmStrip) { SLOT(activate())); } } + +//------------------------------------------------------------------------------ + +void OnioniSkinMaskGUI::resetShiftTraceFrameOffset() { + auto setGhostOffset = [](int firstOffset, int secondOffset) { + OnionSkinMask osm = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + osm.setShiftTraceGhostFrameOffset(0, firstOffset); + osm.setShiftTraceGhostFrameOffset(1, secondOffset); + TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm); + }; + + TApp *app = TApp::instance(); + if (app->getCurrentFrame()->isEditingLevel()) { + TXshSimpleLevel *level = app->getCurrentLevel()->getSimpleLevel(); + if (!level) { + setGhostOffset(0, 0); + return; + } + TFrameId fid = app->getCurrentFrame()->getFid(); + int firstOffset = (fid > level->getFirstFid()) ? -1 : 0; + int secondOffset = (fid < level->getLastFid()) ? 1 : 0; + + setGhostOffset(firstOffset, secondOffset); + } else { // when scene frame is selected + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + int col = app->getCurrentColumn()->getColumnIndex(); + TXshColumn *column = xsh->getColumn(col); + if (!column || column->isEmpty()) { + setGhostOffset(0, 0); + return; + } + int r0, r1; + column->getRange(r0, r1); + int row = app->getCurrentFrame()->getFrame(); + TXshCell cell = xsh->getCell(row, col); + int firstOffset = -1; + while (1) { + int r = row + firstOffset; + if (r < r0) { + firstOffset = 0; + break; + } + if (!xsh->getCell(r, col).isEmpty() && xsh->getCell(r, col) != cell) { + break; + } + firstOffset--; + } + int secondOffset = 1; + while (1) { + int r = row + secondOffset; + if (r > r1) { + secondOffset = 0; + break; + } + if (!xsh->getCell(r, col).isEmpty() && xsh->getCell(r, col) != cell) { + break; + } + secondOffset++; + } + setGhostOffset(firstOffset, secondOffset); + } +} \ No newline at end of file diff --git a/toonz/sources/toonz/onionskinmaskgui.h b/toonz/sources/toonz/onionskinmaskgui.h index bbe0d3b..90e0648 100644 --- a/toonz/sources/toonz/onionskinmaskgui.h +++ b/toonz/sources/toonz/onionskinmaskgui.h @@ -15,6 +15,8 @@ namespace OnioniSkinMaskGUI { // Da fare per la filmstrip!! void addOnionSkinCommand(QMenu *, bool isFilmStrip = false); +void resetShiftTraceFrameOffset(); + //============================================================================= // OnionSkinSwitcher //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/sceneviewer.cpp b/toonz/sources/toonz/sceneviewer.cpp index 0cd90ed..4f21bc2 100644 --- a/toonz/sources/toonz/sceneviewer.cpp +++ b/toonz/sources/toonz/sceneviewer.cpp @@ -391,10 +391,9 @@ public: if (std::string(m_cmdId) == MI_ShiftTrace) { cm->enable(MI_EditShift, checked); cm->enable(MI_NoShift, checked); - if (!checked) { - cm->setChecked(MI_EditShift, false); - } + if (checked) OnioniSkinMaskGUI::resetShiftTraceFrameOffset(); // cm->getAction(MI_NoShift)->setChecked(false); + TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged(); } else if (std::string(m_cmdId) == MI_EditShift) { if (checked) { QAction *noShiftAction = @@ -427,6 +426,7 @@ public: OnionSkinMask osm = TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); osm.setShiftTraceStatus(status); + osm.clearGhostFlipKey(); TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm); } @@ -2377,30 +2377,36 @@ includeInvisible); int SceneViewer::posToRow(const TPointD &p, double distance, bool includeInvisible) const { - int oldRasterizePli = TXshSimpleLevel::m_rasterizePli; - TApp *app = TApp::instance(); - ToonzScene *scene = app->getCurrentScene()->getScene(); - TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); - int frame = app->getCurrentFrame()->getFrame(); - int currentColumnIndex = app->getCurrentColumn()->getColumnIndex(); - OnionSkinMask osm = app->getCurrentOnionSkin()->getOnionSkinMask(); + int oldRasterizePli = TXshSimpleLevel::m_rasterizePli; + TApp *app = TApp::instance(); + OnionSkinMask osm = app->getCurrentOnionSkin()->getOnionSkinMask(); TPointD pos = TPointD(p.x - width() / 2, p.y - height() / 2); Stage::Picker picker(getViewMatrix(), pos, m_visualSettings); picker.setDistance(distance); - TXshSimpleLevel::m_rasterizePli = 0; + if (app->getCurrentFrame()->isEditingLevel()) { + Stage::visit(picker, app->getCurrentLevel()->getLevel(), + app->getCurrentFrame()->getFid(), osm, + app->getCurrentFrame()->isPlaying(), false); + } else { + ToonzScene *scene = app->getCurrentScene()->getScene(); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + int frame = app->getCurrentFrame()->getFrame(); + int currentColumnIndex = app->getCurrentColumn()->getColumnIndex(); - Stage::VisitArgs args; - args.m_scene = scene; - args.m_xsh = xsh; - args.m_row = frame; - args.m_col = currentColumnIndex; - args.m_osm = &osm; - args.m_onlyVisible = includeInvisible; + TXshSimpleLevel::m_rasterizePli = 0; - Stage::visit(picker, args); + Stage::VisitArgs args; + args.m_scene = scene; + args.m_xsh = xsh; + args.m_row = frame; + args.m_col = currentColumnIndex; + args.m_osm = &osm; + args.m_onlyVisible = includeInvisible; + Stage::visit(picker, args); + } TXshSimpleLevel::m_rasterizePli = oldRasterizePli; return picker.getRow(); } diff --git a/toonz/sources/toonz/sceneviewerevents.cpp b/toonz/sources/toonz/sceneviewerevents.cpp index 0c2a037..d161190 100644 --- a/toonz/sources/toonz/sceneviewerevents.cpp +++ b/toonz/sources/toonz/sceneviewerevents.cpp @@ -1039,6 +1039,16 @@ bool SceneViewer::event(QEvent *e) { else if (tool && tool->isEventAcceptable(e)) { e->accept(); } + // if the Shift & Trace mode is active, then override F1, F2 and F3 key + // actions by flipping feature + else if (CommandManager::instance() + ->getAction(MI_ShiftTrace) + ->isChecked() && + TTool::getTool("T_ShiftTrace", TTool::ToonzImage) + ->isEventAcceptable(e)) { + e->accept(); + } + return true; } if (e->type() == QEvent::KeyRelease) { @@ -1211,6 +1221,18 @@ void SceneViewer::keyPressEvent(QKeyEvent *event) { event->ignore(); return true; } + // pressing F1, F2 or F3 key will flip between corresponding ghost + if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked() && + (Qt::Key_F1 <= key && key <= Qt::Key_F3)) { + OnionSkinMask osm = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + if (osm.getGhostFlipKey() != key) { + osm.appendGhostFlipKey(key); + TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm); + TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged(); + } + return true; + } } if (!tool->isEnabled()) return false; @@ -1309,6 +1331,15 @@ void SceneViewer::keyReleaseEvent(QKeyEvent *event) { setToolCursor(this, tool->getCursorId()); } + if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked() && + (Qt::Key_F1 <= key && key <= Qt::Key_F3)) { + OnionSkinMask osm = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + osm.removeGhostFlipKey(key); + TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm); + TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged(); + } + if (tool->getName() == T_Type) event->accept(); else diff --git a/toonz/sources/toonz/shifttracetool.cpp b/toonz/sources/toonz/shifttracetool.cpp index 96c54b7..cbd7dcc 100644 --- a/toonz/sources/toonz/shifttracetool.cpp +++ b/toonz/sources/toonz/shifttracetool.cpp @@ -14,13 +14,20 @@ #include "toonz/txsheethandle.h" #include "toonz/tframehandle.h" #include "toonz/tcolumnhandle.h" +#include "toonz/txshlevelhandle.h" +#include "toonz/txshsimplelevel.h" #include "toonz/dpiscale.h" +#include "toonz/stage.h" #include "tapp.h" #include "tpixel.h" #include "toonzqt/menubarcommand.h" +#include "toonz/preferences.h" +#include "toonzqt/gutil.h" + #include "tgl.h" #include +#include //============================================================================= @@ -54,12 +61,14 @@ public: enum GadgetId { NoGadget, + NoGadget_InBox, CurveP0Gadget, CurveP1Gadget, CurvePmGadget, MoveCenterGadget, RotateGadget, - TranslateGadget + TranslateGadget, + ScaleGadget }; inline bool isCurveGadget(GadgetId id) const { return CurveP0Gadget <= id && id <= CurvePmGadget; @@ -80,6 +89,8 @@ private: TAffine m_aff[2]; TPointD m_center[2]; + TAffine m_oldAff; + public: ShiftTraceTool(); @@ -92,8 +103,10 @@ public: void updateGhost(); void reset() override { + int ghostIndex = m_ghostIndex; onActivate(); invalidate(); + m_ghostIndex = ghostIndex; } void mouseMove(const TPointD &, const TMouseEvent &e) override; @@ -124,6 +137,7 @@ public: QAction *action = CommandManager::instance()->getAction("MI_EditShift"); action->setChecked(false); } + bool isEventAcceptable(QEvent *e) override; int getCursorId() const override; }; @@ -152,71 +166,95 @@ void ShiftTraceTool::clearData() { } void ShiftTraceTool::updateBox() { - if (0 <= m_ghostIndex && m_ghostIndex < 2 && m_row[m_ghostIndex] >= 0) { - int col = TApp::instance()->getCurrentColumn()->getColumnIndex(); + if (m_ghostIndex < 0 || 2 <= m_ghostIndex || m_row[m_ghostIndex] < 0) return; + + TImageP img; + + TApp *app = TApp::instance(); + if (app->getCurrentFrame()->isEditingScene()) { + int col = app->getCurrentColumn()->getColumnIndex(); int row = m_row[m_ghostIndex]; - TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); TXshCell cell = xsh->getCell(row, col); TXshSimpleLevel *sl = cell.getSimpleLevel(); if (sl) { - m_dpiAff = getDpiAffine(sl, cell.m_frameId); - TImageP img = cell.getImage(false); - if (img) { - if (TRasterImageP ri = img) { - TRasterP ras = ri->getRaster(); - m_box = (convert(ras->getBounds()) - ras->getCenterD()) * - ri->getSubsampling(); - } else if (TToonzImageP ti = img) { - TRasterP ras = ti->getRaster(); - m_box = (convert(ras->getBounds()) - ras->getCenterD()) * - ti->getSubsampling(); - } else if (TVectorImageP vi = img) { - m_box = vi->getBBox(); - } - } + m_dpiAff = getDpiAffine(sl, cell.m_frameId); + img = cell.getImage(false); + } + } + // on editing level + else { + TXshLevel *level = app->getCurrentLevel()->getLevel(); + if (!level) return; + TXshSimpleLevel *sl = level->getSimpleLevel(); + if (!sl) return; + + const TFrameId &ghostFid = sl->index2fid(m_row[m_ghostIndex]); + m_dpiAff = getDpiAffine(sl, ghostFid); + img = sl->getFrame(ghostFid, false); + } + + if (img) { + if (TRasterImageP ri = img) { + TRasterP ras = ri->getRaster(); + m_box = (convert(ras->getBounds()) - ras->getCenterD()) * + ri->getSubsampling(); + } else if (TToonzImageP ti = img) { + TRasterP ras = ti->getRaster(); + m_box = (convert(ras->getBounds()) - ras->getCenterD()) * + ti->getSubsampling(); + } else if (TVectorImageP vi = img) { + m_box = vi->getBBox(); } } } void ShiftTraceTool::updateData() { - TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); - int row = TApp::instance()->getCurrentFrame()->getFrame(); - int col = TApp::instance()->getCurrentColumn()->getColumnIndex(); - TXshCell cell = xsh->getCell(row, col); - m_box = TRectD(); + m_box = TRectD(); for (int i = 0; i < 2; i++) m_row[i] = -1; m_dpiAff = TAffine(); + TApp *app = TApp::instance(); + OnionSkinMask osm = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + int previousOffset = osm.getShiftTraceGhostFrameOffset(0); + int forwardOffset = osm.getShiftTraceGhostFrameOffset(1); // we must find the prev (m_row[0]) and next (m_row[1]) reference images // (either might not exist) // see also stage.cpp, StageBuilder::addCellWithOnionSkin + if (app->getCurrentFrame()->isEditingScene()) { + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + int row = app->getCurrentFrame()->getFrame(); + int col = app->getCurrentColumn()->getColumnIndex(); + TXshCell cell = xsh->getCell(row, col); + int r; + r = row + previousOffset; + if (r >= 0 && xsh->getCell(r, col) != cell && + (cell.getSimpleLevel() == 0 || + xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) { + m_row[0] = r; + } - if (cell.isEmpty()) { - // current cell is empty. search for the prev ref img - int r = row - 1; - while (r >= 0 && xsh->getCell(r, col).getSimpleLevel() == 0) r--; - if (r >= 0) m_row[0] = r; - // else prev drawing doesn't exist : nothing to do - } else { - // current cell is not empty - // search for prev ref img - TXshSimpleLevel *sl = cell.getSimpleLevel(); - int r = row - 1; - if (r >= 0) { - TXshCell otherCell = xsh->getCell(r, col); - if (otherCell.getSimpleLevel() == sl) { - // find the span start - while (r - 1 >= 0 && xsh->getCell(r - 1, col) == otherCell) r--; - m_row[0] = r; + r = row + forwardOffset; + if (r >= 0 && xsh->getCell(r, col) != cell && + (cell.getSimpleLevel() == 0 || + xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) { + m_row[1] = r; + } + } + // on editing level + else { + TXshLevel *level = app->getCurrentLevel()->getLevel(); + if (level) { + TXshSimpleLevel *sl = level->getSimpleLevel(); + if (sl) { + TFrameId fid = app->getCurrentFrame()->getFid(); + int row = sl->guessIndex(fid); + m_row[0] = row + previousOffset; + m_row[1] = row + forwardOffset; } } - - // search for next ref img - r = row + 1; - while (xsh->getCell(r, col) == cell) r++; - // first cell after the current span has the same level - if (xsh->getCell(r, col).getSimpleLevel() == sl) m_row[1] = r; } updateBox(); } @@ -269,35 +307,42 @@ void ShiftTraceTool::drawDot(const TPointD ¢er, double r, tglDrawCircle(center, r); } -void ShiftTraceTool::drawControlRect() { +void ShiftTraceTool::drawControlRect() { // TODO if (m_ghostIndex < 0 || m_ghostIndex > 1) return; int row = m_row[m_ghostIndex]; if (row < 0) return; - int col = TApp::instance()->getCurrentColumn()->getColumnIndex(); - TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); - TXshCell cell = xsh->getCell(row, col); - if (cell.isEmpty()) return; - TImageP img = cell.getImage(false); - if (!img) return; - TRectD box; - if (TRasterImageP ri = img) { - TRasterP ras = ri->getRaster(); - box = - (convert(ras->getBounds()) - ras->getCenterD()) * ri->getSubsampling(); - } else if (TToonzImageP ti = img) { - TRasterP ras = ti->getRaster(); - box = - (convert(ras->getBounds()) - ras->getCenterD()) * ti->getSubsampling(); - } else if (TVectorImageP vi = img) { - box = vi->getBBox(); - } else { - return; - } + + TRectD box = m_box; + if (box.isEmpty()) return; glPushMatrix(); tglMultMatrix(getGhostAff()); + TPixel32 color; - color = m_highlightedGadget == TranslateGadget ? TPixel32(200, 100, 100) - : TPixel32(120, 120, 120); + + // draw onion-colored rectangle to indicate which ghost is grabbed + { + TPixel32 frontOniColor, backOniColor; + bool inksOnly; + Preferences::instance()->getOnionData(frontOniColor, backOniColor, + inksOnly); + color = (m_ghostIndex == 0) ? backOniColor : frontOniColor; + double unit = sqrt(tglGetPixelSize2()); + unit *= getDevPixRatio(); + TRectD coloredBox = box.enlarge(3.0 * unit); + tglColor(color); + glBegin(GL_LINE_STRIP); + glVertex2d(coloredBox.x0, coloredBox.y0); + glVertex2d(coloredBox.x1, coloredBox.y0); + glVertex2d(coloredBox.x1, coloredBox.y1); + glVertex2d(coloredBox.x0, coloredBox.y1); + glVertex2d(coloredBox.x0, coloredBox.y0); + glEnd(); + } + + color = m_highlightedGadget == TranslateGadget + ? TPixel32(200, 100, 100) + : m_highlightedGadget == RotateGadget ? TPixel32(100, 200, 100) + : TPixel32(120, 120, 120); tglColor(color); glBegin(GL_LINE_STRIP); glVertex2d(box.x0, box.y0); @@ -306,16 +351,16 @@ void ShiftTraceTool::drawControlRect() { glVertex2d(box.x0, box.y1); glVertex2d(box.x0, box.y0); glEnd(); - color = - m_highlightedGadget == 2000 ? TPixel32(200, 100, 100) : TPixel32::White; + color = m_highlightedGadget == ScaleGadget ? TPixel32(200, 100, 100) + : TPixel32::White; double r = 4 * sqrt(tglGetPixelSize2()); drawDot(box.getP00(), r, color); drawDot(box.getP01(), r, color); drawDot(box.getP10(), r, color); drawDot(box.getP11(), r, color); if (m_curveStatus == NoCurve) { - color = - m_highlightedGadget == 2001 ? TPixel32(200, 100, 100) : TPixel32::White; + color = m_highlightedGadget == MoveCenterGadget ? TPixel32(200, 100, 100) + : TPixel32::White; TPointD c = m_center[m_ghostIndex]; drawDot(c, r, color); } @@ -327,18 +372,20 @@ void ShiftTraceTool::drawCurve() { double r = 4 * sqrt(tglGetPixelSize2()); double u = getPixelSize(); if (m_curveStatus == TwoPointsCurve) { - TPixel32 color = - m_highlightedGadget == 1000 ? TPixel32(200, 100, 100) : TPixel32::White; + TPixel32 color = m_highlightedGadget == CurveP0Gadget + ? TPixel32(200, 100, 100) + : TPixel32::White; drawDot(m_p0, r, color); glColor3d(0.2, 0.2, 0.2); tglDrawSegment(m_p0, m_p1); drawDot(m_p1, r, TPixel32::Red); } else if (m_curveStatus == ThreePointsCurve) { - TPixel32 color = - m_highlightedGadget == 1000 ? TPixel32(200, 100, 100) : TPixel32::White; + TPixel32 color = m_highlightedGadget == CurveP0Gadget + ? TPixel32(200, 100, 100) + : TPixel32::White; drawDot(m_p0, r, color); - color = - m_highlightedGadget == 1001 ? TPixel32(200, 100, 100) : TPixel32::White; + color = m_highlightedGadget == CurveP1Gadget ? TPixel32(200, 100, 100) + : TPixel32::White; drawDot(m_p1, r, color); glColor3d(0.2, 0.2, 0.2); @@ -364,8 +411,8 @@ void ShiftTraceTool::drawCurve() { } else { tglDrawSegment(m_p0, m_p1); } - color = - m_highlightedGadget == 1002 ? TPixel32(200, 100, 100) : TPixel32::White; + color = m_highlightedGadget == CurvePmGadget ? TPixel32(200, 100, 100) + : TPixel32::White; drawDot(m_p2, r, color); } } @@ -375,17 +422,19 @@ ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) { gadgets.push_back(std::make_pair(m_p0, CurveP0Gadget)); gadgets.push_back(std::make_pair(m_p1, CurveP1Gadget)); gadgets.push_back(std::make_pair(m_p2, CurvePmGadget)); - TAffine aff = getGhostAff(); + TAffine aff = getGhostAff(); + double pixelSize = getPixelSize(); + double d = 15 * pixelSize; // offset for rotation handle if (0 <= m_ghostIndex && m_ghostIndex < 2) { - gadgets.push_back(std::make_pair(aff * m_box.getP00(), RotateGadget)); - gadgets.push_back(std::make_pair(aff * m_box.getP01(), RotateGadget)); - gadgets.push_back(std::make_pair(aff * m_box.getP10(), RotateGadget)); - gadgets.push_back(std::make_pair(aff * m_box.getP11(), RotateGadget)); + gadgets.push_back(std::make_pair(aff * m_box.getP00(), ScaleGadget)); + gadgets.push_back(std::make_pair(aff * m_box.getP01(), ScaleGadget)); + gadgets.push_back(std::make_pair(aff * m_box.getP10(), ScaleGadget)); + gadgets.push_back(std::make_pair(aff * m_box.getP11(), ScaleGadget)); gadgets.push_back( std::make_pair(aff * m_center[m_ghostIndex], MoveCenterGadget)); } int k = -1; - double minDist2 = pow(10 * getPixelSize(), 2); + double minDist2 = pow(10 * pixelSize, 2); for (int i = 0; i < (int)gadgets.size(); i++) { double d2 = norm2(gadgets[i].first - p); if (d2 < minDist2) { @@ -397,8 +446,7 @@ ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) { // rect-point if (0 <= m_ghostIndex && m_ghostIndex < 2) { - TPointD q = aff.inv() * p; - + TPointD q = aff.inv() * p; double big = 1.0e6; double d = big, x = 0, y = 0; if (m_box.x0 < q.x && q.x < m_box.x1) { @@ -414,8 +462,8 @@ ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) { } } if (m_box.y0 < q.y && q.y < m_box.y1) { - double d0 = fabs(m_box.x0 - q.y); - double d1 = fabs(m_box.x1 - q.y); + double d0 = fabs(m_box.x0 - q.x); + double d1 = fabs(m_box.x1 - q.x); if (d0 < d) { d = d0; y = q.y; @@ -431,9 +479,16 @@ ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) { TPointD pp = aff * TPointD(x, y); double d = norm(p - pp); if (d < 10 * getPixelSize()) { - return TranslateGadget; + if (m_box.contains(q)) + return TranslateGadget; + else + return RotateGadget; } } + if (m_box.contains(q)) + return NoGadget_InBox; + else + return NoGadget; } return NoGadget; } @@ -450,31 +505,46 @@ void ShiftTraceTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) { m_gadget = m_highlightedGadget; m_oldPos = m_startPos = pos; - if (m_gadget == NoGadget) { + if (m_gadget == NoGadget || m_gadget == NoGadget_InBox) { + if (!e.isCtrlPressed()) { + if (m_gadget == NoGadget_InBox) + m_gadget = TranslateGadget; + else + m_gadget = RotateGadget; + // m_curveStatus = NoCurve; + } int row = getViewer()->posToRow(e.m_pos, 5 * getPixelSize(), false); if (row >= 0) { - int currentRow = getFrame(); - int index = -1; - if (m_row[0] >= 0 && row <= currentRow) - index = 0; - else if (m_row[1] >= 0 && row > currentRow) - index = 1; + int index = -1; + TApp *app = TApp::instance(); + if (app->getCurrentFrame()->isEditingScene()) { + int currentRow = getFrame(); + if (m_row[0] >= 0 && row <= currentRow) + index = 0; + else if (m_row[1] >= 0 && row > currentRow) + index = 1; + } else { + if (m_row[0] == row) + index = 0; + else if (m_row[1] == row) + index = 1; + } + if (index >= 0) { m_ghostIndex = index; updateBox(); + m_gadget = TranslateGadget; + m_highlightedGadget = TranslateGadget; } } - - if (!e.isCtrlPressed()) { - m_gadget = TranslateGadget; - // m_curveStatus = NoCurve; - } } + + m_oldAff = m_aff[m_ghostIndex]; invalidate(); } -void ShiftTraceTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &) { - if (m_gadget == NoGadget) { +void ShiftTraceTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) { + if (m_gadget == NoGadget || m_gadget == NoGadget_InBox) { if (norm(pos - m_oldPos) > 10 * getPixelSize()) { m_curveStatus = TwoPointsCurve; m_p0 = m_oldPos; @@ -512,6 +582,17 @@ void ShiftTraceTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &) { TPointD delta = pos - m_oldPos; m_oldPos = pos; m_aff[m_ghostIndex] = TTranslation(delta) * m_aff[m_ghostIndex]; + } else if (m_gadget == ScaleGadget) { + TAffine aff = getGhostAff(); + TPointD c = aff * m_center[m_ghostIndex]; + TPointD a = m_oldPos - c; + TPointD b = pos - c; + if (e.isShiftPressed()) + m_aff[m_ghostIndex] = m_oldAff * TScale(b.x / a.x, b.y / a.y); + else { + double scale = std::max(b.x / a.x, b.y / a.y); + m_aff[m_ghostIndex] = m_oldAff * TScale(scale, scale); + } } updateGhost(); @@ -541,12 +622,21 @@ void ShiftTraceTool::draw() { } int ShiftTraceTool::getCursorId() const { - if (m_highlightedGadget == RotateGadget) + if (m_highlightedGadget == RotateGadget || m_highlightedGadget == NoGadget) return ToolCursor::RotateCursor; + else if (m_highlightedGadget == ScaleGadget) + return ToolCursor::ScaleCursor; else if (isCurveGadget(m_highlightedGadget)) return ToolCursor::PinchCursor; - else + else // Curve Points, TranslateGadget, NoGadget_InBox return ToolCursor::MoveCursor; } +bool ShiftTraceTool::isEventAcceptable(QEvent *e) { + // F1, F2 and F3 keys are used for flipping + QKeyEvent *keyEvent = static_cast(e); + int key = keyEvent->key(); + return (Qt::Key_F1 <= key && key <= Qt::Key_F3); +} + ShiftTraceTool shiftTraceTool; diff --git a/toonz/sources/toonz/xshrowviewer.cpp b/toonz/sources/toonz/xshrowviewer.cpp index 99145d2..da90586 100644 --- a/toonz/sources/toonz/xshrowviewer.cpp +++ b/toonz/sources/toonz/xshrowviewer.cpp @@ -561,6 +561,105 @@ void RowArea::drawCurrentTimeLine(QPainter &p) { //----------------------------------------------------------------------------- +void RowArea::drawShiftTraceMarker(QPainter &p) { + TApp *app = TApp::instance(); + OnionSkinMask osMask = app->getCurrentOnionSkin()->getOnionSkinMask(); + + TXsheet *xsh = app->getCurrentScene()->getScene()->getXsheet(); + assert(xsh); + int currentRow = m_viewer->getCurrentRow(); + + int frameAdj = m_viewer->getFrameZoomAdjustment(); + + // get onion colors + TPixel frontPixel, backPixel; + bool inksOnly; + Preferences::instance()->getOnionData(frontPixel, backPixel, inksOnly); + QColor frontColor((int)frontPixel.r, (int)frontPixel.g, (int)frontPixel.b); + QColor backColor((int)backPixel.r, (int)backPixel.g, (int)backPixel.b); + + // draw lines to ghost frames + int prevOffset = osMask.getShiftTraceGhostFrameOffset(0); + int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1); + + QRect onionRect = + m_viewer->orientation()->rect(PredefinedRect::SHIFTTRACE_DOT); + int onionCenter_frame = + m_viewer->orientation()->frameSide(onionRect).middle(); + int onionCenter_layer = + m_viewer->orientation()->layerSide(onionRect).middle(); + + if (currentRow > 0 && prevOffset < 0) // previous ghost + { + int layerAxis = onionCenter_layer; + int fromFrameAxis = m_viewer->rowToFrameAxis(currentRow + prevOffset) + + onionCenter_frame - (frameAdj / 2); + int toFrameAxis = m_viewer->rowToFrameAxis(currentRow) + onionCenter_frame - + (frameAdj / 2); + QLine verticalLine = m_viewer->orientation()->verticalLine( + layerAxis, NumberRange(fromFrameAxis, toFrameAxis)); + p.setPen(backColor); + p.setBrush(Qt::NoBrush); + p.drawLine(verticalLine); + } + if (forwardOffset > 0) // forward ghost + { + int layerAxis = onionCenter_layer; + int fromFrameAxis = m_viewer->rowToFrameAxis(currentRow) + + onionCenter_frame - (frameAdj / 2); + int toFrameAxis = m_viewer->rowToFrameAxis(currentRow + forwardOffset) + + onionCenter_frame - (frameAdj / 2); + QLine verticalLine = m_viewer->orientation()->verticalLine( + layerAxis, NumberRange(fromFrameAxis, toFrameAxis)); + p.setPen(frontColor); + p.setBrush(Qt::NoBrush); + p.drawLine(verticalLine); + } + + if (!m_viewer->orientation()->isVerticalTimeline()) + drawCurrentTimeIndicator(p); + + // draw dots + std::vector offsVec = {prevOffset, 0, forwardOffset}; + std::vector colorsVec = {backColor, QColor(0, 162, 232), frontColor}; + QFont currentFont = p.font(); + QFont tmpFont = p.font(); + tmpFont.setPointSize(7); + p.setFont(tmpFont); + for (int i = 0; i < 3; i++) { + if (i != 1 && offsVec[i] == 0) continue; + p.setPen(colorsVec[i]); + p.setBrush(Qt::gray); + QPoint topLeft = + m_viewer->positionToXY(CellPosition(currentRow + offsVec[i], 0)); + if (!m_viewer->orientation()->isVerticalTimeline()) topLeft.setY(0); + QRect dotRect = m_viewer->orientation() + ->rect(PredefinedRect::SHIFTTRACE_DOT) + .translated(topLeft); + dotRect.adjust(-frameAdj / 2, 0, -frameAdj / 2, 0); + p.drawRect(dotRect); + // draw shortcut numbers + p.setPen(Qt::black); + p.drawText(dotRect, Qt::AlignCenter, QString::number(i + 1)); + } + p.setFont(currentFont); + + //-- onion placement hint under mouse + if (m_showOnionToSet == ShiftTraceGhost) { + p.setPen(QColor(255, 255, 0)); + p.setBrush(QColor(255, 255, 0, 180)); + QPoint topLeft = m_viewer->positionToXY(CellPosition(m_row, 0)); + if (!m_viewer->orientation()->isVerticalTimeline()) topLeft.setY(0); + QRect dotRect = m_viewer->orientation() + ->rect(PredefinedRect::SHIFTTRACE_DOT) + .translated(topLeft); + dotRect.adjust(-frameAdj / 2, 0, -frameAdj / 2, 0); + p.drawRect(dotRect); + } +} + +//----------------------------------------------------------------------------- + namespace { TStageObjectId getAncestor(TXsheet *xsh, TStageObjectId id) { @@ -672,7 +771,9 @@ void RowArea::paintEvent(QPaintEvent *event) { drawRows(p, r0, r1); if (TApp::instance()->getCurrentFrame()->isEditingScene()) { - if (Preferences::instance()->isOnionSkinEnabled()) + if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked()) + drawShiftTraceMarker(p); + else if (Preferences::instance()->isOnionSkinEnabled()) drawOnionSkinSelection(p); else if (Preferences::instance()->isCurrentTimelineIndicatorEnabled() && !m_viewer->orientation()->isVerticalTimeline()) @@ -711,10 +812,41 @@ void RowArea::mousePressEvent(QMouseEvent *event) { QPoint mouseInCell = event->pos() - topLeft; int frameAdj = m_viewer->getFrameZoomAdjustment(); - if (Preferences::instance()->isOnionSkinEnabled() && - o->rect(PredefinedRect::ONION_AREA) + if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked() && + o->rect(PredefinedRect::SHIFTTRACE_DOT_AREA) .adjusted(0, 0, -frameAdj, 0) .contains(mouseInCell)) { + // Reset ghosts to neighbor frames + if (row == currentFrame) + OnioniSkinMaskGUI::resetShiftTraceFrameOffset(); + else { + OnionSkinMask osMask = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + int prevOffset = osMask.getShiftTraceGhostFrameOffset(0); + int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1); + // Hide previous ghost + if (row == currentFrame + prevOffset) + osMask.setShiftTraceGhostFrameOffset(0, 0); + // Hide forward ghost + else if (row == currentFrame + forwardOffset) + osMask.setShiftTraceGhostFrameOffset(1, 0); + // Move previous ghost + else if (row < currentFrame) + osMask.setShiftTraceGhostFrameOffset(0, row - currentFrame); + // Move forward ghost + else + osMask.setShiftTraceGhostFrameOffset(1, row - currentFrame); + TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osMask); + } + TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged(); + return; + } else if (!CommandManager::instance() + ->getAction(MI_ShiftTrace) + ->isChecked() && + Preferences::instance()->isOnionSkinEnabled() && + o->rect(PredefinedRect::ONION_AREA) + .adjusted(0, 0, -frameAdj, 0) + .contains(mouseInCell)) { if (row == currentFrame) { setDragTool( XsheetGUI::DragTool::makeCurrentFrameModifierTool(m_viewer)); @@ -833,8 +965,38 @@ void RowArea::mouseMoveEvent(QMouseEvent *event) { if (!m_viewer->orientation()->isVerticalTimeline()) topLeft.setY(0); QPoint mouseInCell = event->pos() - topLeft; if (row < 0) return; + + m_tooltip = tr(""); + + // whether to show ability to move the shift and trace ghost frame + if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked()) { + if (o->rect(PredefinedRect::SHIFTTRACE_DOT_AREA) + .adjusted(0, 0, -frameAdj, 0) + .contains(mouseInCell)) { + m_showOnionToSet = ShiftTraceGhost; + + OnionSkinMask osMask = + TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); + int prevOffset = osMask.getShiftTraceGhostFrameOffset(0); + int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1); + if (row == currentRow) + m_tooltip = + tr("Click to Reset Shift & Trace Markers to Neighbor Frames\nHold " + "F2 Key on the Viewer to Show This Frame Only"); + else if (row == currentRow + prevOffset) + m_tooltip = + tr("Click to Hide This Frame from Shift & Trace\nHold F1 Key on " + "the Viewer to Show This Frame Only"); + else if (row == currentRow + forwardOffset) + m_tooltip = + tr("Click to Hide This Frame from Shift & Trace\nHold F3 Key on " + "the Viewer to Show This Frame Only"); + else + m_tooltip = tr("Click to Move Shift & Trace Marker"); + } + } // whether to show ability to set onion marks - if (Preferences::instance()->isOnionSkinEnabled() && row != currentRow) { + else if (Preferences::instance()->isOnionSkinEnabled() && row != currentRow) { if (o->rect(PredefinedRect::ONION_FIXED_DOT_AREA) .adjusted(0, 0, -frameAdj, 0) .contains(mouseInCell)) @@ -884,6 +1046,7 @@ void RowArea::mouseMoveEvent(QMouseEvent *event) { QPainterPath endArrow = o->path(PredefinedPath::END_PLAY_RANGE).translated(base1); + if (!m_tooltip.isEmpty()) return; if (startArrow.contains(m_pos)) m_tooltip = tr("Playback Start Marker"); else if (endArrow.contains(m_pos)) @@ -904,8 +1067,6 @@ void RowArea::mouseMoveEvent(QMouseEvent *event) { m_tooltip = tr("Fixed Onion Skin Toggle"); else if (m_showOnionToSet == Mos) m_tooltip = tr("Relative Onion Skin Toggle"); - else - m_tooltip = tr(""); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/xshrowviewer.h b/toonz/sources/toonz/xshrowviewer.h index d7af6f7..366d0c1 100644 --- a/toonz/sources/toonz/xshrowviewer.h +++ b/toonz/sources/toonz/xshrowviewer.h @@ -26,8 +26,9 @@ class RowArea final : public QWidget { enum ShowOnionToSetFlag { None = 0, Fos, - Mos - } m_showOnionToSet; // TODO:�����͂����Fos,Mos�ǂ�����n�C���C�g���Ă���̂����肳����I�I�I�I + Mos, + ShiftTraceGhost + } m_showOnionToSet; enum Direction { up = 0, down }; @@ -51,6 +52,7 @@ class RowArea final : public QWidget { void drawPinnedCenterKeys(QPainter &p, int r0, int r1); void drawCurrentTimeIndicator(QPainter &p); void drawCurrentTimeLine(QPainter &p); + void drawShiftTraceMarker(QPainter &p); DragTool *getDragTool() const; void setDragTool(DragTool *dragTool); diff --git a/toonz/sources/toonzlib/onionskinmask.cpp b/toonz/sources/toonzlib/onionskinmask.cpp index 7a71ff8..0000031 100644 --- a/toonz/sources/toonzlib/onionskinmask.cpp +++ b/toonz/sources/toonzlib/onionskinmask.cpp @@ -64,6 +64,8 @@ void OnionSkinMask::clear() { m_ghostAff[1] = TAffine(); m_ghostCenter[0] = TPointD(); m_ghostCenter[1] = TPointD(); + m_ghostFrame[0] = 0; + m_ghostFrame[1] = 0; } //------------------------------------------------------------------- diff --git a/toonz/sources/toonzlib/orientation.cpp b/toonz/sources/toonzlib/orientation.cpp index b307f6a..eb2b13f 100644 --- a/toonz/sources/toonzlib/orientation.cpp +++ b/toonz/sources/toonzlib/orientation.cpp @@ -10,15 +10,16 @@ using std::pair; namespace { -const int KEY_ICON_WIDTH = 11; -const int KEY_ICON_HEIGHT = 13; -const int EASE_TRIANGLE_SIZE = 4; -const int PLAY_MARKER_SIZE = 10; -const int ONION_SIZE = 19; -const int ONION_DOT_SIZE = 8; -const int PINNED_SIZE = 10; -const int FRAME_MARKER_SIZE = 4; -const int FOLDED_CELL_SIZE = 9; +const int KEY_ICON_WIDTH = 11; +const int KEY_ICON_HEIGHT = 13; +const int EASE_TRIANGLE_SIZE = 4; +const int PLAY_MARKER_SIZE = 10; +const int ONION_SIZE = 19; +const int ONION_DOT_SIZE = 8; +const int PINNED_SIZE = 10; +const int FRAME_MARKER_SIZE = 4; +const int FOLDED_CELL_SIZE = 9; +const int SHIFTTRACE_DOT_SIZE = 12; } class TopToBottomOrientation : public Orientation { @@ -34,9 +35,10 @@ class TopToBottomOrientation : public Orientation { const int FRAME_HEADER_WIDTH = CELL_WIDTH; const int PLAY_RANGE_X = FRAME_HEADER_WIDTH / 2 - PLAY_MARKER_SIZE; const int ONION_X = 0, ONION_Y = 0; - const int ICON_WIDTH = 18; - const int ICON_HEIGHT = 18; - const int TRACKLEN = 60; + const int ICON_WIDTH = 18; + const int ICON_HEIGHT = 18; + const int TRACKLEN = 60; + const int SHIFTTRACE_DOT_OFFSET = 3; public: TopToBottomOrientation(); @@ -97,6 +99,7 @@ class LeftToRightOrientation : public Orientation { const int FOLDED_LAYER_HEADER_HEIGHT = 8; const int FOLDED_LAYER_HEADER_WIDTH = LAYER_HEADER_WIDTH; const int TRACKLEN = 60; + const int SHIFTTRACE_DOT_OFFSET = 5; public: LeftToRightOrientation(); @@ -347,6 +350,12 @@ TopToBottomOrientation::TopToBottomOrientation() { PredefinedRect::PREVIEW_FRAME_AREA, QRect(PLAY_RANGE_X, 0, (FRAME_HEADER_WIDTH - PLAY_RANGE_X), CELL_HEIGHT)); + addRect(PredefinedRect::SHIFTTRACE_DOT, + QRect(SHIFTTRACE_DOT_OFFSET, (CELL_HEIGHT - SHIFTTRACE_DOT_SIZE) / 2, + SHIFTTRACE_DOT_SIZE, SHIFTTRACE_DOT_SIZE)); + addRect(PredefinedRect::SHIFTTRACE_DOT_AREA, + QRect(SHIFTTRACE_DOT_OFFSET, 0, SHIFTTRACE_DOT_SIZE, CELL_HEIGHT)); + // Column viewer addRect(PredefinedRect::LAYER_HEADER, QRect(0, 1, CELL_WIDTH, use_header_height - 3)); @@ -953,6 +962,13 @@ LeftToRightOrientation::LeftToRightOrientation() { PredefinedRect::PREVIEW_FRAME_AREA, QRect(0, PLAY_RANGE_Y, CELL_WIDTH, (FRAME_HEADER_HEIGHT - PLAY_RANGE_Y))); + addRect(PredefinedRect::SHIFTTRACE_DOT, + QRect((CELL_WIDTH - SHIFTTRACE_DOT_SIZE) / 2, SHIFTTRACE_DOT_OFFSET, + SHIFTTRACE_DOT_SIZE, SHIFTTRACE_DOT_SIZE) + .adjusted(-1, 0, -1, 0)); + addRect(PredefinedRect::SHIFTTRACE_DOT_AREA, + QRect(0, SHIFTTRACE_DOT_OFFSET, CELL_WIDTH, SHIFTTRACE_DOT_SIZE)); + // Column viewer addRect(PredefinedRect::LAYER_HEADER, QRect(1, 0, LAYER_HEADER_WIDTH - 2, CELL_HEIGHT)); diff --git a/toonz/sources/toonzlib/stage.cpp b/toonz/sources/toonzlib/stage.cpp index cb9be4a..4c3466e 100644 --- a/toonz/sources/toonzlib/stage.cpp +++ b/toonz/sources/toonzlib/stage.cpp @@ -398,23 +398,46 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, } if (m_shiftTraceGhostId != NO_GHOST) { - if (m_shiftTraceGhostId != TRACED) player.m_opacity = 127; - int opacity = player.m_opacity; - player.m_bingoOrder = 10; - if (m_onionSkinMask.getShiftTraceStatus() != - OnionSkinMask::ENABLED_WITHOUT_GHOST_MOVEMENTS) { - if (m_shiftTraceGhostId == FIRST_GHOST) { - player.m_opacity = 30; + // if F1, F2 or F3 key is pressed, then draw only the corresponding ghost + int flipKey = m_onionSkinMask.getGhostFlipKey(); + if (Qt::Key_F1 <= flipKey && flipKey <= Qt::Key_F3) { + if (m_shiftTraceGhostId == TRACED && flipKey == Qt::Key_F2) { players.push_back(player); - player.m_opacity = opacity; + } else if (m_shiftTraceGhostId == FIRST_GHOST && + flipKey == Qt::Key_F1) { player.m_placement = m_onionSkinMask.getShiftTraceGhostAff(0) * player.m_placement; - } else if (m_shiftTraceGhostId == SECOND_GHOST) { - player.m_opacity = 30; players.push_back(player); - player.m_opacity = opacity; + } else if (m_shiftTraceGhostId == SECOND_GHOST && + flipKey == Qt::Key_F3) { player.m_placement = m_onionSkinMask.getShiftTraceGhostAff(1) * player.m_placement; + players.push_back(player); + } + return; + } + + else { + if (m_shiftTraceGhostId != TRACED) + player.m_opacity = + UCHAR(255.0 * (1.0 - OnionSkinMask::getOnionSkinFade(1))); + int opacity = player.m_opacity; + player.m_bingoOrder = 10; + if (m_onionSkinMask.getShiftTraceStatus() != + OnionSkinMask::ENABLED_WITHOUT_GHOST_MOVEMENTS) { + if (m_shiftTraceGhostId == FIRST_GHOST) { + player.m_opacity = 30; + players.push_back(player); + player.m_opacity = opacity; + player.m_placement = + m_onionSkinMask.getShiftTraceGhostAff(0) * player.m_placement; + } else if (m_shiftTraceGhostId == SECOND_GHOST) { + player.m_opacity = 30; + players.push_back(player); + player.m_opacity = opacity; + player.m_placement = + m_onionSkinMask.getShiftTraceGhostAff(1) * player.m_placement; + } } } } @@ -511,31 +534,26 @@ void StageBuilder::addCellWithOnionSkin(PlayerSet &players, ToonzScene *scene, if (m_onionSkinMask.isShiftTraceEnabled() && col == m_currentColumnIndex) { TXshCell cell = xsh->getCell(row, col); - int r = row - 1; - // r,col can be a hold. find its starting point - for (; r - 1 >= 0 && xsh->getCell(r - 1, col) == cell; r--) - ; - if (cell.isEmpty()) r--; - - if (r >= 0 && + // First Ghost + int r; + r = row + m_onionSkinMask.getShiftTraceGhostFrameOffset(0); + if (r >= 0 && xsh->getCell(r, col) != cell && (cell.getSimpleLevel() == 0 || xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) { m_shiftTraceGhostId = FIRST_GHOST; addCell(players, scene, xsh, r, col, level); } - TXshCell otherCell; - if (cell.getSimpleLevel() != 0) { - for (r = row + 1; (otherCell = xsh->getCell(r, col)) == cell; r++) - ; - - if (cell.getSimpleLevel() == 0 || - otherCell.getSimpleLevel() == cell.getSimpleLevel()) { - m_shiftTraceGhostId = SECOND_GHOST; - addCell(players, scene, xsh, r, col, level); - } + r = row + m_onionSkinMask.getShiftTraceGhostFrameOffset(1); + if (r >= 0 && xsh->getCell(r, col) != cell && + (cell.getSimpleLevel() == 0 || + xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) { + m_shiftTraceGhostId = SECOND_GHOST; + addCell(players, scene, xsh, r, col, level); } + + // draw current working frame if (!cell.isEmpty()) { m_shiftTraceGhostId = TRACED; addCell(players, scene, xsh, row, col, level); @@ -632,10 +650,77 @@ void StageBuilder::addFrame(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, void StageBuilder::addSimpleLevelFrame(PlayerSet &players, TXshSimpleLevel *level, const TFrameId &fid) { + auto addGhost = [&](int ghostIndex, int ghostRow, bool fullOpac = false) { + const TFrameId &ghostFid = level->index2fid(ghostRow); + + Player player; + player.m_sl = level; + player.m_frame = level->guessIndex(ghostFid); + player.m_fid = ghostFid; + player.m_isCurrentColumn = true; + player.m_isCurrentXsheetLevel = true; + player.m_isEditingLevel = true; + player.m_currentFrameId = m_currentFrameId; + player.m_isGuidedDrawingEnabled = m_isGuidedDrawingEnabled; + player.m_isVisibleinOSM = ghostRow >= 0; + player.m_onionSkinDistance = m_onionSkinDistance; + player.m_dpiAff = getDpiAffine(level, ghostFid); + player.m_ancestorColumnIndex = -1; + + if (fullOpac) { + player.m_placement = m_onionSkinMask.getShiftTraceGhostAff(ghostIndex) * + player.m_placement; + players.push_back(player); + return; + } + + if (m_shiftTraceGhostId != TRACED) + player.m_opacity = + UCHAR(255.0 * (1.0 - OnionSkinMask::getOnionSkinFade(1))); + ; + int opacity = player.m_opacity; + player.m_bingoOrder = 10; + if (m_onionSkinMask.getShiftTraceStatus() != + OnionSkinMask::ENABLED_WITHOUT_GHOST_MOVEMENTS) { + player.m_opacity = 30; + players.push_back(player); + player.m_opacity = opacity; + player.m_placement = m_onionSkinMask.getShiftTraceGhostAff(ghostIndex) * + player.m_placement; + } + players.push_back(player); + }; + int index = -1; int row = level->guessIndex(fid); - if (!m_onionSkinMask.isEmpty() && m_onionSkinMask.isEnabled()) { + + // Shift & Trace + if (m_onionSkinMask.isShiftTraceEnabled()) { + int previousOffset = m_onionSkinMask.getShiftTraceGhostFrameOffset(0); + int forwardOffset = m_onionSkinMask.getShiftTraceGhostFrameOffset(1); + + // If F1, F2 or F3 key is pressed, then only + // display the corresponding ghost + int flipKey = m_onionSkinMask.getGhostFlipKey(); + if (Qt::Key_F1 <= flipKey && flipKey <= Qt::Key_F3) { + if (flipKey == Qt::Key_F1 && previousOffset != 0) { + addGhost(0, row + previousOffset, true); + return; + } else if (flipKey == Qt::Key_F3 && forwardOffset != 0) { + addGhost(1, row + forwardOffset, true); + return; + } + } + + else { + // draw the first ghost + if (previousOffset != 0) addGhost(0, row + previousOffset); + if (forwardOffset != 0) addGhost(1, row + forwardOffset); + } + } + // Onion Skin + else if (!m_onionSkinMask.isEmpty() && m_onionSkinMask.isEnabled()) { std::vector rows; m_onionSkinMask.getAll(row, rows);