Warning, /plasma/kdeplasma-addons/kwin/windowswitchers/coverswitch/contents/ui/main.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  SPDX-FileCopyrightText: 2021 Ismael Asensio <isma.af@gmail.com>
0003 
0004  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 import QtQuick 2.15
0008 import QtQuick.Controls 2.15 as QQC2
0009 import Qt5Compat.GraphicalEffects
0010 import QtQuick.Layouts 1.15
0011 import QtQuick.Window 2.15
0012 
0013 import org.kde.kirigami 2.20 as Kirigami
0014 import org.kde.ksvg 1.0 as KSvg
0015 import org.kde.plasma.components 3.0 as PC3
0016 
0017 import org.kde.kwin 3.0 as KWin
0018 import org.kde.kwin.private.effects 1.0
0019 
0020 
0021 KWin.TabBoxSwitcher {
0022     id: tabBox
0023     currentIndex: thumbnailView ? thumbnailView.currentIndex : -1
0024 
0025     // TODO: Make it user configurable ?
0026     property bool enableBlur: true
0027 
0028     Window {
0029         id: window
0030 
0031         x: tabBox.screenGeometry.x
0032         y: tabBox.screenGeometry.y
0033         width: tabBox.screenGeometry.width
0034         height: tabBox.screenGeometry.height
0035         flags: Qt.BypassWindowManagerHint | Qt.FramelessWindowHint
0036         visibility: Window.FullScreen
0037         // Workaround QTBUG-35244. Do not directly assign here to avoid warning
0038         visible: true
0039 
0040         color: "transparent"
0041 
0042         KWin.DesktopBackground {
0043             activity: KWin.Workspace.currentActivity
0044             desktop: KWin.Workspace.currentVirtualDesktop
0045             outputName: window.screen.name
0046 
0047             layer.enabled: true
0048             layer.effect: FastBlur {
0049                 radius: enableBlur ? 64 : 0
0050             }
0051         }
0052 
0053         Rectangle {
0054             anchors {
0055                 top: enableBlur ? parent.top : infoBar.top
0056                 topMargin: enableBlur ? 0 : -infoBar.anchors.bottomMargin
0057                 left: parent.left
0058                 right: parent.right
0059                 bottom: parent.bottom
0060             }
0061             color: Kirigami.Theme.backgroundColor
0062             opacity: enableBlur ? 0.5 : 0.75
0063         }
0064 
0065         PathView {
0066             id: thumbnailView
0067 
0068             readonly property int visibleCount: Math.min(count, pathItemCount)
0069             readonly property real boxScaleFactor: 0.5
0070             readonly property int boxWidth: tabBox.screenGeometry.width * boxScaleFactor
0071             readonly property int boxHeight: tabBox.screenGeometry.height * boxScaleFactor
0072 
0073             focus: true
0074 
0075             anchors.fill: parent
0076 
0077             preferredHighlightBegin: 0.49
0078             preferredHighlightEnd: preferredHighlightBegin
0079             highlightRangeMode: PathView.StrictlyEnforceRange
0080 
0081             // This property sets the animation duration between the current position to the next one,
0082             // without taking into account how much distance the thumbnails travel in that time.
0083             // To compensate the speed, we slowly reduce the duration with the number of thumbnails,
0084             // starting from `veryLongDuration` when there are 2 of them
0085             highlightMoveDuration: Kirigami.Units.veryLongDuration * (2 / Math.sqrt(visibleCount + 1))
0086 
0087             pathItemCount: 13
0088 
0089             path: Path {
0090                 // Left stack
0091                 startX: thumbnailView.width * 0.1; startY: thumbnailView.height * 0.5
0092                 PathAttribute { name: "progress"; value: 0 }
0093                 PathAttribute { name: "scale"; value: 0.7 }
0094                 PathAttribute { name: "rotation"; value: 70 }
0095                 PathPercent { value: 0 }
0096 
0097                 PathLine { x: thumbnailView.width * 0.25 ; y: thumbnailView.height * 0.5 }
0098                 PathAttribute { name: "progress"; value: 0.8 }
0099                 PathAttribute { name: "scale"; value: 0.7 }
0100                 PathAttribute { name: "rotation"; value: 70 }
0101                 PathPercent { value: 0.4 }
0102 
0103                 // Center Item
0104                 PathQuad {
0105                     x: thumbnailView.width * 0.5 ; y: thumbnailView.height * 0.6
0106                     controlX: thumbnailView.width * 0.45; controlY: thumbnailView.height * 0.55
0107                 }
0108                 PathAttribute { name: "progress"; value: 1 }
0109                 PathAttribute { name: "scale"; value: 1 }
0110                 PathAttribute { name: "rotation"; value: 0 }
0111                 PathPercent { value: 0.49 } // A bit less than 50% so items preferrably stack on the right side
0112 
0113                 // Right stack
0114                 PathQuad {
0115                     x: thumbnailView.width * 0.75 ; y: thumbnailView.height * 0.5
0116                     controlX: thumbnailView.width * 0.55; controlY: thumbnailView.height * 0.55
0117                 }
0118                 PathAttribute { name: "progress"; value: 0.8 }
0119                 PathAttribute { name: "scale"; value: 0.7 }
0120                 PathAttribute { name: "rotation"; value: -70 }
0121                 PathPercent { value: 0.6 }
0122 
0123                 PathLine { x: thumbnailView.width * 0.9 ; y: thumbnailView.height * 0.5 }
0124                 PathAttribute { name: "progress"; value: 0 }
0125                 PathAttribute { name: "scale"; value: 0.7 }
0126                 PathAttribute { name: "rotation"; value: -70 }
0127                 PathPercent { value: 1 }
0128             }
0129 
0130             model: tabBox.model
0131 
0132             delegate: Item {
0133                 id: delegateItem
0134 
0135                 readonly property string caption: model.caption
0136                 readonly property var icon: model.icon
0137 
0138                 readonly property real scaleFactor: {
0139                     if (thumbnail.implicitWidth < thumbnailView.boxWidth && thumbnail.implicitHeight < thumbnailView.boxHeight) {
0140                         // Do not scale up thumbnails smaller than the box frame
0141                         return 1;
0142                     } else if (thumbnail.ratio > thumbnailView.boxWidth / thumbnailView.boxHeight) {
0143                         // Thumbnail is wider than the box
0144                         return thumbnailView.boxWidth / thumbnail.implicitWidth;
0145                     } else {
0146                         // Thumbnail is taller than the box
0147                         return thumbnailView.boxHeight / thumbnail.implicitHeight;
0148                     }
0149                 }
0150 
0151                 width: Math.round(thumbnail.implicitWidth * scaleFactor)
0152                 height: Math.round(thumbnail.implicitHeight * scaleFactor)
0153                 scale: PathView.onPath ? PathView.scale : 0
0154                 z: PathView.onPath ? Math.floor(PathView.progress * thumbnailView.visibleCount) : -1
0155 
0156                 KWin.WindowThumbnail {
0157                     id: thumbnail
0158                     readonly property double ratio: implicitWidth / implicitHeight
0159 
0160                     wId: windowId
0161                     anchors.fill: parent
0162                 }
0163 
0164                 Kirigami.ShadowedRectangle {
0165                     anchors.fill: parent
0166                     z: -1
0167 
0168                     color: "transparent"
0169                     shadow.size: Kirigami.Units.gridUnit
0170                     shadow.color: "black"
0171                     opacity: 0.5
0172                 }
0173 
0174                 transform: Rotation {
0175                     origin { x: delegateItem.width/2; y: delegateItem.height/2 }
0176                     axis { x: 0; y: 1; z: 0 }
0177                     angle: delegateItem.PathView.rotation
0178                 }
0179 
0180                 TapHandler {
0181                     grabPermissions: PointerHandler.TakeOverForbidden
0182                     gesturePolicy: TapHandler.WithinBounds
0183                     onSingleTapped: {
0184                         if (index === thumbnailView.currentIndex) {
0185                             thumbnailView.model.activate(index);
0186                             return;
0187                         }
0188                         thumbnailView.movementDirection = (delegateItem.PathView.rotation < 0) ? PathView.Positive : PathView.Negative
0189                         thumbnailView.currentIndex = index
0190                     }
0191                 }
0192             }
0193 
0194             highlight: KSvg.FrameSvgItem {
0195                 id: highlightItem
0196                 imagePath: "widgets/viewitem"
0197                 prefix: "hover"
0198 
0199                 readonly property Item target: thumbnailView.currentItem
0200 
0201                 visible: target !== null
0202                 // Make sure the highlight is pixel perfect aligned on both sides even if the target is not
0203                 anchors.centerIn: target
0204                 anchors.horizontalCenterOffset: target ? Math.round(target.x) - target.x : 0
0205                 anchors.verticalCenterOffset: target ? Math.round(target.y) - target.y : 0
0206                 width: target ? Math.round(target.width/2 + 3 * Kirigami.Units.smallSpacing) * 2 : 0
0207                 height: target ? Math.round(target.height/2 + 3 * Kirigami.Units.smallSpacing) * 2 : 0
0208                 scale: target ? target.scale : 1
0209                 z: target ? target.z - 0.5 : -0.5
0210                 // The transform cannot be directly assigned as the transform origin is different
0211                 transform: Rotation {
0212                     origin { x: highlightItem.width/2; y: highlightItem.height/2 }
0213                     axis { x: 0; y: 1; z: 0 }
0214                     angle: target ? target.PathView.rotation : 0
0215                 }
0216             }
0217 
0218             layer.enabled: true
0219             layer.smooth: true
0220 
0221             onMovementStarted: movementDirection = PathView.Shortest
0222 
0223             Keys.onUpPressed: decrementCurrentIndex()
0224             Keys.onLeftPressed: decrementCurrentIndex()
0225             Keys.onDownPressed: incrementCurrentIndex()
0226             Keys.onRightPressed: incrementCurrentIndex()
0227         }
0228 
0229         RowLayout {
0230             id: infoBar
0231 
0232             height: Kirigami.Units.iconSizes.large
0233             spacing: Kirigami.Units.gridUnit
0234 
0235             anchors {
0236                 horizontalCenter: parent.horizontalCenter
0237                 bottom: parent.bottom
0238                 margins: Kirigami.Units.gridUnit
0239             }
0240 
0241             Kirigami.Icon {
0242                 source: thumbnailView.currentItem ? thumbnailView.currentItem.icon : ""
0243                 implicitWidth: Kirigami.Units.iconSizes.large
0244                 implicitHeight: Kirigami.Units.iconSizes.large
0245                 Layout.alignment: Qt.AlignCenter
0246             }
0247 
0248             PC3.Label {
0249                 font.bold: true
0250                 font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.6)
0251                 text: thumbnailView.currentItem ? thumbnailView.currentItem.caption : ""
0252                 textFormat: Text.PlainText
0253                 maximumLineCount: 1
0254                 elide: Text.ElideMiddle
0255                 Layout.maximumWidth: tabBox.screenGeometry.width * 0.8
0256                 Layout.alignment: Qt.AlignCenter
0257             }
0258         }
0259     }
0260 
0261     onCurrentIndexChanged: {
0262         if (currentIndex === thumbnailView.currentIndex) {
0263             return
0264         }
0265 
0266         // WindowSwitcher always changes currentIndex in increments of 1.
0267         // Detect the change direction and set the PathView movement accordingly, so fast changes
0268         // in the same direction don't result into a combination of forward and backward movements.
0269         if (thumbnailView.count === 2 || (currentIndex === 0 && thumbnailView.currentIndex === thumbnailView.count - 1)) {
0270             thumbnailView.movementDirection = PathView.Positive
0271         } else if (currentIndex === thumbnailView.count - 1 && thumbnailView.currentIndex === 0) {
0272             thumbnailView.movementDirection = PathView.Negative
0273         } else {
0274             thumbnailView.movementDirection = (currentIndex > thumbnailView.currentIndex) ? PathView.Positive : PathView.Negative
0275         }
0276 
0277         thumbnailView.currentIndex = tabBox.currentIndex
0278     }
0279 
0280     onVisibleChanged: {
0281         // Reset the PathView index when hiding to avoid unwanted animations on relaunch
0282         if (!visible) {
0283             thumbnailView.currentIndex = 0;
0284         }
0285         window.visible = visible;
0286     }
0287 }