Pianoroll: nudge/snap while dragging notes (#5848)

* pianoroll: nudge/snap while dragging notes

combobox for switching behavior
old behavior (nudge) is default

* snap note start or note end to nearest grid line

* fix member variable name

* change default width of pianoroll

* remove trailing white space

* use mouse position instead of m_draggednote

* Uses enum instead of text for GridMode selection

	To avoid problems with translations breaking the conditional,
PianoRoll::changeSnapMode now uses the value of the ComboBox model,
casted to the GridMode enum instead of the text.

* Fixes last code style issues

	A few code style issues were left, those are fixed in this
commit.

* Removes forgotten comma on enum

	Since the last enum value was commented out, there's an
unnecessary comma left that was removed.

* Rewrites some of the grid logic

	Separates Nudge logic and Snap logic more explicitly.
	Avoids recalculation of mouse conversion to time line ticks.
	Disables shift resizing for Snap mode.
	Removes unnecessary range checks.
	Fixes bug with note key boundary checks (which is present in master).

* Make noteOffset an int instead of TimePos

Co-authored-by: Ian Caio <iancaio_dev@hotmail.com>
This commit is contained in:
rghvdberg
2021-03-05 13:38:33 +01:00
committed by GitHub
parent 5d366ef06f
commit ea295c4ee8
3 changed files with 139 additions and 51 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

View File

@@ -214,6 +214,8 @@ protected slots:
void fitNoteLengths(bool fill);
void constrainNoteLengths(bool constrainMax);
void changeSnapMode();
signals:
void currentPatternChanged();
@@ -258,6 +260,13 @@ private:
PR_BLACK_KEY
};
enum GridMode
{
gridNudge,
gridSnap
// gridFree
};
PositionLine * m_positionLine;
QVector<QString> m_nemStr; // gui names of each edit mode
@@ -302,7 +311,7 @@ private:
int noteEditRight() const;
int noteEditLeft() const;
void dragNotes( int x, int y, bool alt, bool shift, bool ctrl );
void dragNotes(int x, int y, bool alt, bool shift, bool ctrl);
static const int cm_scrollAmtHoriz = 10;
static const int cm_scrollAmtVert = 1;
@@ -325,6 +334,7 @@ private:
ComboBoxModel m_keyModel;
ComboBoxModel m_scaleModel;
ComboBoxModel m_chordModel;
ComboBoxModel m_snapModel;
static const QVector<double> m_zoomLevels;
static const QVector<double> m_zoomYLevels;
@@ -347,6 +357,7 @@ private:
Note * m_currentNote;
Actions m_action;
NoteEditMode m_noteEditMode;
GridMode m_gridMode;
int m_selectStartTick;
int m_selectedTick;
@@ -528,6 +539,7 @@ private:
ComboBox * m_keyComboBox;
ComboBox * m_scaleComboBox;
ComboBox * m_chordComboBox;
ComboBox* m_snapComboBox;
QPushButton * m_clearGhostButton;
};

View File

@@ -74,7 +74,7 @@ typedef AutomationPattern::timeMap timeMap;
// some constants...
const int INITIAL_PIANOROLL_WIDTH = 860;
const int INITIAL_PIANOROLL_WIDTH = 970;
const int INITIAL_PIANOROLL_HEIGHT = 485;
const int SCROLLBAR_SIZE = 12;
@@ -438,6 +438,13 @@ PianoRoll::PianoRoll() :
connect( m_timeLine, SIGNAL( regionSelectedFromPixels( int, int ) ),
this, SLOT( selectRegionFromPixels( int, int ) ) );
// Set up snap model
m_snapModel.addItem(tr("Nudge"));
m_snapModel.addItem(tr("Snap"));
m_snapModel.setValue(0);
connect(&m_snapModel, SIGNAL(dataChanged()),
this, SLOT(changeSnapMode()));
m_stepRecorder.initialize();
}
@@ -1300,10 +1307,13 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke)
if( m_action == ActionMoveNote ||
m_action == ActionResizeNote )
{
dragNotes( m_lastMouseX, m_lastMouseY,
ke->modifiers() & Qt::AltModifier,
ke->modifiers() & Qt::ShiftModifier,
ke->modifiers() & Qt::ControlModifier );
dragNotes(
m_lastMouseX,
m_lastMouseY,
ke->modifiers() & Qt::AltModifier,
ke->modifiers() & Qt::ShiftModifier,
ke->modifiers() & Qt::ControlModifier
);
}
}
ke->accept();
@@ -1356,10 +1366,13 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke)
if( m_action == ActionMoveNote ||
m_action == ActionResizeNote )
{
dragNotes( m_lastMouseX, m_lastMouseY,
ke->modifiers() & Qt::AltModifier,
ke->modifiers() & Qt::ShiftModifier,
ke->modifiers() & Qt::ControlModifier );
dragNotes(
m_lastMouseX,
m_lastMouseY,
ke->modifiers() & Qt::AltModifier,
ke->modifiers() & Qt::ShiftModifier,
ke->modifiers() & Qt::ControlModifier
);
}
}
@@ -2387,10 +2400,13 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
pauseTestNotes();
}
dragNotes( me->x(), me->y(),
dragNotes(
me->x(),
me->y(),
me->modifiers() & Qt::AltModifier,
me->modifiers() & Qt::ShiftModifier,
me->modifiers() & Qt::ControlModifier );
me->modifiers() & Qt::ControlModifier
);
if( replay_note && m_action == ActionMoveNote && ! ( ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) )
{
@@ -2745,7 +2761,7 @@ void PianoRoll::updateKnifePos(QMouseEvent* me)
void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
void PianoRoll::dragNotes(int x, int y, bool alt, bool shift, bool ctrl)
{
// dragging one or more notes around
@@ -2758,43 +2774,67 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
off_ticks -= m_mouseDownTick - m_currentPosition;
off_key -= m_mouseDownKey - m_startKey;
// if they're not holding alt, quantize the offset
if( ! alt )
{
off_ticks = floor( off_ticks / quantization() )
* quantization();
}
// make sure notes won't go outside boundary conditions
if( m_action == ActionMoveNote && ! ( shift && ! m_startedWithShift ) )
{
if( m_moveBoundaryLeft + off_ticks < 0 )
{
off_ticks -= (off_ticks + m_moveBoundaryLeft);
}
if( m_moveBoundaryTop + off_key > NumKeys )
{
off_key -= NumKeys - (m_moveBoundaryTop + off_key);
}
if( m_moveBoundaryBottom + off_key < 0 )
{
off_key -= (m_moveBoundaryBottom + off_key);
}
}
// get note-vector of current pattern
const NoteVector & notes = m_pattern->notes();
if (m_action == ActionMoveNote)
{
// Calculate the offset for either Nudge or Snap modes
int noteOffset = off_ticks;
if (m_gridMode == gridSnap && quantization () > 1)
{
// Get the mouse timeline absolute position
TimePos mousePos(m_currentNote->oldPos().getTicks() + off_ticks);
// We create a mousePos that is relative to the end of the note instead
// of the beginning. That's to see if we will snap the beginning or end
// of the note
TimePos mousePosEnd(mousePos);
mousePosEnd += m_currentNote->oldLength();
// Now we quantize the mouse position to snap it to the grid
TimePos mousePosQ = mousePos.quantize(static_cast<float>(quantization()) / DefaultTicksPerBar);
TimePos mousePosEndQ = mousePosEnd.quantize(static_cast<float>(quantization()) / DefaultTicksPerBar);
bool snapEnd = abs(mousePosEndQ - mousePosEnd) < abs(mousePosQ - mousePos);
// Set the offset
noteOffset = snapEnd
? mousePosEndQ.getTicks() - m_currentNote->oldPos().getTicks() - m_currentNote->oldLength().getTicks()
: mousePosQ.getTicks() - m_currentNote->oldPos().getTicks();
}
else if (m_gridMode == gridNudge)
{
// if they're not holding alt, quantize the offset
if (!alt)
{
noteOffset = floor(off_ticks / quantization()) * quantization();
}
}
// Make sure notes won't go outside boundary conditions
if (m_moveBoundaryLeft + noteOffset < 0)
{
noteOffset = -m_moveBoundaryLeft;
}
if (m_moveBoundaryTop + off_key >= NumKeys)
{
off_key = -m_moveBoundaryTop + NumKeys - 1;
}
if (m_moveBoundaryBottom + off_key < 0)
{
off_key = -m_moveBoundaryBottom;
}
// Apply offset to all selected notes
for (Note *note : getSelectedNotes())
{
if (shift && ! m_startedWithShift)
// Quick resize is only enabled on Nudge mode, since resizing the note
// while in Snap mode breaks the calculation of the note offset
if (shift && ! m_startedWithShift && m_gridMode == gridNudge)
{
// quick resize, toggled by holding shift after starting a note move, but not before
int ticks_new = note->oldLength().getTicks() + off_ticks;
int ticks_new = note->oldLength().getTicks() + noteOffset;
if( ticks_new <= 0 )
{
ticks_new = 1;
@@ -2805,17 +2845,13 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
else
{
// moving note
int pos_ticks = note->oldPos().getTicks() + off_ticks;
// Final position of the note
TimePos posTicks(note->oldPos().getTicks() + noteOffset);
int key_num = note->oldKey() + off_key;
// ticks can't be negative
pos_ticks = qMax(0, pos_ticks);
// upper/lower bound checks on key_num
key_num = qMax(0, key_num);
key_num = qMin(key_num, NumKeys);
note->setPos( TimePos( pos_ticks ) );
note->setKey( key_num );
note->setPos(posTicks);
note->setKey(key_num);
}
}
}
@@ -2827,6 +2863,12 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
// If shift + ctrl then we also rearrange all posterior notes (sticky)
// If shift is pressed but only one note is selected, apply sticky
// Quantize the resizing if alt is not pressed
if (!alt)
{
off_ticks = floor(off_ticks / quantization()) * quantization();
}
auto selectedNotes = getSelectedNotes();
if (shift)
@@ -2914,6 +2956,16 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
// shift is not pressed; stretch length of selected notes but not their position
int minLength = alt ? 1 : m_minResizeLen.getTicks();
if (m_gridMode == gridSnap)
{
// Calculate the end point of the note being dragged
TimePos oldEndPoint = m_currentNote->oldPos() + m_currentNote->oldLength();
// Quantize that position
TimePos quantizedEndPoint = Note::quantized(oldEndPoint, quantization());
// Add that difference to the offset from the resize
off_ticks += quantizedEndPoint - oldEndPoint;
}
for (Note *note : selectedNotes)
{
int newLength = qMax(minLength, note->oldLength() + off_ticks);
@@ -4611,8 +4663,14 @@ Note * PianoRoll::noteUnderMouse()
return NULL;
}
void PianoRoll::changeSnapMode()
{
// gridNudge,
// gridSnap,
// gridFree - to be implemented
m_gridMode = static_cast<GridMode>(m_snapModel.value());
}
PianoRollWindow::PianoRollWindow() :
Editor(true, true),
@@ -4783,6 +4841,15 @@ PianoRollWindow::PianoRollWindow() :
m_chordComboBox->setFixedSize( 105, ComboBox::DEFAULT_HEIGHT );
m_chordComboBox->setToolTip( tr( "Chord" ) );
// setup snap-stuff
QLabel* snapLbl = new QLabel(m_toolBar);
snapLbl->setPixmap(embed::getIconPixmap("gridmode"));
m_snapComboBox = new ComboBox(m_toolBar);
m_snapComboBox->setModel(&m_editor->m_snapModel);
m_snapComboBox->setFixedSize(105, ComboBox::DEFAULT_HEIGHT);
m_snapComboBox->setToolTip(tr("Snap mode"));
// -- Clear ghost pattern button
m_clearGhostButton = new QPushButton( m_toolBar );
m_clearGhostButton->setIcon( embed::getIconPixmap( "clear_ghost_note" ) );
@@ -4850,6 +4917,15 @@ PianoRollWindow::PianoRollWindow() :
zoomAndNotesToolBar->addSeparator();
zoomAndNotesToolBar->addWidget( m_clearGhostButton );
QWidget* snapWidget = new QWidget();
QHBoxLayout* snapHbox = new QHBoxLayout();
snapHbox->setContentsMargins(0, 0, 0, 0);
snapHbox->addWidget(snapLbl);
snapHbox->addWidget(m_snapComboBox);
snapWidget->setLayout(snapHbox);
zoomAndNotesToolBar->addSeparator();
zoomAndNotesToolBar->addWidget(snapWidget);
// setup our actual window
setFocusPolicy( Qt::StrongFocus );
setFocus();