diff --git a/data/themes/default/step_btn_highlight.png b/data/themes/default/step_btn_highlight.png new file mode 100644 index 000000000..e2cabfcd7 Binary files /dev/null and b/data/themes/default/step_btn_highlight.png differ diff --git a/include/MidiClipView.h b/include/MidiClipView.h index 9f1894755..50ef635dd 100644 --- a/include/MidiClipView.h +++ b/include/MidiClipView.h @@ -97,6 +97,7 @@ private: 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 5787f27ec..0ea006120 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; + tick_t m_maxClipLength; void makeSteps( bool clone ); }; diff --git a/include/PatternStore.h b/include/PatternStore.h index fcb55e411..512ac7cad 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/src/core/PatternStore.cpp b/src/core/PatternStore.cpp index 6af434f65..f9c38d9bc 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 70f41eb89..eb44cedab 100644 --- a/src/gui/clips/MidiClipView.cpp +++ b/src/gui/clips/MidiClipView.cpp @@ -641,6 +641,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; @@ -653,6 +654,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 { @@ -662,6 +665,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(); @@ -679,6 +685,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 42bb47e9c..8cea58a82 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,25 @@ 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_maxClipLength(TimePos::ticksPerBar()) { 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); + setFocusPolicy(Qt::StrongFocus); setFocus(); } @@ -81,6 +99,7 @@ void PatternEditor::removeSteps() p->removeSteps(); } } + updateMaxSteps(); } @@ -120,6 +139,7 @@ void PatternEditor::saveSettings(QDomDocument& doc, QDomElement& element) void PatternEditor::loadSettings(const QDomElement& element) { MainWindow::restoreWidgetState(parentWidget(), element); + updateMaxSteps(); } @@ -162,18 +182,49 @@ 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(m_maxClipLength != 0 + ? (width() - m_trackHeadWidth) * TimePos::ticksPerBar() / m_maxClipLength + : (width() - m_trackHeadWidth)); + m_timeLine->setPixelsPerBar(pixelsPerBar()); +} +void PatternEditor::updateMaxSteps() +{ + const TrackContainer::TrackList& tl = model()->tracks(); + + m_maxClipLength = 0; + for (const auto& track : tl) + { + if (track->type() == Track::Type::Instrument) + { + auto mClip = static_cast(track->getClip(m_ps->currentPattern())); + m_maxClipLength = std::max(m_maxClipLength, static_cast(mClip->length())); + } + } + updatePixelsPerBar(); +} void PatternEditor::makeSteps( bool clone ) @@ -194,6 +245,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 @@ -287,6 +339,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()));