#include "stdafx.h" #include "SplineEditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "RobloxMainWindow.h" #include "UpdateUIManager.h" #include "v8datamodel/ChangeHistory.h" #include "G3D/Vector4.h" #include "G3d/g3dmath.h" using G3D::Vector4; using G3D::clamp; #include "v8datamodel/CustomParticleEmitter.h" static bool dbgmm = false; // debug mousemoves ? struct Sortpr{ bool operator()(const Vector4& a, const Vector4& b ) const { return a.x < b.x; }}; struct Killpr { int id; bool operator()(const Vector4& a) const { return int(a.w + 0.5f) == id; } }; SplineEditor::SplineEditor(QWidget* parent, SplineDef* def, const char* settingsName) : Base(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::FramelessWindowHint) { RBXASSERT(def); m_sel = -1; m_selEnvHandle = -1; m_mode = Free; m_clickX = m_clickY = -100; m_keepHighlight = false; m_Xconstraint = -1; m_savePt.set(-1,-1,-1,-1); m_spline = def; if (m_spline->pt.empty()) { m_spline->pt.push_back( Vector4(0,0,0,0) ); } m_splineBackup = new SplineDef(*m_spline); m_lastid = m_spline->pt.size()+1000; m_pRootFrame = new QFrame(this); m_pCanvasWidget = new SplineEditorCanvas(m_pRootFrame, this, m_spline); m_pResetButton = new QPushButton("Reset", m_pRootFrame); m_pCloseButton = new QPushButton("Close", m_pRootFrame); m_pXEdit = new QLineEdit("-", m_pRootFrame); m_pYEdit = new QLineEdit("-", m_pRootFrame); m_pEnvEdit = new QLineEdit("-", m_pRootFrame); m_pKillPtButton = new QPushButton("Delete", m_pRootFrame); QGridLayout* xyBlock = new QGridLayout; // left side: "Time"[X] | "Value"[Y] | "Envelope"[E] xyBlock->addWidget(new QLabel("Time"), 0, 0); xyBlock->addWidget(m_pXEdit, 0, 1); xyBlock->addItem(new QSpacerItem(30, 0), 0, 2); xyBlock->addWidget(new QLabel("Value"), 0, 3); xyBlock->addWidget(m_pYEdit, 0, 4); xyBlock->addItem(new QSpacerItem(30, 0), 0, 5); xyBlock->addWidget(new QLabel("Envelope"), 0, 6); xyBlock->addWidget(m_pEnvEdit, 0, 7); xyBlock->addWidget(m_pKillPtButton, 0, 8, Qt::AlignTop); // right side: Reset and Close buttons QGridLayout* buttonBlock = new QGridLayout; buttonBlock->addWidget(m_pResetButton, 0, 0 ); buttonBlock->addWidget(m_pCloseButton, 0, 1 ); // Bottom row: XYblock | empty space | button block QGridLayout* bottomRow = new QGridLayout; bottomRow->addLayout( xyBlock, 0, 0, Qt::AlignLeft ); bottomRow->addItem(new QSpacerItem(100, 0), 0, 1); bottomRow->addLayout( buttonBlock, 0, 2, Qt::AlignRight ); bottomRow->setColumnStretch(0, 0 ); bottomRow->setColumnStretch(1, 10 ); bottomRow->setColumnStretch(2, 0); // main layout QGridLayout* toplevel = new QGridLayout; m_pCanvasWidget->setMinimumSize( QSize(200,200) ); toplevel->addWidget( m_pCanvasWidget, 0, 0, 1, 1 ); toplevel->addLayout( bottomRow, 1, 0, 1, 1 ); m_pRootFrame->setLayout( toplevel ); this->setWidget(m_pRootFrame); this->setObjectName("SplineEditor"); connect( m_pXEdit, SIGNAL(textEdited(QString)), this, SLOT(onPointDataChanged()) ); connect( m_pYEdit, SIGNAL(textEdited(QString)), this, SLOT(onPointDataChanged()) ); connect( m_pEnvEdit, SIGNAL(textEdited(QString)), this, SLOT(onPointDataChanged()) ); connect( m_pKillPtButton, SIGNAL(clicked()), this, SLOT(onKillPtClicked()) ); connect( m_pResetButton, SIGNAL(clicked()), this, SLOT(onReset()) ); connect( m_pCloseButton, SIGNAL(clicked()), this, SLOT(onClose()) ); m_settingsName = QString("SplineEditor/") + settingsName; QSettings settings; if( !restoreGeometry(settings.value(m_settingsName + "/geometry").toByteArray()) ) this->setFloating(true); this->setFloating( !settings.value(m_settingsName + "/docked").toBool() ); select( -1 ); this->setFocusPolicy(Qt::StrongFocus); } SplineEditor::~SplineEditor() { QSettings settings; settings.setValue(m_settingsName + "/geometry", saveGeometry()); settings.setValue(m_settingsName + "/docked", !this->isFloating()); } void SplineEditor::onCanvasMouse( QMouseEvent* e ) { Qt::MouseButtons buttons = e->buttons(); switch( e->type() ) { case QEvent::MouseButtonPress: { // depends on what we click and how... if( buttons == Qt::LeftButton && m_mode == Free) { Selection what = updateSelection( e->x(), e->y() ); if( what == Sel_Point ) // we're on a point, begin dragging it { if (dbgmm) qDebug("MousePress left click - on a point handle"); m_clickX = e->x(); m_clickY = e->y(); int idx = pointById( getsel() ); RBXASSERT( idx >= 0 ); m_savePt = m_spline->pt[idx]; if( getsel() == leftmostPoint() ) m_Xconstraint = 0; else if( getsel() == rightmostPoint() ) m_Xconstraint = 1; else m_Xconstraint = -1; m_pCanvasWidget->select(getsel()); m_pCanvasWidget->update(); m_mode = Drag; } else if( what == Sel_EnvUpper || what == Sel_EnvLower ) { if (dbgmm) qDebug("MousePress left click - on an envelope handle"); m_clickX = e->x(); m_clickY = e->y(); int idx = pointById( getsel() ); RBXASSERT( idx >= 0 ); m_savePt = m_spline->pt[idx]; m_pCanvasWidget->select(getsel()); m_pCanvasWidget->update(); m_selEnvHandle = what; m_mode = DragEnvHandle; } else if( m_spline->pt.size() < RBX::NumberSequence::kMaxSize ) // we're not on a point, create a new point and drag it { if (dbgmm) qDebug("MousePress left click - not on a point handle"); float ptx,pty; m_pCanvasWidget->cvt( &ptx, &pty, e->x(), e->y() ); if( ptx < 0 || ptx > 1 || pty < 0 || pty > m_spline->ymax ) break; // false alarm Vector4* pt = newPoint( ptx, pty, -1 ); select( pt->w + 0.5f ); sort(); pt = &m_spline->pt[ pointById(m_sel) ]; // re-acquire it constrainValue(pt); // fit the value m_savePt.set(-1,-1,pt->z, -1); // save the initial envelope to pt->z, because dragging code uses that to constrain the envelopes m_clickX = e->x(); m_clickY = e->y(); m_keepHighlight = true; if( m_spline->pt.size() <= 2 ) // we've just created the second point { if( getsel() == leftmostPoint() ) pt->x = m_Xconstraint = 0; else if( getsel() == rightmostPoint() ) pt->x = m_Xconstraint = 1; else m_Xconstraint = -1; } updateGhosting( -1, -1, false ); syncPoint(getsel(), true); m_pCanvasWidget->select(getsel()); m_pCanvasWidget->update(); m_mode = DragNew; Q_EMIT splineEdited(false); } break; } if( (buttons & Qt::RightButton && m_mode == Drag) // the user right-clicks while dragging an existing point || (buttons & Qt::LeftButton && m_mode == DragEnv) // -or- the user left-clicks while dragging an envelope || (buttons & Qt::RightButton && m_mode == DragEnvHandle) ) // -or- the user right-clicks while dragging an envelope size handle { if (dbgmm) qDebug("MousePress left+right - while dragging"); int idx = pointById( getsel() ); RBXASSERT( idx >= 0 ); m_spline->pt[idx] = m_savePt; m_savePt.set(-1, -1, -1, -1 ); select( -1 ); m_clickX = m_clickY = -100; m_keepHighlight = false; m_selEnvHandle = 0; sort(); updateGhosting( e->x(), e->y(), true ); m_pCanvasWidget->select(getsel()); m_pCanvasWidget->update(); m_mode = Free; Q_EMIT splineEdited(false); break; } if( buttons & Qt::RightButton && m_mode == DragNew ) // user right-clicks while drag-creating a new point { if (dbgmm) qDebug("MousePress right click - while newDragging"); killPoint(getsel()); // abort creation m_savePt.set(-1, -1, -1, -1 ); select(-1); m_clickX = m_clickY = -100; m_keepHighlight = false; sort(); updateGhosting( e->x(), e->y(), true ); m_pCanvasWidget->select(getsel()); m_pCanvasWidget->update(); m_mode = Free; Q_EMIT splineEdited(false); break; } if( buttons & Qt::RightButton && m_mode == Free ) // user right-drags a handle { bool b = updateSelection( e->x(), e->y() ); if( b ) { qDebug("Mousepress right - on a handle"); m_clickX = e->x(); m_clickY = e->y(); int idx = pointById( getsel() ); RBXASSERT( idx >= 0 ); m_savePt = m_spline->pt[idx]; m_keepHighlight = true; m_pCanvasWidget->select(getsel()); m_pCanvasWidget->update(); m_mode = DragEnv; } break; } } break; case QEvent::MouseButtonRelease: { switch( m_mode ) { case Drag: case DragNew: { if (dbgmm) qDebug( "MouseRelease - while dragging" ); int idx = pointById(getsel()); RBXASSERT(idx >= 0); Vector4* pt = &m_spline->pt[idx]; bool createUndoPoint = true; if( pt->x < 0 || pt->x > 1 ) { killPoint(getsel()); // feature: dragging the point outside of the 0..1 range kills it createUndoPoint = (m_mode == Drag); // this will count as 'delete existing' } else { constrainValue(pt); // fit the value } m_savePt.set(-1, -1, -1, -1 ); m_clickX = m_clickY = -100; m_keepHighlight = false; m_Xconstraint = -1; updateSelection( e->x(), e->y() ); updateGhosting( e->x(), e->y(), true ); m_mode = Free; // done moving the point Q_EMIT splineEdited(createUndoPoint); break; } case DragEnv: case DragEnvHandle: m_savePt.set(-1, -1, -1, -1 ); m_clickX = m_clickY = -100; m_keepHighlight = false; m_Xconstraint = -1; updateSelection( e->x(), e->y() ); updateGhosting( e->x(), e->y(), true ); m_mode = Free; // done moving the point Q_EMIT splineEdited(true); break; default: break; } break; } case QEvent::MouseMove: { this->setFocus(); updateGhosting( e->x(), e->y(), m_mode == Free ); switch(m_mode) { case Free: if (dbgmm) qDebug( "MouseMove - free movement" ); updateSelection( e->x(), e->y() ); break; case Drag: case DragNew: { if (dbgmm) qDebug( "MouseMove - while dragging" ); int idx = pointById(getsel()); RBXASSERT( idx >= 0 ); Vector4* pt = &m_spline->pt[ idx ]; int x = e->x(), y = e->y(); m_pCanvasWidget->cvt( &pt->x, &pt->y, x, y ); // move to the new location if( m_Xconstraint >= 0 ) pt->x = m_Xconstraint; constrainValue(pt); // fit the value pt->z = m_savePt.z; constrainEnvelope(pt); // fit the envelope sort(); m_pCanvasWidget->update(); syncPoint(getsel(), true); Q_EMIT splineEdited(false); break; } case DragEnv: { if (dbgmm) qDebug( "MouseMove - while right-dragging" ); int idx = pointById(getsel()); RBXASSERT( idx >= 0 ); Vector4* pt = &m_spline->pt[ idx ]; int x = e->x(), y = e->y(); float fx, fy, bx, by; m_pCanvasWidget->cvt( &bx, &by, m_clickX, m_clickY ); // drag start point in spline space m_pCanvasWidget->cvt( &fx, &fy, x, y ); // cursor position in spline space pt->z = ( fy - by + m_savePt.z ); // new envelope value constrainEnvelope( pt ); m_pCanvasWidget->update(); syncPoint(getsel(), true); Q_EMIT splineEdited(false); break; } case DragEnvHandle: { if (dbgmm) qDebug( "MouseMove - while dragging envelope handle"); int idx = pointById(getsel()); RBXASSERT( idx >= 0 ); Vector4* pt = &m_spline->pt[ idx ]; int x = e->x(), y = e->y(); float fx, fy, bx, by; m_pCanvasWidget->cvt( &bx, &by, m_clickX, m_clickY ); m_pCanvasWidget->cvt( &fx, &fy, x, y ); switch(m_selEnvHandle) { case Sel_EnvUpper: pt->z = (fy - by + m_savePt.z); break; case Sel_EnvLower: pt->z = (by - fy + m_savePt.z); break; default: RBXASSERT(0); break; } constrainEnvelope( pt ); m_pCanvasWidget->update(); syncPoint(getsel(), true); Q_EMIT splineEdited(false); break; } default: break; } } break; default: break; } } void SplineEditor::onCanvasLeave( QEvent* e ) { m_selEnvHandle = 0; updateGhosting(-1,-1,false); m_pCanvasWidget->update(); } SplineEditor::Selection SplineEditor::updateSelection( int x, int y ) { int sel = m_pCanvasWidget->getPointAt( x, y ); if (sel>=0 ) { if( sel != getsel() ) { select(sel); m_pCanvasWidget->select(sel); syncPoint(sel, true); } m_pKillPtButton->setEnabled( getsel() >= 0 && getsel() != leftmostPoint() && getsel() != rightmostPoint() ); m_pCanvasWidget->setHighlight(1); m_pCanvasWidget->update(); return Sel_Point; } std::pair hsel = m_pCanvasWidget->getEnvHandleAt(x, y); if( hsel.first == getsel() && hsel.second >= 0 ) // we're pointing at the envelope handle of the currently selected point { m_pCanvasWidget->setHighlight( 2 + hsel.second ); m_pCanvasWidget->update(); return hsel.second == 0 ? Sel_EnvUpper : Sel_EnvLower; } // keep the selection but remove the highlight, unless told to keep it... m_pCanvasWidget->setHighlight( int(false || m_keepHighlight) ); m_pCanvasWidget->update(); return Sel_None; } void SplineEditor::syncPoint(int sel, bool direction) { int idx = pointById(sel); if( idx<0 ) return; Vector4& v = m_spline->pt[idx]; if( direction ) // write to widgets { m_pXEdit->setText( QString::number( v.x, 'g', 3 ) ); m_pYEdit->setText( QString::number( v.y, 'g', 3 ) ); m_pEnvEdit->setText( QString::number( v.z, 'g', 3 ) ); } else // read from widgets { bool ok[3]; float x = m_pXEdit->text().toFloat(ok); float y = m_pYEdit->text().toFloat(ok+1); float z = m_pEnvEdit->text().toFloat(ok+2); if( ok[0] && ok[1] && ok[2] ) { v.x = clamp( x, 0, 1 ); v.y = y; v.z = z; constrainValue(&v); constrainEnvelope(&v); Q_EMIT splineEdited(true); } } } int SplineEditor::pointById(int id) { for( int j=0, e=m_spline->pt.size(); jpt[j].w + 0.5) == id ) { return j; } } return -1; } Vector4* SplineEditor::newPoint(float x, float y, float e) { if( e < 0 ) { e = autoEnvelope(x, y); } m_spline->pt.push_back( Vector4(x, y, e, m_lastid++) ); return &m_spline->pt.back(); } void SplineEditor::killPoint(int id) { int idx = this->pointById(id); if( idx < 0) return; m_spline->pt.erase(m_spline->pt.begin() + idx); } float SplineEditor::autoEnvelope( float x, float y ) { std::vector< Vector4 >& pts = m_spline->pt; float ymax = m_spline->ymax; for( int j=1, e=pts.size(); j x ) { j -= 1; bool notLast = j + 1 < (int)pts.size(); float x1 = pts[j].x; float x2 = notLast ? pts[j+1].x : x1+1; float e1 = pts[j].z; float e2 = notLast ? pts[j+1].z : e1; float s = (x - x1)/(x2 - x1); float e = fabsf( e1 + (e2-e1)*s ); if( y + e > ymax ) e = ymax - y; if( y - e < 0) e = y; if( e < 0 ) e = 0; return e; } } return 0; } void SplineEditor::constrainValue(G3D::Vector4* pt) { float ymax = m_spline->ymax; if (pt->y < 0) pt->y = 0; if (pt->y > ymax) pt->y = ymax; } void SplineEditor::constrainEnvelope( Vector4* pt ) { float e = pt->z; float ymax = m_spline->ymax; if( e < 0 ) e = 0; if( pt->y + e > ymax ) e = ymax - pt->y; if( pt->y - e < 0 ) e = pt->y; pt->z = e; } void SplineEditor::sort() { std::sort( m_spline->pt.begin(), m_spline->pt.end(), Sortpr() ); // sort the points by x } int SplineEditor::leftmostPoint() { if( m_spline->pt.empty() ) return -1; return m_spline->pt.front().w + 0.5f; } int SplineEditor::rightmostPoint() { if( m_spline->pt.empty() ) return -1; return m_spline->pt.back().w + 0.5f; } void SplineEditor::onPointDataChanged() { syncPoint(getsel(), false); // read m_pCanvasWidget->update(); } void SplineEditor::updateGhosting(int cx, int cy, bool show) { show = show && m_spline->pt.size() < RBX::NumberSequence::kMaxSize; if( show ) { float x,y; m_pCanvasWidget->cvt( &x, &y, cx, cy ); m_pCanvasWidget->ghost( x, y, 0 ); } else { m_pCanvasWidget->ghost(-1,-1,-1); } } void SplineEditor::select( int id ) { m_sel = id; bool good = pointById(id) >= 0; m_pXEdit->setEnabled( good ); m_pYEdit->setEnabled( good ); m_pEnvEdit->setEnabled( good ); if( !good ) { m_pXEdit->setText("-"); m_pYEdit->setText("-"); m_pEnvEdit->setText("-"); } } int SplineEditor::getsel() { return m_sel; } void SplineEditor::onKillPtClicked() { int pt = getsel(); if (pt >= 0 && ( pt != leftmostPoint() && pt != rightmostPoint() )) { killPoint(pt); select(-1); m_pCanvasWidget->update(); Q_EMIT splineEdited(true); } } void SplineEditor::onReset() { select(-1); m_spline->pt = m_splineBackup->pt; Q_EMIT splineEdited(true); refreshData(0); } void SplineEditor::onClose() { close(); } void SplineEditor::closeEvent(QCloseEvent *event) { Base::closeEvent(event); Q_EMIT finished(); } void SplineEditor::refreshData(void* arg) { if (dbgmm) qDebug("refreshData()"); // abort drags switch(m_mode) { case Drag: case DragEnv: { int idx = pointById( getsel() ); RBXASSERT( idx >= 0 ); m_spline->pt[idx] = m_savePt; break; } case DragNew: { killPoint(getsel()); // abort creation break; } default: break; } if (arg) { SplineDef* sd = reinterpret_cast(arg); *m_spline = *sd; delete sd; } m_savePt.set(-1, -1, -1, -1 ); select( -1 ); m_clickX = m_clickY = -100; m_keepHighlight = false; m_mode = Free; m_selEnvHandle = 0; sort(); updateGhosting( -1, -1, false ); Base::update(); m_pCanvasWidget->select(getsel()); m_pCanvasWidget->update(); if (dbgmm) qDebug("refreshData done!()"); } void SplineEditor::keyPressEvent(QKeyEvent * event) { if (event->key() == Qt::Key_Delete) { event->accept(); int id = getsel(); if (id < 0 || id == leftmostPoint() || id == rightmostPoint() ) return killPoint(id); m_savePt.set(-1, -1, -1, -1 ); select(-1); updateGhosting( -1, -1, false ); m_clickX = m_clickY = -100; m_keepHighlight = false; m_pCanvasWidget->select(getsel()); m_pCanvasWidget->update(); m_mode = Free; Q_EMIT splineEdited(true); } } bool SplineEditor::event(QEvent* ev) { if (ev->type() == QEvent::ShortcutOverride) { QKeyEvent* ke = static_cast(ev); if (ke->key() == Qt::Key_Delete) { ev->accept(); // this will inhibit the shortcut and keyPressEvent will be called return true; } } return Base::event(ev); } ////////////////////////////////////////////////////////////////////////// SplineEditorCanvas::SplineEditorCanvas(QWidget* parent, SplineEditor* owner, SplineDef* spline ) : QWidget(parent), m_pOwner(owner), m_spline(spline) { m_selPt = 1; m_highlight_ = 0; setMouseTracking(true); m_ghostX = m_ghostY = m_ghostE = -1; } SplineEditorCanvas::~SplineEditorCanvas() { } const int boxSize = 4; const int gridH = 5; // number of horizontal grid lines const int gridV = 11; // number of vertical grid lines const int kSizeHandleLength = 20; // const int kSizeHandleWidth = 3; void SplineEditorCanvas::paintEvent(QPaintEvent * e) { int W = this->width(), H = this->height(); (void)H; const std::vector< Vector4 >& src = m_spline->pt; // source points std::vector< QPoint > dest( src.size() ); // translated points std::vector< int > destEnv( src.size() ); for( int j=0, e=dest.size(); jymax ); p.drawLine( x, y0, x, y1 ); } for( int j=0; jymax * float(j) / (gridH - 1); int x0, x1, y; cvt( &x0, &y, 0.0f, yf ); cvt( &x1, &y, 1.0f, yf ); p.drawLine( x0, y, x1, y ); } // ghost if( m_ghostX >= 0 && m_ghostX <= 1 && m_ghostY >= 0 && m_ghostY <= m_spline->ymax && !m_highlight_ ) { // find the end points int idx = -1; for( int j=1, e=(int)src.size(); j= 0 ) { int gx, gy; cvt( &gx, &gy, m_ghostX, m_ghostY ); p.setPen( ghostPen ); p.setBrush( transparentBrush ); p.drawRect( gx - boxSize/2, gy - boxSize/2, boxSize, boxSize ); p.drawLine( dest[idx], QPoint(gx,gy) ); p.drawLine( QPoint(gx,gy), dest[idx+1] ); } else if( dest.size() > 0 ) { int gx, gy; cvt( &gx, &gy, m_ghostX, m_ghostY ); p.setPen( ghostPen ); p.setBrush( transparentBrush ); gx = W*(src[0].x < 0.5f); p.drawRect( gx - boxSize/2, gy - boxSize/2, boxSize, boxSize ); p.drawLine( dest[0], QPoint(gx ,gy) ); } } // lines if( src.size() > 1 ) { p.setPen( thinPen ); p.drawPolyline( &dest[0], dest.size() ); p.setBrush(blackBrush); p.setBackgroundMode( Qt::OpaqueMode ); } else if( src.size() == 1 ) { p.setPen( thinDashedPen ); p.drawLine( QPoint( 0, dest[0].y() ), QPoint(W,dest[0].y()) ); } // point handles p.setPen( thinPen ); p.setBrush( blackBrush ); for( int j=0, e=src.size(); j& src = m_spline->pt; // source points for( int j=0, e=src.size(); j SplineEditorCanvas::getEnvHandleAt(int x, int y) { const std::vector< Vector4 >& src = m_spline->pt; // source points const int boxWidth = kSizeHandleWidth + 7; const int boxHeight = kSizeHandleLength; for( int j=0, e=src.size(); jwidth() - 2*kHBorderSize); float sy = 1.0f/(this->height() - 2*kVBorderSize); *fx = (x - kHBorderSize ) * sx; *fy = m_spline->ymax * (1.0f - (y - kVBorderSize)*sy); } void SplineEditorCanvas::cvt( int* ix, int* iy, float x, float y ) { float sx = this->width() - 2*kHBorderSize; float sy = this->height() - 2*kVBorderSize; *ix = x * sx + kHBorderSize; *iy = ( 1.0f - y / m_spline->ymax ) * sy + kVBorderSize; } void SplineEditorCanvas::mouseMoveEvent(QMouseEvent * e) { m_pOwner->onCanvasMouse(e); } void SplineEditorCanvas::mousePressEvent(QMouseEvent * e) { m_pOwner->onCanvasMouse(e); } void SplineEditorCanvas::mouseReleaseEvent(QMouseEvent * e) { m_pOwner->onCanvasMouse(e); } void SplineEditorCanvas::leaveEvent( QEvent* e ) { m_pOwner->onCanvasLeave(e); } ////////////////////////////////////////////////////////////////////////// #include "reflection/Property.h" #include "v8datamodel/DataModel.h" #include "v8datamodel/NumberSequence.h" static RBX::NumberSequence::Key convert(const G3D::Vector4& a) { return RBX::NumberSequence::Key(a.x, a.y, a.z); } static G3D::Vector4 convert(const RBX::NumberSequence::Key& k, int j ) { return G3D::Vector4( k.time, k.value, k.envelope, 0.1f + j ); } static void convert(SplineDef* dest, const RBX::NumberSequence* src, float ymax) { const std::vector& keys = src->getPoints(); dest->ymax = ymax; dest->pt.resize(keys.size()); for( int j=0, e=keys.size(); jpt[j] = convert(keys[j], j); dest->pt[j].x = clamp(dest->pt[j].x, 0, 1); dest->pt[j].y = clamp(dest->pt[j].y, 0, ymax); dest->pt[j].z = clamp(dest->pt[j].z, 0, dest->pt[j].y ); dest->pt[j].z = clamp(dest->pt[j].z, 0, ymax - dest->pt[j].y ); } } static void convert(RBX::NumberSequence* dest, const SplineDef* src) { std::vector keys; keys.reserve( src->pt.size() ); for( int j=0, e=src->pt.size(); jpt[j]); if (key.time >= 0.0f && key.time <= 1.0f) keys.push_back(key); } if (!RBX::NumberSequence::validate(keys, false)) RBXASSERT(RBX::NumberSequence::validate(keys, false)); // if this happens, then the editor is supplying a borked curve and this conversion function is not smart enough to filter it *dest = keys; } SplineEditorAdapter::SplineEditorAdapter( const RBX::Reflection::PropertyDescriptor* pPropertyDescriptor, boost::shared_ptr dataModel, RBX::Instance* inst ) : m_pPropertyDescriptor(pPropertyDescriptor) , m_dataModel(dataModel) , m_pInst(inst) , m_inhibitPropChanged(false) { const char* settingsName = "generic"; float ymax = 1; // default // this sucks, but until we get a better way to communicate the max. range of the values in the sequence, the code below shall remain: if (pPropertyDescriptor == &RBX::CustomParticleEmitter::prop_transp) { ymax = 1; settingsName = "Transparency"; } else if (pPropertyDescriptor == &RBX::CustomParticleEmitter::prop_size) { ymax = 10; settingsName = "Size"; } RBX::Reflection::Variant variant; m_pPropertyDescriptor->getVariant( m_pInst, variant ); RBX::NumberSequence ns = variant.get(); convert( &m_spline, &ns, ymax ); m_pSplineEditor = new SplineEditor(&UpdateUIManager::Instance().getMainWindow(), &m_spline, settingsName); if (1) // set title { QString title = QString::fromStdString(inst->getFullName() + "." + m_pPropertyDescriptor->name.str); m_pSplineEditor->setWindowTitle(title); } UpdateUIManager::Instance().getMainWindow().addDockWidget(Qt::BottomDockWidgetArea, m_pSplineEditor ); connect( m_pSplineEditor, SIGNAL(splineEdited(bool)), this, SLOT(onEdited(bool))); connect( m_pSplineEditor, SIGNAL(finished()), this, SLOT(onClosed())); m_pSplineEditor->setFocus(); m_propChangedConn = m_pInst->propertyChangedSignal.connect( boost::bind(&SplineEditorAdapter::onPropChanged, this, _1) ); m_ancestryChangedConn = m_pInst->ancestryChangedSignal.connect( boost::bind(&SplineEditorAdapter::onAncestryChanged, this, _1, _2) ); } SplineEditorAdapter::~SplineEditorAdapter() { m_pSplineEditor->deleteLater(); disconnect( this, SLOT(onEdited(bool)) ); disconnect( this, SLOT(onClosed()) ); }; void SplineEditorAdapter::show() { m_pSplineEditor->show(); } void SplineEditorAdapter::onEdited(bool undo) { RBX::NumberSequence ns; convert( &ns, &m_spline ); m_inhibitPropChanged = true; RBX::DataModel::LegacyLock lock(m_dataModel.get(), RBX::DataModelJob::Write); m_pPropertyDescriptor->setVariant(m_pInst, ns); if( undo ) { RBX::ChangeHistoryService::requestWaypoint(m_pPropertyDescriptor->name.c_str(), m_dataModel.get()); } m_inhibitPropChanged = false; } void SplineEditorAdapter::onPropChanged(const RBX::Reflection::PropertyDescriptor* pd) { if( !m_inhibitPropChanged && pd == m_pPropertyDescriptor ) { RBX::Reflection::Variant variant; m_pPropertyDescriptor->getVariant( m_pInst, variant ); RBX::NumberSequence ns = variant.get(); SplineDef* sd = new SplineDef(); convert( sd, &ns, m_spline.ymax ); QMetaObject::invokeMethod(m_pSplineEditor, "refreshData", Qt::QueuedConnection, Q_ARG(void*, sd) ); // this sucks, Qt can't into arbitrary types } } void SplineEditorAdapter::onAncestryChanged(shared_ptr, shared_ptr) { RBX::DataModel* dm = RBX::DataModel::get(m_pInst); if (!dm) // we're effectively dead { m_pSplineEditor->close(); return; } } void SplineEditorAdapter::onClosed() { onEdited(true); delete this; }