diff --git a/data/themes/default/step_btn_highlight.png b/data/themes/default/step_btn_highlight.png new file mode 100644 index 00000000000..e2cabfcd7f2 Binary files /dev/null and b/data/themes/default/step_btn_highlight.png differ diff --git a/include/MidiClip.h b/include/MidiClip.h index f3150ba6f24..17bf6395587 100644 --- a/include/MidiClip.h +++ b/include/MidiClip.h @@ -91,6 +91,11 @@ class LMMS_EXPORT MidiClip : public Clip return m_clipType; } + int steps() const + { + return m_steps; + } + // next/previous track based on position in the containing track MidiClip * previousMidiClip() const; diff --git a/include/MidiClipView.h b/include/MidiClipView.h index 4285bf9da0f..ddfefa719a3 100644 --- a/include/MidiClipView.h +++ b/include/MidiClipView.h @@ -91,6 +91,7 @@ protected slots: QPixmap m_stepBtnOn200 = embed::getIconPixmap("step_btn_on_200"); QPixmap m_stepBtnOff = embed::getIconPixmap("step_btn_off"); QPixmap m_stepBtnOffLight = embed::getIconPixmap("step_btn_off_light"); + QPixmap m_stepBtnHighlight = embed::getIconPixmap("step_btn_highlight"); MidiClip* m_clip; QPixmap m_paintPixmap; diff --git a/include/PatternEditor.h b/include/PatternEditor.h index 5787f27ec12..0520c6d573c 100644 --- a/include/PatternEditor.h +++ b/include/PatternEditor.h @@ -37,6 +37,7 @@ namespace gui { class ComboBox; +class TimeLineWidget; class PatternEditor : public TrackContainerView @@ -62,13 +63,19 @@ public slots: void addSampleTrack(); void addAutomationTrack(); void cloneClip(); + void updateMaxSteps(); protected slots: void dropEvent(QDropEvent * de ) override; + void resizeEvent(QResizeEvent* de) override; void updatePosition(); + void updatePixelsPerBar(); private: PatternStore* m_ps; + TimeLineWidget * m_timeLine; + int m_trackHeadWidth; + int m_maxSteps; void makeSteps( bool clone ); }; diff --git a/include/PatternStore.h b/include/PatternStore.h index fcb55e4117a..512ac7cad80 100644 --- a/include/PatternStore.h +++ b/include/PatternStore.h @@ -100,6 +100,8 @@ public slots: void updateComboBox(); void currentPatternChanged(); +signals: + void trackUpdated(); private: ComboBoxModel m_patternComboBoxModel; diff --git a/include/TrackView.h b/include/TrackView.h index 4954071927a..f33c11e016a 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -73,6 +73,11 @@ class TrackView : public QWidget, public ModelView, public JournallingObject return m_track; } + const std::vector& getClipViews() + { + return m_clipViews; + } + inline TrackContainerView* trackContainerView() { return m_trackContainerView; @@ -151,6 +156,7 @@ public slots: Track * m_track; TrackContainerView * m_trackContainerView; + std::vector m_clipViews; TrackOperationsWidget m_trackOperationsWidget; QWidget m_trackSettingsWidget; diff --git a/src/core/PatternStore.cpp b/src/core/PatternStore.cpp index 6af434f65b3..f9c38d9bc56 100644 --- a/src/core/PatternStore.cpp +++ b/src/core/PatternStore.cpp @@ -152,6 +152,7 @@ void PatternStore::updatePatternTrack(Clip* clip) { t->dataChanged(); } + emit trackUpdated(); } diff --git a/src/gui/clips/MidiClipView.cpp b/src/gui/clips/MidiClipView.cpp index b735913e4d0..65c9f9cf13e 100644 --- a/src/gui/clips/MidiClipView.cpp +++ b/src/gui/clips/MidiClipView.cpp @@ -460,6 +460,7 @@ void MidiClipView::paintEvent( QPaintEvent * ) QPixmap stepon200; QPixmap stepoff; QPixmap stepoffl; + QPixmap stephighlight; const int steps = std::max(1, m_clip->m_steps); const int w = width() - 2 * BORDER_WIDTH; @@ -472,6 +473,8 @@ void MidiClipView::paintEvent( QPaintEvent * ) = m_stepBtnOff.scaled(w / steps, m_stepBtnOff.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); stepoffl = m_stepBtnOffLight.scaled( w / steps, m_stepBtnOffLight.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + stephighlight = m_stepBtnHighlight.scaled( + w / steps, m_stepBtnHighlight.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); for (int it = 0; it < steps; it++) // go through all the steps in the beat clip { @@ -481,6 +484,9 @@ void MidiClipView::paintEvent( QPaintEvent * ) const int x = BORDER_WIDTH + static_cast(it * w / steps); const int y = BeatStepButtonOffset; + const bool isAtPlayPos = Engine::getSong()->getPlayPos(Song::PlayMode::Pattern) * TimePos::stepsPerBar() / TimePos::ticksPerBar() == it + && Engine::getSong()->playMode() == Song::PlayMode::Pattern; + if (n) { const int vol = n->getVolume(); @@ -498,6 +504,10 @@ void MidiClipView::paintEvent( QPaintEvent * ) { p.drawPixmap(x, y, stepoff); } + if (isAtPlayPos) + { + p.drawPixmap(x, y, stephighlight); + } } // end for loop // draw a transparent rectangle over muted clips diff --git a/src/gui/editors/PatternEditor.cpp b/src/gui/editors/PatternEditor.cpp index 2915786981d..42d296a0fb4 100644 --- a/src/gui/editors/PatternEditor.cpp +++ b/src/gui/editors/PatternEditor.cpp @@ -25,6 +25,7 @@ #include "PatternEditor.h" #include +#include #include "ClipView.h" #include "ComboBox.h" @@ -35,6 +36,7 @@ #include "PatternTrack.h" #include "Song.h" #include "StringPairDrag.h" +#include "TimeLineWidget.h" #include "TrackView.h" #include "MidiClip.h" @@ -46,9 +48,24 @@ namespace lmms::gui PatternEditor::PatternEditor(PatternStore* ps) : TrackContainerView(ps), - m_ps(ps) + m_ps(ps), + m_trackHeadWidth(ConfigManager::inst()->value("ui", "compacttrackbuttons").toInt() == 1 + ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT + : DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH), + m_maxSteps(TimePos::stepsPerBar()) { setModel(ps); + + m_timeLine = new TimeLineWidget(m_trackHeadWidth, 32, pixelsPerBar(), + Engine::getSong()->getPlayPos(Song::PlayMode::Pattern), + Engine::getSong()->getTimeline(Song::PlayMode::Pattern), + m_currentPosition, Song::PlayMode::Pattern, this + ); + connect(m_timeLine, &TimeLineWidget::positionChanged, this, &PatternEditor::updatePosition); + static_cast( layout() )->insertWidget( 0, m_timeLine ); + + connect(m_ps, &PatternStore::trackUpdated, + this, &PatternEditor::updateMaxSteps); } @@ -79,6 +96,7 @@ void PatternEditor::removeSteps() p->removeSteps(); } } + updateMaxSteps(); } @@ -118,6 +136,7 @@ void PatternEditor::saveSettings(QDomDocument& doc, QDomElement& element) void PatternEditor::loadSettings(const QDomElement& element) { MainWindow::restoreWidgetState(parentWidget(), element); + updateMaxSteps(); } @@ -160,18 +179,47 @@ void PatternEditor::dropEvent(QDropEvent* de) { TrackContainerView::dropEvent( de ); } + updateMaxSteps(); } +void PatternEditor::resizeEvent(QResizeEvent* re) +{ + updatePixelsPerBar(); +} void PatternEditor::updatePosition() { //realignTracks(); + for (const auto& trackView : trackViews()) + { + trackView->update(); + } emit positionChanged( m_currentPosition ); } +void PatternEditor::updatePixelsPerBar() +{ + setPixelsPerBar((width() - m_trackHeadWidth) * TimePos::stepsPerBar() / m_maxSteps); + m_timeLine->setPixelsPerBar(pixelsPerBar()); +} +void PatternEditor::updateMaxSteps() +{ + const TrackContainer::TrackList& tl = model()->tracks(); + + m_maxSteps = 0; + for (const auto& track : tl) + { + if (track->type() == Track::Type::Instrument) + { + auto p = static_cast(track->getClip(m_ps->currentPattern())); + m_maxSteps = std::max(m_maxSteps, p->steps()); + } + } + updatePixelsPerBar(); +} void PatternEditor::makeSteps( bool clone ) @@ -192,6 +240,7 @@ void PatternEditor::makeSteps( bool clone ) } } } + updateMaxSteps(); } // Creates a clone of the current pattern track with the same content, but no clips in the song editor @@ -285,6 +334,7 @@ PatternEditorWindow::PatternEditorWindow(PatternStore* ps) : connect(&ps->m_patternComboBoxModel, SIGNAL(dataChanged()), m_editor, SLOT(updatePosition())); + connect(&ps->m_patternComboBoxModel, &ComboBoxModel::dataChanged, m_editor, &PatternEditor::updateMaxSteps); auto viewNext = new QAction(this); connect(viewNext, SIGNAL(triggered()), m_patternComboBox, SLOT(selectNext())); diff --git a/src/gui/tracks/TrackView.cpp b/src/gui/tracks/TrackView.cpp index ecd397975f1..6d68104be0f 100644 --- a/src/gui/tracks/TrackView.cpp +++ b/src/gui/tracks/TrackView.cpp @@ -428,6 +428,7 @@ void TrackView::createClipView( Clip * clip ) tv->setSelected( true ); } clip->selectViewOnCreate( false ); + m_clipViews.push_back(tv); }