import QtQml 2.15
import QtQuick 2.15
import QtQuick.Layouts 1.2
+import QtGraphicalEffects 1.15
import Style 1.0
RowLayout {
property int titleFontSize: Style.unifiedSearchResultTitleFontSize
property int sublineFontSize: Style.unifiedSearchResultSublineFontSize
- property color titleColor: Style.ncTextColor
- property color sublineColor: Style.ncSecondaryTextColor
+ Accessible.role: Accessible.ListItem
+ Accessible.name: qsTr("Search result skeleton.").arg(model.index)
- property string iconColor: "#afafaf"
+ height: Style.trayWindowHeaderHeight
- property int index: 0
+ /*
+ * An overview of what goes on here:
+ *
+ * We want to provide the user with a loading animation of a unified search result list item "skeleton".
+ * This skeleton has a square and two rectangles that are meant to represent the icon and the text of a
+ * real search result.
+ *
+ * We also want to provide a nice animation that has a dark gradient moving over these shapes. To do so,
+ * we very simply create rectangles that have a gradient going transparent->dark->dark->transparent, and
+ * overlay them over the shapes beneath.
+ *
+ * We then use NumberAnimations to move these gradients left-to-right over the base shapes below. Since
+ * the gradient rectangles are child elements of the base-color rectangles which they move over, as long
+ * as we ensure that the parent rectangle has the "clip" property enabled this will look nice and won't
+ * spill over onto other elements.
+ *
+ * We also want to make sure that, even though these gradient rectangles are separate, it looks as it is
+ * one single gradient sweeping over the base color components
+ */
- Accessible.role: Accessible.ListItem
- Accessible.name: qsTr("Search result skeleton.").arg(index)
+ property color baseGradientColor: Style.lightHover
+ property color progressGradientColor: Style.darkMode ? Qt.lighter(baseGradientColor, 1.2) : Qt.darker(baseGradientColor, 1.1)
- height: Style.trayWindowHeaderHeight
+ property int animationRectangleWidth: Style.trayWindowWidth
+ property int animationStartX: -animationRectangleWidth
+ property int animationEndX: animationRectangleWidth
- Rectangle {
- id: unifiedSearchResultSkeletonThumbnail
- color: unifiedSearchResultSkeletonItemDetails.iconColor
+ Component {
+ id: gradientAnimationRectangle
+ Rectangle {
+ width: unifiedSearchResultSkeletonItemDetails.animationRectangleWidth
+ height: parent.height
+ gradient: Gradient {
+ orientation: Gradient.Horizontal
+ GradientStop {
+ position: 0
+ color: "transparent"
+ }
+ GradientStop {
+ position: 0.4
+ color: unifiedSearchResultSkeletonItemDetails.progressGradientColor
+ }
+ GradientStop {
+ position: 0.6
+ color: unifiedSearchResultSkeletonItemDetails.progressGradientColor
+ }
+ GradientStop {
+ position: 1.0
+ color: "transparent"
+ }
+ }
+
+ NumberAnimation on x {
+ from: unifiedSearchResultSkeletonItemDetails.animationStartX
+ to: unifiedSearchResultSkeletonItemDetails.animationEndX
+ duration: 1000
+ loops: Animation.Infinite
+ running: true
+ }
+ }
+ }
+
+ Item {
Layout.preferredWidth: unifiedSearchResultSkeletonItemDetails.iconWidth
Layout.preferredHeight: unifiedSearchResultSkeletonItemDetails.iconWidth
Layout.leftMargin: unifiedSearchResultSkeletonItemDetails.iconLeftMargin
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+
+ Rectangle {
+ id: unifiedSearchResultSkeletonThumbnail
+ anchors.fill: parent
+ color: unifiedSearchResultSkeletonItemDetails.baseGradientColor
+ clip: true
+ visible: false
+
+ Loader {
+ x: mapFromItem(unifiedSearchResultSkeletonItemDetails, 0, 0).x
+ height: parent.height
+ sourceComponent: gradientAnimationRectangle
+ }
+ }
+
+ Rectangle {
+ id: unifiedSearchResultSkeletonThumbnailMask
+ anchors.fill: unifiedSearchResultSkeletonThumbnail
+ color: "white"
+ radius: 100
+ visible: false
+ }
+
+ OpacityMask {
+ anchors.fill: unifiedSearchResultSkeletonThumbnail
+ source: unifiedSearchResultSkeletonThumbnail
+ maskSource: unifiedSearchResultSkeletonThumbnailMask
+ }
}
- ColumnLayout {
+ Column {
id: unifiedSearchResultSkeletonTextContainer
+
Layout.fillWidth: true
+ Layout.leftMargin: unifiedSearchResultSkeletonItemDetails.textLeftMargin
+ Layout.rightMargin: unifiedSearchResultSkeletonItemDetails.textRightMargin
- Rectangle {
- id: unifiedSearchResultSkeletonTitleText
- color: unifiedSearchResultSkeletonItemDetails.titleColor
- Layout.preferredHeight: unifiedSearchResultSkeletonItemDetails.titleFontSize
- Layout.leftMargin: unifiedSearchResultSkeletonItemDetails.textLeftMargin
- Layout.rightMargin: unifiedSearchResultSkeletonItemDetails.textRightMargin
- Layout.fillWidth: true
+ spacing: Style.standardSpacing
+
+ Item {
+ height: unifiedSearchResultSkeletonItemDetails.titleFontSize
+ width: parent.width
+
+ Rectangle {
+ id: unifiedSearchResultSkeletonTitleText
+ anchors.fill: parent
+ color: unifiedSearchResultSkeletonItemDetails.baseGradientColor
+ clip: true
+ visible: false
+
+ Loader {
+ x: mapFromItem(unifiedSearchResultSkeletonItemDetails, 0, 0).x
+ height: parent.height
+ sourceComponent: gradientAnimationRectangle
+ }
+ }
+
+ Rectangle {
+ id: unifiedSearchResultSkeletonTitleTextMask
+ anchors.fill: unifiedSearchResultSkeletonTitleText
+ color: "white"
+ radius: Style.veryRoundedButtonRadius
+ visible: false
+ }
+
+ OpacityMask {
+ anchors.fill: unifiedSearchResultSkeletonTitleText
+ source: unifiedSearchResultSkeletonTitleText
+ maskSource: unifiedSearchResultSkeletonTitleTextMask
+ }
}
- Rectangle {
- id: unifiedSearchResultSkeletonTextSubline
- color: unifiedSearchResultSkeletonItemDetails.sublineColor
- Layout.preferredHeight: unifiedSearchResultSkeletonItemDetails.sublineFontSize
- Layout.leftMargin: unifiedSearchResultSkeletonItemDetails.textLeftMargin
- Layout.rightMargin: unifiedSearchResultSkeletonItemDetails.textRightMargin
- Layout.fillWidth: true
+ Item {
+ height: unifiedSearchResultSkeletonItemDetails.sublineFontSize
+ width: parent.width
+
+ Rectangle {
+ id: unifiedSearchResultSkeletonTextSubline
+ anchors.fill: parent
+ color: unifiedSearchResultSkeletonItemDetails.baseGradientColor
+ clip: true
+ visible: false
+
+ Loader {
+ x: mapFromItem(unifiedSearchResultSkeletonItemDetails, 0, 0).x
+ height: parent.height
+ sourceComponent: gradientAnimationRectangle
+ }
+ }
+
+ Rectangle {
+ id: unifiedSearchResultSkeletonTextSublineMask
+ anchors.fill: unifiedSearchResultSkeletonTextSubline
+ color: "white"
+ radius: Style.veryRoundedButtonRadius
+ visible: false
+ }
+
+ OpacityMask {
+ anchors.fill:unifiedSearchResultSkeletonTextSubline
+ source: unifiedSearchResultSkeletonTextSubline
+ maskSource: unifiedSearchResultSkeletonTextSublineMask
+ }
}
}
}
Rectangle {\r
id: trayWindowBackground\r
\r
- property bool isUnifiedSearchActive: unifiedSearchResultsListViewSkeleton.visible\r
+ property bool isUnifiedSearchActive: unifiedSearchResultsListViewSkeletonLoader.active\r
|| unifiedSearchResultNothingFound.visible\r
|| unifiedSearchResultsErrorLabel.visible\r
|| unifiedSearchResultsListView.visible\r
}\r
}\r
\r
- UnifiedSearchResultItemSkeletonContainer {\r
- id: unifiedSearchResultsListViewSkeleton\r
- visible: !unifiedSearchResultNothingFound.visible && !unifiedSearchResultsListView.visible && ! UserModel.currentUser.unifiedSearchResultsListModel.errorString && UserModel.currentUser.unifiedSearchResultsListModel.searchTerm\r
+ Loader {\r
+ id: unifiedSearchResultsListViewSkeletonLoader\r
anchors.top: trayWindowUnifiedSearchInputContainer.bottom\r
anchors.left: trayWindowBackground.left\r
anchors.right: trayWindowBackground.right\r
anchors.bottom: trayWindowBackground.bottom\r
+\r
+ active: !unifiedSearchResultNothingFound.visible &&\r
+ !unifiedSearchResultsListView.visible &&\r
+ !UserModel.currentUser.unifiedSearchResultsListModel.errorString &&\r
+ UserModel.currentUser.unifiedSearchResultsListModel.searchTerm\r
+\r
+ sourceComponent: UnifiedSearchResultItemSkeletonContainer {\r
+ anchors.fill: parent\r
+ animationRectangleWidth: trayWindow.width\r
+ }\r
}\r
\r
ScrollView {\r