From 52a275d06042a3b9aa9883ff45bead3a0ff95588 Mon Sep 17 00:00:00 2001 From: m0dB <79429057+m0dB@users.noreply.github.com> Date: Sat, 4 Nov 2023 15:46:07 +0100 Subject: [PATCH] draw overlapping marks at increasing level --- .../allshader/waveformrendermark.cpp | 3 +- src/waveform/renderers/waveformmark.cpp | 53 +++++++++++--- src/waveform/renderers/waveformmark.h | 31 +++++---- src/waveform/renderers/waveformmarkset.cpp | 69 ++++++++++++++++++- 4 files changed, 127 insertions(+), 29 deletions(-) diff --git a/src/waveform/renderers/allshader/waveformrendermark.cpp b/src/waveform/renderers/allshader/waveformrendermark.cpp index 0b7fd5c799c3..7b18ca2dcbd1 100644 --- a/src/waveform/renderers/allshader/waveformrendermark.cpp +++ b/src/waveform/renderers/allshader/waveformrendermark.cpp @@ -380,10 +380,9 @@ void allshader::WaveformRenderMark::checkCuesUpdated() { if (pMark->m_text.isNull() || newLabel != pMark->m_text || !pMark->fillColor().isValid() || newColor != pMark->fillColor()) { - pMark->m_text = newLabel; + pMark->setText(newLabel); const int dimBrightThreshold = m_waveformRenderer->getDimBrightThreshold(); pMark->setBaseColor(newColor, dimBrightThreshold); - generateMarkImage(pMark); } } } diff --git a/src/waveform/renderers/waveformmark.cpp b/src/waveform/renderers/waveformmark.cpp index 98672acabb50..95a7f2932414 100644 --- a/src/waveform/renderers/waveformmark.cpp +++ b/src/waveform/renderers/waveformmark.cpp @@ -56,9 +56,10 @@ Qt::Alignment decodeAlignmentFlags(const QString& alignString, Qt::Alignment def WaveformMark::WaveformMark(const QString& group, const QDomNode& node, const SkinContext& context, + int priority, const WaveformSignalColors& signalColors, int hotCue) - : m_linePosition{}, m_breadth{}, m_iHotCue{hotCue} { + : m_linePosition{}, m_breadth{}, m_level{}, m_iPriority(priority), m_iHotCue(hotCue) { QString positionControl; QString endPositionControl; if (hotCue != Cue::kNoHotCue) { @@ -121,16 +122,45 @@ WaveformMark::WaveformMark(const QString& group, WaveformMark::~WaveformMark() = default; void WaveformMark::setBaseColor(QColor baseColor, int dimBrightThreshold) { - if (m_pGraphics) { - m_pGraphics->m_obsolete = true; + if (m_fillColor == baseColor) { + return; } + m_fillColor = baseColor; m_borderColor = Color::chooseContrastColor(baseColor, dimBrightThreshold); m_labelColor = Color::chooseColorByBrightness(baseColor, QColor(255, 255, 255, 255), QColor(0, 0, 0, 255), dimBrightThreshold); -}; + + if (m_pGraphics) { + m_pGraphics->m_obsolete = true; + } +} + +void WaveformMark::setText(const QString& text) { + if (m_text == text) { + return; + } + + m_text = text; + + if (m_pGraphics) { + m_pGraphics->m_obsolete = true; + } +} + +void WaveformMark::setLevel(int level) { + if (level == m_level) { + return; + } + + m_level = level; + + if (m_pGraphics) { + m_pGraphics->m_obsolete = true; + } +} bool WaveformMark::contains(QPoint point, Qt::Orientation orientation) const { // Without some padding, the user would only have a single pixel width that @@ -148,13 +178,17 @@ bool WaveformMark::contains(QPoint point, Qt::Orientation orientation) const { // Helper struct to calculate the geometry and fontsize needed by generateImage // to draw the label and text struct MarkerGeometry { - bool m_isSymbol; // it the label normal text or a single symbol (e.g. open circle arrow) + bool m_isSymbol; // is the label normal text or a single symbol (e.g. open circle arrow) QFont m_font; QRectF m_contentRect; QRectF m_labelRect; QSizeF m_imageSize; - MarkerGeometry(const QString& label, bool useIcon, Qt::Alignment align, float breadth) { + MarkerGeometry(const QString& label, + bool useIcon, + Qt::Alignment align, + float breadth, + int level) { // If the label is 1 character long, and this character isn't a letter or a number, // we can assume it's a special symbol m_isSymbol = !useIcon && label.length() == 1 && !label[0].isLetterOrNumber(); @@ -240,9 +274,10 @@ struct MarkerGeometry { if (alignV == Qt::AlignVCenter) { m_labelRect.moveTop((m_imageSize.height() - m_labelRect.height()) / 2.f); } else if (alignV == Qt::AlignBottom) { - m_labelRect.moveBottom(m_imageSize.height() - 0.5f); + m_labelRect.moveBottom(m_imageSize.height() - 0.5f - + level * (m_labelRect.height() + 2.f)); } else { - m_labelRect.moveTop(0.5f); + m_labelRect.moveTop(0.5f + level * (m_labelRect.height() + 2.f)); } } QSize getImageSize(float devicePixelRatio) const { @@ -292,7 +327,7 @@ QImage WaveformMark::generateImage(float breadth, float devicePixelRatio) { const bool useIcon = m_iconPath != ""; // Determine drawing geometries - const MarkerGeometry markerGeometry(label, useIcon, m_align, breadth); + const MarkerGeometry markerGeometry(label, useIcon, m_align, breadth, m_level); m_label.setAreaRect(markerGeometry.m_labelRect); diff --git a/src/waveform/renderers/waveformmark.h b/src/waveform/renderers/waveformmark.h index 11a53131dcb2..7b4b28c57ce8 100644 --- a/src/waveform/renderers/waveformmark.h +++ b/src/waveform/renderers/waveformmark.h @@ -33,6 +33,7 @@ class WaveformMark { const QString& group, const QDomNode& node, const SkinContext& context, + int priority, const WaveformSignalColors& signalColors, int hotCue = Cue::kNoHotCue); ~WaveformMark(); @@ -44,6 +45,9 @@ class WaveformMark { int getHotCue() const { return m_iHotCue; }; + int getPriority() const { + return m_iPriority; + }; // The m_pPositionCO related function bool isValid() const { @@ -89,6 +93,8 @@ class WaveformMark { m_pVisibleCO->connectValueChanged(receiver, slot, Qt::AutoConnection); } + void setText(const QString& text); + // Sets the appropriate mark colors based on the base color void setBaseColor(QColor baseColor, int dimBrightThreshold); QColor fillColor() const { @@ -101,6 +107,8 @@ class WaveformMark { return m_labelColor; } + void setLevel(int level); + // Check if a point (in image coordinates) lies on drawn image. bool contains(QPoint point, Qt::Orientation orientation) const; @@ -114,6 +122,7 @@ class WaveformMark { float m_linePosition; int m_breadth; + int m_level; WaveformMarkLabel m_label; @@ -126,6 +135,7 @@ class WaveformMark { std::unique_ptr m_pGraphics; + int m_iPriority; int m_iHotCue; QColor m_fillColor; @@ -144,27 +154,18 @@ typedef QSharedPointer WaveformMarkPointer; // temporarily incorrect sort order is acceptable. class WaveformMarkSortKey { public: - WaveformMarkSortKey(double samplePosition, int hotcue) + WaveformMarkSortKey(double samplePosition, int priority) : m_samplePosition(samplePosition), - m_hotcue(hotcue) { + m_priority(priority) { } bool operator<(const WaveformMarkSortKey& other) const { - if (m_samplePosition == other.m_samplePosition) { - // Sort WaveformMarks without hotcues before those with hotcues; - // if both have hotcues, sort numerically by hotcue number. - if (m_hotcue == Cue::kNoHotCue && other.m_hotcue != Cue::kNoHotCue) { - return true; - } else if (m_hotcue != Cue::kNoHotCue && other.m_hotcue == Cue::kNoHotCue) { - return false; - } else { - return m_hotcue < other.m_hotcue; - } - } - return m_samplePosition < other.m_samplePosition; + return m_samplePosition == other.m_samplePosition + ? m_priority < other.m_priority + : m_samplePosition < other.m_samplePosition; } private: double m_samplePosition; - int m_hotcue; + int m_priority; }; diff --git a/src/waveform/renderers/waveformmarkset.cpp b/src/waveform/renderers/waveformmarkset.cpp index 45ab38fda3a6..7aedf467d99f 100644 --- a/src/waveform/renderers/waveformmarkset.cpp +++ b/src/waveform/renderers/waveformmarkset.cpp @@ -25,13 +25,16 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, QDomNode child = node.firstChild(); QDomNode defaultChild; + int priority = 0; while (!child.isNull()) { if (child.nodeName() == "DefaultMark") { - m_pDefaultMark = WaveformMarkPointer(new WaveformMark(group, child, context, signalColors)); + m_pDefaultMark = WaveformMarkPointer(new WaveformMark( + group, child, context, --priority, signalColors)); hasDefaultMark = true; defaultChild = child; } else if (child.nodeName() == "Mark") { - WaveformMarkPointer pMark(new WaveformMark(group, child, context, signalColors)); + WaveformMarkPointer pMark(new WaveformMark( + group, child, context, --priority, signalColors)); if (pMark->isValid()) { // guarantee uniqueness even if there is a misdesigned skin QString item = pMark->getItem(); @@ -54,7 +57,8 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, for (int i = 0; i < NUM_HOT_CUES; ++i) { if (m_hotCueMarks.value(i).isNull()) { //qDebug() << "WaveformRenderMark::setup - Automatic mark" << hotCueControlItem; - WaveformMarkPointer pMark(new WaveformMark(group, defaultChild, context, signalColors, i)); + WaveformMarkPointer pMark(new WaveformMark( + group, defaultChild, context, i, signalColors, i)); m_marks.push_front(pMark); m_hotCueMarks.insert(pMark->getHotCue(), pMark); } @@ -69,3 +73,62 @@ WaveformMarkPointer WaveformMarkSet::getHotCueMark(int hotCue) const { WaveformMarkPointer WaveformMarkSet::getDefaultMark() const { return m_pDefaultMark; } + +void WaveformMarkSet::update() { + std::map map; + for (const auto& pMark : m_marks) { + if (pMark->isValid() && pMark->isVisible()) { + double samplePosition = pMark->getSamplePosition(); + if (samplePosition != Cue::kNoPosition) { + // Create a stable key for sorting, because the WaveformMark's samplePosition is a + // ControlObject which can change at any time by other threads. Such a change causes + // another updateCues() call, rebuilding map. + auto key = WaveformMarkSortKey(samplePosition, pMark->getPriority()); + map.emplace(key, pMark); + } + } + } + + m_marksToRender.clear(); + m_marksToRender.reserve(static_cast(map.size())); + std::transform(map.begin(), + map.end(), + std::back_inserter(m_marksToRender), + [](auto const& pair) { return pair.second; }); + + double prevSamplePosition = Cue::kNoPosition; + int levelTop = 0; + int levelBottom = 0; + for (auto& pMark : m_marksToRender) { + if (pMark->getSamplePosition() != prevSamplePosition) { + prevSamplePosition = pMark->getSamplePosition(); + levelTop = 0; + levelBottom = 0; + } + const Qt::Alignment alignV = pMark->m_align & Qt::AlignVertical_Mask; + if (alignV == Qt::AlignVCenter) { + pMark->setLevel(0); + } else if (alignV == Qt::AlignBottom) { + pMark->setLevel(levelBottom++); + } else { + pMark->setLevel(levelTop++); + } + } +} + +WaveformMarkPointer WaveformMarkSet::findHoveredMark( + QPoint pos, Qt::Orientation orientation) const { + // Non-hotcue marks (intro/outro cues, main cue, loop in/out) are sorted + // before hotcues in m_marksToRender so if there is a hotcue in the same + // location, the hotcue gets rendered on top. When right clicking, the + // the hotcue rendered on top must be assigned to m_pHoveredMark to show + // the CueMenuPopup. To accomplish this, m_marksToRender is iterated in + // reverse and the loop breaks as soon as m_pHoveredMark is set. + for (auto it = m_marksToRender.crbegin(); it != m_marksToRender.crend(); ++it) { + const WaveformMarkPointer& pMark = *it; + if (pMark->contains(pos, orientation)) { + return pMark; + } + } + return nullptr; +}