diff --git a/toonz/sources/include/tools/tool.h b/toonz/sources/include/tools/tool.h index 7742ebf..91e4bdd 100644 --- a/toonz/sources/include/tools/tool.h +++ b/toonz/sources/include/tools/tool.h @@ -607,7 +607,8 @@ public: //! onionskins) //! (window coordinate, pixels, bottom-left origin) virtual int posToRow(const TPointD &p, double distance, - bool includeInvisible = true) const = 0; + bool includeInvisible = true, + bool currentColumnOnly = false) const = 0; //! return pos in pixel, bottom-left origin virtual TPointD worldToPos(const TPointD &worldPos) const = 0; diff --git a/toonz/sources/include/tools/tooloptions.h b/toonz/sources/include/tools/tooloptions.h index 7ca81a6..c5b503d 100644 --- a/toonz/sources/include/tools/tooloptions.h +++ b/toonz/sources/include/tools/tooloptions.h @@ -20,6 +20,7 @@ #include #include #include +#include // STD includes #include @@ -678,15 +679,25 @@ protected slots: class ShiftTraceToolOptionBox final : public ToolOptionsBox { Q_OBJECT - QPushButton *m_resetPrevGhostBtn; - QPushButton *m_resetAfterGhostBtn; + QFrame *m_prevFrame, *m_afterFrame; + QRadioButton *m_prevRadioBtn, *m_afterRadioBtn; + QPushButton *m_resetPrevGhostBtn, *m_resetAfterGhostBtn; + TTool *m_tool; void resetGhost(int index); +protected: + void showEvent(QShowEvent *); + void hideEvent(QShowEvent *); + public: - ShiftTraceToolOptionBox(QWidget *parent = 0); + ShiftTraceToolOptionBox(QWidget *parent = 0, TTool *tool = 0); + void updateStatus() override; protected slots: void onResetPrevGhostBtnPressed(); void onResetAfterGhostBtnPressed(); + void onPrevRadioBtnClicked(); + void onAfterRadioBtnClicked(); + void updateColors(); }; //----------------------------------------------------------------------------- diff --git a/toonz/sources/include/toonz/preferences.h b/toonz/sources/include/toonz/preferences.h index 4d5141c..6818966 100644 --- a/toonz/sources/include/toonz/preferences.h +++ b/toonz/sources/include/toonz/preferences.h @@ -507,6 +507,11 @@ public: } bool getOnionSkinDuringPlayback() { return m_onionSkinDuringPlayback; } void setOnionSkinDuringPlayback(bool on); + + void useOnionColorsForShiftAndTraceGhosts(bool on); + bool areOnionColorsUsedForShiftAndTraceGhosts() const { + return m_useOnionColorsForShiftAndTraceGhosts; + } // Transparency Check tab void setTranspCheckData(const TPixel &bg, const TPixel &ink, @@ -711,7 +716,8 @@ private: TPixel32 m_currentColumnColor; - bool m_enableWinInk = false; + bool m_enableWinInk = false; + bool m_useOnionColorsForShiftAndTraceGhosts = false; private: Preferences(); diff --git a/toonz/sources/include/toonz/stageplayer.h b/toonz/sources/include/toonz/stageplayer.h index cdb6a8b..d1aa926 100644 --- a/toonz/sources/include/toonz/stageplayer.h +++ b/toonz/sources/include/toonz/stageplayer.h @@ -107,6 +107,8 @@ public: static double m_firstBackOnionSkin; static double m_lastBackVisibleSkin; + static bool m_isShiftAndTraceEnabled; + TPixel32 m_filterColor; public: diff --git a/toonz/sources/include/toonz/stagevisitor.h b/toonz/sources/include/toonz/stagevisitor.h index 4461362..a861222 100644 --- a/toonz/sources/include/toonz/stagevisitor.h +++ b/toonz/sources/include/toonz/stagevisitor.h @@ -289,6 +289,8 @@ class DVAPI Picker final : public Visitor { TAffine m_viewAff; double m_minDist2; + int m_currentColumnIndex = -1; + public: Picker(const TAffine &viewAff, const TPointD &p, const ImagePainter::VisualSettings &vs); @@ -304,6 +306,8 @@ public: int getColumnIndex() const; void getColumnIndexes(std::vector &indexes) const; int getRow() const; + + void setCurrentColumnIndex(int index) { m_currentColumnIndex = index; } }; //============================================================================= diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt index 06626e1..b4aa919 100644 --- a/toonz/sources/tnztools/CMakeLists.txt +++ b/toonz/sources/tnztools/CMakeLists.txt @@ -36,6 +36,7 @@ set(HEADERS ${MOC_HEADERS} ../include/tools/toolutils.h ../include/tools/RGBpicker.h mypainttoonzbrush.h + shifttracetool.h ) set(SOURCES @@ -96,6 +97,7 @@ set(SOURCES fingertool.cpp rulertool.cpp mypainttoonzbrush.cpp + shifttracetool.cpp ) set(RESOURCES tnztools.qrc) diff --git a/toonz/sources/tnztools/shifttracetool.cpp b/toonz/sources/tnztools/shifttracetool.cpp new file mode 100644 index 0000000..a4f3647 --- /dev/null +++ b/toonz/sources/tnztools/shifttracetool.cpp @@ -0,0 +1,603 @@ + + +#include "shifttracetool.h" +#include "toonz/onionskinmask.h" +#include "toonz/tonionskinmaskhandle.h" +#include "tools/cursors.h" +#include "timage.h" +#include "trasterimage.h" +#include "ttoonzimage.h" +#include "tvectorimage.h" +#include "toonz/txsheet.h" +#include "toonz/txshcell.h" +#include "toonz/txsheethandle.h" +#include "toonz/tframehandle.h" +#include "toonz/tcolumnhandle.h" +#include "toonz/txshlevelhandle.h" +#include "tools/toolhandle.h" +#include "toonz/txshsimplelevel.h" +#include "toonz/dpiscale.h" +#include "toonz/stage.h" +#include "tpixel.h" +#include "toonzqt/menubarcommand.h" + +#include "toonz/preferences.h" +#include "toonzqt/gutil.h" + +#include "tgl.h" +#include +#include + +//============================================================================= + +static bool circumCenter(TPointD &out, const TPointD &a, const TPointD &b, + const TPointD &c) { + double d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)); + if (fabs(d) < 0.0001) { + out = TPointD(); + return false; + } + out.x = ((a.y * a.y + a.x * a.x) * (b.y - c.y) + + (b.y * b.y + b.x * b.x) * (c.y - a.y) + + (c.y * c.y + c.x * c.x) * (a.y - b.y)) / + d; + out.y = ((a.y * a.y + a.x * a.x) * (c.x - b.x) + + (b.y * b.y + b.x * b.x) * (a.x - c.x) + + (c.y * c.y + c.x * c.x) * (b.x - a.x)) / + d; + return true; +} + +//============================================================================= + +ShiftTraceTool::ShiftTraceTool() + : TTool("T_ShiftTrace") + , m_ghostIndex(0) + , m_curveStatus(NoCurve) + , m_gadget(NoGadget) + , m_highlightedGadget(NoGadget) { + bind(TTool::AllTargets); // Deals with tool deactivation internally +} + +void ShiftTraceTool::clearData() { + m_ghostIndex = 0; + m_curveStatus = NoCurve; + m_gadget = NoGadget; + m_highlightedGadget = NoGadget; + + m_box = TRectD(); + for (int i = 0; i < 2; i++) { + m_row[i] = -1; + m_aff[i] = TAffine(); + m_center[i] = TPointD(); + } +} + +void ShiftTraceTool::updateBox() { + if (m_ghostIndex < 0 || 2 <= m_ghostIndex || m_row[m_ghostIndex] < 0) return; + + TImageP img; + + TApplication *app = TTool::getApplication(); + if (app->getCurrentFrame()->isEditingScene()) { + int col = app->getCurrentColumn()->getColumnIndex(); + int row = m_row[m_ghostIndex]; + TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); + + TXshCell cell = xsh->getCell(row, col); + TXshSimpleLevel *sl = cell.getSimpleLevel(); + if (sl) { + 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() { + m_box = TRectD(); + for (int i = 0; i < 2; i++) m_row[i] = -1; + m_dpiAff = TAffine(); + TApplication *app = TTool::getApplication(); + + OnionSkinMask osm = app->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; + } + + 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; + } + } + } + updateBox(); +} + +// +// Compute m_aff[0] and m_aff[1] according to the current curve +// +void ShiftTraceTool::updateCurveAffs() { + if (m_curveStatus != ThreePointsCurve) { + m_aff[0] = m_aff[1] = TAffine(); + } else { + double phi0 = 0, phi1 = 0; + TPointD center; + if (circumCenter(center, m_p0, m_p1, m_p2)) { + TPointD v0 = normalize(m_p0 - center); + TPointD v1 = normalize(m_p1 - center); + TPointD v2 = normalize(m_p2 - center); + TPointD u0(-v0.y, v0.x); + TPointD u1(-v1.y, v1.x); + phi0 = atan2((v2 * u0), (v2 * v0)) * 180.0 / 3.1415; + phi1 = atan2((v2 * u1), (v2 * v1)) * 180.0 / 3.1415; + } + m_aff[0] = TTranslation(m_p2 - m_p0) * TRotation(m_p0, phi0); + m_aff[1] = TTranslation(m_p2 - m_p1) * TRotation(m_p1, phi1); + } +} + +void ShiftTraceTool::updateGhost() { + OnionSkinMask osm = + TTool::getApplication()->getCurrentOnionSkin()->getOnionSkinMask(); + osm.setShiftTraceGhostAff(0, m_aff[0]); + osm.setShiftTraceGhostAff(1, m_aff[1]); + osm.setShiftTraceGhostCenter(0, m_center[0]); + osm.setShiftTraceGhostCenter(1, m_center[1]); + TTool::getApplication()->getCurrentOnionSkin()->setOnionSkinMask(osm); +} + +void ShiftTraceTool::reset() { + int ghostIndex = m_ghostIndex; + onActivate(); + invalidate(); + m_ghostIndex = ghostIndex; + + TTool::getApplication() + ->getCurrentTool() + ->notifyToolChanged(); // Refreshes toolbar values +} + +TAffine ShiftTraceTool::getGhostAff() { + if (0 <= m_ghostIndex && m_ghostIndex < 2) + return m_aff[m_ghostIndex] * m_dpiAff; + else + return TAffine(); +} + +void ShiftTraceTool::drawDot(const TPointD ¢er, double r, + const TPixel32 &color) { + tglColor(color); + tglDrawDisk(center, r); + glColor3d(0.2, 0.2, 0.2); + tglDrawCircle(center, r); +} + +void ShiftTraceTool::drawControlRect() { // TODO + if (m_ghostIndex < 0 || m_ghostIndex > 1) return; + int row = m_row[m_ghostIndex]; + if (row < 0) return; + + TRectD box = m_box; + if (box.isEmpty()) return; + glPushMatrix(); + tglMultMatrix(getGhostAff()); + + TPixel32 color; + + // 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); + glVertex2d(box.x1, box.y0); + glVertex2d(box.x1, box.y1); + glVertex2d(box.x0, box.y1); + glVertex2d(box.x0, box.y0); + glEnd(); + 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 == MoveCenterGadget ? TPixel32(200, 100, 100) + : TPixel32::White; + TPointD c = m_center[m_ghostIndex]; + drawDot(c, r, color); + } + glPopMatrix(); +} + +void ShiftTraceTool::drawCurve() { + if (m_curveStatus == NoCurve) return; + double r = 4 * sqrt(tglGetPixelSize2()); + double u = getPixelSize(); + if (m_curveStatus == TwoPointsCurve) { + 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 == CurveP0Gadget + ? TPixel32(200, 100, 100) + : TPixel32::White; + drawDot(m_p0, r, color); + color = m_highlightedGadget == CurveP1Gadget ? TPixel32(200, 100, 100) + : TPixel32::White; + drawDot(m_p1, r, color); + + glColor3d(0.2, 0.2, 0.2); + + TPointD center; + if (circumCenter(center, m_p0, m_p1, m_p2)) { + double radius = norm(center - m_p1); + glBegin(GL_LINE_STRIP); + int n = 100; + for (int i = 0; i < n; i++) { + double t = (double)i / n; + TPointD p = (1 - t) * m_p0 + t * m_p2; + p = center + radius * normalize(p - center); + tglVertex(p); + } + for (int i = 0; i < n; i++) { + double t = (double)i / n; + TPointD p = (1 - t) * m_p2 + t * m_p1; + p = center + radius * normalize(p - center); + tglVertex(p); + } + glEnd(); + } else { + tglDrawSegment(m_p0, m_p1); + } + color = m_highlightedGadget == CurvePmGadget ? TPixel32(200, 100, 100) + : TPixel32::White; + drawDot(m_p2, r, color); + } +} + +void ShiftTraceTool::onActivate() { + m_ghostIndex = 0; + m_curveStatus = NoCurve; + clearData(); + OnionSkinMask osm = + TTool::getApplication()->getCurrentOnionSkin()->getOnionSkinMask(); + m_aff[0] = osm.getShiftTraceGhostAff(0); + m_aff[1] = osm.getShiftTraceGhostAff(1); + m_center[0] = osm.getShiftTraceGhostCenter(0); + m_center[1] = osm.getShiftTraceGhostCenter(1); +} + +void ShiftTraceTool::onDeactivate() { + QAction *action = CommandManager::instance()->getAction("MI_EditShift"); + action->setChecked(false); +} + +ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) { + std::vector> gadgets; + 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(); + 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(), 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 * pixelSize, 2); + for (int i = 0; i < (int)gadgets.size(); i++) { + double d2 = norm2(gadgets[i].first - p); + if (d2 < minDist2) { + minDist2 = d2; + k = i; + } + } + if (k >= 0) return gadgets[k].second; + + // rect-point + if (0 <= m_ghostIndex && m_ghostIndex < 2) { + 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) { + x = q.x; + double d0 = fabs(m_box.y0 - q.y); + double d1 = fabs(m_box.y1 - q.y); + if (d0 < d1) { + d = d0; + y = m_box.y0; + } else { + d = d1; + y = m_box.y1; + } + } + if (m_box.y0 < q.y && q.y < m_box.y1) { + double d0 = fabs(m_box.x0 - q.x); + double d1 = fabs(m_box.x1 - q.x); + if (d0 < d) { + d = d0; + y = q.y; + x = m_box.x0; + } + if (d1 < d) { + d = d1; + y = q.y; + x = m_box.x1; + } + } + if (d < big) { + TPointD pp = aff * TPointD(x, y); + double d = norm(p - pp); + if (d < 10 * getPixelSize()) { + if (m_box.contains(q)) + return TranslateGadget; + else + return RotateGadget; + } + } + if (m_box.contains(q)) + return NoGadget_InBox; + else + return NoGadget; + } + return NoGadget; +} + +void ShiftTraceTool::mouseMove(const TPointD &pos, const TMouseEvent &e) { + GadgetId highlightedGadget = getGadget(pos); + if (highlightedGadget != m_highlightedGadget) { + m_highlightedGadget = highlightedGadget; + invalidate(); + } +} + +void ShiftTraceTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) { + m_gadget = m_highlightedGadget; + m_oldPos = m_startPos = pos; + + bool notify = false; + + 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, true); + if (row >= 0) { + int index = -1; + TApplication *app = TTool::getApplication(); + 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; + notify = true; + } + } + } + + m_oldAff = m_aff[m_ghostIndex]; + invalidate(); + + if (notify) { + TTool::getApplication() + ->getCurrentTool() + ->notifyToolChanged(); // Refreshes toolbar values + } +} + +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; + m_gadget = CurveP1Gadget; + } + } + + if (isCurveGadget(m_gadget)) { + if (m_gadget == CurveP0Gadget) + m_p0 = pos; + else if (m_gadget == CurveP1Gadget) + m_p1 = pos; + else + m_p2 = pos; + updateCurveAffs(); + } else if (m_gadget == RotateGadget) { + TAffine aff = getGhostAff(); + TPointD c = aff * m_center[m_ghostIndex]; + TPointD a = m_oldPos - c; + TPointD b = pos - c; + m_oldPos = pos; + TPointD u = normalize(a); + double phi = + atan2(-u.y * b.x + u.x * b.y, u.x * b.x + u.y * b.y) * 180.0 / 3.14153; + + TPointD imgC = aff * m_center[m_ghostIndex]; + + m_aff[m_ghostIndex] = TRotation(imgC, phi) * m_aff[m_ghostIndex]; + } else if (m_gadget == MoveCenterGadget) { + TAffine aff = getGhostAff().inv(); + TPointD delta = aff * pos - aff * m_oldPos; + m_oldPos = pos; + m_center[m_ghostIndex] += delta; + } else if (m_gadget == TranslateGadget) { + 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(); + invalidate(); +} + +void ShiftTraceTool::leftButtonUp(const TPointD &pos, const TMouseEvent &) { + if (CurveP0Gadget <= m_gadget && m_gadget <= CurvePmGadget) { + if (m_curveStatus == TwoPointsCurve) { + m_p2 = (m_p0 + m_p1) * 0.5; + m_curveStatus = ThreePointsCurve; + updateCurveAffs(); + updateGhost(); + + m_center[0] = (m_aff[0] * m_dpiAff).inv() * m_p2; + m_center[1] = (m_aff[1] * m_dpiAff).inv() * m_p2; + } + } + m_gadget = NoGadget; + invalidate(); + + TTool::getApplication() + ->getCurrentTool() + ->notifyToolChanged(); // Refreshes toolbar values +} + +void ShiftTraceTool::draw() { + updateData(); + drawControlRect(); + drawCurve(); +} + +int ShiftTraceTool::getCursorId() const { + 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 // 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); +} + +void ShiftTraceTool::onLeave() { + OnionSkinMask osm = + TTool::getApplication()->getCurrentOnionSkin()->getOnionSkinMask(); + osm.clearGhostFlipKey(); + TTool::getApplication()->getCurrentOnionSkin()->setOnionSkinMask(osm); +} + +void ShiftTraceTool::setCurrentGhostIndex(int index) { + m_ghostIndex = index; + updateBox(); + invalidate(); +} + +ShiftTraceTool shiftTraceTool; diff --git a/toonz/sources/tnztools/shifttracetool.h b/toonz/sources/tnztools/shifttracetool.h new file mode 100644 index 0000000..3fd6412 --- /dev/null +++ b/toonz/sources/tnztools/shifttracetool.h @@ -0,0 +1,82 @@ +#pragma once + +#include "tools/tool.h" + +class ShiftTraceTool final : public TTool { +public: + enum CurveStatus { + NoCurve, + TwoPointsCurve, // just during the first click&drag + ThreePointsCurve + }; + + enum GadgetId { + NoGadget, + NoGadget_InBox, + CurveP0Gadget, + CurveP1Gadget, + CurvePmGadget, + MoveCenterGadget, + RotateGadget, + TranslateGadget, + ScaleGadget + }; + inline bool isCurveGadget(GadgetId id) const { + return CurveP0Gadget <= id && id <= CurvePmGadget; + } + +private: + TPointD m_oldPos, m_startPos; + int m_ghostIndex; + TPointD m_p0, m_p1, m_p2; + + CurveStatus m_curveStatus; + GadgetId m_gadget; + GadgetId m_highlightedGadget; + + TRectD m_box; + TAffine m_dpiAff; + int m_row[2]; + TAffine m_aff[2]; + TPointD m_center[2]; + + TAffine m_oldAff; + +public: + ShiftTraceTool(); + + ToolType getToolType() const override { return GenericTool; } + + void clearData(); + void updateData(); + void updateBox(); + void updateCurveAffs(); + void updateGhost(); + + void reset() override; + + void mouseMove(const TPointD &, const TMouseEvent &e) override; + void leftButtonDown(const TPointD &, const TMouseEvent &) override; + void leftButtonDrag(const TPointD &, const TMouseEvent &) override; + void leftButtonUp(const TPointD &, const TMouseEvent &) override; + void draw() override; + + TAffine getGhostAff(); + GadgetId getGadget(const TPointD &); + void drawDot(const TPointD ¢er, double r, + const TPixel32 &color = TPixel32::White); + void drawControlRect(); + void drawCurve(); + + void onActivate() override; + void onDeactivate() override; + + void onLeave() override; + + bool isEventAcceptable(QEvent *e) override; + + int getCursorId() const override; + + int getCurrentGhostIndex() { return m_ghostIndex; } + void setCurrentGhostIndex(int index); +}; \ No newline at end of file diff --git a/toonz/sources/tnztools/tooloptions.cpp b/toonz/sources/tnztools/tooloptions.cpp index a7c98bc..dfc150f 100644 --- a/toonz/sources/tnztools/tooloptions.cpp +++ b/toonz/sources/tnztools/tooloptions.cpp @@ -16,6 +16,7 @@ //#include "rgbpickertool.h" #include "rulertool.h" +#include "shifttracetool.h" // TnzQt includes #include "toonzqt/dvdialog.h" @@ -2501,24 +2502,58 @@ void StylePickerToolOptionsBox::updateRealTimePickLabel(const int ink, // ShiftTraceToolOptionBox //----------------------------------------------------------------------------- -ShiftTraceToolOptionBox::ShiftTraceToolOptionBox(QWidget *parent) - : ToolOptionsBox(parent) { +ShiftTraceToolOptionBox::ShiftTraceToolOptionBox(QWidget *parent, TTool *tool) + : ToolOptionsBox(parent), m_tool(tool) { 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_prevFrame = new QFrame(this); + m_afterFrame = new QFrame(this); + + m_resetPrevGhostBtn = new QPushButton(tr("Reset Previous"), this); + m_resetAfterGhostBtn = new QPushButton(tr("Reset Following"), this); + + m_prevRadioBtn = new QRadioButton(tr("Previous Drawing"), this); + m_afterRadioBtn = new QRadioButton(tr("Following Drawing"), this); + m_prevFrame->setFixedSize(10, 21); + m_afterFrame->setFixedSize(10, 21); + + m_layout->addWidget(m_prevFrame, 0); + m_layout->addWidget(m_prevRadioBtn, 0); m_layout->addWidget(m_resetPrevGhostBtn, 0); + + m_layout->addWidget(new DVGui::Separator("", this, false)); + + m_layout->addWidget(m_afterFrame, 0); + m_layout->addWidget(m_afterRadioBtn, 0); m_layout->addWidget(m_resetAfterGhostBtn, 0); + m_layout->addStretch(1); - connect(m_resetPrevGhostBtn, SIGNAL(clicked()), this, + connect(m_resetPrevGhostBtn, SIGNAL(clicked(bool)), this, SLOT(onResetPrevGhostBtnPressed())); - connect(m_resetAfterGhostBtn, SIGNAL(clicked()), this, + connect(m_resetAfterGhostBtn, SIGNAL(clicked(bool)), this, SLOT(onResetAfterGhostBtnPressed())); + connect(m_prevRadioBtn, SIGNAL(clicked(bool)), this, + SLOT(onPrevRadioBtnClicked())); + connect(m_afterRadioBtn, SIGNAL(clicked(bool)), this, + SLOT(onAfterRadioBtnClicked())); + + updateStatus(); +} + +void ShiftTraceToolOptionBox::showEvent(QShowEvent *) { + TTool::Application *app = TTool::getApplication(); + connect(app->getCurrentOnionSkin(), SIGNAL(onionSkinMaskChanged()), this, + SLOT(updateColors())); + updateColors(); +} + +void ShiftTraceToolOptionBox::hideEvent(QShowEvent *) { + TTool::Application *app = TTool::getApplication(); + disconnect(app->getCurrentOnionSkin(), SIGNAL(onionSkinMaskChanged()), this, + SLOT(updateColors())); } void ShiftTraceToolOptionBox::resetGhost(int index) { @@ -2530,12 +2565,68 @@ void ShiftTraceToolOptionBox::resetGhost(int index) { app->getCurrentOnionSkin()->notifyOnionSkinMaskChanged(); TTool *tool = app->getCurrentTool()->getTool(); if (tool) tool->reset(); + + if (index == 0) + m_resetPrevGhostBtn->setDisabled(true); + else // index == 1 + m_resetAfterGhostBtn->setDisabled(true); } void ShiftTraceToolOptionBox::onResetPrevGhostBtnPressed() { resetGhost(0); } void ShiftTraceToolOptionBox::onResetAfterGhostBtnPressed() { resetGhost(1); } +void ShiftTraceToolOptionBox::updateColors() { + TPixel front, back; + bool ink; + Preferences::instance()->getOnionData(front, back, ink); + + m_prevFrame->setStyleSheet(QString("background:rgb(%1,%2,%3,255);") + .arg((int)back.r) + .arg((int)back.g) + .arg((int)back.b)); + m_afterFrame->setStyleSheet(QString("background:rgb(%1,%2,%3,255);") + .arg((int)front.r) + .arg((int)front.g) + .arg((int)front.b)); +} + +void ShiftTraceToolOptionBox::updateStatus() { + TTool::Application *app = TTool::getApplication(); + OnionSkinMask osm = app->getCurrentOnionSkin()->getOnionSkinMask(); + if (osm.getShiftTraceGhostAff(0).isIdentity() && + osm.getShiftTraceGhostCenter(0) == TPointD()) + m_resetPrevGhostBtn->setDisabled(true); + else + m_resetPrevGhostBtn->setEnabled(true); + + if (osm.getShiftTraceGhostAff(1).isIdentity() && + osm.getShiftTraceGhostCenter(1) == TPointD()) + m_resetAfterGhostBtn->setDisabled(true); + else + m_resetAfterGhostBtn->setEnabled(true); + + // Check the ghost index + ShiftTraceTool *stTool = (ShiftTraceTool *)m_tool; + if (!stTool) return; + if (stTool->getCurrentGhostIndex() == 0) + m_prevRadioBtn->setChecked(true); + else // ghostIndex == 1 + m_afterRadioBtn->setChecked(true); +} + +void ShiftTraceToolOptionBox::onPrevRadioBtnClicked() { + ShiftTraceTool *stTool = (ShiftTraceTool *)m_tool; + if (!stTool) return; + stTool->setCurrentGhostIndex(0); +} + +void ShiftTraceToolOptionBox::onAfterRadioBtnClicked() { + ShiftTraceTool *stTool = (ShiftTraceTool *)m_tool; + if (!stTool) return; + stTool->setCurrentGhostIndex(1); +} + //============================================================================= // ToolOptions //----------------------------------------------------------------------------- @@ -2645,7 +2736,7 @@ void ToolOptions::onToolSwitched() { panel = new StylePickerToolOptionsBox(0, tool, currPalette, currTool, app->getPaletteController()); else if (tool->getName() == "T_ShiftTrace") - panel = new ShiftTraceToolOptionBox(this); + panel = new ShiftTraceToolOptionBox(this, tool); else panel = tool->createOptionsBox(); // Only this line should remain out // of that if/else monstrosity diff --git a/toonz/sources/toonz/CMakeLists.txt b/toonz/sources/toonz/CMakeLists.txt index b262ffc..004b5b1 100644 --- a/toonz/sources/toonz/CMakeLists.txt +++ b/toonz/sources/toonz/CMakeLists.txt @@ -107,7 +107,6 @@ set(MOC_HEADERS sceneviewerevents.h scriptconsolepanel.h selectionutils.h - shifttracetool.h shortcutpopup.h soundtrackexport.h startuppopup.h @@ -255,7 +254,6 @@ set(SOURCES sceneviewercontextmenu.cpp scenesettingspopup.cpp scriptconsolepanel.cpp - shifttracetool.cpp shortcutpopup.cpp soundtrackexport.cpp startuppopup.cpp diff --git a/toonz/sources/toonz/Resources/shift_and_trace.svg b/toonz/sources/toonz/Resources/shift_and_trace.svg new file mode 100644 index 0000000..b97685d --- /dev/null +++ b/toonz/sources/toonz/Resources/shift_and_trace.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/toonz/sources/toonz/Resources/shift_and_trace_edit.svg b/toonz/sources/toonz/Resources/shift_and_trace_edit.svg new file mode 100644 index 0000000..cb78a22 --- /dev/null +++ b/toonz/sources/toonz/Resources/shift_and_trace_edit.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toonz/sources/toonz/Resources/shift_and_trace_reset.svg b/toonz/sources/toonz/Resources/shift_and_trace_reset.svg new file mode 100644 index 0000000..6e85189 --- /dev/null +++ b/toonz/sources/toonz/Resources/shift_and_trace_reset.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index 49993c0..a942650 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -1956,13 +1956,16 @@ void MainWindow::defineActions() { MenuViewCommandType); createToggle(MI_ACheck, tr("&Gap Check"), "", ACheckToggleAction ? 1 : 0, MenuViewCommandType); - createToggle(MI_ShiftTrace, tr("Shift and Trace"), "", false, + QAction* shiftTraceAction = createToggle(MI_ShiftTrace, tr("Shift and Trace"), "", false, MenuViewCommandType); - createToggle(MI_EditShift, tr("Edit Shift"), "", false, MenuViewCommandType); + shiftTraceAction->setIcon(QIcon(":Resources/shift_and_trace.svg")); + shiftTraceAction = createToggle(MI_EditShift, tr("Edit Shift"), "", false, MenuViewCommandType); + shiftTraceAction->setIcon(QIcon(":Resources/shift_and_trace_edit.svg")); 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); + shiftTraceAction = createAction(MI_ResetShift, tr("Reset Shift"), "", MenuViewCommandType); + shiftTraceAction->setIcon(QIcon(":Resources/shift_and_trace_reset.svg")); if (QGLPixelBuffer::hasOpenGLPbuffers()) createToggle(MI_RasterizePli, tr("&Visualize Vector As Raster"), "", diff --git a/toonz/sources/toonz/preferencespopup.cpp b/toonz/sources/toonz/preferencespopup.cpp index 409d555..e5c946f 100644 --- a/toonz/sources/toonz/preferencespopup.cpp +++ b/toonz/sources/toonz/preferencespopup.cpp @@ -506,6 +506,7 @@ void PreferencesPopup::onOnionDataChanged(const TPixel32 &, bool isDragging) { TApp::instance()->getCurrentScene()->notifySceneChanged(); TApp::instance()->getCurrentLevel()->notifyLevelViewChange(); + TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged(); } //----------------------------------------------------------------------------- @@ -806,6 +807,12 @@ void PreferencesPopup::onOnionSkinDuringPlaybackChanged(int index) { //----------------------------------------------------------------------------- +void PreferencesPopup::onOnionColorsForShiftAndTraceChanged(int index) { + m_pref->useOnionColorsForShiftAndTraceGhosts(index == Qt::Checked); +} + +//----------------------------------------------------------------------------- + void PreferencesPopup::onGuidedDrawingStyleChanged(int index) { m_pref->setAnimatedGuidedDrawing(index); } @@ -1533,9 +1540,11 @@ PreferencesPopup::PreferencesPopup() m_onionSkinVisibility = new CheckBox(tr("Onion Skin ON")); m_onionSkinDuringPlayback = new CheckBox(tr("Show Onion Skin During Playback")); - m_frontOnionColor = new ColorField(this, false, frontColor); - m_backOnionColor = new ColorField(this, false, backColor); - m_inksOnly = new DVGui::CheckBox(tr("Display Lines Only ")); + m_frontOnionColor = new ColorField(this, false, frontColor); + m_backOnionColor = new ColorField(this, false, backColor); + m_useOnionColorsForShiftAndTraceCB = new CheckBox( + tr("Use Onion Skin Colors for Reference Drawings of Shift and Trace")); + m_inksOnly = new DVGui::CheckBox(tr("Display Lines Only ")); m_inksOnly->setChecked(onlyInks); int thickness = m_pref->getOnionPaperThickness(); @@ -1898,6 +1907,8 @@ PreferencesPopup::PreferencesPopup() m_onionSkinDuringPlayback->setChecked(m_pref->getOnionSkinDuringPlayback()); m_frontOnionColor->setEnabled(m_pref->isOnionSkinEnabled()); m_backOnionColor->setEnabled(m_pref->isOnionSkinEnabled()); + m_useOnionColorsForShiftAndTraceCB->setChecked( + m_pref->areOnionColorsUsedForShiftAndTraceGhosts()); m_inksOnly->setEnabled(m_pref->isOnionSkinEnabled()); QStringList guidedDrawingStyles; guidedDrawingStyles << tr("Arrow Markers") << tr("Animated Guide"); @@ -2558,6 +2569,8 @@ PreferencesPopup::PreferencesPopup() onionLay->addWidget(m_inksOnly, 0, Qt::AlignLeft | Qt::AlignVCenter); onionLay->addWidget(m_onionSkinDuringPlayback, 0, Qt::AlignLeft | Qt::AlignVCenter); + onionLay->addWidget(m_useOnionColorsForShiftAndTraceCB, 0, + Qt::AlignLeft | Qt::AlignVCenter); QGridLayout *guidedDrawingLay = new QGridLayout(); { guidedDrawingLay->addWidget(new QLabel(tr("Vector Guided Style:")), 0, @@ -2952,6 +2965,9 @@ PreferencesPopup::PreferencesPopup() SLOT(onOnionSkinVisibilityChanged(int))); ret = ret && connect(m_onionSkinDuringPlayback, SIGNAL(stateChanged(int)), SLOT(onOnionSkinDuringPlaybackChanged(int))); + ret = ret && + connect(m_useOnionColorsForShiftAndTraceCB, SIGNAL(stateChanged(int)), + SLOT(onOnionColorsForShiftAndTraceChanged(int))); ret = ret && connect(m_onionPaperThickness, SIGNAL(editingFinished()), SLOT(onOnionPaperThicknessChanged())); ret = ret && connect(m_guidedDrawingStyle, SIGNAL(currentIndexChanged(int)), diff --git a/toonz/sources/toonz/preferencespopup.h b/toonz/sources/toonz/preferencespopup.h index f361667..d53ab77 100644 --- a/toonz/sources/toonz/preferencespopup.h +++ b/toonz/sources/toonz/preferencespopup.h @@ -82,7 +82,8 @@ private: *m_useHigherDpiOnVectorSimplifyCB, *m_keepFillOnVectorSimplifyCB, *m_newLevelToCameraSizeCB, *m_ignoreImageDpiCB, *m_syncLevelRenumberWithXsheet, *m_downArrowInLevelStripCreatesNewFrame, - *m_enableAutoStretch, *m_enableWinInk; + *m_enableAutoStretch, *m_enableWinInk, + *m_useOnionColorsForShiftAndTraceCB; DVGui::FileField *m_customProjectRootFileField; @@ -182,6 +183,7 @@ private slots: void onReplaceAfterSaveLevelAsChanged(int index); void onOnionSkinVisibilityChanged(int); void onOnionSkinDuringPlaybackChanged(int); + void onOnionColorsForShiftAndTraceChanged(int); void onGuidedDrawingStyleChanged(int); void onActualPixelOnSceneModeChanged(int); void onMultiLayerStylePickerChanged(int); diff --git a/toonz/sources/toonz/sceneviewer.cpp b/toonz/sources/toonz/sceneviewer.cpp index 4f21bc2..2655b3e 100644 --- a/toonz/sources/toonz/sceneviewer.cpp +++ b/toonz/sources/toonz/sceneviewer.cpp @@ -2376,7 +2376,7 @@ includeInvisible); //----------------------------------------------------------------------------- int SceneViewer::posToRow(const TPointD &p, double distance, - bool includeInvisible) const { + bool includeInvisible, bool currentColumnOnly) const { int oldRasterizePli = TXshSimpleLevel::m_rasterizePli; TApp *app = TApp::instance(); OnionSkinMask osm = app->getCurrentOnionSkin()->getOnionSkinMask(); @@ -2405,6 +2405,8 @@ int SceneViewer::posToRow(const TPointD &p, double distance, args.m_osm = &osm; args.m_onlyVisible = includeInvisible; + if (currentColumnOnly) picker.setCurrentColumnIndex(currentColumnIndex); + Stage::visit(picker, args); } TXshSimpleLevel::m_rasterizePli = oldRasterizePli; diff --git a/toonz/sources/toonz/sceneviewer.h b/toonz/sources/toonz/sceneviewer.h index 05c6a47..03d3e1a 100644 --- a/toonz/sources/toonz/sceneviewer.h +++ b/toonz/sources/toonz/sceneviewer.h @@ -370,8 +370,8 @@ protected: //! return the row of the drawings intersecting point \b p (used with onion //! skins) //! (window coordinate, pixels, bottom-left origin) - int posToRow(const TPointD &p, double distance, - bool includeInvisible = true) const override; + int posToRow(const TPointD &p, double distance, bool includeInvisible = true, + bool currentColumnOnly = false) const override; void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; diff --git a/toonz/sources/toonz/sceneviewerevents.cpp b/toonz/sources/toonz/sceneviewerevents.cpp index d161190..48ad6ed 100644 --- a/toonz/sources/toonz/sceneviewerevents.cpp +++ b/toonz/sources/toonz/sceneviewerevents.cpp @@ -365,6 +365,11 @@ void SceneViewer::onLeave() { if (m_freezedStatus != NO_FREEZED) return; TTool *tool = TApp::instance()->getCurrentTool()->getTool(); if (tool && tool->isEnabled()) tool->onLeave(); + + // force reset the flipping of shift & trace + if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked()) + TTool::getTool("T_ShiftTrace", TTool::ToonzImage)->onLeave(); + update(); } diff --git a/toonz/sources/toonz/shifttracetool.cpp b/toonz/sources/toonz/shifttracetool.cpp deleted file mode 100644 index cbd7dcc..0000000 --- a/toonz/sources/toonz/shifttracetool.cpp +++ /dev/null @@ -1,642 +0,0 @@ - - -#include "shifttracetool.h" -#include "toonz/onionskinmask.h" -#include "toonz/tonionskinmaskhandle.h" -#include "tools/cursors.h" -#include "tools/tool.h" -#include "timage.h" -#include "trasterimage.h" -#include "ttoonzimage.h" -#include "tvectorimage.h" -#include "toonz/txsheet.h" -#include "toonz/txshcell.h" -#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 - -//============================================================================= - -static bool circumCenter(TPointD &out, const TPointD &a, const TPointD &b, - const TPointD &c) { - double d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)); - if (fabs(d) < 0.0001) { - out = TPointD(); - return false; - } - out.x = ((a.y * a.y + a.x * a.x) * (b.y - c.y) + - (b.y * b.y + b.x * b.x) * (c.y - a.y) + - (c.y * c.y + c.x * c.x) * (a.y - b.y)) / - d; - out.y = ((a.y * a.y + a.x * a.x) * (c.x - b.x) + - (b.y * b.y + b.x * b.x) * (a.x - c.x) + - (c.y * c.y + c.x * c.x) * (b.x - a.x)) / - d; - return true; -} - -//============================================================================= - -class ShiftTraceTool final : public TTool { -public: - enum CurveStatus { - NoCurve, - TwoPointsCurve, // just during the first click&drag - ThreePointsCurve - }; - - enum GadgetId { - NoGadget, - NoGadget_InBox, - CurveP0Gadget, - CurveP1Gadget, - CurvePmGadget, - MoveCenterGadget, - RotateGadget, - TranslateGadget, - ScaleGadget - }; - inline bool isCurveGadget(GadgetId id) const { - return CurveP0Gadget <= id && id <= CurvePmGadget; - } - -private: - TPointD m_oldPos, m_startPos; - int m_ghostIndex; - TPointD m_p0, m_p1, m_p2; - - CurveStatus m_curveStatus; - GadgetId m_gadget; - GadgetId m_highlightedGadget; - - TRectD m_box; - TAffine m_dpiAff; - int m_row[2]; - TAffine m_aff[2]; - TPointD m_center[2]; - - TAffine m_oldAff; - -public: - ShiftTraceTool(); - - ToolType getToolType() const override { return GenericTool; } - - void clearData(); - void updateData(); - void updateBox(); - void updateCurveAffs(); - void updateGhost(); - - void reset() override { - int ghostIndex = m_ghostIndex; - onActivate(); - invalidate(); - m_ghostIndex = ghostIndex; - } - - void mouseMove(const TPointD &, const TMouseEvent &e) override; - void leftButtonDown(const TPointD &, const TMouseEvent &) override; - void leftButtonDrag(const TPointD &, const TMouseEvent &) override; - void leftButtonUp(const TPointD &, const TMouseEvent &) override; - void draw() override; - - TAffine getGhostAff(); - GadgetId getGadget(const TPointD &); - void drawDot(const TPointD ¢er, double r, - const TPixel32 &color = TPixel32::White); - void drawControlRect(); - void drawCurve(); - - void onActivate() override { - m_ghostIndex = 0; - m_curveStatus = NoCurve; - clearData(); - OnionSkinMask osm = - TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); - m_aff[0] = osm.getShiftTraceGhostAff(0); - m_aff[1] = osm.getShiftTraceGhostAff(1); - m_center[0] = osm.getShiftTraceGhostCenter(0); - m_center[1] = osm.getShiftTraceGhostCenter(1); - } - void onDeactivate() override { - QAction *action = CommandManager::instance()->getAction("MI_EditShift"); - action->setChecked(false); - } - bool isEventAcceptable(QEvent *e) override; - - int getCursorId() const override; -}; - -ShiftTraceTool::ShiftTraceTool() - : TTool("T_ShiftTrace") - , m_ghostIndex(0) - , m_curveStatus(NoCurve) - , m_gadget(NoGadget) - , m_highlightedGadget(NoGadget) { - bind(TTool::AllTargets); // Deals with tool deactivation internally -} - -void ShiftTraceTool::clearData() { - m_ghostIndex = 0; - m_curveStatus = NoCurve; - m_gadget = NoGadget; - m_highlightedGadget = NoGadget; - - m_box = TRectD(); - for (int i = 0; i < 2; i++) { - m_row[i] = -1; - m_aff[i] = TAffine(); - m_center[i] = TPointD(); - } -} - -void ShiftTraceTool::updateBox() { - 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 = app->getCurrentXsheet()->getXsheet(); - - TXshCell cell = xsh->getCell(row, col); - TXshSimpleLevel *sl = cell.getSimpleLevel(); - if (sl) { - 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() { - 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; - } - - 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; - } - } - } - updateBox(); -} - -// -// Compute m_aff[0] and m_aff[1] according to the current curve -// -void ShiftTraceTool::updateCurveAffs() { - if (m_curveStatus != ThreePointsCurve) { - m_aff[0] = m_aff[1] = TAffine(); - } else { - double phi0 = 0, phi1 = 0; - TPointD center; - if (circumCenter(center, m_p0, m_p1, m_p2)) { - TPointD v0 = normalize(m_p0 - center); - TPointD v1 = normalize(m_p1 - center); - TPointD v2 = normalize(m_p2 - center); - TPointD u0(-v0.y, v0.x); - TPointD u1(-v1.y, v1.x); - phi0 = atan2((v2 * u0), (v2 * v0)) * 180.0 / 3.1415; - phi1 = atan2((v2 * u1), (v2 * v1)) * 180.0 / 3.1415; - } - m_aff[0] = TTranslation(m_p2 - m_p0) * TRotation(m_p0, phi0); - m_aff[1] = TTranslation(m_p2 - m_p1) * TRotation(m_p1, phi1); - } -} - -void ShiftTraceTool::updateGhost() { - OnionSkinMask osm = - TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask(); - osm.setShiftTraceGhostAff(0, m_aff[0]); - osm.setShiftTraceGhostAff(1, m_aff[1]); - osm.setShiftTraceGhostCenter(0, m_center[0]); - osm.setShiftTraceGhostCenter(1, m_center[1]); - TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm); -} - -TAffine ShiftTraceTool::getGhostAff() { - if (0 <= m_ghostIndex && m_ghostIndex < 2) - return m_aff[m_ghostIndex] * m_dpiAff; - else - return TAffine(); -} - -void ShiftTraceTool::drawDot(const TPointD ¢er, double r, - const TPixel32 &color) { - tglColor(color); - tglDrawDisk(center, r); - glColor3d(0.2, 0.2, 0.2); - tglDrawCircle(center, r); -} - -void ShiftTraceTool::drawControlRect() { // TODO - if (m_ghostIndex < 0 || m_ghostIndex > 1) return; - int row = m_row[m_ghostIndex]; - if (row < 0) return; - - TRectD box = m_box; - if (box.isEmpty()) return; - glPushMatrix(); - tglMultMatrix(getGhostAff()); - - TPixel32 color; - - // 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); - glVertex2d(box.x1, box.y0); - glVertex2d(box.x1, box.y1); - glVertex2d(box.x0, box.y1); - glVertex2d(box.x0, box.y0); - glEnd(); - 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 == MoveCenterGadget ? TPixel32(200, 100, 100) - : TPixel32::White; - TPointD c = m_center[m_ghostIndex]; - drawDot(c, r, color); - } - glPopMatrix(); -} - -void ShiftTraceTool::drawCurve() { - if (m_curveStatus == NoCurve) return; - double r = 4 * sqrt(tglGetPixelSize2()); - double u = getPixelSize(); - if (m_curveStatus == TwoPointsCurve) { - 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 == CurveP0Gadget - ? TPixel32(200, 100, 100) - : TPixel32::White; - drawDot(m_p0, r, color); - color = m_highlightedGadget == CurveP1Gadget ? TPixel32(200, 100, 100) - : TPixel32::White; - drawDot(m_p1, r, color); - - glColor3d(0.2, 0.2, 0.2); - - TPointD center; - if (circumCenter(center, m_p0, m_p1, m_p2)) { - double radius = norm(center - m_p1); - glBegin(GL_LINE_STRIP); - int n = 100; - for (int i = 0; i < n; i++) { - double t = (double)i / n; - TPointD p = (1 - t) * m_p0 + t * m_p2; - p = center + radius * normalize(p - center); - tglVertex(p); - } - for (int i = 0; i < n; i++) { - double t = (double)i / n; - TPointD p = (1 - t) * m_p2 + t * m_p1; - p = center + radius * normalize(p - center); - tglVertex(p); - } - glEnd(); - } else { - tglDrawSegment(m_p0, m_p1); - } - color = m_highlightedGadget == CurvePmGadget ? TPixel32(200, 100, 100) - : TPixel32::White; - drawDot(m_p2, r, color); - } -} - -ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) { - std::vector> gadgets; - 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(); - 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(), 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 * pixelSize, 2); - for (int i = 0; i < (int)gadgets.size(); i++) { - double d2 = norm2(gadgets[i].first - p); - if (d2 < minDist2) { - minDist2 = d2; - k = i; - } - } - if (k >= 0) return gadgets[k].second; - - // rect-point - if (0 <= m_ghostIndex && m_ghostIndex < 2) { - 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) { - x = q.x; - double d0 = fabs(m_box.y0 - q.y); - double d1 = fabs(m_box.y1 - q.y); - if (d0 < d1) { - d = d0; - y = m_box.y0; - } else { - d = d1; - y = m_box.y1; - } - } - if (m_box.y0 < q.y && q.y < m_box.y1) { - double d0 = fabs(m_box.x0 - q.x); - double d1 = fabs(m_box.x1 - q.x); - if (d0 < d) { - d = d0; - y = q.y; - x = m_box.x0; - } - if (d1 < d) { - d = d1; - y = q.y; - x = m_box.x1; - } - } - if (d < big) { - TPointD pp = aff * TPointD(x, y); - double d = norm(p - pp); - if (d < 10 * getPixelSize()) { - if (m_box.contains(q)) - return TranslateGadget; - else - return RotateGadget; - } - } - if (m_box.contains(q)) - return NoGadget_InBox; - else - return NoGadget; - } - return NoGadget; -} - -void ShiftTraceTool::mouseMove(const TPointD &pos, const TMouseEvent &e) { - GadgetId highlightedGadget = getGadget(pos); - if (highlightedGadget != m_highlightedGadget) { - m_highlightedGadget = highlightedGadget; - invalidate(); - } -} - -void ShiftTraceTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) { - m_gadget = m_highlightedGadget; - m_oldPos = m_startPos = pos; - - 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 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; - } - } - } - - m_oldAff = m_aff[m_ghostIndex]; - invalidate(); -} - -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; - m_gadget = CurveP1Gadget; - } - } - - if (isCurveGadget(m_gadget)) { - if (m_gadget == CurveP0Gadget) - m_p0 = pos; - else if (m_gadget == CurveP1Gadget) - m_p1 = pos; - else - m_p2 = pos; - updateCurveAffs(); - } else if (m_gadget == RotateGadget) { - TAffine aff = getGhostAff(); - TPointD c = aff * m_center[m_ghostIndex]; - TPointD a = m_oldPos - c; - TPointD b = pos - c; - m_oldPos = pos; - TPointD u = normalize(a); - double phi = - atan2(-u.y * b.x + u.x * b.y, u.x * b.x + u.y * b.y) * 180.0 / 3.14153; - - TPointD imgC = aff * m_center[m_ghostIndex]; - - m_aff[m_ghostIndex] = TRotation(imgC, phi) * m_aff[m_ghostIndex]; - } else if (m_gadget == MoveCenterGadget) { - TAffine aff = getGhostAff().inv(); - TPointD delta = aff * pos - aff * m_oldPos; - m_oldPos = pos; - m_center[m_ghostIndex] += delta; - } else if (m_gadget == TranslateGadget) { - 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(); - invalidate(); -} - -void ShiftTraceTool::leftButtonUp(const TPointD &pos, const TMouseEvent &) { - if (CurveP0Gadget <= m_gadget && m_gadget <= CurvePmGadget) { - if (m_curveStatus == TwoPointsCurve) { - m_p2 = (m_p0 + m_p1) * 0.5; - m_curveStatus = ThreePointsCurve; - updateCurveAffs(); - updateGhost(); - - m_center[0] = (m_aff[0] * m_dpiAff).inv() * m_p2; - m_center[1] = (m_aff[1] * m_dpiAff).inv() * m_p2; - } - } - m_gadget = NoGadget; - invalidate(); -} - -void ShiftTraceTool::draw() { - updateData(); - drawControlRect(); - drawCurve(); -} - -int ShiftTraceTool::getCursorId() const { - 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 // 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/shifttracetool.h b/toonz/sources/toonz/shifttracetool.h deleted file mode 100644 index 6f70f09..0000000 --- a/toonz/sources/toonz/shifttracetool.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/toonz/sources/toonz/toonz.qrc b/toonz/sources/toonz/toonz.qrc index 13c887d..bc223d9 100644 --- a/toonz/sources/toonz/toonz.qrc +++ b/toonz/sources/toonz/toonz.qrc @@ -465,5 +465,8 @@ Resources/colorchiporder_upleft.svg Resources/timeline2xsheet.svg Resources/xsheet2timeline.svg + Resources/shift_and_trace.svg + Resources/shift_and_trace_edit.svg + Resources/shift_and_trace_reset.svg diff --git a/toonz/sources/toonzlib/preferences.cpp b/toonz/sources/toonzlib/preferences.cpp index 606b2ac..a14e85f 100644 --- a/toonz/sources/toonzlib/preferences.cpp +++ b/toonz/sources/toonzlib/preferences.cpp @@ -344,7 +344,8 @@ Preferences::Preferences() , m_cursorBrushStyle("Default") , m_cursorOutlineEnabled(true) , m_currentColumnColor(TPixel::Black) - , m_enableWinInk(false) { + , m_enableWinInk(false) + , m_useOnionColorsForShiftAndTraceGhosts(false) { TCamera camera; m_defLevelType = PLI_XSHLEVEL; m_defLevelWidth = camera.getSize().lx; @@ -620,6 +621,8 @@ Preferences::Preferences() m_moveCurrentFrameByClickCellArea); getValue(*m_settings, "onionSkinEnabled", m_onionSkinEnabled); getValue(*m_settings, "onionSkinDuringPlayback", m_onionSkinDuringPlayback); + getValue(*m_settings, "useOnionColorsForShiftAndTraceGhosts", + m_useOnionColorsForShiftAndTraceGhosts); getValue(*m_settings, "multiLayerStylePickerEnabled", m_multiLayerStylePickerEnabled); getValue(*m_settings, "showKeyframesOnXsheetCellArea", @@ -1107,6 +1110,13 @@ void Preferences::setOnionSkinDuringPlayback(bool on) { //----------------------------------------------------------------- +void Preferences::useOnionColorsForShiftAndTraceGhosts(bool on) { + m_useOnionColorsForShiftAndTraceGhosts = on; + m_settings->setValue("useOnionColorsForShiftAndTraceGhosts", on ? "1" : "0"); +} + +//----------------------------------------------------------------- + void Preferences::setShow0ThickLines(bool on) { m_show0ThickLines = on; m_settings->setValue(s_show0ThickLines, s_bool[on]); diff --git a/toonz/sources/toonzlib/stage.cpp b/toonz/sources/toonzlib/stage.cpp index 4c3466e..987771e 100644 --- a/toonz/sources/toonzlib/stage.cpp +++ b/toonz/sources/toonzlib/stage.cpp @@ -418,9 +418,6 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, } 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() != @@ -428,13 +425,15 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, if (m_shiftTraceGhostId == FIRST_GHOST) { player.m_opacity = 30; players.push_back(player); - player.m_opacity = opacity; + player.m_opacity = opacity; + player.m_onionSkinDistance = -1; 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_opacity = opacity; + player.m_onionSkinDistance = 1; player.m_placement = m_onionSkinMask.getShiftTraceGhostAff(1) * player.m_placement; } @@ -532,32 +531,46 @@ void StageBuilder::addCellWithOnionSkin(PlayerSet &players, ToonzScene *scene, } }; // locals - if (m_onionSkinMask.isShiftTraceEnabled() && col == m_currentColumnIndex) { - TXshCell cell = xsh->getCell(row, col); - - // 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); - } + if (m_onionSkinMask.isShiftTraceEnabled()) { + if (col == m_currentColumnIndex) { + TXshCell cell = xsh->getCell(row, col); + + // 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); + } - 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); - } + 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); - m_shiftTraceGhostId = NO_GHOST; + // draw current working frame + if (!cell.isEmpty()) { + m_shiftTraceGhostId = TRACED; + addCell(players, scene, xsh, row, col, level); + m_shiftTraceGhostId = NO_GHOST; + } + } + // flip non-current columns as well + else { + int flipKey = m_onionSkinMask.getGhostFlipKey(); + if (flipKey == Qt::Key_F1) { + int r = row + m_onionSkinMask.getShiftTraceGhostFrameOffset(0); + addCell(players, scene, xsh, r, col, level); + } else if (flipKey == Qt::Key_F3) { + int r = row + m_onionSkinMask.getShiftTraceGhostFrameOffset(1); + addCell(players, scene, xsh, r, col, level); + } else + addCell(players, scene, xsh, row, col, level); } } else if (locals::doStandardOnionSkin(this, xsh, level, col)) { std::vector rows; @@ -674,17 +687,14 @@ void StageBuilder::addSimpleLevelFrame(PlayerSet &players, 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_opacity = opacity; + player.m_onionSkinDistance = (ghostIndex == 0) ? -1 : 1; player.m_placement = m_onionSkinMask.getShiftTraceGhostAff(ghostIndex) * player.m_placement; } @@ -832,10 +842,11 @@ void Stage::visit(Visitor &visitor, const VisitArgs &args) { sb.m_onionSkinMask = *osm; sb.m_currentFrameId = args.m_currentFrameId; sb.m_isGuidedDrawingEnabled = args.m_isGuidedDrawingEnabled; - Player::m_onionSkinFrontSize = 0; - Player::m_onionSkinBackSize = 0; - Player::m_firstBackOnionSkin = 0; - Player::m_lastBackVisibleSkin = 0; + Player::m_onionSkinFrontSize = 0; + Player::m_onionSkinBackSize = 0; + Player::m_firstBackOnionSkin = 0; + Player::m_lastBackVisibleSkin = 0; + Player::m_isShiftAndTraceEnabled = osm->isShiftTraceEnabled(); sb.addFrame(sb.m_players, scene, xsh, row, 0, args.m_onlyVisible, args.m_checkPreviewVisibility); @@ -867,14 +878,15 @@ void Stage::visit(Visitor &visitor, TXshSimpleLevel *level, const TFrameId &fid, const OnionSkinMask &osm, bool isPlaying, int isGuidedDrawingEnabled) { StageBuilder sb; - sb.m_vs = &visitor.m_vs; - sb.m_onionSkinMask = osm; - sb.m_currentFrameId = fid; - sb.m_isGuidedDrawingEnabled = isGuidedDrawingEnabled; - Player::m_onionSkinFrontSize = 0; - Player::m_onionSkinBackSize = 0; - Player::m_firstBackOnionSkin = 0; - Player::m_lastBackVisibleSkin = 0; + sb.m_vs = &visitor.m_vs; + sb.m_onionSkinMask = osm; + sb.m_currentFrameId = fid; + sb.m_isGuidedDrawingEnabled = isGuidedDrawingEnabled; + Player::m_onionSkinFrontSize = 0; + Player::m_onionSkinBackSize = 0; + Player::m_firstBackOnionSkin = 0; + Player::m_lastBackVisibleSkin = 0; + Player::m_isShiftAndTraceEnabled = osm.isShiftTraceEnabled(); sb.addSimpleLevelFrame(sb.m_players, level, fid); updateOnionSkinSize(sb.m_players); sb.visit(sb.m_players, visitor, isPlaying); diff --git a/toonz/sources/toonzlib/stageplayer.cpp b/toonz/sources/toonzlib/stageplayer.cpp index f7ea268..2f7ff4e 100644 --- a/toonz/sources/toonzlib/stageplayer.cpp +++ b/toonz/sources/toonzlib/stageplayer.cpp @@ -19,10 +19,11 @@ using namespace Stage; // Stage::Player implementation //***************************************************************************************** -double Player::m_onionSkinFrontSize = 0; -double Player::m_onionSkinBackSize = 0; -double Player::m_firstBackOnionSkin = 0; -double Player::m_lastBackVisibleSkin = 0; +double Player::m_onionSkinFrontSize = 0; +double Player::m_onionSkinBackSize = 0; +double Player::m_firstBackOnionSkin = 0; +double Player::m_lastBackVisibleSkin = 0; +bool Player::m_isShiftAndTraceEnabled = false; //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonzlib/stagevisitor.cpp b/toonz/sources/toonzlib/stagevisitor.cpp index eaf3b4d..46be125 100644 --- a/toonz/sources/toonzlib/stagevisitor.cpp +++ b/toonz/sources/toonzlib/stagevisitor.cpp @@ -49,6 +49,7 @@ #include "toonz/txshleveltypes.h" #include "imagebuilders.h" #include "toonz/tframehandle.h" +#include "toonz/preferences.h" // Qt includes #include @@ -228,6 +229,12 @@ void Picker::setDistance(double d) { m_minDist2 = d * d; } //----------------------------------------------------------------------------- void Picker::onImage(const Stage::Player &player) { + // if m_currentColumnIndex is other than the default value (-1), + // then pick only the current column. + if (m_currentColumnIndex != -1 && + m_currentColumnIndex != player.m_ancestorColumnIndex) + return; + bool picked = false; TAffine aff = m_viewAff * player.m_placement; TPointD point = aff.inv() * m_point; @@ -815,7 +822,9 @@ void RasterPainter::onVectorImage(TVectorImage *vi, if (player.m_onionSkinDistance != c_noOnionSkin) { TPixel32 frontOnionColor, backOnionColor; - if (player.m_onionSkinDistance != 0) { + if (player.m_onionSkinDistance != 0 && + (!player.m_isShiftAndTraceEnabled || + Preferences::instance()->areOnionColorsUsedForShiftAndTraceGhosts())) { prefs.getOnionData(frontOnionColor, backOnionColor, inksOnly); bgColor = (player.m_onionSkinDistance < 0) ? backOnionColor : frontOnionColor; @@ -961,11 +970,17 @@ void RasterPainter::onRasterImage(TRasterImage *ri, ? 0.9 : (1.0 - OnionSkinMask::getOnionSkinFade( player.m_onionSkinDistance)); - alpha = tcrop(tround(onionSkiFade * 255.0), 0, 255); - onionMode = (player.m_onionSkinDistance > 0) - ? Node::eOnionSkinFront - : ((player.m_onionSkinDistance < 0) ? Node::eOnionSkinBack - : Node::eOnionSkinNone); + alpha = tcrop(tround(onionSkiFade * 255.0), 0, 255); + if (player.m_isShiftAndTraceEnabled && + !Preferences::instance()->areOnionColorsUsedForShiftAndTraceGhosts()) + onionMode = Node::eOnionSkinNone; + else { + onionMode = + (player.m_onionSkinDistance > 0) + ? Node::eOnionSkinFront + : ((player.m_onionSkinDistance < 0) ? Node::eOnionSkinBack + : Node::eOnionSkinNone); + } } else if (player.m_opacity < 255) alpha = player.m_opacity; TXshSimpleLevel *sl = player.m_sl; @@ -1018,11 +1033,19 @@ void RasterPainter::onToonzImage(TToonzImage *ti, const Stage::Player &player) { ? 0.9 : (1.0 - OnionSkinMask::getOnionSkinFade( player.m_onionSkinDistance)); - alpha = tcrop(tround(onionSkiFade * 255.0), 0, 255); - onionMode = (player.m_onionSkinDistance > 0) - ? Node::eOnionSkinFront - : ((player.m_onionSkinDistance < 0) ? Node::eOnionSkinBack - : Node::eOnionSkinNone); + alpha = tcrop(tround(onionSkiFade * 255.0), 0, 255); + + if (player.m_isShiftAndTraceEnabled && + !Preferences::instance()->areOnionColorsUsedForShiftAndTraceGhosts()) + onionMode = Node::eOnionSkinNone; + else { + onionMode = + (player.m_onionSkinDistance > 0) + ? Node::eOnionSkinFront + : ((player.m_onionSkinDistance < 0) ? Node::eOnionSkinBack + : Node::eOnionSkinNone); + } + } else if (player.m_opacity < 255) alpha = player.m_opacity;