1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-17 03:24:15 +02:00

more work on the new station view

This commit is contained in:
Michael Zanetti
2012-07-12 16:40:19 +02:00
parent 976a2eeb0a
commit a89d20665f
9 changed files with 319 additions and 154 deletions

122
data/qml/CoverImage.qml Normal file
View File

@@ -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 }
}
}
}

View File

@@ -5,171 +5,118 @@ Rectangle {
id: scene id: scene
color: "black" color: "black"
anchors.fill: parent anchors.fill: parent
state: echonestStation.configured ? "list" : "configure"
property int coverSize: 230 property int coverSize: 230
Component { states: [
id: coverImage State {
name: "configure" //; when: scene.state === "configure"
Rectangle { PropertyChanges { target: coverView; anchors.leftMargin: scene.width + scene.coverSize + 20 }
height: scene.coverSize PropertyChanges { target: styleCloud; anchors.leftMargin: 0 }
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
}
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")
}
}
}
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 { transitions: [
color: scene.color Transition {
anchors.fill: parent NumberAnimation {
opacity: mirroredItem.shadowOp target: coverView
} properties: "anchors.leftMargin"; easing.type: Easing.InOutQuad; duration: 500
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 }
}
} }
NumberAnimation {
target: styleCloud
properties: "anchors.leftMargin"; easing.type: Easing.InOutQuad; duration: 500
} }
} }
]
PathView { PathView {
id: view id: coverView
anchors.fill: parent anchors.fill: parent
highlight: appHighlight
preferredHighlightBegin: 0.07
preferredHighlightEnd: 0.07 preferredHighlightBegin: 0.07 // scene.width / 11000
preferredHighlightEnd: preferredHighlightBegin
pathItemCount: 4 pathItemCount: 4
highlightMoveDuration: 500 highlightMoveDuration: 500
model: dynamicModel model: dynamicModel
currentIndex: currentlyPlayedIndex 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 { path: Path {
startX: scene.width / 2 + 20 startX: coverView.width / 2 + 20
startY: 155 startY: 155
PathAttribute { name: "itemOpacity"; value: 0 } PathAttribute { name: "itemOpacity"; value: 0 }
PathAttribute { name: "shadowOpacity"; value: 0 } PathAttribute { name: "itemBrightness"; value: 0 }
PathAttribute { name: "itemScale"; value: 1.5 } 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: "itemOpacity"; value: 1 }
PathAttribute { name: "shadowOpacity"; value: 0 } PathAttribute { name: "itemBrightness"; value: 1 }
PathAttribute { name: "itemScale"; value: 1.0 } 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: "itemOpacity"; value: 1 }
PathAttribute { name: "shadowOpacity"; value: 0 } PathAttribute { name: "itemBrightness"; value: 1 }
PathAttribute { name: "itemScale"; value: 0.6 } PathAttribute { name: "itemScale"; value: 0.6 }
PathLine { x: 100; y: 100;} PathLine { x: 100; y: 100;}
PathAttribute { name: "itemOpacity"; value: 1 } PathAttribute { name: "itemOpacity"; value: 1 }
PathAttribute { name: "shadowOpacity"; value: 1 } PathAttribute { name: "itemBrighness"; value: 0.4 }
PathAttribute { name: "itemScale"; 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);
}
}
}
}
}
}
} }

View File

@@ -148,5 +148,6 @@
<file>data/images/scrollbar-horizontal-handle.png</file> <file>data/images/scrollbar-horizontal-handle.png</file>
<file>data/qml/ArtistInfoScene.qml</file> <file>data/qml/ArtistInfoScene.qml</file>
<file>data/qml/StationScene.qml</file> <file>data/qml/StationScene.qml</file>
<file>data/qml/CoverImage.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -56,6 +56,7 @@ void
Tomahawk::GeneratorInterface::addControl( const Tomahawk::dyncontrol_ptr& control ) Tomahawk::GeneratorInterface::addControl( const Tomahawk::dyncontrol_ptr& control )
{ {
m_controls << control; m_controls << control;
emit controlAdded( control );
} }
@@ -77,6 +78,7 @@ void
Tomahawk::GeneratorInterface::removeControl( const Tomahawk::dyncontrol_ptr& control ) Tomahawk::GeneratorInterface::removeControl( const Tomahawk::dyncontrol_ptr& control )
{ {
m_controls.removeAll( control ); m_controls.removeAll( control );
controlRemoved( control );
} }

View File

@@ -59,7 +59,7 @@ public:
// empty QString means use default // empty QString means use default
/// The generator will keep track of all the controls it creates. No need to tell it about controls /// The generator will keep track of all the controls it creates. No need to tell it about controls
/// you ask it to create /// 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 /// A logo to display for this generator, if it has one
virtual QPixmap logo(); virtual QPixmap logo();
@@ -127,6 +127,8 @@ signals:
void error( const QString& title, const QString& body); void error( const QString& title, const QString& body);
void generated( const QList< Tomahawk::query_ptr>& queries ); void generated( const QList< Tomahawk::query_ptr>& queries );
void nextTrackGenerated( const Tomahawk::query_ptr& track ); void nextTrackGenerated( const Tomahawk::query_ptr& track );
void controlAdded(const dyncontrol_ptr& control);
void controlRemoved(const dyncontrol_ptr& control);
protected: protected:
QString m_type; QString m_type;

View File

@@ -150,6 +150,7 @@ dyncontrol_ptr
EchonestGenerator::createControl( const QString& type ) EchonestGenerator::createControl( const QString& type )
{ {
m_controls << dyncontrol_ptr( new EchonestControl( type, GeneratorFactory::typeSelectors( m_type ) ) ); m_controls << dyncontrol_ptr( new EchonestControl( type, GeneratorFactory::typeSelectors( m_type ) ) );
emit controlAdded( m_controls.last() );
return m_controls.last(); return m_controls.last();
} }

View File

@@ -71,7 +71,7 @@ public:
explicit EchonestGenerator( QObject* parent = 0 ); explicit EchonestGenerator( QObject* parent = 0 );
virtual ~EchonestGenerator(); virtual ~EchonestGenerator();
virtual dyncontrol_ptr createControl( const QString& type = QString() ); Q_INVOKABLE virtual dyncontrol_ptr createControl( const QString& type = QString() );
virtual QPixmap logo(); virtual QPixmap logo();
virtual void generate ( int number = -1 ); virtual void generate ( int number = -1 );
virtual void startOnDemand(); virtual void startOnDemand();
@@ -80,7 +80,7 @@ public:
virtual bool onDemandSteerable() const { return true; } virtual bool onDemandSteerable() const { return true; }
virtual QWidget* steeringWidget(); virtual QWidget* steeringWidget();
static QStringList styles(); Q_INVOKABLE static QStringList styles();
static QStringList moods(); static QStringList moods();
static QStringList userCatalogs(); static QStringList userCatalogs();
static QByteArray catalogId( const QString& collectionId ); static QByteArray catalogId( const QString& collectionId );

View File

@@ -4,6 +4,7 @@
#include "playlist/PlayableProxyModel.h" #include "playlist/PlayableProxyModel.h"
#include "utils/TomahawkUtilsGui.h" #include "utils/TomahawkUtilsGui.h"
#include "dynamic/DynamicModel.h" #include "dynamic/DynamicModel.h"
#include "dynamic/echonest/EchonestControl.h"
#include "dynamic/GeneratorInterface.h" #include "dynamic/GeneratorInterface.h"
#include "PlayableItem.h" #include "PlayableItem.h"
#include "Source.h" #include "Source.h"
@@ -30,19 +31,40 @@ DynamicQmlWidget::DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* pa
m_model = new DynamicModel( this ); m_model = new DynamicModel( this );
m_proxyModel = new PlayableProxyModel( this ); m_proxyModel = new PlayableProxyModel( this );
m_proxyModel->setSourcePlayableModel( m_model ); m_proxyModel->setSourcePlayableModel( m_model );
m_proxyModel->setShowOfflineResults( false ); m_proxyModel->setShowOfflineResults( true );
m_model->loadPlaylist( m_playlist ); m_model->loadPlaylist( m_playlist );
m_model->startOnDemand();
// Initially seed the playlist
m_playlist->generator()->generate( 20 ); m_playlist->generator()->generate( 20 );
m_playlist->resolve();
rootContext()->setContextProperty( "dynamicModel", m_proxyModel ); qDebug() << "###got" << m_playlist->generator()->controls().size() << "controls";
currentItemChanged( m_model->currentItem() );
qmlRegisterUncreatableType<EchonestStation>("tomahawk", 1, 0, "EchonestStation", "bla");
// TODO: In case QML is used in more places, this should probably be moved to some generic place // TODO: In case QML is used in more places, this should probably be moved to some generic place
qmlRegisterType<PlayableItem>("tomahawk", 1, 0, "PlayableItem"); qmlRegisterType<PlayableItem>("tomahawk", 1, 0, "PlayableItem");
// qmlRegisterUncreatableType<Tomahawk::DynamicControl>("tomahawk", 1, 0, "DynamicControl", "use generator.createControl() isntead");
// qmlRegisterUncreatableType<Tomahawk::EchonestControl>("tomahawk", 1, 0, "EchonestControl", "use Generator.createControl() instead");
qmlRegisterUncreatableType<DynamicControl>("tomahawk", 1, 0, "DynamicControl", "use generator.createControl() isntead");
qmlRegisterUncreatableType<EchonestControl>("tomahawk", 1, 0, "EchonestControl", "use Generator.createControl() instead");
qmlRegisterUncreatableType<GeneratorInterface>("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" ) ); setSource( QUrl( "qrc" RESPATH "qml/StationScene.qml" ) );
@@ -127,19 +149,12 @@ void DynamicQmlWidget::currentItemChanged( const QPersistentModelIndex &currentI
void void
DynamicQmlWidget::tracksGenerated( const QList< query_ptr >& queries ) DynamicQmlWidget::tracksGenerated( const QList< query_ptr >& queries )
{ {
int limit = -1; // only limit the "preview" of a station qDebug() << queries.count() << "tracks generated";
// if ( m_playlist->author()->isLocal() && m_playlist->mode() == Static ) m_model->tracksGenerated( queries, queries.count() );
// {
// m_resolveOnNextLoad = true;
// }
// else if ( m_playlist->mode() == OnDemand )
// {
limit = 5;
// }
m_model->tracksGenerated( queries, limit );
m_playlist->resolve(); m_playlist->resolve();
// Ok... we have some intial stuff, switch to dynamic mode
//m_model->startOnDemand();
} }
void DynamicQmlWidget::onRevisionLoaded(DynamicPlaylistRevision) void DynamicQmlWidget::onRevisionLoaded(DynamicPlaylistRevision)

View File

@@ -53,6 +53,7 @@ public:
QPixmap requestPixmap( const QString &id, QSize *size, const QSize &requestedSize ); QPixmap requestPixmap( const QString &id, QSize *size, const QSize &requestedSize );
private slots: private slots:
void currentItemChanged( const QPersistentModelIndex &currentIndex ); void currentItemChanged( const QPersistentModelIndex &currentIndex );
void tracksGenerated( const QList< Tomahawk::query_ptr>& queries ); void tracksGenerated( const QList< Tomahawk::query_ptr>& queries );
@@ -64,5 +65,79 @@ private:
dynplaylist_ptr m_playlist; 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 #endif // DYNAMIC_QML_WIDGET_H