diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2a834f2a..918a3731 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,492 +1,493 @@ include_directories(${elisa_BINARY_DIR}) set(elisaLib_SOURCES mediaplaylist.cpp musicaudiotrack.cpp progressindicator.cpp databaseinterface.cpp musiclistenersmanager.cpp managemediaplayercontrol.cpp manageheaderbar.cpp manageaudioplayer.cpp trackslistener.cpp elisaapplication.cpp modeldataloader.cpp notificationitem.cpp topnotificationmanager.cpp elisautils.cpp datatype.cpp abstractfile/abstractfilelistener.cpp abstractfile/abstractfilelisting.cpp filescanner.cpp viewmanager.cpp powermanagementinterface.cpp file/filelistener.cpp file/localfilelisting.cpp models/datamodel.cpp models/abstractmediaproxymodel.cpp models/gridviewproxymodel.cpp models/alltracksproxymodel.cpp models/singlealbumproxymodel.cpp models/trackmetadatamodel.cpp models/trackcontextmetadatamodel.cpp models/viewsmodel.cpp ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "indexersManager.h" IDENTIFIER "orgKdeElisaIndexersManager" CATEGORY_NAME "org.kde.elisa.indexers.manager" DEFAULT_SEVERITY Info ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "databaseLogging.h" IDENTIFIER "orgKdeElisaDatabase" CATEGORY_NAME "org.kde.elisa.database" DEFAULT_SEVERITY Info ) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "abstractfile/indexercommon.h" IDENTIFIER "orgKdeElisaIndexer" CATEGORY_NAME "org.kde.elisa.indexer" DEFAULT_SEVERITY Info ) if (LIBVLC_FOUND) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "vlcLogging.h" IDENTIFIER "orgKdeElisaPlayerVlc" CATEGORY_NAME "org.kde.elisa.player.vlc" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} audiowrapper_libvlc.cpp ) else() ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "qtMultimediaLogging.h" IDENTIFIER "orgKdeElisaPlayerQtMultimedia" CATEGORY_NAME "org.kde.elisa.player.qtMultimedia" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} audiowrapper_qtmultimedia.cpp ) endif() if (ANDROID) set(elisaLib_SOURCES ${elisaLib_SOURCES} android/androidmusiclistener.cpp ) endif() if (KF5KIO_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} models/filebrowsermodel.cpp models/filebrowserproxymodel.cpp ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) ecm_qt_declare_logging_category(elisaLib_SOURCES HEADER "baloo/baloocommon.h" IDENTIFIER "orgKdeElisaBaloo" CATEGORY_NAME "org.kde.elisa.baloo" DEFAULT_SEVERITY Info ) set(elisaLib_SOURCES ${elisaLib_SOURCES} baloo/localbaloofilelisting.cpp baloo/baloolistener.cpp baloo/baloodetector.cpp ) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.main.xml baloo/main) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) qt5_add_dbus_adaptor(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.BalooWatcherApplication.xml baloo/localbaloofilelisting.h LocalBalooFileListing) endif() endif() if (Qt5DBus_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} mpris2/mpris2.cpp mpris2/mediaplayer2.cpp mpris2/mediaplayer2player.cpp ) endif() if (UPNPQT_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} upnp/upnpcontrolcontentdirectory.cpp upnp/upnpcontentdirectorymodel.cpp upnp/upnpcontrolconnectionmanager.cpp upnp/upnpcontrolmediaserver.cpp upnp/didlparser.cpp upnp/upnplistener.cpp upnp/upnpdiscoverallmusic.cpp ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(elisaLib_SOURCES ${elisaLib_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() kconfig_add_kcfg_files(elisaLib_SOURCES ../src/elisa_settings.kcfgc ) set(elisaLib_SOURCES ${elisaLib_SOURCES} ../src/elisa_core.kcfg ) add_library(elisaLib ${elisaLib_SOURCES}) target_link_libraries(elisaLib LINK_PUBLIC Qt5::Multimedia LINK_PRIVATE Qt5::Core Qt5::Sql Qt5::Widgets Qt5::Concurrent Qt5::Qml KF5::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui) if (KF5FileMetaData_FOUND) target_link_libraries(elisaLib LINK_PRIVATE KF5::FileMetaData ) endif() if (KF5KIO_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets ) endif() if (KF5XmlGui_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::XmlGui ) endif() if (KF5ConfigWidgets_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::ConfigWidgets ) endif() if (KF5KCMUtils_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::KCMUtils ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::Baloo ) endif() endif() if (Qt5DBus_FOUND) target_link_libraries(elisaLib LINK_PUBLIC Qt5::DBus ) if (KF5DBusAddons_FOUND) target_link_libraries(elisaLib LINK_PUBLIC KF5::DBusAddons ) endif() endif() if (LIBVLC_FOUND) target_include_directories(elisaLib PRIVATE ${LIBVLC_INCLUDE_DIR} ) target_link_libraries(elisaLib LINK_PRIVATE ${LIBVLC_LIBRARY} ) endif() if (ANDROID) target_link_libraries(elisaLib LINK_PUBLIC Qt5::AndroidExtras ) endif() generate_export_header(elisaLib BASE_NAME ElisaLib EXPORT_FILE_NAME elisaLib_export.h) set_target_properties(elisaLib PROPERTIES VERSION 0.1 SOVERSION 0 EXPORT_NAME ElisaLib ) if (NOT APPLE AND NOT WIN32) install(TARGETS elisaLib LIBRARY DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa NAMELINK_SKIP RUNTIME DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa BUNDLE DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa ) else() install(TARGETS elisaLib ${INSTALL_TARGETS_DEFAULT_ARGS}) endif() set(elisaqmlplugin_SOURCES elisaqmlplugin.cpp datatype.cpp elisautils.cpp ) if (KF5FileMetaData_FOUND) set(elisaqmlplugin_SOURCES ${elisaqmlplugin_SOURCES} embeddedcoverageimageprovider.cpp ) endif() add_library(elisaqmlplugin SHARED ${elisaqmlplugin_SOURCES}) target_link_libraries(elisaqmlplugin LINK_PRIVATE Qt5::Quick Qt5::Widgets KF5::ConfigCore KF5::ConfigGui elisaLib ) if (KF5FileMetaData_FOUND) target_link_libraries(elisaqmlplugin LINK_PRIVATE KF5::FileMetaData ) endif() set_target_properties(elisaqmlplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa ) if (NOT APPLE AND NOT WIN32) set_target_properties(elisaqmlplugin PROPERTIES INSTALL_RPATH "${KDE_INSTALL_FULL_LIBDIR}/elisa;${CMAKE_INSTALL_RPATH}" ) endif() install(TARGETS elisaqmlplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/elisa/) install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/elisa) add_custom_target(copy) add_custom_target(copy2) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa) add_custom_command(TARGET copy PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/qmldir ${CMAKE_BINARY_DIR}/bin/org/kde/elisa/) add_custom_command(TARGET copy2 PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugins.qmltypes ${CMAKE_BINARY_DIR}/bin/org/kde/elisa/) add_dependencies(elisaqmlplugin copy copy2) if (Qt5Quick_FOUND AND Qt5Widgets_FOUND) set(elisa_SOURCES main.cpp windows/WindowsTheme.qml windows/PlatformIntegration.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml android/AlbumsView.qml android/ArtistsView.qml android/TracksView.qml android/GenresView.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/BaseTheme.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/RatingStar.qml qml/DraggableItem.qml qml/PassiveNotification.qml qml/TopNotification.qml qml/TopNotificationItem.qml qml/TrackImportNotification.qml qml/HeaderBar.qml qml/NavigationActionBar.qml qml/MediaPlayerControl.qml qml/ContextView.qml qml/ContextViewLyrics.qml qml/ContentView.qml qml/ViewSelector.qml + qml/ViewSelectorDelegate.qml qml/DataGridView.qml qml/TracksView.qml qml/AlbumView.qml qml/RecentlyPlayedTracks.qml qml/FrequentlyPlayedTracks.qml qml/MediaPlayListView.qml qml/PlayListBasicView.qml qml/PlayListEntry.qml qml/SimplePlayListView.qml qml/SimplePlayListEntry.qml qml/PlayListAlbumHeader.qml qml/BasicPlayListAlbumHeader.qml qml/MetaDataDelegate.qml qml/MediaTrackDelegate.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml qml/ListBrowserView.qml qml/FileBrowserDelegate.qml qml/FileBrowserView.qml qml/ScrollHelper.qml qml/FlatButtonWithToolTip.qml ) qt5_add_resources(elisa_SOURCES resources.qrc) set_property(SOURCE qrc_resources.cpp PROPERTY SKIP_AUTOMOC ON) set(elisa_ICONS_PNG ../icons/128-apps-elisa.png ../icons/64-apps-elisa.png ../icons/48-apps-elisa.png ../icons/32-apps-elisa.png ../icons/22-apps-elisa.png ../icons/16-apps-elisa.png ) # add icons to application sources, to have them bundled ecm_add_app_icon(elisa_SOURCES ICONS ${elisa_ICONS_PNG}) add_executable(elisa ${elisa_SOURCES}) target_include_directories(elisa PRIVATE ${KDSoap_INCLUDE_DIRS}) target_link_libraries(elisa LINK_PRIVATE elisaLib Qt5::Widgets Qt5::QuickControls2 KF5::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui ) if (ANDROID) target_link_libraries(elisa LINK_PRIVATE Qt5::AndroidExtras Qt5::Svg Qt5::Sql Qt5::Concurrent KF5::Kirigami2 ) endif() if (KF5Crash_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Crash ) endif() if (KF5Declarative_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Declarative ) endif() if (NOT APPLE AND NOT WIN32) set_target_properties(elisa PROPERTIES INSTALL_RPATH "${KDE_INSTALL_FULL_LIBDIR}/elisa;${CMAKE_INSTALL_RPATH}" ) endif() install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS}) endif() if (KF5ConfigWidgets_FOUND AND KF5Declarative_FOUND) add_subdirectory(localFileConfiguration) endif() set(elisaImport_SOURCES elisaimport.cpp elisaimportapplication.cpp ) kconfig_add_kcfg_files(elisaImport_SOURCES ../src/elisa_settings.kcfgc ) set(elisaImport_SOURCES ${elisaImport_SOURCES} ../src/elisa_core.kcfg ) add_executable(elisaImport ${elisaImport_SOURCES}) target_link_libraries(elisaImport LINK_PRIVATE KF5::ConfigCore KF5::ConfigGui elisaLib ) if (KF5FileMetaData_FOUND) target_link_libraries(elisaImport LINK_PRIVATE KF5::FileMetaData ) endif() set(QML_IMPORT_PATH ${CMAKE_BINARY_DIR}/bin CACHE INTERNAL "qml import path" FORCE) if (ANDROID) kirigami_package_breeze_icons(ICONS elisa) endif() diff --git a/src/qml/AlbumView.qml b/src/qml/AlbumView.qml index 9e69b6ca..a9fdb7bc 100644 --- a/src/qml/AlbumView.qml +++ b/src/qml/AlbumView.qml @@ -1,127 +1,133 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.3 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: viewHeader property var viewType property alias mainTitle: albumGridView.mainTitle property alias secondaryTitle: albumGridView.secondaryTitle property alias image: albumGridView.image property alias databaseId: albumGridView.databaseId DataModel { id: realModel } SingleAlbumProxyModel { id: proxyModel sourceModel: realModel onEntriesToEnqueue: elisa.mediaPlayList.enqueue(newEntries, databaseIdType, enqueueMode, triggerPlay) } ListBrowserView { id: albumGridView focus: true - activeFocusOnTab: true anchors.fill: parent contentModel: proxyModel isSubPage: true enableSorting: false delegate: MediaAlbumTrackDelegate { id: entry width: albumGridView.delegateWidth height: ((true && !true) ? elisaTheme.delegateHeight*2 : elisaTheme.delegateHeight) focus: true databaseId: model.databaseId title: model.title artist: model.artist album: (model.album !== undefined && model.album !== '' ? model.album : '') albumArtist: model.albumArtist duration: model.duration imageUrl: (model.imageUrl !== undefined && model.imageUrl !== '' ? model.imageUrl : '') trackNumber: model.trackNumber discNumber: model.discNumber rating: model.rating isFirstTrackOfDisc: true isSingleDiscAlbum: true + isSelected: albumGridView.currentIndex === index isAlternateColor: (index % 2) === 1 mediaTrack.onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, ElisaUtils.Track, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) mediaTrack.onReplaceAndPlay: elisa.mediaPlayList.enqueue(databaseId, name, ElisaUtils.Track, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) mediaTrack.onClicked: albumGridView.currentIndex = index + + onActiveFocusChanged: { + if (activeFocus && albumGridView.currentIndex !== index) { + albumGridView.currentIndex = index + } + } } allowArtistNavigation: true onShowArtist: { viewManager.openChildView(secondaryTitle, '', elisaTheme.artistIcon, 0, ElisaUtils.Artist) } onGoBack: viewManager.goBack() Loader { anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height visible: realModel.isBusy active: realModel.isBusy sourceComponent: BusyIndicator { anchors.centerIn: parent } } } Connections { target: elisa onMusicManagerChanged: realModel.initializeById(elisa.musicManager, elisa.musicManager.viewDatabase, ElisaUtils.Track, databaseId) } Component.onCompleted: { if (elisa.musicManager) { realModel.initializeById(elisa.musicManager, elisa.musicManager.viewDatabase, ElisaUtils.Track, databaseId) } } } diff --git a/src/qml/ContentView.qml b/src/qml/ContentView.qml index 08ccc915..85cc38f7 100644 --- a/src/qml/ContentView.qml +++ b/src/qml/ContentView.qml @@ -1,571 +1,566 @@ /* * Copyright 2016-2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import org.kde.elisa 1.0 RowLayout { id: contentViewContainer spacing: 0 property bool showPlaylist property alias currentViewIndex: listViews.currentIndex signal toggleSearch() function goBack() { viewManager.goBack() } function openArtist(name) { viewManager.openChildView(name, '', elisaTheme.artistIcon, 0, ElisaUtils.Artist) } function openAlbum(album, artist, image, albumID) { image = !image ? elisaTheme.defaultAlbumImage : image; viewManager.openChildView(album, artist, image, albumID, ElisaUtils.Album); } function openNowPlaying() { viewManager.closeAllViews(); } ViewManager { id: viewManager onSwitchOffAllViews: { listViews.setCurrentIndex(pageModel.indexFromViewType(viewType)) while(browseStackView.depth > 1) { browseStackView.pop() } } onSwitchRecentlyPlayedTracksView: { listViews.setCurrentIndex(pageModel.indexFromViewType(viewType)) while(browseStackView.depth > expectedDepth) { browseStackView.pop() } browseStackView.push(allRecentlyPlayedTracksView, { viewType: viewType, mainTitle: mainTitle, image: imageUrl, modelType: dataType, stackView: browseStackView, opacity: 0, }) } onSwitchFrequentlyPlayedTracksView: { listViews.setCurrentIndex(pageModel.indexFromViewType(viewType)) while(browseStackView.depth > expectedDepth) { browseStackView.pop() } browseStackView.push(allFrequentlyPlayedTracksView, { viewType: viewType, mainTitle: mainTitle, image: imageUrl, modelType: dataType, stackView: browseStackView, opacity: 0, }) } onOpenGridView: { if (expectedDepth === 1) { listViews.setCurrentIndex(pageModel.indexFromViewType(viewType)) } while(browseStackView.depth > expectedDepth) { browseStackView.pop() } browseStackView.push(dataGridView, { viewType: viewType, mainTitle: pageModel.viewMainTitle(viewType, mainTitle), secondaryTitle: secondaryTitle, image: pageModel.viewImageUrl(viewType, imageUrl), modelType: dataType, defaultIcon: viewDefaultIcon, showRating: viewShowRating, delegateDisplaySecondaryText: viewDelegateDisplaySecondaryText, genreFilterText: genreNameFilter, artistFilter: artistNameFilter, isSubPage: (browseStackView.depth >= 2), stackView: browseStackView, opacity: 0, }) } onSwitchOneAlbumView: { while(browseStackView.depth > expectedDepth) { browseStackView.pop() } browseStackView.push(albumView, { viewType: viewType, mainTitle: mainTitle, secondaryTitle: secondaryTitle, image: imageUrl, databaseId: databaseId, stackView: browseStackView, opacity: 0, }) } onSwitchAllTracksView: { listViews.setCurrentIndex(pageModel.indexFromViewType(viewType)) while(browseStackView.depth > expectedDepth) { browseStackView.pop() } browseStackView.push(allTracksView, { viewType: viewType, mainTitle: mainTitle, image: imageUrl, modelType: dataType, stackView: browseStackView, opacity: 0, }) } onSwitchFilesBrowserView: { listViews.setCurrentIndex(pageModel.indexFromViewType(viewType)) while(browseStackView.depth > expectedDepth) { browseStackView.pop() } browseStackView.push(filesBrowserView, { viewType: viewType, mainTitle: mainTitle, image: imageUrl, opacity: 0, }) } onPopOneView: { if (browseStackView.depth > 2) { browseStackView.pop() } } } ViewsModel { id: pageModel } ViewSelector { id: listViews model: pageModel Layout.fillHeight: true Behavior on Layout.maximumWidth { NumberAnimation { duration: 150 } } onSwitchView: viewManager.openParentView(viewType, pageModel.viewMainTitle(viewType, ""), pageModel.viewImageUrl(viewType, "")) } 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 MouseArea { anchors.fill: parent acceptedButtons: Qt.BackButton onClicked: goBack() } Rectangle { - border { - color: (mainContentView.activeFocus ? myPalette.highlight : myPalette.base) - width: 1 - } - radius: 3 color: myPalette.base anchors.fill: parent StackView { id: browseStackView anchors.fill: parent clip: true initialItem: Item { } popEnter: Transition { OpacityAnimator { from: 0.0 to: 1.0 duration: 300 } } popExit: Transition { OpacityAnimator { from: 1.0 to: 0.0 duration: 300 } } pushEnter: Transition { OpacityAnimator { from: 0.0 to: 1.0 duration: 300 } } pushExit: Transition { OpacityAnimator { from: 1.0 to: 0.0 duration: 300 } } replaceEnter: Transition { OpacityAnimator { from: 0.0 to: 1.0 duration: 300 } } replaceExit: Transition { OpacityAnimator { from: 1.0 to: 0.0 duration: 300 } } } 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 Layout.fillHeight: true Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 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 } Loader { id: albumContext active: Layout.minimumWidth != 0 sourceComponent: ContextView { anchors.fill: parent databaseId: elisa.manageHeaderBar.databaseId title: elisa.manageHeaderBar.title artistName: elisa.manageHeaderBar.artist albumName: elisa.manageHeaderBar.album albumArtUrl: elisa.manageHeaderBar.image fileUrl: elisa.manageHeaderBar.fileName } Layout.fillHeight: true Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 1.5 : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 1.5 : 0 visible: Layout.minimumWidth != 0 } } } states: [ State { name: 'playList' when: listViews.currentIndex === 0 PropertyChanges { target: mainContentView 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 / 5 + elisaTheme.layoutHorizontalMargin Layout.maximumWidth: contentZone.width * 2 / 5 + elisaTheme.layoutHorizontalMargin Layout.preferredWidth: contentZone.width * 2 / 5 + elisaTheme.layoutHorizontalMargin } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: albumContext Layout.minimumWidth: contentZone.width * 3 / 5 - 2 - 3.5 * elisaTheme.layoutHorizontalMargin Layout.maximumWidth: contentZone.width * 3 / 5 - 2 - 3.5 * elisaTheme.layoutHorizontalMargin Layout.preferredWidth: contentZone.width * 3 / 5 - 2 - 3.5 * elisaTheme.layoutHorizontalMargin } }, State { name: "browsingViewsNoPlaylist" when: listViews.currentIndex !== 0 && contentViewContainer.showPlaylist !== true extend: "browsingViews" PropertyChanges { target: mainContentView Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width } PropertyChanges { target: playList Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } }, State { name: 'browsingViews' when: listViews.currentIndex !== 0 PropertyChanges { target: mainContentView 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 - 3 * elisaTheme.layoutHorizontalMargin + 2 Layout.maximumWidth: contentZone.width * 0.33 - 3 * elisaTheme.layoutHorizontalMargin + 2 Layout.preferredWidth: contentZone.width * 0.33 - 3 * elisaTheme.layoutHorizontalMargin + 2 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" easing.type: Easing.InOutQuad duration: 300 } } } Component { id: allFrequentlyPlayedTracksView FrequentlyPlayedTracks { StackView.onActivated: viewManager.viewIsLoaded(viewType) } } Component { id: allRecentlyPlayedTracksView RecentlyPlayedTracks { StackView.onActivated: viewManager.viewIsLoaded(viewType) } } Component { id: dataGridView DataGridView { StackView.onActivated: viewManager.viewIsLoaded(viewType) } } Component { id: allTracksView TracksView { StackView.onActivated: viewManager.viewIsLoaded(viewType) } } Component { id: albumView AlbumView { StackView.onActivated: viewManager.viewIsLoaded(viewType) } } Component { id: filesBrowserView FileBrowserView { StackView.onActivated: viewManager.viewIsLoaded(viewType) } } } diff --git a/src/qml/FileBrowserDelegate.qml b/src/qml/FileBrowserDelegate.qml index 1fc74baf..a9ac1f34 100644 --- a/src/qml/FileBrowserDelegate.qml +++ b/src/qml/FileBrowserDelegate.qml @@ -1,277 +1,340 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import org.kde.elisa 1.0 FocusScope { - id: fileDelegate + id: gridEntry property var fileName property var fileUrl property var imageUrl property var contentModel property bool isDirectory property bool isPlayList + property bool isSelected signal enqueue(var data) signal replaceAndPlay(var data) signal loadPlayList(var data) signal open(var data) signal selected() Loader { id: metadataLoader active: false onLoaded: item.show() sourceComponent: MediaTrackMetadataView { - fileName: fileDelegate.fileUrl + fileName: gridEntry.fileUrl onRejected: metadataLoader.active = false; } } - Keys.onReturnPressed: fileDelegate.enqueue(fileUrl) - Keys.onEnterPressed: fileDelegate.enqueue(fileUrl) + Keys.onReturnPressed: gridEntry.enqueue(fileUrl) + Keys.onEnterPressed: gridEntry.enqueue(fileUrl) + + Rectangle { + id: stateIndicator + + anchors.fill: parent + z: 1 + + color: "transparent" + opacity: 0.4 + + radius: 3 + } ColumnLayout { anchors.fill: parent + z: 2 spacing: 0 MouseArea { id: hoverArea hoverEnabled: true acceptedButtons: Qt.LeftButton - Layout.preferredHeight: fileDelegate.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + + Layout.preferredHeight: gridEntry.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) Layout.fillWidth: true - onClicked: fileDelegate.selected() + onClicked: gridEntry.selected() - onDoubleClicked: fileDelegate.open(fileUrl) + onDoubleClicked: gridEntry.open(fileUrl) TextMetrics { id: mainLabelSize font: mainLabel.font text: mainLabel.text } ColumnLayout { id: mainData spacing: 0 anchors.fill: parent Item { - Layout.preferredHeight: fileDelegate.width * 0.85 - Layout.preferredWidth: fileDelegate.width * 0.85 + Layout.preferredHeight: gridEntry.width * 0.85 + Layout.preferredWidth: gridEntry.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Loader { id: hoverLoader active: false anchors { bottom: parent.bottom bottomMargin: 2 left: parent.left leftMargin: 2 } z: 1 opacity: 0 sourceComponent: Row { spacing: 2 Button { id: detailsButton Layout.preferredHeight: elisaTheme.delegateHeight Layout.preferredWidth: elisaTheme.delegateHeight visible: !isDirectory && !isPlayList icon.name: "help-about" onClicked: { if (metadataLoader.active === false) { metadataLoader.active = true metadataLoader.item.trackDataHelper.trackData = contentModel.loadMetaDataFromUrl(fileUrl) } else { metadataLoader.item.close(); metadataLoader.active = false } } ToolTip { text: i18nc("Show track metadata", "View Details") } } Button { id: enqueueOpenButton Layout.preferredHeight: elisaTheme.delegateHeight Layout.preferredWidth: elisaTheme.delegateHeight visible: !isPlayList icon.name: isDirectory ? "go-next-view-page" : "media-track-add-amarok" onClicked: isDirectory ? open(fileUrl) : enqueue(fileUrl) ToolTip { text: isDirectory ? i18nc("Open view of the container", "Open") : i18nc("Enqueue current track", "Enqueue") } } Button { id: replaceAndPlayButton Layout.preferredHeight: elisaTheme.delegateHeight Layout.preferredWidth: elisaTheme.delegateHeight scale: LayoutMirroring.enabled ? -1 : 1 visible: !isDirectory icon.name: "media-playback-start" onClicked: replaceAndPlay(fileUrl) ToolTip { text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") } } } } Image { id: icon anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height fillMode: Image.PreserveAspectFit smooth: true source: imageUrl asynchronous: true } } LabelWithToolTip { id: mainLabel font.weight: Font.Bold color: myPalette.text // FIXME: Center-aligned text looks better overall, but // sometimes results in font kerning issues // See https://bugreports.qt.io/browse/QTBUG-49646 horizontalAlignment: Text.AlignHCenter Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 - Layout.maximumWidth: fileDelegate.width * 0.9 + Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.maximumHeight: (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) * 2 Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom text: fileName wrapMode: Label.Wrap elide: Text.ElideRight } Item { Layout.fillHeight: true } } } Item { Layout.fillHeight: true } } states: [ State { name: 'notSelected' - when: !fileDelegate.activeFocus && !hoverArea.containsMouse + when: !gridEntry.activeFocus && !hoverHandle.containsMouse && !gridEntry.isSelected + PropertyChanges { + target: stateIndicator + color: 'transparent' + } + PropertyChanges { + target: stateIndicator + opacity: 1.0 + } PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } }, + State { + name: 'hovered' + when: hoverHandle.containsMouse && !gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.highlight + } + PropertyChanges { + target: stateIndicator + opacity: 0.2 + } + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + }, + State { + name: 'selected' + when: gridEntry.isSelected && !gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.mid + } + PropertyChanges { + target: stateIndicator + opacity: 0.6 + } + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0. + } + }, State { name: 'hoveredOrSelected' - when: fileDelegate.activeFocus || hoverArea.containsMouse + when: gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.highlight + } + PropertyChanges { + target: stateIndicator + opacity: 0.6 + } PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } } ] transitions: [ Transition { - to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - } - }, - Transition { - to: 'notSelected' - SequentialAnimation { - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - PropertyAction { - properties: "active" + ParallelAnimation { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 300 + } + ColorAnimation { + properties: "color" + easing.type: Easing.InOutQuad + duration: 300 + } } } } ] } diff --git a/src/qml/FileBrowserView.qml b/src/qml/FileBrowserView.qml index edadf3f1..e987c19c 100644 --- a/src/qml/FileBrowserView.qml +++ b/src/qml/FileBrowserView.qml @@ -1,166 +1,179 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import org.kde.elisa 1.0 FocusScope { id: fileView property var viewType property bool isSubPage: false property alias expandedFilterView: navigationBar.expandedFilterView function goBack() { proxyModel.openParentFolder() } function loadFolderAndClear(data) { proxyModel.openFolder(data) navigationBar.filterText = "" } FileBrowserModel { id: realModel } FileBrowserProxyModel { id: proxyModel sourceModel: realModel onLoadPlayListFromUrl: elisa.mediaPlayList.loadPlaylist(playListUrl) onFilesToEnqueue: elisa.mediaPlayList.enqueue(newFiles, databaseIdType, enqueueMode, triggerPlay) } MouseArea { anchors.fill: parent hoverEnabled: false acceptedButtons: Qt.BackButton onClicked: proxyModel.openParentFolder() } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: i18nc("Title of the file browser view", "Files") image: elisaTheme.folderIcon secondaryTitle: proxyModel.url enableGoBack: proxyModel.canGoBack sortOrder: proxyModel.sortedAscending showRating: false height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true Binding { target: proxyModel property: 'filterText' value: navigationBar.filterText } onEnqueue: proxyModel.enqueueToPlayList() onReplaceAndPlay: proxyModel.replaceAndPlayOfPlayList() onGoBack: proxyModel.openParentFolder() onSort: proxyModel.sortModel(order) } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true clip: true GridView { id: contentDirectoryView anchors.topMargin: 20 anchors.fill: parent - focus: true + activeFocusOnTab: true + keyNavigationEnabled: true ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds + currentIndex: -1 + model: proxyModel ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } add: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 100 } } remove: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 100 } } cellWidth: elisaTheme.gridDelegateWidth cellHeight:elisaTheme.gridDelegateHeight delegate: FileBrowserDelegate { width: contentDirectoryView.cellWidth height: contentDirectoryView.cellHeight + focus: true + isSelected: contentDirectoryView.currentIndex === index + isDirectory: model.directory isPlayList: model.isPlaylist fileName: model.name fileUrl: model.containerData imageUrl: model.imageUrl contentModel: proxyModel onEnqueue: elisa.mediaPlayList.enqueue(0, data, ElisaUtils.FileName, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: elisa.mediaPlayList.enqueue(0, data, ElisaUtils.FileName, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index } + + onActiveFocusChanged: { + if (activeFocus && contentDirectoryView.currentIndex !== model.index) { + contentDirectoryView.currentIndex = model.index + } + } + onOpen: loadFolderAndClear(data) } } } } } diff --git a/src/qml/FrequentlyPlayedTracks.qml b/src/qml/FrequentlyPlayedTracks.qml index 07e875ed..bfc99ebb 100644 --- a/src/qml/FrequentlyPlayedTracks.qml +++ b/src/qml/FrequentlyPlayedTracks.qml @@ -1,119 +1,123 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.3 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: viewHeader property var viewType property alias mainTitle: listView.mainTitle property alias image: listView.image property var modelType focus: true DataModel { id: realModel } AllTracksProxyModel { id: proxyModel sortRole: DatabaseInterface.PlayFrequency sourceModel: realModel onEntriesToEnqueue: elisa.mediaPlayList.enqueue(newEntries, databaseIdType, enqueueMode, triggerPlay) } ListBrowserView { id: listView focus: true - activeFocusOnTab: true anchors.fill: parent contentModel: proxyModel delegate: MediaTrackDelegate { id: entry width: listView.delegateWidth height: elisaTheme.trackDelegateHeight focus: true databaseId: model.databaseId title: model.title artist: model.artist album: (model.album !== undefined && model.album !== '' ? model.album : '') - albumArtist: model.albumArtist + albumArtist: (model.albumArtist !== undefined && model.albumArtist !== '' ? model.albumArtist : '') duration: model.duration imageUrl: (model.imageUrl !== undefined && model.imageUrl !== '' ? model.imageUrl : '') trackNumber: model.trackNumber discNumber: model.discNumber rating: model.rating isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum + isSelected: listView.currentIndex === index + isAlternateColor: (index % 2) === 1 onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) - onClicked: contentDirectoryView.currentIndex = index + onClicked: { + listView.currentIndex = index + entry.forceActiveFocus() + } } Loader { anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height visible: realModel.isBusy active: realModel.isBusy sourceComponent: BusyIndicator { anchors.centerIn: parent } } } Connections { target: elisa onMusicManagerChanged: realModel.initializeFrequentlyPlayed(elisa.musicManager, elisa.musicManager.viewDatabase, modelType) } Component.onCompleted: { if (elisa.musicManager) { realModel.initializeFrequentlyPlayed(elisa.musicManager, elisa.musicManager.viewDatabase, modelType) } proxyModel.sortModel(Qt.DescendingOrder) } } diff --git a/src/qml/GridBrowserDelegate.qml b/src/qml/GridBrowserDelegate.qml index 6ecdaffb..2bf50f91 100644 --- a/src/qml/GridBrowserDelegate.qml +++ b/src/qml/GridBrowserDelegate.qml @@ -1,320 +1,385 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.5 as Kirigami FocusScope { id: gridEntry property var imageUrl property bool shadowForImage property alias mainText: mainLabel.text property alias secondaryText: secondaryLabel.text property var databaseId property bool delegateDisplaySecondaryText: true property bool isPartial + property bool isSelected signal enqueue(var databaseId, var name) signal replaceAndPlay(var databaseId, var name) signal open() signal selected() Keys.onReturnPressed: open() Keys.onEnterPressed: open() + Rectangle { + id: stateIndicator + + anchors.fill: parent + z: 1 + + color: "transparent" + opacity: 0.4 + + radius: 3 + } + ColumnLayout { anchors.fill: parent + z: 2 spacing: 0 MouseArea { id: hoverHandle hoverEnabled: true acceptedButtons: Qt.LeftButton Layout.preferredHeight: gridEntry.width * 0.85 + elisaTheme.layoutVerticalMargin * 0.5 + (mainLabelSize.boundingRect.height - mainLabelSize.boundingRect.y) + (secondaryLabelSize.boundingRect.height - secondaryLabelSize.boundingRect.y) Layout.fillWidth: true onClicked: { gridEntry.selected() } onDoubleClicked: open() TextMetrics { id: mainLabelSize font: mainLabel.font text: mainLabel.text } TextMetrics { id: secondaryLabelSize font: secondaryLabel.font text: secondaryLabel.text } ColumnLayout { id: mainData spacing: 0 anchors.fill: parent Item { Layout.preferredHeight: gridEntry.width * 0.85 Layout.preferredWidth: gridEntry.width * 0.85 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Loader { id: hoverLoader active: false anchors { bottom: parent.bottom bottomMargin: 2 left: parent.left leftMargin: 2 } z: 1 opacity: 0 sourceComponent: Row { spacing: 2 Button { id: replaceAndPlayButton objectName: 'replaceAndPlayButton' icon.name: 'media-playback-start' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Clear play list and add whole container to play list", "Play now, replacing current playlist") onClicked: replaceAndPlay(databaseId, mainText) Keys.onReturnPressed: replaceAndPlay(databaseId, mainText) Keys.onEnterPressed: replaceAndPlay(databaseId, mainText) visible: databaseId !== undefined width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } Button { id: enqueueButton objectName: 'enqueueButton' icon.name: 'media-track-add-amarok' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Add whole container to play list", "Add to playlist") onClicked: enqueue(databaseId, mainText) Keys.onReturnPressed: enqueue(databaseId, mainText) Keys.onEnterPressed: enqueue(databaseId, mainText) visible: databaseId !== undefined width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } Button { id: openButton objectName: 'openButton' icon.name: 'go-next-view-page' hoverEnabled: true ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.text: i18nc("Open view of the container", "Open") onClicked: open() width: elisaTheme.delegateToolButtonSize height: elisaTheme.delegateToolButtonSize } } } Loader { id: coverImageLoader active: !isPartial anchors.fill: parent sourceComponent: Image { id: coverImage anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height fillMode: Image.PreserveAspectFit smooth: true source: (gridEntry.imageUrl !== undefined ? gridEntry.imageUrl : "") asynchronous: true layer.enabled: shadowForImage layer.effect: DropShadow { source: coverImage radius: 10 spread: 0.1 samples: 21 color: myPalette.shadow } } } Loader { active: isPartial anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height sourceComponent: BusyIndicator { anchors.centerIn: parent running: true } } } LabelWithToolTip { id: mainLabel font.weight: Font.Bold color: myPalette.text // FIXME: Center-aligned text looks better overall, but // sometimes results in font kerning issues // See https://bugreports.qt.io/browse/QTBUG-49646 horizontalAlignment: Text.AlignHCenter Layout.topMargin: elisaTheme.layoutVerticalMargin * 0.5 Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + Layout.bottomMargin: delegateDisplaySecondaryText ? 0 : elisaTheme.layoutVerticalMargin elide: Text.ElideRight } LabelWithToolTip { id: secondaryLabel font.weight: Font.Light color: myPalette.text // FIXME: Center-aligned text looks better overall, but // sometimes results in font kerning issues // See https://bugreports.qt.io/browse/QTBUG-49646 horizontalAlignment: Text.AlignHCenter + Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.maximumWidth: gridEntry.width * 0.9 Layout.minimumWidth: Layout.maximumWidth Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom visible: delegateDisplaySecondaryText elide: Text.ElideRight } } } Item { Layout.fillHeight: true } } states: [ State { name: 'notSelected' - when: !gridEntry.activeFocus && !hoverHandle.containsMouse + when: !gridEntry.activeFocus && !hoverHandle.containsMouse && !gridEntry.isSelected + PropertyChanges { + target: stateIndicator + color: 'transparent' + } + PropertyChanges { + target: stateIndicator + opacity: 1.0 + } PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } }, + State { + name: 'hovered' + when: hoverHandle.containsMouse && !gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.highlight + } + PropertyChanges { + target: stateIndicator + opacity: 0.2 + } + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + }, + State { + name: 'selected' + when: gridEntry.isSelected && !gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.mid + } + PropertyChanges { + target: stateIndicator + opacity: 0.6 + } + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0. + } + }, State { name: 'hoveredOrSelected' - when: gridEntry.activeFocus || hoverHandle.containsMouse + when: gridEntry.activeFocus + PropertyChanges { + target: stateIndicator + color: myPalette.highlight + } + PropertyChanges { + target: stateIndicator + opacity: 0.6 + } PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } } ] transitions: [ Transition { - to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - } - }, - Transition { - to: 'notSelected' - SequentialAnimation { - NumberAnimation { - properties: "opacity" - easing.type: Easing.InOutQuad - duration: 100 - } - PropertyAction { - properties: "active" + ParallelAnimation { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 300 + } + ColorAnimation { + properties: "color" + easing.type: Easing.InOutQuad + duration: 300 + } } } } ] } diff --git a/src/qml/GridBrowserView.qml b/src/qml/GridBrowserView.qml index a05347e7..09865130 100644 --- a/src/qml/GridBrowserView.qml +++ b/src/qml/GridBrowserView.qml @@ -1,160 +1,169 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: gridView property bool isSubPage: false property string mainTitle property string secondaryTitle property url image property alias contentModel: contentDirectoryView.model property alias showRating: navigationBar.showRating property bool delegateDisplaySecondaryText: true property alias expandedFilterView: navigationBar.expandedFilterView property var stackView property url defaultIcon signal enqueue(int databaseId, string name) signal replaceAndPlay(int databaseId, string name) signal open(string innerMainTitle, string innerSecondaryTitle, url innerImage, int databaseId, var dataType) signal goBack() ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar mainTitle: gridView.mainTitle secondaryTitle: gridView.secondaryTitle image: gridView.image enableGoBack: isSubPage sortOrder: if (contentModel) {contentModel.sortedAscending} else true height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true Loader { active: contentModel !== undefined sourceComponent: Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } } Loader { active: contentModel sourceComponent: Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } } onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay:contentModel.replaceAndPlayOfPlayList() onGoBack: gridView.goBack() onSort: contentModel.sortModel(order) } FocusScope { Layout.fillHeight: true Layout.fillWidth: true clip: true GridView { id: contentDirectoryView anchors.topMargin: 20 - focus: true activeFocusOnTab: true keyNavigationEnabled: true anchors.fill: parent ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds + currentIndex: -1 + TextMetrics { id: secondaryLabelSize text: 'example' } ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } cellWidth: elisaTheme.gridDelegateWidth cellHeight: delegateDisplaySecondaryText ? elisaTheme.gridDelegateHeight : elisaTheme.gridDelegateHeight - (secondaryLabelSize.boundingRect.height - secondaryLabelSize.boundingRect.y) delegate: GridBrowserDelegate { width: contentDirectoryView.cellWidth height: contentDirectoryView.cellHeight focus: true + isSelected: contentDirectoryView.currentIndex === index + isPartial: false mainText: model.display secondaryText: if (gridView.delegateDisplaySecondaryText) {model.secondaryText} else {""} imageUrl: (model && model.imageUrl && model.imageUrl.toString() !== "" ? model.imageUrl : defaultIcon) shadowForImage: (model && model.imageUrl && model.imageUrl.toString() !== "" ? true : false) databaseId: model.databaseId delegateDisplaySecondaryText: gridView.delegateDisplaySecondaryText onEnqueue: gridView.enqueue(databaseId, name) onReplaceAndPlay: gridView.replaceAndPlay(databaseId, name) onOpen: gridView.open(model.display, model.secondaryText, (model && model.imageUrl && model.imageUrl.toString() !== "" ? model.imageUrl : defaultIcon), model.databaseId, model.dataType) onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index } + + onActiveFocusChanged: { + if (activeFocus && contentDirectoryView.currentIndex !== model.index) { + contentDirectoryView.currentIndex = model.index + } + } } } } } } diff --git a/src/qml/ListBrowserView.qml b/src/qml/ListBrowserView.qml index cb6671c1..4054220e 100644 --- a/src/qml/ListBrowserView.qml +++ b/src/qml/ListBrowserView.qml @@ -1,126 +1,132 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Window 2.2 import QtQml.Models 2.2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: listView property bool isSubPage: false property alias mainTitle: navigationBar.mainTitle property alias secondaryTitle: navigationBar.secondaryTitle property alias image: navigationBar.image property int databaseId property alias delegate: contentDirectoryView.delegate property alias contentModel: contentDirectoryView.model property alias expandedFilterView: navigationBar.expandedFilterView property alias showRating: navigationBar.showRating property alias allowArtistNavigation: navigationBar.allowArtistNavigation property var delegateWidth: scrollBar.visible ? contentDirectoryView.width - scrollBar.width : contentDirectoryView.width property alias currentIndex: contentDirectoryView.currentIndex property alias enableSorting: navigationBar.enableSorting property var stackView signal goBack() signal showArtist(var name) SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } ColumnLayout { anchors.fill: parent spacing: 0 NavigationActionBar { id: navigationBar enableGoBack: listView.isSubPage sortOrder: contentModel.sortedAscending height: elisaTheme.navigationBarHeight Layout.preferredHeight: height Layout.minimumHeight: height Layout.maximumHeight: height Layout.fillWidth: true Binding { target: contentModel property: 'filterText' value: navigationBar.filterText } Binding { target: contentModel property: 'filterRating' value: navigationBar.filterRating } onEnqueue: contentModel.enqueueToPlayList() onReplaceAndPlay: contentModel.replaceAndPlayOfPlayList() onGoBack: listView.goBack() onShowArtist: listView.showArtist(listView.contentModel.sourceModel.author) onSort: contentModel.sortModel(order) } Rectangle { color: myPalette.base Layout.fillHeight: true Layout.fillWidth: true + Layout.margins: 2 ListView { id: contentDirectoryView anchors.topMargin: 20 anchors.fill: parent - focus: true activeFocusOnTab: true keyNavigationEnabled: true + currentIndex: -1 + ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds clip: true ScrollHelper { id: scrollHelper flickable: contentDirectoryView anchors.fill: contentDirectoryView } + + onCountChanged: if (count === 0) { + currentIndex = -1; + } } } } } diff --git a/src/qml/MediaAlbumTrackDelegate.qml b/src/qml/MediaAlbumTrackDelegate.qml index b63e5fdb..6027ace7 100644 --- a/src/qml/MediaAlbumTrackDelegate.qml +++ b/src/qml/MediaAlbumTrackDelegate.qml @@ -1,84 +1,85 @@ /* * Copyright 2016-2017 Matthieu Gallien * Copyright 2017 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 FocusScope { id: albumTrack property alias mediaTrack: mediaTrack property alias databaseId: mediaTrack.databaseId property alias title: mediaTrack.title property alias artist: mediaTrack.artist property alias album: mediaTrack.album property alias albumArtist: mediaTrack.albumArtist property alias duration: mediaTrack.duration property alias imageUrl: mediaTrack.imageUrl property alias trackNumber: mediaTrack.trackNumber property alias discNumber: mediaTrack.discNumber property alias rating: mediaTrack.rating property alias isFirstTrackOfDisc: mediaTrack.isFirstTrackOfDisc property alias isSingleDiscAlbum: mediaTrack.isSingleDiscAlbum + property alias isSelected: mediaTrack.isSelected property alias isAlternateColor: mediaTrack.isAlternateColor ColumnLayout { anchors.fill: parent spacing: 0 Rectangle { Layout.preferredHeight: elisaTheme.delegateHeight Layout.minimumHeight: elisaTheme.delegateHeight Layout.maximumHeight: elisaTheme.delegateHeight Layout.fillWidth: true color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) visible: isFirstTrackOfDisc && !isSingleDiscAlbum LabelWithToolTip { id: discHeaderLabel text: 'CD ' + discNumber font.weight: Font.Bold font.italic: true color: myPalette.text anchors.fill: parent anchors.topMargin: elisaTheme.layoutVerticalMargin anchors.leftMargin: elisaTheme.layoutHorizontalMargin elide: Text.ElideRight } } MediaTrackDelegate { id: mediaTrack Layout.preferredHeight: elisaTheme.delegateHeight Layout.minimumHeight: elisaTheme.delegateHeight Layout.maximumHeight: elisaTheme.delegateHeight Layout.fillWidth: true focus: true detailedView: false } } } diff --git a/src/qml/MediaTrackDelegate.qml b/src/qml/MediaTrackDelegate.qml index 0352660a..f025e82c 100644 --- a/src/qml/MediaTrackDelegate.qml +++ b/src/qml/MediaTrackDelegate.qml @@ -1,433 +1,470 @@ /* * Copyright 2016-2017 Matthieu Gallien * Copyright 2017 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.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: mediaTrack property var databaseId property string title property string artist property string album property string albumArtist property string duration property url imageUrl property int trackNumber property int discNumber property int rating property bool isFirstTrackOfDisc property bool isSingleDiscAlbum + property bool isSelected property bool isAlternateColor property bool detailedView: true signal clicked() signal enqueue(var databaseId, var name) signal replaceAndPlay(var databaseId, var name) Controls1.Action { id: enqueueAction text: i18nc("Enqueue current track", "Enqueue") iconName: "media-track-add-amarok" onTriggered: enqueue(databaseId, title) } Controls1.Action { id: viewDetailsAction text: i18nc("Show track metadata", "View Details") iconName: "help-about" onTriggered: { if (metadataLoader.active === false) { metadataLoader.active = true } else { metadataLoader.item.close(); metadataLoader.active = false } } } Controls1.Action { id: replaceAndPlayAction text: i18nc("Clear play list and enqueue current track", "Play Now and Replace Play List") iconName: "media-playback-start" onTriggered: replaceAndPlay(databaseId, title) } Keys.onReturnPressed: enqueue(databaseId, title) Keys.onEnterPressed: enqueue(databaseId, title) Loader { id: metadataLoader active: false onLoaded: item.show() sourceComponent: MediaTrackMetadataView { databaseId: mediaTrack.databaseId onRejected: metadataLoader.active = false; } } Rectangle { id: rowRoot anchors.fill: parent + z: 1 color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) + } - MouseArea { - id: hoverArea + MouseArea { + id: hoverArea - anchors.fill: parent + anchors.fill: parent + z: 2 - hoverEnabled: true - focus: true - acceptedButtons: Qt.LeftButton + hoverEnabled: true + acceptedButtons: Qt.LeftButton - onClicked: { - hoverArea.forceActiveFocus() - mediaTrack.clicked() - } + onClicked: { + mediaTrack.clicked() + } - onDoubleClicked: enqueue(databaseId, title) + onDoubleClicked: enqueue(databaseId, title) - RowLayout { - anchors.fill: parent - spacing: 0 + RowLayout { + anchors.fill: parent + spacing: 0 - LabelWithToolTip { - id: mainLabel + LabelWithToolTip { + id: mainLabel - visible: !detailedView + visible: !detailedView - text: { - if (trackNumber !== 0) { - if (artist !== albumArtist) - return i18nc("%1: track number. %2: track title. %3: artist name", - "%1 - %2 - %3", - trackNumber.toLocaleString(Qt.locale(), 'f', 0), - title, artist); - else - return i18nc("%1: track number. %2: track title.", - "%1 - %2", - trackNumber.toLocaleString(Qt.locale(), 'f', 0), - title); - } else { - if (artist !== albumArtist) - return i18nc("%1: track title. %2: artist name", - "%1 - %2", - title, artist); - else - return i18nc("%1: track title", - "%1", - title); - } + text: { + if (trackNumber !== 0) { + if (artist !== albumArtist) + return i18nc("%1: track number. %2: track title. %3: artist name", + "%1 - %2 - %3", + trackNumber.toLocaleString(Qt.locale(), 'f', 0), + title, artist); + else + return i18nc("%1: track number. %2: track title.", + "%1 - %2", + trackNumber.toLocaleString(Qt.locale(), 'f', 0), + title); + } else { + if (artist !== albumArtist) + return i18nc("%1: track title. %2: artist name", + "%1 - %2", + title, artist); + else + return i18nc("%1: track title", + "%1", + title); } + } - elide: Text.ElideRight - horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft - color: myPalette.text + color: myPalette.text - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true - Layout.leftMargin: { - if (!LayoutMirroring.enabled) - return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) - else - return 0 - } - Layout.rightMargin: { - if (LayoutMirroring.enabled) - return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) - else - return 0 - } + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: { + if (!LayoutMirroring.enabled) + return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) + else + return 0 + } + Layout.rightMargin: { + if (LayoutMirroring.enabled) + return (!isSingleDiscAlbum ? elisaTheme.layoutHorizontalMargin * 4 : elisaTheme.layoutHorizontalMargin) + else + return 0 } + } - Item { - Layout.preferredHeight: mediaTrack.height * 0.9 - Layout.preferredWidth: mediaTrack.height * 0.9 + Item { + Layout.preferredHeight: mediaTrack.height * 0.9 + Layout.preferredWidth: mediaTrack.height * 0.9 - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - visible: detailedView + visible: detailedView - Image { - id: coverImageElement + Image { + id: coverImageElement - anchors.fill: parent + anchors.fill: parent - sourceSize.width: mediaTrack.height * 0.9 - sourceSize.height: mediaTrack.height * 0.9 - fillMode: Image.PreserveAspectFit - smooth: true + sourceSize.width: mediaTrack.height * 0.9 + sourceSize.height: mediaTrack.height * 0.9 + fillMode: Image.PreserveAspectFit + smooth: true - source: (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) + source: (imageUrl != '' ? imageUrl : Qt.resolvedUrl(elisaTheme.defaultAlbumImage)) - asynchronous: true + asynchronous: true - layer.enabled: imageUrl != '' + layer.enabled: imageUrl != '' - layer.effect: DropShadow { - source: coverImageElement + layer.effect: DropShadow { + source: coverImageElement - radius: 10 - spread: 0.1 - samples: 21 + radius: 10 + spread: 0.1 + samples: 21 - color: myPalette.shadow - } + color: myPalette.shadow } } + } - ColumnLayout { - visible: detailedView + ColumnLayout { + visible: detailedView - Layout.fillWidth: true - Layout.fillHeight: true - Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignLeft - spacing: 0 + spacing: 0 - LabelWithToolTip { - id: mainLabelDetailed + LabelWithToolTip { + id: mainLabelDetailed - text: { - if (trackNumber !== 0) { - return i18nc("%1: track number. %2: track title", "%1 - %2", - trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); - } else { - return title; - } + text: { + if (trackNumber !== 0) { + return i18nc("%1: track number. %2: track title", "%1 - %2", + trackNumber.toLocaleString(Qt.locale(), 'f', 0), title); + } else { + return title; } + } - horizontalAlignment: Text.AlignLeft + horizontalAlignment: Text.AlignLeft - font.weight: Font.Bold - color: myPalette.text + font.weight: Font.Bold + color: myPalette.text - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.fillWidth: true - Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.fillWidth: true + Layout.topMargin: elisaTheme.layoutVerticalMargin / 2 - elide: Text.ElideRight - } + elide: Text.ElideRight + } - Item { - Layout.fillHeight: true - } + Item { + Layout.fillHeight: true + } - LabelWithToolTip { - id: artistLabel + LabelWithToolTip { + id: artistLabel - text: { - var labelText = "" - if (artist) { - labelText += artist - } - if (album !== '') { - labelText += ' - ' + album - if (!isSingleDiscAlbum) { - labelText += ' - CD ' + discNumber - } + text: { + var labelText = "" + if (artist) { + labelText += artist + } + if (album !== '') { + labelText += ' - ' + album + if (!isSingleDiscAlbum) { + labelText += ' - CD ' + discNumber } - return labelText; } - horizontalAlignment: Text.AlignLeft + return labelText; + } + horizontalAlignment: Text.AlignLeft - font.weight: Font.Light - font.italic: true - color: myPalette.text + font.weight: Font.Light + font.italic: true + color: myPalette.text - Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.fillWidth: true - Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 2 + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.fillWidth: true + Layout.bottomMargin: elisaTheme.layoutVerticalMargin / 2 - elide: Text.ElideRight - } + elide: Text.ElideRight } + } - Loader { - id: hoverLoader - active: false + Loader { + id: hoverLoader + active: false - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.rightMargin: 10 + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: 10 - z: 1 - opacity: 0 + z: 1 + opacity: 0 - sourceComponent: Row { - anchors.centerIn: parent + sourceComponent: Row { + anchors.centerIn: parent - Controls1.ToolButton { - id: detailsButton + Controls1.ToolButton { + id: detailsButton - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: elisaTheme.delegateHeight + width: elisaTheme.delegateHeight - action: viewDetailsAction - } + action: viewDetailsAction + } - Controls1.ToolButton { - id: enqueueButton + Controls1.ToolButton { + id: enqueueButton - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: elisaTheme.delegateHeight + width: elisaTheme.delegateHeight - action: enqueueAction - } + action: enqueueAction + } - Controls1.ToolButton { - id: clearAndEnqueueButton + Controls1.ToolButton { + id: clearAndEnqueueButton - scale: LayoutMirroring.enabled ? -1 : 1 + scale: LayoutMirroring.enabled ? -1 : 1 - height: elisaTheme.delegateHeight - width: elisaTheme.delegateHeight + height: elisaTheme.delegateHeight + width: elisaTheme.delegateHeight - action: replaceAndPlayAction - } + action: replaceAndPlayAction } } + } - RatingStar { - id: ratingWidget + RatingStar { + id: ratingWidget - starSize: elisaTheme.ratingStarSize + starSize: elisaTheme.ratingStarSize - starRating: rating + starRating: rating - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.leftMargin: elisaTheme.layoutHorizontalMargin - Layout.rightMargin: elisaTheme.layoutHorizontalMargin - } + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.rightMargin: elisaTheme.layoutHorizontalMargin + } - LabelWithToolTip { - id: durationLabel + LabelWithToolTip { + id: durationLabel - text: duration + text: duration - font.weight: Font.Light - color: myPalette.text + font.weight: Font.Light + color: myPalette.text - horizontalAlignment: Text.AlignRight + horizontalAlignment: Text.AlignRight - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } states: [ State { name: 'notSelected' - when: !hoverArea.containsMouse && !mediaTrack.activeFocus + when: !mediaTrack.activeFocus && !hoverArea.containsMouse && !mediaTrack.isSelected PropertyChanges { target: hoverLoader active: false } PropertyChanges { target: hoverLoader opacity: 0.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } PropertyChanges { target: rowRoot color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } + PropertyChanges { + target: rowRoot + opacity: 1 + } }, State { - name: 'hoveredOrSelected' - when: hoverArea.containsMouse || mediaTrack.activeFocus + name: 'hovered' + when: !mediaTrack.activeFocus && hoverArea.containsMouse PropertyChanges { target: hoverLoader active: true } PropertyChanges { target: hoverLoader opacity: 1.0 } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } + PropertyChanges { + target: rowRoot + color: myPalette.highlight + } + PropertyChanges { + target: rowRoot + opacity: 0.2 + } + }, + State { + name: 'selected' + when: !mediaTrack.activeFocus && mediaTrack.isSelected + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: ratingWidget + hoverWidgetOpacity: 1.0 + } PropertyChanges { target: rowRoot color: myPalette.mid } + PropertyChanges { + target: rowRoot + opacity: 1. + } + }, + State { + name: 'focused' + when: mediaTrack.activeFocus + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + PropertyChanges { + target: ratingWidget + hoverWidgetOpacity: 1.0 + } + PropertyChanges { + target: rowRoot + color: myPalette.highlight + } + PropertyChanges { + target: rowRoot + opacity: 0.6 + } } ] transitions: [ Transition { - to: 'hoveredOrSelected' SequentialAnimation { PropertyAction { properties: "active" } ParallelAnimation { NumberAnimation { properties: "opacity, hoverWidgetOpacity" easing.type: Easing.InOutQuad - duration: 250 + duration: 200 } ColorAnimation { properties: "color" - duration: 250 + duration: 350 } } } - }, - Transition { - to: 'notSelected' - SequentialAnimation { - ParallelAnimation { - NumberAnimation { - properties: "opacity, hoverWidgetOpacity" - easing.type: Easing.InOutQuad - duration: 250 - } - ColorAnimation { - properties: "color" - duration: 250 - } - } - PropertyAction { - properties: "active" - } - } } ] } diff --git a/src/qml/NavigationActionBar.qml b/src/qml/NavigationActionBar.qml index 58986057..7155fb86 100644 --- a/src/qml/NavigationActionBar.qml +++ b/src/qml/NavigationActionBar.qml @@ -1,402 +1,413 @@ /* * Copyright 2016 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQml 2.2 import QtQuick 2.7 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 import QtQuick.Controls 1.4 as Controls1 -FocusScope { +ColumnLayout { id: navigationBar + spacing: 0 + + anchors.topMargin: elisaTheme.layoutVerticalMargin + anchors.bottomMargin: elisaTheme.layoutVerticalMargin + property string mainTitle property string secondaryTitle property url image property bool allowArtistNavigation: false property string labelText property bool showRating: true property alias filterText: filterTextInput.text property alias filterRating: ratingFilter.starRating property bool enableGoBack: true property bool expandedFilterView: persistentSettings.expandedFilterView property bool enableSorting: true property bool sortOrder property var findAction: elisa.action("edit_find") signal enqueue(); signal replaceAndPlay(); signal goBack(); signal showArtist(); signal sort(var order); Controls1.Action { id: goPreviousAction text: i18nc("navigate back in the views stack", "Back") iconName: (Qt.application.layoutDirection == Qt.RightToLeft) ? "go-next" : "go-previous" onTriggered: goBack() } Controls1.Action { id: showFilterAction shortcut: findAction.shortcut text: !navigationBar.expandedFilterView ? i18nc("Show filters in the navigation bar", "Show Search Options") : i18nc("Hide filters in the navigation bar", "Hide Search Options") iconName: !navigationBar.expandedFilterView ? "go-down-search" : "go-up-search" onTriggered: { persistentSettings.expandedFilterView = !persistentSettings.expandedFilterView expandedFilterView = persistentSettings.expandedFilterView if (expandedFilterView) { filterTextInput.forceActiveFocus() } } } Controls1.Action { id: sortAction text: i18nc("Toggle between ascending and descending order", "Toggle sort order") iconName: sortOrder ? "view-sort-ascending" : "view-sort-descending" onTriggered: sortOrder ? sort(Qt.DescendingOrder) : sort(Qt.AscendingOrder) } - ColumnLayout { - anchors.fill: parent + RowLayout { spacing: 0 + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: elisaTheme.navigationBarHeight + Layout.minimumHeight: elisaTheme.navigationBarHeight + Layout.maximumHeight: elisaTheme.navigationBarHeight - anchors.topMargin: elisaTheme.layoutVerticalMargin - anchors.bottomMargin: elisaTheme.layoutVerticalMargin + Controls1.ToolButton { + action: goPreviousAction + objectName: 'goPreviousButton' - RowLayout { - spacing: 0 - Layout.alignment: Qt.AlignTop - Layout.preferredHeight: elisaTheme.navigationBarHeight - Layout.minimumHeight: elisaTheme.navigationBarHeight - Layout.maximumHeight: elisaTheme.navigationBarHeight + Keys.onReturnPressed: action.trigger() - Controls1.ToolButton { - action: goPreviousAction - objectName: 'goPreviousButton' + activeFocusOnTab: true + focus: enableGoBack + + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + visible: enableGoBack + } - Keys.onReturnPressed: action.trigger() + Image { + id: mainIcon + source: image - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - visible: enableGoBack - } + asynchronous: true - Image { - id: mainIcon - source: image + sourceSize.height: elisaTheme.coverImageSize / 2 + sourceSize.width: elisaTheme.coverImageSize / 2 - asynchronous: true + fillMode: Image.PreserveAspectFit - sourceSize.height: elisaTheme.coverImageSize / 2 - sourceSize.width: elisaTheme.coverImageSize / 2 + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - fillMode: Image.PreserveAspectFit + Layout.preferredHeight: elisaTheme.coverImageSize / 2 + Layout.minimumHeight: elisaTheme.coverImageSize / 2 + Layout.maximumHeight: elisaTheme.coverImageSize / 2 + Layout.preferredWidth: elisaTheme.coverImageSize / 2 + Layout.minimumWidth: elisaTheme.coverImageSize / 2 + Layout.maximumWidth: elisaTheme.coverImageSize / 2 + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + } - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + ColumnLayout { + Layout.preferredHeight: elisaTheme.coverImageSize / 1.9 + Layout.minimumHeight: elisaTheme.coverImageSize / 1.9 + Layout.maximumHeight: elisaTheme.coverImageSize / 1.9 - Layout.preferredHeight: elisaTheme.coverImageSize / 2 - Layout.minimumHeight: elisaTheme.coverImageSize / 2 - Layout.maximumHeight: elisaTheme.coverImageSize / 2 - Layout.preferredWidth: elisaTheme.coverImageSize / 2 - Layout.minimumWidth: elisaTheme.coverImageSize / 2 - Layout.maximumWidth: elisaTheme.coverImageSize / 2 - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + spacing: 0 - ColumnLayout { - Layout.preferredHeight: elisaTheme.coverImageSize / 1.9 - Layout.minimumHeight: elisaTheme.coverImageSize / 1.9 - Layout.maximumHeight: elisaTheme.coverImageSize / 1.9 + Layout.fillWidth: true + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - spacing: 0 + LabelWithToolTip { + id: albumLabel + + text: mainTitle Layout.fillWidth: true - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.topMargin: secondaryTitle !== "" ? 0 : 9 - LabelWithToolTip { - id: albumLabel + elide: Text.ElideRight + fontSizeMode: Text.Fit - text: mainTitle + Layout.preferredHeight: elisaTheme.coverImageSize / 5 + Layout.minimumHeight: elisaTheme.coverImageSize / 5 + Layout.maximumHeight: elisaTheme.coverImageSize / 5 - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.topMargin: secondaryTitle !== "" ? 0 : 9 + color: myPalette.text + + font { + pointSize: elisaTheme.defaultFontPointSize * 2 + } + } - elide: Text.ElideRight - fontSizeMode: Text.Fit + LabelWithToolTip { + id: authorLabel - Layout.preferredHeight: elisaTheme.coverImageSize / 5 - Layout.minimumHeight: elisaTheme.coverImageSize / 5 - Layout.maximumHeight: elisaTheme.coverImageSize / 5 + text: secondaryTitle - color: myPalette.text + color: myPalette.text - font { - pointSize: elisaTheme.defaultFontPointSize * 2 - } + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + + font { + pointSize: elisaTheme.defaultFontPointSize } - LabelWithToolTip { - id: authorLabel + elide: Text.ElideRight - text: secondaryTitle + visible: secondaryTitle !== "" + } - color: myPalette.text + RowLayout { + Layout.fillWidth: true + spacing: 0 + Layout.bottomMargin: secondaryTitle !== "" ? 0 : 14 - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Controls1.Button { + objectName: 'enqueueButton' + text: i18nc("Add current list to playlist", "Enqueue") + iconName: "media-track-add-amarok" - font { - pointSize: elisaTheme.defaultFontPointSize - } + activeFocusOnTab: true + focus: true - elide: Text.ElideRight + onClicked: enqueue() + Keys.onReturnPressed: enqueue() - visible: secondaryTitle !== "" + Layout.leftMargin: 0 + Layout.rightMargin: 0 } - RowLayout { - Layout.fillWidth: true - spacing: 0 - Layout.bottomMargin: secondaryTitle !== "" ? 0 : 14 - - Controls1.Button { - objectName: 'enqueueButton' - text: i18nc("Add current list to playlist", "Enqueue") - iconName: "media-track-add-amarok" + Controls1.Button { + objectName: 'replaceAndPlayButton' + text: i18nc("Clear playlist and play", "Replace and Play") + tooltip: i18nc("Clear playlist and add current list to it", "Replace PlayList and Play Now") + iconName: "media-playback-start" - onClicked: enqueue() - Keys.onReturnPressed: enqueue() + activeFocusOnTab: true - Layout.leftMargin: 0 - Layout.rightMargin: 0 - } + onClicked: replaceAndPlay() + Keys.onReturnPressed: replaceAndPlay() - Controls1.Button { - objectName: 'replaceAndPlayButton' - text: i18nc("Clear playlist and play", "Replace and Play") - tooltip: i18nc("Clear playlist and add current list to it", "Replace PlayList and Play Now") - iconName: "media-playback-start" + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + } - onClicked: replaceAndPlay() - Keys.onReturnPressed: replaceAndPlay() + Controls1.Button { + objectName: 'showArtistButton' + id: showArtistButton - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + visible: allowArtistNavigation + text: i18nc("Button to navigate to the artist of the album", "Display Artist") + iconName: "view-media-artist" - Controls1.Button { - objectName: 'showArtistButton' - id: showArtistButton + activeFocusOnTab: true - visible: allowArtistNavigation - text: i18nc("Button to navigate to the artist of the album", "Display Artist") - iconName: "view-media-artist" + onClicked: showArtist() + Keys.onReturnPressed: showArtist() - onClicked: showArtist() - Keys.onReturnPressed: showArtist() + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + } - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + Item { + Layout.fillWidth: true + } - Item { - Layout.fillWidth: true - } + Controls1.ToolButton { + action: showFilterAction + objectName: 'showFilterButton' - Controls1.ToolButton { - action: showFilterAction - objectName: 'showFilterButton' + activeFocusOnTab: true - Keys.onReturnPressed: action.trigger() + Keys.onReturnPressed: action.trigger() - Layout.alignment: Qt.AlignRight - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - } + Layout.alignment: Qt.AlignRight + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } + } - RowLayout { - id: filterRow + RowLayout { + id: filterRow - spacing: 0 + spacing: 0 - visible: opacity > 0.0 + visible: opacity > 0.0 - opacity: 0 + opacity: 0 - Layout.preferredHeight: elisaTheme.navigationBarFilterHeight - Layout.minimumHeight: elisaTheme.navigationBarFilterHeight - Layout.maximumHeight: elisaTheme.navigationBarFilterHeight - Layout.fillWidth: true - Layout.topMargin: elisaTheme.layoutVerticalMargin * 2 - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.alignment: Qt.AlignTop + Layout.preferredHeight: elisaTheme.navigationBarFilterHeight + Layout.minimumHeight: elisaTheme.navigationBarFilterHeight + Layout.maximumHeight: elisaTheme.navigationBarFilterHeight + Layout.fillWidth: true + Layout.topMargin: elisaTheme.layoutVerticalMargin * 2 + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.alignment: Qt.AlignTop - LabelWithToolTip { - text: i18nc("before the TextField input of the filter", "Search: ") + LabelWithToolTip { + text: i18nc("before the TextField input of the filter", "Search: ") - font.bold: true + font.bold: true - Layout.bottomMargin: 0 + Layout.bottomMargin: 0 - color: myPalette.text - } + color: myPalette.text + } - TextField { - id: filterTextInput - objectName: 'filterTextInput' + TextField { + id: filterTextInput + objectName: 'filterTextInput' - horizontalAlignment: TextInput.AlignLeft + horizontalAlignment: TextInput.AlignLeft - placeholderText: i18nc("Placeholder text in the filter text box", "Album name, artist, etc.") + placeholderText: i18nc("Placeholder text in the filter text box", "Album name, artist, etc.") - Layout.bottomMargin: 0 - Layout.fillWidth: true - Layout.minimumWidth: (placeHolderTextWidth.boundingRect.width - placeHolderTextWidth.boundingRect.x) * 1.2 - implicitWidth: (placeHolderTextWidth.boundingRect.width - placeHolderTextWidth.boundingRect.x) * 1.2 + Layout.bottomMargin: 0 + Layout.fillWidth: true + Layout.minimumWidth: (placeHolderTextWidth.boundingRect.width - placeHolderTextWidth.boundingRect.x) * 1.2 + implicitWidth: (placeHolderTextWidth.boundingRect.width - placeHolderTextWidth.boundingRect.x) * 1.2 - TextMetrics { - id: placeHolderTextWidth - text: filterTextInput.placeholderText - } + TextMetrics { + id: placeHolderTextWidth + text: filterTextInput.placeholderText + } - Image { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: elisaTheme.filterClearButtonMargin - id: clearText - fillMode: Image.PreserveAspectFit - smooth: true - visible: parent.text - source: Qt.resolvedUrl(elisaTheme.clearIcon) - height: parent.height - width: parent.height - sourceSize.width: parent.height - sourceSize.height: parent.height - mirror: LayoutMirroring.enabled - - MouseArea { - id: clear - anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } - height: parent.parent.height - width: parent.parent.height - onClicked: { - parent.parent.text = "" - parent.parent.forceActiveFocus() - } + Image { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: elisaTheme.filterClearButtonMargin + id: clearText + fillMode: Image.PreserveAspectFit + smooth: true + visible: parent.text + source: Qt.resolvedUrl(elisaTheme.clearIcon) + height: parent.height + width: parent.height + sourceSize.width: parent.height + sourceSize.height: parent.height + mirror: LayoutMirroring.enabled + + MouseArea { + id: clear + anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } + height: parent.parent.height + width: parent.parent.height + onClicked: { + parent.parent.text = "" + parent.parent.forceActiveFocus() } } } + } - LabelWithToolTip { - text: i18nc("before the Rating widget input of the filter", "Rating: ") + LabelWithToolTip { + text: i18nc("before the Rating widget input of the filter", "Rating: ") - visible: showRating + visible: showRating - font.bold: true + font.bold: true - color: myPalette.text + color: myPalette.text - Layout.bottomMargin: 0 - Layout.leftMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 - Layout.rightMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 - } + Layout.bottomMargin: 0 + Layout.leftMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 + Layout.rightMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 + } - RatingStar { - id: ratingFilter - objectName: 'ratingFilter' + RatingStar { + id: ratingFilter + objectName: 'ratingFilter' - visible: showRating - hoverWidgetOpacity: 1 + visible: showRating + hoverWidgetOpacity: 1 - readOnly: false + readOnly: false - starSize: elisaTheme.ratingStarSize + starSize: elisaTheme.ratingStarSize - Layout.bottomMargin: 0 - } + Layout.bottomMargin: 0 + } - Item { - Layout.fillWidth: true - implicitWidth: elisaTheme.layoutHorizontalMargin * 4 - } + Item { + Layout.fillWidth: true + implicitWidth: elisaTheme.layoutHorizontalMargin * 4 + } - Controls1.ToolButton { - action: sortAction - objectName: 'sortAscendingButton' + Controls1.ToolButton { + action: sortAction + objectName: 'sortAscendingButton' - Keys.onReturnPressed: action.trigger() + activeFocusOnTab: true - Layout.alignment: Qt.AlignRight - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 - visible: enableSorting - } + Keys.onReturnPressed: action.trigger() + + Layout.alignment: Qt.AlignRight + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 + visible: enableSorting } } states: [ State { name: 'collapsed' when: !expandedFilterView PropertyChanges { target: navigationBar height: elisaTheme.navigationBarHeight + elisaTheme.layoutVerticalMargin * 2 } PropertyChanges { target: filterRow opacity: 0.0 } }, State { name: 'expanded' when: expandedFilterView PropertyChanges { target: navigationBar height: elisaTheme.navigationBarHeight + elisaTheme.navigationBarFilterHeight + elisaTheme.layoutVerticalMargin * 4 } PropertyChanges { target: filterRow opacity: 1.0 } } ] transitions: Transition { from: "expanded,collapsed" PropertyAnimation { properties: "height" easing.type: Easing.Linear duration: 250 } PropertyAnimation { properties: "opacity" easing.type: Easing.Linear duration: 250 } } } diff --git a/src/qml/PlayListBasicView.qml b/src/qml/PlayListBasicView.qml index 6a6a398a..578245af 100644 --- a/src/qml/PlayListBasicView.qml +++ b/src/qml/PlayListBasicView.qml @@ -1,155 +1,167 @@ /* * Copyright 2016-2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.2 import QtQml.Models 2.1 import org.kde.elisa 1.0 ListView { id: playListView property alias playListModel: playListModelDelegate.model signal startPlayback() signal pausePlayback() signal displayError(var errorText) focus: true - activeFocusOnTab: true keyNavigationEnabled: true + activeFocusOnTab: true + + currentIndex: -1 section.property: 'albumSection' section.criteria: ViewSection.FullString section.labelPositioning: ViewSection.InlineLabels section.delegate: PlayListAlbumHeader { headerData: JSON.parse(section) width: scrollBar.visible ? (!LayoutMirroring.enabled ? playListView.width - scrollBar.width : playListView.width) : playListView.width height: elisaTheme.playListHeaderHeight } ScrollBar.vertical: ScrollBar { id: scrollBar } boundsBehavior: Flickable.StopAtBounds clip: true ScrollHelper { id: scrollHelper flickable: playListView anchors.fill: playListView } 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 groups: [ DelegateModelGroup { name: "selected" } ] delegate: DraggableItem { id: item placeholderHeight: topItem.placeholderHeight focus: true PlayListEntry { id: entry focus: true width: scrollBar.visible ? (!LayoutMirroring.enabled ? playListView.width - scrollBar.width : playListView.width) : playListView.width scrollBarWidth: scrollBar.visible ? scrollBar.width : 0 index: model.index isAlternateColor: item.DelegateModel.itemsIndex % 2 isSelected: playListView.currentIndex === index containsMouse: item.containsMouse databaseId: (model.databaseId ? model.databaseId : 0) title: model.title artist: model.artist album: model.album albumArtist: (model.albumArtist ? model.albumArtist : '') duration: (model.duration ? model.duration : '') fileName: (model.trackResource ? model.trackResource : '') imageUrl: model.imageUrl trackNumber: (model.trackNumber ? model.trackNumber : -1) discNumber: (model.discNumber ? model.discNumber : -1) rating: (model.rating ? model.rating : 0) isSingleDiscAlbum: (model.isSingleDiscAlbum ? model.isSingleDiscAlbum : true) isValid: model.isValid isPlaying: model.isPlaying onStartPlayback: playListView.startPlayback() onPausePlayback: playListView.pausePlayback() onRemoveFromPlaylist: playListView.playListModel.removeRows(trackIndex, 1) onSwitchToTrack: playListView.playListModel.switchTo(trackIndex) + + onActiveFocusChanged: { + if (activeFocus && playListView.currentIndex !== index) { + playListView.currentIndex = index + } + } } draggedItemParent: playListView onClicked: { playListView.currentIndex = index entry.forceActiveFocus() } onDoubleClicked: { if (model.isValid) { playListView.playListModel.switchTo(model.index) playListView.startPlayback() } } onMoveItemRequested: { playListModel.move(from, to, 1); } } } + + onCountChanged: if (count === 0) { + currentIndex = -1; + } } diff --git a/src/qml/PlayListEntry.qml b/src/qml/PlayListEntry.qml index 2fd5efb6..bb805053 100644 --- a/src/qml/PlayListEntry.qml +++ b/src/qml/PlayListEntry.qml @@ -1,480 +1,553 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.3 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 int databaseId: 0 property string title property string artist property string album property string albumArtist property string duration property url fileName property url imageUrl property int trackNumber property int discNumber property int rating property bool hasValidDiscNumber: true property int scrollBarWidth property bool noBackground: false signal startPlayback() signal pausePlayback() signal removeFromPlaylist(var trackIndex) signal switchToTrack(var trackIndex) height: elisaTheme.playListDelegateHeight Controls1.Action { id: removeFromPlayList text: i18nc("Remove current track from play list", "Remove") iconName: "error" 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: { if (isPlaying === MediaPlayList.NotPlaying) { 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 } } } Loader { id: metadataLoader active: false onLoaded: item.show() sourceComponent: MediaTrackMetadataView { databaseId: playListEntry.databaseId fileName: playListEntry.fileName onRejected: metadataLoader.active = false; } } Rectangle { id: entryBackground anchors.fill: parent anchors.rightMargin: LayoutMirroring.enabled ? scrollBarWidth : 0 + z: 1 - color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) + color: myPalette.base height: elisaTheme.playListDelegateHeight + } - focus: true - - ColumnLayout { - spacing: 0 + ColumnLayout { + spacing: 0 - anchors.fill: parent + anchors.fill: parent + anchors.rightMargin: LayoutMirroring.enabled ? scrollBarWidth : 0 + z: 2 - Item { - Layout.fillWidth: true - Layout.fillHeight: true + Item { + Layout.fillWidth: true + Layout.fillHeight: true - RowLayout { - id: trackRow + RowLayout { + id: trackRow - anchors.fill: parent + anchors.fill: parent - spacing: elisaTheme.layoutHorizontalMargin / 4 + spacing: elisaTheme.layoutHorizontalMargin / 4 - Item { - id: playIconItem + Item { + id: playIconItem - implicitHeight: elisaTheme.smallDelegateToolButtonSize - implicitWidth: elisaTheme.smallDelegateToolButtonSize - Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize - Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 - Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 + implicitHeight: elisaTheme.smallDelegateToolButtonSize + implicitWidth: elisaTheme.smallDelegateToolButtonSize + Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize + Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 + Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin / 2 : 0 - Image { - id: playIcon + Image { + id: playIcon - anchors.fill: parent + anchors.fill: parent - opacity: 0 + opacity: 0 - source: (isPlaying === MediaPlayList.IsPlaying ? - Qt.resolvedUrl(elisaTheme.playingIndicatorIcon) : Qt.resolvedUrl(elisaTheme.pausedIndicatorIcon)) + source: (isPlaying === MediaPlayList.IsPlaying ? + Qt.resolvedUrl(elisaTheme.playingIndicatorIcon) : Qt.resolvedUrl(elisaTheme.pausedIndicatorIcon)) - width: parent.height * 1. - height: parent.height * 1. + width: parent.height * 1. + height: parent.height * 1. - sourceSize.width: parent.height * 1. - sourceSize.height: parent.height * 1. - fillMode: Image.PreserveAspectFit - mirror: LayoutMirroring.enabled - visible: opacity > 0.0 - } + sourceSize.width: parent.height * 1. + sourceSize.height: parent.height * 1. + fillMode: Image.PreserveAspectFit + mirror: LayoutMirroring.enabled + visible: opacity > 0.0 } + } - Item { - id: fakeDiscNumberItem + Item { + id: fakeDiscNumberItem - visible: isValid && (!hasValidDiscNumber || isSingleDiscAlbum) + visible: isValid && (!hasValidDiscNumber || isSingleDiscAlbum) - Layout.preferredWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) - Layout.minimumWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) - Layout.maximumWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) + Layout.preferredWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) + Layout.minimumWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) + Layout.maximumWidth: (fakeDiscNumberSize.boundingRect.width - fakeDiscNumberSize.boundingRect.x) + (elisaTheme.layoutHorizontalMargin / 4) - TextMetrics { - id: fakeDiscNumberSize + TextMetrics { + id: fakeDiscNumberSize - text: '/9' - } + text: '/9' } + } - Label { - id: trackNumberLabel + Label { + id: trackNumberLabel - horizontalAlignment: Text.AlignRight + horizontalAlignment: Text.AlignRight - text: trackNumber !== 0 && trackNumber !== -1 ? Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0) : '' + text: trackNumber !== 0 && trackNumber !== -1 ? Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0) : '' - font.weight: (isPlaying ? Font.Bold : Font.Light) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Light) + color: myPalette.text - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - visible: isValid + visible: isValid - Layout.preferredWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) - Layout.minimumWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) - Layout.maximumWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) + Layout.preferredWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) + Layout.minimumWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) + Layout.maximumWidth: ((trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) > (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x) ? (trackNumberSize.boundingRect.width - trackNumberSize.boundingRect.x) : (realTrackNumberSize.boundingRect.width - realTrackNumberSize.boundingRect.x)) - Layout.rightMargin: !LayoutMirroring.enabled ? (discNumber !== 0 && !isSingleDiscAlbum ? - 0 : elisaTheme.layoutHorizontalMargin / 2) : 0 - Layout.leftMargin: LayoutMirroring.enabled ? (discNumber !== 0 && !isSingleDiscAlbum ? - 0 : elisaTheme.layoutHorizontalMargin / 2) : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? (discNumber !== 0 && !isSingleDiscAlbum ? + 0 : elisaTheme.layoutHorizontalMargin / 2) : 0 + Layout.leftMargin: LayoutMirroring.enabled ? (discNumber !== 0 && !isSingleDiscAlbum ? + 0 : elisaTheme.layoutHorizontalMargin / 2) : 0 - TextMetrics { - id: trackNumberSize + TextMetrics { + id: trackNumberSize - text: (99).toLocaleString(Qt.locale(), 'f', 0) - } + text: (99).toLocaleString(Qt.locale(), 'f', 0) + } - TextMetrics { - id: realTrackNumberSize + TextMetrics { + id: realTrackNumberSize - text: Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0) - } + text: Number(trackNumber).toLocaleString(Qt.locale(), 'f', 0) } + } - Label { - horizontalAlignment: Text.AlignCenter + Label { + horizontalAlignment: Text.AlignCenter - text: '/' + text: '/' - visible: isValid && discNumber !== 0 && !isSingleDiscAlbum + visible: isValid && discNumber !== 0 && !isSingleDiscAlbum - font.weight: (isPlaying ? Font.Bold : Font.Light) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Light) + color: myPalette.text - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.preferredWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) - Layout.minimumWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) - Layout.maximumWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) + Layout.preferredWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) + Layout.minimumWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) + Layout.maximumWidth: (numberSeparatorSize.boundingRect.width - numberSeparatorSize.boundingRect.x) - TextMetrics { - id: numberSeparatorSize + TextMetrics { + id: numberSeparatorSize - text: '/' - } + text: '/' } + } - Label { - horizontalAlignment: Text.AlignRight + Label { + horizontalAlignment: Text.AlignRight - font.weight: (isPlaying ? Font.Bold : Font.Light) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Light) + color: myPalette.text - text: Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) + text: Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) - visible: isValid && discNumber !== 0 && !isSingleDiscAlbum + visible: isValid && discNumber !== 0 && !isSingleDiscAlbum - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.preferredWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? - (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : - (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) - Layout.minimumWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? - (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : - (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) - Layout.maximumWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? - (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : - (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) + Layout.preferredWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? + (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : + (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) + Layout.minimumWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? + (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : + (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) + Layout.maximumWidth: ((discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) > (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x) ? + (discNumberSize.boundingRect.width - discNumberSize.boundingRect.x) : + (realDiscNumberSize.boundingRect.width - realDiscNumberSize.boundingRect.x)) - Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin / 2) : 0 - Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin / 2) : 0 + Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin / 2) : 0 + Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin / 2) : 0 - TextMetrics { - id: discNumberSize + TextMetrics { + id: discNumberSize - text: '9' - } + text: '9' + } - TextMetrics { - id: realDiscNumberSize + TextMetrics { + id: realDiscNumberSize - text: Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) - } + text: Number(discNumber).toLocaleString(Qt.locale(), 'f', 0) } + } - LabelWithToolTip { - id: mainCompactLabel + LabelWithToolTip { + id: mainCompactLabel - text: title + text: title - font.weight: (isPlaying ? Font.Bold : Font.Normal) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Normal) + color: myPalette.text - Layout.maximumWidth: mainCompactLabel.implicitWidth + 1 - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.maximumWidth: mainCompactLabel.implicitWidth + 1 + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - visible: isValid + visible: isValid - elide: Text.ElideRight - horizontalAlignment: Text.AlignLeft - } + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + } - LabelWithToolTip { - id: mainInvalidCompactLabel + LabelWithToolTip { + id: mainInvalidCompactLabel - text: title + text: title - font.weight: Font.Normal - color: myPalette.text + font.weight: Font.Normal + color: myPalette.text - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - visible: !isValid + visible: !isValid - elide: Text.ElideRight - } + elide: Text.ElideRight + } - Item { - Layout.fillWidth: true - Layout.preferredWidth: 0 - } + Item { + Layout.fillWidth: true + Layout.preferredWidth: 0 + } - Controls1.ToolButton { - id: infoButton - objectName: 'infoButton' + Controls1.ToolButton { + id: infoButton + objectName: 'infoButton' - implicitHeight: elisaTheme.smallDelegateToolButtonSize - implicitWidth: elisaTheme.smallDelegateToolButtonSize + implicitHeight: elisaTheme.smallDelegateToolButtonSize + implicitWidth: elisaTheme.smallDelegateToolButtonSize - opacity: 0 + opacity: 0 - visible: opacity > 0.1 + visible: opacity > 0.1 - action: showInfo - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } + action: showInfo + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } - Controls1.ToolButton { - id: playPauseButton - objectName: 'playPauseButton' + Controls1.ToolButton { + id: playPauseButton + objectName: 'playPauseButton' - implicitHeight: elisaTheme.smallDelegateToolButtonSize - implicitWidth: elisaTheme.smallDelegateToolButtonSize + implicitHeight: elisaTheme.smallDelegateToolButtonSize + implicitWidth: elisaTheme.smallDelegateToolButtonSize - opacity: 0 + opacity: 0 - scale: LayoutMirroring.enabled ? -1 : 1 // We can mirror the symmetrical pause icon + scale: LayoutMirroring.enabled ? -1 : 1 // We can mirror the symmetrical pause icon - visible: opacity > 0.1 - action: !(isPlaying === MediaPlayList.IsPlaying) ? playNow : pauseNow - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } + visible: opacity > 0.1 + action: !(isPlaying === MediaPlayList.IsPlaying) ? playNow : pauseNow + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } - Item { - implicitHeight: elisaTheme.smallDelegateToolButtonSize - implicitWidth: elisaTheme.smallDelegateToolButtonSize - Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize - Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Item { + implicitHeight: elisaTheme.smallDelegateToolButtonSize + implicitWidth: elisaTheme.smallDelegateToolButtonSize + Layout.maximumWidth: elisaTheme.smallDelegateToolButtonSize + Layout.maximumHeight: elisaTheme.smallDelegateToolButtonSize + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Controls1.ToolButton { - id: removeButton - objectName: 'removeButton' + Controls1.ToolButton { + id: removeButton + objectName: 'removeButton' - anchors.fill: parent + anchors.fill: parent - opacity: 0 + opacity: 0 - visible: opacity > 0.1 - action: removeFromPlayList - } + visible: opacity > 0.1 + action: removeFromPlayList } + } - RatingStar { - id: ratingWidget + RatingStar { + id: ratingWidget - starRating: rating + starRating: rating - starSize: elisaTheme.ratingStarSize + starSize: elisaTheme.ratingStarSize - visible: rating > 0 - } + visible: rating > 0 + } - LabelWithToolTip { - id: durationLabel + LabelWithToolTip { + id: durationLabel - text: duration + text: duration - font.weight: (isPlaying ? Font.Bold : Font.Normal) - color: myPalette.text + font.weight: (isPlaying ? Font.Bold : Font.Normal) + color: myPalette.text - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.leftMargin: elisaTheme.layoutHorizontalMargin / 2 - Layout.rightMargin: elisaTheme.layoutHorizontalMargin / 2 + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.leftMargin: elisaTheme.layoutHorizontalMargin / 2 + Layout.rightMargin: elisaTheme.layoutHorizontalMargin / 2 - horizontalAlignment: Text.AlignRight - } + horizontalAlignment: Text.AlignRight } } } } states: [ State { name: 'notSelected' - when: !containsMouse && (!playListEntry.activeFocus || !isSelected) + when: !containsMouse && !isSelected && !playListEntry.activeFocus PropertyChanges { target: removeButton opacity: 0 } PropertyChanges { target: infoButton opacity: 0 } PropertyChanges { target: playPauseButton opacity: 0 } PropertyChanges { target: playIcon opacity: (isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused ? 1.0 : 0.0) } PropertyChanges { target: entryBackground color: (isAlternateColor ? myPalette.alternateBase : myPalette.base) } + PropertyChanges { + target: entryBackground + opacity: 1. + } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 0.0 } }, State { - name: 'hoveredOrSelected' - when: containsMouse || (playListEntry.activeFocus && isSelected) + name: 'hovered' + when: containsMouse && !playListEntry.activeFocus PropertyChanges { target: removeButton opacity: 1 } PropertyChanges { target: playPauseButton opacity: 1 } PropertyChanges { target: infoButton opacity: 1 } PropertyChanges { target: playIcon opacity: (isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused ? 1.0 : 0.0) } + PropertyChanges { + target: entryBackground + color: myPalette.highlight + } + PropertyChanges { + target: entryBackground + opacity: 0.2 + } + PropertyChanges { + target: ratingWidget + hoverWidgetOpacity: 1.0 + } + }, + State { + name: 'selected' + when: !playListEntry.activeFocus && isSelected + PropertyChanges { + target: removeButton + opacity: 0 + } + PropertyChanges { + target: playPauseButton + opacity: 0 + } + PropertyChanges { + target: infoButton + opacity: 0 + } + PropertyChanges { + target: playIcon + opacity: (isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused ? 1.0 : 0.0) + } PropertyChanges { target: entryBackground color: myPalette.mid } + PropertyChanges { + target: entryBackground + opacity: 1. + } + PropertyChanges { + target: ratingWidget + hoverWidgetOpacity: 1.0 + } + }, + State { + name: 'focused' + when: playListEntry.activeFocus + PropertyChanges { + target: removeButton + opacity: 1 + } + PropertyChanges { + target: playPauseButton + opacity: 1 + } + PropertyChanges { + target: infoButton + opacity: 1 + } + PropertyChanges { + target: playIcon + opacity: (isPlaying === MediaPlayList.IsPlaying || isPlaying === MediaPlayList.IsPaused ? 1.0 : 0.0) + } + PropertyChanges { + target: entryBackground + color: myPalette.highlight + } + PropertyChanges { + target: entryBackground + opacity: 0.6 + } PropertyChanges { target: ratingWidget hoverWidgetOpacity: 1.0 } } ] transitions: Transition { ParallelAnimation { NumberAnimation { properties: "opacity, hoverWidgetOpacity" easing.type: Easing.InOutQuad duration: 250 } ColorAnimation { properties: "color" duration: 250 } } } } diff --git a/src/qml/RecentlyPlayedTracks.qml b/src/qml/RecentlyPlayedTracks.qml index 260f8b42..c7140471 100644 --- a/src/qml/RecentlyPlayedTracks.qml +++ b/src/qml/RecentlyPlayedTracks.qml @@ -1,119 +1,123 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.3 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: viewHeader property var viewType property alias mainTitle: listView.mainTitle property alias image: listView.image property var modelType focus: true DataModel { id: realModel } AllTracksProxyModel { id: proxyModel sortRole: DatabaseInterface.LastPlayDate sourceModel: realModel onEntriesToEnqueue: elisa.mediaPlayList.enqueue(newEntries, databaseIdType, enqueueMode, triggerPlay) } ListBrowserView { id: listView focus: true - activeFocusOnTab: true anchors.fill: parent contentModel: proxyModel delegate: MediaTrackDelegate { id: entry width: listView.delegateWidth height: elisaTheme.trackDelegateHeight focus: true databaseId: model.databaseId title: model.title artist: model.artist album: (model.album !== undefined && model.album !== '' ? model.album : '') - albumArtist: model.albumArtist + albumArtist: (model.albumArtist !== undefined && model.albumArtist !== '' ? model.albumArtist : '') duration: model.duration imageUrl: (model.imageUrl !== undefined && model.imageUrl !== '' ? model.imageUrl : '') trackNumber: model.trackNumber discNumber: model.discNumber rating: model.rating isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum + isSelected: listView.currentIndex === index + isAlternateColor: (index % 2) === 1 onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) - onClicked: contentDirectoryView.currentIndex = index + onClicked: { + listView.currentIndex = index + entry.forceActiveFocus() + } } Loader { anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height visible: realModel.isBusy active: realModel.isBusy sourceComponent: BusyIndicator { anchors.centerIn: parent } } } Connections { target: elisa onMusicManagerChanged: realModel.initializeRecentlyPlayed(elisa.musicManager, elisa.musicManager.viewDatabase, modelType) } Component.onCompleted: { if (elisa.musicManager) { realModel.initializeRecentlyPlayed(elisa.musicManager, elisa.musicManager.viewDatabase, modelType) } proxyModel.sortModel(Qt.DescendingOrder) } } diff --git a/src/qml/TracksView.qml b/src/qml/TracksView.qml index 5bcef3b1..9ee0077d 100644 --- a/src/qml/TracksView.qml +++ b/src/qml/TracksView.qml @@ -1,116 +1,120 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.10 import QtQuick.Controls 2.3 import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 FocusScope { id: viewHeader property var viewType property alias mainTitle: listView.mainTitle property alias image: listView.image property var modelType focus: true DataModel { id: realModel } AllTracksProxyModel { id: proxyModel sortRole: Qt.DisplayRole sourceModel: realModel onEntriesToEnqueue: elisa.mediaPlayList.enqueue(newEntries, databaseIdType, enqueueMode, triggerPlay) } ListBrowserView { id: listView focus: true - activeFocusOnTab: true anchors.fill: parent contentModel: proxyModel delegate: MediaTrackDelegate { id: entry width: listView.delegateWidth height: elisaTheme.trackDelegateHeight focus: true databaseId: model.databaseId title: model.title artist: model.artist album: (model.album !== undefined && model.album !== '' ? model.album : '') - albumArtist: model.albumArtist + albumArtist: (model.albumArtist !== undefined && model.albumArtist !== '' ? model.albumArtist : '') duration: model.duration imageUrl: (model.imageUrl !== undefined && model.imageUrl !== '' ? model.imageUrl : '') trackNumber: model.trackNumber discNumber: model.discNumber rating: model.rating isFirstTrackOfDisc: false isSingleDiscAlbum: model.isSingleDiscAlbum + isSelected: listView.currentIndex === index + isAlternateColor: (index % 2) === 1 onEnqueue: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.AppendPlayList, ElisaUtils.DoNotTriggerPlay) onReplaceAndPlay: elisa.mediaPlayList.enqueue(databaseId, name, modelType, ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) - onClicked: contentDirectoryView.currentIndex = index + onClicked: { + listView.currentIndex = index + entry.forceActiveFocus() + } } Loader { anchors.centerIn: parent height: Kirigami.Units.gridUnit * 5 width: height visible: realModel.isBusy active: realModel.isBusy sourceComponent: BusyIndicator { anchors.centerIn: parent } } } Connections { target: elisa onMusicManagerChanged: realModel.initialize(elisa.musicManager, elisa.musicManager.viewDatabase, modelType) } Component.onCompleted: { if (elisa.musicManager) { realModel.initialize(elisa.musicManager, elisa.musicManager.viewDatabase, modelType) } } } diff --git a/src/qml/ViewSelector.qml b/src/qml/ViewSelector.qml index 05a861a0..d850922f 100644 --- a/src/qml/ViewSelector.qml +++ b/src/qml/ViewSelector.qml @@ -1,256 +1,154 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQml.Models 2.2 import QtGraphicalEffects 1.0 import org.kde.elisa 1.0 FocusScope { id: rootFocusScope readonly property alias currentIndex: viewModeView.currentIndex property double textOpacity property alias model: pageDelegateModel.model signal switchView(var viewType) function setCurrentIndex(index) { viewModeView.ignoreCurrentItemChanges = true viewModeView.currentIndex = index viewModeView.ignoreCurrentItemChanges = false } implicitWidth: elisaTheme.dp(225) Rectangle { anchors.fill: parent + z: 1 - color: myPalette.base - border { - color: (rootFocusScope.activeFocus ? myPalette.highlight : "transparent") - width: 1 - } - - ScrollView { - focus: true - - anchors.fill: parent - - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - - - ListView { - id: viewModeView - - focus: true - activeFocusOnTab: true - keyNavigationEnabled: true - - property bool ignoreCurrentItemChanges: false - - z: 2 - - anchors.topMargin: elisaTheme.layoutHorizontalMargin * 2 - - model: DelegateModel { - id: pageDelegateModel - - delegate: MouseArea { - id: itemMouseArea - - property var viewType: model.type - - height: elisaTheme.viewSelectorDelegateHeight * 1.4 - width: viewModeView.width - - hoverEnabled: true - acceptedButtons: Qt.LeftButton - - Loader { - anchors.fill: parent - active: itemMouseArea && itemMouseArea.containsMouse && !nameLabel.visible - - sourceComponent: ToolTip { - delay: Qt.styleHints.mousePressAndHoldInterval - text: model.display - visible: itemMouseArea && itemMouseArea.containsMouse && !nameLabel.visible - - contentItem: Label { - text: model.display - color: myPalette.highlightedText - } - - enter: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 0.0; to: 1.0; duration: 300; } } - exit: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 1.0; to: 0.0; duration: 300; } } - - background: Rectangle { - color: myPalette.shadow - radius: elisaTheme.tooltipRadius - - layer.enabled: true - layer.effect: DropShadow { - horizontalOffset: elisaTheme.shadowOffset - verticalOffset: elisaTheme.shadowOffset - radius: 8 - samples: 17 - color: myPalette.shadow - } - } - } - } + border.color: myPalette.base - Image { - id: viewIcon - - z: 2 - - anchors { - verticalCenter: parent.verticalCenter - leftMargin: elisaTheme.layoutHorizontalMargin - left: parent.left - } - - height: elisaTheme.viewSelectorDelegateHeight - width: elisaTheme.viewSelectorDelegateHeight + Behavior on border.color { + ColorAnimation { + duration: 300 + } + } + } - sourceSize { - width: elisaTheme.viewSelectorDelegateHeight - height: elisaTheme.viewSelectorDelegateHeight - } + ScrollView { + focus: true - source: model.image + anchors.fill: parent + z: 2 - layer.effect: ColorOverlay { + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - color: (index === viewModeView.currentIndex || itemMouseArea.containsMouse ? myPalette.highlight : "transparent") + ListView { + id: viewModeView - Behavior on color { - ColorAnimation { - duration: 300 - } - } - } - } + focus: true + activeFocusOnTab: true + keyNavigationEnabled: true - LabelWithToolTip { - id: nameLabel + property bool ignoreCurrentItemChanges: false - z: 2 + z: 2 - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: elisaTheme.layoutHorizontalMargin - anchors.left: viewIcon.right - anchors.right: parent.right - anchors.rightMargin: elisaTheme.layoutHorizontalMargin - verticalAlignment: "AlignVCenter" + anchors.topMargin: elisaTheme.layoutHorizontalMargin * 2 - font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.1) + model: DelegateModel { + id: pageDelegateModel - text: model.display - elide: Text.ElideRight + delegate: ViewSelectorDelegate { + id: entry - opacity: textOpacity - visible: opacity > 0 + height: Math.round(elisaTheme.viewSelectorDelegateHeight * 1.4) + width: viewModeView.width - Behavior on opacity { - NumberAnimation { - duration: 150 - } - } + focus: true - color: (viewModeView.currentIndex === index || itemMouseArea.containsMouse ? myPalette.highlight : myPalette.text) + isSelected: viewModeView.currentIndex === index - Behavior on color { - ColorAnimation { - duration: 300 - } - } - } - - onClicked: viewModeView.currentIndex = index + onClicked: { + viewModeView.currentIndex = index + entry.forceActiveFocus() } } + } - footer: MouseArea { - width: viewModeView.width - height: viewModeView.height - y + footer: MouseArea { + width: viewModeView.width + height: viewModeView.height - y - acceptedButtons: Qt.LeftButton + acceptedButtons: Qt.LeftButton - onClicked: - { - rootFocusScope.focus = true - } + onClicked: + { + rootFocusScope.focus = true } - - onCurrentItemChanged: if (!ignoreCurrentItemChanges) switchView(currentItem.viewType) } - } - Behavior on border.color { - ColorAnimation { - duration: 300 - } + onCurrentItemChanged: if (!ignoreCurrentItemChanges) switchView(currentItem.viewType) } } Connections { target: elisa onInitializationDone: { viewModeView.currentIndex = 3 } } Behavior on implicitWidth { NumberAnimation { duration: 150 } } Behavior on width { NumberAnimation { duration: 150 } } states: [ State { name: 'iconsAndText' when: mainWindow.width >= elisaTheme.viewSelectorSmallSizeThreshold PropertyChanges { target: rootFocusScope textOpacity: 1 implicitWidth: elisaTheme.dp(225) } }, State { name: 'iconsOnly' when: mainWindow.width < elisaTheme.viewSelectorSmallSizeThreshold PropertyChanges { target: rootFocusScope textOpacity: 0 implicitWidth: elisaTheme.viewSelectorDelegateHeight + 2 * elisaTheme.layoutHorizontalMargin } } ] } diff --git a/src/qml/ViewSelectorDelegate.qml b/src/qml/ViewSelectorDelegate.qml new file mode 100644 index 00000000..85e1ff55 --- /dev/null +++ b/src/qml/ViewSelectorDelegate.qml @@ -0,0 +1,252 @@ +/* + * Copyright 2016-2019 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 + +FocusScope { + id: rootItem + + property var viewType: model.type + property bool isSelected + + signal clicked() + + Rectangle { + id: backgroundHighlight + + anchors.fill: parent + z: 1 + + color: "transparent" + } + + MouseArea { + id: hoverArea + + anchors.fill: parent + z: 2 + + hoverEnabled: true + acceptedButtons: Qt.LeftButton + + onClicked: { + rootItem.clicked() + } + + Loader { + id: hoverLoader + + anchors.fill: parent + active: false + + sourceComponent: ToolTip { + delay: Qt.styleHints.mousePressAndHoldInterval + text: model.display + visible: hoverArea && hoverArea.containsMouse && !nameLabel.visible + + contentItem: Label { + text: model.display + color: myPalette.highlightedText + } + + enter: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 0.0; to: 1.0; duration: 300; } } + exit: Transition { NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; from: 1.0; to: 0.0; duration: 300; } } + + background: Rectangle { + color: myPalette.shadow + radius: elisaTheme.tooltipRadius + + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: elisaTheme.shadowOffset + verticalOffset: elisaTheme.shadowOffset + radius: 8 + samples: 17 + color: myPalette.shadow + } + } + } + } + + Image { + id: viewIcon + + z: 2 + + anchors { + verticalCenter: parent.verticalCenter + leftMargin: elisaTheme.layoutHorizontalMargin + left: parent.left + } + + height: elisaTheme.viewSelectorDelegateHeight + width: elisaTheme.viewSelectorDelegateHeight + + sourceSize { + width: elisaTheme.viewSelectorDelegateHeight + height: elisaTheme.viewSelectorDelegateHeight + } + + source: model.image + + layer.enabled: true + layer.effect: ColorOverlay { + color: nameLabel.color + } + } + + LabelWithToolTip { + id: nameLabel + + z: 2 + + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: elisaTheme.layoutHorizontalMargin + anchors.left: viewIcon.right + anchors.right: parent.right + anchors.rightMargin: elisaTheme.layoutHorizontalMargin + verticalAlignment: "AlignVCenter" + + font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.1) + + text: model.display + elide: Text.ElideRight + + opacity: textOpacity + visible: opacity > 0 + + color: (viewModeView.currentIndex === index || hoverArea.containsMouse ? myPalette.highlight : myPalette.text) + } + } + + states: [ + State { + name: 'notSelected' + when: !rootItem.activeFocus && !hoverArea.containsMouse && !rootItem.isSelected + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: viewIcon + opacity: 1 + } + PropertyChanges { + target: nameLabel + color: myPalette.buttonText + } + PropertyChanges { + target: backgroundHighlight + color: 'transparent' + } + }, + State { + name: 'hovered' + when: !rootItem.activeFocus && hoverArea.containsMouse + PropertyChanges { + target: hoverLoader + active: true + } + PropertyChanges { + target: hoverLoader + opacity: 1.0 + } + PropertyChanges { + target: viewIcon + opacity: 0.4 + } + PropertyChanges { + target: nameLabel + color: myPalette.buttonText + } + PropertyChanges { + target: backgroundHighlight + color: Qt.rgba(myPalette.highlight.r, myPalette.highlight.g, myPalette.highlight.b, 0.2) + } + }, + State { + name: 'selected' + when: !rootItem.activeFocus && rootItem.isSelected + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: viewIcon + opacity: 1 + } + PropertyChanges { + target: nameLabel + color: myPalette.buttonText + } + PropertyChanges { + target: backgroundHighlight + color: myPalette.mid + } + }, + State { + name: 'focused' + when: rootItem.activeFocus + PropertyChanges { + target: hoverLoader + active: false + } + PropertyChanges { + target: hoverLoader + opacity: 0.0 + } + PropertyChanges { + target: viewIcon + opacity: 1. + } + PropertyChanges { + target: nameLabel + color: myPalette.highlightedText + } + PropertyChanges { + target: backgroundHighlight + color: myPalette.highlight + } + } + ] + + transitions: [ + Transition { + ParallelAnimation { + NumberAnimation { + properties: "opacity" + easing.type: Easing.InOutQuad + duration: 200 + } + ColorAnimation { + properties: "color" + duration: 250 + } + } + } + ] +} diff --git a/src/resources.qrc b/src/resources.qrc index f10baa37..d508f4e8 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,62 +1,63 @@ qml/MediaPlayerControl.qml qml/RatingStar.qml qml/MediaPlayListView.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/HeaderBar.qml qml/ContextView.qml qml/ContentView.qml qml/DraggableItem.qml qml/PassiveNotification.qml qml/NavigationActionBar.qml qml/PlayListEntry.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/TopNotification.qml qml/TrackImportNotification.qml qml/TopNotificationItem.qml qml/MediaTrackDelegate.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml qml/ListBrowserView.qml qml/FileBrowserDelegate.qml qml/FileBrowserView.qml qtquickcontrols2.conf background.png qml/BaseTheme.qml qml/ScrollHelper.qml qml/FlatButtonWithToolTip.qml qml/DataGridView.qml qml/TracksView.qml qml/AlbumView.qml qml/RecentlyPlayedTracks.qml qml/FrequentlyPlayedTracks.qml qml/PlayListBasicView.qml qml/SimplePlayListView.qml qml/SimplePlayListEntry.qml qml/ViewSelector.qml qml/PlayListAlbumHeader.qml qml/BasicPlayListAlbumHeader.qml qml/MetaDataDelegate.qml qml/ContextViewLyrics.qml + qml/ViewSelectorDelegate.qml windows/WindowsTheme.qml windows/PlatformIntegration.qml windows/LabelWithToolTip.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml android/AlbumsView.qml android/ArtistsView.qml android/TracksView.qml android/GenresView.qml