diff --git a/data/qml/CoverImage.qml b/data/qml/CoverImage.qml
new file mode 100644
index 000000000..442d835cb
--- /dev/null
+++ b/data/qml/CoverImage.qml
@@ -0,0 +1,122 @@
+import QtQuick 1.1
+
+Item {
+ id: root
+
+ // Labels & Cover
+ property string artistName
+ property string trackName
+ property string artworkId
+
+ // The border color for the cover image
+ property color borderColor: "black"
+ // The border width for the cover image
+ property int borderWidth: 2
+
+ // needed to adjust the shadow
+ property color backgroundColor: "black"
+
+ // sets the brightness for the item and its mirror (1: brightest, 0: darkest)
+ property double itemBrightness: 1
+ property double mirrorBrightness: .5
+
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: print("cover clicked")
+ }
+
+
+ Component {
+ id: coverImage
+
+ Rectangle {
+ color: "white"
+ border.color: borderColor
+ border.width: borderWidth
+
+ Image {
+ anchors.fill: parent
+ //anchors.margins: borderWidth
+ source: "image://albumart/" + artworkId
+ }
+
+
+ Rectangle {
+ id: textBackground
+ anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
+ height: 32
+ anchors.margins: 5
+ color: "black"
+ opacity: 0.5
+ radius: 3
+
+ }
+ Text {
+ color: "white"
+ font.bold: true
+ text: trackName
+ anchors { left: textBackground.left; right: textBackground.right; top: textBackground.top }
+ anchors.margins: 2
+ horizontalAlignment: Text.AlignHCenter
+ elide: Text.ElideRight
+ }
+ Text {
+ color: "white"
+ text: artistName
+ anchors { left: textBackground.left; right: textBackground.right; bottom: textBackground.bottom }
+ anchors.margins: 2
+ horizontalAlignment: Text.AlignHCenter
+ elide: Text.ElideRight
+ }
+ }
+
+ }
+ Loader {
+ sourceComponent: coverImage
+ anchors.fill: parent
+ }
+
+ Loader {
+ id: mirroredCover
+ sourceComponent: coverImage
+ opacity: parent.mirrorOpacity
+ anchors.fill: parent
+ transform : [
+ Rotation {
+ angle: 180; origin.y: root.height
+ axis.x: 1; axis.y: 0; axis.z: 0
+ }
+ ]
+ }
+
+ Rectangle {
+ id: itemShadow
+ color: backgroundColor
+ anchors.fill: parent
+ anchors.bottomMargin: - parent.height
+
+ // scaling might be off a pixel... make sure that the shadow is at least as large as the image
+ anchors.leftMargin: -2
+ anchors.rightMargin: -2
+ anchors.topMargin: -2
+
+ opacity: 1 - itemBrightness
+ }
+
+ Rectangle {
+ id: mirrorShadow
+ color: parent.backgroundColor
+ height: parent.height + 2
+ width: parent.width + 4
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: parent.height
+
+ gradient: Gradient {
+ // TODO: no clue how to get the RGB component of the container rectangle color
+ // For now the Qt.rgba needs to be manually updated to match the backgroundColor
+ GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, 1-mirrorBrightness) }
+ GradientStop { position: 0.5; color: backgroundColor }
+ }
+ }
+}
diff --git a/data/qml/StationScene.qml b/data/qml/StationScene.qml
index 58a01eac0..01a6d08c4 100644
--- a/data/qml/StationScene.qml
+++ b/data/qml/StationScene.qml
@@ -5,171 +5,118 @@ Rectangle {
id: scene
color: "black"
anchors.fill: parent
+ state: echonestStation.configured ? "list" : "configure"
property int coverSize: 230
- Component {
- id: coverImage
+ states: [
+ State {
+ name: "configure" //; when: scene.state === "configure"
+ PropertyChanges { target: coverView; anchors.leftMargin: scene.width + scene.coverSize + 20 }
+ PropertyChanges { target: styleCloud; anchors.leftMargin: 0 }
+ }
+ ]
- Rectangle {
- height: scene.coverSize
- width: scene.coverSize
- color: scene.color
- border.color: scene.color
- border.width: 2
-
- property string artistName
- property string trackName
- property string artworkId
-
- Image {
- anchors.fill: parent
- anchors.margins: parent.border.width
- source: "image://albumart/" + parent.artworkId
+ transitions: [
+ Transition {
+ NumberAnimation {
+ target: coverView
+ properties: "anchors.leftMargin"; easing.type: Easing.InOutQuad; duration: 500
}
-
- Rectangle {
- id: textBackground
- anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
- height: 32
- anchors.margins: 5
- color: "black"
- opacity: 0.5
- radius: 3
-
- }
- Text {
- color: "white"
- font.bold: true
- text: trackName
- anchors { left: textBackground.left; right: textBackground.right; top: textBackground.top }
- anchors.margins: 2
- horizontalAlignment: Text.AlignHCenter
- elide: Text.ElideRight
- }
- Text {
- color: "white"
- text: artistName
- anchors { left: textBackground.left; right: textBackground.right; bottom: textBackground.bottom }
- anchors.margins: 2
- horizontalAlignment: Text.AlignHCenter
- elide: Text.ElideRight
- }
-
- MouseArea {
- anchors.fill: parent
- onClicked: print("cover clicked")
+ NumberAnimation {
+ target: styleCloud
+ properties: "anchors.leftMargin"; easing.type: Easing.InOutQuad; duration: 500
}
}
- }
+ ]
- Component {
- id: mirroredDelegate
-
- Item {
- id: mirroredItem
- height: scene.coverSize
- width: scene.coverSize
-
- z: x
- scale: PathView.itemScale
-
- property double itemOpacity: PathView.itemOpacity
- property double shadowOp: PathView.shadowOpacity
-
- Connections {
- target: dynamicModel.itemFromIndex( index )
- onCoverChanged: {
- // We need to unset and re-set it because QML wouldn't re-query the image if it still has the same url
- normalCover.item.artworkId = ""
- mirroredCover.item.artworkId = ""
- normalCover.item.artworkId = index
- mirroredCover.item.artworkId = index
- }
- }
- //Component.onCompleted: print("created delegate for", dynamicModel.itemFromIndex( index ) )
-
- Loader {
- id: normalCover
- sourceComponent: coverImage
- opacity: parent.itemOpacity
- onLoaded: {
- item.trackName = trackName
- item.artistName = artistName
- item.artworkId = index
- }
- }
- Loader {
- id: mirroredCover
- sourceComponent: coverImage
- opacity: parent.itemOpacity
- onLoaded: {
- item.trackName = trackName
- item.artistName = artistName
- item.artworkId = index
- }
- transform : [
- Rotation {
- angle: 180; origin.y: scene.coverSize
- axis.x: 1; axis.y: 0; axis.z: 0
- }
- ]
- }
-
- Rectangle {
- color: scene.color
- anchors.fill: parent
- opacity: mirroredItem.shadowOp
- }
- Rectangle {
- color: scene.color
- height: scene.coverSize
- width: scene.coverSize
- anchors.centerIn: parent
- anchors.verticalCenterOffset: scene.coverSize
- gradient: Gradient {
- // TODO: no clue how to get the RGB component of the container rectangle color
- // For now the Qt.rgba needs to be manually updated to match scene.color
- GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, mirroredItem.shadowOp + ( (1 - mirroredItem.shadowOp) * .4)) }
- GradientStop { position: 0.5; color: scene.color }
- }
- }
- }
- }
PathView {
- id: view
+ id: coverView
anchors.fill: parent
- highlight: appHighlight
- preferredHighlightBegin: 0.07
- preferredHighlightEnd: 0.07
+
+ preferredHighlightBegin: 0.07 // scene.width / 11000
+ preferredHighlightEnd: preferredHighlightBegin
pathItemCount: 4
highlightMoveDuration: 500
model: dynamicModel
currentIndex: currentlyPlayedIndex
- delegate: mirroredDelegate
+ delegate: CoverImage {
+ height: scene.coverSize
+ width: scene.coverSize
+
+ scale: PathView.itemScale
+
+ artistName: model.artistName
+ trackName: model.trackName
+ artworkId: index
+
+ itemBrightness: PathView.itemBrightness
+ z: x
+ }
path: Path {
- startX: scene.width / 2 + 20
+ startX: coverView.width / 2 + 20
startY: 155
+
PathAttribute { name: "itemOpacity"; value: 0 }
- PathAttribute { name: "shadowOpacity"; value: 0 }
+ PathAttribute { name: "itemBrightness"; value: 0 }
PathAttribute { name: "itemScale"; value: 1.5 }
- PathLine { x: scene.width / 2; y: 150 }
+ PathLine { x: coverView.width / 2; y: 150 }
PathAttribute { name: "itemOpacity"; value: 1 }
- PathAttribute { name: "shadowOpacity"; value: 0 }
+ PathAttribute { name: "itemBrightness"; value: 1 }
PathAttribute { name: "itemScale"; value: 1.0 }
- PathLine { x: scene.width / 2 - 100; y: 180;}
+ PathLine { x: coverView.width / 2 - 100; y: 180;}
PathAttribute { name: "itemOpacity"; value: 1 }
- PathAttribute { name: "shadowOpacity"; value: 0 }
+ PathAttribute { name: "itemBrightness"; value: 1 }
PathAttribute { name: "itemScale"; value: 0.6 }
PathLine { x: 100; y: 100;}
PathAttribute { name: "itemOpacity"; value: 1 }
- PathAttribute { name: "shadowOpacity"; value: 1 }
+ PathAttribute { name: "itemBrighness"; value: 0.4 }
PathAttribute { name: "itemScale"; value: 0.4 }
}
}
+
+ Item {
+ id: styleCloud
+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom }
+ anchors.leftMargin: scene.width
+ width: scene.width
+
+ function randomNumber(min, max) {
+ var date = new Date();
+ return Math.floor((max - min) * Math.random(date.getSeconds())) + min
+ }
+
+ Flow {
+ anchors.centerIn: parent
+ width: parent.width - 100
+ //model: controlModel
+ spacing: 3
+ Repeater {
+ model: generator.styles()
+
+ delegate: Item {
+ width: delegateText.width
+ height: 28
+ Text {
+ id: delegateText
+ color: "white"
+ //text: controlModel.controlAt( index ).summary
+ text: modelData
+ font.pixelSize: styleCloud.randomNumber(11, 28)
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.verticalCenterOffset: styleCloud.randomNumber(0, 15)
+ MouseArea {
+ anchors.fill: parent
+ onClicked: echonestStation.setMainControl(modelData);
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/resources.qrc b/resources.qrc
index 4c05c85bf..f99f12517 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -148,5 +148,6 @@
data/images/scrollbar-horizontal-handle.png
data/qml/ArtistInfoScene.qml
data/qml/StationScene.qml
+ data/qml/CoverImage.qml
diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp
index 4b6bd1aca..885bd9d29 100644
--- a/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp
+++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.cpp
@@ -56,6 +56,7 @@ void
Tomahawk::GeneratorInterface::addControl( const Tomahawk::dyncontrol_ptr& control )
{
m_controls << control;
+ emit controlAdded( control );
}
@@ -77,6 +78,7 @@ void
Tomahawk::GeneratorInterface::removeControl( const Tomahawk::dyncontrol_ptr& control )
{
m_controls.removeAll( control );
+ controlRemoved( control );
}
diff --git a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h
index fd0746a5b..f4c4d5f3d 100644
--- a/src/libtomahawk/playlist/dynamic/GeneratorInterface.h
+++ b/src/libtomahawk/playlist/dynamic/GeneratorInterface.h
@@ -59,7 +59,7 @@ public:
// empty QString means use default
/// The generator will keep track of all the controls it creates. No need to tell it about controls
/// you ask it to create
- virtual dyncontrol_ptr createControl( const QString& type = QString() );
+ Q_INVOKABLE virtual dyncontrol_ptr createControl( const QString& type = QString() );
/// A logo to display for this generator, if it has one
virtual QPixmap logo();
@@ -127,6 +127,8 @@ signals:
void error( const QString& title, const QString& body);
void generated( const QList< Tomahawk::query_ptr>& queries );
void nextTrackGenerated( const Tomahawk::query_ptr& track );
+ void controlAdded(const dyncontrol_ptr& control);
+ void controlRemoved(const dyncontrol_ptr& control);
protected:
QString m_type;
diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
index 1f3fd0cff..3059f9815 100644
--- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
+++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp
@@ -150,6 +150,7 @@ dyncontrol_ptr
EchonestGenerator::createControl( const QString& type )
{
m_controls << dyncontrol_ptr( new EchonestControl( type, GeneratorFactory::typeSelectors( m_type ) ) );
+ emit controlAdded( m_controls.last() );
return m_controls.last();
}
diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
index 90cb7c338..231415eb7 100644
--- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
+++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h
@@ -71,7 +71,7 @@ public:
explicit EchonestGenerator( QObject* parent = 0 );
virtual ~EchonestGenerator();
- virtual dyncontrol_ptr createControl( const QString& type = QString() );
+ Q_INVOKABLE virtual dyncontrol_ptr createControl( const QString& type = QString() );
virtual QPixmap logo();
virtual void generate ( int number = -1 );
virtual void startOnDemand();
@@ -80,7 +80,7 @@ public:
virtual bool onDemandSteerable() const { return true; }
virtual QWidget* steeringWidget();
- static QStringList styles();
+ Q_INVOKABLE static QStringList styles();
static QStringList moods();
static QStringList userCatalogs();
static QByteArray catalogId( const QString& collectionId );
diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp
index cb543e38f..1cc65b626 100644
--- a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp
+++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp
@@ -4,6 +4,7 @@
#include "playlist/PlayableProxyModel.h"
#include "utils/TomahawkUtilsGui.h"
#include "dynamic/DynamicModel.h"
+#include "dynamic/echonest/EchonestControl.h"
#include "dynamic/GeneratorInterface.h"
#include "PlayableItem.h"
#include "Source.h"
@@ -30,19 +31,40 @@ DynamicQmlWidget::DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* pa
m_model = new DynamicModel( this );
m_proxyModel = new PlayableProxyModel( this );
m_proxyModel->setSourcePlayableModel( m_model );
- m_proxyModel->setShowOfflineResults( false );
+ m_proxyModel->setShowOfflineResults( true );
m_model->loadPlaylist( m_playlist );
- m_model->startOnDemand();
+ // Initially seed the playlist
m_playlist->generator()->generate( 20 );
- m_playlist->resolve();
- rootContext()->setContextProperty( "dynamicModel", m_proxyModel );
- currentItemChanged( m_model->currentItem() );
+ qDebug() << "###got" << m_playlist->generator()->controls().size() << "controls";
+
+ qmlRegisterUncreatableType("tomahawk", 1, 0, "EchonestStation", "bla");
// TODO: In case QML is used in more places, this should probably be moved to some generic place
qmlRegisterType("tomahawk", 1, 0, "PlayableItem");
+// qmlRegisterUncreatableType("tomahawk", 1, 0, "DynamicControl", "use generator.createControl() isntead");
+// qmlRegisterUncreatableType("tomahawk", 1, 0, "EchonestControl", "use Generator.createControl() instead");
+ qmlRegisterUncreatableType("tomahawk", 1, 0, "DynamicControl", "use generator.createControl() isntead");
+ qmlRegisterUncreatableType("tomahawk", 1, 0, "EchonestControl", "use Generator.createControl() instead");
+ qmlRegisterUncreatableType("tomahawk", 1, 0, "Generator", "you cannot create it on your own - should be set in context");
+
+ QStringList generatorControls;
+
+ foreach(dyncontrol_ptr control, m_playlist->generator()->controls()) {
+ qDebug() << "**CTRL" << control->summary() << control->input() << control->match() << control->type() << control->selectedType();
+ generatorControls << control->summary();
+ }
+
+ ControlModel *controls = new ControlModel(m_playlist->generator(), this);
+
+ EchonestStation *station = new EchonestStation(m_playlist->generator(), this);
+ rootContext()->setContextProperty( "echonestStation", station);
+ rootContext()->setContextProperty( "controlModel", controls );
+ rootContext()->setContextProperty( "dynamicModel", m_proxyModel );
+ rootContext()->setContextProperty( "generator", m_playlist->generator().data() );
+ currentItemChanged( m_model->currentItem() );
setSource( QUrl( "qrc" RESPATH "qml/StationScene.qml" ) );
@@ -127,19 +149,12 @@ void DynamicQmlWidget::currentItemChanged( const QPersistentModelIndex ¤tI
void
DynamicQmlWidget::tracksGenerated( const QList< query_ptr >& queries )
{
- int limit = -1; // only limit the "preview" of a station
-// if ( m_playlist->author()->isLocal() && m_playlist->mode() == Static )
-// {
-// m_resolveOnNextLoad = true;
-// }
-// else if ( m_playlist->mode() == OnDemand )
-// {
- limit = 5;
-// }
-
- m_model->tracksGenerated( queries, limit );
+ qDebug() << queries.count() << "tracks generated";
+ m_model->tracksGenerated( queries, queries.count() );
m_playlist->resolve();
+ // Ok... we have some intial stuff, switch to dynamic mode
+ //m_model->startOnDemand();
}
void DynamicQmlWidget::onRevisionLoaded(DynamicPlaylistRevision)
diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h
index d050d8ff5..ee7e173ef 100644
--- a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h
+++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h
@@ -53,6 +53,7 @@ public:
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 );
@@ -64,5 +65,79 @@ private:
dynplaylist_ptr m_playlist;
};
+}
+
+#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
+{
+
+class ControlModel: public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ ControlModel(geninterface_ptr generator, QObject *parent = 0): QAbstractListModel(parent), m_generator(generator) {
+ connect(generator.data(), SIGNAL(controlAdded(const dyncontrol_ptr&)), SLOT(controlAdded()));
+ }
+
+ int rowCount(const QModelIndex &parent) const { return m_generator->controls().size(); }
+ QVariant data(const QModelIndex &index, int role) const {
+ return "blabla";
+ }
+ Q_INVOKABLE Tomahawk::DynamicControl *controlAt( int index ) { qDebug() << "returning" << m_generator->controls().at(index).data(); return m_generator->controls().at(index).data(); }
+
+private slots:
+ void controlAdded() {
+ qDebug() << "control added";
+ beginInsertRows(QModelIndex(), m_generator->controls().size() - 1, m_generator->controls().size() - 1);
+ endInsertRows();
+ }
+private:
+ geninterface_ptr m_generator;
+
+};
+
}
#endif // DYNAMIC_QML_WIDGET_H