diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index e199f3b3..70297e65 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,941 +1,941 @@ /* * Copyright 2016-2018 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Controls 1.4 as Controls1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import org.kde.elisa 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 1000 minimumHeight: 600 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true x: persistentSettings.x y: persistentSettings.y width: persistentSettings.width height: persistentSettings.height title: i18n("Elisa") property var goBackAction: elisa.action("go_back") Controls1.Action { shortcut: goBackAction.shortcut onTriggered: { localAlbums.goBack() localArtists.goBack() } } Controls1.Action { id: applicationMenuAction text: i18nc("open application menu", "Application Menu") iconName: "application-menu" onTriggered: applicationMenu.popup() } ApplicationMenu { id: applicationMenu } SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 1000 property int height : 600 property var playListState property var playListControlerState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false property string filterState } Connections { target: Qt.application onAboutToQuit: { persistentSettings.x = mainWindow.x; persistentSettings.y = mainWindow.y; persistentSettings.width = mainWindow.width; persistentSettings.height = mainWindow.height; persistentSettings.playListState = elisa.mediaPlayList.persistentState; persistentSettings.playListControlerState = elisa.mediaPlayList.persistentState; persistentSettings.audioPlayerState = manageAudioPlayer.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted } } PlatformIntegration { id: platformInterface playListModel: elisa.mediaPlayList playListControler: elisa.mediaPlayList audioPlayerManager: manageAudioPlayer headerBarManager: myHeaderBarManager manageMediaPlayerControl: myPlayControlManager player: audioPlayer onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } AudioWrapper { id: audioPlayer muted: headerBar.playerControl.muted volume: headerBar.playerControl.volume onVolumeChanged: headerBar.playerControl.volume = volume onMutedChanged: headerBar.playerControl.muted = muted source: manageAudioPlayer.playerSource onPlaying: { myPlayControlManager.playerPlaying() } onPaused: { myPlayControlManager.playerPaused() } onStopped: { myPlayControlManager.playerStopped() } } Connections { target: elisa.mediaPlayList onPlayListLoadFailed: { messageNotification.showNotification(i18nc("message of passive notification when playlist load failed", "Load of playlist failed"), 3000) } onEnsurePlay: manageAudioPlayer.ensurePlay() onPlayListFinished: manageAudioPlayer.playListFinished() } ManageHeaderBar { id: myHeaderBarManager playListModel: elisa.mediaPlayList currentTrack: elisa.mediaPlayList.currentTrack artistRole: MediaPlayList.ArtistRole titleRole: MediaPlayList.TitleRole albumRole: MediaPlayList.AlbumRole imageRole: MediaPlayList.ImageRole isValidRole: MediaPlayList.IsValidRole } ManageAudioPlayer { id: manageAudioPlayer currentTrack: elisa.mediaPlayList.currentTrack playListModel: elisa.mediaPlayList urlRole: MediaPlayList.ResourceRole isPlayingRole: MediaPlayList.IsPlayingRole titleRole: MediaPlayList.TitleRole artistNameRole: MediaPlayList.ArtistRole albumNameRole: MediaPlayList.AlbumRole playerStatus: audioPlayer.status playerPlaybackState: audioPlayer.playbackState playerError: audioPlayer.error audioDuration: audioPlayer.duration playerIsSeekable: audioPlayer.seekable playerPosition: audioPlayer.position persistentState: persistentSettings.audioPlayerState onPlayerPlay: audioPlayer.play() onPlayerPause: audioPlayer.pause() onPlayerStop: audioPlayer.stop() onSkipNextTrack: elisa.mediaPlayList.skipNextTrack() onSeek: audioPlayer.seek(position) onSourceInError: { elisa.mediaPlayList.trackInError(source, playerError) elisa.musicManager.playBackError(source, playerError) } onDisplayTrackError: messageNotification.showNotification(i18n("Error when playing %1", "" + fileName), 3000) } ManageMediaPlayerControl { id: myPlayControlManager playListModel: elisa.mediaPlayList currentTrack: elisa.mediaPlayList.currentTrack } PassiveNotification { id: messageNotification } Rectangle { color: myPalette.base anchors.fill: parent ColumnLayout { anchors.fill: parent spacing: 0 Item { Layout.preferredHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.fillWidth: true HeaderBar { id: headerBar focus: true anchors.fill: parent tracksCount: myHeaderBarManager.remainingTracks album: myHeaderBarManager.album title: myHeaderBarManager.title artist: myHeaderBarManager.artist image: myHeaderBarManager.image ratingVisible: false playerControl.duration: audioPlayer.duration playerControl.seekable: audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: audioPlayer.position playerControl.skipBackwardEnabled: myPlayControlManager.skipBackwardControlEnabled playerControl.skipForwardEnabled: myPlayControlManager.skipForwardControlEnabled playerControl.playEnabled: myPlayControlManager.playControlEnabled playerControl.isPlaying: myPlayControlManager.musicPlaying playerControl.onSeek: audioPlayer.seek(position) playerControl.onPlay: manageAudioPlayer.playPause() playerControl.onPause: manageAudioPlayer.playPause() playerControl.onPlayPrevious: elisa.mediaPlayList.skipPreviousTrack() playerControl.onPlayNext: elisa.mediaPlayList.skipNextTrack() Controls1.ToolButton { id: menuButton action: applicationMenuAction z: 2 anchors { right: parent.right top: parent.top rightMargin: elisaTheme.layoutHorizontalMargin * 3 topMargin: elisaTheme.layoutHorizontalMargin * 3 } } Rectangle { anchors.fill: menuButton z: 1 radius: width / 2 color: myPalette.window } TrackImportNotification { id: importedTracksCountNotification anchors { right: menuButton.left top: menuButton.top bottom: menuButton.bottom rightMargin: elisaTheme.layoutHorizontalMargin * 3 } } Binding { target: importedTracksCountNotification property: 'musicManager' value: elisa.musicManager when: elisa.musicManager !== undefined } Loader { sourceComponent: Binding { target: importedTracksCountNotification property: 'indexingRunning' value: elisa.musicManager.indexingRunning } active: elisa.musicManager !== undefined } Loader { sourceComponent: Binding { target: importedTracksCountNotification property: 'importedTracksCount' value: elisa.musicManager.importedTracksCount } active: elisa.musicManager !== undefined } } } RowLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 ViewSelector { id: listViews Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.15 Layout.maximumWidth: mainWindow.width * 0.15 } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 TopNotification { id: invalidBalooConfiguration Layout.fillWidth: true musicManager: elisa.musicManager focus: true } Item { Layout.fillHeight: true Layout.fillWidth: true RowLayout { anchors.fill: parent spacing: 0 id: contentZone FocusScope { id: mainContentView focus: true Layout.fillHeight: true Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 visible: Layout.minimumWidth != 0 Rectangle { border { color: (mainContentView.activeFocus ? myPalette.highlight : myPalette.base) width: 1 } radius: 3 color: myPalette.base anchors.fill: parent - BusyIndicator { - id: busyScanningMusic - hoverEnabled: false + Loader { anchors.fill: parent anchors.leftMargin: parent.width / 3 anchors.rightMargin: parent.width / 3 anchors.topMargin: parent.height / 3 anchors.bottomMargin: parent.height / 3 - opacity: 0.8 + z: 2 - visible: running + sourceComponent: BusyIndicator { + id: busyScanningMusic + hoverEnabled: false - z: 2 - } + anchors.fill: parent - Loader { - sourceComponent: Binding { - target: busyScanningMusic - property: 'running' - value: elisa.musicManager.indexerBusy + opacity: 0.8 + + visible: true + running: true + + z: 2 } - active: elisa.musicManager !== undefined + active: elisa.musicManager.indexerBusy } MediaBrowser { id: localAlbums focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allAlbumsView focus: true contentModel: elisa.allAlbumsProxyModel image: elisaTheme.albumIcon mainTitle: i18nc("Title of the view of all albums", "Albums") onOpen: { elisa.singleAlbumProxyModel.loadAlbumData(databaseId) localAlbums.stackView.push(albumView, { stackView: localAlbums.stackView, albumName: innerMainTitle, artistName: innerSecondaryTitle, albumArtUrl: innerImage, }) } onGoBack: localAlbums.stackView.pop() Binding { target: allAlbumsView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } MediaBrowser { id: localArtists focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allArtistsView focus: true showRating: false delegateDisplaySecondaryText: false contentModel: elisa.allArtistsProxyModel image: elisaTheme.artistIcon mainTitle: i18nc("Title of the view of all artists", "Artists") onOpen: { elisa.singleArtistProxyModel.setArtistFilterText(innerMainTitle) localArtists.stackView.push(innerAlbumView, { mainTitle: innerMainTitle, secondaryTitle: innerSecondaryTitle, image: innerImage, stackView: localArtists.stackView }) } onGoBack: localArtists.stackView.pop() Binding { target: allArtistsView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } MediaBrowser { id: localTracks focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: MediaAllTracksView { id: allTracksView focus: true stackView: localTracks.stackView contentModel: elisa.allTracksProxyModel Binding { target: allTracksView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } visible: opacity > 0 } Behavior on border.color { ColorAnimation { duration: 300 } } } } Rectangle { id: firstViewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } MediaPlayListView { id: playList playListModel: elisa.mediaPlayList randomPlayChecked: elisa.mediaPlayList.randomPlay repeatPlayChecked: elisa.mediaPlayList.repeatPlay Layout.fillHeight: true Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width onStartPlayback: manageAudioPlayer.ensurePlay() onPausePlayback: manageAudioPlayer.playPause() onDisplayError: messageNotification.showNotification(errorText) } Rectangle { id: viewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: Layout.minimumWidth != 0 Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } ContextView { id: albumContext Layout.fillHeight: true Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width visible: Layout.minimumWidth != 0 artistName: myHeaderBarManager.artist albumName: myHeaderBarManager.album albumArtUrl: myHeaderBarManager.image } } } states: [ State { name: 'full' when: listViews.currentIndex === 0 PropertyChanges { target: mainContentView Layout.fillWidth: false Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: albumContext Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allAlbums' when: listViews.currentIndex === 1 StateChangeScript { script: { localAlbums.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 1 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allArtists' when: listViews.currentIndex === 2 StateChangeScript { script: { localArtists.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 1 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allTracks' when: listViews.currentIndex === 3 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 1 } } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" easing.type: Easing.InOutQuad duration: 300 } } } } } } Component { id: innerAlbumView GridBrowserView { id: innerAlbumGridView property var stackView contentModel: elisa.singleArtistProxyModel isSubPage: true onOpen: { elisa.singleAlbumProxyModel.loadAlbumData(databaseId) localArtists.stackView.push(albumView, { stackView: localArtists.stackView, albumName: innerMainTitle, artistName: innerSecondaryTitle, albumArtUrl: innerImage, }) } onGoBack: stackView.pop() Binding { target: innerAlbumGridView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } } Component { id: albumView MediaAlbumView { id: albumGridView property var stackView contentModel: elisa.singleAlbumProxyModel onShowArtist: { listViews.currentIndex = 2 if (localArtists.stackView.depth === 3) { localArtists.stackView.pop() } if (localArtists.stackView.depth === 2) { var artistPage = localArtists.stackView.get(1) if (artistPage.mainTitle === name) { return } else { localArtists.stackView.pop() } } allArtistsView.open(name, name, elisaTheme.defaultArtistImage, '') } onGoBack: stackView.pop() Binding { target: albumGridView property: 'filterState' value: persistentSettings.filterState } onFilterViewChanged: persistentSettings.filterState = filterState } } Component.onCompleted: { elisa.initialize() elisa.mediaPlayList.randomPlay = Qt.binding(function() { return playList.randomPlayChecked }) elisa.mediaPlayList.repeatPlay = Qt.binding(function() { return playList.repeatPlayChecked }) myPlayControlManager.randomOrContinuePlay = Qt.binding(function() { return playList.randomPlayChecked || playList.repeatPlayChecked }) if (persistentSettings.playListState) { elisa.mediaPlayList.persistentState = persistentSettings.playListState } elisa.mediaPlayList.enqueue(elisa.arguments) } } diff --git a/src/qml/PlayListEntry.qml b/src/qml/PlayListEntry.qml index 02d49017..79acc133 100644 --- a/src/qml/PlayListEntry.qml +++ b/src/qml/PlayListEntry.qml @@ -1,481 +1,449 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.2 import QtQuick.Controls 1.4 as Controls1 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: playListEntry property var index property bool isSingleDiscAlbum property int isPlaying property bool isSelected property bool isValid property bool isAlternateColor property bool containsMouse property bool hasAlbumHeader property string titleDisplay property alias trackData: dataHelper.trackData signal startPlayback() signal pausePlayback() signal removeFromPlaylist(var trackIndex) signal switchToTrack(var trackIndex) height: (hasAlbumHeader ? elisaTheme.delegateWithHeaderHeight : elisaTheme.delegateHeight) Controls1.Action { id: removeFromPlayList text: i18nc("Remove current track from play list", "Remove") iconName: "list-remove" onTriggered: { playListEntry.removeFromPlaylist(playListEntry.index) } } Controls1.Action { id: playNow text: i18nc("Play now current track from play list", "Play Now") iconName: "media-playback-start" enabled: !(isPlaying == MediaPlayList.IsPlaying) && isValid onTriggered: { playListEntry.switchToTrack(playListEntry.index) playListEntry.startPlayback() } } Controls1.Action { id: pauseNow text: i18nc("Pause current track from play list", "Pause") iconName: "media-playback-pause" enabled: isPlaying == MediaPlayList.IsPlaying && isValid onTriggered: playListEntry.pausePlayback() } Controls1.Action { id: showInfo text: i18nc("Show track metadata", "View Details") iconName: "help-about" enabled: isValid onTriggered: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } } TrackDataHelper { id: dataHelper } Loader { id: metadataLoader active: false onLoaded: item.show() sourceComponent: MediaTrackMetadataView { trackDataHelper: dataHelper onRejected: metadataLoader.active = false; } } Rectangle { id: entryBackground anchors.fill: parent color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) height: (hasAlbumHeader ? elisaTheme.delegateWithHeaderHeight : elisaTheme.delegateHeight) focus: true ColumnLayout { spacing: 0 anchors.fill: parent anchors.leftMargin: elisaTheme.layoutHorizontalMargin anchors.rightMargin: elisaTheme.layoutHorizontalMargin Item { Layout.fillWidth: true Layout.preferredHeight: elisaTheme.delegateWithHeaderHeight - elisaTheme.delegateHeight Layout.minimumHeight: elisaTheme.delegateWithHeaderHeight - elisaTheme.delegateHeight Layout.maximumHeight: elisaTheme.delegateWithHeaderHeight - elisaTheme.delegateHeight visible: hasAlbumHeader RowLayout { id: headerRow spacing: elisaTheme.layoutHorizontalMargin anchors.fill: parent Image { id: mainIcon source: (isValid ? (dataHelper.hasValidAlbumCover() ? dataHelper.albumCover : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) : Qt.resolvedUrl(elisaTheme.errorIcon)) Layout.minimumWidth: headerRow.height - 4 Layout.maximumWidth: headerRow.height - 4 Layout.preferredWidth: headerRow.height - 4 Layout.minimumHeight: headerRow.height - 4 Layout.maximumHeight: headerRow.height - 4 Layout.preferredHeight: headerRow.height - 4 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter sourceSize.width: headerRow.height - 4 sourceSize.height: parent.height - 4 fillMode: Image.PreserveAspectFit asynchronous: true visible: isValid } BrightnessContrast { source: mainIcon cached: true visible: !isValid contrast: -0.9 Layout.minimumWidth: headerRow.height - 4 Layout.maximumWidth: headerRow.height - 4 Layout.preferredWidth: headerRow.height - 4 Layout.minimumHeight: headerRow.height - 4 Layout.maximumHeight: headerRow.height - 4 Layout.preferredHeight: headerRow.height - 4 Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 LabelWithToolTip { id: mainLabel text: dataHelper.albumName font.weight: Font.Bold color: myPalette.text horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignCenter Layout.topMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } Item { Layout.fillHeight: true } LabelWithToolTip { id: authorLabel text: dataHelper.albumArtist font.weight: Font.Light color: myPalette.text horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true Layout.alignment: Qt.AlignCenter Layout.bottomMargin: elisaTheme.layoutVerticalMargin elide: Text.ElideRight } } } } Item { Layout.fillWidth: true Layout.fillHeight: true RowLayout { id: trackRow anchors.fill: parent spacing: elisaTheme.layoutHorizontalMargin LabelWithToolTip { id: mainCompactLabel text: { if (dataHelper.hasValidTrackNumber()) { if (dataHelper.hasValidDiscNumber() && !isSingleDiscAlbum) return i18nc("%1: disk number. %2: track number. %3: track title", "%1 - %2 - %3", Number(dataHelper.discNumber).toLocaleString(Qt.locale(), 'f', 0), Number(dataHelper.trackNumber).toLocaleString(Qt.locale(), 'f', 0), dataHelper.title); else return i18nc("%1: track number. %2: track title", "%1 - %2", Number(dataHelper.trackNumber).toLocaleString(Qt.locale(), 'f', 0), dataHelper.title); } else { return dataHelper.title; } } font.weight: (isPlaying ? Font.Bold : Font.Normal) color: myPalette.text Layout.maximumWidth: mainCompactLabel.implicitWidth + 1 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: isValid elide: Text.ElideRight horizontalAlignment: Text.AlignLeft } LabelWithToolTip { id: mainInvalidCompactLabel text: titleDisplay font.weight: Font.Normal color: myPalette.text Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: !isValid elide: Text.ElideRight } Item { Layout.fillWidth: true Layout.preferredWidth: 0 } Controls1.ToolButton { id: infoButton implicitHeight: elisaTheme.smallDelegateToolButtonSize implicitWidth: elisaTheme.smallDelegateToolButtonSize opacity: 0 visible: opacity > 0.1 action: showInfo Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } Controls1.ToolButton { id: playPauseButton implicitHeight: elisaTheme.smallDelegateToolButtonSize implicitWidth: elisaTheme.smallDelegateToolButtonSize opacity: 0 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 } 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 anchors.fill: parent opacity: 0 visible: opacity > 0.1 action: removeFromPlayList } Image { id: playIcon anchors.fill: parent opacity: 0 - source: (isPlaying == MediaPlayList.IsPlaying ? Qt.resolvedUrl(elisaTheme.playIcon) : Qt.resolvedUrl(elisaTheme.pauseIcon)) + source: Qt.resolvedUrl(elisaTheme.playIndicatorIcon) 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: isPlaying == MediaPlayList.IsPlaying || isPlaying == MediaPlayList.IsPaused - - SequentialAnimation on opacity { - running: isPlaying == MediaPlayList.IsPlaying && playListEntry.state != 'hoveredOrSelected' - loops: Animation.Infinite - - NumberAnimation { - from: 0 - to: 1. - duration: 1000 - easing.type: Easing.InOutCubic - } - NumberAnimation { - from: 1 - to: 0 - duration: 1000 - easing.type: Easing.InOutCubic - } - } - - SequentialAnimation on opacity { - running: isPlaying == MediaPlayList.IsPaused && playListEntry.state != 'hoveredOrSelected' - - NumberAnimation { - from: playIcon.opacity - to: 1. - duration: 1000 - easing.type: Easing.InOutCubic - } - } - - SequentialAnimation on opacity { - running: playListEntry.state == 'hoveredOrSelected' - - NumberAnimation { - from: playIcon.opacity - to: 0 - duration: 250 - easing.type: Easing.InOutCubic - } - } + visible: opacity > 0.0 } } RatingStar { id: ratingWidget starRating: dataHelper.rating starSize: elisaTheme.ratingStarSize } TextMetrics { id: durationTextMetrics text: i18nc("This is used to preserve a fixed width for the duration text.", "00:00") } LabelWithToolTip { id: durationLabel text: dataHelper.duration color: myPalette.text Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.preferredWidth: durationTextMetrics.width + 1 horizontalAlignment: Text.AlignRight } } } } } states: [ State { name: 'notSelected' when: !containsMouse && (!playListEntry.activeFocus || !isSelected) PropertyChanges { target: removeButton opacity: 0 } PropertyChanges { target: infoButton opacity: 0 } PropertyChanges { target: playPauseButton opacity: 0 } + PropertyChanges { + target: playIcon + opacity: (isPlaying === MediaPlayList.IsPlaying ? 1.0 : 0.0) + } PropertyChanges { target: entryBackground color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } }, State { name: 'hoveredOrSelected' when: containsMouse || (playListEntry.activeFocus && isSelected) PropertyChanges { target: removeButton opacity: 1 } PropertyChanges { target: playPauseButton opacity: 1 } PropertyChanges { target: infoButton opacity: 1 } + PropertyChanges { + target: playIcon + opacity: 0 + } PropertyChanges { target: entryBackground color: myPalette.mid } } ] transitions: Transition { ParallelAnimation { NumberAnimation { properties: "opacity" easing.type: Easing.InOutQuad duration: 250 } ColorAnimation { properties: "color" duration: 250 } } } } diff --git a/src/qml/Theme.qml b/src/qml/Theme.qml index 06a82471..dcd65fad 100644 --- a/src/qml/Theme.qml +++ b/src/qml/Theme.qml @@ -1,90 +1,91 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Controls 2.2 Item { function dp(pixel) { // 96 - common, "base" DPI value return Math.round(pixel * logicalDpi / 96); } property string defaultAlbumImage: 'image://icon/media-optical-audio' property string defaultArtistImage: 'image://icon/view-media-artist' property string defaultBackgroundImage: 'qrc:///background.png' property string artistIcon: 'image://icon/view-media-artist' property string albumIcon: 'image://icon/media-album-cover-manager-amarok' property string playlistIcon: 'image://icon/amarok_playlist' property string tracksIcon: 'image://icon/media-album-track' property string clearIcon: 'image://icon/edit-clear' property string skipBackwardIcon: 'image://icon/media-skip-backward' property string pauseIcon: 'image://icon/media-playback-pause' property string playIcon: 'image://icon/media-playback-start' property string skipForwardIcon: 'image://icon/media-skip-forward' property string playerVolumeMutedIcon: 'image://icon/player-volume-muted' property string playerVolumeIcon: 'image://icon/player-volume' property string ratingIcon: 'image://icon/rating' property string ratingUnratedIcon: 'image://icon/rating-unrated' property string errorIcon: 'image://icon/error' + property string playIndicatorIcon: 'image://icon/audio-volume-high' property int layoutHorizontalMargin: dp(8) property int layoutVerticalMargin: dp(6) property int delegateHeight: dp(28) property int delegateWithHeaderHeight: dp(68) property int trackDelegateHeight: dp(45) property int coverImageSize: dp(180) property int smallImageSize: dp(32) property int trackMetadataWidth: dp(300) property int tooltipRadius: dp(3) property int shadowOffset: dp(2) property int delegateToolButtonSize: dp(34) property int smallDelegateToolButtonSize: dp(20) property int ratingStarSize: dp(15) property int mediaPlayerControlHeight: dp(48) property real mediaPlayerControlOpacity: 0.6 property int smallControlButtonHeight: dp(32) property int bigControlButtonHeight: dp(44) property int volumeSliderWidth: dp(140) property int dragDropPlaceholderHeight: dp(28) property int navigationBarHeight: dp(100) property int navigationBarFilterHeight: dp(44) property int gridDelegateHeight: dp(100) + layoutVerticalMargin + fontSize.height * 2 property int gridDelegateWidth: dp(100) property int viewSelectorDelegateHeight: dp(32) property int filterClearButtonMargin: layoutVerticalMargin property alias defaultFontPointSize: fontSize.font.pointSize Label { id: fontSize } }