diff --git a/data/images/inputfield-border.svg b/data/images/inputfield-border.svg new file mode 100644 index 000000000..3f3ff841c --- /dev/null +++ b/data/images/inputfield-border.svg @@ -0,0 +1,145 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/images/station-artist.svg b/data/images/station-artist.svg new file mode 100644 index 000000000..43ed51948 --- /dev/null +++ b/data/images/station-artist.svg @@ -0,0 +1,14 @@ + + + station-artist + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + \ No newline at end of file diff --git a/data/images/station-genre.svg b/data/images/station-genre.svg new file mode 100644 index 000000000..ba5c1157f --- /dev/null +++ b/data/images/station-genre.svg @@ -0,0 +1,18 @@ + + + station-genre + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/images/station-year.svg b/data/images/station-year.svg new file mode 100644 index 000000000..a18fb23d3 --- /dev/null +++ b/data/images/station-year.svg @@ -0,0 +1,19 @@ + + + station-year + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/qml/DeclarativeHeader.qml b/data/qml/DeclarativeHeader.qml new file mode 100644 index 000000000..5863a9d27 --- /dev/null +++ b/data/qml/DeclarativeHeader.qml @@ -0,0 +1,21 @@ +import QtQuick 1.1 +import tomahawk 1.0 +import "tomahawkimports" + +// Only to be used together with DeclarativeHeader C++ class +// If you want to use the header in QML, use FlexibleHeader + +Item { + anchors.fill: parent + + FlexibleHeader { + anchors.fill: parent + icon: iconSource + title: caption + subtitle: description + buttonModel: buttonList + + onSearchTextChanged: mainView.setFilterText(searchText) + onCurrentButtonIndexChanged: mainView.viewModeSelected(currentButtonIndex) + } +} diff --git a/data/qml/QmlGridView.qml b/data/qml/QmlGridView.qml new file mode 100644 index 000000000..1d27a2d5e --- /dev/null +++ b/data/qml/QmlGridView.qml @@ -0,0 +1,65 @@ +import QtQuick 1.1 +//import tomahawk 1.0 +import "tomahawkimports" + +Rectangle { + anchors.fill: parent + color: "black" + + Text { + id: fontMetrics + text: "Here's some sample text" + opacity: 0 + } + + GridView { + id: gridView + anchors.fill: parent + //anchors.rightMargin: scrollBar.width + + cellHeight: cellWidth + cellWidth: calculateCoverSize(gridView.width - 3) + + cacheBuffer: cellHeight * 5 + + function calculateCoverSize(rectWidth) { + var itemWidth = fontMetrics.width; + var itemsPerRow = Math.max( 1, Math.floor( rectWidth / itemWidth ) ); + + var remSpace = rectWidth - ( itemsPerRow * itemWidth ); + var extraSpace = remSpace / itemsPerRow; + return itemWidth + extraSpace; + + } + + model: mainModel + + delegate: CoverImage { + height: gridView.cellHeight// * 0.95 + width: gridView.cellWidth// * 0.95 + + showLabels: true + showMirror: false + artistName: model.artistName + trackName: model.trackName + artworkId: model.coverID + showPlayButton: true + currentlyPlaying: isPlaying + smooth: !gridView.moving + + onClicked: { + rootView.onItemClicked(index) + } + onPlayClicked: { + rootView.onItemPlayClicked(index) + } + } + } + + ScrollBar { + id: scrollBar + listView: gridView + orientation: Qt.Vertical + margin: -width + } +} diff --git a/data/qml/SpinnerTest.qml b/data/qml/SpinnerTest.qml new file mode 100644 index 000000000..217b4dbcf --- /dev/null +++ b/data/qml/SpinnerTest.qml @@ -0,0 +1,34 @@ +import QtQuick 1.1 +import "tomahawkimports" + +Rectangle { +width: 1400 +height: 900 +color: "black" + +BusyIndicator { + anchors.centerIn: parent + anchors.horizontalCenterOffset: 200 + height: 200 + width: 200 +} + +Image { + id: svgSpinner + source: "../images/loading-animation.svg" + smooth: true + height: 200 + width: 200 + anchors.centerIn: parent + anchors.horizontalCenterOffset: -200 + + Timer { + running: true + repeat: true + interval: 200 + onTriggered: svgSpinner.rotation += 360 / 12 + } +} + + +} diff --git a/data/qml/StationView.qml b/data/qml/StationView.qml new file mode 100644 index 000000000..7bb79e1b4 --- /dev/null +++ b/data/qml/StationView.qml @@ -0,0 +1,162 @@ +import QtQuick 1.1 +import tomahawk 1.0 +import "tomahawkimports" +import "stations" +Rectangle { + id: scene + color: "black" + anchors.fill: parent + state: "list" + + FlexibleHeader { + id: header + anchors { + left: parent.left + top: parent.top + right: parent.right + } + height: defaultFontHeight * 4 + width: parent.width + icon: "../images/station.svg" + title: mainView.title + subtitle: generator.summary + showSearchField: false + showBackButton: stationListView.currentIndex > 0 + showNextButton: stationListView.currentIndex == 2 + nextButtonText: "Save" + + z: 1 //cover albumcovers that may leave their area + + onBackPressed: stationListView.decrementCurrentIndex() + onNextPressed: stationListView.incrementCurrentIndex() + } + + ListModel { + id: modeModel + ListElement { label: "By Artist"; image: "../../images/station-artist.svg"; creatorContent: "stations/CreateByArtist.qml" } + ListElement { label: "By Genre"; image: "../../images/station-genre.svg"; creatorContent: "stations/CreateByGenre.qml" } + ListElement { label: "By Year"; image: "../../images/station-year.svg"; creatorContent: "year" } + } + + VisualItemModel { + id: stationVisualModel + + StationCreatorPage1 { + height: scene.height - header.height + width: scene.width + model: modeModel + + onItemClicked: { + stationCreator.content = modeModel.get(index).creatorContent + stationListView.incrementCurrentIndex() + } + } + + StationCreatorPage2 { + id: stationCreator + height: stationListView.height + width: stationListView.width + + onNext: stationListView.incrementCurrentIndex() + } + + Item { + id: stationItem + height: stationListView.height + width: stationListView.width + + CoverFlip { + id: coverView + anchors.right: parent.right + anchors.top: parent.top + height: parent.height + width: parent.width + interactive: false + + backgroundColor: scene.color + + model: dynamicModel + currentIndex: currentlyPlayedIndex + + onItemPlayPauseClicked: { + mainView.playItem(index) + } + + onItemClicked: { + mainView.playItem(index) + } + + states: [ + State { + name: "empty"; when: mainView.loading + PropertyChanges { + target: coverView + anchors.rightMargin: -coverView.width + anchors.topMargin: - coverView.height + scale: 0 + } + } + ] + transitions: [ + Transition { + from: "empty" + to: "*" + NumberAnimation { + properties: "anchors.topMargin,anchors.rightMargin,scale" + duration: 1000 + easing.type: Easing.OutQuad + } + } + + ] +// Behavior on anchors.topMargin { +// NumberAnimation { duration: 500 } +// } +// Behavior on anchors.rightMargin { +// NumberAnimation { duration: 500 } +// } +// Behavior on scale { +// NumberAnimation { duration: 500 } +// } + + } + BusyIndicator { + id: busyIndicator + anchors.centerIn: parent + height: defaultFontHeight * 4 + width: height + + opacity: mainView.loading ? 1 : 0 + running: mainView.loading + } + + } + + } + + + ListView { + id: stationListView + anchors { + left: parent.left + top: header.bottom + right: parent.right + bottom: parent.bottom + } + + contentHeight: height + contentWidth: width + orientation: ListView.Horizontal + model: stationVisualModel + interactive: false + highlightMoveDuration: 300 + + onHeightChanged: { + contentHeight = scene.height + } + onWidthChanged: { + contentWidth = scene.width + } + } + +} diff --git a/data/qml/stations/CreateByArtist.qml b/data/qml/stations/CreateByArtist.qml new file mode 100644 index 000000000..f3f2f4ceb --- /dev/null +++ b/data/qml/stations/CreateByArtist.qml @@ -0,0 +1,69 @@ +import QtQuick 1.1 +import tomahawk 1.0 +import "../tomahawkimports" + +Item { + id: root + anchors.fill: parent + + signal done() + + function createStation(artist) { + mainView.startStationFromArtist(artist) + root.done() + } + + Column { + id: upperColumn + anchors.horizontalCenter: parent.horizontalCenter + height: parent.height + width: defaultFontHeight * 30 + anchors.bottomMargin: defaultFontHeight + spacing: defaultFontHeight + + HeaderLabel { + id: headerText + text: "Create station by artist..." + } + + Row { + height: artistInputField.height + width: parent.width + spacing: defaultFontHeight * 0.5 + + InputField { + id: artistInputField + width: parent.width - createFromInputButton.width - parent.spacing + + onAccepted: createStation(text) + } + + PushButton { + id: createFromInputButton + text: "Go!" + enabled: artistInputField.text.length > 2 + onClicked: createStation(artistInputField.text) + } + } + + Item { + height: parent.height - headerText.height - artistInputField.height - parent.spacing * 3 + width: parent.width + ArtistView { + id: artistView + height: parent.height + width: parent.width + model: artistChartsModel + clip: true + delegateHeight: defaultFontHeight * 6 + + onItemClicked: { + createStation(artistChartsModel.itemFromIndex(index).artistName); + } + } + ScrollBar { + listView: artistView + } + } + } +} diff --git a/data/qml/stations/CreateByGenre.qml b/data/qml/stations/CreateByGenre.qml new file mode 100644 index 000000000..9d2fb1b9b --- /dev/null +++ b/data/qml/stations/CreateByGenre.qml @@ -0,0 +1,88 @@ +import QtQuick 1.1 +import tomahawk 1.0 +import "../tomahawkimports" + +Item { + id: root + anchors.fill: parent + + signal done() + + function createStation(genre) { + mainView.startStationFromGenre(genre) + root.done() + } + + ListModel { + id: styleModel + ListElement { modelData: "acoustic" } + ListElement { modelData: "alternative" } + ListElement { modelData: "alternative rock" } + ListElement { modelData: "classic" } + ListElement { modelData: "folk" } + ListElement { modelData: "indie" } + ListElement { modelData: "pop" } + ListElement { modelData: "rock" } + ListElement { modelData: "hip-hop" } + ListElement { modelData: "punk" } + ListElement { modelData: "grunge" } + ListElement { modelData: "indie" } + ListElement { modelData: "electronic" } + ListElement { modelData: "country" } + ListElement { modelData: "jazz" } + ListElement { modelData: "psychodelic" } + ListElement { modelData: "soundtrack" } + ListElement { modelData: "reggae" } + ListElement { modelData: "house" } + ListElement { modelData: "drum and base" } + } + + Column { + id: upperColumn + anchors.fill: parent + anchors.bottomMargin: defaultFontHeight + spacing: defaultFontHeight + + HeaderLabel { + id: headerText + anchors.horizontalCenter: parent.horizontalCenter + text: "Create station by genre..." + } + + Row { + width: defaultFontHeight * 30 + height: genreInputField.height + spacing: defaultFontHeight * 0.5 + anchors.horizontalCenter: parent.horizontalCenter + + InputField { + id: genreInputField + width: parent.width - createFromInputButton.width - parent.spacing + + onAccepted: createStation(text); + } + + PushButton { + id: createFromInputButton + text: "Go!" + height: genreInputField.height + enabled: genreInputField.text.length > 2 + onClicked: createStation(genreInputField.text) + } + } + + Item { + height: parent.height - headerText.height - genreInputField.height + width: parent.width + TagCloud { + anchors.fill: parent + anchors.margins: parent.width / 6 + model: styleModel + + onTagClicked: { + root.createStation(tag); + } + } + } + } +} diff --git a/data/qml/stations/StationConfig.qml b/data/qml/stations/StationConfig.qml new file mode 100644 index 000000000..d6f0ce11d --- /dev/null +++ b/data/qml/stations/StationConfig.qml @@ -0,0 +1,79 @@ +import QtQuick 1.1 +import tomahawk 1.0 +import "tomahawkimports" + +Item { + id: fineTuneView + + property color textColor: "white" + + signal done(); + + Grid { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: 50 + anchors.horizontalCenter: parent.horizontalCenter + width: scene.width / 2 + spacing: 50 + columns: 2 + + Text { + color: fineTuneView.textColor + text: "Name:" + + } + InputField { + text: echonestStation.name + + onAccepted: { + print("text changed!!!") + echonestStation.name = text; + } + } + + Text { + id: tempoText + text: "Tempo:" + color: "white" + } + DoubleSlider { + width: 500 + height: tempoText.height + min: 0 + max: 500 + lowerSliderPos: echonestStation.minTempo + upperSliderPos: echonestStation.maxTempo + onValueChanged: echonestStation.setTempo( lowerSliderPos, upperSliderPos ) + } + + Text { + id: hotnessText + text: "Hotness:" + color: "white" + } + DoubleSlider { + width: 500 + height: hotnessText.height + min: 0 + max: 100 + minLabel: "Less" + maxLabel: "More" + showFloatingLabel: false + lowerSliderPos: echonestStation.minHotttness * 100 + upperSliderPos: echonestStation.maxHotttness * 100 + onValueChanged: echonestStation.setHotttness( 1.0 * lowerSliderPos / 100, 1.0 * upperSliderPos / 100 ) + } + } + + + Button { + id: configureButton + onClicked: fineTuneView.done(); + text: "configure" + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + } + +} diff --git a/data/qml/stations/StationCreatorPage1.qml b/data/qml/stations/StationCreatorPage1.qml new file mode 100644 index 000000000..826d69d69 --- /dev/null +++ b/data/qml/stations/StationCreatorPage1.qml @@ -0,0 +1,64 @@ +import QtQuick 1.1 +import tomahawk 1.0 +import "../tomahawkimports" + + +Item { + id: root + property alias model: gridView.model + property int spacing: 10 + + signal itemClicked(int index) + + GridView { + id: gridView + anchors.centerIn: parent + width: root.width * 9 / 10 + height: cellHeight + + cellWidth: (width - 1) / 3 + cellHeight: cellWidth //* 10 / 16 + + delegate: Image { + width: gridView.cellWidth - root.spacing + height: gridView.cellHeight - root.spacing + source: image + smooth: true + + Rectangle { + id: textBackground + anchors { + left: parent.left + bottom: parent.bottom + right: parent.right + } + height: parent.height / 5 + color: "black" + opacity: .5 + + } + Text { + anchors.centerIn: textBackground + text: label + color: "white" + font.bold: true + } + Rectangle { + id: hoverShade + anchors.fill: parent + color: "white" + opacity: mouseArea.containsMouse ? .2 : 0 + + Behavior on opacity { + NumberAnimation { easing.type: Easing.Linear; duration: 300 } + } + } + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: root.itemClicked(index) + } + } + } +} diff --git a/data/qml/stations/StationCreatorPage2.qml b/data/qml/stations/StationCreatorPage2.qml new file mode 100644 index 000000000..34f4b65cd --- /dev/null +++ b/data/qml/stations/StationCreatorPage2.qml @@ -0,0 +1,25 @@ +import QtQuick 1.1 +import tomahawk 1.0 +import "../tomahawkimports" + +Item { + id: root + + property int margins: defaultFontHeight * 2 + property alias content: contentLoader.source + + signal next() + + Loader { + id: contentLoader + anchors.fill: parent + anchors.margins: root.margins + } + + Connections { + target: contentLoader.item + + onDone: root.next() + } + +} diff --git a/data/qml/tomahawkimports/ArtistView.qml b/data/qml/tomahawkimports/ArtistView.qml new file mode 100644 index 000000000..a35c9b428 --- /dev/null +++ b/data/qml/tomahawkimports/ArtistView.qml @@ -0,0 +1,71 @@ +import QtQuick 1.1 +import tomahawk 1.0 + +ListView { + id: root + + property int delegateHeight: defaultFontHeight * 3 + + signal itemClicked(int index) + + delegate: Item { + width: parent.width + height: root.delegateHeight + + Rectangle { + id: background + anchors.fill: parent + radius: defaultFontHeight / 2 + opacity: 0.5 + gradient: Gradient { + GradientStop { position: 0.0; color: "#00FFFFFF" } + GradientStop { position: 1.0; color: "#AAFFFFFF" } + } + + states: [ + State { + name: "hovered"; when: mouseArea.containsMouse + PropertyChanges { target: background; opacity: 1 } + } + ] + + transitions: [ + Transition { + from: "*"; to: "hovered" + NumberAnimation { properties: "opacity"; duration: 100 } + }, + Transition { + from: "hovered"; to: "*" + NumberAnimation { properties: "opacity"; duration: 600 } + } + ] + } + + Row { + anchors.fill: parent + spacing: defaultFontHeight + + CoverImage { + id: coverImage + height: parent.height + width: height + showLabels: false + artworkId: model.coverID + } + Text { + text: model.artistName + color: "white" + anchors.verticalCenter: parent.verticalCenter + width: parent.width - coverImage.width - parent.spacing + elide: Text.ElideRight + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: root.itemClicked(index) + hoverEnabled: true + } + } +} diff --git a/data/qml/tomahawkimports/BusyIndicator.qml b/data/qml/tomahawkimports/BusyIndicator.qml new file mode 100644 index 000000000..52b6e0fba --- /dev/null +++ b/data/qml/tomahawkimports/BusyIndicator.qml @@ -0,0 +1,52 @@ +import QtQuick 1.1 + +Item { + id: busyIndicator + width: 100 + height: width + property int barWidth: width / 10 + property int barHeight: height / 4 + property int count: 12 + property color color: "white" + property int currentHighlight: 0 + property bool running: true + property int interval: 200 + + Behavior on opacity { + NumberAnimation { duration: 500 } + } + + Repeater { + model: busyIndicator.count + + + Item { + height: parent.height + width: busyIndicator.barWidth + anchors.centerIn: parent + Rectangle { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: busyIndicator.barHeight + radius: width / 2 + + color: busyIndicator.color + } + rotation: 360 / busyIndicator.count * index + opacity: 1 - ((index > busyIndicator.currentHighlight ? busyIndicator.currentHighlight + busyIndicator.count : busyIndicator.currentHighlight) - index) / busyIndicator.count + Behavior on opacity { + NumberAnimation { duration: busyIndicator.interval } + } + } + } + + Timer { + interval: busyIndicator.interval + running: busyIndicator.running + repeat: true + onTriggered: parent.currentHighlight = (parent.currentHighlight + 1) % busyIndicator.count + } +} diff --git a/data/qml/tomahawkimports/Button.qml b/data/qml/tomahawkimports/Button.qml new file mode 100644 index 000000000..1c73d7c8c --- /dev/null +++ b/data/qml/tomahawkimports/Button.qml @@ -0,0 +1,29 @@ +import QtQuick 1.1 + +Rectangle { + id: root + color: buttonMouseArea.containsMouse ? "blue" : "gray" + border.width: 2 + border.color: "white" + radius: height/2 + height: buttonText.height * 1.2 + width: buttonText.width * 1.5 + + property alias text: buttonText.text + property color textColor: "white" + + signal clicked() + + Text { + id: buttonText + anchors.centerIn: parent + color: root.textColor + } + + MouseArea { + id: buttonMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: root.clicked(); + } +} diff --git a/data/qml/tomahawkimports/CoverFlip.qml b/data/qml/tomahawkimports/CoverFlip.qml new file mode 100644 index 000000000..58e85824c --- /dev/null +++ b/data/qml/tomahawkimports/CoverFlip.qml @@ -0,0 +1,137 @@ +import QtQuick 1.1 +import tomahawk 1.0 + +PathView { + id: coverView + + // The start coordinates for the covers + // Default is left, centered in height + property int pathStartX: 0 + property int pathStartY: height + + // The size of the covers in the path + property int coverSize: height + + property color backgroundColor: "black" + + // emitted when a cover is clicked + signal itemClicked(int index) + + // emitted when a cover is clicked + signal itemPlayPauseClicked(int index) + + preferredHighlightBegin: 0.2 // scene.width / 11000 + preferredHighlightEnd: preferredHighlightBegin + pathItemCount: 5 + //highlightMoveDuration: 500 + + property bool itemHovered: false + + delegate: Item { + id: delegateItem + height: coverView.coverSize + width: coverView.coverSize + + scale: PathView.itemScale + // itemBrightness: PathView.itemBrightness - ((coverView.itemHovered && !coverDelegate.containsMouse) ? .4 : 0) + property double itemBrightness: PathView.itemBrightness + property double itemOpacity: PathView.itemOpacity + property int _origZ + + z: coverView.width - x + + CoverImage { + id: coverDelegate + height: coverView.coverSize + width: coverView.coverSize + anchors { + top: parent.top + right: parent.right + } + + backgroundColor: coverView.backgroundColor + + showLabels: true + showMirror: true + artistName: model.artistName + trackName: model.trackName + artworkId: model.coverID + showPlayButton: true + currentlyPlaying: isPlaying + smooth: true + + // itemBrightness: PathView.itemBrightness - ((coverView.itemHovered && !coverDelegate.containsMouse) ? .4 : 0) + itemBrightness: coverDelegate.containsMouse ? 1 : parent.itemBrightness * (coverView.itemHovered ? .5 : 1) + opacity: parent.itemOpacity + z: coverView.width - x + + onPlayClicked: { + console.log("***************") + coverView.itemPlayPauseClicked(index) + } + + onClicked: { + coverView.itemClicked(index) + } + + onContainsMouseChanged: { + if (containsMouse) { + delegateItem._origZ = delegateItem.z; + coverView.itemHovered = true + } else { + coverView.itemHovered = false + } + } + + + } + states: [ + State { + name: "hovered"; when: coverDelegate.containsMouse && !coverView.moving && index !== currentIndex + PropertyChanges { + target: delegateItem + width: coverView.coverSize * 2 + z: delegateItem._origZ + } + } + ] + transitions: [ + Transition { + NumberAnimation { + properties: "width" + duration: 300 + easing.type: Easing.InOutSine + } + + } + ] + } + + path: Path { + startX: coverView.pathStartX + startY: coverView.pathStartY + + PathAttribute { name: "itemOpacity"; value: 0 } + PathAttribute { name: "itemBrightness"; value: 0 } + PathAttribute { name: "itemScale"; value: 1.3 } + + PathLine { x: coverView.width / 4; y: coverView.height / 4 * 3} + PathPercent { value: 0.1 } + PathAttribute { name: "itemOpacity"; value: 0 } + PathAttribute { name: "itemBrightness"; value: 1 } + PathAttribute { name: "itemScale"; value: 1.0 } + + PathLine { x: coverView.width / 2; y: coverView.height / 2} + PathPercent { value: 0.2 } + PathAttribute { name: "itemOpacity"; value: 1 } + PathAttribute { name: "itemBrightness"; value: 1 } + PathAttribute { name: "itemScale"; value: 0.5 } + + PathLine { x: coverView.width; y: 0 } + PathPercent { value: 1 } + PathAttribute { name: "itemOpacity"; value: 1 } + PathAttribute { name: "itemBrightness"; value: 0 } + PathAttribute { name: "itemScale"; value: 0.1 } + } + +} diff --git a/data/qml/tomahawkimports/CoverImage.qml b/data/qml/tomahawkimports/CoverImage.qml new file mode 100644 index 000000000..518137aac --- /dev/null +++ b/data/qml/tomahawkimports/CoverImage.qml @@ -0,0 +1,197 @@ +import QtQuick 1.1 + +Item { + id: root + + // Should the artist + track labels be painted + property bool showLabels: true + + // Should the play button be painted on mouse hover? + property bool showPlayButton: false + + // if this is true, the play button will be swapped by a pause button + property bool currentlyPlaying: false + + // Should the mirror be painted? + property bool showMirror: false + + // 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 + + // set this to true if you want to smoothly scale the cover (be aware of performance impacts) + property bool smooth: false + + // will be emitted when the on hower play button is clicked + signal playClicked() + // will be emitted when the cover is clicked + signal clicked() + // will be emitted when the cover is hovered by the mouse + property alias containsMouse: mouseArea.containsMouse + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + + onClicked: { + print("Cover clicked"); + root.clicked(); + } + } + + Rectangle { + id: itemShadow + color: backgroundColor + anchors.fill: parent + + //opacity: 1 - itemBrightness + + Behavior on opacity { + NumberAnimation { easing.type: Easing.Linear; duration: 300 } + } + } + + Component { + id: coverImage + + Item { + property bool isMirror: false + + Image { + anchors.fill: parent + source: "image://albumart/" + artworkId + (isMirror ? "-mirror" : "") + (showLabels ? "-labels" : "") + smooth: root.smooth + opacity: itemBrightness + Behavior on opacity { + NumberAnimation { duration: 300 } + } + } + + Rectangle { + id: itemGlow + anchors.fill: parent + anchors.topMargin: isMirror ? parent.height / 2 : 0 + + opacity: (mouseArea.containsMouse ? .2 : 0) + + Gradient { + id: glowGradient + GradientStop { position: 0.0; color: "white" } + GradientStop { position: 0.7; color: "white" } + GradientStop { position: 0.8; color: "#00000000" } + GradientStop { position: 1.0; color: "#00000000" } + } + Gradient { + id: mirrorGlowGradient + GradientStop { position: 0.0; color: "#00000000" } + GradientStop { position: 0.5; color: "#00000000" } + GradientStop { position: 1.0; color: "#44FFFFFF" } + } + + states: [ + State { + name: "mirrored"; when: isMirror + PropertyChanges { + target: itemGlow + gradient: mirrorGlowGradient + } + }, + State { + name: "normal"; when: !isMirror + PropertyChanges { + target: itemGlow + gradient: glowGradient + } + } + ] + + Behavior on opacity { + NumberAnimation { easing.type: Easing.Linear; duration: 300 } + } + } + + Text { + id: trackText + color: "white" + font.bold: true + text: trackName + anchors { left: parent.left; right: parent.right; bottom: artistText.top } + anchors.margins: 2 + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + opacity: showLabels ? itemBrightness * (isMirror ? 0.5 : 1): 0 + font.pixelSize: root.height / 15 + Behavior on opacity { + NumberAnimation { duration: 300 } + } + } + Text { + id: artistText + color: "white" + font.bold: trackText.text.length == 0 + text: artistName + anchors { left: parent.left; right: parent.right; bottom: parent.bottom } + anchors.margins: root.height / 20 + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + opacity: showLabels ? itemBrightness * (isMirror ? 0.5 : 1) : 0 + font.pixelSize: trackText.text.length == 0 ? root.height / 10 : root.height / 15 + Behavior on opacity { + NumberAnimation { duration: 300 } + } + } + } + + } + Loader { + sourceComponent: coverImage + anchors.fill: parent + } + + Loader { + id: mirroredCover + sourceComponent: parent.showMirror ? coverImage : undefined + anchors.fill: parent + onLoaded: { + item.isMirror = true + } + transform : [ + Rotation { + angle: 180; origin.y: root.height + axis.x: 1; axis.y: 0; axis.z: 0 + } + ] + } + + Image { + id: playButton + visible: showPlayButton ? (mouseArea.containsMouse || currentlyPlaying) : false + source: currentlyPlaying ? "../../images/pause-rest.svg" : "../../images/play-rest.svg" + anchors.centerIn: parent + height: mirroredCover.height / 5 + width: height + smooth: root.smooth + MouseArea { + anchors.fill: parent + onClicked: { + print("Play button clicked"); + root.playClicked(); + } + } + } + +} diff --git a/data/qml/tomahawkimports/DoubleSlider.qml b/data/qml/tomahawkimports/DoubleSlider.qml new file mode 100644 index 000000000..b1dc522cc --- /dev/null +++ b/data/qml/tomahawkimports/DoubleSlider.qml @@ -0,0 +1,151 @@ +import QtQuick 1.1 + +Item { + id: root + width: 500 + height: 10 + + property int min: 0 + property int max: 100 + + /** The labels next to the slider + * if empty, min and max values are used + */ + property string minLabel: "" + property string maxLabel: "" + + /** Should the floating label indicating the current position be shown? */ + property bool showFloatingLabel: true + + property int lowerSliderPos: 25 + property int upperSliderPos: 75 + + signal valueChanged() + + Row { + anchors.fill: parent + spacing: 10 + + Text { + id: minText + text: root.minLabel.length > 0 ? root.minLabel : min + color: "white" + } + + Item { + id: sliderRect + height: root.height + width: parent.width - minText.width - maxText.width - parent.spacing * 2 + + function sliderPosToValue( sliderPos ) { + var percent = sliderPos * 100 / (sliderRect.width - lowerSlider.width); + return Math.floor(percent * (root.max - root.min) / 100) + root.min + } + + function valueToSloderPos( value ) { + var percent = (value - root.min) * 100 / (root.max - root.min) + return percent * (sliderRect.width - lowerSlider.width) / 100 + } + + Rectangle { + id: sliderBase + height: root.height / 5 + width: parent.width + color: "white" + radius: height / 2 + anchors.centerIn: parent + + } + Rectangle { + id: lowerSlider + height: root.height + width: height + anchors.top: root.top + radius: height/2 + border.color: "black" + border.width: 2 + x: sliderRect.valueToSloderPos(root.lowerSliderPos) + + Rectangle { + id: lowerFloatingRect + color: "white" + anchors.bottom: lowerSlider.top + anchors.bottomMargin: 10 + visible: root.showFloatingLabel && lowerSliderMouseArea.pressed + width: lowerFloatingText.width * 1.2 + height: lowerFloatingText.height + height * 1.2 + x: -(width - lowerSlider.width) / 2 + radius: height / 4 + + Text { + id: lowerFloatingText + anchors.centerIn: parent + text: sliderRect.sliderPosToValue(lowerSlider.x) + } + } + } + MouseArea { + id: lowerSliderMouseArea + anchors.fill: lowerSlider + drag.target: lowerSlider + drag.axis: "XAxis" + drag.minimumX: 0 + drag.maximumX: upperSlider.x - lowerSlider.width + onReleased: { + root.lowerSliderPos = sliderRect.sliderPosToValue( lowerSlider.x ); + root.valueChanged(); + } + } + + Rectangle { + id: upperSlider + height: root.height + width: height + anchors.top: root.top + radius: height/2 + border.color: "black" + border.width: 2 + x: sliderRect.valueToSloderPos(root.upperSliderPos) + Rectangle { + id: upperFloatingRect + color: "white" + anchors.bottom: upperSlider.top + anchors.bottomMargin: 10 + visible: root.showFloatingLabel && upperSliderMouseArea.pressed + width: upperFloatingText.width * 1.2 + height: upperFloatingText.height + height * 1.2 + radius: height / 4 + x: -(width - upperSlider.width) / 2 + + Text { + id: upperFloatingText + anchors.centerIn: parent + text: sliderRect.sliderPosToValue(upperSlider.x) + } + } + + } + MouseArea { + id: upperSliderMouseArea + anchors.fill: upperSlider + onClicked: print("button pressed") + drag.target: upperSlider + drag.axis: "XAxis" + drag.minimumX: lowerSlider.x + lowerSlider.width + drag.maximumX: parent.width - upperSlider.width + onReleased: { + root.upperSliderPos = sliderRect.sliderPosToValue( upperSlider.x ); + root.valueChanged(); + } + + } + } + + + Text { + id: maxText + text: root.maxLabel.length > 0 ? root.maxLabel : max + color: "white" + } + } +} diff --git a/data/qml/tomahawkimports/FlexibleHeader.qml b/data/qml/tomahawkimports/FlexibleHeader.qml new file mode 100644 index 000000000..04c5e2566 --- /dev/null +++ b/data/qml/tomahawkimports/FlexibleHeader.qml @@ -0,0 +1,199 @@ +import QtQuick 1.1 +import tomahawk 1.0 + +Rectangle { + id: root + + // The icon + property alias icon: iconImage.source + + // The title + property alias title: titleItem.titleText + + // The subtitle/description + property alias subtitle: subtitleText.text + + // The model for the ToggleViewButtons. + // "modelData" role name holds the iconSource + // => You can use a QStringList or StandardListModel here + property alias buttonModel: toggleViewButtons.model + + // The index of the currently selected item + property alias currentButtonIndex: toggleViewButtons.currentIndex + + // Should we show the searchfield? + property bool showSearchField: true + + // The SearchFields text + property alias searchText: searchField.text + + property bool showBackButton: false + property bool showNextButton: false + + property string backButtonText: "Back" + property string nextButtonText: "Next" + + // Layout spacing + property int spacing: defaultFontHeight * 0.5 + + signal backPressed() + signal nextPressed() + signal savePressed() + + gradient: Gradient { + GradientStop { position: 0.0; color: "#615858" } + GradientStop { position: 1.0; color: "#231F1F" } + } + + Row { + id: leftRow + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + right: rightRow.left + } + + anchors.margins: root.spacing + spacing: root.spacing + + Image { + id: iconImage + height: parent.height * 0.8 + width: height + anchors.verticalCenter: parent.verticalCenter + smooth: true + } + + Column { + height: parent.height + width: parent.width - iconImage.width - parent.spacing + + Item { + id: titleItem + height: captionText1.height + width: parent.width + clip: true + + property string titleText + + onTitleTextChanged: { + if(captionText1.text.length > 0) { + captionText2.text = titleText; + renewTitleAnimation.start(); + } else { + captionText1.text = titleText; + } + } + + ParallelAnimation { + id: renewTitleAnimation + property int duration: 500 + property variant easingType: Easing.OutBounce; + + NumberAnimation { target: captionText2; property: "anchors.topMargin"; to: 0; duration: renewTitleAnimation.duration; easing.type: renewTitleAnimation.easingType } + NumberAnimation { target: captionText1; property: "anchors.topMargin"; to: captionText1.height * 2; duration: renewTitleAnimation.duration; easing.type: renewTitleAnimation.easingType } + + onCompleted: { + captionText1.text = titleItem.titleText + captionText2.anchors.topMargin = -captionText2.height * 2 + captionText1.anchors.topMargin = 0 + } + } + + Text { + id: captionText1 + color: "white" + anchors.left: parent.left + anchors.top: parent.top + + font.pointSize: defaultFontSize * 1.5 + font.bold: true + width: parent.width + elide: Text.ElideRight + } + Text { + id: captionText2 + color: "white" + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: -height * 2 + font.pointSize: defaultFontSize * 1.5 + font.bold: true + width: parent.width + elide: Text.ElideRight + } + + } + Text { + id: subtitleText + color: "white" + font.pointSize: defaultFontSize * 1.2 + width: parent.width + elide: Text.ElideRight + } + } + + } + + Row { + id: rightRow + anchors { + top: parent.top + right: parent.right + rightMargin: -backButton.width - root.spacing - nextButton.width + bottom: parent.bottom + margins: root.spacing + } + width: childrenRect.width + spacing: root.spacing + layoutDirection: Qt.RightToLeft + + states: [ + State { + name: "oneVisible"; when: root.showBackButton && !root.showNextButton + PropertyChanges { + target: rightRow + anchors.rightMargin: -nextButton.width + } + }, + State { + name: "bothVisible"; when: root.showBackButton && root.showNextButton + PropertyChanges { + target: rightRow + anchors.rightMargin: root.spacing + } + } + + ] + + Behavior on anchors.rightMargin { + NumberAnimation { duration: 200 } + } + + PushButton { + id: nextButton + anchors.verticalCenter: parent.verticalCenter + text: root.nextButtonText + onClicked: root.nextPressed(); + } + PushButton { + id: backButton + anchors.verticalCenter: parent.verticalCenter + text: root.backButtonText + onClicked: root.backPressed(); + } + InputField { + id: searchField + visible: root.showSearchField + anchors.verticalCenter: parent.verticalCenter + placeholderText: "Search..." + showSearchIcon: true + } + ToggleViewButtons { + id: toggleViewButtons + anchors.verticalCenter: parent.verticalCenter + height: defaultFontHeight * 1.5 + } + } +} diff --git a/data/qml/tomahawkimports/HeaderLabel.qml b/data/qml/tomahawkimports/HeaderLabel.qml new file mode 100644 index 000000000..ebffedbd4 --- /dev/null +++ b/data/qml/tomahawkimports/HeaderLabel.qml @@ -0,0 +1,7 @@ +import QtQuick 1.1 + +Text { + color: "white" + font.pointSize: defaultFontSize + 5 + font.bold: true +} diff --git a/data/qml/tomahawkimports/InputField.qml b/data/qml/tomahawkimports/InputField.qml new file mode 100644 index 000000000..9f052124d --- /dev/null +++ b/data/qml/tomahawkimports/InputField.qml @@ -0,0 +1,90 @@ +import QtQuick 1.1 + +Rectangle { + id: root + color: "white" + border.color: "black" + border.width: defaultFontHeight * 0.1 + radius: defaultFontHeight * 0.25 + + height: textInput.height * 1.4 + width: 300 + + property bool showSearchIcon: false + property string text: "" + property string placeholderText: "" + + property int spacing: defaultFontHeight * 0.2 + signal accepted( string text ) + + Image { + id: searchIcon + anchors { + left: parent.left + leftMargin: root.spacing + verticalCenter: parent.verticalCenter + } + height: parent.height * 0.6 + width: root.showSearchIcon ? height : 1 + opacity: root.showSearchIcon ? 1 : 0 + smooth: true + source: "../../images/search-icon.svg" + } + + Item { + id: textItem + anchors.left: searchIcon.right + anchors.leftMargin: root.spacing + anchors.right: clearIcon.right + anchors.rightMargin: root.spacing + height: textInput.height + anchors.verticalCenter: parent.verticalCenter + + TextInput { + id: textInput + width: parent.width + anchors.centerIn: parent + text: root.text + font.pointSize: defaultFontSize + + onAccepted: root.accepted( text ); + onTextChanged: root.text = text; + } + Text { + width: parent.width + anchors.centerIn: parent + text: root.text.length === 0 ? root.placeholderText : "" + color: "lightgray" + font.pointSize: defaultFontSize + } + } + + Image { + id: clearIcon + anchors { + right: parent.right + rightMargin: root.spacing + verticalCenter: parent.verticalCenter + } + height: parent.height * 0.8 + width: (root.showSearchIcon && root.text.length > 0) ? height : 1 + opacity: (root.showSearchIcon && root.text.length > 0) ? 1 : 0 + smooth: true + source: "../../images/search-box-dismiss-x.svg" + + MouseArea { + anchors.fill: parent + onClicked: textInput.text = "" + } + } + + + BorderImage { + source: "../../images/inputfield-border.svg" + anchors.fill: parent + anchors.margins: root.radius * 0.1 + clip: true + border.left: defaultFontHeight/4; border.top: defaultFontHeight/4 + border.right: defaultFontHeight/4; border.bottom: defaultFontHeight/4 + } +} diff --git a/data/qml/tomahawkimports/PushButton.qml b/data/qml/tomahawkimports/PushButton.qml new file mode 100644 index 000000000..b2c4b2503 --- /dev/null +++ b/data/qml/tomahawkimports/PushButton.qml @@ -0,0 +1,34 @@ +import QtQuick 1.1 +//import tomahawk 1.0 + +Rectangle { + id: root + height: buttonText.height * 1.4 + width: buttonText.width + (spacing * 2) + radius: defaultFontHeight * 0.25 + border.width: defaultFontHeight * 0.05 + border.color: "#a7a7a7" + + gradient: Gradient { + GradientStop { position: 0.0; color: mouseArea.pressed ? "#040404" : "#fbfbfb" } + GradientStop { position: 1.0; color: mouseArea.pressed ? "#8e8f8e" : "#787878" } + } + + property int spacing: defaultFontHeight * 0.5 + property alias text: buttonText.text + + signal clicked() + + Text { + id: buttonText + anchors.centerIn: root + font.pointSize: defaultFontSize + color: mouseArea.pressed ? "white" : "black" + } + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: root.clicked() + } +} diff --git a/data/qml/tomahawkimports/RoundedButton.qml b/data/qml/tomahawkimports/RoundedButton.qml new file mode 100644 index 000000000..790afe227 --- /dev/null +++ b/data/qml/tomahawkimports/RoundedButton.qml @@ -0,0 +1,43 @@ +import QtQuick 1.1 +import tomahawk 1.0 + +Rectangle { + id: root + border.width: 4 + border.color: enabled ? "white" : "grey" + radius: height / 2 + color: (buttonMouseArea.containsMouse && enabled) ? "#22ffffff" : "black" + opacity: hidden ? 0 : 1 + + height: defaultFontHeight * 2 + width: height + + property string text + property bool enabled: true + property bool hidden: false + + signal clicked() + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + + Behavior on color { + ColorAnimation { duration: 200 } + } + + Text { + anchors.centerIn: parent + text: parent.text + color: root.border.color + font.pixelSize: parent.height * .75 + font.bold: true + } + MouseArea { + id: buttonMouseArea + anchors.fill: parent + hoverEnabled: true + enabled: root.enabled + onClicked: parent.clicked() + } +} diff --git a/data/qml/tomahawkimports/ScrollBar.qml b/data/qml/tomahawkimports/ScrollBar.qml new file mode 100644 index 000000000..351e37803 --- /dev/null +++ b/data/qml/tomahawkimports/ScrollBar.qml @@ -0,0 +1,69 @@ +import QtQuick 1.1 + +Item { + id: scrollBar + width: defaultFontHeight / 2 + + // the ListView where to attach this scrollbar + property variant listView + // the orientation of the scrollbar + property variant orientation : Qt.Vertical + + property int margin: defaultFontHeight * 0.25 + + states: [ + State { + name: "hidden"; when: !listView.moving + PropertyChanges { target: scrollBar; opacity: 0 } + }, + State { + name: "visible"; when: listView.moving + PropertyChanges { target: scrollBar; opacity: 1 } + } + ] + transitions: [ + Transition { + from: "hidden" + to: "visible" + NumberAnimation { properties: "opacity"; duration: 200 } + }, + Transition { + from: "visible" + to: "hidden" + NumberAnimation { properties: "opacity"; duration: 2000 } + } + ] + + anchors { + left: orientation == Qt.Vertical ? listView.right : listView.left + leftMargin: orientation == Qt.Vertical ? scrollBar.margin : 0 + top: orientation == Qt.Vertical ? listView.top : listView.bottom + topMargin: orientation == Qt.Vertical ? 0 : scrollBar.margin + bottom: orientation == Qt.Vertical ? listView.bottom : undefined + right: orientation == Qt.Vertical ? undefined : listView.right + } + + // A light, semi-transparent background + Rectangle { + id: background + anchors.fill: parent + radius: orientation == Qt.Vertical ? (width/2 - 1) : (height/2 - 1) + color: "white" + opacity: 0.2 + clip: true + // Size the bar to the required size, depending upon the orientation. + Rectangle { + property real position: orientation == Qt.Vertical ? (listView.contentY / listView.contentHeight) : (listView.contentX / listView.contentWidth) + property real pageSize: orientation == Qt.Vertical ? (listView.height / listView.contentHeight) : (listView.width / listView.contentWidth) + + x: orientation == Qt.Vertical ? 1 : (position * (scrollBar.width-2) + 1) + y: orientation == Qt.Vertical ? (position * (scrollBar.height-2) + 1) : 1 + width: orientation == Qt.Vertical ? (parent.width-2) : (pageSize * (scrollBar.width-2)) + height: orientation == Qt.Vertical ? (pageSize * (scrollBar.height-2)) : (parent.height-2) + radius: orientation == Qt.Vertical ? (width/2 - 1) : (height/2 - 1) + color: "white" + opacity: 1 + } + } + +} diff --git a/data/qml/tomahawkimports/TagCloud.qml b/data/qml/tomahawkimports/TagCloud.qml new file mode 100644 index 000000000..ba3de03aa --- /dev/null +++ b/data/qml/tomahawkimports/TagCloud.qml @@ -0,0 +1,80 @@ +import QtQuick 1.1 +import tomahawk 1.0 + +Item { + id: tagCloud + + property variant model: 10 + + signal tagClicked( string tag ) + + function randomNumber(min, max) { + var date = new Date(); + return (max - min) * Math.random(date.getSeconds()) + min + } + + Flow { + anchors.centerIn: parent + width: parent.width + spacing: 3 + + Repeater { + id: cloudRepeater + model: tagCloud.model + + delegate: Item { + id: cloudItem + width: delegateText.width * 1.1 + height: delegateText.height + property double itemScale: Math.random() + .3 + scale: itemScale + Text { + id: delegateText + color: "gray" + //text: controlModel.controlAt( index ).summary + text: modelData + font.pointSize: 16 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: tagCloud.randomNumber(0, 15) + + states: [ + State { + name: "hovered"; when: cloudItemMouseArea.containsMouse + PropertyChanges { + target: delegateText + color: "white" + } + } + ] + transitions: [ + Transition { + from: "*" + to: "hovered" + ColorAnimation { + duration: 200 + } + }, + Transition { + from: "hovered" + to: "*" + ColorAnimation { + duration: 1000 + } + } + ] + + } + MouseArea { + id: cloudItemMouseArea + hoverEnabled: true + anchors.fill: parent + onClicked: tagCloud.tagClicked( modelData ) + } + + Behavior on scale { + NumberAnimation { easing: Easing.Linear; duration: 1000 } + } + } + } + } +} diff --git a/data/qml/tomahawkimports/ToggleViewButtons.qml b/data/qml/tomahawkimports/ToggleViewButtons.qml new file mode 100644 index 000000000..066d5f4c9 --- /dev/null +++ b/data/qml/tomahawkimports/ToggleViewButtons.qml @@ -0,0 +1,34 @@ +import QtQuick 1.1 +import tomahawk 1.0 + +Row { + id: root + width: repeater.width + + property alias model: repeater.model + property int currentIndex: 0 + + Repeater { + id: repeater + height: root.height + width: count * height + + + delegate: Image { + height: repeater.height + width: height + + source: "../../images/view-toggle-" + (index === root.currentIndex ? "active-" : "inactive-" ) + (index === 0 ? "left" : ( index === repeater.count - 1 ? "right" : "centre" )) + ".svg" + smooth: true + Image { + anchors.fill: parent + source: "../../images/" + modelData + (index === root.currentIndex ? "-active.svg" : "-inactive.svg") + } + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: root.currentIndex = index + } + } + } +}