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:
BIN
data/themes/default/step_btn_highlight.png
Normal file
BIN
data/themes/default/step_btn_highlight.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
};
|
||||
|
||||
|
||||
@@ -100,6 +100,8 @@ public slots:
|
||||
void updateComboBox();
|
||||
void currentPatternChanged();
|
||||
|
||||
signals:
|
||||
void trackUpdated();
|
||||
|
||||
private:
|
||||
ComboBoxModel m_patternComboBoxModel;
|
||||
|
||||
@@ -152,6 +152,7 @@ void PatternStore::updatePatternTrack(Clip* clip)
|
||||
{
|
||||
t->dataChanged();
|
||||
}
|
||||
emit trackUpdated();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
|
||||
Reference in New Issue
Block a user