diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index ea19f5ccb..6957c8d6f 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -792,7 +792,7 @@ lmms--gui--ClipView { qproperty-selectedColor: rgb( 0, 125, 255 ); qproperty-patternClipBackground: rgb( 80, 80, 80 ); qproperty-textColor: rgb( 255, 255, 255 ); - qproperty-textBackgroundColor: rgba(0, 0, 0, 75); + qproperty-textBackgroundColor: rgba(0, 0, 0, 100); qproperty-textShadowColor: rgb( 0, 0, 0 ); qproperty-gradient: true; /* boolean property, set true to have a gradient */ qproperty-markerColor: rgb(0, 0, 0); @@ -825,6 +825,11 @@ lmms--gui--AutomationClipView { /* pattern clip */ lmms--gui--PatternClipView { background-color: rgb( 128, 182, 175 ); /* default colour for pattern tracks */ + qproperty-emptyTrackHeightRatio: 0.5; /* height of empty tracks versus non-empty tracks */ + qproperty-verticalPadding: 0.15; /* padding above and below the beat preview on the clip, fraction of the total clip height */ + qproperty-noteVerticalSpacing: 0.2; /* spacing above and below each note, fraction of the note height */ + qproperty-noteHorizontalSpacing: 0.2; /* spacing left/right of each note, fraction of the note width */ + qproperty-noteColor: #ffffff; /* colour of the beat/note rectangles on the clip */ } /* Subwindows in MDI-Area */ diff --git a/data/themes/default/style.css b/data/themes/default/style.css index efe2dc080..7323eb6c9 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -852,7 +852,7 @@ lmms--gui--ClipView { qproperty-selectedColor: #006B65; qproperty-patternClipBackground: #373d48; qproperty-textColor: #fff; - qproperty-textBackgroundColor: rgba(0, 0, 0, 75); + qproperty-textBackgroundColor: rgba(0, 0, 0, 100); qproperty-textShadowColor: rgba(0,0,0,200); qproperty-gradient: false; /* boolean property, set true to have a gradient */ qproperty-markerColor: rgb(0, 0, 0); @@ -885,6 +885,11 @@ lmms--gui--AutomationClipView { /* pattern clip */ lmms--gui--PatternClipView { background-color: #20BDB2; /* default colour for pattern tracks */ + qproperty-emptyTrackHeightRatio: 0.5; /* height of empty tracks versus non-empty tracks */ + qproperty-verticalPadding: 0.15; /* padding above and below the beat preview on the clip, fraction of the total clip height */ + qproperty-noteVerticalSpacing: 0.2; /* spacing above and below each note, fraction of the note height */ + qproperty-noteHorizontalSpacing: 0.2; /* spacing left/right of each note, fraction of the note width */ + qproperty-noteColor: #ffffff; /* colour of the beat/note rectangles on the clip */ } /* Subwindows in MDI-Area */ diff --git a/include/PatternClipView.h b/include/PatternClipView.h index ee313f32a..c45c957ef 100644 --- a/include/PatternClipView.h +++ b/include/PatternClipView.h @@ -40,6 +40,11 @@ namespace gui class PatternClipView : public ClipView { Q_OBJECT + Q_PROPERTY(float emptyTrackHeightRatio MEMBER m_emptyTrackHeightRatio) + Q_PROPERTY(float verticalPadding MEMBER m_verticalPadding) + Q_PROPERTY(float noteVerticalSpacing MEMBER m_noteVerticalSpacing) + Q_PROPERTY(float noteHorizontalSpacing MEMBER m_noteHorizontalSpacing) + Q_PROPERTY(QColor noteColor MEMBER m_noteColor) public: PatternClipView(Clip* clip, TrackView* tv); ~PatternClipView() override = default; @@ -65,6 +70,12 @@ private: QPixmap m_paintPixmap; QStaticText m_staticTextName; + + float m_emptyTrackHeightRatio {0.5f}; + float m_verticalPadding {0.15f}; + float m_noteVerticalSpacing {0.2f}; + float m_noteHorizontalSpacing {0.2f}; + QColor m_noteColor {255, 255, 255}; } ; diff --git a/include/TrackContainer.h b/include/TrackContainer.h index 01e94df54..797b47289 100644 --- a/include/TrackContainer.h +++ b/include/TrackContainer.h @@ -99,6 +99,7 @@ public: signals: void trackAdded( lmms::Track * _track ); + void trackRemoved(); protected: static AutomatedValueMap automatedValuesFromTracks(const TrackList &tracks, TimePos timeStart, int clipNum = -1); diff --git a/src/core/TrackContainer.cpp b/src/core/TrackContainer.cpp index a2c4ee6c1..5a68ac8db 100644 --- a/src/core/TrackContainer.cpp +++ b/src/core/TrackContainer.cpp @@ -210,6 +210,7 @@ void TrackContainer::removeTrack( Track * _track ) { Engine::getSong()->setModified(); } + emit trackRemoved(); } } diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index a4e702bc9..37ee4c29d 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -583,7 +583,7 @@ void ClipView::paintTextLabel(QString const & text, QPainter & painter) elidedClipName = text.trimmed(); } - painter.fillRect(QRect(0, 0, width(), fontMetrics.height() + 2 * textTop), textBackgroundColor()); + painter.fillRect(QRect(0, 0, fontMetrics.horizontalAdvance(elidedClipName) + 8, fontMetrics.height() + 2 * textTop), textBackgroundColor()); int const finalTextTop = textTop + fontMetrics.ascent(); painter.setPen(textShadowColor()); diff --git a/src/gui/clips/MidiClipView.cpp b/src/gui/clips/MidiClipView.cpp index 2aa1c7f1d..c92215d65 100644 --- a/src/gui/clips/MidiClipView.cpp +++ b/src/gui/clips/MidiClipView.cpp @@ -533,10 +533,7 @@ void MidiClipView::wheelEvent(QWheelEvent * we) Engine::getSong()->setModified(); update(); - if( getGUI()->pianoRoll()->currentMidiClip() == m_clip ) - { - getGUI()->pianoRoll()->update(); - } + m_clip->updatePatternTrack(); } we->accept(); } diff --git a/src/gui/clips/PatternClipView.cpp b/src/gui/clips/PatternClipView.cpp index 677728185..1dd8ad683 100644 --- a/src/gui/clips/PatternClipView.cpp +++ b/src/gui/clips/PatternClipView.cpp @@ -31,8 +31,10 @@ #include "Engine.h" #include "GuiApplication.h" #include "MainWindow.h" +#include "MidiClip.h" #include "PatternClip.h" #include "PatternStore.h" +#include "PatternTrack.h" #include "RenameDialog.h" #include "TrackContainerView.h" #include "TrackView.h" @@ -48,6 +50,10 @@ PatternClipView::PatternClipView(Clip* _clip, TrackView* _tv) : { connect( _clip->getTrack(), SIGNAL(dataChanged()), this, SLOT(update())); + connect(Engine::patternStore(), &TrackContainer::trackAdded, + this, &PatternClipView::update); + connect(Engine::patternStore(), &TrackContainer::trackRemoved, + this, &PatternClipView::update); setStyle( QApplication::style() ); } @@ -108,6 +114,13 @@ void PatternClipView::paintEvent(QPaintEvent*) // paint a black rectangle under the clip to prevent glitches with transparent backgrounds p.fillRect( rect(), QColor( 0, 0, 0 ) ); + const int pixelsPerPattern = Engine::patternStore()->lengthOfPattern(m_patternClip->patternIndex()) * pixelsPerBar(); + int offset = static_cast(m_patternClip->startTimeOffset() * (pixelsPerBar() / TimePos::ticksPerBar())) + % pixelsPerPattern; + if (offset < 2) { + offset += pixelsPerPattern; + } + if( gradient() ) { p.fillRect( rect(), lingrad ); @@ -116,15 +129,71 @@ void PatternClipView::paintEvent(QPaintEvent*) { p.fillRect( rect(), c ); } + + // Draw notes + + const int patternIndex = static_cast(m_patternClip->getTrack())->patternIndex(); + // Count the number of non-empty instrument tracks. + // Only used midi tracks will be drawn, but empty tracks will still give a bit of empty space for padding. + int numberInstrumentTracksUsed = 0; + for (const auto& track : Engine::patternStore()->tracks()) + { + const MidiClip* const mClip = dynamic_cast(track->getClip(patternIndex)); + if (mClip && mClip->notes().size() > 0) + { + numberInstrumentTracksUsed++; + } + } + const auto totalTracks = Engine::patternStore()->tracks().size(); + const auto numberEmptyTracks = totalTracks - numberInstrumentTracksUsed; + + const float totalHeight = height() * (1.0f - 2 * m_verticalPadding); + const float totalHeightForEmptyTracks = totalHeight * numberEmptyTracks / totalTracks * m_emptyTrackHeightRatio; + const float totalHeightForTracks = totalHeight - totalHeightForEmptyTracks; + + const float trackHeight = numberInstrumentTracksUsed > 0 + ? totalHeightForTracks / numberInstrumentTracksUsed + : 0.f; + const float emptyTrackHeight = numberEmptyTracks > 0 + ? totalHeightForEmptyTracks / numberEmptyTracks + : 0.f; + + const int verticalNoteSpacing = trackHeight * m_noteVerticalSpacing; + const int horizontalNoteSpacing = pixelsPerBar() / TimePos::stepsPerBar() * m_noteHorizontalSpacing; + + float lastY = height() * m_verticalPadding; + for (const auto& track : Engine::patternStore()->tracks()) + { + const MidiClip* const mClip = dynamic_cast(track->getClip(patternIndex)); + if (!mClip || mClip->notes().size() == 0) + { + lastY += emptyTrackHeight; + continue; + } + + // Compare how long the clip view is compared to the underlying pattern. First +1 for ceiling, second +1 for possible previous bar. + const int maxPossibleRepetions = getClip()->length() / mClip->length() + 1 + 1; + for (const Note* note : mClip->notes()) + { + QRect noteRect = QRect( + note->pos() * pixelsPerBar() / TimePos::ticksPerBar() + offset + horizontalNoteSpacing / 2, + lastY + verticalNoteSpacing / 2, + std::max(1.0f, pixelsPerBar() / TimePos::stepsPerBar() - horizontalNoteSpacing), + std::max(1.0f, trackHeight - verticalNoteSpacing) + ); + // Loop through all the possible bars this pattern could affect. Starting at -1 for the possibility of start offset + const auto noteColor = QColor(m_noteColor.red(), m_noteColor.green(), m_noteColor.blue(), std::clamp(note->getVolume() * 255 / 100, 50, 255)); + for (int i = -1; i < maxPossibleRepetions - 1; i++) + { + noteRect.moveLeft(note->pos() * pixelsPerBar() / TimePos::ticksPerBar() + offset + i * pixelsPerPattern + horizontalNoteSpacing / 2); + p.fillRect(noteRect, noteColor); + } + } + lastY += trackHeight; + } // bar lines const int lineSize = 3; - int pixelsPerPattern = Engine::patternStore()->lengthOfPattern(m_patternClip->patternIndex()) * pixelsPerBar(); - int offset = static_cast(m_patternClip->startTimeOffset() * (pixelsPerBar() / TimePos::ticksPerBar())) - % pixelsPerPattern; - if (offset < 2) { - offset += pixelsPerPattern; - } p.setPen( c.darker( 200 ) );