diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -372,6 +372,7 @@ qml/ContextViewLyrics.qml qml/ContentView.qml qml/ViewSelector.qml + qml/ViewSelectorDelegate.qml qml/DataGridView.qml qml/TracksView.qml qml/AlbumView.qml diff --git a/src/qml/AlbumView.qml b/src/qml/AlbumView.qml --- a/src/qml/AlbumView.qml +++ b/src/qml/AlbumView.qml @@ -45,7 +45,6 @@ id: albumGridView focus: true - activeFocusOnTab: true anchors.fill: parent @@ -75,6 +74,7 @@ rating: model.rating isFirstTrackOfDisc: true isSingleDiscAlbum: true + isSelected: albumGridView.currentIndex === index isAlternateColor: (index % 2) === 1 mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, ElisaUtils.Track, @@ -87,6 +87,12 @@ mediaTrack.onClicked: albumGridView.currentIndex = index + + onActiveFocusChanged: { + if (activeFocus && albumGridView.currentIndex !== index) { + albumGridView.currentIndex = index + } + } } allowArtistNavigation: true diff --git a/src/qml/ContentView.qml b/src/qml/ContentView.qml --- a/src/qml/ContentView.qml +++ b/src/qml/ContentView.qml @@ -260,11 +260,6 @@ } Rectangle { - border { - color: (mainContentView.activeFocus ? myPalette.highlight : myPalette.base) - width: 1 - } - radius: 3 color: myPalette.base diff --git a/src/qml/FileBrowserDelegate.qml b/src/qml/FileBrowserDelegate.qml --- a/src/qml/FileBrowserDelegate.qml +++ b/src/qml/FileBrowserDelegate.qml @@ -23,14 +23,15 @@ import org.kde.elisa 1.0 FocusScope { - id: fileDelegate + id: gridEntry property var fileName property var fileUrl property var imageUrl property var contentModel property bool isDirectory property bool isPlayList + property bool isSelected signal enqueue(var data) signal replaceAndPlay(var data) @@ -44,16 +45,29 @@ onLoaded: item.show() sourceComponent: MediaTrackMetadataView { - fileName: fileDelegate.fileUrl + fileName: gridEntry.fileUrl onRejected: metadataLoader.active = false; } } - Keys.onReturnPressed: fileDelegate.enqueue(fileUrl) - Keys.onEnterPressed: fileDelegate.enqueue(fileUrl) + Keys.onReturnPressed: gridEntry.enqueue(fileUrl) + Keys.onEnterPressed: gridEntry.enqueue(fileUrl) + + Rectangle { + id: stateIndicator + + anchors.fill: parent + z: 1 + + color: "transparent" + opacity: 0.4 + + radius: 3 + } ColumnLayout { anchors.fill: parent + z: 2 spacing: 0 @@ -63,13 +77,13 @@ hoverEnabled: true acceptedButtons: Qt.LeftButton - Layout.preferredHeight: fileDelegate.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + + Layout.preferredHeight: gridEntry.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) Layout.fillWidth: true - onClicked: fileDelegate.selected() + onClicked: gridEntry.selected() - onDoubleClicked: fileDelegate.open(fileUrl) + onDoubleClicked: gridEntry.open(fileUrl) TextMetrics { id: mainLabelSize @@ -84,8 +98,8 @@ anchors.fill: parent Item { - Layout.preferredHeight: fileDelegate.width * 0.85 - Layout.preferredWidth: fileDelegate.width * 0.85 + Layout.preferredHeight: gridEntry.width * 0.85 + Layout.preferredWidth: gridEntry.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter @@ -197,7 +211,7 @@ horizontalAlignment: Text.AlignHCenter Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 - Layout.maximumWidth: fileDelegate.width * 0.9 + Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.maximumHeight: (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) * 2 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom @@ -221,7 +235,15 @@ states: [ State { name: 'notSelected' - when: !fileDelegate.activeFocus && !hoverArea.containsMouse + when: !gridEntry.activeFocus && !hoverHandle.containsMouse && !gridEntry.isSelected + PropertyChanges { + target: stateIndicator + color: 'transparent' + } + PropertyChanges { + target: stateIndicator + opacity: 1.0 + } PropertyChanges { target: hoverLoader active: false @@ -231,9 +253,57 @@ opacity: 0.0 } }, + State { + name: 'hovered' + when: hoverHandle.containsMouse && !gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.highlight + } + PropertyChanges { + target: stateIndicator + opacity: 0.2 + } + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + }, + State { + name: 'selected' + when: gridEntry.isSelected && !gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.mid + } + PropertyChanges { + target: stateIndicator + opacity: 0.6 + } + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0. + } + }, State { name: 'hoveredOrSelected' - when: fileDelegate.activeFocus || hoverArea.containsMouse + when: gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.highlight + } + PropertyChanges { + target: stateIndicator + opacity: 0.6 + } PropertyChanges { target: hoverLoader active: true @@ -247,28 +317,21 @@ transitions: [ Transition { - to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - } - }, - Transition { - to: 'notSelected' - SequentialAnimation { - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - PropertyAction { - properties: "active" + ParallelAnimation { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 300 + } + ColorAnimation { + properties: "color" + easing.type: Easing.InOutQuad + duration: 300 + } } } } diff --git a/src/qml/FileBrowserView.qml b/src/qml/FileBrowserView.qml --- a/src/qml/FileBrowserView.qml +++ b/src/qml/FileBrowserView.qml @@ -104,13 +104,16 @@ anchors.topMargin: 20 anchors.fill: parent - focus: true + activeFocusOnTab: true + keyNavigationEnabled: true ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds + currentIndex: -1 + model: proxyModel ScrollHelper { @@ -143,8 +146,11 @@ delegate: FileBrowserDelegate { width: contentDirectoryView.cellWidth height: contentDirectoryView.cellHeight + focus: true + isSelected: contentDirectoryView.currentIndex === index + isDirectory: model.directory isPlayList: model.isPlaylist fileName: model.name @@ -158,6 +164,13 @@ forceActiveFocus() contentDirectoryView.currentIndex = model.index } + + onActiveFocusChanged: { + if (activeFocus && contentDirectoryView.currentIndex !== model.index) { + contentDirectoryView.currentIndex = model.index + } + } + onOpen: loadFolderAndClear(data) } } diff --git a/src/qml/FrequentlyPlayedTracks.qml b/src/qml/FrequentlyPlayedTracks.qml --- a/src/qml/FrequentlyPlayedTracks.qml +++ b/src/qml/FrequentlyPlayedTracks.qml @@ -47,7 +47,6 @@ id: listView focus: true - activeFocusOnTab: true anchors.fill: parent @@ -65,14 +64,16 @@ title: model.title artist: model.artist album: (model.album !== undefined && model.album !== '' ? model.album : '') - albumArtist: model.albumArtist + albumArtist: (model.albumArtist !== undefined && model.albumArtist !== '' ? model.albumArtist : '') duration: model.duration imageUrl: (model.imageUrl !== undefined && model.imageUrl !== '' ? model.imageUrl : '') trackNumber: model.trackNumber discNumber: model.discNumber rating: model.rating isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum + isSelected: listView.currentIndex === index + isAlternateColor: (index % 2) === 1 onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.AppendPlayList, @@ -82,7 +83,10 @@ ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) - onClicked: contentDirectoryView.currentIndex = index + onClicked: { + listView.currentIndex = index + entry.forceActiveFocus() + } } Loader { diff --git a/src/qml/GridBrowserDelegate.qml b/src/qml/GridBrowserDelegate.qml --- a/src/qml/GridBrowserDelegate.qml +++ b/src/qml/GridBrowserDelegate.qml @@ -33,6 +33,7 @@ property var databaseId property bool delegateDisplaySecondaryText: true property bool isPartial + property bool isSelected signal enqueue(var databaseId, var name) signal replaceAndPlay(var databaseId, var name) @@ -42,8 +43,21 @@ Keys.onReturnPressed: open() Keys.onEnterPressed: open() + Rectangle { + id: stateIndicator + + anchors.fill: parent + z: 1 + + color: "transparent" + opacity: 0.4 + + radius: 3 + } + ColumnLayout { anchors.fill: parent + z: 2 spacing: 0 @@ -231,6 +245,7 @@ Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + Layout.bottomMargin: delegateDisplaySecondaryText ? 0 : elisaTheme.layoutVerticalMargin elide: Text.ElideRight } @@ -246,6 +261,7 @@ // See https://bugreports.qt.io/browse/QTBUG-49646 horizontalAlignment: Text.AlignHCenter + Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom @@ -265,7 +281,15 @@ states: [ State { name: 'notSelected' - when: !gridEntry.activeFocus && !hoverHandle.containsMouse + when: !gridEntry.activeFocus && !hoverHandle.containsMouse && !gridEntry.isSelected + PropertyChanges { + target: stateIndicator + color: 'transparent' + } + PropertyChanges { + target: stateIndicator + opacity: 1.0 + } PropertyChanges { target: hoverLoader active: false @@ -275,9 +299,57 @@ opacity: 0.0 } }, + State { + name: 'hovered' + when: hoverHandle.containsMouse && !gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.highlight + } + PropertyChanges { + target: stateIndicator + opacity: 0.2 + } + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + }, + State { + name: 'selected' + when: gridEntry.isSelected && !gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.mid + } + PropertyChanges { + target: stateIndicator + opacity: 0.6 + } + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0. + } + }, State { name: 'hoveredOrSelected' - when: gridEntry.activeFocus || hoverHandle.containsMouse + when: gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.highlight + } + PropertyChanges { + target: stateIndicator + opacity: 0.6 + } PropertyChanges { target: hoverLoader active: true @@ -291,28 +363,21 @@ transitions: [ Transition { - to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - } - }, - Transition { - to: 'notSelected' - SequentialAnimation { - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - PropertyAction { - properties: "active" + ParallelAnimation { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 300 + } + ColorAnimation { + properties: "color" + easing.type: Easing.InOutQuad + duration: 300 + } } } } diff --git a/src/qml/GridBrowserView.qml b/src/qml/GridBrowserView.qml --- a/src/qml/GridBrowserView.qml +++ b/src/qml/GridBrowserView.qml @@ -102,7 +102,6 @@ id: contentDirectoryView anchors.topMargin: 20 - focus: true activeFocusOnTab: true keyNavigationEnabled: true @@ -113,6 +112,8 @@ } boundsBehavior: Flickable.StopAtBounds + currentIndex: -1 + TextMetrics { id: secondaryLabelSize text: 'example' @@ -135,6 +136,8 @@ focus: true + isSelected: contentDirectoryView.currentIndex === index + isPartial: false mainText: model.display @@ -153,6 +156,12 @@ forceActiveFocus() contentDirectoryView.currentIndex = model.index } + + onActiveFocusChanged: { + if (activeFocus && contentDirectoryView.currentIndex !== model.index) { + contentDirectoryView.currentIndex = model.index + } + } } } } diff --git a/src/qml/ListBrowserView.qml b/src/qml/ListBrowserView.qml --- a/src/qml/ListBrowserView.qml +++ b/src/qml/ListBrowserView.qml @@ -98,16 +98,18 @@ Layout.fillHeight: true Layout.fillWidth: true + Layout.margins: 2 ListView { id: contentDirectoryView anchors.topMargin: 20 anchors.fill: parent - focus: true activeFocusOnTab: true keyNavigationEnabled: true + currentIndex: -1 + ScrollBar.vertical: ScrollBar { id: scrollBar } @@ -119,6 +121,10 @@ flickable: contentDirectoryView anchors.fill: contentDirectoryView } + + onCountChanged: if (count === 0) { + currentIndex = -1; + } } } } diff --git a/src/qml/MediaAlbumTrackDelegate.qml b/src/qml/MediaAlbumTrackDelegate.qml --- a/src/qml/MediaAlbumTrackDelegate.qml +++ b/src/qml/MediaAlbumTrackDelegate.qml @@ -35,6 +35,7 @@ property alias rating: mediaTrack.rating property alias isFirstTrackOfDisc: mediaTrack.isFirstTrackOfDisc property alias isSingleDiscAlbum: mediaTrack.isSingleDiscAlbum + property alias isSelected: mediaTrack.isSelected property alias isAlternateColor: mediaTrack.isAlternateColor ColumnLayout { diff --git a/src/qml/MediaTrackDelegate.qml b/src/qml/MediaTrackDelegate.qml --- a/src/qml/MediaTrackDelegate.qml +++ b/src/qml/MediaTrackDelegate.qml @@ -39,6 +39,7 @@ property int rating property bool isFirstTrackOfDisc property bool isSingleDiscAlbum + property bool isSelected property bool isAlternateColor property bool detailedView: true @@ -93,264 +94,264 @@ id: rowRoot anchors.fill: parent + z: 1 color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) + } - MouseArea { - id: hoverArea + MouseArea { + id: hoverArea - anchors.fill: parent + anchors.fill: parent + z: 2 - hoverEnabled: true - focus: true - acceptedButtons: Qt.LeftButton + hoverEnabled: true + acceptedButtons: Qt.LeftButton - onClicked: { - hoverArea.forceActiveFocus() - mediaTrack.clicked() - } + onClicked: { + mediaTrack.clicked() + } - onDoubleClicked: enqueue(databaseId, title) + onDoubleClicked: enqueue(databaseId, title) - RowLayout { - anchors.fill: parent - spacing: 0 + RowLayout { + anchors.fill: parent + spacing: 0 - LabelWithToolTip { - id: mainLabel + LabelWithToolTip { + id: mainLabel - visible: !detailedView + visible: !detailedView - text: { - if (trackNumber !== 0) { - if (artist !== albumArtist) - return i18nc("%1: track number. %2: track title. %3: artist name", - "%1 - %2 - %3", - trackNumber.toLocaleString(Qt.locale(), 'f', 0), - title, artist); - else - return i18nc("%1: track number. %2: track title.", - "%1 - %2", - trackNumber.toLocaleString(Qt.locale(), 'f', 0), - title); - } else { - if (artist !== albumArtist) - return i18nc("%1: track title. %2: artist name", - "%1 - %2", - title, artist); - else - return i18nc("%1: track title", - "%1", - title); - } + text: { + if (trackNumber !== 0) { + if (artist !== albumArtist) + return i18nc("%1: track number. %2: track title. %3: artist name", + "%1 - %2 - %3", + trackNumber.toLocaleString(Qt.locale(), 'f', 0), + title, artist); + else + return i18nc("%1: track number. %2: track title.", + "%1 - %2", + trackNumber.toLocaleString(Qt.locale(), 'f', 0), + title); + } else { + if (artist !== albumArtist) + return i18nc("%1: track title. %2: artist name", + "%1 - %2", + title, artist); + else + return i18nc("%1: track title", + "%1", + title); } + } - elide: Text.ElideRight - horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft - color: myPalette.text + color: myPalette.text - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true - Layout.leftMargin: { - if (!LayoutMirroring.enabled) - return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) - else - return 0 - } - Layout.rightMargin: { - if (LayoutMirroring.enabled) - return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) - else - return 0 - } + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: { + if (!LayoutMirroring.enabled) + return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) + else + return 0 + } + Layout.rightMargin: { + if (LayoutMirroring.enabled) + return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) + else + return 0 } + } - Item { - Layout.preferredHeight: mediaTrack.height * 0.9 - Layout.preferredWidth: mediaTrack.height * 0.9 + Item { + Layout.preferredHeight: mediaTrack.height * 0.9 + Layout.preferredWidth: mediaTrack.height * 0.9 - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - visible: detailedView + visible: detailedView - Image { - id: coverImageElement + Image { + id: coverImageElement - anchors.fill: parent + anchors.fill: parent - sourceSize.width: mediaTrack.height * 0.9 - sourceSize.height: mediaTrack.height * 0.9 - fillMode: Image.PreserveAspectFit - smooth: true + sourceSize.width: mediaTrack.height * 0.9 + sourceSize.height: mediaTrack.height * 0.9 + fillMode: Image.PreserveAspectFit + smooth: true - source: (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) + source: (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) - asynchronous: true + asynchronous: true - layer.enabled: imageUrl != '' + layer.enabled: imageUrl != '' - layer.effect: DropShadow { - source: coverImageElement + layer.effect: DropShadow { + source: coverImageElement - radius: 10 - spread: 0.1 - samples: 21 + radius: 10 + spread: 0.1 + samples: 21 - color: myPalette.shadow - } + color: myPalette.shadow } } + } - ColumnLayout { - visible: detailedView + ColumnLayout { + visible: detailedView - Layout.fillWidth: true - Layout.fillHeight: true - Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignLeft - spacing: 0 + spacing: 0 - LabelWithToolTip { - id: mainLabelDetailed + LabelWithToolTip { + id: mainLabelDetailed - text: { - if (trackNumber !== 0) { - return i18nc("%1: track number. %2: track title", "%1 - %2", - trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); - } else { - return title; - } + text: { + if (trackNumber !== 0) { + return i18nc("%1: track number. %2: track title", "%1 - %2", + trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); + } else { + return title; } + } - horizontalAlignment: Text.AlignLeft + horizontalAlignment: Text.AlignLeft - font.weight: Font.Bold - color: myPalette.text + font.weight: Font.Bold + color: myPalette.text - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.fillWidth: true - Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.fillWidth: true + Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 - elide: Text.ElideRight - } + elide: Text.ElideRight + } - Item { - Layout.fillHeight: true - } + Item { + Layout.fillHeight: true + } - LabelWithToolTip { - id: artistLabel + LabelWithToolTip { + id: artistLabel - text: { - var labelText = "" - if (artist) { - labelText += artist - } - if (album !== '') { - labelText += ' - ' + album - if (!isSingleDiscAlbum) { - labelText += ' - CD ' + discNumber - } + text: { + var labelText = "" + if (artist) { + labelText += artist + } + if (album !== '') { + labelText += ' - ' + album + if (!isSingleDiscAlbum) { + labelText += ' - CD ' + discNumber } - return labelText; } - horizontalAlignment: Text.AlignLeft + return labelText; + } + horizontalAlignment: Text.AlignLeft - font.weight: Font.Light - font.italic: true - color: myPalette.text + font.weight: Font.Light + font.italic: true + color: myPalette.text - Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.fillWidth: true - Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 2 + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.fillWidth: true + Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 2 - elide: Text.ElideRight - } + elide: Text.ElideRight } + } - Loader { - id: hoverLoader - active: false + Loader { + id: hoverLoader + active: false - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.rightMargin: 10 + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: 10 - z: 1 - opacity: 0 + z: 1 + opacity: 0 - sourceComponent: Row { - anchors.centerIn: parent + sourceComponent: Row { + anchors.centerIn: parent - Controls1.ToolButton { - id: detailsButton + Controls1.ToolButton { + id: detailsButton - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: elisaTheme.delegateHeight + width: elisaTheme.delegateHeight - action: viewDetailsAction - } + action: viewDetailsAction + } - Controls1.ToolButton { - id: enqueueButton + Controls1.ToolButton { + id: enqueueButton - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: elisaTheme.delegateHeight + width: elisaTheme.delegateHeight - action: enqueueAction - } + action: enqueueAction + } - Controls1.ToolButton { - id: clearAndEnqueueButton + Controls1.ToolButton { + id: clearAndEnqueueButton - scale: LayoutMirroring.enabled ? -1 : 1 + scale: LayoutMirroring.enabled ? -1 : 1 - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: elisaTheme.delegateHeight + width: elisaTheme.delegateHeight - action: replaceAndPlayAction - } + action: replaceAndPlayAction } } + } - RatingStar { - id: ratingWidget + RatingStar { + id: ratingWidget - starSize: elisaTheme.ratingStarSize + starSize: elisaTheme.ratingStarSize - starRating: rating + starRating: rating - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.leftMargin: elisaTheme.layoutHorizontalMargin - Layout.rightMargin: elisaTheme.layoutHorizontalMargin - } + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.rightMargin: elisaTheme.layoutHorizontalMargin + } - LabelWithToolTip { - id: durationLabel + LabelWithToolTip { + id: durationLabel - text: duration + text: duration - font.weight: Font.Light - color: myPalette.text + font.weight: Font.Light + color: myPalette.text - horizontalAlignment: Text.AlignRight + horizontalAlignment: Text.AlignRight - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } states: [ State { name: 'notSelected' - when: !hoverArea.containsMouse && !mediaTrack.activeFocus + when: !mediaTrack.activeFocus && !hoverArea.containsMouse && !mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false @@ -367,10 +368,14 @@ target: rowRoot color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } + PropertyChanges { + target: rowRoot + opacity: 1 + } }, State { - name: 'hoveredOrSelected' - when: hoverArea.containsMouse || mediaTrack.activeFocus + name: 'hovered' + when: !mediaTrack.activeFocus && hoverArea.containsMouse PropertyChanges { target: hoverLoader active: true @@ -383,51 +388,83 @@ target: ratingWidget hoverWidgetOpacity: 1.0 } + PropertyChanges { + target: rowRoot + color: myPalette.highlight + } + PropertyChanges { + target: rowRoot + opacity: 0.2 + } + }, + State { + name: 'selected' + when: !mediaTrack.activeFocus && mediaTrack.isSelected + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: ratingWidget + hoverWidgetOpacity: 1.0 + } PropertyChanges { target: rowRoot color: myPalette.mid } + PropertyChanges { + target: rowRoot + opacity: 1. + } + }, + State { + name: 'focused' + when: mediaTrack.activeFocus + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + PropertyChanges { + target: ratingWidget + hoverWidgetOpacity: 1.0 + } + PropertyChanges { + target: rowRoot + color: myPalette.highlight + } + PropertyChanges { + target: rowRoot + opacity: 0.6 + } } ] transitions: [ Transition { - to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } ParallelAnimation { NumberAnimation { properties: "opacity, hoverWidgetOpacity" easing.type: Easing.InOutQuad - duration: 250 + duration: 200 } ColorAnimation { properties: "color" - duration: 250 + duration: 350 } } } - }, - Transition { - to: 'notSelected' - SequentialAnimation { - ParallelAnimation { - NumberAnimation { - properties: "opacity, hoverWidgetOpacity" - easing.type: Easing.InOutQuad - duration: 250 - } - ColorAnimation { - properties: "color" - duration: 250 - } - } - PropertyAction { - properties: "active" - } - } } ] } diff --git a/src/qml/NavigationActionBar.qml b/src/qml/NavigationActionBar.qml --- a/src/qml/NavigationActionBar.qml +++ b/src/qml/NavigationActionBar.qml @@ -21,9 +21,14 @@ import QtQuick.Controls 2.2 import QtQuick.Controls 1.4 as Controls1 -FocusScope { +ColumnLayout { id: navigationBar + spacing: 0 + + anchors.topMargin: elisaTheme.layoutVerticalMargin + anchors.bottomMargin: elisaTheme.layoutVerticalMargin + property string mainTitle property string secondaryTitle property url image @@ -73,290 +78,296 @@ onTriggered: sortOrder ? sort(Qt.DescendingOrder) : sort(Qt.AscendingOrder) } - ColumnLayout { - anchors.fill: parent + RowLayout { spacing: 0 + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: elisaTheme.navigationBarHeight + Layout.minimumHeight: elisaTheme.navigationBarHeight + Layout.maximumHeight: elisaTheme.navigationBarHeight - anchors.topMargin: elisaTheme.layoutVerticalMargin - anchors.bottomMargin: elisaTheme.layoutVerticalMargin + Controls1.ToolButton { + action: goPreviousAction + objectName: 'goPreviousButton' - RowLayout { - spacing: 0 - Layout.alignment: Qt.AlignTop - Layout.preferredHeight: elisaTheme.navigationBarHeight - Layout.minimumHeight: elisaTheme.navigationBarHeight - Layout.maximumHeight: elisaTheme.navigationBarHeight + Keys.onReturnPressed: action.trigger() - Controls1.ToolButton { - action: goPreviousAction - objectName: 'goPreviousButton' + activeFocusOnTab: true + focus: enableGoBack + + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + visible: enableGoBack + } - Keys.onReturnPressed: action.trigger() + Image { + id: mainIcon + source: image - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - visible: enableGoBack - } + asynchronous: true - Image { - id: mainIcon - source: image + sourceSize.height: elisaTheme.coverImageSize / 2 + sourceSize.width: elisaTheme.coverImageSize / 2 - asynchronous: true + fillMode: Image.PreserveAspectFit - sourceSize.height: elisaTheme.coverImageSize / 2 - sourceSize.width: elisaTheme.coverImageSize / 2 + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - fillMode: Image.PreserveAspectFit + Layout.preferredHeight: elisaTheme.coverImageSize / 2 + Layout.minimumHeight: elisaTheme.coverImageSize / 2 + Layout.maximumHeight: elisaTheme.coverImageSize / 2 + Layout.preferredWidth: elisaTheme.coverImageSize / 2 + Layout.minimumWidth: elisaTheme.coverImageSize / 2 + Layout.maximumWidth: elisaTheme.coverImageSize / 2 + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + } - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + ColumnLayout { + Layout.preferredHeight: elisaTheme.coverImageSize / 1.9 + Layout.minimumHeight: elisaTheme.coverImageSize / 1.9 + Layout.maximumHeight: elisaTheme.coverImageSize / 1.9 - Layout.preferredHeight: elisaTheme.coverImageSize / 2 - Layout.minimumHeight: elisaTheme.coverImageSize / 2 - Layout.maximumHeight: elisaTheme.coverImageSize / 2 - Layout.preferredWidth: elisaTheme.coverImageSize / 2 - Layout.minimumWidth: elisaTheme.coverImageSize / 2 - Layout.maximumWidth: elisaTheme.coverImageSize / 2 - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + spacing: 0 - ColumnLayout { - Layout.preferredHeight: elisaTheme.coverImageSize / 1.9 - Layout.minimumHeight: elisaTheme.coverImageSize / 1.9 - Layout.maximumHeight: elisaTheme.coverImageSize / 1.9 + Layout.fillWidth: true + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - spacing: 0 + LabelWithToolTip { + id: albumLabel + + text: mainTitle Layout.fillWidth: true - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.topMargin: secondaryTitle !== "" ? 0 : 9 - LabelWithToolTip { - id: albumLabel + elide: Text.ElideRight + fontSizeMode: Text.Fit - text: mainTitle + Layout.preferredHeight: elisaTheme.coverImageSize / 5 + Layout.minimumHeight: elisaTheme.coverImageSize / 5 + Layout.maximumHeight: elisaTheme.coverImageSize / 5 - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.topMargin: secondaryTitle !== "" ? 0 : 9 + color: myPalette.text + + font { + pointSize: elisaTheme.defaultFontPointSize * 2 + } + } - elide: Text.ElideRight - fontSizeMode: Text.Fit + LabelWithToolTip { + id: authorLabel - Layout.preferredHeight: elisaTheme.coverImageSize / 5 - Layout.minimumHeight: elisaTheme.coverImageSize / 5 - Layout.maximumHeight: elisaTheme.coverImageSize / 5 + text: secondaryTitle - color: myPalette.text + color: myPalette.text - font { - pointSize: elisaTheme.defaultFontPointSize * 2 - } + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + + font { + pointSize: elisaTheme.defaultFontPointSize } - LabelWithToolTip { - id: authorLabel + elide: Text.ElideRight - text: secondaryTitle + visible: secondaryTitle !== "" + } - color: myPalette.text + RowLayout { + Layout.fillWidth: true + spacing: 0 + Layout.bottomMargin: secondaryTitle !== "" ? 0 : 14 - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Controls1.Button { + objectName: 'enqueueButton' + text: i18nc("Add current list to playlist", "Enqueue") + iconName: "media-track-add-amarok" - font { - pointSize: elisaTheme.defaultFontPointSize - } + activeFocusOnTab: true + focus: true - elide: Text.ElideRight + onClicked: enqueue() + Keys.onReturnPressed: enqueue() - visible: secondaryTitle !== "" + Layout.leftMargin: 0 + Layout.rightMargin: 0 } - RowLayout { - Layout.fillWidth: true - spacing: 0 - Layout.bottomMargin: secondaryTitle !== "" ? 0 : 14 - - Controls1.Button { - objectName: 'enqueueButton' - text: i18nc("Add current list to playlist", "Enqueue") - iconName: "media-track-add-amarok" + Controls1.Button { + objectName: 'replaceAndPlayButton' + text: i18nc("Clear playlist and play", "Replace and Play") + tooltip: i18nc("Clear playlist and add current list to it", "Replace PlayList and Play Now") + iconName: "media-playback-start" - onClicked: enqueue() - Keys.onReturnPressed: enqueue() + activeFocusOnTab: true - Layout.leftMargin: 0 - Layout.rightMargin: 0 - } + onClicked: replaceAndPlay() + Keys.onReturnPressed: replaceAndPlay() - Controls1.Button { - objectName: 'replaceAndPlayButton' - text: i18nc("Clear playlist and play", "Replace and Play") - tooltip: i18nc("Clear playlist and add current list to it", "Replace PlayList and Play Now") - iconName: "media-playback-start" + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + } - onClicked: replaceAndPlay() - Keys.onReturnPressed: replaceAndPlay() + Controls1.Button { + objectName: 'showArtistButton' + id: showArtistButton - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + visible: allowArtistNavigation + text: i18nc("Button to navigate to the artist of the album", "Display Artist") + iconName: "view-media-artist" - Controls1.Button { - objectName: 'showArtistButton' - id: showArtistButton + activeFocusOnTab: true - visible: allowArtistNavigation - text: i18nc("Button to navigate to the artist of the album", "Display Artist") - iconName: "view-media-artist" + onClicked: showArtist() + Keys.onReturnPressed: showArtist() - onClicked: showArtist() - Keys.onReturnPressed: showArtist() + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + } - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + Item { + Layout.fillWidth: true + } - Item { - Layout.fillWidth: true - } + Controls1.ToolButton { + action: showFilterAction + objectName: 'showFilterButton' - Controls1.ToolButton { - action: showFilterAction - objectName: 'showFilterButton' + activeFocusOnTab: true - Keys.onReturnPressed: action.trigger() + Keys.onReturnPressed: action.trigger() - Layout.alignment: Qt.AlignRight - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + Layout.alignment: Qt.AlignRight + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } + } - RowLayout { - id: filterRow + RowLayout { + id: filterRow - spacing: 0 + spacing: 0 - visible: opacity > 0.0 + visible: opacity > 0.0 - opacity: 0 + opacity: 0 - Layout.preferredHeight: elisaTheme.navigationBarFilterHeight - Layout.minimumHeight: elisaTheme.navigationBarFilterHeight - Layout.maximumHeight: elisaTheme.navigationBarFilterHeight - Layout.fillWidth: true - Layout.topMargin: elisaTheme.layoutVerticalMargin * 2 - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.alignment: Qt.AlignTop + Layout.preferredHeight: elisaTheme.navigationBarFilterHeight + Layout.minimumHeight: elisaTheme.navigationBarFilterHeight + Layout.maximumHeight: elisaTheme.navigationBarFilterHeight + Layout.fillWidth: true + Layout.topMargin: elisaTheme.layoutVerticalMargin * 2 + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.alignment: Qt.AlignTop - LabelWithToolTip { - text: i18nc("before the TextField input of the filter", "Search: ") + LabelWithToolTip { + text: i18nc("before the TextField input of the filter", "Search: ") - font.bold: true + font.bold: true - Layout.bottomMargin: 0 + Layout.bottomMargin: 0 - color: myPalette.text - } + color: myPalette.text + } - TextField { - id: filterTextInput - objectName: 'filterTextInput' + TextField { + id: filterTextInput + objectName: 'filterTextInput' - horizontalAlignment: TextInput.AlignLeft + horizontalAlignment: TextInput.AlignLeft - placeholderText: i18nc("Placeholder text in the filter text box", "Album name, artist, etc.") + placeholderText: i18nc("Placeholder text in the filter text box", "Album name, artist, etc.") - Layout.bottomMargin: 0 - Layout.fillWidth: true - Layout.minimumWidth: (placeHolderTextWidth.boundingRect.width - placeHolderTextWidth.boundingRect.x) * 1.2 - implicitWidth: (placeHolderTextWidth.boundingRect.width - placeHolderTextWidth.boundingRect.x) * 1.2 + Layout.bottomMargin: 0 + Layout.fillWidth: true + Layout.minimumWidth: (placeHolderTextWidth.boundingRect.width - placeHolderTextWidth.boundingRect.x) * 1.2 + implicitWidth: (placeHolderTextWidth.boundingRect.width - placeHolderTextWidth.boundingRect.x) * 1.2 - TextMetrics { - id: placeHolderTextWidth - text: filterTextInput.placeholderText - } + TextMetrics { + id: placeHolderTextWidth + text: filterTextInput.placeholderText + } - Image { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: elisaTheme.filterClearButtonMargin - id: clearText - fillMode: Image.PreserveAspectFit - smooth: true - visible: parent.text - source: Qt.resolvedUrl(elisaTheme.clearIcon) - height: parent.height - width: parent.height - sourceSize.width: parent.height - sourceSize.height: parent.height - mirror: LayoutMirroring.enabled - - MouseArea { - id: clear - anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } - height: parent.parent.height - width: parent.parent.height - onClicked: { - parent.parent.text = "" - parent.parent.forceActiveFocus() - } + Image { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: elisaTheme.filterClearButtonMargin + id: clearText + fillMode: Image.PreserveAspectFit + smooth: true + visible: parent.text + source: Qt.resolvedUrl(elisaTheme.clearIcon) + height: parent.height + width: parent.height + sourceSize.width: parent.height + sourceSize.height: parent.height + mirror: LayoutMirroring.enabled + + MouseArea { + id: clear + anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } + height: parent.parent.height + width: parent.parent.height + onClicked: { + parent.parent.text = "" + parent.parent.forceActiveFocus() } } } + } - LabelWithToolTip { - text: i18nc("before the Rating widget input of the filter", "Rating: ") + LabelWithToolTip { + text: i18nc("before the Rating widget input of the filter", "Rating: ") - visible: showRating + visible: showRating - font.bold: true + font.bold: true - color: myPalette.text + color: myPalette.text - Layout.bottomMargin: 0 - Layout.leftMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 - Layout.rightMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 - } + Layout.bottomMargin: 0 + Layout.leftMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 + Layout.rightMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 + } - RatingStar { - id: ratingFilter - objectName: 'ratingFilter' + RatingStar { + id: ratingFilter + objectName: 'ratingFilter' - visible: showRating - hoverWidgetOpacity: 1 + visible: showRating + hoverWidgetOpacity: 1 - readOnly: false + readOnly: false - starSize: elisaTheme.ratingStarSize + starSize: elisaTheme.ratingStarSize - Layout.bottomMargin: 0 - } + Layout.bottomMargin: 0 + } - Item { - Layout.fillWidth: true - implicitWidth: elisaTheme.layoutHorizontalMargin * 4 - } + Item { + Layout.fillWidth: true + implicitWidth: elisaTheme.layoutHorizontalMargin * 4 + } - Controls1.ToolButton { - action: sortAction - objectName: 'sortAscendingButton' + Controls1.ToolButton { + action: sortAction + objectName: 'sortAscendingButton' - Keys.onReturnPressed: action.trigger() + activeFocusOnTab: true - Layout.alignment: Qt.AlignRight - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - visible: enableSorting - } + Keys.onReturnPressed: action.trigger() + + Layout.alignment: Qt.AlignRight + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + visible: enableSorting } } diff --git a/src/qml/PlayListBasicView.qml b/src/qml/PlayListBasicView.qml --- a/src/qml/PlayListBasicView.qml +++ b/src/qml/PlayListBasicView.qml @@ -31,8 +31,10 @@ signal displayError(var errorText) focus: true - activeFocusOnTab: true keyNavigationEnabled: true + activeFocusOnTab: true + + currentIndex: -1 section.property: 'albumSection' section.criteria: ViewSection.FullString @@ -131,6 +133,12 @@ onPausePlayback: playListView.pausePlayback() onRemoveFromPlaylist: playListView.playListModel.removeRows(trackIndex, 1) onSwitchToTrack: playListView.playListModel.switchTo(trackIndex) + + onActiveFocusChanged: { + if (activeFocus && playListView.currentIndex !== index) { + playListView.currentIndex = index + } + } } draggedItemParent: playListView @@ -152,4 +160,8 @@ } } } + + onCountChanged: if (count === 0) { + currentIndex = -1; + } } diff --git a/src/qml/PlayListEntry.qml b/src/qml/PlayListEntry.qml --- a/src/qml/PlayListEntry.qml +++ b/src/qml/PlayListEntry.qml @@ -118,297 +118,298 @@ anchors.fill: parent anchors.rightMargin: LayoutMirroring.enabled ? scrollBarWidth : 0 + z: 1 - color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) + color: myPalette.base height: elisaTheme.playListDelegateHeight + } - focus: true - - ColumnLayout { - spacing: 0 + ColumnLayout { + spacing: 0 - anchors.fill: parent + anchors.fill: parent + anchors.rightMargin: LayoutMirroring.enabled ? scrollBarWidth : 0 + z: 2 - Item { - Layout.fillWidth: true - Layout.fillHeight: true + Item { + Layout.fillWidth: true + Layout.fillHeight: true - RowLayout { - id: trackRow + RowLayout { + id: trackRow - anchors.fill: parent + anchors.fill: parent - spacing: elisaTheme.layoutHorizontalMargin / 4 + spacing: elisaTheme.layoutHorizontalMargin / 4 - Item { - id: playIconItem + Item { + id: playIconItem - implicitHeight: elisaTheme.smallDelegateToolButtonSize - implicitWidth: elisaTheme.smallDelegateToolButtonSize - Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize - Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 + implicitHeight: elisaTheme.smallDelegateToolButtonSize + implicitWidth: elisaTheme.smallDelegateToolButtonSize + Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize + Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 - Image { - id: playIcon + Image { + id: playIcon - anchors.fill: parent + anchors.fill: parent - opacity: 0 + opacity: 0 - source: (isPlaying === MediaPlayList.IsPlaying ? - Qt.resolvedUrl(elisaTheme.playingIndicatorIcon) : Qt.resolvedUrl(elisaTheme.pausedIndicatorIcon)) + source: (isPlaying === MediaPlayList.IsPlaying ? + Qt.resolvedUrl(elisaTheme.playingIndicatorIcon) : Qt.resolvedUrl(elisaTheme.pausedIndicatorIcon)) - width: parent.height * 1. - height: parent.height * 1. + width: parent.height * 1. + height: parent.height * 1. - sourceSize.width: parent.height * 1. - sourceSize.height: parent.height * 1. - fillMode: Image.PreserveAspectFit - mirror: LayoutMirroring.enabled - visible: opacity > 0.0 - } + sourceSize.width: parent.height * 1. + sourceSize.height: parent.height * 1. + fillMode: Image.PreserveAspectFit + mirror: LayoutMirroring.enabled + visible: opacity > 0.0 } + } - Item { - id: fakeDiscNumberItem + Item { + id: fakeDiscNumberItem - visible: isValid && (!hasValidDiscNumber || isSingleDiscAlbum) + visible: isValid && (!hasValidDiscNumber || isSingleDiscAlbum) - Layout.preferredWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) - Layout.minimumWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) - Layout.maximumWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) + Layout.preferredWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) + Layout.minimumWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) + Layout.maximumWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) - TextMetrics { - id: fakeDiscNumberSize + TextMetrics { + id: fakeDiscNumberSize - text: '/9' - } + text: '/9' } + } - Label { - id: trackNumberLabel + Label { + id: trackNumberLabel - horizontalAlignment: Text.AlignRight + horizontalAlignment: Text.AlignRight - text: trackNumber !== 0 && trackNumber !== -1 ? Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0) : '' + text: trackNumber !== 0 && trackNumber !== -1 ? Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0) : '' - font.weight: (isPlaying ? Font.Bold : Font.Light) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Light) + color: myPalette.text - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - visible: isValid + visible: isValid - Layout.preferredWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) - Layout.minimumWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) - Layout.maximumWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) + Layout.preferredWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) + Layout.minimumWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) + Layout.maximumWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) - Layout.rightMargin: !LayoutMirroring.enabled ? (discNumber !== 0 && !isSingleDiscAlbum ? - 0 : elisaTheme.layoutHorizontalMargin / 2) : 0 - Layout.leftMargin: LayoutMirroring.enabled ? (discNumber !== 0 && !isSingleDiscAlbum ? - 0 : elisaTheme.layoutHorizontalMargin / 2) : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? (discNumber !== 0 && !isSingleDiscAlbum ? + 0 : elisaTheme.layoutHorizontalMargin / 2) : 0 + Layout.leftMargin: LayoutMirroring.enabled ? (discNumber !== 0 && !isSingleDiscAlbum ? + 0 : elisaTheme.layoutHorizontalMargin / 2) : 0 - TextMetrics { - id: trackNumberSize + TextMetrics { + id: trackNumberSize - text: (99).toLocaleString(Qt.locale(), 'f', 0) - } + text: (99).toLocaleString(Qt.locale(), 'f', 0) + } - TextMetrics { - id: realTrackNumberSize + TextMetrics { + id: realTrackNumberSize - text: Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0) - } + text: Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0) } + } - Label { - horizontalAlignment: Text.AlignCenter + Label { + horizontalAlignment: Text.AlignCenter - text: '/' + text: '/' - visible: isValid && discNumber !== 0 && !isSingleDiscAlbum + visible: isValid && discNumber !== 0 && !isSingleDiscAlbum - font.weight: (isPlaying ? Font.Bold : Font.Light) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Light) + color: myPalette.text - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.preferredWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) - Layout.minimumWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) - Layout.maximumWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) + Layout.preferredWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) + Layout.minimumWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) + Layout.maximumWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) - TextMetrics { - id: numberSeparatorSize + TextMetrics { + id: numberSeparatorSize - text: '/' - } + text: '/' } + } - Label { - horizontalAlignment: Text.AlignRight + Label { + horizontalAlignment: Text.AlignRight - font.weight: (isPlaying ? Font.Bold : Font.Light) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Light) + color: myPalette.text - text: Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) + text: Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) - visible: isValid && discNumber !== 0 && !isSingleDiscAlbum + visible: isValid && discNumber !== 0 && !isSingleDiscAlbum - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.preferredWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? - (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : - (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) - Layout.minimumWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? - (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : - (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) - Layout.maximumWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? - (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : - (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) + Layout.preferredWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? + (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : + (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) + Layout.minimumWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? + (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : + (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) + Layout.maximumWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? + (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : + (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) - Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin / 2) : 0 - Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin / 2) : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin / 2) : 0 + Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin / 2) : 0 - TextMetrics { - id: discNumberSize + TextMetrics { + id: discNumberSize - text: '9' - } + text: '9' + } - TextMetrics { - id: realDiscNumberSize + TextMetrics { + id: realDiscNumberSize - text: Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) - } + text: Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) } + } - LabelWithToolTip { - id: mainCompactLabel + LabelWithToolTip { + id: mainCompactLabel - text: title + text: title - font.weight: (isPlaying ? Font.Bold : Font.Normal) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Normal) + color: myPalette.text - Layout.maximumWidth: mainCompactLabel.implicitWidth + 1 - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.maximumWidth: mainCompactLabel.implicitWidth + 1 + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - visible: isValid + visible: isValid - elide: Text.ElideRight - horizontalAlignment: Text.AlignLeft - } + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + } - LabelWithToolTip { - id: mainInvalidCompactLabel + LabelWithToolTip { + id: mainInvalidCompactLabel - text: title + text: title - font.weight: Font.Normal - color: myPalette.text + font.weight: Font.Normal + color: myPalette.text - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - visible: !isValid + visible: !isValid - elide: Text.ElideRight - } + elide: Text.ElideRight + } - Item { - Layout.fillWidth: true - Layout.preferredWidth: 0 - } + Item { + Layout.fillWidth: true + Layout.preferredWidth: 0 + } - Controls1.ToolButton { - id: infoButton - objectName: 'infoButton' + Controls1.ToolButton { + id: infoButton + objectName: 'infoButton' - implicitHeight: elisaTheme.smallDelegateToolButtonSize - implicitWidth: elisaTheme.smallDelegateToolButtonSize + implicitHeight: elisaTheme.smallDelegateToolButtonSize + implicitWidth: elisaTheme.smallDelegateToolButtonSize - opacity: 0 + opacity: 0 - visible: opacity > 0.1 + visible: opacity > 0.1 - action: showInfo - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } + action: showInfo + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } - Controls1.ToolButton { - id: playPauseButton - objectName: 'playPauseButton' + Controls1.ToolButton { + id: playPauseButton + objectName: 'playPauseButton' - implicitHeight: elisaTheme.smallDelegateToolButtonSize - implicitWidth: elisaTheme.smallDelegateToolButtonSize + implicitHeight: elisaTheme.smallDelegateToolButtonSize + implicitWidth: elisaTheme.smallDelegateToolButtonSize - opacity: 0 + opacity: 0 - scale: LayoutMirroring.enabled ? -1 : 1 // We can mirror the symmetrical pause icon + scale: LayoutMirroring.enabled ? -1 : 1 // We can mirror the symmetrical pause icon - visible: opacity > 0.1 - action: !(isPlaying === MediaPlayList.IsPlaying) ? playNow : pauseNow - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } + visible: opacity > 0.1 + action: !(isPlaying === MediaPlayList.IsPlaying) ? playNow : pauseNow + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } - Item { - implicitHeight: elisaTheme.smallDelegateToolButtonSize - implicitWidth: elisaTheme.smallDelegateToolButtonSize - Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize - Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Item { + implicitHeight: elisaTheme.smallDelegateToolButtonSize + implicitWidth: elisaTheme.smallDelegateToolButtonSize + Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize + Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Controls1.ToolButton { - id: removeButton - objectName: 'removeButton' + Controls1.ToolButton { + id: removeButton + objectName: 'removeButton' - anchors.fill: parent + anchors.fill: parent - opacity: 0 + opacity: 0 - visible: opacity > 0.1 - action: removeFromPlayList - } + visible: opacity > 0.1 + action: removeFromPlayList } + } - RatingStar { - id: ratingWidget + RatingStar { + id: ratingWidget - starRating: rating + starRating: rating - starSize: elisaTheme.ratingStarSize + starSize: elisaTheme.ratingStarSize - visible: rating > 0 - } + visible: rating > 0 + } - LabelWithToolTip { - id: durationLabel + LabelWithToolTip { + id: durationLabel - text: duration + text: duration - font.weight: (isPlaying ? Font.Bold : Font.Normal) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Normal) + color: myPalette.text - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.leftMargin: elisaTheme.layoutHorizontalMargin / 2 - Layout.rightMargin: elisaTheme.layoutHorizontalMargin / 2 + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.leftMargin: elisaTheme.layoutHorizontalMargin / 2 + Layout.rightMargin: elisaTheme.layoutHorizontalMargin / 2 - horizontalAlignment: Text.AlignRight - } + horizontalAlignment: Text.AlignRight } } } } states: [ State { name: 'notSelected' - when: !containsMouse && (!playListEntry.activeFocus || !isSelected) + when: !containsMouse && !isSelected && !playListEntry.activeFocus PropertyChanges { target: removeButton opacity: 0 @@ -429,14 +430,18 @@ target: entryBackground color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } + PropertyChanges { + target: entryBackground + opacity: 1. + } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } }, State { - name: 'hoveredOrSelected' - when: containsMouse || (playListEntry.activeFocus && isSelected) + name: 'hovered' + when: containsMouse && !playListEntry.activeFocus PropertyChanges { target: removeButton opacity: 1 @@ -453,10 +458,78 @@ target: playIcon opacity: (isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused ? 1.0 : 0.0) } + PropertyChanges { + target: entryBackground + color: myPalette.highlight + } + PropertyChanges { + target: entryBackground + opacity: 0.2 + } + PropertyChanges { + target: ratingWidget + hoverWidgetOpacity: 1.0 + } + }, + State { + name: 'selected' + when: !playListEntry.activeFocus && isSelected + PropertyChanges { + target: removeButton + opacity: 0 + } + PropertyChanges { + target: playPauseButton + opacity: 0 + } + PropertyChanges { + target: infoButton + opacity: 0 + } + PropertyChanges { + target: playIcon + opacity: (isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused ? 1.0 : 0.0) + } PropertyChanges { target: entryBackground color: myPalette.mid } + PropertyChanges { + target: entryBackground + opacity: 1. + } + PropertyChanges { + target: ratingWidget + hoverWidgetOpacity: 1.0 + } + }, + State { + name: 'focused' + when: playListEntry.activeFocus + PropertyChanges { + target: removeButton + opacity: 1 + } + PropertyChanges { + target: playPauseButton + opacity: 1 + } + PropertyChanges { + target: infoButton + opacity: 1 + } + PropertyChanges { + target: playIcon + opacity: (isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused ? 1.0 : 0.0) + } + PropertyChanges { + target: entryBackground + color: myPalette.highlight + } + PropertyChanges { + target: entryBackground + opacity: 0.6 + } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 diff --git a/src/qml/RecentlyPlayedTracks.qml b/src/qml/RecentlyPlayedTracks.qml --- a/src/qml/RecentlyPlayedTracks.qml +++ b/src/qml/RecentlyPlayedTracks.qml @@ -47,7 +47,6 @@ id: listView focus: true - activeFocusOnTab: true anchors.fill: parent @@ -65,14 +64,16 @@ title: model.title artist: model.artist album: (model.album !== undefined && model.album !== '' ? model.album : '') - albumArtist: model.albumArtist + albumArtist: (model.albumArtist !== undefined && model.albumArtist !== '' ? model.albumArtist : '') duration: model.duration imageUrl: (model.imageUrl !== undefined && model.imageUrl !== '' ? model.imageUrl : '') trackNumber: model.trackNumber discNumber: model.discNumber rating: model.rating isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum + isSelected: listView.currentIndex === index + isAlternateColor: (index % 2) === 1 onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.AppendPlayList, @@ -82,7 +83,10 @@ ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) - onClicked: contentDirectoryView.currentIndex = index + onClicked: { + listView.currentIndex = index + entry.forceActiveFocus() + } } Loader { diff --git a/src/qml/TracksView.qml b/src/qml/TracksView.qml --- a/src/qml/TracksView.qml +++ b/src/qml/TracksView.qml @@ -47,7 +47,6 @@ id: listView focus: true - activeFocusOnTab: true anchors.fill: parent @@ -65,14 +64,16 @@ title: model.title artist: model.artist album: (model.album !== undefined && model.album !== '' ? model.album : '') - albumArtist: model.albumArtist + albumArtist: (model.albumArtist !== undefined && model.albumArtist !== '' ? model.albumArtist : '') duration: model.duration imageUrl: (model.imageUrl !== undefined && model.imageUrl !== '' ? model.imageUrl : '') trackNumber: model.trackNumber discNumber: model.discNumber rating: model.rating isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum + isSelected: listView.currentIndex === index + isAlternateColor: (index % 2) === 1 onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.AppendPlayList, @@ -82,7 +83,10 @@ ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) - onClicked: contentDirectoryView.currentIndex = index + onClicked: { + listView.currentIndex = index + entry.forceActiveFocus() + } } Loader { diff --git a/src/qml/ViewSelector.qml b/src/qml/ViewSelector.qml --- a/src/qml/ViewSelector.qml +++ b/src/qml/ViewSelector.qml @@ -42,174 +42,72 @@ Rectangle { anchors.fill: parent + z: 1 - color: myPalette.base - border { - color: (rootFocusScope.activeFocus ? myPalette.highlight : "transparent") - width: 1 - } - - ScrollView { - focus: true - - anchors.fill: parent - - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - - - ListView { - id: viewModeView - - focus: true - activeFocusOnTab: true - keyNavigationEnabled: true - - property bool ignoreCurrentItemChanges: false - - z: 2 - - anchors.topMargin: elisaTheme.layoutHorizontalMargin * 2 - - model: DelegateModel { - id: pageDelegateModel - - delegate: MouseArea { - id: itemMouseArea - - property var viewType: model.type - - height: elisaTheme.viewSelectorDelegateHeight * 1.4 - width: viewModeView.width - - hoverEnabled: true - acceptedButtons: Qt.LeftButton - - Loader { - anchors.fill: parent - active: itemMouseArea && itemMouseArea.containsMouse && !nameLabel.visible - - sourceComponent: ToolTip { - delay: Qt.styleHints.mousePressAndHoldInterval - text: model.display - visible: itemMouseArea && itemMouseArea.containsMouse && !nameLabel.visible - - contentItem: Label { - text: model.display - color: myPalette.highlightedText - } - - enter: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 0.0; to: 1.0; duration: 300; } } - exit: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 1.0; to: 0.0; duration: 300; } } - - background: Rectangle { - color: myPalette.shadow - radius: elisaTheme.tooltipRadius - - layer.enabled: true - layer.effect: DropShadow { - horizontalOffset: elisaTheme.shadowOffset - verticalOffset: elisaTheme.shadowOffset - radius: 8 - samples: 17 - color: myPalette.shadow - } - } - } - } + border.color: myPalette.base - Image { - id: viewIcon - - z: 2 - - anchors { - verticalCenter: parent.verticalCenter - leftMargin: elisaTheme.layoutHorizontalMargin - left: parent.left - } - - height: elisaTheme.viewSelectorDelegateHeight - width: elisaTheme.viewSelectorDelegateHeight + Behavior on border.color { + ColorAnimation { + duration: 300 + } + } + } - sourceSize { - width: elisaTheme.viewSelectorDelegateHeight - height: elisaTheme.viewSelectorDelegateHeight - } + ScrollView { + focus: true - source: model.image + anchors.fill: parent + z: 2 - layer.effect: ColorOverlay { + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - color: (index === viewModeView.currentIndex || itemMouseArea.containsMouse ? myPalette.highlight : "transparent") + ListView { + id: viewModeView - Behavior on color { - ColorAnimation { - duration: 300 - } - } - } - } + focus: true + activeFocusOnTab: true + keyNavigationEnabled: true - LabelWithToolTip { - id: nameLabel + property bool ignoreCurrentItemChanges: false - z: 2 + z: 2 - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: elisaTheme.layoutHorizontalMargin - anchors.left: viewIcon.right - anchors.right: parent.right - anchors.rightMargin: elisaTheme.layoutHorizontalMargin - verticalAlignment: "AlignVCenter" + anchors.topMargin: elisaTheme.layoutHorizontalMargin * 2 - font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.1) + model: DelegateModel { + id: pageDelegateModel - text: model.display - elide: Text.ElideRight + delegate: ViewSelectorDelegate { + id: entry - opacity: textOpacity - visible: opacity > 0 + height: Math.round(elisaTheme.viewSelectorDelegateHeight * 1.4) + width: viewModeView.width - Behavior on opacity { - NumberAnimation { - duration: 150 - } - } + focus: true - color: (viewModeView.currentIndex === index || itemMouseArea.containsMouse ? myPalette.highlight : myPalette.text) + isSelected: viewModeView.currentIndex === index - Behavior on color { - ColorAnimation { - duration: 300 - } - } - } - - onClicked: viewModeView.currentIndex = index + onClicked: { + viewModeView.currentIndex = index + entry.forceActiveFocus() } } + } - footer: MouseArea { - width: viewModeView.width - height: viewModeView.height - y + footer: MouseArea { + width: viewModeView.width + height: viewModeView.height - y - acceptedButtons: Qt.LeftButton + acceptedButtons: Qt.LeftButton - onClicked: - { - rootFocusScope.focus = true - } + onClicked: + { + rootFocusScope.focus = true } - - onCurrentItemChanged: if (!ignoreCurrentItemChanges) switchView(currentItem.viewType) } - } - Behavior on border.color { - ColorAnimation { - duration: 300 - } + onCurrentItemChanged: if (!ignoreCurrentItemChanges) switchView(currentItem.viewType) } } diff --git a/src/qml/ViewSelectorDelegate.qml b/src/qml/ViewSelectorDelegate.qml new file mode 100644 --- /dev/null +++ b/src/qml/ViewSelectorDelegate.qml @@ -0,0 +1,252 @@ +/* + * Copyright 2016-2019 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 + +FocusScope { + id: rootItem + + property var viewType: model.type + property bool isSelected + + signal clicked() + + Rectangle { + id: backgroundHighlight + + anchors.fill: parent + z: 1 + + color: "transparent" + } + + MouseArea { + id: hoverArea + + anchors.fill: parent + z: 2 + + hoverEnabled: true + acceptedButtons: Qt.LeftButton + + onClicked: { + rootItem.clicked() + } + + Loader { + id: hoverLoader + + anchors.fill: parent + active: false + + sourceComponent: ToolTip { + delay: Qt.styleHints.mousePressAndHoldInterval + text: model.display + visible: hoverArea && hoverArea.containsMouse && !nameLabel.visible + + contentItem: Label { + text: model.display + color: myPalette.highlightedText + } + + enter: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 0.0; to: 1.0; duration: 300; } } + exit: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 1.0; to: 0.0; duration: 300; } } + + background: Rectangle { + color: myPalette.shadow + radius: elisaTheme.tooltipRadius + + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: elisaTheme.shadowOffset + verticalOffset: elisaTheme.shadowOffset + radius: 8 + samples: 17 + color: myPalette.shadow + } + } + } + } + + Image { + id: viewIcon + + z: 2 + + anchors { + verticalCenter: parent.verticalCenter + leftMargin: elisaTheme.layoutHorizontalMargin + left: parent.left + } + + height: elisaTheme.viewSelectorDelegateHeight + width: elisaTheme.viewSelectorDelegateHeight + + sourceSize { + width: elisaTheme.viewSelectorDelegateHeight + height: elisaTheme.viewSelectorDelegateHeight + } + + source: model.image + + layer.enabled: true + layer.effect: ColorOverlay { + color: nameLabel.color + } + } + + LabelWithToolTip { + id: nameLabel + + z: 2 + + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: elisaTheme.layoutHorizontalMargin + anchors.left: viewIcon.right + anchors.right: parent.right + anchors.rightMargin: elisaTheme.layoutHorizontalMargin + verticalAlignment: "AlignVCenter" + + font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.1) + + text: model.display + elide: Text.ElideRight + + opacity: textOpacity + visible: opacity > 0 + + color: (viewModeView.currentIndex === index || hoverArea.containsMouse ? myPalette.highlight : myPalette.text) + } + } + + states: [ + State { + name: 'notSelected' + when: !rootItem.activeFocus && !hoverArea.containsMouse && !rootItem.isSelected + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: viewIcon + opacity: 1 + } + PropertyChanges { + target: nameLabel + color: myPalette.buttonText + } + PropertyChanges { + target: backgroundHighlight + color: 'transparent' + } + }, + State { + name: 'hovered' + when: !rootItem.activeFocus && hoverArea.containsMouse + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + PropertyChanges { + target: viewIcon + opacity: 0.4 + } + PropertyChanges { + target: nameLabel + color: myPalette.buttonText + } + PropertyChanges { + target: backgroundHighlight + color: Qt.rgba(myPalette.highlight.r, myPalette.highlight.g, myPalette.highlight.b, 0.2) + } + }, + State { + name: 'selected' + when: !rootItem.activeFocus && rootItem.isSelected + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: viewIcon + opacity: 1 + } + PropertyChanges { + target: nameLabel + color: myPalette.buttonText + } + PropertyChanges { + target: backgroundHighlight + color: myPalette.mid + } + }, + State { + name: 'focused' + when: rootItem.activeFocus + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: viewIcon + opacity: 1. + } + PropertyChanges { + target: nameLabel + color: myPalette.highlightedText + } + PropertyChanges { + target: backgroundHighlight + color: myPalette.highlight + } + } + ] + + transitions: [ + Transition { + ParallelAnimation { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 200 + } + ColorAnimation { + properties: "color" + duration: 250 + } + } + } + ] +} diff --git a/src/resources.qrc b/src/resources.qrc --- a/src/resources.qrc +++ b/src/resources.qrc @@ -44,6 +44,7 @@ qml/BasicPlayListAlbumHeader.qml qml/MetaDataDelegate.qml qml/ContextViewLyrics.qml + qml/ViewSelectorDelegate.qml windows/WindowsTheme.qml