diff --git a/src/qml/ContentView.qml b/src/qml/ContentView.qml index bac9eadc..c7cd4d55 100644 --- a/src/qml/ContentView.qml +++ b/src/qml/ContentView.qml @@ -1,690 +1,685 @@ /* * 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.Layouts 1.2 import QtQuick.Window 2.2 import org.kde.elisa 1.0 RowLayout { id: contentViewContainer spacing: 0 - property alias playList: playList - signal toggleSearch() function goBack() { localAlbums.goBack() localArtists.goBack() } ViewSelector { id: listViews Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.11 Layout.maximumWidth: mainWindow.width * 0.11 } Rectangle { id: viewSelectorSeparatorItem 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 } 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 Loader { anchors.fill: parent anchors.leftMargin: parent.width / 3 anchors.rightMargin: parent.width / 3 anchors.topMargin: parent.height / 3 anchors.bottomMargin: parent.height / 3 z: 2 sourceComponent: BusyIndicator { id: busyScanningMusic hoverEnabled: false anchors.fill: parent opacity: 0.8 visible: true running: true z: 2 } 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, { mainTitle: innerMainTitle, secondaryTitle: innerSecondaryTitle, image: innerImage, stackView: localAlbums.stackView, }) } onGoBack: localAlbums.stackView.pop() Binding { target: allAlbumsView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } 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, }) } onGoBack: localArtists.stackView.pop() Binding { target: allArtistsView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } visible: opacity > 0 } MediaBrowser { id: localTracks focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: ListBrowserView { id: allTracksView focus: true contentModel: elisa.allTracksProxyModel delegate: MediaTrackDelegate { id: entry width: allTracksView.delegateWidth height: elisaTheme.trackDelegateHeight focus: true trackData: model.containerData isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum onEnqueue: elisa.mediaPlayList.enqueue(data) onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) onClicked: contentDirectoryView.currentIndex = index } image: elisaTheme.tracksIcon mainTitle: i18nc("Title of the view of all tracks", "Tracks") Binding { target: allTracksView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } 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: elisa.audioControl.ensurePlay() onPausePlayback: elisa.audioControl.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 contentModel: elisa.singleArtistProxyModel isSubPage: true onOpen: { elisa.singleAlbumProxyModel.loadAlbumData(databaseId) localArtists.stackView.push(albumView, { mainTitle: innerMainTitle, secondaryTitle: innerSecondaryTitle, image: innerImage, stackView: localArtists.stackView, }) } onGoBack: localArtists.stackView.pop() Binding { target: innerAlbumGridView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } Component { id: albumView ListBrowserView { id: albumGridView property var stackView contentModel: elisa.singleAlbumProxyModel isSubPage: true delegate: MediaAlbumTrackDelegate { id: entry width: albumGridView.delegateWidth height: ((model.isFirstTrackOfDisc && !isSingleDiscAlbum) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) focus: true mediaTrack.trackData: model.containerData mediaTrack.isFirstTrackOfDisc: model.isFirstTrackOfDisc mediaTrack.isSingleDiscAlbum: model.isSingleDiscAlbum mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(data) mediaTrack.onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) mediaTrack.isAlternateColor: (index % 2) === 1 mediaTrack.onClicked: contentDirectoryView.currentIndex = index } allowArtistNavigation: true 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() expandedFilterView: true Binding { target: albumGridView property: 'expandedFilterView' value: persistentSettings.expandedFilterView } onFilterViewChanged: persistentSettings.expandedFilterView = expandedFilterView } } } diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index 0864ca0c..791eff10 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,343 +1,327 @@ /* * 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: 1100 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") property var findAction: elisa.action("edit_find") Controls1.Action { shortcut: findAction.shortcut onTriggered: { if ( persistentSettings.expandedFilterView == true) { persistentSettings.expandedFilterView = false } else { persistentSettings.expandedFilterView = true } } } Controls1.Action { shortcut: goBackAction.shortcut onTriggered: contentView.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 : 1100 property int height : 600 property var playListState property var playListControlerState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false + property bool playControlItemRepeat : false + property bool playControlItemShuffle : false + property bool expandedFilterView: false } 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 = elisa.audioControl.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted + + persistentSettings.playControlItemRepeat = headerBar.playerControl.repeat + persistentSettings.playControlItemShuffle = headerBar.playerControl.shuffle } } Loader { id: mprisloader active: false sourceComponent: PlatformIntegration { id: platformInterface playListModel: elisa.mediaPlayList playListControler: elisa.mediaPlayList audioPlayerManager: elisa.audioControl player: elisa.audioPlayer headerBarManager: myHeaderBarManager manageMediaPlayerControl: myPlayControlManager onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } } Connections { target: elisa.audioPlayer onVolumeChanged: headerBar.playerControl.volume = elisa.audioPlayer.volume onMutedChanged: headerBar.playerControl.muted = elisa.audioPlayer.muted 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) } } 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 } 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: elisa.audioPlayer.duration playerControl.seekable: elisa.audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: elisa.audioPlayer.position playerControl.skipBackwardEnabled: myPlayControlManager.skipBackwardControlEnabled playerControl.skipForwardEnabled: myPlayControlManager.skipForwardControlEnabled playerControl.playEnabled: myPlayControlManager.playControlEnabled playerControl.isPlaying: myPlayControlManager.musicPlaying + playerControl.repeat: persistentSettings.playControlItemRepeat + playerControl.shuffle: persistentSettings.playControlItemShuffle + playerControl.onSeek: elisa.audioPlayer.seek(position) playerControl.onPlay: elisa.audioControl.playPause() playerControl.onPause: elisa.audioControl.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 + right: headerBar.right + top: headerBar.top + rightMargin: elisaTheme.layoutHorizontalMargin * 1.75 + topMargin: 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 } } } ContentView { id: contentView Layout.fillHeight: true Layout.fillWidth: true } } } Component.onCompleted: { elisa.initialize() - elisa.mediaPlayList.randomPlay = Qt.binding(function() { return contentView.playList.randomPlayChecked }) - elisa.mediaPlayList.repeatPlay = Qt.binding(function() { return contentView.playList.repeatPlayChecked }) - myPlayControlManager.randomOrContinuePlay = Qt.binding(function() { return contentView.playList.randomPlayChecked || contentView.playList.repeatPlayChecked }) + elisa.mediaPlayList.randomPlay = Qt.binding(function() { return headerBar.playerControl.shuffle }) + elisa.mediaPlayList.repeatPlay = Qt.binding(function() { return headerBar.playerControl.repeat }) + myPlayControlManager.randomOrContinuePlay = Qt.binding(function() { return headerBar.playerControl.shuffle || headerBar.playerControl.repeat }) myPlayControlManager.playListModel = Qt.binding(function() { return elisa.mediaPlayList }) myPlayControlManager.currentTrack = Qt.binding(function() { return elisa.mediaPlayList.currentTrack }) if (persistentSettings.playListState) { elisa.mediaPlayList.persistentState = persistentSettings.playListState } + if (persistentSettings.audioPlayerState) { elisa.audioControl.persistentState = persistentSettings.audioPlayerState } elisa.audioPlayer.muted = Qt.binding(function() { return headerBar.playerControl.muted }) elisa.audioPlayer.volume = Qt.binding(function() { return headerBar.playerControl.volume }) volume: headerBar.playerControl.volume mprisloader.active = true } } diff --git a/src/qml/HeaderBar.qml b/src/qml/HeaderBar.qml index cb52b07d..58c99747 100644 --- a/src/qml/HeaderBar.qml +++ b/src/qml/HeaderBar.qml @@ -1,389 +1,389 @@ /* * Copyright 2016 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.Window 2.2 import QtGraphicalEffects 1.0 FocusScope { id: headerBar property string title property string artist property string album property string image property string newImage property string oldImage property string tracksCount property int trackRating property bool ratingVisible property alias playerControl: playControlItem onImageChanged: { if (changeBackgroundTransition.running) { changeBackgroundTransition.complete() } newImage = image changeBackgroundTransition.start() } Item { id: background anchors.fill: parent Image { id: oldBackground source: (oldImage ? oldImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) asynchronous: true anchors.fill: parent fillMode: Image.PreserveAspectCrop sourceSize.width: parent.width opacity: 1 layer.enabled: true layer.effect: HueSaturation { cached: true lightness: -0.5 saturation: 0.9 layer.enabled: true layer.effect: GaussianBlur { cached: true radius: 256 deviation: 12 samples: 129 transparentBorder: false } } } Image { id: newBackground source: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) asynchronous: true anchors.fill: parent fillMode: Image.PreserveAspectCrop sourceSize.width: parent.width visible: false opacity: 0 layer.enabled: true layer.effect: HueSaturation { cached: true lightness: -0.5 saturation: 0.9 layer.enabled: true layer.effect: GaussianBlur { cached: true radius: 256 deviation: 12 samples: 129 transparentBorder: false } } } } MediaPlayerControl { id: playControlItem focus: true anchors.left: background.left anchors.right: background.right anchors.bottom: background.bottom height: elisaTheme.mediaPlayerControlHeight } ColumnLayout { id: contentZone anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: playControlItem.top spacing: 0 Item { Layout.fillHeight: true Layout.fillWidth: true } RowLayout { spacing: 0 Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.fillHeight: true Item { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.preferredHeight: contentZone.height * 0.9 Layout.minimumHeight: contentZone.height * 0.9 Layout.maximumHeight: contentZone.height * 0.9 Layout.preferredWidth: contentZone.height * 0.9 Layout.minimumWidth: contentZone.height * 0.9 Layout.maximumWidth: contentZone.height * 0.9 Layout.leftMargin: !LayoutMirroring.enabled ? contentZone.width * 0.15 : 0 Layout.rightMargin: LayoutMirroring.enabled ? contentZone.width * 0.15 : 0 Image { id: oldMainIcon anchors.fill: parent asynchronous: true source: (oldImage ? oldImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) sourceSize { width: contentZone.height * 0.9 height: contentZone.height * 0.9 } fillMode: Image.PreserveAspectFit } Image { id: newMainIcon anchors.fill: parent asynchronous: true source: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) visible: false opacity: 0 sourceSize { width: contentZone.height * 0.9 height: contentZone.height * 0.9 } fillMode: Image.PreserveAspectFit } } ColumnLayout { spacing: 0 Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.fillWidth: true Layout.fillHeight: true TextMetrics { id: titleFontInfo font { bold: albumLabel.font.bold pointSize: albumLabel.font.pointSize } text: albumLabel.text } LabelWithToolTip { id: mainLabel text: title Layout.fillWidth: true Layout.alignment: Qt.AlignLeft elide: Text.ElideRight color: myPalette.highlightedText font.pointSize: elisaTheme.defaultFontPointSize * 2.5 font.bold: true Layout.bottomMargin: titleFontInfo.height * 0.5 } LabelWithToolTip { id: authorLabel text: artist Layout.fillWidth: true Layout.alignment: Qt.AlignLeft elide: Text.ElideRight color: myPalette.highlightedText font.pointSize: elisaTheme.defaultFontPointSize * 1.5 layer.effect: Glow { cached: true color: myPalette.shadow radius: 4.0 samples: 9 } } LabelWithToolTip { id: albumLabel text: album Layout.fillWidth: true Layout.alignment: Qt.AlignLeft elide: Text.ElideRight color: myPalette.highlightedText font.weight: Font.Light font.pointSize: elisaTheme.defaultFontPointSize * 1 layer.effect: Glow { cached: true color: myPalette.shadow radius: 4.0 samples: 9 } } RatingStar { id: mainRating visible: ratingVisible starSize: elisaTheme.ratingStarSize starRating: trackRating Layout.alignment: Qt.AlignLeft } } LabelWithToolTip { id: remainingTracksLabel text: i18np("1 track remaining", "%1 tracks remaining", tracksCount) Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.leftMargin: elisaTheme.layoutHorizontalMargin - Layout.rightMargin: elisaTheme.layoutHorizontalMargin + Layout.rightMargin: elisaTheme.layoutHorizontalMargin * 1.75 elide: Text.ElideRight visible: tracksCount > 0 color: myPalette.highlightedText } } Item { Layout.fillHeight: true Layout.fillWidth: true } } SequentialAnimation { id: changeBackgroundTransition PropertyAction { targets: [newBackground, newMainIcon] property: 'opacity' value: 0 } PropertyAction { targets: [newBackground, newMainIcon] property: 'visible' value: true } PropertyAction { target: newBackground property: "source" value: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) } PropertyAction { target: newMainIcon property: "source" value: (newImage ? newImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) } ParallelAnimation { NumberAnimation { targets: [newBackground, newMainIcon] property: 'opacity' from: 0 to: 1 duration: 250 easing.type: Easing.Linear } NumberAnimation { targets: [oldBackground, oldMainIcon] property: 'opacity' from: 1 to: 0 duration: 250 easing.type: Easing.Linear } } PropertyAction { target: headerBar property: "oldImage" value: image } PropertyAction { target: oldBackground property: 'source' value: (headerBar.oldImage ? headerBar.oldImage : Qt.resolvedUrl(elisaTheme.defaultBackgroundImage)) } PropertyAction { target: oldMainIcon property: 'source' value: (headerBar.oldImage ? headerBar.oldImage : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) } PropertyAction { targets: [oldBackground, oldMainIcon] property: 'opacity' value: 1 } PropertyAction { targets: [newBackground, newMainIcon] property: 'visible' value: false } onStopped: { oldImage = newImage } } } diff --git a/src/qml/MediaPlayListView.qml b/src/qml/MediaPlayListView.qml index c3377eeb..518f7be2 100644 --- a/src/qml/MediaPlayListView.qml +++ b/src/qml/MediaPlayListView.qml @@ -1,340 +1,321 @@ /* * 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.5 import QtQuick.Controls 2.2 import QtQuick.Controls 1.3 as Controls1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import QtQml.Models 2.1 import Qt.labs.platform 1.0 as PlatformDialog import org.kde.elisa 1.0 FocusScope { property StackView parentStackView property MediaPlayList playListModel - property alias randomPlayChecked: shuffleOption.checked - property alias repeatPlayChecked: repeatOption.checked - property int placeholderHeight: elisaTheme.dragDropPlaceholderHeight signal startPlayback() signal pausePlayback() signal displayError(var errorText) id: topItem Controls1.Action { id: clearPlayList text: i18nc("Remove all tracks from play list", "Clear Play List") iconName: "list-remove" enabled: playListModelDelegate.items.count > 0 onTriggered: playListModel.clearPlayList() } Controls1.Action { id: showCurrentTrack text: i18nc("Show currently played track inside playlist", "Show Current Track") iconName: 'media-show-active-track-amarok' enabled: playListModelDelegate.items.count > 0 onTriggered: { playListView.positionViewAtIndex(playListModel.currentTrackRow, ListView.Contain) playListView.currentIndex = playListModel.currentTrackRow playListView.currentItem.forceActiveFocus() } } Controls1.Action { id: loadPlaylist text: i18nc("Load a playlist file", "Load a Playlist") iconName: 'document-open' onTriggered: { fileDialog.fileMode = PlatformDialog.FileDialog.OpenFile fileDialog.file = '' fileDialog.open() } } Controls1.Action { id: savePlaylist text: i18nc("Save a playlist file", "Save a Playlist") iconName: 'document-save' enabled: playListModelDelegate.items.count > 0 onTriggered: { fileDialog.fileMode = PlatformDialog.FileDialog.SaveFile fileDialog.file = '' fileDialog.open() } } PlatformDialog.FileDialog { id: fileDialog defaultSuffix: 'm3u' folder: PlatformDialog.StandardPaths.writableLocation(PlatformDialog.StandardPaths.MusicLocation) nameFilters: [i18nc("file type (mime type) for m3u playlist", "Playlist (*.m3u)")] onAccepted: { if (fileMode === PlatformDialog.FileDialog.SaveFile) { if (!playListModel.savePlaylist(fileDialog.file)) { displayError(i18nc("message of passive notification when playlist load failed", "Save of playlist failed")) } } else { playListModel.loadPlaylist(fileDialog.file) } } } ColumnLayout { anchors.fill: parent spacing: 0 ColumnLayout { height: elisaTheme.navigationBarHeight Layout.preferredHeight: elisaTheme.navigationBarHeight Layout.minimumHeight: elisaTheme.navigationBarHeight Layout.maximumHeight: elisaTheme.navigationBarHeight spacing: 0 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 TextMetrics { id: titleHeight text: viewTitleHeight.text font { pointSize: viewTitleHeight.font.pointSize bold: viewTitleHeight.font.bold } } LabelWithToolTip { id: viewTitleHeight text: i18nc("Title of the view of the playlist", "Now Playing") color: myPalette.text font.pointSize: elisaTheme.defaultFontPointSize * 2 Layout.topMargin: elisaTheme.layoutVerticalMargin } LabelWithToolTip { id: playListInfo text: i18np("1 track", "%1 tracks", playListModel.tracksCount) color: myPalette.text Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter } Item { Layout.fillHeight: true } - - RowLayout { - Layout.bottomMargin: elisaTheme.layoutVerticalMargin - - CheckBox { - id: shuffleOption - - text: i18n("Shuffle") - } - - CheckBox { - id: repeatOption - - text: i18n("Repeat") - } - } } ListView { id: playListView Layout.fillWidth: true Layout.fillHeight: true focus: true ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds clip: true TextEdit { readOnly: true visible: playListModelDelegate.count === 0 wrapMode: TextEdit.Wrap color: myPalette.text font.weight: Font.ExtraLight font.pixelSize: elisaTheme.defaultFontPixelSize * 1.5 text: i18nc("Text shown when play list is empty", "Your play list is empty.\nIn order to start, you can explore your music library with the views on the left.\nUse the available buttons to add your selection.") anchors.fill: parent anchors.margins: elisaTheme.layoutHorizontalMargin } add: Transition { NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 100 } } populate: Transition { NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 100 } } remove: Transition { NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 } } displaced: Transition { NumberAnimation { properties: "x,y"; duration: 100; easing.type: Easing.InOutQuad} } model: DelegateModel { id: playListModelDelegate model: playListModel groups: [ DelegateModelGroup { name: "selected" } ] delegate: DraggableItem { id: item placeholderHeight: topItem.placeholderHeight focus: true PlayListEntry { id: entry focus: true width: scrollBar.visible ? playListView.width - scrollBar.width : playListView.width index: model.index isAlternateColor: item.DelegateModel.itemsIndex % 2 hasAlbumHeader: model.hasAlbumHeader isSingleDiscAlbum: model.isSingleDiscAlbum trackData: model.trackData titleDisplay: model.title isValid: model.isValid isPlaying: model.isPlaying isSelected: playListView.currentIndex === index containsMouse: item.containsMouse onStartPlayback: topItem.startPlayback() onPausePlayback: topItem.pausePlayback() onRemoveFromPlaylist: topItem.playListModel.removeRows(trackIndex, 1) onSwitchToTrack: topItem.playListModel.switchTo(trackIndex) } draggedItemParent: topItem onClicked: { playListView.currentIndex = index entry.forceActiveFocus() } onDoubleClicked: { if (model.isValid) { topItem.playListModel.switchTo(model.index) topItem.startPlayback() } } onMoveItemRequested: { playListModel.move(from, to, 1); } } } } Rectangle { id: actionBar Layout.fillWidth: true Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.preferredHeight: elisaTheme.delegateToolButtonSize color: myPalette.mid RowLayout { id: actionBarLayout anchors.fill: parent Controls1.ToolButton { action: clearPlayList Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Controls1.ToolButton { action: showCurrentTrack Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Controls1.ToolButton { action: loadPlaylist Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Controls1.ToolButton { action: savePlaylist Layout.bottomMargin: elisaTheme.layoutVerticalMargin } Item { Layout.fillWidth: true } } } } } diff --git a/src/qml/MediaPlayerControl.qml b/src/qml/MediaPlayerControl.qml index dc612309..92e65c52 100644 --- a/src/qml/MediaPlayerControl.qml +++ b/src/qml/MediaPlayerControl.qml @@ -1,482 +1,567 @@ /* * Copyright 2016 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 QtGraphicalEffects 1.0 import QtQuick.Controls 2.2 import org.kde.elisa 1.0 +import QtQuick.Controls 1.4 as Controls1 FocusScope { property double volume property int position property int duration property bool muted property bool isPlaying property bool seekable property bool playEnabled property bool skipForwardEnabled property bool skipBackwardEnabled + property bool shuffle + property bool repeat + signal play() signal pause() signal playPrevious() signal playNext() signal seek(int position) id: musicWidget SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Rectangle { anchors.fill: parent color: myPalette.midlight opacity: elisaTheme.mediaPlayerControlOpacity } RowLayout { anchors.fill: parent - spacing: 0 + spacing: 5 RoundButton { focus: skipBackwardEnabled - Layout.preferredWidth: elisaTheme.smallControlButtonHeight - Layout.preferredHeight: elisaTheme.smallControlButtonHeight + Layout.preferredWidth: elisaTheme.smallControlButtonSize + Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter - Layout.maximumWidth: elisaTheme.smallControlButtonHeight - Layout.maximumHeight: elisaTheme.smallControlButtonHeight - Layout.minimumWidth: elisaTheme.smallControlButtonHeight - Layout.minimumHeight: elisaTheme.smallControlButtonHeight - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.maximumWidth: elisaTheme.smallControlButtonSize + Layout.maximumHeight: elisaTheme.smallControlButtonSize + Layout.minimumWidth: elisaTheme.smallControlButtonSize + Layout.minimumHeight: elisaTheme.smallControlButtonSize + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.mediaPlayerHorizontalMargin : 0 + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.mediaPlayerHorizontalMargin : 0 enabled: skipBackwardEnabled hoverEnabled: true onClicked: { musicWidget.playPrevious() } contentItem: Image { anchors.fill: parent source: Qt.resolvedUrl(LayoutMirroring.enabled ? elisaTheme.skipForwardIcon : elisaTheme.skipBackwardIcon) - width: elisaTheme.smallControlButtonHeight - height: elisaTheme.smallControlButtonHeight + width: elisaTheme.smallControlButtonSize + height: elisaTheme.smallControlButtonSize - sourceSize.width: elisaTheme.smallControlButtonHeight - sourceSize.height: elisaTheme.smallControlButtonHeight + sourceSize.width: elisaTheme.smallControlButtonSize + sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit opacity: skipBackwardEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 - radius: elisaTheme.smallControlButtonHeight + radius: elisaTheme.smallControlButtonSize Behavior on border.color { ColorAnimation { duration: 300 } } } } RoundButton { focus: playEnabled - Layout.preferredWidth: elisaTheme.bigControlButtonHeight - Layout.preferredHeight: elisaTheme.bigControlButtonHeight + Layout.preferredWidth: elisaTheme.smallControlButtonSize + Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter - Layout.maximumWidth: elisaTheme.bigControlButtonHeight - Layout.maximumHeight: elisaTheme.bigControlButtonHeight - Layout.minimumWidth: elisaTheme.bigControlButtonHeight - Layout.minimumHeight: elisaTheme.bigControlButtonHeight + Layout.maximumWidth: elisaTheme.smallControlButtonSize + Layout.maximumHeight: elisaTheme.smallControlButtonSize + Layout.minimumWidth: elisaTheme.smallControlButtonSize + Layout.minimumHeight: elisaTheme.smallControlButtonSize enabled: playEnabled hoverEnabled: true onClicked: { if (musicWidget.isPlaying) { musicWidget.pause() } else { musicWidget.play() } } contentItem: Image { anchors.fill: parent source: { if (musicWidget.isPlaying) Qt.resolvedUrl(elisaTheme.pauseIcon) else Qt.resolvedUrl(elisaTheme.playIcon) } - width: elisaTheme.bigControlButtonHeight - height: elisaTheme.bigControlButtonHeight + width: elisaTheme.smallControlButtonSize + height: elisaTheme.smallControlButtonSize - sourceSize.width: elisaTheme.bigControlButtonHeight - sourceSize.height: elisaTheme.bigControlButtonHeight + sourceSize.width: elisaTheme.smallControlButtonSize + sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit mirror: LayoutMirroring.enabled opacity: playEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 - radius: elisaTheme.bigControlButtonHeight + radius: elisaTheme.smallControlButtonSize Behavior on border.color { ColorAnimation { duration: 300 } } } } RoundButton { focus: skipForwardEnabled - Layout.preferredWidth: elisaTheme.smallControlButtonHeight - Layout.preferredHeight: elisaTheme.smallControlButtonHeight + Layout.preferredWidth: elisaTheme.smallControlButtonSize + Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter - Layout.maximumWidth: elisaTheme.smallControlButtonHeight - Layout.maximumHeight: elisaTheme.smallControlButtonHeight - Layout.minimumWidth: elisaTheme.smallControlButtonHeight - Layout.minimumHeight: elisaTheme.smallControlButtonHeight - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.smallControlButtonHeight : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.smallControlButtonHeight : 0 + Layout.maximumWidth: elisaTheme.smallControlButtonSize + Layout.maximumHeight: elisaTheme.smallControlButtonSize + Layout.minimumWidth: elisaTheme.smallControlButtonSize + Layout.minimumHeight: elisaTheme.smallControlButtonSize enabled: skipForwardEnabled hoverEnabled: true onClicked: { musicWidget.playNext() } contentItem: Image { anchors.fill: parent source: Qt.resolvedUrl(LayoutMirroring.enabled ? elisaTheme.skipBackwardIcon : elisaTheme.skipForwardIcon) - width: elisaTheme.smallControlButtonHeight - height: elisaTheme.smallControlButtonHeight + width: elisaTheme.smallControlButtonSize + height: elisaTheme.smallControlButtonSize - sourceSize.width: elisaTheme.smallControlButtonHeight - sourceSize.height: elisaTheme.smallControlButtonHeight + sourceSize.width: elisaTheme.smallControlButtonSize + sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit opacity: skipForwardEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 - radius: elisaTheme.smallControlButtonHeight + radius: elisaTheme.smallControlButtonSize Behavior on border.color { ColorAnimation { duration: 300 } } } } TextMetrics { id: durationTextMetrics text: i18nc("This is used to preserve a fixed width for the duration text.", "00:00:00") } LabelWithToolTip { id: positionLabel text: timeIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : elisaTheme.layoutHorizontalMargin * 2 + Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : elisaTheme.layoutHorizontalMargin * 2 Layout.preferredWidth: durationTextMetrics.width+5 // be in the safe side verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight ProgressIndicator { id: timeIndicator position: musicWidget.position } } Slider { property bool seekStarted: false property int seekValue id: musicProgress from: 0 to: musicWidget.duration Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 enabled: musicWidget.seekable && musicWidget.playEnabled live: true onValueChanged: { if (seekStarted) { seekValue = value } } onPressedChanged: { if (pressed) { seekStarted = true; seekValue = value } else { musicWidget.seek(seekValue) seekStarted = false; } } background: Rectangle { x: musicProgress.leftPadding y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: 200 implicitHeight: 6 width: musicProgress.availableWidth height: implicitHeight radius: 3 - color: myPalette.mid + color: myPalette.dark Rectangle { x: (LayoutMirroring.enabled ? musicProgress.visualPosition * parent.width : 0) - width: (LayoutMirroring.enabled ? parent.width - musicProgress.visualPosition * parent.width : musicProgress.visualPosition * parent.width) + width: LayoutMirroring.enabled ? parent.width - musicProgress.visualPosition * parent.width: musicProgress.handle.x + radius height: parent.height color: myPalette.text radius: 3 } } handle: Rectangle { x: musicProgress.leftPadding + musicProgress.visualPosition * (musicProgress.availableWidth - width) y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 color: myPalette.button border.color: musicProgress.pressed ? myPalette.highlight : myPalette.dark } } LabelWithToolTip { id: durationLabel text: durationIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true - Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 10) : 0 - Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 10) : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 + Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 Layout.preferredWidth: durationTextMetrics.width verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft ProgressIndicator { id: durationIndicator position: musicWidget.duration } } Image { id: volumeIcon source: if (musicWidget.muted) Qt.resolvedUrl(elisaTheme.playerVolumeMutedIcon) else Qt.resolvedUrl(elisaTheme.playerVolumeIcon) - Layout.preferredWidth: elisaTheme.smallControlButtonHeight - Layout.preferredHeight: elisaTheme.smallControlButtonHeight + Layout.preferredWidth: elisaTheme.smallControlButtonSize + Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter - Layout.maximumWidth: elisaTheme.smallControlButtonHeight - Layout.maximumHeight: elisaTheme.smallControlButtonHeight - Layout.minimumWidth: elisaTheme.smallControlButtonHeight - Layout.minimumHeight: elisaTheme.smallControlButtonHeight + Layout.maximumWidth: elisaTheme.smallControlButtonSize + Layout.maximumHeight: elisaTheme.smallControlButtonSize + Layout.minimumWidth: elisaTheme.smallControlButtonSize + Layout.minimumHeight: elisaTheme.smallControlButtonSize Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - sourceSize.width: elisaTheme.smallControlButtonHeight - sourceSize.height: elisaTheme.smallControlButtonHeight + sourceSize.width: elisaTheme.smallControlButtonSize + sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit visible: false } RoundButton { focus: true - Layout.preferredWidth: elisaTheme.smallControlButtonHeight - Layout.preferredHeight: elisaTheme.smallControlButtonHeight + Layout.preferredWidth: elisaTheme.smallControlButtonSize + Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter - Layout.maximumWidth: elisaTheme.smallControlButtonHeight - Layout.maximumHeight: elisaTheme.smallControlButtonHeight - Layout.minimumWidth: elisaTheme.smallControlButtonHeight - Layout.minimumHeight: elisaTheme.smallControlButtonHeight - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.maximumWidth: elisaTheme.smallControlButtonSize + Layout.maximumHeight: elisaTheme.smallControlButtonSize + Layout.minimumWidth: elisaTheme.smallControlButtonSize + Layout.minimumHeight: elisaTheme.smallControlButtonSize hoverEnabled: true onClicked: musicWidget.muted = !musicWidget.muted contentItem: Image { anchors.fill: parent source: if (musicWidget.muted) Qt.resolvedUrl(elisaTheme.playerVolumeMutedIcon) else Qt.resolvedUrl(elisaTheme.playerVolumeIcon) - width: elisaTheme.smallControlButtonHeight - height: elisaTheme.smallControlButtonHeight + width: elisaTheme.smallControlButtonSize + height: elisaTheme.smallControlButtonSize - sourceSize.width: elisaTheme.smallControlButtonHeight - sourceSize.height: elisaTheme.smallControlButtonHeight + sourceSize.width: elisaTheme.smallControlButtonSize + sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 - radius: elisaTheme.smallControlButtonHeight + radius: elisaTheme.smallControlButtonSize Behavior on border.color { ColorAnimation { duration: 300 } } } } Slider { id: volumeSlider from: 0 to: 100 value: musicWidget.volume onValueChanged: musicWidget.volume = value enabled: !muted Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: elisaTheme.volumeSliderWidth Layout.maximumWidth: elisaTheme.volumeSliderWidth Layout.minimumWidth: elisaTheme.volumeSliderWidth - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 3 : 0 + Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 3 : 0 width: elisaTheme.volumeSliderWidth background: Rectangle { x: volumeSlider.leftPadding y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: 200 implicitHeight: 6 width: volumeSlider.availableWidth height: implicitHeight radius: 3 - color: myPalette.mid + color: myPalette.dark Rectangle { x: (LayoutMirroring.enabled ? volumeSlider.visualPosition * parent.width : 0) width: (LayoutMirroring.enabled ? parent.width - volumeSlider.visualPosition * parent.width : volumeSlider.visualPosition * parent.width) height: parent.height color: myPalette.text radius: 3 } } handle: Rectangle { x: volumeSlider.leftPadding + volumeSlider.visualPosition * (volumeSlider.availableWidth - width) y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 color: myPalette.button border.color: volumeSlider.pressed ? myPalette.highlight : myPalette.dark } } + + RoundButton { + focus: true + id: shuffleButton + + Layout.preferredWidth: elisaTheme.smallControlButtonSize + Layout.preferredHeight: elisaTheme.smallControlButtonSize + Layout.alignment: Qt.AlignCenter + Layout.maximumWidth: elisaTheme.smallControlButtonSize + Layout.maximumHeight: elisaTheme.smallControlButtonSize + Layout.minimumWidth: elisaTheme.smallControlButtonSize + Layout.minimumHeight: elisaTheme.smallControlButtonSize + + hoverEnabled: true + onClicked: musicWidget.shuffle = !musicWidget.shuffle + + contentItem: Image { + anchors.fill: parent + + source: musicWidget.shuffle ? Qt.resolvedUrl(elisaTheme.shuffleIcon) : Qt.resolvedUrl(elisaTheme.noShuffleIcon) + + width: elisaTheme.smallControlButtonSize + height: elisaTheme.smallControlButtonSize + + sourceSize.width: elisaTheme.smallControlButtonSize + sourceSize.height: elisaTheme.smallControlButtonSize + + fillMode: Image.PreserveAspectFit + } + + background: Rectangle { + color: "transparent" + } + } + + + RoundButton { + focus: true + id: repeatButton + + Layout.preferredWidth: elisaTheme.smallControlButtonSize + Layout.preferredHeight: elisaTheme.smallControlButtonSize + Layout.alignment: Qt.AlignCenter + Layout.maximumWidth: elisaTheme.smallControlButtonSize + Layout.maximumHeight: elisaTheme.smallControlButtonSize + Layout.minimumWidth: elisaTheme.smallControlButtonSize + Layout.minimumHeight: elisaTheme.smallControlButtonSize + + hoverEnabled: true + onClicked: musicWidget.repeat = !musicWidget.repeat + + contentItem: Image { + anchors.fill: parent + + source: musicWidget.repeat ? Qt.resolvedUrl(elisaTheme.repeatIcon) : Qt.resolvedUrl(elisaTheme.noRepeatIcon) + + width: elisaTheme.smallControlButtonSize + height: elisaTheme.smallControlButtonSize + + sourceSize.width: elisaTheme.smallControlButtonSize + sourceSize.height: elisaTheme.smallControlButtonSize + + fillMode: Image.PreserveAspectFit + } + + background: Rectangle { + color: "transparent" + } + + } + + Controls1.ToolButton { + id: menuButton + action: applicationMenuAction + + Layout.preferredWidth: elisaTheme.smallControlButtonSize + Layout.preferredHeight: elisaTheme.smallControlButtonSize + Layout.alignment: Qt.AlignVCenter + Layout.maximumWidth: elisaTheme.smallControlButtonSize + Layout.maximumHeight: elisaTheme.smallControlButtonSize + Layout.minimumWidth: elisaTheme.smallControlButtonSize + Layout.minimumHeight: elisaTheme.smallControlButtonSize + Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.mediaPlayerHorizontalMargin : elisaTheme.mediaPlayerHorizontalMargin * 1.5 + Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.mediaPlayerHorizontalMargin : elisaTheme.mediaPlayerHorizontalMargin * 1.5 + } } onPositionChanged: { if (!musicProgress.seekStarted) { musicProgress.value = position } } onVolumeChanged: { console.log('volume of player controls changed: ' + volume) } } diff --git a/src/qml/Theme.qml b/src/qml/Theme.qml index e87d03f2..3d972d0d 100644 --- a/src/qml/Theme.qml +++ b/src/qml/Theme.qml @@ -1,91 +1,95 @@ /* * 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 string repeatIcon: 'image://icon/media-repeat-all' + property string shuffleIcon: 'image://icon/media-playlist-shuffle' + property string noRepeatIcon: 'image://icon/media-repeat-none' + property string noShuffleIcon: 'image://icon/media-playlist-normal' 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 int mediaPlayerControlHeight: dp(42) + property int mediaPlayerHorizontalMargin: dp(10) property real mediaPlayerControlOpacity: 0.6 - property int smallControlButtonHeight: dp(32) - property int bigControlButtonHeight: dp(44) - property int volumeSliderWidth: dp(140) + property int smallControlButtonSize: dp(22) + property int volumeSliderWidth: dp(100) 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(24) property int filterClearButtonMargin: layoutVerticalMargin property alias defaultFontPointSize: fontSize.font.pointSize Label { id: fontSize } } diff --git a/src/qml/TrackImportNotification.qml b/src/qml/TrackImportNotification.qml index 4418ffcd..c8fcb319 100644 --- a/src/qml/TrackImportNotification.qml +++ b/src/qml/TrackImportNotification.qml @@ -1,70 +1,71 @@ /* * 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 import org.kde.elisa 1.0 Rectangle { id: rootComponent property bool indexingRunning property int importedTracksCount property MusicListenersManager musicManager color: myPalette.highlight width: elisaTheme.gridDelegateWidth * 1.5 + height: elisaTheme.smallControlButtonSize * 1.5 visible: opacity > 0 opacity: (indexingRunning ? 1 : 0) Label { anchors.centerIn: parent text: i18ncp("number of imported tracks", "Imported one track", "Imported %1 tracks", importedTracksCount) color: myPalette.highlightedText } Timer { id: hideTimer interval: 6000 repeat: false onTriggered: { rootComponent.opacity = 0 } } Behavior on opacity { NumberAnimation { easing.type: Easing.InOutQuad duration: 300 } } onIndexingRunningChanged: if (indexingRunning) { hideTimer.stop() opacity = 1 } else { hideTimer.start() } }