Add Playhead and Timeline to Pattern Editor (#7794)

Adds a visual indicator to show the pattern editor playing, along with a timeline widget to allow the user to skip forward/backward in a pattern.
This commit is contained in:
regulus79
2025-07-02 18:04:16 -04:00
committed by GitHub
parent 186beec156
commit bbdfeff5e1
7 changed files with 75 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -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;

View File

@@ -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 );
};

View File

@@ -100,6 +100,8 @@ public slots:
void updateComboBox();
void currentPatternChanged();
signals:
void trackUpdated();
private:
ComboBoxModel m_patternComboBoxModel;

View File

@@ -152,6 +152,7 @@ void PatternStore::updatePatternTrack(Clip* clip)
{
t->dataChanged();
}
emit trackUpdated();
}

View File

@@ -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<int>(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

View File

@@ -25,6 +25,7 @@
#include "PatternEditor.h"
#include <QAction>
#include <QVBoxLayout>
#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<QVBoxLayout*>(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<MidiClip*>(track->getClip(m_ps->currentPattern()));
m_maxClipLength = std::max(m_maxClipLength, static_cast<tick_t>(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()));