| |
| |
| #include "toonzqt/functionpanel.h" |
| |
| |
| #include "toonzqt/functionselection.h" |
| #include "toonzqt/functionsegmentviewer.h" |
| #include "toonzqt/imageutils.h" |
| #include "functionpaneltools.h" |
| #include "toonzqt/gutil.h" |
| |
| |
| #include "toonz/tframehandle.h" |
| #include "toonz/doubleparamcmd.h" |
| #include "toonz/toonzfolders.h" |
| #include "toonz/preferences.h" |
| |
| #include "tdoubleparam.h" |
| #include "tdoublekeyframe.h" |
| #include "tunit.h" |
| |
| |
| #include "tcommon.h" |
| |
| |
| #include <QPainter> |
| #include <QPainterPath> |
| #include <QMouseEvent> |
| #include <QWheelEvent> |
| #include <QMenu> |
| #include <QSettings> |
| |
| #include <cmath> |
| |
| namespace { |
| |
| void drawCircle(QPainter &painter, double x, double y, double r) { |
| painter.drawEllipse(x - r, y - r, 2 * r, 2 * r); |
| } |
| void drawCircle(QPainter &painter, const QPointF &p, double r) { |
| painter.drawEllipse(p.x() - r, p.y() - r, 2 * r, 2 * r); |
| } |
| void drawSquare(QPainter &painter, double x, double y, double r) { |
| painter.drawRect(x - r, y - r, 2 * r, 2 * r); |
| } |
| void drawSquare(QPainter &painter, const QPointF &p, double r) { |
| drawSquare(painter, p.x(), p.y(), r); |
| } |
| |
| void drawRoundedSquare(QPainter &painter, const QPointF &p, double r) { |
| painter.drawRoundedRect(p.x() - r, p.y() - r, 2 * r, 2 * r, 99, 99, |
| Qt::RelativeSize); |
| } |
| |
| double norm2(const QPointF &p) { return p.x() * p.x() + p.y() * p.y(); } |
| |
| class FunctionPanelZoomer final : public ImageUtils::ShortcutZoomer { |
| FunctionPanel *m_panel; |
| |
| public: |
| FunctionPanelZoomer(FunctionPanel *panel) |
| : ShortcutZoomer(panel), m_panel(panel) {} |
| |
| bool zoom(bool zoomin, bool resetZoom) override { |
| if (resetZoom) |
| m_panel->fitGraphToWindow(); |
| else { |
| double f = 1.25; |
| double sc = zoomin ? f : 1.0 / f; |
| QPoint center(m_panel->width() / 2, m_panel->height() / 2); |
| m_panel->zoom(sc, sc, center); |
| } |
| |
| return true; |
| } |
| }; |
| |
| } |
| |
| |
| |
| |
| |
| |
| |
| class Ruler { |
| double m_minValue, m_step; |
| int m_labelPeriod, m_labelOffset, m_tickCount; |
| |
| double m_unit, m_pan, m_vOrigin; |
| int m_x0, m_x1; |
| int m_minLabelDistance, m_minDistance; |
| double m_minStep; |
| |
| public: |
| Ruler(); |
| |
| |
| |
| |
| |
| |
| void setTransform(double unit, double pan, double vOrigin = 0) { |
| m_unit = unit; |
| m_pan = pan; |
| m_vOrigin = vOrigin; |
| } |
| |
| void setRange(int x0, int x1) { |
| m_x0 = x0; |
| m_x1 = x1; |
| } |
| |
| |
| void setMinLabelDistance(int distance) { m_minLabelDistance = distance; } |
| |
| void setMinDistance(int distance) { m_minDistance = distance; } |
| |
| |
| |
| void setMinStep(double step) { m_minStep = step; } |
| |
| void compute(); |
| |
| int getTickCount() const { return m_tickCount; } |
| double getTick(int index) const { return m_minValue + index * m_step; } |
| bool isLabel(int index) const { |
| return ((m_labelOffset + index) % m_labelPeriod) == 0; |
| } |
| }; |
| |
| |
| |
| Ruler::Ruler() |
| : m_minValue(0) |
| , m_step(1) |
| , m_labelPeriod(2) |
| , m_labelOffset(0) |
| , m_tickCount(0) |
| , m_unit(1) |
| , m_pan(0) |
| , m_x0(0) |
| , m_x1(100) |
| , m_minLabelDistance(20) |
| , m_minDistance(5) |
| , m_minStep(0) {} |
| |
| |
| |
| void Ruler::compute() { |
| assert(m_x0 < m_x1); |
| assert(m_unit != 0.0); |
| assert(m_minLabelDistance > 0); |
| assert(m_minDistance >= 0); |
| |
| |
| |
| |
| |
| const double absUnit = std::abs(m_unit); |
| const double minLabelWorldDistance = m_minLabelDistance / absUnit; |
| const double minWorldDistance = m_minDistance / absUnit; |
| |
| |
| |
| |
| |
| |
| m_step = 1; |
| m_labelPeriod = 5; |
| if (m_step * m_labelPeriod >= minLabelWorldDistance && |
| m_step >= minWorldDistance) { |
| while (m_step >= minLabelWorldDistance && |
| m_step / (7 - m_labelPeriod) >= minWorldDistance) { |
| m_labelPeriod = 7 - m_labelPeriod; |
| m_step /= m_labelPeriod; |
| } |
| } else { |
| do { |
| m_step *= m_labelPeriod; |
| m_labelPeriod = |
| 7 - m_labelPeriod; |
| } while (m_step * m_labelPeriod < minLabelWorldDistance || |
| m_step < minWorldDistance); |
| } |
| |
| if (m_step >= minLabelWorldDistance) { |
| m_labelPeriod = 1; |
| } |
| |
| if (m_step * m_labelPeriod < m_minStep) { |
| m_step = m_minStep; |
| m_labelPeriod = 1; |
| } else if (m_step < m_minStep) { |
| m_step *= m_labelPeriod; |
| m_labelPeriod = 1; |
| } |
| |
| |
| double v0 = (m_x0 - m_pan) / m_unit; |
| double v1 = (m_x1 - m_pan) / m_unit; |
| if (m_unit < 0) std::swap(v0, v1); |
| int i0 = |
| tfloor((v0 - m_vOrigin) / m_step); |
| int i1 = |
| tceil((v1 - m_vOrigin) / m_step); |
| m_minValue = i0 * m_step + m_vOrigin; |
| m_tickCount = i1 - i0 + 1; |
| |
| m_labelOffset = i0 >= 0 ? (i0 % m_labelPeriod) |
| : (m_labelPeriod - ((-i0) % m_labelPeriod)); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| FunctionPanel::Gadget::Gadget(FunctionPanel::Handle handle, int kIndex, |
| const QPointF &p, int rx, int ry, |
| const QPointF &pointPos) |
| : m_handle(handle) |
| , m_kIndex(kIndex) |
| , m_hitRegion(QRect((int)p.x() - rx, (int)p.y() - ry, 2 * rx, 2 * ry)) |
| , m_pos(p) |
| , m_pointPos(pointPos) |
| , m_channel(0) |
| , m_keyframePosition(0) {} |
| |
| |
| |
| |
| |
| |
| |
| FunctionPanel::FunctionPanel(QWidget *parent, bool isFloating) |
| : QDialog(parent) |
| , m_functionTreeModel(0) |
| , m_viewTransform() |
| , m_valueAxisX(50) |
| , m_frameAxisY(50) |
| , m_graphViewportY(50) |
| , m_frameHandle(0) |
| , m_dragTool(0) |
| , m_currentFrameStatus(0) |
| , m_selection(0) |
| , m_curveShape(SMOOTH) |
| , m_isFloating(isFloating) { |
| setWindowTitle(tr("Function Curves")); |
| |
| m_viewTransform.translate(50, 200); |
| m_viewTransform.scale(5, -1); |
| |
| setFocusPolicy(Qt::ClickFocus); |
| setMouseTracking(true); |
| m_highlighted.handle = None; |
| m_highlighted.gIndex = -1; |
| m_cursor.visible = false; |
| m_cursor.frame = m_cursor.value = 0; |
| m_curveLabel.text = ""; |
| m_curveLabel.curve = 0; |
| |
| if (m_isFloating) { |
| |
| TFilePath fp(ToonzFolder::getMyModuleDir() + TFilePath("popups.ini")); |
| QSettings settings(toQString(fp), QSettings::IniFormat); |
| |
| setGeometry( |
| settings.value("FunctionCurves", QRect(500, 500, 400, 300)).toRect()); |
| } |
| } |
| |
| |
| |
| FunctionPanel::~FunctionPanel() { |
| if (m_isFloating) { |
| |
| TFilePath fp(ToonzFolder::getMyModuleDir() + TFilePath("popups.ini")); |
| QSettings settings(toQString(fp), QSettings::IniFormat); |
| |
| settings.setValue("FunctionCurves", geometry()); |
| } |
| |
| delete m_dragTool; |
| } |
| |
| |
| |
| double FunctionPanel::getPixelRatio(TDoubleParam *curve) const { |
| double framePixelSize = xToFrame(1) - xToFrame(0); |
| assert(framePixelSize > 0); |
| double valuePixelSize = fabs(yToValue(curve, 1) - yToValue(curve, 0)); |
| assert(valuePixelSize > 0); |
| return framePixelSize / valuePixelSize; |
| } |
| |
| |
| |
| double FunctionPanel::frameToX(double f) const { |
| return m_viewTransform.m11() * f + m_viewTransform.dx(); |
| } |
| |
| |
| |
| double FunctionPanel::xToFrame(double x) const { |
| return (x - m_viewTransform.dx()) / m_viewTransform.m11(); |
| } |
| |
| |
| |
| double FunctionPanel::valueToY(TDoubleParam *curve, double v) const { |
| const double bigNumber = 1.0e9; |
| TMeasure *m = curve->getMeasure(); |
| if (m) { |
| const TUnit *unit = m->getCurrentUnit(); |
| v = unit->convertTo(v); |
| } |
| return tcrop(m_viewTransform.m22() * v + m_viewTransform.dy(), -bigNumber, |
| bigNumber); |
| } |
| |
| |
| |
| double FunctionPanel::yToValue(TDoubleParam *curve, double y) const { |
| double v = (y - m_viewTransform.dy()) / m_viewTransform.m22(); |
| TMeasure *m = curve->getMeasure(); |
| if (m) { |
| const TUnit *unit = m->getCurrentUnit(); |
| v = unit->convertFrom(v); |
| } |
| return v; |
| } |
| |
| |
| |
| void FunctionPanel::pan(int dx, int dy) { |
| QTransform m; |
| m.translate(dx, dy); |
| m_viewTransform *= m; |
| update(); |
| } |
| |
| |
| |
| void FunctionPanel::zoom(double sx, double sy, const QPoint ¢er) { |
| QTransform m; |
| m.translate(center.x(), center.y()); |
| m.scale(sx, sy); |
| m.translate(-center.x(), -center.y()); |
| m_viewTransform *= m; |
| update(); |
| } |
| |
| |
| |
| QPointF FunctionPanel::getWinPos(TDoubleParam *curve, double frame, |
| double value) const { |
| return QPointF(frameToX(frame), valueToY(curve, value)); |
| } |
| |
| |
| |
| QPointF FunctionPanel::getWinPos(TDoubleParam *curve, double frame) const { |
| return getWinPos(curve, frame, curve->getValue(frame)); |
| } |
| |
| |
| |
| QPointF FunctionPanel::getWinPos(TDoubleParam *curve, |
| const TDoubleKeyframe &kf) const { |
| return getWinPos(curve, kf.m_frame, kf.m_value); |
| } |
| |
| |
| |
| int FunctionPanel::getCurveDistance(TDoubleParam *curve, const QPoint &winPos) { |
| double frame = xToFrame(winPos.x()); |
| double value = curve->getValue(frame); |
| double curveY = valueToY(curve, value); |
| return std::abs(curveY - winPos.y()); |
| } |
| |
| |
| |
| FunctionTreeModel::Channel *FunctionPanel::findClosestChannel( |
| const QPoint &winPos, int maxWinDistance) { |
| FunctionTreeModel::Channel *closestChannel = 0; |
| int minDistance = maxWinDistance; |
| int i; |
| for (i = 0; i < m_functionTreeModel->getActiveChannelCount(); i++) { |
| FunctionTreeModel::Channel *channel = |
| m_functionTreeModel->getActiveChannel(i); |
| TDoubleParam *curve = channel->getParam(); |
| int distance = getCurveDistance(curve, winPos); |
| if (distance < minDistance) { |
| closestChannel = channel; |
| minDistance = distance; |
| } |
| } |
| return closestChannel; |
| } |
| |
| |
| |
| TDoubleParam *FunctionPanel::findClosestCurve(const QPoint &winPos, |
| int maxWinDistance) { |
| FunctionTreeModel::Channel *closestChannel = |
| findClosestChannel(winPos, maxWinDistance); |
| return closestChannel ? closestChannel->getParam() : 0; |
| } |
| |
| |
| |
| |
| int FunctionPanel::findClosestGadget(const QPoint &winPos, Handle &handle, |
| int maxDistance) { |
| |
| int minDistance = maxDistance; |
| int k = -1; |
| for (int i = 0; i < m_gadgets.size(); i++) { |
| if (m_gadgets[i].m_hitRegion.contains(winPos)) { |
| QPoint p; |
| double d = (m_gadgets[i].m_hitRegion.center() - winPos).manhattanLength(); |
| if (d < minDistance) { |
| k = i; |
| minDistance = d; |
| } |
| } |
| } |
| if (k >= 0) { |
| handle = m_gadgets[k].m_handle; |
| return k; |
| } else { |
| handle = None; |
| return -1; |
| } |
| } |
| |
| |
| |
| TDoubleParam *FunctionPanel::getCurrentCurve() const { |
| FunctionTreeModel::Channel *currentChannel = |
| m_functionTreeModel ? m_functionTreeModel->getCurrentChannel() : 0; |
| if (!currentChannel) |
| return 0; |
| else |
| return currentChannel->getParam(); |
| } |
| |
| |
| |
| QPainterPath FunctionPanel::getSegmentPainterPath(TDoubleParam *curve, |
| int segmentIndex, int x0, |
| int x1) { |
| double frame0 = xToFrame(x0), frame1 = xToFrame(x1); |
| int kCount = curve->getKeyframeCount(); |
| int step = 1; |
| if (kCount > 0) { |
| if (segmentIndex < 0) |
| frame1 = std::min( |
| frame1, curve->keyframeIndexToFrame(0)); |
| else if (segmentIndex >= kCount - 1) |
| frame0 = std::max(frame0, curve->keyframeIndexToFrame( |
| kCount - 1)); |
| else { |
| |
| TDoubleKeyframe kf = curve->getKeyframe(segmentIndex); |
| frame0 = std::max(frame0, kf.m_frame); |
| double f = curve->keyframeIndexToFrame(segmentIndex + 1); |
| frame1 = std::min(frame1, f); |
| step = kf.m_step; |
| } |
| } |
| if (frame0 >= frame1) return QPainterPath(); |
| double frame; |
| double df = xToFrame(3) - xToFrame(0); |
| |
| if (m_curveShape == SMOOTH) { |
| frame = frame0; |
| } else |
| { |
| frame = (double)tfloor(frame0); |
| df = std::max(df, 1.0); |
| } |
| |
| QPainterPath path; |
| if (0 <= segmentIndex && segmentIndex < kCount && step > 1) { |
| |
| path.moveTo(getWinPos(curve, frame)); |
| |
| int f0 = curve->keyframeIndexToFrame(segmentIndex); |
| int vFrame = f0 + tfloor(tfloor(frame - f0), step); |
| double vValue = curve->getValue(vFrame); |
| assert(vFrame <= frame); |
| assert(vFrame + step > frame); |
| while (vFrame + step < frame1) { |
| vValue = curve->getValue(vFrame); |
| path.lineTo(getWinPos(curve, vFrame, vValue)); |
| vFrame += step; |
| path.lineTo(getWinPos(curve, vFrame, vValue)); |
| vValue = curve->getValue(vFrame); |
| path.lineTo(getWinPos(curve, vFrame, vValue)); |
| } |
| path.lineTo(getWinPos(curve, frame1, vValue)); |
| path.lineTo(getWinPos(curve, frame1, curve->getValue(frame1, true))); |
| } else { |
| |
| path.moveTo(getWinPos(curve, frame)); |
| while (frame + df < frame1) { |
| frame += df; |
| path.lineTo(getWinPos(curve, frame)); |
| } |
| path.lineTo(getWinPos(curve, frame1, curve->getValue(frame1, true))); |
| } |
| return path; |
| } |
| |
| |
| |
| void FunctionPanel::drawCurrentFrame(QPainter &painter) { |
| int currentframe = 0; |
| if (m_frameHandle) currentframe = m_frameHandle->getFrame(); |
| int x = frameToX(currentframe); |
| if (m_currentFrameStatus == 0) |
| painter.setPen(Qt::magenta); |
| else if (m_currentFrameStatus == 1) |
| painter.setPen(Qt::white); |
| else |
| painter.setPen(m_selectedColor); |
| int y = m_graphViewportY + 1; |
| painter.drawLine(x - 1, y, x - 1, height()); |
| painter.drawLine(x + 1, y, x + 1, height()); |
| } |
| |
| |
| |
| void FunctionPanel::drawFrameGrid(QPainter &painter) { |
| QFontMetrics fm(painter.font()); |
| |
| |
| painter.setPen(Qt::NoPen); |
| painter.setBrush(getRulerBackground()); |
| painter.drawRect(0, 0, width(), m_frameAxisY); |
| |
| |
| Ruler ruler; |
| ruler.setTransform(m_viewTransform.m11(), m_viewTransform.dx(), -1); |
| ruler.setRange(m_valueAxisX, width()); |
| ruler.setMinLabelDistance(fm.horizontalAdvance("-8888") + 2); |
| ruler.setMinDistance(5); |
| ruler.setMinStep(1); |
| ruler.compute(); |
| for (int i = 0; i < ruler.getTickCount(); i++) { |
| double f = ruler.getTick(i); |
| bool isLabel = ruler.isLabel(i); |
| int x = frameToX(f); |
| painter.setPen(m_textColor); |
| int y = m_frameAxisY; |
| painter.drawLine(x, y - (isLabel ? 4 : 2), x, y); |
| painter.setPen(getFrameLineColor()); |
| painter.drawLine(x, m_graphViewportY, x, height()); |
| if (isLabel) { |
| painter.setPen(m_textColor); |
| QString labelText = QString::number(f + 1); |
| painter.drawText(x - fm.horizontalAdvance(labelText) / 2, y - 6, |
| labelText); |
| } |
| } |
| } |
| |
| |
| |
| void FunctionPanel::drawValueGrid(QPainter &painter) { |
| TDoubleParam *curve = getCurrentCurve(); |
| if (!curve) return; |
| |
| QFontMetrics fm(painter.font()); |
| |
| |
| painter.setPen(Qt::NoPen); |
| painter.setBrush(getRulerBackground()); |
| painter.drawRect(0, 0, m_valueAxisX, height()); |
| |
| Ruler ruler; |
| ruler.setTransform(m_viewTransform.m22(), m_viewTransform.dy()); |
| ruler.setRange(m_graphViewportY, height()); |
| ruler.setMinLabelDistance(fm.height() + 2); |
| ruler.setMinDistance(5); |
| ruler.compute(); |
| |
| painter.setBrush(Qt::NoBrush); |
| for (int i = 0; i < ruler.getTickCount(); i++) { |
| double v = ruler.getTick(i); |
| bool isLabel = ruler.isLabel(i); |
| int y = tround(m_viewTransform.m22() * v + |
| m_viewTransform.dy()); |
| painter.setPen(m_textColor); |
| int x = m_valueAxisX; |
| painter.drawLine(x - (isLabel ? 5 : 2), y, x, y); |
| |
| painter.setPen(getValueLineColor()); |
| painter.drawLine(x, y, width(), y); |
| |
| if (isLabel) { |
| painter.setPen(m_textColor); |
| QString labelText = QString::number(v); |
| painter.drawText(std::max(0, x - 5 - fm.horizontalAdvance(labelText)), |
| y + fm.height() / 2, labelText); |
| } |
| } |
| if (false && ruler.getTickCount() > 10) { |
| double value = ruler.getTick(9); |
| double dv = fabs(ruler.getTick(10)); |
| double frame = 10; |
| double df = dv * getPixelRatio(curve); |
| QPointF p0 = getWinPos(curve, frame, value); |
| QPointF p1 = getWinPos(curve, frame + df, value + dv); |
| |
| painter.setPen(Qt::magenta); |
| painter.drawRect(p0.x(), p0.y(), (p1 - p0).x(), (p1 - p0).y()); |
| } |
| } |
| |
| |
| |
| void FunctionPanel::drawOtherCurves(QPainter &painter) { |
| painter.setRenderHint(QPainter::Antialiasing, false); |
| painter.setBrush(Qt::NoBrush); |
| int x0 = m_valueAxisX; |
| int x1 = width(); |
| |
| QPen solidPen; |
| QPen dashedPen; |
| QVector<qreal> dashes; |
| dashes << 4 << 4; |
| dashedPen.setDashPattern(dashes); |
| |
| for (int i = 0; i < m_functionTreeModel->getActiveChannelCount(); i++) { |
| FunctionTreeModel::Channel *channel = |
| m_functionTreeModel->getActiveChannel(i); |
| if (channel->isCurrent()) continue; |
| TDoubleParam *curve = channel->getParam(); |
| QColor color = |
| curve == m_curveLabel.curve ? m_selectedColor : getOtherCurvesColor(); |
| solidPen.setColor(color); |
| dashedPen.setColor(color); |
| painter.setBrush(Qt::NoBrush); |
| |
| int kCount = curve->getKeyframeCount(); |
| if (kCount == 0) { |
| |
| painter.setPen(dashedPen); |
| painter.drawPath(getSegmentPainterPath(curve, 0, x0, x1)); |
| } |
| |
| else { |
| for (int k = -1; k < kCount; k++) { |
| painter.setPen((k < 0 || k >= kCount - 1) ? dashedPen : solidPen); |
| painter.drawPath(getSegmentPainterPath(curve, k, x0, x1)); |
| } |
| painter.setPen(m_textColor); |
| painter.setBrush(m_subColor); |
| for (int k = 0; k < kCount; k++) { |
| double frame = curve->keyframeIndexToFrame(k); |
| QPointF p = getWinPos(curve, frame, curve->getValue(frame)); |
| painter.drawRect(p.x() - 2, p.y() - 2, 5, 5); |
| QPointF p2 = getWinPos(curve, frame, curve->getValue(frame, true)); |
| if (p2.y() != p.y()) { |
| painter.drawRect(p2.x() - 2, p2.y() - 2, 5, 5); |
| painter.setPen(solidPen); |
| painter.drawLine(p, p2); |
| painter.setPen(m_textColor); |
| } |
| } |
| } |
| } |
| painter.setBrush(Qt::NoBrush); |
| painter.setPen(m_textColor); |
| painter.setRenderHint(QPainter::Antialiasing, false); |
| } |
| |
| |
| |
| void FunctionPanel::updateGadgets(TDoubleParam *curve) { |
| m_gadgets.clear(); |
| |
| TDoubleKeyframe oldKf; |
| oldKf.m_type = TDoubleKeyframe::None; |
| |
| int keyframeCount = curve->getKeyframeCount(); |
| |
| for (int i = 0; i != keyframeCount; ++i) { |
| const int pointHitRadius = 10, handleHitRadius = 6; |
| |
| TDoubleKeyframe kf = curve->getKeyframe(i); |
| kf.m_value = curve->getValue( |
| kf.m_frame); |
| |
| |
| QPointF p = getWinPos(curve, kf.m_frame); |
| QPointF pLeft = p; |
| |
| if (i == keyframeCount - 1 && |
| curve |
| ->isCycleEnabled()) |
| p = getWinPos(curve, kf.m_frame, |
| curve->getValue( |
| kf.m_frame, |
| true)); |
| |
| |
| m_gadgets.push_back(Gadget(Point, i, p, pointHitRadius, pointHitRadius)); |
| |
| TPointD currentPointRight(kf.m_frame, kf.m_value); |
| TPointD currentPointLeft(currentPointRight); |
| |
| |
| |
| if (i > 0 && |
| (!TDoubleKeyframe::isKeyframeBased( |
| kf.m_type) || |
| !TDoubleKeyframe::isKeyframeBased( |
| curve->getKeyframe(i - 1) |
| .m_type))) |
| { |
| currentPointLeft.y = curve->getValue(kf.m_frame, true); |
| pLeft = getWinPos(curve, currentPointLeft); |
| m_gadgets.push_back( |
| Gadget(Point, i, pLeft, pointHitRadius, pointHitRadius)); |
| } |
| |
| |
| if (getSelection()->isSelected(curve, i)) { |
| |
| switch (oldKf.m_type) { |
| case TDoubleKeyframe::SpeedInOut: { |
| TPointD speedIn = curve->getSpeedIn(i); |
| if (norm2(speedIn) > 0) { |
| QPointF q = getWinPos(curve, currentPointLeft + speedIn); |
| m_gadgets.push_back( |
| Gadget(SpeedIn, i, q, handleHitRadius, handleHitRadius, pLeft)); |
| } |
| break; |
| } |
| |
| case TDoubleKeyframe::EaseInOut: { |
| QPointF q = getWinPos(curve, kf.m_frame + kf.m_speedIn.x); |
| m_gadgets.push_back(Gadget(EaseIn, i, q, 6, 15)); |
| break; |
| } |
| |
| case TDoubleKeyframe::EaseInOutPercentage: { |
| double easeIn = kf.m_speedIn.x * (kf.m_frame - oldKf.m_frame) * 0.01; |
| QPointF q = getWinPos(curve, kf.m_frame + easeIn); |
| m_gadgets.push_back(Gadget(EaseInPercentage, i, q, 6, 15)); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| |
| if (i != keyframeCount - 1) { |
| switch (kf.m_type) { |
| case TDoubleKeyframe::SpeedInOut: { |
| TPointD speedOut = curve->getSpeedOut(i); |
| if (norm2(speedOut) > 0) { |
| QPointF q = getWinPos(curve, currentPointRight + speedOut); |
| m_gadgets.push_back( |
| Gadget(SpeedOut, i, q, handleHitRadius, handleHitRadius, p)); |
| } |
| break; |
| } |
| |
| case TDoubleKeyframe::EaseInOut: { |
| QPointF q = getWinPos(curve, kf.m_frame + kf.m_speedOut.x); |
| m_gadgets.push_back(Gadget(EaseOut, i, q, 6, 15)); |
| break; |
| } |
| |
| case TDoubleKeyframe::EaseInOutPercentage: { |
| double segmentWidth = curve->keyframeIndexToFrame(i + 1) - kf.m_frame; |
| double easeOut = segmentWidth * kf.m_speedOut.x * 0.01; |
| |
| QPointF q = getWinPos(curve, kf.m_frame + easeOut); |
| m_gadgets.push_back(Gadget(EaseOutPercentage, i, q, 6, 15)); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| |
| oldKf = kf; |
| } |
| |
| |
| |
| int channelCount = m_functionTreeModel->getActiveChannelCount(); |
| |
| |
| |
| |
| |
| |
| typedef std::map<double, std::vector<TDoubleKeyframe>> |
| KeyframeTable; |
| KeyframeTable keyframes; |
| |
| for (int i = 0; i != channelCount; ++i) { |
| FunctionTreeModel::Channel *channel = |
| m_functionTreeModel->getActiveChannel(i); |
| if (!channel) continue; |
| |
| TDoubleParam *curve = channel->getParam(); |
| for (int j = 0; j != curve->getKeyframeCount(); ++j) { |
| TDoubleKeyframe kf = curve->getKeyframe( |
| j); |
| keyframes[kf.m_frame].push_back( |
| kf); |
| } |
| } |
| |
| int groupHandleY = m_graphViewportY - 6; |
| |
| KeyframeTable::iterator it, iEnd(keyframes.end()), |
| iLast(keyframes.empty() ? iEnd : --iEnd); |
| for (KeyframeTable::iterator it = keyframes.begin(); it != keyframes.end(); |
| ++it) { |
| assert(!it->second.empty()); |
| |
| double frame = it->first; |
| QPointF p(frameToX(frame), groupHandleY); |
| |
| Gadget gadget((FunctionPanel::Handle)100, -1, p, 6, |
| 6); |
| gadget.m_keyframePosition = frame; |
| |
| m_gadgets.push_back(gadget); |
| |
| TDoubleKeyframe kf = it->second[0]; |
| |
| if ((int)it->second.size() < channelCount) continue; |
| |
| |
| |
| for (int i = 1; i < channelCount; ++i) { |
| |
| const TDoubleKeyframe &kf2 = it->second[i]; |
| |
| if (kf.m_type != kf2.m_type || kf.m_speedOut.x != kf2.m_speedOut.x) |
| kf.m_type = TDoubleKeyframe::None; |
| if (kf.m_prevType != kf2.m_prevType || kf.m_speedIn.x != kf2.m_speedIn.x) |
| kf.m_prevType = TDoubleKeyframe::None; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| if (it != iLast && |
| (kf.m_type == TDoubleKeyframe::SpeedInOut || |
| kf.m_type == TDoubleKeyframe::EaseInOut) && |
| kf.m_speedOut.x != 0) { |
| QPointF p(frameToX(frame + kf.m_speedOut.x), groupHandleY); |
| Gadget gadget((FunctionPanel::Handle)101, -1, p, 6, 15); |
| gadget.m_keyframePosition = frame; |
| m_gadgets.push_back(gadget); |
| } |
| |
| if ((kf.m_prevType == TDoubleKeyframe::SpeedInOut || |
| kf.m_prevType == TDoubleKeyframe::EaseInOut) && |
| kf.m_speedIn.x != 0) { |
| QPointF p(frameToX(frame + kf.m_speedIn.x), groupHandleY); |
| Gadget gadget((FunctionPanel::Handle)102, -1, p, 6, 15); |
| gadget.m_keyframePosition = frame; |
| m_gadgets.push_back(gadget); |
| } |
| } |
| } |
| |
| |
| |
| void FunctionPanel::drawCurrentCurve(QPainter &painter) { |
| FunctionTreeModel::Channel *channel = |
| m_functionTreeModel ? m_functionTreeModel->getCurrentChannel() : 0; |
| if (!channel) return; |
| TDoubleParam *curve = channel->getParam(); |
| |
| painter.setRenderHint(QPainter::Antialiasing, true); |
| QColor color = Qt::red; |
| QPen solidPen(color); |
| QPen dashedPen(color); |
| QVector<qreal> dashes; |
| dashes << 4 << 4; |
| dashedPen.setDashPattern(dashes); |
| painter.setBrush(Qt::NoBrush); |
| |
| int x0 = m_valueAxisX; |
| int x1 = width(); |
| |
| |
| int kCount = curve->getKeyframeCount(); |
| if (kCount == 0) { |
| |
| painter.setPen(dashedPen); |
| painter.drawPath(getSegmentPainterPath(curve, 0, x0, x1)); |
| } else { |
| for (int k = -1; k < kCount; k++) { |
| if (k < 0 || k >= kCount - 1) { |
| painter.setPen(dashedPen); |
| painter.drawPath(getSegmentPainterPath(curve, k, x0, x1)); |
| } else { |
| TDoubleKeyframe::Type segmentType = curve->getKeyframe(k).m_type; |
| QColor color = Qt::red; |
| if (segmentType == TDoubleKeyframe::Expression || |
| segmentType == TDoubleKeyframe::SimilarShape || |
| segmentType == TDoubleKeyframe::File) |
| color = QColor(185, 0, 0); |
| if (getSelection()->isSegmentSelected(curve, k)) |
| solidPen.setWidth(2); |
| else |
| solidPen.setWidth(0); |
| solidPen.setColor(color); |
| painter.setPen(solidPen); |
| painter.drawPath(getSegmentPainterPath(curve, k, x0, x1)); |
| } |
| } |
| } |
| painter.setPen(QPen(m_textColor, 0)); |
| |
| |
| updateGadgets(curve); |
| painter.setPen(m_textColor); |
| for (int j = 0; j < (int)m_gadgets.size(); j++) { |
| const Gadget &g = m_gadgets[j]; |
| if (g.m_handle == SpeedIn || g.m_handle == SpeedOut) |
| painter.drawLine(g.m_pointPos, g.m_pos); |
| } |
| solidPen.setWidth(0); |
| solidPen.setColor(Qt::red); |
| painter.setPen(solidPen); |
| for (int j = 0; j < (int)m_gadgets.size() - 1; j++) |
| if (m_gadgets[j].m_handle == Point && m_gadgets[j + 1].m_handle && |
| m_gadgets[j + 1].m_handle != 100 && |
| m_gadgets[j].m_pos.x() == m_gadgets[j + 1].m_pos.x()) |
| painter.drawLine(m_gadgets[j].m_pos, m_gadgets[j + 1].m_pos); |
| |
| painter.setRenderHint(QPainter::Antialiasing, false); |
| for (int j = 0; j < (int)m_gadgets.size(); j++) { |
| const Gadget &g = m_gadgets[j]; |
| int i = g.m_kIndex; |
| int r = 1; |
| QPointF p = g.m_pos; |
| double easeDx = 0, easeHeight = 15, easeTick = 2; |
| bool isSelected = getSelection()->isSelected(curve, i); |
| bool isHighlighted = |
| m_highlighted.handle == g.m_handle && m_highlighted.gIndex == j; |
| switch (g.m_handle) { |
| case Point: |
| painter.setBrush(isSelected ? QColor(255, 126, 0) : m_subColor); |
| painter.setPen(m_textColor); |
| r = isHighlighted ? 4 : 3; |
| drawSquare(painter, p, r); |
| break; |
| |
| case SpeedIn: |
| case SpeedOut: |
| painter.setBrush(m_subColor); |
| painter.setPen(m_textColor); |
| r = isHighlighted ? 4 : 3; |
| drawCircle(painter, p, r); |
| break; |
| |
| case EaseIn: |
| case EaseOut: |
| case EaseInPercentage: |
| case EaseOutPercentage: |
| painter.setBrush(Qt::NoBrush); |
| painter.setPen(isHighlighted ? QColor(255, 126, 0) : m_textColor); |
| painter.drawLine(p.x(), p.y() - easeHeight, p.x(), p.y() + easeHeight); |
| if (g.m_handle == EaseIn || g.m_handle == EaseInPercentage) |
| easeDx = easeTick; |
| else |
| easeDx = -easeTick; |
| painter.drawLine(p.x(), p.y() - easeHeight, p.x() + easeDx, |
| p.y() - easeHeight - easeTick); |
| painter.drawLine(p.x(), p.y() + easeHeight, p.x() + easeDx, |
| p.y() + easeHeight + easeTick); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| painter.setRenderHint(QPainter::Antialiasing, false); |
| } |
| |
| |
| |
| void FunctionPanel::drawGroupKeyframes(QPainter &painter) { |
| FunctionTreeModel::Channel *channel = |
| m_functionTreeModel ? m_functionTreeModel->getCurrentChannel() : 0; |
| if (!channel) return; |
| QColor color = Qt::red; |
| QPen solidPen(color); |
| QPen dashedPen(color); |
| QVector<qreal> dashes; |
| dashes << 4 << 4; |
| dashedPen.setDashPattern(dashes); |
| painter.setBrush(Qt::NoBrush); |
| |
| int x0 = m_valueAxisX; |
| int x1 = width(); |
| |
| solidPen.setWidth(0); |
| solidPen.setColor(Qt::red); |
| painter.setPen(solidPen); |
| |
| std::vector<double> keyframes; |
| int y = 0; |
| for (int j = 0; j < (int)m_gadgets.size(); j++) { |
| const Gadget &g = m_gadgets[j]; |
| int i = g.m_kIndex; |
| int r = 1; |
| QPointF p = g.m_pos; |
| double easeDx = 0, easeHeight = 15, easeTick = 2; |
| bool isSelected = false; |
| bool isHighlighted = |
| m_highlighted.handle == g.m_handle && m_highlighted.gIndex == j; |
| painter.setBrush(isSelected ? QColor(255, 126, 0) : m_subColor); |
| painter.setPen(m_textColor); |
| r = isHighlighted ? 3 : 2; |
| QPainterPath pp; |
| int d = 2; |
| int h = 4; |
| switch (g.m_handle) { |
| case 100: |
| drawSquare(painter, p, r); |
| y = p.y(); |
| keyframes.push_back(p.x()); |
| break; |
| case 101: |
| d = -d; |
| |
| case 102: |
| painter.setBrush(Qt::NoBrush); |
| painter.setPen(isHighlighted ? QColor(255, 126, 0) : m_textColor); |
| pp.moveTo(p + QPointF(d, -h)); |
| pp.lineTo(p + QPointF(0, -h)); |
| pp.lineTo(p + QPointF(0, h)); |
| pp.lineTo(p + QPointF(d, h)); |
| painter.drawPath(pp); |
| break; |
| default: |
| break; |
| } |
| } |
| painter.setPen(m_textColor); |
| for (int i = 0; i + 1 < (int)keyframes.size(); i++) { |
| painter.drawLine(keyframes[i] + 3, y, keyframes[i + 1] - 3, y); |
| } |
| } |
| |
| |
| |
| void FunctionPanel::paintEvent(QPaintEvent *e) { |
| m_gadgets.clear(); |
| |
| QString fontName = Preferences::instance()->getInterfaceFont(); |
| if (fontName == "") { |
| #ifdef _WIN32 |
| fontName = "Arial"; |
| #else |
| fontName = "Helvetica"; |
| #endif |
| } |
| |
| QPainter painter(this); |
| QFont font(fontName, 8); |
| painter.setFont(font); |
| QFontMetrics fm(font); |
| |
| |
| m_valueAxisX = fm.horizontalAdvance("-888.88") + 2; |
| m_frameAxisY = fm.height() + 2; |
| m_graphViewportY = m_frameAxisY + 12; |
| int ox = m_valueAxisX; |
| int oy0 = m_graphViewportY; |
| int oy1 = m_frameAxisY; |
| |
| |
| |
| |
| painter.setBrush(getBGColor()); |
| painter.setPen(Qt::NoPen); |
| painter.drawRect(ox, oy0, width() - ox, height() - oy0); |
| |
| painter.setClipRect(ox, 0, width() - ox, height()); |
| drawCurrentFrame(painter); |
| drawFrameGrid(painter); |
| |
| painter.setClipRect(0, oy0, width(), height() - oy0); |
| drawValueGrid(painter); |
| |
| |
| painter.setClipping(false); |
| painter.setPen(m_textColor); |
| painter.drawLine(0, oy0, width(), oy0); |
| painter.drawLine(ox, oy1, width(), oy1); |
| painter.drawLine(ox, 0, ox, height()); |
| |
| |
| painter.setClipRect(ox + 1, oy0 + 1, width() - ox - 1, height() - oy0 - 1); |
| drawOtherCurves(painter); |
| drawCurrentCurve(painter); |
| |
| painter.setClipping(false); |
| painter.setClipRect(ox + 1, oy1 + 1, width() - ox - 1, oy0 - oy1 - 1); |
| drawGroupKeyframes(painter); |
| painter.setClipRect(ox + 1, oy0 + 1, width() - ox - 1, height() - oy0 - 1); |
| |
| |
| if (m_dragTool) m_dragTool->draw(painter); |
| |
| |
| if (m_cursor.visible) { |
| painter.setClipRect(ox + 1, oy0 + 1, width() - ox - 1, height() - oy0 - 1); |
| painter.setPen(getOtherCurvesColor()); |
| int x = frameToX(m_cursor.frame); |
| painter.drawLine(x, oy0 + 1, x, oy0 + 10); |
| QString text = QString::number(tround(m_cursor.frame) + 1); |
| painter.drawText(x - fm.horizontalAdvance(text) / 2, oy0 + 10 + fm.height(), |
| text); |
| |
| TDoubleParam *currentCurve = getCurrentCurve(); |
| if (currentCurve) { |
| const TUnit *unit = 0; |
| if (currentCurve->getMeasure()) |
| unit = currentCurve->getMeasure()->getCurrentUnit(); |
| double displayValue = m_cursor.value; |
| if (unit) displayValue = unit->convertTo(displayValue); |
| |
| int y = valueToY(currentCurve, m_cursor.value); |
| painter.drawLine(ox, y, ox + 10, y); |
| painter.drawText(m_origin.x() + 10, y + 4, QString::number(displayValue)); |
| } |
| } |
| |
| |
| if (m_curveLabel.text != "") { |
| painter.setClipRect(ox, oy0, width() - ox, height() - oy0); |
| painter.setPen(m_selectedColor); |
| painter.drawLine(m_curveLabel.curvePos, m_curveLabel.labelPos); |
| painter.drawText(m_curveLabel.labelPos, |
| QString::fromStdString(m_curveLabel.text)); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| void FunctionPanel::mousePressEvent(QMouseEvent *e) { |
| m_cursor.visible = false; |
| |
| |
| |
| if (m_dragTool) { |
| m_dragTool->release(e); |
| delete m_dragTool; |
| m_dragTool = nullptr; |
| } |
| |
| if (e->button() == Qt::MiddleButton) { |
| |
| bool xLocked = e->pos().x() <= m_valueAxisX; |
| bool yLocked = e->pos().y() <= m_valueAxisX; |
| m_dragTool = new PanDragTool(this, xLocked, yLocked); |
| m_dragTool->click(e); |
| return; |
| } else if (e->button() == Qt::RightButton) { |
| |
| openContextMenu(e); |
| return; |
| } |
| |
| QPoint winPos = e->pos(); |
| Handle handle = None; |
| const int maxDistance = 20; |
| int closestGadgetId = findClosestGadget(e->pos(), handle, maxDistance); |
| |
| if (e->pos().x() > m_valueAxisX && e->pos().y() < m_frameAxisY && |
| closestGadgetId < 0 && (e->modifiers() & Qt::ControlModifier) == 0) { |
| |
| m_dragTool = new ZoomDragTool(this, ZoomDragTool::FrameZoom); |
| } else if (e->pos().x() < m_valueAxisX && e->pos().y() > m_graphViewportY) { |
| |
| m_dragTool = new ZoomDragTool(this, ZoomDragTool::ValueZoom); |
| } else if (m_currentFrameStatus == 1 && m_frameHandle != 0 && |
| closestGadgetId < 0) { |
| |
| m_currentFrameStatus = 2; |
| m_dragTool = new MoveFrameDragTool(this, m_frameHandle); |
| } |
| |
| if (0 <= closestGadgetId && closestGadgetId < (int)m_gadgets.size()) { |
| if (handle == 100) |
| { |
| MovePointDragTool *dragTool = new MovePointDragTool(this, 0); |
| dragTool->selectKeyframes(m_gadgets[closestGadgetId].m_keyframePosition); |
| m_dragTool = dragTool; |
| } else if (handle == 101 || handle == 102) { |
| m_dragTool = new MoveGroupHandleDragTool( |
| this, m_gadgets[closestGadgetId].m_keyframePosition, handle); |
| } |
| } |
| |
| if (m_dragTool) { |
| m_dragTool->click(e); |
| return; |
| } |
| |
| FunctionTreeModel::Channel *currentChannel = |
| m_functionTreeModel ? m_functionTreeModel->getCurrentChannel() : 0; |
| if (!currentChannel || |
| (getCurveDistance(currentChannel->getParam(), winPos) > maxDistance && |
| closestGadgetId < 0)) { |
| |
| |
| |
| FunctionTreeModel::Channel *channel = |
| findClosestChannel(winPos, maxDistance); |
| if (channel) { |
| channel->setIsCurrent(true); |
| |
| FunctionTreeModel::ChannelGroup *channelGroup = |
| channel->getChannelGroup(); |
| if (!channelGroup->isOpen()) |
| channelGroup->getModel()->setExpandedItem(channelGroup->createIndex(), |
| true); |
| currentChannel = channel; |
| getSelection()->selectNone(); |
| } |
| } |
| |
| if (currentChannel) { |
| TDoubleParam *currentCurve = currentChannel->getParam(); |
| if (currentCurve) { |
| int kIndex = |
| closestGadgetId >= 0 ? m_gadgets[closestGadgetId].m_kIndex : -1; |
| if (kIndex >= 0) { |
| |
| if (handle == FunctionPanel::Point) { |
| |
| if (!getSelection()->isSelected(currentCurve, kIndex)) { |
| |
| if (0 == (e->modifiers() & Qt::ShiftModifier)) |
| getSelection()->deselectAllKeyframes(); |
| getSelection()->select(currentCurve, kIndex); |
| } |
| |
| MovePointDragTool *dragTool = |
| new MovePointDragTool(this, currentCurve); |
| if (getSelection()->getSelectedSegment().first != 0) { |
| |
| dragTool->addKeyframe2(kIndex); |
| } else { |
| dragTool->setSelection(getSelection()); |
| } |
| m_dragTool = dragTool; |
| } else { |
| m_dragTool = |
| new MoveHandleDragTool(this, currentCurve, kIndex, handle); |
| } |
| } else { |
| |
| int curveDistance = |
| getCurveDistance(currentChannel->getParam(), winPos); |
| bool isKeyframeable = true; |
| bool isGroup = abs(winPos.y() - (m_graphViewportY - 5)) < 5; |
| if (0 != (e->modifiers() & Qt::ControlModifier) && |
| (curveDistance < maxDistance || isGroup) && isKeyframeable) { |
| |
| double frame = tround(xToFrame(winPos.x())); |
| MovePointDragTool *dragTool = |
| new MovePointDragTool(this, isGroup ? 0 : currentCurve); |
| |
| |
| |
| dragTool->createKeyframe(frame); |
| dragTool->selectKeyframes(frame); |
| m_dragTool = dragTool; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } else if (curveDistance < maxDistance) { |
| |
| getSelection()->deselectAllKeyframes(); |
| double frame = xToFrame(winPos.x()); |
| int k0 = currentCurve->getPrevKeyframe(frame); |
| int k1 = currentCurve->getNextKeyframe(frame); |
| if (k0 >= 0 && k1 == k0 + 1) { |
| |
| getSelection()->selectSegment(currentCurve, k0); |
| MovePointDragTool *dragTool = |
| new MovePointDragTool(this, currentCurve); |
| dragTool->addKeyframe2(k0); |
| dragTool->addKeyframe2(k1); |
| m_dragTool = dragTool; |
| } else { |
| |
| m_dragTool = new RectSelectTool(this, currentCurve); |
| } |
| } else { |
| |
| getSelection()->deselectAllKeyframes(); |
| m_dragTool = new RectSelectTool(this, currentCurve); |
| } |
| } |
| } |
| } |
| |
| if (m_dragTool) m_dragTool->click(e); |
| update(); |
| } |
| |
| |
| |
| void FunctionPanel::mouseReleaseEvent(QMouseEvent *e) { |
| if (m_dragTool) { |
| m_dragTool->release(e); |
| delete m_dragTool; |
| m_dragTool = nullptr; |
| } |
| m_cursor.visible = true; |
| m_currentFrameStatus = 0; |
| update(); |
| } |
| |
| |
| |
| void FunctionPanel::mouseMoveEvent(QMouseEvent *e) { |
| if (e->buttons()) { |
| if (m_dragTool) m_dragTool->drag(e); |
| } else { |
| m_cursor.frame = xToFrame(e->pos().x()); |
| m_cursor.value = 0; |
| m_cursor.visible = true; |
| |
| TDoubleParam *currentCurve = getCurrentCurve(); |
| if (currentCurve) { |
| Handle handle = None; |
| int gIndex = findClosestGadget(e->pos(), handle, 20); |
| if (m_highlighted.handle != handle || m_highlighted.gIndex != gIndex) { |
| m_highlighted.handle = handle; |
| m_highlighted.gIndex = gIndex; |
| } |
| m_cursor.value = yToValue(currentCurve, e->pos().y()); |
| } |
| |
| double currentFrame = m_frameHandle ? m_frameHandle->getFrame() : 0; |
| if (m_highlighted.handle == None && |
| std::abs(e->pos().x() - frameToX(currentFrame)) < 5) |
| m_currentFrameStatus = 1; |
| else |
| m_currentFrameStatus = 0; |
| |
| FunctionTreeModel::Channel *closestChannel = |
| findClosestChannel(e->pos(), 20); |
| if (closestChannel && m_highlighted.handle == None) { |
| TDoubleParam *curve = closestChannel->getParam(); |
| if (m_functionTreeModel->getActiveChannelCount() <= 1) |
| |
| curve = 0; |
| if (curve && m_curveLabel.curve != curve) { |
| m_curveLabel.curve = curve; |
| QString channelName = closestChannel->data(Qt::DisplayRole).toString(); |
| QString parentChannelName = |
| closestChannel->getChannelGroup()->data(Qt::DisplayRole).toString(); |
| QString name = parentChannelName + QString(", ") + channelName; |
| m_curveLabel.text = name.toStdString(); |
| |
| |
| int textWidth = fontMetrics().horizontalAdvance(name) + 30; |
| double frame = xToFrame(width() - textWidth); |
| |
| m_curveLabel.curvePos = getWinPos(curve, frame).toPoint(); |
| m_curveLabel.labelPos = m_curveLabel.curvePos + QPoint(20, -10); |
| } |
| } else { |
| m_curveLabel.text = ""; |
| m_curveLabel.curve = 0; |
| } |
| |
| update(); |
| } |
| } |
| |
| |
| |
| void FunctionPanel::keyPressEvent(QKeyEvent *e) { |
| FunctionPanelZoomer(this).exec(e); |
| } |
| |
| |
| |
| void FunctionPanel::enterEvent(QEvent *) { |
| m_cursor.visible = true; |
| update(); |
| } |
| |
| |
| |
| void FunctionPanel::leaveEvent(QEvent *) { |
| m_cursor.visible = false; |
| update(); |
| } |
| |
| |
| |
| void FunctionPanel::wheelEvent(QWheelEvent *e) { |
| double factor = exp(0.002 * (double)e->angleDelta().y()); |
| #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) |
| zoom(factor, factor, e->position().toPoint()); |
| #else |
| zoom(factor, factor, e->pos()); |
| #endif |
| } |
| |
| |
| |
| void FunctionPanel::fitGraphToWindow(bool currentCurveOnly) { |
| double f0 = 0, f1 = -1; |
| double v0 = 0, v1 = -1; |
| |
| for (int i = 0; i < m_functionTreeModel->getActiveChannelCount(); i++) { |
| FunctionTreeModel::Channel *channel = |
| m_functionTreeModel->getActiveChannel(i); |
| TDoubleParam *curve = channel->getParam(); |
| if (currentCurveOnly && curve != getCurrentCurve()) continue; |
| |
| const TUnit *unit = 0; |
| if (curve->getMeasure()) unit = curve->getMeasure()->getCurrentUnit(); |
| int n = curve->getKeyframeCount(); |
| if (n == 0) { |
| double v = curve->getDefaultValue(); |
| if (unit) v = unit->convertTo(v); |
| if (v0 > v1) |
| v0 = v1 = v; |
| else if (v > v1) |
| v1 = v; |
| else if (v < v0) |
| v0 = v; |
| } else { |
| TDoubleKeyframe k = curve->getKeyframe(0); |
| double fa = k.m_frame; |
| k = curve->getKeyframe(n - 1); |
| double fb = k.m_frame; |
| if (f0 > f1) { |
| f0 = fa; |
| f1 = fb; |
| } else { |
| f0 = std::min(f0, fa); |
| f1 = std::max(f1, fb); |
| } |
| double v = curve->getValue(fa); |
| if (unit) v = unit->convertTo(v); |
| if (v0 > v1) v0 = v1 = v; |
| int m = 50; |
| for (int j = 0; j < m; j++) { |
| double t = (double)j / (double)(m - 1); |
| double v = curve->getValue((1 - t) * fa + t * fb); |
| if (unit) v = unit->convertTo(v); |
| v0 = std::min(v0, v); |
| v1 = std::max(v1, v); |
| } |
| } |
| } |
| if (f0 >= f1 || v0 >= v1) { |
| m_viewTransform = QTransform(); |
| m_viewTransform.translate(m_valueAxisX, 200); |
| m_viewTransform.scale(5, -1); |
| } else { |
| double mx = (width() - m_valueAxisX - 20) / (f1 - f0); |
| double my = -(height() - m_graphViewportY - 20) / (v1 - v0); |
| double dx = m_valueAxisX + 10 - f0 * mx; |
| double dy = m_graphViewportY + 10 - v1 * my; |
| m_viewTransform = QTransform(mx, 0, 0, my, dx, dy); |
| } |
| update(); |
| } |
| |
| |
| |
| void FunctionPanel::fitSelectedPoints() { fitGraphToWindow(true); } |
| |
| |
| |
| void FunctionPanel::fitCurve() { fitGraphToWindow(); } |
| |
| |
| |
| void FunctionPanel::fitRegion(double f0, double v0, double f1, double v1) {} |
| |
| |
| |
| static void setSegmentType(FunctionSelection *selection, TDoubleParam *curve, |
| int segmentIndex, TDoubleKeyframe::Type type) { |
| selection->selectSegment(curve, segmentIndex); |
| KeyframeSetter setter(curve, segmentIndex); |
| setter.setType(type); |
| } |
| |
| |
| |
| void FunctionPanel::openContextMenu(QMouseEvent *e) { |
| QAction linkHandlesAction(tr("Link Handles"), 0); |
| QAction unlinkHandlesAction(tr("Unlink Handles"), 0); |
| QAction resetHandlesAction(tr("Reset Handles"), 0); |
| QAction deleteKeyframeAction(tr("Delete"), 0); |
| QAction insertKeyframeAction(tr("Set Key"), 0); |
| QAction activateCycleAction(tr("Activate Cycle"), 0); |
| QAction deactivateCycleAction(tr("Deactivate Cycle"), 0); |
| QAction setLinearAction(tr("Linear Interpolation"), 0); |
| QAction setSpeedInOutAction(tr("Speed In / Speed Out Interpolation"), 0); |
| QAction setEaseInOutAction(tr("Ease In / Ease Out Interpolation"), 0); |
| QAction setEaseInOut2Action(tr("Ease In / Ease Out (%) Interpolation"), 0); |
| QAction setExponentialAction(tr("Exponential Interpolation"), 0); |
| QAction setExpressionAction(tr("Expression Interpolation"), 0); |
| QAction setFileAction(tr("File Interpolation"), 0); |
| QAction setConstantAction(tr("Constant Interpolation"), 0); |
| QAction setSimilarShapeAction(tr("Similar Shape Interpolation"), 0); |
| QAction fitSelectedAction(tr("Fit Selection"), 0); |
| QAction fitAllAction(tr("Fit"), 0); |
| QAction setStep1Action(tr("Step 1"), 0); |
| QAction setStep2Action(tr("Step 2"), 0); |
| QAction setStep3Action(tr("Step 3"), 0); |
| QAction setStep4Action(tr("Step 4"), 0); |
| |
| TDoubleParam *curve = getCurrentCurve(); |
| int segmentIndex = -1; |
| if (!curve) return; |
| TDoubleKeyframe kf; |
| double frame = xToFrame(e->pos().x()); |
| |
| |
| QMenu menu(0); |
| if (m_highlighted.handle == Point && m_highlighted.gIndex >= 0 && |
| m_gadgets[m_highlighted.gIndex].m_handle != 100) { |
| kf = curve->getKeyframe(m_gadgets[m_highlighted.gIndex].m_kIndex); |
| if (kf.m_linkedHandles) |
| menu.addAction(&unlinkHandlesAction); |
| else |
| menu.addAction(&linkHandlesAction); |
| menu.addAction(&resetHandlesAction); |
| menu.addAction(&deleteKeyframeAction); |
| } else { |
| int k0 = curve->getPrevKeyframe(frame); |
| int k1 = curve->getNextKeyframe(frame); |
| if (k0 == curve->getKeyframeCount() - 1) |
| { |
| if (curve->isCycleEnabled()) |
| menu.addAction(&deactivateCycleAction); |
| else |
| menu.addAction(&activateCycleAction); |
| } |
| menu.addAction(&insertKeyframeAction); |
| if (k0 >= 0 && k1 >= 0) { |
| menu.addSeparator(); |
| segmentIndex = k0; |
| kf = curve->getKeyframe(k0); |
| menu.addAction(&setLinearAction); |
| if (kf.m_type == TDoubleKeyframe::Linear) |
| setLinearAction.setEnabled(false); |
| menu.addAction(&setSpeedInOutAction); |
| if (kf.m_type == TDoubleKeyframe::SpeedInOut) |
| setSpeedInOutAction.setEnabled(false); |
| menu.addAction(&setEaseInOutAction); |
| if (kf.m_type == TDoubleKeyframe::EaseInOut) |
| setEaseInOutAction.setEnabled(false); |
| menu.addAction(&setEaseInOut2Action); |
| if (kf.m_type == TDoubleKeyframe::EaseInOutPercentage) |
| setEaseInOut2Action.setEnabled(false); |
| menu.addAction(&setExponentialAction); |
| if (kf.m_type == TDoubleKeyframe::Exponential) |
| setExponentialAction.setEnabled(false); |
| menu.addAction(&setExpressionAction); |
| if (kf.m_type == TDoubleKeyframe::Expression) |
| setExpressionAction.setEnabled(false); |
| menu.addAction(&setSimilarShapeAction); |
| if (kf.m_type == TDoubleKeyframe::SimilarShape) |
| setSimilarShapeAction.setEnabled(false); |
| menu.addAction(&setFileAction); |
| if (kf.m_type == TDoubleKeyframe::File) setFileAction.setEnabled(false); |
| menu.addAction(&setConstantAction); |
| if (kf.m_type == TDoubleKeyframe::Constant) |
| setConstantAction.setEnabled(false); |
| menu.addSeparator(); |
| if (kf.m_step != 1) menu.addAction(&setStep1Action); |
| if (kf.m_step != 2) menu.addAction(&setStep2Action); |
| if (kf.m_step != 3) menu.addAction(&setStep3Action); |
| if (kf.m_step != 4) menu.addAction(&setStep4Action); |
| menu.addSeparator(); |
| } |
| } |
| if (!getSelection()->isEmpty()) menu.addAction(&fitSelectedAction); |
| menu.addAction(&fitAllAction); |
| |
| |
| QAction curveShapeSmoothAction(tr("Smooth"), 0); |
| QAction curveShapeFrameBasedAction(tr("Frame Based"), 0); |
| QMenu curveShapeSubmenu(tr("Curve Shape"), 0); |
| menu.addSeparator(); |
| curveShapeSubmenu.addAction(&curveShapeSmoothAction); |
| curveShapeSubmenu.addAction(&curveShapeFrameBasedAction); |
| menu.addMenu(&curveShapeSubmenu); |
| |
| curveShapeSmoothAction.setCheckable(true); |
| curveShapeSmoothAction.setChecked(m_curveShape == SMOOTH); |
| curveShapeFrameBasedAction.setCheckable(true); |
| curveShapeFrameBasedAction.setChecked(m_curveShape == FRAME_BASED); |
| |
| |
| Highlighted highlighted(m_highlighted); |
| |
| |
| QAction *action = menu.exec(e->globalPos()); |
| |
| |
| if (action == &linkHandlesAction) |
| |
| { |
| if (m_gadgets[highlighted.gIndex].m_handle != 100) |
| KeyframeSetter(curve, m_gadgets[highlighted.gIndex].m_kIndex) |
| .linkHandles(); |
| } else if (action == &unlinkHandlesAction) { |
| if (m_gadgets[highlighted.gIndex].m_handle != 100) |
| KeyframeSetter(curve, m_gadgets[highlighted.gIndex].m_kIndex) |
| .unlinkHandles(); |
| } else if (action == &resetHandlesAction) { |
| kf.m_speedIn = TPointD(-5, 0); |
| kf.m_speedOut = -kf.m_speedIn; |
| curve->setKeyframe(kf); |
| } else if (action == &deleteKeyframeAction) { |
| KeyframeSetter::removeKeyframeAt(curve, kf.m_frame); |
| } else if (action == &insertKeyframeAction) { |
| KeyframeSetter(curve).createKeyframe(tround(frame)); |
| } else if (action == &activateCycleAction) { |
| KeyframeSetter::enableCycle(curve, true); |
| } else if (action == &deactivateCycleAction) { |
| KeyframeSetter::enableCycle(curve, false); |
| } else if (action == &setLinearAction) |
| setSegmentType(getSelection(), curve, segmentIndex, |
| TDoubleKeyframe::Linear); |
| else if (action == &setSpeedInOutAction) |
| setSegmentType(getSelection(), curve, segmentIndex, |
| TDoubleKeyframe::SpeedInOut); |
| else if (action == &setEaseInOutAction) |
| setSegmentType(getSelection(), curve, segmentIndex, |
| TDoubleKeyframe::EaseInOut); |
| else if (action == &setEaseInOut2Action) |
| setSegmentType(getSelection(), curve, segmentIndex, |
| TDoubleKeyframe::EaseInOutPercentage); |
| else if (action == &setExponentialAction) |
| setSegmentType(getSelection(), curve, segmentIndex, |
| TDoubleKeyframe::Exponential); |
| else if (action == &setExpressionAction) |
| setSegmentType(getSelection(), curve, segmentIndex, |
| TDoubleKeyframe::Expression); |
| else if (action == &setSimilarShapeAction) |
| setSegmentType(getSelection(), curve, segmentIndex, |
| TDoubleKeyframe::SimilarShape); |
| else if (action == &setFileAction) |
| setSegmentType(getSelection(), curve, segmentIndex, TDoubleKeyframe::File); |
| else if (action == &setConstantAction) |
| setSegmentType(getSelection(), curve, segmentIndex, |
| TDoubleKeyframe::Constant); |
| else if (action == &fitSelectedAction) |
| fitSelectedPoints(); |
| else if (action == &fitAllAction) |
| fitCurve(); |
| else if (action == &setStep1Action) |
| KeyframeSetter(curve, segmentIndex).setStep(1); |
| else if (action == &setStep2Action) |
| KeyframeSetter(curve, segmentIndex).setStep(2); |
| else if (action == &setStep3Action) |
| KeyframeSetter(curve, segmentIndex).setStep(3); |
| else if (action == &setStep4Action) |
| KeyframeSetter(curve, segmentIndex).setStep(4); |
| |
| else if (action == &curveShapeSmoothAction) |
| m_curveShape = SMOOTH; |
| else if (action == &curveShapeFrameBasedAction) |
| m_curveShape = FRAME_BASED; |
| |
| update(); |
| } |
| |
| |
| |
| void FunctionPanel::setFrameHandle(TFrameHandle *frameHandle) { |
| if (m_frameHandle == frameHandle) return; |
| if (m_frameHandle) m_frameHandle->disconnect(this); |
| m_frameHandle = frameHandle; |
| if (isVisible() && m_frameHandle) { |
| connect(m_frameHandle, SIGNAL(frameSwitched()), this, |
| SLOT(onFrameSwitched())); |
| update(); |
| } |
| assert(m_selection); |
| m_selection->setFrameHandle(frameHandle); |
| } |
| |
| |
| |
| void FunctionPanel::showEvent(QShowEvent *) { |
| if (m_frameHandle) |
| connect(m_frameHandle, SIGNAL(frameSwitched()), this, |
| SLOT(onFrameSwitched())); |
| } |
| |
| |
| |
| void FunctionPanel::hideEvent(QHideEvent *) { |
| if (m_frameHandle) m_frameHandle->disconnect(this); |
| } |
| |
| |
| |
| void FunctionPanel::onFrameSwitched() { update(); } |
| |