/* * Copyright 2021 Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtGraphicalEffects 1.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet import org.kde.latte.core 0.2 as LatteCore import org.kde.latte.components 1.0 as LatteComponents import "animations" as TaskAnimations Item { id: taskIconContainer anchors.fill: parent property bool toBeDestroyed: false readonly property color backgroundColor: taskIconItem.backgroundColor readonly property color glowColor: taskIconItem.glowColor readonly property bool smartLauncherEnabled: ((taskItem.isStartup === false) && (root.showInfoBadge || root.showProgressBadge)) readonly property bool progressVisible: smartLauncherItem && smartLauncherItem.progressVisible readonly property real progress: smartLauncherItem && smartLauncherItem.progress ? smartLauncherItem.progress : 0 property QtObject smartLauncherItem: null Rectangle{ id: draggedRectangle width: taskItem.isSeparator ? parent.width + 1 : taskIconItem.width+1 height: taskItem.isSeparator ? parent.height + 1 : taskIconItem.height+1 anchors.centerIn: taskIconContainer opacity: 0 radius: 3 anchors.margins: 5 property color tempColor: theme.highlightColor color: tempColor border.width: 1 border.color: theme.highlightColor onTempColorChanged: tempColor.a = 0.35; } LatteCore.IconItem { id: taskIconItem anchors.fill: parent source: decoration smooth: taskItem.abilities.parabolic.factor.zoom === 1 ? true : false providesColors: taskItem.abilities.indicators.info.needsIconColors opacity: taskItem.abilities.myView.itemShadow.isEnabled && taskWithShadow.active && graphicsSystem.isAccelerated ? 0 : 1 visible: !taskItem.isSeparator && (!badgesLoader.active || !graphicsSystem.isAccelerated) onValidChanged: { if (!valid && (source === decoration || source === "unknown")) { source = "application-x-executable"; } } //! try to show the correct icon when a window is removed... libtaskmanager when a window is removed //! sends an unknown pixmap as icon Connections { target: taskItem onInRemoveStageChanged: { if (taskItem.inRemoveStage && taskIconItem.lastValidSourceName !== "") { taskIconItem.source = taskIconItem.lastValidSourceName; } } } ///states for launcher animation states: [ State{ name: "*" //! since qt 5.14 default state can not use "when" property //! it breaks restoring transitions otherwise AnchorChanges{ target: taskIconItem; anchors.horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined; anchors.verticalCenter: root.vertical ? parent.verticalCenter : undefined; anchors.right: root.location === PlasmaCore.Types.RightEdge ? parent.right : undefined; anchors.left: root.location === PlasmaCore.Types.LeftEdge ? parent.left : undefined; anchors.top: root.location === PlasmaCore.Types.TopEdge ? parent.top : undefined; anchors.bottom: root.location === PlasmaCore.Types.BottomEdge ? parent.bottom : undefined; } }, State{ name: "animating" when: !taskItem.inAddRemoveAnimation && (launcherAnimation.running || newWindowAnimation.running || fastRestoreAnimation.running) AnchorChanges{ target: taskIconItem; anchors.horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined; anchors.verticalCenter: root.vertical ? parent.verticalCenter : undefined; anchors.right: root.location === PlasmaCore.Types.LeftEdge ? parent.right : undefined; anchors.left: root.location === PlasmaCore.Types.RightEdge ? parent.left : undefined; anchors.top: root.location === PlasmaCore.Types.BottomEdge ? parent.top : undefined; anchors.bottom: root.location === PlasmaCore.Types.TopEdge ? parent.bottom : undefined; } } ] ///transitions, basic for the anchor changes transitions: [ Transition{ from: "animating" to: "*" enabled: !fastRestoreAnimation.running AnchorAnimation { duration: 1.5 * taskItem.abilities.animations.speedFactor.current * taskItem.abilities.animations.duration.large } } ] } //! Shadows Loader{ id: taskWithShadow anchors.fill: taskIconContainer active: taskItem.abilities.myView.itemShadow.isEnabled && !taskItem.isSeparator && graphicsSystem.isAccelerated sourceComponent: DropShadow{ anchors.fill: parent color: taskItem.abilities.myView.itemShadow.shadowColor fast: true samples: 2 * radius source: badgesLoader.active ? badgesLoader.item : taskIconItem radius: taskItem.abilities.myView.itemShadow.size verticalOffset: 2 } } //! Shadows //! Combined Loader for Progress and Audio badges masks Loader{ id: badgesLoader anchors.fill: taskIconContainer active: (activateProgress > 0) && graphicsSystem.isAccelerated asynchronous: true opacity: stateColorizer.opacity > 0 ? 0 : 1 property real activateProgress: showInfo || showProgress || showAudio ? 1 : 0 property bool showInfo: (root.showInfoBadge && taskIcon.smartLauncherItem && !taskItem.isSeparator && (taskIcon.smartLauncherItem.countVisible || taskItem.badgeIndicator > 0) && !taskIcon.smartLauncherItem.progressVisible) property bool showProgress: root.showProgressBadge && taskIcon.smartLauncherItem && !taskItem.isSeparator && taskIcon.smartLauncherItem.progressVisible property bool showAudio: (root.showAudioBadge && taskItem.hasAudioStream && taskItem.playingAudio && !taskItem.isSeparator) Behavior on activateProgress { NumberAnimation { duration: 2 * taskItem.abilities.animations.speedFactor.current * taskItem.abilities.animations.duration.large } } sourceComponent: Item{ ShaderEffect { id: iconOverlay enabled: false anchors.fill: parent property var source: ShaderEffectSource { sourceItem: LatteCore.IconItem{ width: taskIconContainer.width height: taskIconContainer.height source: taskIconItem.source Loader{ anchors.fill: parent active: plasmoid.configuration.forceMonochromaticIcons sourceComponent: ColorOverlay { anchors.fill: parent color: latteBridge ? latteBridge.palette.textColor : "transparent" source: taskIconItem } } } } property var mask: ShaderEffectSource { sourceItem: Item{ LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft && !root.vertical LayoutMirroring.childrenInherit: true width: taskIconContainer.width height: taskIconContainer.height Rectangle{ id: maskRect width: Math.max(badgeVisualsLoader.infoBadgeWidth, parent.width / 2) height: parent.height / 2 radius: parent.height visible: badgesLoader.showInfo || badgesLoader.showProgress //! Removes any remainings from the icon around the roundness at the corner Rectangle{ id: maskCorner width: parent.width/2 height: parent.height/2 } states: [ State { name: "default" when: (root.location !== PlasmaCore.Types.RightEdge) AnchorChanges { target: maskRect anchors{ top:parent.top; bottom:undefined; left:undefined; right:parent.right;} } AnchorChanges { target: maskCorner anchors{ top:parent.top; bottom:undefined; left:undefined; right:parent.right;} } }, State { name: "right" when: (root.location === PlasmaCore.Types.RightEdge) AnchorChanges { target: maskRect anchors{ top:parent.top; bottom:undefined; left:parent.left; right:undefined;} } AnchorChanges { target: maskCorner anchors{ top:parent.top; bottom:undefined; left:parent.left; right:undefined;} } } ] } // progressMask Rectangle{ id: maskRect2 width: parent.width/2 height: width radius: width visible: badgesLoader.showAudio Rectangle{ id: maskCorner2 width:parent.width/2 height:parent.height/2 } states: [ State { name: "default" when: (root.location !== PlasmaCore.Types.RightEdge) AnchorChanges { target: maskRect2 anchors{ top:parent.top; bottom:undefined; left:parent.left; right:undefined;} } AnchorChanges { target: maskCorner2 anchors{ top:parent.top; bottom:undefined; left:parent.left; right:undefined;} } }, State { name: "right" when: (root.location === PlasmaCore.Types.RightEdge) AnchorChanges { target: maskRect2 anchors{ top:parent.top; bottom:undefined; left:undefined; right:parent.right;} } AnchorChanges { target: maskCorner2 anchors{ top:parent.top; bottom:undefined; left:undefined; right:parent.right;} } } ] } // audio mask } hideSource: true live: true } //end of mask supportsAtlasTextures: true fragmentShader: " varying highp vec2 qt_TexCoord0; uniform highp float qt_Opacity; uniform lowp sampler2D source; uniform lowp sampler2D mask; void main() { gl_FragColor = texture2D(source, qt_TexCoord0.st) * (1.0 - (texture2D(mask, qt_TexCoord0.st).a)) * qt_Opacity; } " } //end of sourceComponent } } ////! //! START: Badges Visuals //! the badges visual get out from iconGraphic in order to be able to draw shadows that //! extend beyond the iconGraphic boundaries Loader { id: badgeVisualsLoader anchors.fill: taskIconContainer active: (badgesLoader.activateProgress > 0) readonly property int infoBadgeWidth: active ? publishedInfoBadgeWidth : 0 property int publishedInfoBadgeWidth: 0 sourceComponent: Item { ProgressOverlay{ id: infoBadge anchors.right: parent.right anchors.top: parent.top width: Math.max(parent.width, contentWidth) height: parent.height opacity: badgesLoader.activateProgress visible: badgesLoader.showInfo || badgesLoader.showProgress layer.enabled: taskItem.abilities.myView.itemShadow.isEnabled && graphicsSystem.isAccelerated layer.effect: DropShadow { color: taskItem.abilities.myView.itemShadow.shadowColor fast: true samples: 2 * radius source: infoBadge radius: taskItem.abilities.myView.itemShadow.size verticalOffset: 2 } } AudioStream{ id: audioStreamBadge anchors.fill: parent opacity: badgesLoader.activateProgress visible: badgesLoader.showAudio layer.enabled: taskItem.abilities.myView.itemShadow.isEnabled && graphicsSystem.isAccelerated layer.effect: DropShadow { color: taskItem.abilities.myView.itemShadow.shadowColor fast: true samples: 2 * radius source: audioStreamBadge radius: taskItem.abilities.myView.itemShadow.size verticalOffset: 2 } } Binding { target: badgeVisualsLoader property: "publishedInfoBadgeWidth" value: infoBadge.contentWidth } } } //! GREY-ing the information badges when the task is dragged //! moved out of badgeVisualsLoader in order to avoid crashes //! when the latte view is removed Loader { width: taskIconContainer.width height: taskIconContainer.height anchors.centerIn: taskIconContainer active: badgeVisualsLoader.active && graphicsSystem.isAccelerated sourceComponent: Colorize{ source: badgeVisualsLoader.item //! HACK TO AVOID PIXELIZATION //! WORKAROUND: When Effects are enabled e.g. BrightnessContrast, Colorize etc. //! the icon appears pixelated. It is even most notable when parabolic.factor.zoom === 1 //! I don't know enabling cached=true helps, but it does. cached: true opacity: stateColorizer.opacity hue: stateColorizer.hue saturation: stateColorizer.saturation lightness: stateColorizer.lightness } } //! END: Badges Visuals //! Effects Colorize{ id: stateColorizer anchors.centerIn: taskIconContainer width: taskIconContainer.width height: taskIconContainer.height //! HACK TO AVOID PIXELIZATION //! WORKAROUND: When Effects are enabled e.g. BrightnessContrast, Colorize etc. //! the icon appears pixelated. It is even most notable when parabolic.factor.zoom === 1 //! I don't know enabling cached=true helps, but it does. cached: true source: badgesLoader.active ? badgesLoader : taskIconItem visible: !isSeparator opacity:0 hue:0 saturation:0 lightness:0 } BrightnessContrast{ id:hoveredImage anchors.centerIn: taskIconContainer width: taskIconContainer.width height: taskIconContainer.height //! HACK TO AVOID PIXELIZATION //! WORKAROUND: When Effects are enabled e.g. BrightnessContrast, Colorize etc. //! the icon appears pixelated. It is even most notable when parabolic.factor.zoom === 1 //! I don't know enabling cached=true helps, but it does. cached: true source: badgesLoader.active ? badgesLoader : taskIconItem visible: !isSeparator opacity: taskItem.containsMouse && !clickedAnimation.running && !taskItem.abilities.indicators.info.providesHoveredAnimation ? 1 : 0 brightness: 0.30 contrast: 0.1 Behavior on opacity { NumberAnimation { duration: taskItem.abilities.animations.speedFactor.current * taskItem.abilities.animations.duration.large } } } BrightnessContrast { id: brightnessTaskEffect anchors.centerIn: taskIconContainer width: taskIconContainer.width height: taskIconContainer.height //! HACK TO AVOID PIXELIZATION //! WORKAROUND: When Effects are enabled e.g. BrightnessContrast, Colorize etc. //! the icon appears pixelated. It is even most notable when parabolic.factor.zoom === 1 //! I don't know enabling cached=true helps, but it does. cached: true source: badgesLoader.active ? badgesLoader : taskIconItem visible: clickedAnimation.running && !isSeparator } //! Effects Loader { id: dropFilesVisual active: applyOpacity>0 width: !root.vertical ? length : thickness height: !root.vertical ? thickness : length anchors.centerIn: parent readonly property int length: taskItem.abilities.metrics.totals.length readonly property int thickness: taskItem.abilities.metrics.totals.thickness readonly property real applyOpacity: (mouseHandler.isDroppingSeparator || mouseHandler.isDroppingFiles) && (root.dragSource === null) && (mouseHandler.hoveredItem === taskItem) ? 0.7 : 0 sourceComponent: LatteComponents.AddItem { anchors.fill: parent backgroundOpacity: dropFilesVisual.applyOpacity } } //! Animations TaskAnimations.ClickedAnimation { id: clickedAnimation } TaskAnimations.LauncherAnimation { id:launcherAnimation } TaskAnimations.NewWindowAnimation { id: newWindowAnimation } TaskAnimations.RemoveWindowFromGroupAnimation { id: removingAnimation } TaskAnimations.FastRestoreAnimation { id: fastRestoreAnimation } //! Animations Component.onDestruction: { taskIcon.toBeDestroyed = true; if(removingAnimation.removingItem) removingAnimation.removingItem.destroy(); } onSmartLauncherEnabledChanged: { if (smartLauncherEnabled && !smartLauncherItem) { var smartLauncher = Qt.createQmlObject( " import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet; TaskManagerApplet.SmartLauncherItem { }", taskIcon); smartLauncher.launcherUrl = Qt.binding(function() { return taskItem.launcherUrlWithIcon; }); smartLauncherItem = smartLauncher; } else if (!smartLauncherEnabled && smartLauncherItem) { smartLauncherItem.destroy(); smartLauncherItem = null; } } Connections{ target: taskItem onInAttentionChanged:{ if (!taskItem.inAttention && newWindowAnimation.running && taskItem.inAttentionAnimation) { newWindowAnimation.pause(); fastRestoreAnimation.start(); } } } //////////// States //////////////////// states: [ State{ name: "*" //! since qt 5.14 default state can not use "when" property //! it breaks restoring transitions otherwise }, State{ name: "isDragged" when: taskItem.isDragged } ] //////////// Transitions ////////////// readonly property string draggingNeedThicknessEvent: taskIcon + "_dragging" transitions: [ Transition{ id: isDraggedTransition to: "isDragged" property int speed: taskItem.abilities.animations.speedFactor.current * taskItem.abilities.animations.duration.large SequentialAnimation{ ScriptAction{ script: { taskItem.abilities.animations.needThickness.addEvent(draggingNeedThicknessEvent); taskItem.inBlockingAnimation = true; taskItem.abilities.parabolic.setDirectRenderingEnabled(false); } } PropertyAnimation { target: taskItem.parabolicItem property: "zoom" to: 1 duration: taskItem.abilities.parabolic.factor.zoom === 1 ? 0 : (isDraggedTransition.speed*1.2) easing.type: Easing.OutQuad } ParallelAnimation{ PropertyAnimation { target: draggedRectangle property: "opacity" to: 1 duration: isDraggedTransition.speed easing.type: Easing.OutQuad } PropertyAnimation { target: taskIconItem property: "opacity" to: 0 duration: isDraggedTransition.speed easing.type: Easing.OutQuad } PropertyAnimation { target: stateColorizer property: "opacity" to: taskItem.isSeparator ? 0 : 1 duration: isDraggedTransition.speed easing.type: Easing.OutQuad } } ScriptAction{ script: { taskItem.abilities.animations.needThickness.removeEvent(draggingNeedThicknessEvent); } } } onRunningChanged: { if(running){ taskItem.animationStarted(); } else { taskItem.abilities.animations.needThickness.removeEvent(draggingNeedThicknessEvent); } } }, Transition{ id: defaultTransition from: "isDragged" to: "*" property int speed: taskItem.abilities.animations.speedFactor.current * taskItem.abilities.animations.duration.large SequentialAnimation{ ScriptAction{ script: { taskItem.abilities.parabolic.setDirectRenderingEnabled(false); } } ParallelAnimation{ PropertyAnimation { target: draggedRectangle property: "opacity" to: 0 duration: defaultTransition.speed easing.type: Easing.OutQuad } PropertyAnimation { target: taskIconItem property: "opacity" to: 1 duration: defaultTransition.speed easing.type: Easing.OutQuad } PropertyAnimation { target: stateColorizer property: "opacity" to: 0 duration: isDraggedTransition.speed easing.type: Easing.OutQuad } } ScriptAction{ script: { taskItem.inBlockingAnimation = false; } } } onRunningChanged: { if(!running){ taskItem.animationEnded(); } } } ] }