From ba6b14863c343df2a6bf9c0c5e3a5a9cd3e53b01 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 14 Jul 2012 11:53:19 +0200 Subject: [PATCH] some more work on the station qml --- data/qml/CoverImage.qml | 30 ++- data/qml/StationScene.qml | 194 ++++++++++++++++-- src/libtomahawk/CMakeLists.txt | 2 + .../dynamic/widgets/DynamicQmlWidget.cpp | 69 ++++--- .../dynamic/widgets/DynamicQmlWidget.h | 51 +---- 5 files changed, 240 insertions(+), 106 deletions(-) diff --git a/data/qml/CoverImage.qml b/data/qml/CoverImage.qml index 442d835cb..b1fc9d8a2 100644 --- a/data/qml/CoverImage.qml +++ b/data/qml/CoverImage.qml @@ -20,12 +20,17 @@ Item { property double itemBrightness: 1 property double mirrorBrightness: .5 + // will be emitted when the on hower play button is clicked + signal playClicked() MouseArea { + id: mouseArea anchors.fill: parent - onClicked: print("cover clicked") - } + hoverEnabled: true + onClicked: print("cover clicked") + + } Component { id: coverImage @@ -80,7 +85,6 @@ Item { Loader { id: mirroredCover sourceComponent: coverImage - opacity: parent.mirrorOpacity anchors.fill: parent transform : [ Rotation { @@ -101,7 +105,11 @@ Item { anchors.rightMargin: -2 anchors.topMargin: -2 - opacity: 1 - itemBrightness + opacity: 1 - itemBrightness + (mouseArea.containsMouse ? .2 : 0) + + Behavior on opacity { + NumberAnimation { easing: Easing.Linear; duration: 300 } + } } Rectangle { @@ -119,4 +127,18 @@ Item { GradientStop { position: 0.5; color: backgroundColor } } } + + Image { + id: playButton + source: "../images/play-rest.png" + anchors.centerIn: parent +// width: +// height: 32 + visible: mouseArea.containsMouse + MouseArea { + anchors.fill: parent + onClicked: root.playClicked(); + } + } + } diff --git a/data/qml/StationScene.qml b/data/qml/StationScene.qml index 01a6d08c4..7881592ee 100644 --- a/data/qml/StationScene.qml +++ b/data/qml/StationScene.qml @@ -9,23 +9,37 @@ Rectangle { property int coverSize: 230 + onWidthChanged: { + print("width changed to", width) + coverView.model = dynamicModel + } + states: [ State { - name: "configure" //; when: scene.state === "configure" - PropertyChanges { target: coverView; anchors.leftMargin: scene.width + scene.coverSize + 20 } + name: "configure" PropertyChanges { target: styleCloud; anchors.leftMargin: 0 } + PropertyChanges { target: configureButton; opacity: 1 } + }, + State { + name: "finetune" + PropertyChanges { target: fineTuneView; anchors.rightMargin: 0 } + PropertyChanges { target: configureButton; opacity: 1 } } ] transitions: [ Transition { NumberAnimation { - target: coverView + target: styleCloud properties: "anchors.leftMargin"; easing.type: Easing.InOutQuad; duration: 500 } NumberAnimation { - target: styleCloud - properties: "anchors.leftMargin"; easing.type: Easing.InOutQuad; duration: 500 + target: fineTuneView + properties: "anchors.rightMargin"; easing.type: Easing.InOutQuad; duration: 500 + } + NumberAnimation { + target: configureButton + properties: "opacity"; easing.type: Easing.InOutQuad; duration: 500 } } ] @@ -35,51 +49,88 @@ Rectangle { id: coverView anchors.fill: parent + Component.onCompleted: { + print("pathview created:", scene.width) + } - preferredHighlightBegin: 0.07 // scene.width / 11000 + preferredHighlightBegin: 0.1 // scene.width / 11000 preferredHighlightEnd: preferredHighlightBegin - pathItemCount: 4 - highlightMoveDuration: 500 + pathItemCount: 5 + //highlightMoveDuration: 500 model: dynamicModel currentIndex: currentlyPlayedIndex + property int pathStartX: width - scene.coverSize + property int pathStartY: height / 2 + delegate: CoverImage { height: scene.coverSize width: scene.coverSize - scale: PathView.itemScale - artistName: model.artistName trackName: model.trackName artworkId: index + scale: PathView.itemScale itemBrightness: PathView.itemBrightness + opacity: PathView.itemOpacity z: x + + onPlayClicked: echonestStation.playItem( index ) } path: Path { - startX: coverView.width / 2 + 20 - startY: 155 + startX: coverView.pathStartX + startY: coverView.pathStartY PathAttribute { name: "itemOpacity"; value: 0 } PathAttribute { name: "itemBrightness"; value: 0 } PathAttribute { name: "itemScale"; value: 1.5 } - PathLine { x: coverView.width / 2; y: 150 } + PathLine { x: coverView.pathStartX * 9/10 ; y: coverView.pathStartY * 9/10 } + PathPercent { value: .1 } PathAttribute { name: "itemOpacity"; value: 1 } PathAttribute { name: "itemBrightness"; value: 1 } PathAttribute { name: "itemScale"; value: 1.0 } - PathLine { x: coverView.width / 2 - 100; y: 180;} + PathLine { x: coverView.pathStartX * .5; y: coverView.pathStartY * .7} + PathPercent { value: .4 } PathAttribute { name: "itemOpacity"; value: 1 } PathAttribute { name: "itemBrightness"; value: 1 } PathAttribute { name: "itemScale"; value: 0.6 } - PathLine { x: 100; y: 100;} + PathLine { x: coverView.pathStartX * .25 ; y: coverView.pathStartY * .25 } + PathPercent { value: .75 } PathAttribute { name: "itemOpacity"; value: 1 } - PathAttribute { name: "itemBrighness"; value: 0.4 } + PathAttribute { name: "itemBrightness"; value: .5 } PathAttribute { name: "itemScale"; value: 0.4 } + PathLine { x: 0; y: 0 } + PathPercent { value: 1 } + PathAttribute { name: "itemOpacity"; value: 1 } + PathAttribute { name: "itemBrightness"; value: 0 } + PathAttribute { name: "itemScale"; value: 0.2 } } + + states: [ + State { + name: "normal" + PropertyChanges { target: coverView; anchors.rightMargin: 0 } + }, + State { + name: "shrinked" + PropertyChanges { target: coverView; anchors.rightMargin: scene.width / 3 } + } + ] + + transitions: [ + Transition { + NumberAnimation { + target: coverView + properties: "anchors.rightMargin"; easing.type: Easing.InOutQuad; duration: 500 + } + } + ] } + Item { id: styleCloud anchors { left: parent.left; top: parent.top; bottom: parent.bottom } @@ -88,7 +139,7 @@ Rectangle { function randomNumber(min, max) { var date = new Date(); - return Math.floor((max - min) * Math.random(date.getSeconds())) + min + return (max - min) * Math.random(date.getSeconds()) + min } Flow { @@ -96,27 +147,126 @@ Rectangle { width: parent.width - 100 //model: controlModel spacing: 3 + + Timer { + interval: 5000 + running: false + repeat: true + + onTriggered: { + for(var i = 0; i < cloudRepeater.count; i++) { + var item = cloudRepeater.itemAt(i); + if(item.itemScale > 0.6) { + item.itemScale = Math.random(); + } else { + item.itemScale = Math.random(); + } + } + } + } + Repeater { + id: cloudRepeater model: generator.styles() delegate: Item { - width: delegateText.width + id: cloudItem + width: delegateText.width * scale height: 28 + property double itemScale: Math.random() + scale: itemScale Text { id: delegateText color: "white" //text: controlModel.controlAt( index ).summary text: modelData - font.pixelSize: styleCloud.randomNumber(11, 28) + font.pixelSize: 28 anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: styleCloud.randomNumber(0, 15) - MouseArea { - anchors.fill: parent - onClicked: echonestStation.setMainControl(modelData); + } + MouseArea { + hoverEnabled: true + anchors.fill: parent + onClicked: echonestStation.setMainControl(modelData); + + onMousePositionChanged: { + cloudItem.scale = 1; + delegateTimer.restart(); } } + Timer { + id: delegateTimer + interval: 3000 + repeat: false + onTriggered: cloudItem.scale = cloudItem.itemScale + } + + Behavior on scale { + NumberAnimation { easing: Easing.Linear; duration: 1000 } + } } } } } + + Item { + id: fineTuneView + anchors { right: parent.right; top: parent.top; bottom: parent.bottom } + anchors.rightMargin: -width + width: scene.width / 2 + + property color textColor: "white" + + Rectangle { + anchors.fill: parent + anchors.margins: 30 + color: "gray" + border.width: 2 + border.color: "white" + radius: 20 + } + + Grid { + Text { + color: fineTuneView.textColor + text: "Name" + + } + TextInput { + id: stationName + //onTextChanged: echonestStation. + } + } + } + Rectangle { + id: configureButton + anchors.right: parent.right + anchors.rightMargin: 20 + anchors.verticalCenter: parent.verticalCenter + color: "gray" + height: 50 + width: 50 + radius: 25 + //opacity: 0 + + Text { + anchors.centerIn: parent + text: "configure" + } + + MouseArea { + anchors.fill: parent + onClicked: { + print("changing scene state to", scene.state) + if( scene.state === "list" ) { + scene.state = "finetune"; + coverView.state = "shrinked" + } else { + scene.state = "list"; + coverView.state = "normal" + } + print("changed scene state to", scene.state) + } + } + } } diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index e0c591617..a987fbef7 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -80,6 +80,7 @@ set( libGuiSources playlist/dynamic/echonest/EchonestGenerator.cpp playlist/dynamic/echonest/EchonestControl.cpp playlist/dynamic/echonest/EchonestSteerer.cpp + playlist/dynamic/echonest/EchonestStation.cpp playlist/dynamic/widgets/DynamicWidget.cpp playlist/dynamic/widgets/DynamicQmlWidget.cpp playlist/dynamic/widgets/DynamicControlWrapper.cpp @@ -141,6 +142,7 @@ set( libGuiSources widgets/ToggleButton.cpp widgets/FadingPixmap.cpp widgets/SocialPlaylistWidget.cpp + widgets/DeclarativeCoverArtProvider.cpp widgets/infowidgets/SourceInfoWidget.cpp widgets/infowidgets/ArtistInfoWidget.cpp widgets/infowidgets/AlbumInfoWidget.cpp diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp index 1cc65b626..207ad3b93 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp @@ -1,5 +1,6 @@ #include "DynamicQmlWidget.h" +#include "dynamic/echonest/EchonestStation.h" #include "playlist/dynamic/DynamicModel.h" #include "playlist/PlayableProxyModel.h" #include "utils/TomahawkUtilsGui.h" @@ -8,6 +9,7 @@ #include "dynamic/GeneratorInterface.h" #include "PlayableItem.h" #include "Source.h" +#include "widgets/DeclarativeCoverArtProvider.h" #include #include @@ -20,23 +22,28 @@ namespace Tomahawk DynamicQmlWidget::DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* parent ) : QDeclarativeView( parent ) - , QDeclarativeImageProvider( QDeclarativeImageProvider::Pixmap ) , m_playlist( playlist ) { - engine()->addImageProvider( "albumart", this ); + setResizeMode( QDeclarativeView::SizeRootObjectToView ); m_model = new DynamicModel( this ); + m_proxyModel = new PlayableProxyModel( this ); m_proxyModel->setSourcePlayableModel( m_model ); - m_proxyModel->setShowOfflineResults( true ); + m_proxyModel->setShowOfflineResults( false ); + + // QML image providers will be deleted by the view + engine()->addImageProvider( "albumart", new DeclarativeCoverArtProvider( m_proxyModel ) ); m_model->loadPlaylist( m_playlist ); + m_model->startOnDemand(); // Initially seed the playlist - m_playlist->generator()->generate( 20 ); + playlist->generator()->fetchNext(); +// m_playlist->generator()->generate( 20 ); qDebug() << "###got" << m_playlist->generator()->controls().size() << "controls"; @@ -59,7 +66,7 @@ DynamicQmlWidget::DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* pa ControlModel *controls = new ControlModel(m_playlist->generator(), this); - EchonestStation *station = new EchonestStation(m_playlist->generator(), this); + EchonestStation *station = new EchonestStation(m_proxyModel, m_playlist->generator(), this); rootContext()->setContextProperty( "echonestStation", station); rootContext()->setContextProperty( "controlModel", controls ); rootContext()->setContextProperty( "dynamicModel", m_proxyModel ); @@ -70,7 +77,9 @@ DynamicQmlWidget::DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* pa connect( m_model, SIGNAL( currentItemChanged( QPersistentModelIndex ) ), SLOT( currentItemChanged( QPersistentModelIndex ) ) ); connect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); + connect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( nextTrackGenerated( Tomahawk::query_ptr ) ) ); connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) ); + connect( m_playlist->generator().data(), SIGNAL( error( QString, QString )), SLOT( error(QString,QString) ) ); } @@ -111,39 +120,13 @@ DynamicQmlWidget::pixmap() const bool DynamicQmlWidget::jumpToCurrentTrack() { - return false; -} - -QPixmap DynamicQmlWidget::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) -{ - // We always can generate it in the requested size - int width = requestedSize.width() > 0 ? requestedSize.width() : 230; - int height = requestedSize.height() > 0 ? requestedSize.height() : 230; - - if( size ) - *size = QSize( width, height ); - - QModelIndex index = m_proxyModel->mapToSource( m_proxyModel->index( id.toInt(), 0, QModelIndex() ) ); - qDebug() << "!*!*!*! got index" << index << id; - if( index.isValid() ) { - PlayableItem *item = m_model->itemFromIndex( index ); - qDebug() << "item:" << item; - qDebug() << "item2:" << item->artistName() << item->name(); - if ( !item->query().isNull() ) { - return item->query()->displayQuery()->cover( *size ); - } - } - - // TODO: create default cover art image - QPixmap pixmap( *size ); - pixmap.fill(); - - return pixmap; + return true; } void DynamicQmlWidget::currentItemChanged( const QPersistentModelIndex ¤tIndex ) { rootContext()->setContextProperty( "currentlyPlayedIndex", m_proxyModel->mapFromSource( currentIndex ).row() ); + m_playlist->generator()->fetchNext(); } void @@ -157,6 +140,26 @@ DynamicQmlWidget::tracksGenerated( const QList< query_ptr >& queries ) //m_model->startOnDemand(); } +void DynamicQmlWidget::nextTrackGenerated(const query_ptr &track) +{ + m_model->tracksGenerated( QList() << track ); + m_playlist->resolve(); + + qDebug() << "next track generated" << m_proxyModel->rowCount() << m_proxyModel->currentIndex().row(); + if( m_proxyModel->rowCount() <= m_proxyModel->currentIndex().row() + 8 ) { + qDebug() << "fetching next one"; + m_playlist->generator()->fetchNext(); + } +} + +void DynamicQmlWidget::error(const QString &title, const QString &body) +{ + qDebug() << "got a generator error:" << title << body; + + m_playlist->generator()->fetchNext(); + +} + void DynamicQmlWidget::onRevisionLoaded(DynamicPlaylistRevision) { m_playlist->resolve(); diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h index ee7e173ef..c73c21107 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h @@ -32,7 +32,7 @@ namespace Tomahawk class DynamicModel; -class DynamicQmlWidget : public QDeclarativeView, public Tomahawk::ViewPage, public QDeclarativeImageProvider +class DynamicQmlWidget : public QDeclarativeView, public Tomahawk::ViewPage { Q_OBJECT public: @@ -51,12 +51,12 @@ public: virtual bool jumpToCurrentTrack(); - QPixmap requestPixmap( const QString &id, QSize *size, const QSize &requestedSize ); - - private slots: void currentItemChanged( const QPersistentModelIndex ¤tIndex ); void tracksGenerated( const QList< Tomahawk::query_ptr>& queries ); + void nextTrackGenerated( const Tomahawk::query_ptr& track ); + void error( const QString& title, const QString& body); + void onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ); private: DynamicModel* m_model; @@ -68,49 +68,6 @@ private: } #include "dynamic/GeneratorInterface.h" - -namespace Tomahawk -{ -class EchonestStation: public QObject -{ - Q_OBJECT - Q_PROPERTY(bool configured READ configured NOTIFY configuredChanged) - Q_PROPERTY(Tomahawk::DynamicControl* mainControl READ mainControl) - -public: - EchonestStation(geninterface_ptr generator, QObject *parent = 0) : QObject(parent), m_generator(generator) {} - - Tomahawk::DynamicControl* mainControl() { - foreach(dyncontrol_ptr control, m_generator->controls()) { - qDebug() << "got control" << control->selectedType(); - if(control->selectedType() == "Artist" || control->selectedType() == "Style") { - return control.data(); - } - } - return 0; - } - - bool configured() { return mainControl() != 0; } - - Q_INVOKABLE void setMainControl(const QString &type) { - dyncontrol_ptr control = m_generator->createControl("echonest"); - control->setSelectedType("Style"); - control->setMatch("1"); - control->setInput(type); - qDebug() << "created control" << control->type() << control->selectedType() << control->match(); - m_generator->generate(20); - - emit configuredChanged(); - } - -signals: - void configuredChanged(); - -private: - geninterface_ptr m_generator; -}; -} - namespace Tomahawk {