diff --git a/CMakeLists.txt b/CMakeLists.txt index caf11c86..ba0bda1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,160 +1,160 @@ cmake_minimum_required(VERSION 3.5) project(elisa) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 14) set(REQUIRED_QT_VERSION "5.10.0") -find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Network Qml Quick Test Sql Multimedia Svg Gui Widgets QuickTest Concurrent) +find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Network Qml Quick Test Sql Multimedia Svg Gui Widgets QuickTest Concurrent QuickControls2) set(REQUIRED_KF5_VERSION "5.45.0") find_package(ECM ${REQUIRED_KF5_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(FeatureSummary) include(ECMAddAppIcon) include(ECMAddTests) if (NOT WIN32) find_package(Qt5DBus ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt5 DBus is needed to provide MPris2 interface to allow remote control by the desktop workspace." TYPE OPTIONAL) endif() find_package(Qt5QuickWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "Qt5 Quick Widgets is needed at runtime to provide the interface." TYPE RUNTIME) find_package(Qt5QuickControls2 ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5QuickControls2 PROPERTIES DESCRIPTION "Qt5 Quick Controls version 2 is needed at runtime to provide the interface." TYPE RUNTIME) if (ANDROID) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG QUIET OPTIONAL_COMPONENTS AndroidExtras) endif() find_package(KF5I18n ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5I18n PROPERTIES DESCRIPTION "KF5 text internationalization library." TYPE REQUIRED) find_package(KF5Declarative ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Declarative PROPERTIES DESCRIPTION "Integration of QML and KDE work spaces." TYPE RECOMMENDED) find_package(KF5CoreAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5CoreAddons PROPERTIES DESCRIPTION "Qt addon library with a collection of non-GUI utilities." TYPE REQUIRED) find_package(KF5Baloo ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "Baloo provides file searching and indexing." TYPE RECOMMENDED) find_package(KF5FileMetaData ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5FileMetaData PROPERTIES DESCRIPTION "Provides a simple library for extracting metadata." TYPE REQUIRED) find_package(KF5DocTools ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Create documentation from DocBook library." TYPE OPTIONAL) find_package(KF5XmlGui ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5XmlGui PROPERTIES DESCRIPTION "Framework for managing menu and toolbar actions." TYPE RECOMMENDED) find_package(KF5Config ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Config PROPERTIES DESCRIPTION "Persistent platform-independent application settings." TYPE REQUIRED) find_package(KF5ConfigWidgets ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5ConfigWidgets PROPERTIES DESCRIPTION "Widgets for configuration dialogs." TYPE RECOMMENDED) find_package(KF5Crash ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "Graceful handling of application crashes." TYPE OPTIONAL) if (NOT WIN32) find_package(KF5DBusAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5DBusAddons PROPERTIES DESCRIPTION "Convenience classes for D-Bus." TYPE OPTIONAL) endif() find_package(KF5KCMUtils ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5KCMUtils PROPERTIES DESCRIPTION "KF5 Utilities for KDE System Settings modules library." TYPE RECOMMENDED) find_package(KF5Package ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Package PROPERTIES DESCRIPTION "KF5 package management library needed to get the configuration dialogs." TYPE RECOMMENDED) find_package(KF5KIO ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "File management libraries used for file browsing." TYPE REQUIRED) find_package(UPNPQT CONFIG QUIET) set_package_properties(UPNPQT PROPERTIES DESCRIPTION "UPNP layer build with Qt 5. UPnP support is currently broken. You should probably avoid this dependency." URL "https://gitlab.com/homeautomationqt/upnp-player-qt" TYPE OPTIONAL) if (UPNPQT_FOUND) message(WARNING "UPnP support is experimental and may not work.") endif() include(FeatureSummary) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) if (CMAKE_SYSTEM_NAME STREQUAL Android) set(QT_QMAKE_EXECUTABLE "$ENV{Qt5_android}/bin/qmake") endif() configure_file(config-upnp-qt.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-upnp-qt.h ) add_subdirectory(src) add_subdirectory(icons) if (BUILD_TESTING) add_subdirectory(autotests) endif() add_subdirectory(doc) install( PROGRAMS org.kde.elisa.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES org.kde.elisa.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7d42f4a1..d73fb330 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,295 +1,295 @@ include_directories(${elisa_BINARY_DIR}) set(elisaLib_SOURCES mediaplaylist.cpp musicalbum.cpp musicaudiotrack.cpp musicartist.cpp progressindicator.cpp databaseinterface.cpp musiclistenersmanager.cpp managemediaplayercontrol.cpp manageheaderbar.cpp manageaudioplayer.cpp trackslistener.cpp elisaapplication.cpp audiowrapper.cpp notificationitem.cpp topnotificationmanager.cpp elisautils.cpp trackdatahelper.cpp abstractfile/abstractfilelistener.cpp abstractfile/abstractfilelisting.cpp file/filelistener.cpp file/localfilelisting.cpp models/albummodel.cpp models/allalbumsmodel.cpp models/allartistsmodel.cpp models/alltracksmodel.cpp models/abstractmediaproxymodel.cpp models/allalbumsproxymodel.cpp models/allartistsproxymodel.cpp models/alltracksproxymodel.cpp models/singleartistproxymodel.cpp models/singlealbumproxymodel.cpp models/filebrowsermodel.cpp models/filebrowserproxymodel.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} baloo/localbaloofilelisting.cpp baloo/baloolistener.cpp ) 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) 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 KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets LINK_PRIVATE Qt5::Core Qt5::Sql Qt5::Widgets KF5::I18n Qt5::Concurrent Qt5::Qml KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData) 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() 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 ) install(TARGETS elisaLib ${INSTALL_TARGETS_DEFAULT_ARGS}) set(elisaqmlplugin_SOURCES elisaqmlplugin.cpp elisautils.cpp ) add_library(elisaqmlplugin SHARED ${elisaqmlplugin_SOURCES}) target_link_libraries(elisaqmlplugin LINK_PRIVATE Qt5::Quick Qt5::Widgets KF5::FileMetaData KF5::ConfigCore KF5::ConfigGui elisaLib ) set_target_properties(elisaqmlplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa) 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 AND KF5Declarative_FOUND) set(elisa_SOURCES main.cpp windows/WindowsTheme.qml windows/PlatformIntegration.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/RatingStar.qml qml/PlayListEntry.qml qml/MediaBrowser.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/ContentView.qml qml/ViewSelector.qml qml/MediaPlayListView.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 ) 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::Widgets Qt5::QuickControls2 KF5::Declarative KF5::I18n KF5::Crash KF5::ConfigCore KF5::ConfigGui ) endif() install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS}) if (KF5ConfigWidgets_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 KF5::FileMetaData elisaLib ) set(QML_IMPORT_PATH ${CMAKE_BINARY_DIR}/bin CACHE INTERNAL "qml import path" FORCE) diff --git a/src/main.cpp b/src/main.cpp index 52a6e6f8..23e4b015 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,154 +1,158 @@ /* * Copyright 2015-2018 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config-upnp-qt.h" #include "elisaapplication.h" #include "elisa_settings.h" //#define QT_QML_DEBUG #if defined KF5Declarative_FOUND && KF5Declarative_FOUND #include #endif #include #include #include #if defined KF5Crash_FOUND && KF5Crash_FOUND #include #endif #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND #include #endif #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND #include #include #endif #if defined Q_OS_ANDROID int __attribute__((visibility("default"))) main(int argc, char *argv[]) #else int main(int argc, char *argv[]) #endif { QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); qputenv("QT_GSTREAMER_USE_PLAYBIN_VOLUME", "true"); QApplication app(argc, argv); #if defined KF5Crash_FOUND && KF5Crash_FOUND KCrash::initialize(); #endif QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("elisa"))); KLocalizedString::setApplicationDomain("elisa"); KAboutData aboutData( QStringLiteral("elisa"), i18n("Elisa"), QStringLiteral("0.1.80"), i18n("A Simple Music Player written with KDE Frameworks"), KAboutLicense::LGPL_V3, i18n("(c) 2015-2018, Elisa contributors")); aboutData.addAuthor(QStringLiteral("Matthieu Gallien"),i18n("Creator"), QStringLiteral("mgallien@mgallien.fr")); aboutData.addAuthor(QStringLiteral("Alexander Stippich"), i18n("Author"), QStringLiteral("a.stippich@gmx.net")); aboutData.addCredit(QStringLiteral("Andrew Lake"), i18n("Concept and design work"), QStringLiteral("jamboarder@gmail.com")); aboutData.addCredit(QStringLiteral("Luigi Toscano"), i18n("Localization support"), QStringLiteral("luigi.toscano@tiscali.it")); aboutData.addCredit(QStringLiteral("Safa Alfulaij"), i18n("Right to left support in interface"), QStringLiteral("safa1996alfulaij@gmail.com")); aboutData.addCredit(QStringLiteral("Diego Gangl"), i18n("Various improvements to the interface"), QStringLiteral("diego@sinestesia.co")); KAboutData::setApplicationData(aboutData); KLocalizedString::setApplicationDomain("elisa"); QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); #if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND qDebug() << QCoreApplication::arguments(); QAndroidJniObject::callStaticMethod("com/kde/elisa/ElisaService", "startMyService", "(Landroid/content/Context;)V", QtAndroid::androidContext().object()); #endif + QQuickStyle::setStyle(QStringLiteral("org.kde.desktop")); + QQuickStyle::setFallbackStyle(QStringLiteral("Fusion")); + QQmlApplicationEngine engine; engine.addImportPath(QStringLiteral("qrc:/imports")); QQmlFileSelector selector(&engine); #if defined KF5Declarative_FOUND && KF5Declarative_FOUND KDeclarative::KDeclarative decl; decl.setDeclarativeEngine(&engine); decl.setupBindings(); #endif engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.rootContext()->setContextProperty(QStringLiteral("logicalDpi"), QGuiApplication::primaryScreen()->logicalDotsPerInch()); #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND KDBusService elisaService(KDBusService::Unique); #endif std::unique_ptr myApp = std::make_unique(); #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND QObject::connect(&elisaService, &KDBusService::activateActionRequested, myApp.get(), &ElisaApplication::activateActionRequested); QObject::connect(&elisaService, &KDBusService::activateRequested, myApp.get(), &ElisaApplication::activateRequested); QObject::connect(&elisaService, &KDBusService::openRequested, myApp.get(), &ElisaApplication::openRequested); #endif myApp->setArguments(parser.positionalArguments()); engine.rootContext()->setContextProperty(QStringLiteral("elisa"), myApp.release()); engine.load(QUrl(QStringLiteral("qrc:/qml/ElisaMainWindow.qml"))); return app.exec(); } diff --git a/src/qml/MediaPlayerControl.qml b/src/qml/MediaPlayerControl.qml index 92e65c52..f6d3b7ca 100644 --- a/src/qml/MediaPlayerControl.qml +++ b/src/qml/MediaPlayerControl.qml @@ -1,567 +1,572 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.2 import org.kde.elisa 1.0 import QtQuick.Controls 1.4 as Controls1 FocusScope { property double volume property int position property int duration property bool muted property bool isPlaying property bool seekable property bool playEnabled property bool skipForwardEnabled property bool skipBackwardEnabled property bool shuffle property bool repeat signal play() signal pause() signal playPrevious() signal playNext() signal seek(int position) id: musicWidget SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Rectangle { anchors.fill: parent color: myPalette.midlight opacity: elisaTheme.mediaPlayerControlOpacity } RowLayout { anchors.fill: parent spacing: 5 RoundButton { focus: skipBackwardEnabled Layout.preferredWidth: elisaTheme.smallControlButtonSize Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonSize Layout.maximumHeight: elisaTheme.smallControlButtonSize Layout.minimumWidth: elisaTheme.smallControlButtonSize Layout.minimumHeight: elisaTheme.smallControlButtonSize Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.mediaPlayerHorizontalMargin : 0 Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.mediaPlayerHorizontalMargin : 0 enabled: skipBackwardEnabled hoverEnabled: true onClicked: { musicWidget.playPrevious() } contentItem: Image { anchors.fill: parent source: Qt.resolvedUrl(LayoutMirroring.enabled ? elisaTheme.skipForwardIcon : elisaTheme.skipBackwardIcon) width: elisaTheme.smallControlButtonSize height: elisaTheme.smallControlButtonSize sourceSize.width: elisaTheme.smallControlButtonSize sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit opacity: skipBackwardEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 radius: elisaTheme.smallControlButtonSize Behavior on border.color { ColorAnimation { duration: 300 } } } } RoundButton { focus: playEnabled Layout.preferredWidth: elisaTheme.smallControlButtonSize Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonSize Layout.maximumHeight: elisaTheme.smallControlButtonSize Layout.minimumWidth: elisaTheme.smallControlButtonSize Layout.minimumHeight: elisaTheme.smallControlButtonSize enabled: playEnabled hoverEnabled: true onClicked: { if (musicWidget.isPlaying) { musicWidget.pause() } else { musicWidget.play() } } contentItem: Image { anchors.fill: parent source: { if (musicWidget.isPlaying) Qt.resolvedUrl(elisaTheme.pauseIcon) else Qt.resolvedUrl(elisaTheme.playIcon) } width: elisaTheme.smallControlButtonSize height: elisaTheme.smallControlButtonSize sourceSize.width: elisaTheme.smallControlButtonSize sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit mirror: LayoutMirroring.enabled opacity: playEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 radius: elisaTheme.smallControlButtonSize Behavior on border.color { ColorAnimation { duration: 300 } } } } RoundButton { focus: skipForwardEnabled Layout.preferredWidth: elisaTheme.smallControlButtonSize Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonSize Layout.maximumHeight: elisaTheme.smallControlButtonSize Layout.minimumWidth: elisaTheme.smallControlButtonSize Layout.minimumHeight: elisaTheme.smallControlButtonSize enabled: skipForwardEnabled hoverEnabled: true onClicked: { musicWidget.playNext() } contentItem: Image { anchors.fill: parent source: Qt.resolvedUrl(LayoutMirroring.enabled ? elisaTheme.skipBackwardIcon : elisaTheme.skipForwardIcon) width: elisaTheme.smallControlButtonSize height: elisaTheme.smallControlButtonSize sourceSize.width: elisaTheme.smallControlButtonSize sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit opacity: skipForwardEnabled ? 1.0 : 0.6 } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 radius: elisaTheme.smallControlButtonSize Behavior on border.color { ColorAnimation { duration: 300 } } } } TextMetrics { id: durationTextMetrics text: i18nc("This is used to preserve a fixed width for the duration text.", "00:00:00") } LabelWithToolTip { id: positionLabel text: timeIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : elisaTheme.layoutHorizontalMargin * 2 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : elisaTheme.layoutHorizontalMargin * 2 Layout.preferredWidth: durationTextMetrics.width+5 // be in the safe side verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight ProgressIndicator { id: timeIndicator position: musicWidget.position } } Slider { property bool seekStarted: false property int seekValue id: musicProgress from: 0 to: musicWidget.duration Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 enabled: musicWidget.seekable && musicWidget.playEnabled live: true onValueChanged: { if (seekStarted) { seekValue = value } } onPressedChanged: { if (pressed) { seekStarted = true; seekValue = value } else { musicWidget.seek(seekValue) seekStarted = false; } } background: Rectangle { x: musicProgress.leftPadding y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: 200 implicitHeight: 6 width: musicProgress.availableWidth height: implicitHeight radius: 3 color: myPalette.dark Rectangle { x: (LayoutMirroring.enabled ? musicProgress.visualPosition * parent.width : 0) width: LayoutMirroring.enabled ? parent.width - musicProgress.visualPosition * parent.width: musicProgress.handle.x + radius height: parent.height color: myPalette.text radius: 3 } } handle: Rectangle { x: musicProgress.leftPadding + musicProgress.visualPosition * (musicProgress.availableWidth - width) y: musicProgress.topPadding + musicProgress.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 - color: myPalette.button - border.color: musicProgress.pressed ? myPalette.highlight : myPalette.dark + color: myPalette.base + border.width: 1 + border.color: musicProgress.pressed ? myPalette.text : myPalette.dark + } } LabelWithToolTip { id: durationLabel text: durationIndicator.progressDuration color: myPalette.text Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true Layout.rightMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 Layout.leftMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 Layout.preferredWidth: durationTextMetrics.width verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft ProgressIndicator { id: durationIndicator position: musicWidget.duration } } Image { id: volumeIcon source: if (musicWidget.muted) Qt.resolvedUrl(elisaTheme.playerVolumeMutedIcon) else Qt.resolvedUrl(elisaTheme.playerVolumeIcon) Layout.preferredWidth: elisaTheme.smallControlButtonSize Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonSize Layout.maximumHeight: elisaTheme.smallControlButtonSize Layout.minimumWidth: elisaTheme.smallControlButtonSize Layout.minimumHeight: elisaTheme.smallControlButtonSize Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 sourceSize.width: elisaTheme.smallControlButtonSize sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit visible: false } RoundButton { focus: true Layout.preferredWidth: elisaTheme.smallControlButtonSize Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonSize Layout.maximumHeight: elisaTheme.smallControlButtonSize Layout.minimumWidth: elisaTheme.smallControlButtonSize Layout.minimumHeight: elisaTheme.smallControlButtonSize hoverEnabled: true onClicked: musicWidget.muted = !musicWidget.muted contentItem: Image { anchors.fill: parent source: if (musicWidget.muted) Qt.resolvedUrl(elisaTheme.playerVolumeMutedIcon) else Qt.resolvedUrl(elisaTheme.playerVolumeIcon) width: elisaTheme.smallControlButtonSize height: elisaTheme.smallControlButtonSize sourceSize.width: elisaTheme.smallControlButtonSize sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit } background: Rectangle { color: "transparent" border.color: (parent.hovered || parent.activeFocus) ? myPalette.highlight : "transparent" border.width: 1 radius: elisaTheme.smallControlButtonSize Behavior on border.color { ColorAnimation { duration: 300 } } } } Slider { id: volumeSlider from: 0 to: 100 value: musicWidget.volume onValueChanged: musicWidget.volume = value enabled: !muted Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: elisaTheme.volumeSliderWidth Layout.maximumWidth: elisaTheme.volumeSliderWidth Layout.minimumWidth: elisaTheme.volumeSliderWidth Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 3 : 0 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 3 : 0 width: elisaTheme.volumeSliderWidth background: Rectangle { x: volumeSlider.leftPadding y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: 200 implicitHeight: 6 width: volumeSlider.availableWidth height: implicitHeight radius: 3 color: myPalette.dark + opacity: muted ? 0.5 : 1 Rectangle { x: (LayoutMirroring.enabled ? volumeSlider.visualPosition * parent.width : 0) width: (LayoutMirroring.enabled ? parent.width - volumeSlider.visualPosition * parent.width : volumeSlider.visualPosition * parent.width) height: parent.height color: myPalette.text radius: 3 + opacity: muted ? 0.5 : 1 } } handle: Rectangle { x: volumeSlider.leftPadding + volumeSlider.visualPosition * (volumeSlider.availableWidth - width) y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2 implicitWidth: 18 implicitHeight: 18 radius: 9 - color: myPalette.button - border.color: volumeSlider.pressed ? myPalette.highlight : myPalette.dark + color: myPalette.base + border.width: 1 + border.color: volumeSlider.pressed ? myPalette.text : (muted ? myPalette.mid : myPalette.dark) } } RoundButton { focus: true id: shuffleButton Layout.preferredWidth: elisaTheme.smallControlButtonSize Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignCenter Layout.maximumWidth: elisaTheme.smallControlButtonSize Layout.maximumHeight: elisaTheme.smallControlButtonSize Layout.minimumWidth: elisaTheme.smallControlButtonSize Layout.minimumHeight: elisaTheme.smallControlButtonSize hoverEnabled: true onClicked: musicWidget.shuffle = !musicWidget.shuffle contentItem: Image { anchors.fill: parent source: musicWidget.shuffle ? Qt.resolvedUrl(elisaTheme.shuffleIcon) : Qt.resolvedUrl(elisaTheme.noShuffleIcon) width: elisaTheme.smallControlButtonSize height: elisaTheme.smallControlButtonSize sourceSize.width: elisaTheme.smallControlButtonSize sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit } background: Rectangle { color: "transparent" } } RoundButton { focus: true id: repeatButton Layout.preferredWidth: elisaTheme.smallControlButtonSize Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignCenter Layout.maximumWidth: elisaTheme.smallControlButtonSize Layout.maximumHeight: elisaTheme.smallControlButtonSize Layout.minimumWidth: elisaTheme.smallControlButtonSize Layout.minimumHeight: elisaTheme.smallControlButtonSize hoverEnabled: true onClicked: musicWidget.repeat = !musicWidget.repeat contentItem: Image { anchors.fill: parent source: musicWidget.repeat ? Qt.resolvedUrl(elisaTheme.repeatIcon) : Qt.resolvedUrl(elisaTheme.noRepeatIcon) width: elisaTheme.smallControlButtonSize height: elisaTheme.smallControlButtonSize sourceSize.width: elisaTheme.smallControlButtonSize sourceSize.height: elisaTheme.smallControlButtonSize fillMode: Image.PreserveAspectFit } background: Rectangle { color: "transparent" } } Controls1.ToolButton { id: menuButton action: applicationMenuAction Layout.preferredWidth: elisaTheme.smallControlButtonSize Layout.preferredHeight: elisaTheme.smallControlButtonSize Layout.alignment: Qt.AlignVCenter Layout.maximumWidth: elisaTheme.smallControlButtonSize Layout.maximumHeight: elisaTheme.smallControlButtonSize Layout.minimumWidth: elisaTheme.smallControlButtonSize Layout.minimumHeight: elisaTheme.smallControlButtonSize Layout.rightMargin: !LayoutMirroring.enabled ? elisaTheme.mediaPlayerHorizontalMargin : elisaTheme.mediaPlayerHorizontalMargin * 1.5 Layout.leftMargin: LayoutMirroring.enabled ? elisaTheme.mediaPlayerHorizontalMargin : elisaTheme.mediaPlayerHorizontalMargin * 1.5 } } onPositionChanged: { if (!musicProgress.seekStarted) { musicProgress.value = position } } onVolumeChanged: { console.log('volume of player controls changed: ' + volume) } } diff --git a/src/qml/NavigationActionBar.qml b/src/qml/NavigationActionBar.qml index 1523bc79..e5bb4c02 100644 --- a/src/qml/NavigationActionBar.qml +++ b/src/qml/NavigationActionBar.qml @@ -1,387 +1,391 @@ /* * Copyright 2016 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import 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 { id: navigationBar 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: false property bool enableSorting: true property bool sortOrder signal enqueue(); signal replaceAndPlay(); signal goBack(); signal showArtist(); signal filterViewChanged(bool expandedFilterView); 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 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: filterViewChanged(!navigationBar.expandedFilterView) } 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 spacing: 0 - anchors.margins: { - top: elisaTheme.layoutVerticalMargin - bottom: elisaTheme.layoutVerticalMargin - } + anchors.topMargin: elisaTheme.layoutVerticalMargin + anchors.bottomMargin: elisaTheme.layoutVerticalMargin RowLayout { spacing: 0 Layout.alignment: Qt.AlignTop + Layout.preferredHeight: elisaTheme.navigationBarHeight + Layout.minimumHeight: elisaTheme.navigationBarHeight + Layout.maximumHeight: elisaTheme.navigationBarHeight Controls1.ToolButton { action: goPreviousAction objectName: 'goPreviousButton' Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 visible: enableGoBack } Image { id: mainIcon source: image asynchronous: true sourceSize.height: elisaTheme.coverImageSize / 2 sourceSize.width: elisaTheme.coverImageSize / 2 fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft 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 } ColumnLayout { Layout.preferredHeight: elisaTheme.coverImageSize / 2 Layout.minimumHeight: elisaTheme.coverImageSize / 2 Layout.maximumHeight: elisaTheme.coverImageSize / 2 spacing: 0 Layout.fillWidth: true Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 TextMetrics { id: albumTextSize text: albumLabel.text font.pointSize: albumLabel.font.pointSize font.bold: albumLabel.font.bold } LabelWithToolTip { id: albumLabel text: mainTitle Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter elide: Text.ElideRight color: myPalette.text font { pointSize: elisaTheme.defaultFontPointSize * 2 } } TextMetrics { id: authorTextSize text: authorLabel.text font.pointSize: authorLabel.font.pointSize font.bold: authorLabel.font.bold } LabelWithToolTip { id: authorLabel text: secondaryTitle color: myPalette.text Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter font { pointSize: elisaTheme.defaultFontPointSize } elide: Text.ElideRight visible: secondaryTitle !== "" } Item { id: emptyBottomFiller Layout.fillWidth: true Layout.fillHeight: true } RowLayout { Layout.fillWidth: true spacing: 0 Controls1.Button { objectName: 'enqueueButton' text: i18nc("Add current list to playlist", "Enqueue") iconName: "media-track-add-amarok" onClicked: enqueue() Layout.leftMargin: 0 Layout.rightMargin: 0 } 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: replaceAndPlay() Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } Controls1.Button { objectName: 'showArtistButton' id: showArtistButton visible: allowArtistNavigation text: i18nc("Button to navigate to the artist of the album", "Display Artist") iconName: "view-media-artist" onClicked: showArtist() Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } Item { Layout.fillWidth: true } Controls1.ToolButton { action: showFilterAction objectName: 'showFilterButton' Layout.alignment: Qt.AlignRight Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin : 0 } } } } RowLayout { id: filterRow spacing: 0 visible: opacity > 0.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 LabelWithToolTip { text: i18nc("before the TextField input of the filter", "Search: ") font.bold: true Layout.bottomMargin: 0 color: myPalette.text } TextField { id: filterTextInput objectName: 'filterTextInput' horizontalAlignment: TextInput.AlignLeft placeholderText: i18nc("Placeholder text in the filter text box", "Album name, artist, etc.") Layout.bottomMargin: 0 Layout.preferredWidth: navigationBar.width / 2 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: ") visible: showRating font.bold: true color: myPalette.text Layout.bottomMargin: 0 Layout.leftMargin: !LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 Layout.rightMargin: LayoutMirroring.enabled ? (elisaTheme.layoutHorizontalMargin * 2) : 0 } RatingStar { id: ratingFilter objectName: 'ratingFilter' visible: showRating readOnly: false starSize: elisaTheme.ratingStarSize Layout.bottomMargin: 0 } Item { Layout.fillWidth: true } Controls1.ToolButton { action: sortAction objectName: 'sortAscendingButton' 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 + 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 + 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 } } }