From d16b1dcfdb35fccfbeafca8acc5daa04eb93f20b Mon Sep 17 00:00:00 2001 From: Antoine C Date: Tue, 22 Oct 2024 23:45:01 +0100 Subject: [PATCH] feat: add scrolling waveform in QML using scenegraph --- CMakeLists.txt | 35 +- res/qml/WaveformDisplay.qml | 238 +++++++++ res/qml/main.qml | 8 +- res/shaders/rendergraph/CMakeLists.txt | 12 + src/qml/qmlwaveformdisplay.cpp | 253 +++++++++ src/qml/qmlwaveformdisplay.h | 126 +++++ src/qml/qmlwaveformrenderer.cpp | 152 ++++++ src/qml/qmlwaveformrenderer.h | 486 ++++++++++++++++++ .../allshader/waveformrenderbeat.cpp | 6 +- .../renderers/allshader/waveformrenderbeat.h | 3 +- .../allshader/waveformrendererendoftrack.cpp | 11 +- .../allshader/waveformrendererendoftrack.h | 3 +- .../allshader/waveformrendererpreroll.cpp | 4 +- .../allshader/waveformrendererpreroll.h | 3 +- .../allshader/waveformrendererrgb.cpp | 13 + .../renderers/allshader/waveformrendererrgb.h | 7 + .../allshader/waveformrendermark.cpp | 144 ++++-- .../renderers/allshader/waveformrendermark.h | 80 +-- .../allshader/waveformrendermarkrange.cpp | 11 +- .../allshader/waveformrendermarkrange.h | 12 +- src/waveform/renderers/waveformmark.cpp | 69 +++ src/waveform/renderers/waveformmark.h | 16 +- src/waveform/renderers/waveformmarkrange.cpp | 60 +++ src/waveform/renderers/waveformmarkrange.h | 14 +- src/waveform/renderers/waveformmarkset.cpp | 65 ++- src/waveform/renderers/waveformmarkset.h | 22 +- .../renderers/waveformrendererabstract.h | 6 +- .../renderers/waveformrendermarkbase.cpp | 7 + .../renderers/waveformrendermarkbase.h | 12 + .../renderers/waveformwidgetrenderer.cpp | 42 +- .../renderers/waveformwidgetrenderer.h | 29 +- src/waveform/waveformwidgetfactory.cpp | 20 +- .../widgets/waveformwidgetabstract.cpp | 1 + src/widget/wspinnybase.cpp | 1 + src/widget/wwaveformviewer.cpp | 4 +- 35 files changed, 1811 insertions(+), 164 deletions(-) create mode 100644 res/qml/WaveformDisplay.qml create mode 100644 src/qml/qmlwaveformdisplay.cpp create mode 100644 src/qml/qmlwaveformdisplay.h create mode 100644 src/qml/qmlwaveformrenderer.cpp create mode 100644 src/qml/qmlwaveformrenderer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 055f374b4c8..80356bb7c03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1502,6 +1502,7 @@ else() ) endif() if(QOPENGL) + target_compile_definitions(mixxx-lib PRIVATE __RENDERGRAPH_IS_OPENGL) target_sources(mixxx-lib PRIVATE src/shaders/endoftrackshader.cpp src/shaders/slipmodeshader.cpp @@ -1770,7 +1771,7 @@ if(QT6) # below that takes care of the correct object order in the resulting binary # According to https://doc.qt.io/qt-6/qt-finalize-target.html it is importand for # builds with Qt < 3.21 - qt_add_executable(mixxx WIN32 src/main.cpp MANUAL_FINALIZATION) + qt_add_executable(mixxx WIN32 MACOSX_BUNDLE src/main.cpp MANUAL_FINALIZATION) else() find_package(Qt5 COMPONENTS Core) # For Qt Core cmake functions # This is the first package form the environment, if this fails give hints how to install the environment @@ -2766,9 +2767,11 @@ if(QML) res/qml/Mixxx/Controls/WaveformOverviewHotcueMarker.qml res/qml/Mixxx/Controls/WaveformOverviewMarker.qml res/qml/Mixxx/Controls/WaveformOverview.qml + res/qml/Mixxx/Controls/WaveformDisplay.qml ) target_link_libraries(mixxx-qml-lib PRIVATE mixxx-qml-mixxxcontrolsplugin) + target_compile_definitions(mixxx-qml-lib PRIVATE __RENDERGRAPH_IS_SCENEGRAPH) target_sources(mixxx-qml-lib PRIVATE src/qml/asyncimageprovider.cpp src/qml/qmlapplication.cpp @@ -2788,6 +2791,22 @@ if(QML) src/qml/qmlvisibleeffectsmodel.cpp src/qml/qmlchainpresetmodel.cpp src/qml/qmlwaveformoverview.cpp + src/qml/qmlwaveformdisplay.cpp + src/qml/qmlwaveformrenderer.cpp + src/waveform/renderers/allshader/digitsrenderer.cpp + src/waveform/renderers/allshader/waveformrenderbeat.cpp + src/waveform/renderers/allshader/waveformrenderer.cpp + src/waveform/renderers/allshader/waveformrendererendoftrack.cpp + src/waveform/renderers/allshader/waveformrendererpreroll.cpp + src/waveform/renderers/allshader/waveformrendererrgb.cpp + src/waveform/renderers/allshader/waveformrenderersignalbase.cpp + src/waveform/renderers/allshader/waveformrendermark.cpp + src/waveform/renderers/allshader/waveformrendermarkrange.cpp + # FIXME depends on rendergraph/openglnode.h + # src/waveform/renderers/allshader/waveformrendererslipmode.cpp + # src/waveform/renderers/allshader/waveformrendererfiltered.cpp + # src/waveform/renderers/allshader/waveformrendererhsv.cpp + # src/waveform/renderers/allshader/waveformrenderersimple.cpp # The following sources need to be in this target to get QML_ELEMENT properly interpreted src/control/controlmodel.cpp src/control/controlsortfiltermodel.cpp @@ -3480,6 +3499,7 @@ if (STEM) if(QML) target_compile_definitions(mixxx-qml-lib PUBLIC __STEM__) target_sources(mixxx-qml-lib PRIVATE + src/waveform/renderers/allshader/waveformrendererstem.cpp src/qml/qmlstemsmodel.cpp ) endif() @@ -3812,8 +3832,21 @@ endif() # rendergraph add_subdirectory(src/rendergraph/opengl) add_subdirectory(res/shaders/rendergraph) +target_compile_definitions(rendergraph_gl PUBLIC + $<$:MIXXX_DEBUG_ASSERTIONS_ENABLED> +) target_link_libraries(mixxx-lib PUBLIC rendergraph_gl) target_compile_definitions(mixxx-lib PRIVATE rendergraph=rendergraph_gl) +target_compile_definitions(mixxx-lib PRIVATE allshader=allshader_gl) +if(QML) + add_subdirectory(src/rendergraph/scenegraph) + target_compile_definitions(rendergraph_sg PUBLIC + $<$:MIXXX_DEBUG_ASSERTIONS_ENABLED> + ) + target_link_libraries(mixxx-qml-lib PRIVATE rendergraph_sg) + target_compile_definitions(mixxx-qml-lib PRIVATE rendergraph=rendergraph_sg) + target_compile_definitions(mixxx-qml-lib PRIVATE allshader=allshader_sg) +endif() # WavPack audio file support find_package(wavpack) diff --git a/res/qml/WaveformDisplay.qml b/res/qml/WaveformDisplay.qml new file mode 100644 index 00000000000..22209a8719e --- /dev/null +++ b/res/qml/WaveformDisplay.qml @@ -0,0 +1,238 @@ +import "." as Skin +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls +import QtQuick 2.12 +import "Theme" + +Item { + id: root + + required property string group + + enum MouseStatus { + Normal, + Bending, + Scratching + } + + MixxxControls.WaveformDisplay { + anchors.fill: parent + group: root.group + zoom: zoomControl.value + backgroundColor: "#5e000000" + + Mixxx.WaveformRendererEndOfTrack { + color: '#ff8872' + } + + Mixxx.WaveformRendererPreroll { + color: '#ff8872' + } + + Mixxx.WaveformRendererMarkRange { + // + Mixxx.WaveformMarkRange { + startControl: "loop_start_position" + endControl: "loop_end_position" + enabledControl: "loop_enabled" + color: '#00b400' + opacity: 0.7 + disabledColor: '#FFFFFF' + disabledOpacity: 0.6 + } + // + Mixxx.WaveformMarkRange { + startControl: "intro_start_position" + endControl: "intro_end_position" + color: '#2c5c9a' + opacity: 0.6 + durationTextColor: '#ffffff' + durationTextLocation: 'after' + } + // + Mixxx.WaveformMarkRange { + startControl: "outro_start_position" + endControl: "outro_end_position" + color: '#2c5c9a' + opacity: 0.6 + durationTextColor: '#ffffff' + durationTextLocation: 'before' + } + } + + Mixxx.WaveformRendererRGB { + axesColor: '#a1a1a1a1' + lowColor: '#ff2154d7' + midColor: '#cfb26606' + highColor: '#e5029c5c' + } + + Mixxx.WaveformRendererStem { } + + Mixxx.WaveformRendererBeat { + color: '#a1a1a1a1' + } + + Mixxx.WaveformRendererMark { + playMarkerColor: 'cyan' + playMarkerBackground: 'orange' + defaultMark: Mixxx.WaveformMark { + align: "bottom|right" + color: "#00d9ff" + textColor: "#1a1a1a" + text: " %1 " + } + + untilMark.showTime: true + untilMark.showBeats: true + untilMark.align: Mixxx.WaveformUntilMark.AlignBottom + untilMark.textSize: 11 + + Mixxx.WaveformMark { + control: "cue_point" + text: 'CUE' + align: 'top|right' + color: 'red' + textColor: '#1a1a1a' + } + Mixxx.WaveformMark { + control: "loop_start_position" + text: '↻' + align: 'top|left' + color: 'green' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "loop_end_position" + align: 'bottom|right' + color: 'green' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "intro_start_position" + align: 'top|right' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "intro_end_position" + text: '◢' + align: 'top|left' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "outro_start_position" + text: '◣' + align: 'top|right' + color: 'blue' + textColor: '#FFFFFF' + } + Mixxx.WaveformMark { + control: "outro_end_position" + align: 'top|left' + color: 'blue' + textColor: '#FFFFFF' + } + } + } + + Mixxx.ControlProxy { + id: scratchPositionEnableControl + + group: root.group + key: "scratch_position_enable" + } + + Mixxx.ControlProxy { + id: scratchPositionControl + + group: root.group + key: "scratch_position" + } + + Mixxx.ControlProxy { + id: wheelControl + + group: root.group + key: "wheel" + } + + Mixxx.ControlProxy { + id: rateRatioControl + + group: root.group + key: "rate_ratio" + } + + Mixxx.ControlProxy { + id: zoomControl + + group: root.group + key: "waveform_zoom" + } + readonly property real effectiveZoomFactor: (1 / rateRatioControl.value) * (100 / zoomControl.value) + + MouseArea { + property int mouseStatus: WaveformDisplay.MouseStatus.Normal + property point mouseAnchor: Qt.point(0, 0) + + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onPressed: { + mouseAnchor = Qt.point(mouse.x, mouse.y); + if (mouse.button == Qt.LeftButton) { + if (mouseStatus == WaveformDisplay.MouseStatus.Bending) + wheelControl.parameter = 0.5; + + mouseStatus = WaveformDisplay.MouseStatus.Scratching; + scratchPositionEnableControl.value = 1; + // TODO: Calculate position properly + scratchPositionControl.value = -mouse.x * zoomControl.value * 50; + } else { + if (mouseStatus == WaveformDisplay.MouseStatus.Scratching) + scratchPositionEnableControl.value = 0; + + wheelControl.parameter = 0.5; + mouseStatus = WaveformDisplay.MouseStatus.Bending; + } + } + onPositionChanged: { + switch (mouseStatus) { + case WaveformDisplay.MouseStatus.Bending: { + const diff = mouse.x - mouseAnchor.x; + // Start at the middle of [0.0, 1.0], and emit values based on how far + // the mouse has traveled horizontally. Note, for legacy (MIDI) reasons, + // this is tuned to 127. + const v = 0.5 + (diff / root.width); + // clamp to [0.0, 1.0] + wheelControl.parameter = Math.max(Math.min(v, 1), 0); + break; + }; + case WaveformDisplay.MouseStatus.Scratching: + // TODO: Calculate position properly + scratchPositionControl.value = -mouse.x * zoomControl.value * 50; + break; + } + } + onReleased: { + switch (mouseStatus) { + case WaveformDisplay.MouseStatus.Bending: + wheelControl.parameter = 0.5; + break; + case WaveformDisplay.MouseStatus.Scratching: + scratchPositionEnableControl.value = 0; + break; + } + mouseStatus = WaveformDisplay.MouseStatus.Normal; + } + + onWheel: { + if (wheel.angleDelta.y < 0 && zoomControl.value > 1) { + zoomControl.value -= 1; + } else if (wheel.angleDelta.y > 0 && zoomControl.value < 10.0) { + zoomControl.value += 1; + } + } + } +} diff --git a/res/qml/main.qml b/res/qml/main.qml index 15578d303cb..5270fa054fa 100644 --- a/res/qml/main.qml +++ b/res/qml/main.qml @@ -98,7 +98,7 @@ ApplicationWindow { } } - WaveformRow { + Skin.WaveformDisplay { id: deck3waveform group: "[Channel3]" @@ -111,7 +111,7 @@ ApplicationWindow { } } - WaveformRow { + Skin.WaveformDisplay { id: deck1waveform group: "[Channel1]" @@ -124,7 +124,7 @@ ApplicationWindow { } } - WaveformRow { + Skin.WaveformDisplay { id: deck2waveform group: "[Channel2]" @@ -137,7 +137,7 @@ ApplicationWindow { } } - WaveformRow { + Skin.WaveformDisplay { id: deck4waveform group: "[Channel4]" diff --git a/res/shaders/rendergraph/CMakeLists.txt b/res/shaders/rendergraph/CMakeLists.txt index dfcecc593a4..54f113f3fbc 100644 --- a/res/shaders/rendergraph/CMakeLists.txt +++ b/res/shaders/rendergraph/CMakeLists.txt @@ -23,6 +23,18 @@ qt6_add_shaders(rendergraph_sg "shaders-qsb" ${shaders} ) +if(TARGET mixxx) + qt6_add_shaders(mixxx "shaders-qsb" + BATCHABLE + PRECOMPILE + OPTIMIZED + PREFIX + /shaders/rendergraph + FILES + ${shaders} + ) +endif() + if(USE_QSHADER_FOR_GL) message(STATUS "Adding qsb shaders to rendergraph_gl") qt6_add_shaders(rendergraph_gl "shaders-qsb" diff --git a/src/qml/qmlwaveformdisplay.cpp b/src/qml/qmlwaveformdisplay.cpp new file mode 100644 index 00000000000..266f3b516f1 --- /dev/null +++ b/src/qml/qmlwaveformdisplay.cpp @@ -0,0 +1,253 @@ +#include "qml/qmlwaveformdisplay.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mixer/basetrackplayer.h" +#include "moc_qmlwaveformdisplay.cpp" +#include "qml/qmlplayerproxy.h" +#include "rendergraph/context.h" +#include "rendergraph/node.h" +#include "waveform/renderers/allshader/waveformrendermark.h" +#include "waveform/renderers/allshader/waveformrendermarkrange.h" + +using namespace allshader; + +namespace mixxx { +namespace qml { + +QmlWaveformDisplay::QmlWaveformDisplay(QQuickItem* parent) + : QQuickItem(parent), + WaveformWidgetRenderer(), + m_pPlayer(nullptr) { + setFlag(QQuickItem::ItemHasContents, true); + + connect(this, + &QmlWaveformDisplay::windowChanged, + this, + &QmlWaveformDisplay::slotWindowChanged, + Qt::DirectConnection); +} + +QmlWaveformDisplay::~QmlWaveformDisplay() { + // The stack contains references to Renderer that are owned and cleared by a BaseNode + m_rendererStack.clear(); +} + +void QmlWaveformDisplay::componentComplete() { + qDebug() << "QmlWaveformDisplay ready for group" << getGroup() << "with" + << m_waveformRenderers.count() << "renderer(s)"; + QQuickItem::componentComplete(); +} + +void QmlWaveformDisplay::slotWindowChanged(QQuickWindow* window) { + m_rendererStack.clear(); + + m_dirtyFlag |= DirtyFlag::Window; + if (window) { + connect(window, &QQuickWindow::afterFrameEnd, this, &QmlWaveformDisplay::slotFrameSwapped); + } + m_timer.restart(); +} + +int QmlWaveformDisplay::fromTimerToNextSyncMicros(const PerformanceTimer& timer) { + // TODO @m0dB probably better to use a singleton instead of deriving QmlWaveformDisplay from + // ISyncTimeProvider and have each keep track of this. + int difference = static_cast(m_timer.difference(timer).toIntegerMicros()); + // int math is fine here, because we do not expect times > 4.2 s + + return difference + m_syncIntervalTimeMicros; +} + +void QmlWaveformDisplay::slotFrameSwapped() { + m_timer.restart(); + + // continuous redraw + update(); +} + +void QmlWaveformDisplay::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { + m_dirtyFlag |= DirtyFlag::Geometry; + update(); + QQuickItem::geometryChange(newGeometry, oldGeometry); +} + +QSGNode* QmlWaveformDisplay::updatePaintNode(QSGNode* node, UpdatePaintNodeData*) { + auto* bgNode = dynamic_cast(node); + static rendergraph::GeometryNode* pPreRoll; + + if (m_dirtyFlag.testFlag(DirtyFlag::Window)) { + delete bgNode; + auto* pContext = getContext(); + if (pContext) { + delete pContext; + } + m_dirtyFlag.setFlag(DirtyFlag::Window, false); + } + if (!bgNode) { + bgNode = new QSGSimpleRectNode(); + bgNode->setRect(boundingRect()); + + if (getContext()) { + delete getContext(); + } + setContext(new rendergraph::Context(window())); + m_pTopNode = new rendergraph::Node; + + m_rendererStack.clear(); + for (auto* pQmlRenderer : m_waveformRenderers) { + auto renderer = pQmlRenderer->create(this); + if (!renderer.renderer) { + continue; + } + addRenderer(renderer.renderer); + m_pTopNode->appendChildNode(std::unique_ptr(renderer.node)); + auto* pWaveformRenderMark = + dynamic_cast( + renderer.renderer); + if (pWaveformRenderMark) { + m_waveformRenderMark = pWaveformRenderMark; + } + auto* pWaveformRenderMarkRange = + dynamic_cast( + renderer.renderer); + if (pWaveformRenderMarkRange) { + m_waveformRenderMarkRange = pWaveformRenderMarkRange; + } + } + + bgNode->appendChildNode(m_pTopNode); + init(); + } + + if (m_dirtyFlag.testFlag(DirtyFlag::Background)) { + m_dirtyFlag.setFlag(DirtyFlag::Background, false); + bgNode->setColor(m_backgroundColor); + } + + if (m_dirtyFlag.testFlag(DirtyFlag::Geometry)) { + m_dirtyFlag.setFlag(DirtyFlag::Geometry, false); + resizeRenderer(boundingRect().width(), + boundingRect().height(), + window()->devicePixelRatio()); + bgNode->setRect(boundingRect()); + + auto rect = QRectF(boundingRect().x() + + boundingRect().width() * m_playMarkerPosition - 1.0, + boundingRect().y(), + 2.0, + boundingRect().height()); + } + + m_waveformRenderMark->update(); + m_waveformRenderMarkRange->update(); + + onPreRender(this); + bgNode->markDirty(QSGNode::DirtyForceUpdate); + + return bgNode; +} + +QmlPlayerProxy* QmlWaveformDisplay::getPlayer() const { + return m_pPlayer; +} + +void QmlWaveformDisplay::setPlayer(QmlPlayerProxy* pPlayer) { + if (m_pPlayer == pPlayer) { + return; + } + + if (m_pPlayer != nullptr) { + m_pPlayer->internalTrackPlayer()->disconnect(this); + } + + m_pPlayer = pPlayer; + + if (m_pPlayer != nullptr) { + setCurrentTrack(m_pPlayer->internalTrackPlayer()->getLoadedTrack()); + connect(m_pPlayer->internalTrackPlayer(), + &BaseTrackPlayer::newTrackLoaded, + this, + &QmlWaveformDisplay::slotTrackLoaded); + connect(m_pPlayer->internalTrackPlayer(), + &BaseTrackPlayer::loadingTrack, + this, + &QmlWaveformDisplay::slotTrackLoading); + connect(m_pPlayer->internalTrackPlayer(), + &BaseTrackPlayer::playerEmpty, + this, + &QmlWaveformDisplay::slotTrackUnloaded); + } + + emit playerChanged(); + update(); +} + +void QmlWaveformDisplay::setGroup(const QString& group) { + if (getGroup() == group) { + return; + } + + WaveformWidgetRenderer::setGroup(group); + emit groupChanged(group); +} + +void QmlWaveformDisplay::slotTrackLoaded(TrackPointer pTrack) { + // TODO: Investigate if it's a bug that this debug assertion fails when + // passing tracks on the command line + // DEBUG_ASSERT(m_pCurrentTrack == pTrack); + setCurrentTrack(pTrack); +} + +void QmlWaveformDisplay::slotTrackLoading(TrackPointer pNewTrack, TrackPointer pOldTrack) { + Q_UNUSED(pOldTrack); // only used in DEBUG_ASSERT + DEBUG_ASSERT(getTrackInfo() == pOldTrack); + setCurrentTrack(pNewTrack); +} + +void QmlWaveformDisplay::slotTrackUnloaded() { + setCurrentTrack(nullptr); +} + +void QmlWaveformDisplay::setCurrentTrack(TrackPointer pTrack) { + auto pCurrentTrack = getTrackInfo(); + // TODO: Check if this is actually possible + if (pCurrentTrack == pTrack) { + return; + } + + if (pCurrentTrack != nullptr) { + disconnect(pCurrentTrack.get(), nullptr, this, nullptr); + } + + setTrack(pTrack); + if (pTrack != nullptr) { + connect(pTrack.get(), + &Track::waveformSummaryUpdated, + this, + &QmlWaveformDisplay::slotWaveformUpdated); + } + slotWaveformUpdated(); +} + +void QmlWaveformDisplay::slotWaveformUpdated() { + update(); +} + +QQmlListProperty QmlWaveformDisplay::renderers() { + return {this, &m_waveformRenderers}; +} + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlwaveformdisplay.h b/src/qml/qmlwaveformdisplay.h new file mode 100644 index 00000000000..6ab0f526af1 --- /dev/null +++ b/src/qml/qmlwaveformdisplay.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "qml/qmlplayerproxy.h" +#include "qml/qmlwaveformrenderer.h" +#include "track/track.h" +#include "util/performancetimer.h" +#include "waveform/isynctimeprovider.h" +#include "waveform/renderers/waveformwidgetrenderer.h" + +class WaveformRendererAbstract; + +namespace allshader { +class WaveformWidget; +class WaveformRenderMark; +class WaveformRenderMarkRange; +} // namespace allshader +namespace rendergraph { +class Node; +class OpacityNode; +class TreeNode; +} // namespace rendergraph + +namespace mixxx { +namespace qml { + +class QmlPlayerProxy; + +class QmlWaveformDisplay : public QQuickItem, ISyncTimeProvider, public WaveformWidgetRenderer { + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QmlPlayerProxy* player READ getPlayer WRITE setPlayer + NOTIFY playerChanged REQUIRED) + Q_PROPERTY(QString group READ getGroup WRITE setGroup NOTIFY groupChanged REQUIRED) + Q_PROPERTY(QQmlListProperty renderers READ renderers) + Q_PROPERTY(double zoom READ getZoom WRITE setZoom NOTIFY zoomChanged) + Q_PROPERTY(QColor backgroundColor READ getBackgroundColor WRITE + setBackgroundColor NOTIFY backgroundColorChanged) + Q_CLASSINFO("DefaultProperty", "renderers") + QML_NAMED_ELEMENT(WaveformDisplay) + + public: + QmlWaveformDisplay(QQuickItem* parent = nullptr); + ~QmlWaveformDisplay() override; + + void setPlayer(QmlPlayerProxy* player); + QmlPlayerProxy* getPlayer() const; + + QColor getBackgroundColor() const { + return m_backgroundColor; + } + void setBackgroundColor(QColor color) { + m_backgroundColor = color; + m_dirtyFlag.setFlag(DirtyFlag::Background, true); + emit backgroundColorChanged(); + } + + void setGroup(const QString& group) override; + void setZoom(double zoom) { + WaveformWidgetRenderer::setZoom(zoom); + emit zoomChanged(); + } + + int fromTimerToNextSyncMicros(const PerformanceTimer& timer) override; + int getSyncIntervalTimeMicros() const override { + return m_syncIntervalTimeMicros; + } + + virtual void componentComplete() override; + + QQmlListProperty renderers(); + + protected: + QSGNode* updatePaintNode(QSGNode* old, QQuickItem::UpdatePaintNodeData*) override; + void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; + private slots: + void slotTrackLoaded(TrackPointer pLoadedTrack); + void slotTrackLoading(TrackPointer pNewTrack, TrackPointer pOldTrack); + void slotTrackUnloaded(); + void slotWaveformUpdated(); + + void slotFrameSwapped(); + void slotWindowChanged(QQuickWindow* window); + signals: + void playerChanged(); + void zoomChanged(); + void groupChanged(const QString& group); + void backgroundColorChanged(); + + private: + void setCurrentTrack(TrackPointer pTrack); + + // Properties + QPointer m_pPlayer; + QColor m_backgroundColor{QColor(0, 0, 0, 255)}; + + PerformanceTimer m_timer; + + int m_syncIntervalTimeMicros{1000000 / 10}; // TODO don't hardcode + + enum class DirtyFlag : int { + None = 0x0, + Geometry = 0x1, + Window = 0x2, + Background = 0x4, + }; + Q_DECLARE_FLAGS(DirtyFlags, DirtyFlag) + + DirtyFlags m_dirtyFlag{DirtyFlag::None}; + QList m_waveformRenderers; + + // Owned by the QML scene? + rendergraph::Node* m_pTopNode; + allshader::WaveformRenderMark* m_waveformRenderMark; + allshader::WaveformRenderMarkRange* m_waveformRenderMarkRange; +}; + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlwaveformrenderer.cpp b/src/qml/qmlwaveformrenderer.cpp new file mode 100644 index 00000000000..610264ec3d5 --- /dev/null +++ b/src/qml/qmlwaveformrenderer.cpp @@ -0,0 +1,152 @@ +#include "qml/qmlwaveformrenderer.h" + +#include + +#include "moc_qmlwaveformrenderer.cpp" +#include "util/assert.h" +#include "waveform/renderers/allshader/waveformrenderbeat.h" +#include "waveform/renderers/allshader/waveformrendererendoftrack.h" +#include "waveform/renderers/allshader/waveformrendererpreroll.h" +#include "waveform/renderers/allshader/waveformrendererrgb.h" +#ifdef __STEM__ +#include "waveform/renderers/allshader/waveformrendererstem.h" +#endif +#include "waveform/renderers/allshader/waveformrendermark.h" +#include "waveform/renderers/allshader/waveformrendermarkrange.h" + +using namespace allshader; + +namespace mixxx { +namespace qml { + +QmlWaveformRendererEndOfTrack::QmlWaveformRendererEndOfTrack() { +} + +QmlWaveformRendererPreroll::QmlWaveformRendererPreroll() { +} + +QmlWaveformRendererRGB::QmlWaveformRendererRGB() { +} + +QmlWaveformRendererBeat::QmlWaveformRendererBeat() { +} + +QmlWaveformRendererMarkRange::QmlWaveformRendererMarkRange() { +} + +QmlWaveformRendererStem::QmlWaveformRendererStem() { +} + +QmlWaveformRendererMark::QmlWaveformRendererMark() + : m_defaultMark(nullptr), + m_untilMark(std::make_unique()) { +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererEndOfTrack::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRendererEndOfTrack(waveformWidget, m_color); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererPreroll::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRendererPreroll( + waveformWidget, WaveformRendererAbstract::Play, m_color); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererRGB::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRendererRGB(waveformWidget, + m_axesColor, + m_lowColor, + m_midColor, + m_highColor, + ::WaveformRendererAbstract::Play); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererBeat::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRenderBeat( + waveformWidget, ::WaveformRendererAbstract::Play, m_color); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererMarkRange::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRenderMarkRange( + waveformWidget); + + for (auto* pMark : m_ranges) { + renderer->addRange(WaveformMarkRange( + waveformWidget->getGroup(), + pMark->color(), + pMark->disabledColor(), + pMark->opacity(), + pMark->disabledOpacity(), + pMark->durationTextColor(), + pMark->startControl(), + pMark->endControl(), + pMark->enabledControl(), + pMark->visibilityControl(), + pMark->durationTextLocation())); + } + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} + +#ifdef __STEM__ +QmlWaveformRendererFactory::Renderer QmlWaveformRendererStem::create( + WaveformWidgetRenderer* waveformWidget) const { + auto* renderer = new WaveformRendererStem( + waveformWidget, ::WaveformRendererAbstract::Play); + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} +#endif + +QmlWaveformRendererFactory::Renderer QmlWaveformRendererMark::create( + WaveformWidgetRenderer* waveformWidget) const { + VERIFY_OR_DEBUG_ASSERT(!!m_untilMark) { + return QmlWaveformRendererFactory::Renderer{}; + } + auto* renderer = new WaveformRenderMark(waveformWidget, + m_playMarkerColor, + m_playMarkerBackground, + m_untilMark->showTime(), + m_untilMark->showBeats(), + static_cast(m_untilMark->align()), + m_untilMark->textSize(), + ::WaveformRendererAbstract::Play); + int priority = 0; + for (auto* pMark : m_marks) { + renderer->addMark(WaveformMarkPointer(new WaveformMark( + waveformWidget->getGroup(), + pMark->control(), + pMark->visibilityControl(), + pMark->textColor(), + pMark->align(), + pMark->text(), + pMark->pixmap(), + pMark->icon(), + pMark->color(), + --priority))); + } + auto* pMark = defaultMark(); + if (pMark != nullptr) { + renderer->setDefaultMark( + waveformWidget->getGroup(), + WaveformMarkSet::Seed{ + pMark->control(), + pMark->visibilityControl(), + pMark->textColor(), + pMark->align(), + pMark->text(), + pMark->pixmap(), + pMark->icon(), + pMark->color(), + }); + } + return QmlWaveformRendererFactory::Renderer{renderer, renderer}; +} +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlwaveformrenderer.h b/src/qml/qmlwaveformrenderer.h new file mode 100644 index 00000000000..3375f667013 --- /dev/null +++ b/src/qml/qmlwaveformrenderer.h @@ -0,0 +1,486 @@ +#pragma once + +#include +#include + +#include "rendergraph/node.h" +#include "waveform/renderers/waveformrendererabstract.h" +#include "waveform/renderers/waveformwidgetrenderer.h" + +class WaveformWidgetRenderer; + +namespace allshaders { +class WaveformRendererEndOfTrack; +class WaveformRendererPreroll; +class WaveformRendererRGB; +class WaveformRenderBeat; +} // namespace allshaders + +namespace mixxx { +namespace qml { + +class QmlWaveformRendererFactory : public QObject { + Q_OBJECT + QML_ANONYMOUS + public: + struct Renderer { + ::WaveformRendererAbstract* renderer{nullptr}; + rendergraph::BaseNode* node{nullptr}; + }; + + virtual Renderer create(WaveformWidgetRenderer* waveformWidget) const = 0; +}; + +class QmlWaveformRendererEndOfTrack + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererEndOfTrack) + + public: + QmlWaveformRendererEndOfTrack(); + + const QColor& color() const { + return m_color; + } + void setColor(QColor color) { + m_color = color; + emit colorChanged(m_color); + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + + signals: + void colorChanged(const QColor&); + + private: + QColor m_color; +}; + +class QmlWaveformRendererPreroll + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererPreroll) + + public: + QmlWaveformRendererPreroll(); + + const QColor& color() const { + return m_color; + } + void setColor(QColor color) { + m_color = color; + emit colorChanged(m_color); + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + signals: + void colorChanged(const QColor&); + + private: + QColor m_color; +}; + +class QmlWaveformRendererRGB + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QColor axesColor READ axesColor WRITE setAxesColor NOTIFY axesColorChanged REQUIRED) + Q_PROPERTY(QColor lowColor READ lowColor WRITE setLowColor NOTIFY lowColorChanged REQUIRED) + Q_PROPERTY(QColor midColor READ midColor WRITE setMidColor NOTIFY midColorChanged REQUIRED) + Q_PROPERTY(QColor highColor READ highColor WRITE setHighColor NOTIFY highColorChanged REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererRGB) + + public: + QmlWaveformRendererRGB(); + + const QColor& axesColor() const { + return m_axesColor; + } + void setAxesColor(QColor color) { + m_axesColor = color; + emit axesColorChanged(m_axesColor); + } + + const QColor& lowColor() const { + return m_lowColor; + } + void setLowColor(QColor color) { + m_lowColor = color; + emit lowColorChanged(m_lowColor); + } + + const QColor& midColor() const { + return m_lowColor; + } + void setMidColor(QColor color) { + m_midColor = color; + emit midColorChanged(m_lowColor); + } + + const QColor& highColor() const { + return m_lowColor; + } + void setHighColor(QColor color) { + m_highColor = color; + emit highColorChanged(m_lowColor); + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + signals: + void axesColorChanged(const QColor&); + void lowColorChanged(const QColor&); + void midColorChanged(const QColor&); + void highColorChanged(const QColor&); + + private: + QColor m_axesColor; + QColor m_lowColor; + QColor m_midColor; + QColor m_highColor; +}; + +class QmlWaveformRendererBeat + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged REQUIRED) + QML_NAMED_ELEMENT(WaveformRendererBeat) + + public: + QmlWaveformRendererBeat(); + + const QColor& color() const { + return m_color; + } + void setColor(QColor color) { + m_color = color; + emit colorChanged(m_color); + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + signals: + void colorChanged(const QColor&); + + private: + QColor m_color; +}; + +class QmlWaveformMarkRange : public QObject { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor) + Q_PROPERTY(QColor disabledColor READ disabledColor WRITE setDisabledColor) + Q_PROPERTY(double opacity READ opacity WRITE setOpacity) + Q_PROPERTY(double disabledOpacity READ disabledOpacity WRITE setDisabledOpacity) + Q_PROPERTY(QColor durationTextColor READ durationTextColor WRITE setDurationTextColor) + Q_PROPERTY(QString startControl READ startControl WRITE setStartControl) + Q_PROPERTY(QString endControl READ endControl WRITE setEndControl) + Q_PROPERTY(QString enabledControl READ enabledControl WRITE setEnabledControl) + Q_PROPERTY(QString visibilityControl READ visibilityControl WRITE setVisibilityControl) + Q_PROPERTY(QString durationTextLocation READ durationTextLocation WRITE setDurationTextLocation) + QML_NAMED_ELEMENT(WaveformMarkRange) + + public: + QColor color() const { + return m_color; + } + + void setColor(const QColor& value) { + m_color = value; + } + + QColor disabledColor() const { + return m_disabledColor; + } + + void setDisabledColor(const QColor& value) { + m_disabledColor = value; + } + + double opacity() const { + return m_opacity; + } + + void setOpacity(double value) { + m_opacity = value; + } + + double disabledOpacity() const { + return m_disabledOpacity; + } + + void setDisabledOpacity(double value) { + m_disabledOpacity = value; + } + + QColor durationTextColor() const { + return m_durationTextColor; + } + + void setDurationTextColor(const QColor& value) { + m_durationTextColor = value; + } + + QString startControl() const { + return m_startControl; + } + + void setStartControl(const QString& value) { + m_startControl = value; + } + + QString endControl() const { + return m_endControl; + } + + void setEndControl(const QString& value) { + m_endControl = value; + } + + QString enabledControl() const { + return m_enabledControl; + } + + void setEnabledControl(const QString& value) { + m_enabledControl = value; + } + + QString visibilityControl() const { + return m_visibilityControl; + } + + void setVisibilityControl(const QString& value) { + m_visibilityControl = value; + } + + QString durationTextLocation() const { + return m_durationTextLocation; + } + + void setDurationTextLocation(const QString& value) { + m_durationTextLocation = value; + } + + private: + double m_opacity{0.5}; + double m_disabledOpacity{0.5}; + QColor m_color; + QColor m_disabledColor; + QColor m_durationTextColor; + QString m_startControl; + QString m_endControl; + QString m_enabledControl; + QString m_visibilityControl; + QString m_durationTextLocation; +}; + +class QmlWaveformMark : public QObject { + Q_OBJECT + Q_PROPERTY(QString control READ control WRITE setControl) + Q_PROPERTY(QString visibilityControl READ visibilityControl WRITE setVisibilityControl) + Q_PROPERTY(QString color READ color WRITE setColor) + Q_PROPERTY(QString textColor READ textColor WRITE setTextColor) + Q_PROPERTY(QString align READ align WRITE setAlign) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString pixmap READ pixmap WRITE setPixmap) + Q_PROPERTY(QString icon READ icon WRITE setIcon) + QML_NAMED_ELEMENT(WaveformMark) + public: + QString control() const { + return m_control; + } + void setControl(const QString& value) { + m_control = value; + } + QString visibilityControl() const { + return m_visibilityControl; + } + void setVisibilityControl(const QString& value) { + m_visibilityControl = value; + } + QString color() const { + return m_color; + } + void setColor(const QString& value) { + m_color = value; + } + QString textColor() const { + return m_textColor; + } + void setTextColor(const QString& value) { + m_textColor = value; + } + QString align() const { + return m_align; + } + void setAlign(const QString& value) { + m_align = value; + } + QString text() const { + return m_text; + } + void setText(const QString& value) { + m_text = value; + } + QString pixmap() const { + return m_pixmap; + } + void setPixmap(const QString& value) { + m_pixmap = value; + } + QString icon() const { + return m_icon; + } + void setIcon(const QString& value) { + m_icon = value; + } + + private: + QString m_control; + QString m_visibilityControl; + QString m_color; + QString m_textColor; + QString m_align; + QString m_text; + QString m_pixmap; + QString m_icon; +}; + +class QmlWaveformUntilMark : public QObject { + Q_OBJECT + Q_PROPERTY(bool showTime READ showTime WRITE setShowTime) + Q_PROPERTY(bool showBeats READ showBeats WRITE setShowBeats) + Q_PROPERTY(HAlignment align READ align WRITE setAlign) + Q_PROPERTY(int textSize READ textSize WRITE setTextSize) + + QML_NAMED_ELEMENT(WaveformUntilMark) + public: + enum HAlignment { AlignTop = Qt::AlignTop, + AlignCenter = Qt::AlignCenter, + AlignBottom = Qt::AlignBottom }; + Q_ENUM(HAlignment) + + bool showTime() const { + return m_showTime; + } + void setShowTime(bool showTime) { + m_showTime = showTime; + } + bool showBeats() const { + return m_showBeats; + } + void setShowBeats(bool showBeats) { + m_showBeats = showBeats; + } + HAlignment align() const { + return m_align; + } + void setAlign(HAlignment align) { + m_align = align; + } + int textSize() const { + return m_textSize; + } + void setTextSize(int textSize) { + m_textSize = textSize; + } + + private: + bool m_showTime; + bool m_showBeats; + HAlignment m_align; + int m_textSize; +}; + +class QmlWaveformRendererMarkRange + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QQmlListProperty ranges READ ranges) + Q_CLASSINFO("DefaultProperty", "ranges") + QML_NAMED_ELEMENT(WaveformRendererMarkRange) + + public: + QmlWaveformRendererMarkRange(); + + QQmlListProperty ranges() { + return {this, &m_ranges}; + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + + private: + QList m_ranges; +}; + +class QmlWaveformRendererStem + : public QmlWaveformRendererFactory { + Q_OBJECT + QML_NAMED_ELEMENT(WaveformRendererStem) + + public: + QmlWaveformRendererStem(); + +#ifdef __STEM__ + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; +#else + Renderer create(WaveformWidgetRenderer* waveformWidget) const override { + return Renderer{}; + } + +#endif +}; + +class QmlWaveformRendererMark + : public QmlWaveformRendererFactory { + Q_OBJECT + Q_PROPERTY(QQmlListProperty marks READ marks) + Q_PROPERTY(QColor playMarkerColor READ playMarkerColor WRITE setPlayMarkerColor) + Q_PROPERTY(QColor playMarkerBackground READ playMarkerBackground WRITE setPlayMarkerBackground) + Q_PROPERTY(QmlWaveformMark* defaultMark READ defaultMark WRITE setDefaultMark) + Q_PROPERTY(QmlWaveformUntilMark* untilMark READ untilMark FINAL) + Q_CLASSINFO("DefaultProperty", "marks") + QML_NAMED_ELEMENT(WaveformRendererMark) + + public: + QmlWaveformRendererMark(); + + QmlWaveformMark* defaultMark() const { + return m_defaultMark; + } + + QmlWaveformUntilMark* untilMark() const { + return m_untilMark.get(); + } + void setDefaultMark(QmlWaveformMark* defaultMark) { + m_defaultMark = defaultMark; + } + + const QColor& playMarkerColor() const { + return m_playMarkerColor; + } + void setPlayMarkerColor(const QColor& playMarkerColor) { + m_playMarkerColor = playMarkerColor; + } + + const QColor& playMarkerBackground() const { + return m_playMarkerBackground; + } + void setPlayMarkerBackground(const QColor& playMarkerBackground) { + m_playMarkerBackground = playMarkerBackground; + } + + Renderer create(WaveformWidgetRenderer* waveformWidget) const override; + + QQmlListProperty marks() { + return {this, &m_marks}; + } + + private: + QColor m_playMarkerColor; + QColor m_playMarkerBackground; + QList m_marks; + QmlWaveformMark* m_defaultMark; + std::unique_ptr m_untilMark; +}; + +} // namespace qml +} // namespace mixxx diff --git a/src/waveform/renderers/allshader/waveformrenderbeat.cpp b/src/waveform/renderers/allshader/waveformrenderbeat.cpp index aa7de4ecfe5..2be51f23971 100644 --- a/src/waveform/renderers/allshader/waveformrenderbeat.cpp +++ b/src/waveform/renderers/allshader/waveformrenderbeat.cpp @@ -15,9 +15,11 @@ using namespace rendergraph; namespace allshader { WaveformRenderBeat::WaveformRenderBeat(WaveformWidgetRenderer* waveformWidget, - ::WaveformRendererAbstract::PositionSource type) + ::WaveformRendererAbstract::PositionSource type, + QColor color) : ::WaveformRendererAbstract(waveformWidget), - m_isSlipRenderer(type == ::WaveformRendererAbstract::Slip) { + m_isSlipRenderer(type == ::WaveformRendererAbstract::Slip), + m_color(color) { initForRectangles(0); setUsePreprocess(true); } diff --git a/src/waveform/renderers/allshader/waveformrenderbeat.h b/src/waveform/renderers/allshader/waveformrenderbeat.h index f14520d8d39..406dac2188e 100644 --- a/src/waveform/renderers/allshader/waveformrenderbeat.h +++ b/src/waveform/renderers/allshader/waveformrenderbeat.h @@ -20,7 +20,8 @@ class allshader::WaveformRenderBeat final public: explicit WaveformRenderBeat(WaveformWidgetRenderer* waveformWidget, ::WaveformRendererAbstract::PositionSource type = - ::WaveformRendererAbstract::Play); + ::WaveformRendererAbstract::Play, + QColor color = QColor()); // Pure virtual from WaveformRendererAbstract, not used void draw(QPainter* painter, QPaintEvent* event) override final; diff --git a/src/waveform/renderers/allshader/waveformrendererendoftrack.cpp b/src/waveform/renderers/allshader/waveformrendererendoftrack.cpp index 6f4b92aa501..1804c6328e4 100644 --- a/src/waveform/renderers/allshader/waveformrendererendoftrack.cpp +++ b/src/waveform/renderers/allshader/waveformrendererendoftrack.cpp @@ -24,10 +24,11 @@ using namespace rendergraph; namespace allshader { WaveformRendererEndOfTrack::WaveformRendererEndOfTrack( - WaveformWidgetRenderer* waveformWidget) + WaveformWidgetRenderer* waveformWidget, QColor color) : ::WaveformRendererAbstract(waveformWidget), m_pEndOfTrackControl(nullptr), - m_pTimeRemainingControl(nullptr) { + m_pTimeRemainingControl(nullptr), + m_color(color) { initForRectangles(0); setUsePreprocess(true); } @@ -59,6 +60,7 @@ void WaveformRendererEndOfTrack::setup(const QDomNode& node, const SkinContext& } void WaveformRendererEndOfTrack::preprocess() { + m_hasRendered = true; const int elapsed = m_timer.elapsed().toIntegerMillis() % kBlinkingPeriodMillis; const double blinkIntensity = (double)(2 * abs(elapsed - kBlinkingPeriodMillis / 2)) / @@ -102,7 +104,10 @@ void WaveformRendererEndOfTrack::preprocess() { } bool WaveformRendererEndOfTrack::isSubtreeBlocked() const { - return !m_pEndOfTrackControl->toBool(); + // It is vital that this function returns `false` the first time it gets + // called by the rendergraph for obscure reason, or it will crash whenever + // this function start returning false + return m_hasRendered && !m_pEndOfTrackControl->toBool(); } } // namespace allshader diff --git a/src/waveform/renderers/allshader/waveformrendererendoftrack.h b/src/waveform/renderers/allshader/waveformrendererendoftrack.h index 8f6d69f7262..40817fa5b57 100644 --- a/src/waveform/renderers/allshader/waveformrendererendoftrack.h +++ b/src/waveform/renderers/allshader/waveformrendererendoftrack.h @@ -22,7 +22,7 @@ class allshader::WaveformRendererEndOfTrack final public rendergraph::GeometryNode { public: explicit WaveformRendererEndOfTrack( - WaveformWidgetRenderer* waveformWidget); + WaveformWidgetRenderer* waveformWidget, QColor color = QColor()); // Pure virtual from WaveformRendererAbstract, not used void draw(QPainter* painter, QPaintEvent* event) override final; @@ -41,6 +41,7 @@ class allshader::WaveformRendererEndOfTrack final QColor m_color; PerformanceTimer m_timer; + bool m_hasRendered{false}; DISALLOW_COPY_AND_ASSIGN(WaveformRendererEndOfTrack); }; diff --git a/src/waveform/renderers/allshader/waveformrendererpreroll.cpp b/src/waveform/renderers/allshader/waveformrendererpreroll.cpp index 47caa6f3300..a8ca7db4ae6 100644 --- a/src/waveform/renderers/allshader/waveformrendererpreroll.cpp +++ b/src/waveform/renderers/allshader/waveformrendererpreroll.cpp @@ -64,8 +64,10 @@ namespace allshader { WaveformRendererPreroll::WaveformRendererPreroll( WaveformWidgetRenderer* waveformWidget, - ::WaveformRendererAbstract::PositionSource type) + ::WaveformRendererAbstract::PositionSource type, + QColor color) : ::WaveformRendererAbstract(waveformWidget), + m_color(color), m_isSlipRenderer(type == ::WaveformRendererAbstract::Slip) { setGeometry(std::make_unique(PatternMaterial::attributes(), 0)); setMaterial(std::make_unique()); diff --git a/src/waveform/renderers/allshader/waveformrendererpreroll.h b/src/waveform/renderers/allshader/waveformrendererpreroll.h index eb28459ef96..24af49a457d 100644 --- a/src/waveform/renderers/allshader/waveformrendererpreroll.h +++ b/src/waveform/renderers/allshader/waveformrendererpreroll.h @@ -21,7 +21,8 @@ class allshader::WaveformRendererPreroll final explicit WaveformRendererPreroll( WaveformWidgetRenderer* waveformWidget, ::WaveformRendererAbstract::PositionSource type = - ::WaveformRendererAbstract::Play); + ::WaveformRendererAbstract::Play, + QColor color = QColor(200, 25, 20)); ~WaveformRendererPreroll() override; // Pure virtual from WaveformRendererAbstract, not used diff --git a/src/waveform/renderers/allshader/waveformrendererrgb.cpp b/src/waveform/renderers/allshader/waveformrendererrgb.cpp index 6dbc8d1752d..2d0765d368d 100644 --- a/src/waveform/renderers/allshader/waveformrendererrgb.cpp +++ b/src/waveform/renderers/allshader/waveformrendererrgb.cpp @@ -18,11 +18,24 @@ inline float math_pow2(float x) { } // namespace WaveformRendererRGB::WaveformRendererRGB(WaveformWidgetRenderer* waveformWidget, +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + QColor axesColor, + QColor lowColor, + QColor midColor, + QColor highColor, +#endif ::WaveformRendererAbstract::PositionSource type, WaveformRendererSignalBase::Options options) : WaveformRendererSignalBase(waveformWidget), m_isSlipRenderer(type == ::WaveformRendererAbstract::Slip), m_options(options) { +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + getRgbF(axesColor, &m_axesColor_r, &m_axesColor_g, &m_axesColor_b, &m_axesColor_a); + + getRgbF(lowColor, &m_rgbLowColor_r, &m_rgbLowColor_g, &m_rgbLowColor_b); + getRgbF(midColor, &m_rgbMidColor_r, &m_rgbMidColor_g, &m_rgbMidColor_b); + getRgbF(highColor, &m_rgbHighColor_r, &m_rgbHighColor_g, &m_rgbHighColor_b); +#endif initForRectangles(0); setUsePreprocess(true); } diff --git a/src/waveform/renderers/allshader/waveformrendererrgb.h b/src/waveform/renderers/allshader/waveformrendererrgb.h index a829cf3af2f..ed44f3fbfad 100644 --- a/src/waveform/renderers/allshader/waveformrendererrgb.h +++ b/src/waveform/renderers/allshader/waveformrendererrgb.h @@ -2,6 +2,7 @@ #include "rendergraph/geometrynode.h" #include "util/class.h" +#include "util/colorcomponents.h" #include "waveform/renderers/allshader/waveformrenderersignalbase.h" namespace allshader { @@ -13,6 +14,12 @@ class allshader::WaveformRendererRGB final public rendergraph::GeometryNode { public: explicit WaveformRendererRGB(WaveformWidgetRenderer* waveformWidget, +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + QColor axesColor, + QColor lowColor, + QColor midColor, + QColor highColor, +#endif ::WaveformRendererAbstract::PositionSource type = ::WaveformRendererAbstract::Play, WaveformRendererSignalBase::Options options = WaveformRendererSignalBase::Option::None); diff --git a/src/waveform/renderers/allshader/waveformrendermark.cpp b/src/waveform/renderers/allshader/waveformrendermark.cpp index 239efc1cec1..c48306975f3 100644 --- a/src/waveform/renderers/allshader/waveformrendermark.cpp +++ b/src/waveform/renderers/allshader/waveformrendermark.cpp @@ -18,36 +18,87 @@ using namespace rendergraph; -allshader::WaveformMarkNode::WaveformMarkNode(WaveformMark* pOwner, - rendergraph::Context* pContext, - const QImage& image) - : m_pOwner(pOwner) { - initForRectangles(1); - updateTexture(pContext, image); -} +namespace { +// On the use of QPainter: +// +// The renderers in this folder are optimized to use GLSL shaders and refrain +// from using QPainter on the QOpenGLWindow, which causes degredated performance. +// +// This renderer does use QPainter (indirectly, in WaveformMark::generateImage), but +// only to draw on a QImage. This is only done once when needed and the images are +// then used as textures to be drawn with a GLSL shader. + +class WaveformMarkNode : public rendergraph::GeometryNode { + public: + WaveformMark* m_pOwner{}; + + WaveformMarkNode(WaveformMark* pOwner, rendergraph::Context* pContext, const QImage& image) + : m_pOwner(pOwner) { + initForRectangles(1); + updateTexture(pContext, image); + } + void updateTexture(rendergraph::Context* pContext, const QImage& image) { + dynamic_cast(material()) + .setTexture(std::make_unique(pContext, image)); + m_textureWidth = image.width(); + m_textureHeight = image.height(); + } + void update(float x, float y, float devicePixelRatio) { + TexturedVertexUpdater vertexUpdater{ + geometry().vertexDataAs()}; + vertexUpdater.addRectangle({x, y}, + {x + m_textureWidth / devicePixelRatio, + y + m_textureHeight / devicePixelRatio}, + {0.f, 0.f}, + {1.f, 1.f}); + } + float textureWidth() const { + return m_textureWidth; + } + float textureHeight() const { + return m_textureHeight; + } -void allshader::WaveformMarkNode::updateTexture( - rendergraph::Context* pContext, const QImage& image) { - dynamic_cast(material()) - .setTexture(std::make_unique(pContext, image)); - m_textureWidth = image.width(); - m_textureHeight = image.height(); -} -void allshader::WaveformMarkNode::update(float x, float y, float devicePixelRatio) { - TexturedVertexUpdater vertexUpdater{ - geometry().vertexDataAs()}; - vertexUpdater.addRectangle({x, y}, - {x + m_textureWidth / devicePixelRatio, - y + m_textureHeight / devicePixelRatio}, - {0.f, 0.f}, - {1.f, 1.f}); -} -allshader::WaveformMarkNodeGraphics::WaveformMarkNodeGraphics(WaveformMark* pOwner, - rendergraph::Context* pContext, - const QImage& image) - : m_pNode(std::make_unique( - pOwner, pContext, image)) { -} + public: + float m_textureWidth{}; + float m_textureHeight{}; +}; + +class WaveformMarkNodeGraphics : public WaveformMark::Graphics { + public: + WaveformMarkNodeGraphics(WaveformMark* pOwner, + rendergraph::Context* pContext, + const QImage& image) + : m_pNode(std::make_unique( + pOwner, pContext, image)) { + } + void updateTexture(rendergraph::Context* pContext, const QImage& image) { + waveformMarkNode()->updateTexture(pContext, image); + } + void update(float x, float y, float devicePixelRatio) { + waveformMarkNode()->update(x, y, devicePixelRatio); + } + float textureWidth() const { + return waveformMarkNode()->textureWidth(); + } + float textureHeight() const { + return waveformMarkNode()->textureHeight(); + } + void setNode(std::unique_ptr&& pNode) { + m_pNode = std::move(pNode); + } + void moveNodeToChildrenOf(rendergraph::Node* pParent) { + pParent->appendChildNode(std::move(m_pNode)); + } + + private: + WaveformMarkNode* waveformMarkNode() const { + return static_cast(m_pNode.get()); + } + + std::unique_ptr m_pNode; +}; +} // namespace // Both allshader::WaveformRenderMark and the non-GL ::WaveformRenderMark derive // from WaveformRenderMarkBase. The base-class takes care of updating the marks @@ -76,11 +127,27 @@ QString timeSecToString(double timeSec) { allshader::WaveformRenderMark::WaveformRenderMark( WaveformWidgetRenderer* waveformWidget, +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + QColor fgPlayColor, + QColor bgPlayColor, + bool untilMarkShowBeats, + bool untilMarkShowTime, + Qt::Alignment untilMarkAlign, + int untilMarkTextSize, +#endif ::WaveformRendererAbstract::PositionSource type) : ::WaveformRenderMarkBase(waveformWidget, false), m_beatsUntilMark(0), m_timeUntilMark(0.0), m_pTimeRemainingControl(nullptr), +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + m_fgPlayColor(fgPlayColor), + m_bgPlayColor(bgPlayColor), + m_untilMarkShowBeats(untilMarkShowBeats), + m_untilMarkShowTime(untilMarkShowTime), + m_untilMarkAlign(untilMarkAlign), + m_untilMarkTextSize(untilMarkTextSize), +#endif m_isSlipRenderer(type == ::WaveformRendererAbstract::Slip), m_playPosHeight(0.f), m_playPosDevicePixelRatio(0.f) { @@ -329,20 +396,32 @@ void allshader::WaveformRenderMark::update() { {1.f, 1.f}); } +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + if (m_untilMarkShowBeats || m_untilMarkShowTime) +#else if (WaveformWidgetFactory::instance()->getUntilMarkShowBeats() || - WaveformWidgetFactory::instance()->getUntilMarkShowTime()) { + WaveformWidgetFactory::instance()->getUntilMarkShowTime()) +#endif + { updateUntilMark(playPosition, nextMarkPosition); drawUntilMark(currentMarkPoint + 20); } } void allshader::WaveformRenderMark::drawUntilMark(float x) { +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + const bool untilMarkShowBeats = m_untilMarkShowBeats; + const bool untilMarkShowTime = m_untilMarkShowTime; + const auto untilMarkAlign = m_untilMarkAlign; + const auto untilMarkTextPointSize = m_untilMarkTextSize; +#else const bool untilMarkShowBeats = WaveformWidgetFactory::instance()->getUntilMarkShowBeats(); const bool untilMarkShowTime = WaveformWidgetFactory::instance()->getUntilMarkShowTime(); const auto untilMarkAlign = WaveformWidgetFactory::instance()->getUntilMarkAlign(); const auto untilMarkTextPointSize = WaveformWidgetFactory::instance()->getUntilMarkTextPointSize(); +#endif m_pDigitsRenderNode->updateTexture(m_waveformRenderer->getContext(), untilMarkTextPointSize, getMaxHeightForText(), @@ -415,8 +494,13 @@ void allshader::WaveformRenderMark::updatePlayPosMarkTexture(rendergraph::Contex painter.setWorldMatrixEnabled(false); +#ifdef __RENDERGRAPH_IS_OPENGL const QColor fgColor{m_waveformRenderer->getWaveformSignalColors()->getPlayPosColor()}; const QColor bgColor{m_waveformRenderer->getWaveformSignalColors()->getBgColor()}; +#else + const QColor& fgColor = m_fgPlayColor; + const QColor& bgColor = m_bgPlayColor; +#endif // draw dim outlines to increase playpos/waveform contrast painter.setPen(bgColor); diff --git a/src/waveform/renderers/allshader/waveformrendermark.h b/src/waveform/renderers/allshader/waveformrendermark.h index a0bff747f61..2f3e080907f 100644 --- a/src/waveform/renderers/allshader/waveformrendermark.h +++ b/src/waveform/renderers/allshader/waveformrendermark.h @@ -16,14 +16,20 @@ class Context; namespace allshader { class DigitsRenderNode; class WaveformRenderMark; -class WaveformMarkNode; -class WaveformMarkNodeGraphics; -} +} // namespace allshader class allshader::WaveformRenderMark : public ::WaveformRenderMarkBase, public rendergraph::Node { public: explicit WaveformRenderMark(WaveformWidgetRenderer* waveformWidget, +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + QColor fgPlayColor, + QColor bgPlayColor, + bool untilMarkShowBeats, + bool untilMarkShowTime, + Qt::Alignment untilMarkAlign, + int untilMarkTextSize, +#endif ::WaveformRendererAbstract::PositionSource type = ::WaveformRendererAbstract::Play); @@ -71,65 +77,15 @@ class allshader::WaveformRenderMark : public ::WaveformRenderMarkBase, DigitsRenderNode* m_pDigitsRenderNode{}; - DISALLOW_COPY_AND_ASSIGN(WaveformRenderMark); -}; +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + QColor m_fgPlayColor; + QColor m_bgPlayColor; -// On the use of QPainter: -// -// The renderers in this folder are optimized to use GLSL shaders and refrain -// from using QPainter on the QOpenGLWindow, which causes degredated performance. -// -// This renderer does use QPainter (indirectly, in WaveformMark::generateImage), but -// only to draw on a QImage. This is only done once when needed and the images are -// then used as textures to be drawn with a GLSL shader. + bool m_untilMarkShowBeats; + bool m_untilMarkShowTime; + Qt::Alignment m_untilMarkAlign; + int m_untilMarkTextSize; +#endif -class allshader::WaveformMarkNode : public rendergraph::GeometryNode { - public: - WaveformMark* m_pOwner{}; - - WaveformMarkNode(WaveformMark* pOwner, rendergraph::Context* pContext, const QImage& image); - void updateTexture(rendergraph::Context* pContext, const QImage& image); - void update(float x, float y, float devicePixelRatio); - float textureWidth() const { - return m_textureWidth; - } - float textureHeight() const { - return m_textureHeight; - } - - public: - float m_textureWidth{}; - float m_textureHeight{}; -}; - -class allshader::WaveformMarkNodeGraphics : public ::WaveformMark::Graphics { - public: - WaveformMarkNodeGraphics(WaveformMark* pOwner, - rendergraph::Context* pContext, - const QImage& image); - void updateTexture(rendergraph::Context* pContext, const QImage& image) { - waveformMarkNode()->updateTexture(pContext, image); - } - void update(float x, float y, float devicePixelRatio) { - waveformMarkNode()->update(x, y, devicePixelRatio); - } - float textureWidth() const { - return waveformMarkNode()->textureWidth(); - } - float textureHeight() const { - return waveformMarkNode()->textureHeight(); - } - void setNode(std::unique_ptr&& pNode) { - m_pNode = std::move(pNode); - } - void moveNodeToChildrenOf(rendergraph::Node* pParent) { - pParent->appendChildNode(std::move(m_pNode)); - } - - private: - WaveformMarkNode* waveformMarkNode() const { - return static_cast(m_pNode.get()); - } - - std::unique_ptr m_pNode; + DISALLOW_COPY_AND_ASSIGN(WaveformRenderMark); }; diff --git a/src/waveform/renderers/allshader/waveformrendermarkrange.cpp b/src/waveform/renderers/allshader/waveformrendermarkrange.cpp index 6efb9a1410b..1de0c3c88af 100644 --- a/src/waveform/renderers/allshader/waveformrendermarkrange.cpp +++ b/src/waveform/renderers/allshader/waveformrendermarkrange.cpp @@ -21,12 +21,11 @@ void WaveformRenderMarkRange::setup(const QDomNode& node, const SkinContext& ski QDomNode child = node.firstChild(); while (!child.isNull()) { if (child.nodeName() == "MarkRange") { - m_markRanges.push_back( - WaveformMarkRange( - m_waveformRenderer->getGroup(), - child, - skinContext, - *m_waveformRenderer->getWaveformSignalColors())); + addRange(WaveformMarkRange( + m_waveformRenderer->getGroup(), + child, + skinContext, + *m_waveformRenderer->getWaveformSignalColors())); } child = child.nextSibling(); } diff --git a/src/waveform/renderers/allshader/waveformrendermarkrange.h b/src/waveform/renderers/allshader/waveformrendermarkrange.h index cabab1bb887..3357d631985 100644 --- a/src/waveform/renderers/allshader/waveformrendermarkrange.h +++ b/src/waveform/renderers/allshader/waveformrendermarkrange.h @@ -2,7 +2,6 @@ #include #include -#include #include "rendergraph/node.h" #include "util/class.h" @@ -14,17 +13,24 @@ class SkinContext; namespace rendergraph { class GeometryNode; -} +} // namespace rendergraph namespace allshader { class WaveformRenderMarkRange; -} +} // namespace allshader class allshader::WaveformRenderMarkRange final : public ::WaveformRendererAbstract, public rendergraph::Node { public: explicit WaveformRenderMarkRange(WaveformWidgetRenderer* waveformWidget); + void clearRanges() { + m_markRanges.clear(); + } + void addRange(WaveformMarkRange&& range) { + m_markRanges.push_back(std::move(range)); + } + // Pure virtual from WaveformRendererAbstract, not used void draw(QPainter* painter, QPaintEvent* event) override final; diff --git a/src/waveform/renderers/waveformmark.cpp b/src/waveform/renderers/waveformmark.cpp index 809e633e551..5a21bdd2439 100644 --- a/src/waveform/renderers/waveformmark.cpp +++ b/src/waveform/renderers/waveformmark.cpp @@ -93,6 +93,75 @@ bool isShowUntilNextPositionControl(const QString& positionControl) { } // anonymous namespace +WaveformMark::WaveformMark(const QString& group, + QString positionControl, + QString visibilityControl, + QString textColor, + QString markAlign, + QString text, + QString pixmapPath, + QString iconPath, + QColor color, + int priority, + int hotCue, + const WaveformSignalColors& signalColors) + : m_textColor(textColor), + m_pixmapPath(pixmapPath), + m_iconPath(iconPath), + m_linePosition{}, + m_breadth{}, + m_level{}, + m_iPriority(priority), + m_iHotCue(hotCue), + m_showUntilNext{} { + QString endPositionControl; + QString typeControl; + if (hotCue != Cue::kNoHotCue) { + positionControl = "hotcue_" + QString::number(hotCue + 1) + "_position"; + endPositionControl = "hotcue_" + QString::number(hotCue + 1) + "_endposition"; + typeControl = "hotcue_" + QString::number(hotCue + 1) + "_type"; + m_showUntilNext = true; + } else { + m_showUntilNext = isShowUntilNextPositionControl(positionControl); + } + + if (!positionControl.isEmpty()) { + m_pPositionCO = std::make_unique(group, positionControl); + } + if (!endPositionControl.isEmpty()) { + m_pEndPositionCO = std::make_unique(group, endPositionControl); + m_pTypeCO = std::make_unique(group, typeControl); + } + + if (!visibilityControl.isEmpty()) { + ConfigKey key = ConfigKey::parseCommaSeparated(visibilityControl); + m_pVisibleCO = std::make_unique(key); + } + + if (!color.isValid()) { + // As a fallback, grab the color from the parent's AxesColor + // color = signalColors.getAxesColor(); + qDebug() << "Didn't get mark :" << color; + } else { + color = WSkinColor::getCorrectColor(color); + } + int dimBrightThreshold = signalColors.getDimBrightThreshold(); + setBaseColor(color, dimBrightThreshold); + + if (!m_textColor.isValid()) { + // Read the text color, otherwise use the parent's BgColor. + m_textColor = signalColors.getBgColor(); + qDebug() << "Didn't get mark , using parent's :" << m_textColor; + } + + m_align = decodeAlignmentFlags(markAlign, Qt::AlignBottom | Qt::AlignHCenter); + + // Hotcue text is set by the cue's label in the database, not by the skin. + if (hotCue == Cue::kNoHotCue) { + m_text = text; + } +} + WaveformMark::WaveformMark(const QString& group, const QDomNode& node, const SkinContext& context, diff --git a/src/waveform/renderers/waveformmark.h b/src/waveform/renderers/waveformmark.h index 66a9bbc40cf..df60340128d 100644 --- a/src/waveform/renderers/waveformmark.h +++ b/src/waveform/renderers/waveformmark.h @@ -5,10 +5,10 @@ #include "control/controlproxy.h" #include "track/cue.h" +#include "waveform/renderers/waveformsignalcolors.h" #include "waveform/waveformmarklabel.h" class SkinContext; -class WaveformSignalColors; class QOpenGLTexture; namespace allshader { @@ -31,6 +31,20 @@ class WaveformMark { int priority, const WaveformSignalColors& signalColors, int hotCue = Cue::kNoHotCue); + + WaveformMark( + const QString& group, + QString positionControl, + QString visibilityControl, + QString textColor, + QString markAlign, + QString text, + QString pixmapPath, + QString iconPath, + QColor color, + int priority, + int hotCue = Cue::kNoHotCue, + const WaveformSignalColors& signalColors = {}); ~WaveformMark(); // Disable copying diff --git a/src/waveform/renderers/waveformmarkrange.cpp b/src/waveform/renderers/waveformmarkrange.cpp index 1df2c6c7979..4cc3d07a4ec 100644 --- a/src/waveform/renderers/waveformmarkrange.cpp +++ b/src/waveform/renderers/waveformmarkrange.cpp @@ -75,6 +75,66 @@ WaveformMarkRange::WaveformMarkRange( } } +WaveformMarkRange::WaveformMarkRange( + const QString& group, + const QColor& activeColor, + const QColor& disabledColor, + double enabledOpacity, + double disabledOpacity, + const QColor& durationTextColor, + const QString& startControl, + const QString& endControl, + const QString& enabledControl, + const QString& visibilityControl, + const QString& durationTextLocation) + : m_activeColor(activeColor), + m_disabledColor(disabledColor), + m_enabledOpacity(enabledOpacity), + m_disabledOpacity(disabledOpacity), + m_durationTextColor(durationTextColor) { + if (!startControl.isEmpty()) { + DEBUG_ASSERT(!m_markStartPointControl); // has not been created yet + m_markStartPointControl = std::make_unique(group, startControl); + } + if (!endControl.isEmpty()) { + DEBUG_ASSERT(!m_markEndPointControl); // has not been created yet + m_markEndPointControl = std::make_unique(group, endControl); + } + + if (!enabledControl.isEmpty()) { + DEBUG_ASSERT(!m_markEnabledControl); // has not been created yet + m_markEnabledControl = std::make_unique(group, enabledControl); + } + if (!visibilityControl.isEmpty()) { + DEBUG_ASSERT(!m_markVisibleControl); // has not been created yet + ConfigKey key = ConfigKey::parseCommaSeparated(visibilityControl); + m_markVisibleControl = std::make_unique(key); + } + + if (durationTextLocation == "before") { + m_durationTextLocation = DurationTextLocation::Before; + } else { + m_durationTextLocation = DurationTextLocation::After; + } + + m_activeColor = WSkinColor::getCorrectColor(m_activeColor); + + if (!m_disabledColor.isValid()) { + if (enabledControl.isEmpty()) { + m_disabledColor = QColor(Qt::transparent); + } else { + // Show warning only when there's no EnabledControl, + // like for intro & outro ranges. + QString rangeSuffix = QStringLiteral("_start_position"); + QString rangeName = QString(startControl).remove(rangeSuffix); + int gray = qGray(m_activeColor.rgb()); + m_disabledColor = QColor(gray, gray, gray); + qDebug() << "Didn't get DisabledColor for mark range" << rangeName + << "- using desaturated Color:" << m_disabledColor; + } + } +} + bool WaveformMarkRange::active() const { const double startValue = start(); const double endValue = end(); diff --git a/src/waveform/renderers/waveformmarkrange.h b/src/waveform/renderers/waveformmarkrange.h index 15100bf8f03..63c0fe42f53 100644 --- a/src/waveform/renderers/waveformmarkrange.h +++ b/src/waveform/renderers/waveformmarkrange.h @@ -24,6 +24,18 @@ class WaveformMarkRange { const QDomNode& node, const SkinContext& context, const WaveformSignalColors& signalColors); + WaveformMarkRange( + const QString& group, + const QColor& activeColor, + const QColor& disabledColor, + double enabledOpacity, + double disabledOpacity, + const QColor& durationTextColor, + const QString& startControl, + const QString& endControl, + const QString& enabledControl, + const QString& visibilityControl, + const QString& durationTextLocation); // This class is only moveable, but not copiable! WaveformMarkRange(WaveformMarkRange&&) = default; WaveformMarkRange(const WaveformMarkRange&) = delete; @@ -56,7 +68,7 @@ class WaveformMarkRange { WaveformMarkLabel m_durationLabel; private: - void generateImage(int weidth, int height); + void generateImage(int width, int height); std::unique_ptr m_markStartPointControl; std::unique_ptr m_markEndPointControl; diff --git a/src/waveform/renderers/waveformmarkset.cpp b/src/waveform/renderers/waveformmarkset.cpp index 8fe24730146..0fa78e5e1e4 100644 --- a/src/waveform/renderers/waveformmarkset.cpp +++ b/src/waveform/renderers/waveformmarkset.cpp @@ -22,14 +22,21 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, bool hasDefaultMark = false; QDomNode child = node.firstChild(); - QDomNode defaultChild; + Seed defaultModel; int priority = 0; while (!child.isNull()) { if (child.nodeName() == "DefaultMark") { - m_pDefaultMark = WaveformMarkPointer(new WaveformMark( - group, child, context, --priority, signalColors)); + defaultModel = Seed{ + context.selectString(node, "Control"), + context.selectString(node, "VisibilityControl"), + context.selectString(node, "TextColor"), + context.selectString(node, "Align"), + context.selectString(node, "Text"), + context.selectString(node, "Pixmap"), + context.selectString(node, "Icon"), + context.selectString(node, "Color"), + }; hasDefaultMark = true; - defaultChild = child; } else if (child.nodeName() == "Mark") { WaveformMarkPointer pMark(new WaveformMark( group, child, context, --priority, signalColors)); @@ -39,7 +46,7 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, if (!controlItemSet.insert(item).second) { qWarning() << "WaveformRenderMark::setup - redefinition of" << item; } else { - m_marks.push_back(pMark); + addMark(pMark); if (pMark->getHotCue() >= 0) { m_hotCueMarks.insert(pMark->getHotCue(), pMark); } @@ -52,14 +59,46 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, // check if there is a default mark and compare declared // and to create all missing hot_cues if (hasDefaultMark) { - 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, i, signalColors, i)); - m_marks.push_front(pMark); - m_hotCueMarks.insert(pMark->getHotCue(), pMark); - } + setDefault(group, defaultModel, signalColors); + } +} + +void WaveformMarkSet::setDefault(const QString& group, + const Seed& model, + const WaveformSignalColors& signalColors) { + m_pDefaultMark = WaveformMarkPointer(new WaveformMark( + + group, + model.positionControl, + model.visibilityControl, + model.textColor, + model.markAlign, + model.text, + model.pixmapPath, + model.iconPath, + model.color, + 0, + Cue::kNoHotCue, + signalColors)); + 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, + model.positionControl, + model.visibilityControl, + model.textColor, + model.markAlign, + model.text, + model.pixmapPath, + model.iconPath, + model.color, + i, + i, + signalColors)); + m_marks.push_front(pMark); + m_hotCueMarks.insert(pMark->getHotCue(), pMark); } } } diff --git a/src/waveform/renderers/waveformmarkset.h b/src/waveform/renderers/waveformmarkset.h index 2ab37568f2f..d6e5b0d158b 100644 --- a/src/waveform/renderers/waveformmarkset.h +++ b/src/waveform/renderers/waveformmarkset.h @@ -11,6 +11,17 @@ // rendered. class WaveformMarkSet { public: + struct Seed { + QString positionControl; + QString visibilityControl; + QString textColor; + QString markAlign; + QString text; + QString pixmapPath; + QString iconPath; + QColor color; + }; + WaveformMarkSet(); virtual ~WaveformMarkSet(); @@ -67,11 +78,20 @@ class WaveformMarkSet { void setBreadth(float breadth); - private: void clear() { m_marks.clear(); m_marksToRender.clear(); } + + void addMark(WaveformMarkPointer pMark) { + m_marks.push_back(pMark); + } + + void setDefault(const QString& group, + const Seed& model, + const WaveformSignalColors& signalColors = {}); + + private: WaveformMarkPointer m_pDefaultMark; QList m_marks; // List of visible WaveformMarks sorted by the order they appear in the track diff --git a/src/waveform/renderers/waveformrendererabstract.h b/src/waveform/renderers/waveformrendererabstract.h index 62ab0a3930e..9d4f84a629e 100644 --- a/src/waveform/renderers/waveformrendererabstract.h +++ b/src/waveform/renderers/waveformrendererabstract.h @@ -2,12 +2,14 @@ #include -#include "rendergraph/node.h" - QT_FORWARD_DECLARE_CLASS(QDomNode) QT_FORWARD_DECLARE_CLASS(QPaintEvent) QT_FORWARD_DECLARE_CLASS(QPainter) +namespace rendergraph { +class Node; +} + class SkinContext; class WaveformWidgetRenderer; diff --git a/src/waveform/renderers/waveformrendermarkbase.cpp b/src/waveform/renderers/waveformrendermarkbase.cpp index f250b73acc1..d14bd386bd0 100644 --- a/src/waveform/renderers/waveformrendermarkbase.cpp +++ b/src/waveform/renderers/waveformrendermarkbase.cpp @@ -9,14 +9,21 @@ WaveformRenderMarkBase::WaveformRenderMarkBase( bool updateImagesImmediately) : WaveformRendererAbstract(pWaveformWidgetRenderer), m_updateImagesImmediately(updateImagesImmediately) { +#ifdef __RENDERGRAPH_IS_SCENEGRAPH + m_marks.connectSamplePositionChanged(this, &WaveformRenderMarkBase::onMarkChanged); + m_marks.connectSampleEndPositionChanged(this, &WaveformRenderMarkBase::onMarkChanged); + m_marks.connectVisibleChanged(this, &WaveformRenderMarkBase::onMarkChanged); +#endif } void WaveformRenderMarkBase::setup(const QDomNode& node, const SkinContext& context) { WaveformSignalColors signalColors = *m_waveformRenderer->getWaveformSignalColors(); m_marks.setup(m_waveformRenderer->getGroup(), node, context, signalColors); +#ifdef __RENDERGRAPH_IS_OPENGL m_marks.connectSamplePositionChanged(this, &WaveformRenderMarkBase::onMarkChanged); m_marks.connectSampleEndPositionChanged(this, &WaveformRenderMarkBase::onMarkChanged); m_marks.connectVisibleChanged(this, &WaveformRenderMarkBase::onMarkChanged); +#endif } void WaveformRenderMarkBase::onSetTrack() { diff --git a/src/waveform/renderers/waveformrendermarkbase.h b/src/waveform/renderers/waveformrendermarkbase.h index b5e4d51dd25..989d9ba3944 100644 --- a/src/waveform/renderers/waveformrendermarkbase.h +++ b/src/waveform/renderers/waveformrendermarkbase.h @@ -22,6 +22,18 @@ class WaveformRenderMarkBase : public QObject, public WaveformRendererAbstract { void onResize() override; + void clearMarks() { + m_marks.clear(); + } + + void setDefaultMark(const QString& group, const WaveformMarkSet::Seed& model) { + m_marks.setDefault(group, model); + } + + void addMark(WaveformMarkPointer pMark) { + m_marks.addMark(pMark); + } + public slots: // Called when the loaded track's cues are added, deleted or modified and // when a new track is loaded. diff --git a/src/waveform/renderers/waveformwidgetrenderer.cpp b/src/waveform/renderers/waveformwidgetrenderer.cpp index ffefe5d6d45..4602a34e1ec 100644 --- a/src/waveform/renderers/waveformwidgetrenderer.cpp +++ b/src/waveform/renderers/waveformwidgetrenderer.cpp @@ -5,9 +5,12 @@ #include "control/controlproxy.h" #include "track/track.h" +#include "util/assert.h" #include "util/math.h" +#include "waveform/isynctimeprovider.h" #include "waveform/renderers/waveformrendererabstract.h" #include "waveform/visualplayposition.h" +#include "waveform/vsyncthread.h" #include "waveform/waveform.h" const double WaveformWidgetRenderer::s_waveformMinZoom = 1.0; @@ -37,10 +40,11 @@ WaveformWidgetRenderer::WaveformWidgetRenderer(const QString& group) m_visualPlayPosition(nullptr), m_totalVSamples(0), m_pRateRatioCO(nullptr), + m_pContext(nullptr), m_pGainControlObject(nullptr), m_gain(1.0), m_pTrackSamplesControlObject(nullptr), - m_trackSamples(0), + m_trackSamples(0.0), m_scaleFactor(1.0), m_playMarkerPosition(s_defaultPlayMarkerPosition), m_passthroughEnabled(false) { @@ -76,25 +80,41 @@ WaveformWidgetRenderer::~WaveformWidgetRenderer() { delete m_rendererStack[i]; } - delete m_pRateRatioCO; - delete m_pGainControlObject; - delete m_pTrackSamplesControlObject; - #ifdef WAVEFORMWIDGETRENDERER_DEBUG delete m_timer; #endif } bool WaveformWidgetRenderer::init() { - //qDebug() << "WaveformWidgetRenderer::init, m_group=" << m_group; + m_trackPixelCount = 0.0; + m_zoomFactor = 1.0; + m_visualSamplePerPixel = 1.0; + m_audioSamplePerPixel = 1.0; + m_totalVSamples = 0; + m_gain = 1.0; + m_trackSamples = 0.0; + + for (int type = ::WaveformRendererAbstract::Play; + type <= ::WaveformRendererAbstract::Slip; + type++) { + m_firstDisplayedPosition[type] = 0.0; + m_lastDisplayedPosition[type] = 0.0; + m_posVSample[type] = 0.0; + m_pos[type] = -1.0; // disable renderers + m_truePosSample[type] = -1.0; + } + + VERIFY_OR_DEBUG_ASSERT(!m_group.isEmpty()) { + return false; + } m_visualPlayPosition = VisualPlayPosition::getVisualPlayPosition(m_group); - m_pRateRatioCO = new ControlProxy( + m_pRateRatioCO = std::make_unique( m_group, "rate_ratio"); - m_pGainControlObject = new ControlProxy( + m_pGainControlObject = std::make_unique( m_group, "total_gain"); - m_pTrackSamplesControlObject = new ControlProxy( + m_pTrackSamplesControlObject = std::make_unique( m_group, "track_samples"); for (int i = 0; i < m_rendererStack.size(); ++i) { @@ -105,7 +125,7 @@ bool WaveformWidgetRenderer::init() { return true; } -void WaveformWidgetRenderer::onPreRender(VSyncThread* vsyncThread) { +void WaveformWidgetRenderer::onPreRender(ISyncTimeProvider* vsyncThread) { if (m_passthroughEnabled) { // disables renderers in draw() for (int type = ::WaveformRendererAbstract::Play; @@ -419,7 +439,7 @@ void WaveformWidgetRenderer::setDisplayBeatGridAlpha(int alpha) { void WaveformWidgetRenderer::setTrack(TrackPointer track) { m_pTrack = track; //used to postpone first display until track sample is actually available - m_trackSamples = -1; + m_trackSamples = -1.0; for (int i = 0; i < m_rendererStack.size(); ++i) { m_rendererStack[i]->onSetTrack(); diff --git a/src/waveform/renderers/waveformwidgetrenderer.h b/src/waveform/renderers/waveformwidgetrenderer.h index 3f61921d4e0..b59e5516f7b 100644 --- a/src/waveform/renderers/waveformwidgetrenderer.h +++ b/src/waveform/renderers/waveformwidgetrenderer.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "track/track_decl.h" #include "util/class.h" #include "waveform/renderers/waveformmark.h" @@ -11,7 +13,7 @@ class ControlProxy; class VisualPlayPosition; -class VSyncThread; +class ISyncTimeProvider; class QPainter; class WaveformRendererAbstract; @@ -32,20 +34,25 @@ class WaveformWidgetRenderer { }; public: - explicit WaveformWidgetRenderer(const QString& group); + explicit WaveformWidgetRenderer(const QString& group = {}); virtual ~WaveformWidgetRenderer(); bool init(); virtual bool onInit() {return true;} void setup(const QDomNode& node, const SkinContext& context); - void onPreRender(VSyncThread* vsyncThread); + void onPreRender(ISyncTimeProvider* vsyncThread); void draw(QPainter* painter, QPaintEvent* event); const QString& getGroup() const { return m_group; } + virtual void setGroup(const QString& group) { + m_group = group; + init(); + } + const TrackPointer& getTrackInfo() const { return m_pTrack; } @@ -113,7 +120,7 @@ class WaveformWidgetRenderer { int getTotalVSample() const { return m_totalVSamples; } - double getZoomFactor() const { + double getZoom() const { return m_zoomFactor; } double getGain(bool applyCompensation) const { @@ -174,6 +181,10 @@ class WaveformWidgetRenderer { return renderer; } + void addRenderer(WaveformRendererAbstract* renderer) { + m_rendererStack.push_back(renderer); + } + void setTrack(TrackPointer track); void setMarkPositions(const QList& markPositions) { m_markPositions = markPositions; @@ -205,7 +216,7 @@ class WaveformWidgetRenderer { } protected: - const QString m_group; + QString m_group; TrackPointer m_pTrack; QList m_rendererStack; Qt::Orientation m_orientation; @@ -231,15 +242,15 @@ class WaveformWidgetRenderer { QSharedPointer m_visualPlayPosition; int m_posVSample[2]; int m_totalVSamples; - ControlProxy* m_pRateRatioCO; - ControlProxy* m_pGainControlObject; + std::unique_ptr m_pRateRatioCO; + std::unique_ptr m_pGainControlObject; + std::unique_ptr m_pTrackSamplesControlObject; double m_gain; - ControlProxy* m_pTrackSamplesControlObject; double m_trackSamples; double m_scaleFactor; double m_playMarkerPosition; // 0.0 - left, 0.5 - center, 1.0 - right - rendergraph::Context* m_pContext; + rendergraph::Context* m_pContext{nullptr}; #ifdef WAVEFORMWIDGETRENDERER_DEBUG PerformanceTimer* m_timer; diff --git a/src/waveform/waveformwidgetfactory.cpp b/src/waveform/waveformwidgetfactory.cpp index a9f84959ef7..1cd172a94db 100644 --- a/src/waveform/waveformwidgetfactory.cpp +++ b/src/waveform/waveformwidgetfactory.cpp @@ -605,7 +605,7 @@ bool WaveformWidgetFactory::setWidgetTypeFromHandle(int handleIndex, bool force) WaveformWidgetAbstract* previousWidget = holder.m_waveformWidget; TrackPointer pTrack = previousWidget->getTrackInfo(); //previousWidget->hold(); - double previousZoom = previousWidget->getZoomFactor(); + double previousZoom = previousWidget->getZoom(); double previousPlayMarkerPosition = previousWidget->getPlayMarkerPosition(); int previousbeatgridAlpha = previousWidget->getBeatGridAlpha(); delete previousWidget; @@ -652,7 +652,7 @@ void WaveformWidgetFactory::setZoomSync(bool sync) { return; } - double refZoom = m_waveformWidgetHolders[0].m_waveformWidget->getZoomFactor(); + double refZoom = m_waveformWidgetHolders[0].m_waveformWidget->getZoom(); for (const auto& holder : std::as_const(m_waveformWidgetHolders)) { holder.m_waveformViewer->setZoom(refZoom); } @@ -707,7 +707,7 @@ void WaveformWidgetFactory::notifyZoomChange(WWaveformViewer* viewer) { if (pWaveformWidget == nullptr || !isZoomSync()) { return; } - double refZoom = pWaveformWidget->getZoomFactor(); + double refZoom = pWaveformWidget->getZoom(); for (const auto& holder : std::as_const(m_waveformWidgetHolders)) { if (holder.m_waveformViewer != viewer) { @@ -1181,12 +1181,14 @@ void WaveformWidgetFactory::startVSync(GuiTick* pGuiTick, VisualsManager* pVisua #ifdef MIXXX_USE_QOPENGL if (m_vsyncThread->vsyncMode() == VSyncThread::ST_PLL) { WGLWidget* widget = SharedGLContext::getWidget(); - connect(widget->getOpenGLWindow(), - &QOpenGLWindow::frameSwapped, - this, - &WaveformWidgetFactory::slotFrameSwapped, - Qt::DirectConnection); - widget->show(); + if (widget) { + connect(widget->getOpenGLWindow(), + &QOpenGLWindow::frameSwapped, + this, + &WaveformWidgetFactory::slotFrameSwapped, + Qt::DirectConnection); + widget->show(); + } } #endif diff --git a/src/waveform/widgets/waveformwidgetabstract.cpp b/src/waveform/widgets/waveformwidgetabstract.cpp index d678d4635f4..5b83ca58790 100644 --- a/src/waveform/widgets/waveformwidgetabstract.cpp +++ b/src/waveform/widgets/waveformwidgetabstract.cpp @@ -3,6 +3,7 @@ #include #include "waveform/renderers/waveformwidgetrenderer.h" +#include "waveform/vsyncthread.h" WaveformWidgetAbstract::WaveformWidgetAbstract(const QString& group) : WaveformWidgetRenderer(group), diff --git a/src/widget/wspinnybase.cpp b/src/widget/wspinnybase.cpp index a0baf58f70e..14414fecf32 100644 --- a/src/widget/wspinnybase.cpp +++ b/src/widget/wspinnybase.cpp @@ -15,6 +15,7 @@ #include "util/fpclassify.h" #include "vinylcontrol/vinylcontrolmanager.h" #include "waveform/visualplayposition.h" +#include "waveform/vsyncthread.h" #include "wimagestore.h" // The SampleBuffers format enables antialiasing. diff --git a/src/widget/wwaveformviewer.cpp b/src/widget/wwaveformviewer.cpp index 22629abc884..f40582ced38 100644 --- a/src/widget/wwaveformviewer.cpp +++ b/src/widget/wwaveformviewer.cpp @@ -195,9 +195,9 @@ void WWaveformViewer::mouseReleaseEvent(QMouseEvent* /*event*/) { void WWaveformViewer::wheelEvent(QWheelEvent* event) { if (m_waveformWidget) { if (event->angleDelta().y() > 0) { - onZoomChange(m_waveformWidget->getZoomFactor() / 1.05); + onZoomChange(m_waveformWidget->getZoom() / 1.05); } else if (event->angleDelta().y() < 0) { - onZoomChange(m_waveformWidget->getZoomFactor() * 1.05); + onZoomChange(m_waveformWidget->getZoom() * 1.05); } } }