/* * SPDX-FileCopyrightText: 2014 Marco Martin * SPDX-FileCopyrightText: 2021 Devin Lin * * SPDX-License-Identifier: LGPL-2.0-or-later */ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.private.nanoshell 2.0 as NanoShell NanoShell.FullScreenOverlay { id: window property int offset: 0 // slide progress property int openThreshold: PlasmaCore.Units.gridUnit * 2 property bool userInteracting: false property bool initiallyOpened: false // whether the panel is already open after a touch release (then don't restrict to collapsed height) // height when quicksettings is fully open required property int fullyOpenHeight // flickable contentY readonly property int openedContentY: wideScreen || offset > (collapsedHeight + openThreshold) ? -topEmptyAreaHeight : offsetToContentY(collapsedHeight) readonly property int closedContentY: mainFlickable.contentHeight readonly property bool wideScreen: width > height || width > units.gridUnit * 45 readonly property int drawerWidth: wideScreen ? contentItem.implicitWidth : width property int drawerX: 0 property alias fixedArea: mainScope property alias flickable: mainFlickable color: "transparent" property alias contentItem: contentArea.contentItem property int topPanelHeight property int collapsedHeight property real topEmptyAreaHeight property bool appletsShown: false // whether notifications or media player applets are shown signal closed width: Screen.width height: Screen.height Component.onCompleted: plasmoid.nativeInterface.panel = window; onInitiallyOpenedChanged: { if (initiallyOpened) mainFlickable.focus = true; } function offsetToContentY(num) { return -num + window.fullyOpenHeight; } function contentYToOffset(num) { return offsetToContentY(num); } // avoids binding loops function updateOffset(delta) { // only go to collapsed height for mousearea when not widescreen let maximum = window.wideScreen ? window.fullyOpenHeight : collapsedHeight + openThreshold / 2; offset = Math.max(0, Math.min(maximum, offset + delta)); if (!mainFlickable.moving && !mainFlickable.dragging && !mainFlickable.flicking) { mainFlickable.contentY = offsetToContentY(window.offset); } } enum MovementDirection { None = 0, Up, Down } property int direction: SlidingContainer.MovementDirection.None function cancelAnimations() { closeAnim.stop(); openAnim.stop(); } function open() { cancelAnimations(); openAnim.restart(); initiallyOpened = true; } function close() { cancelAnimations(); closeAnim.restart(); initiallyOpened = false; } function updateState() { cancelAnimations(); if (window.offset <= 0) { // close immediately, so that we don't have to wait units.longDuration window.visible = false; window.closed(); close(); } else if (window.direction === SlidingContainer.MovementDirection.None) { if (window.offset < openThreshold) { close(); } else { open(); } } else if (offset > openThreshold && window.direction === SlidingContainer.MovementDirection.Down) { open(); } else if (mainFlickable.contentY > openThreshold) { close(); } else { open(); } } Timer { id: updateStateTimer interval: 0 onTriggered: updateState() } onActiveChanged: { if (!active) { close(); } } PropertyAnimation { id: closeAnim target: mainFlickable properties: "contentY" duration: PlasmaCore.Units.longDuration easing.type: Easing.InOutQuad to: window.closedContentY onFinished: { window.visible = false; } } PropertyAnimation { id: openAnim target: mainFlickable properties: "contentY" duration: PlasmaCore.Units.longDuration easing.type: Easing.InOutQuad to: window.openedContentY } // fullscreen background Rectangle { anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.75) opacity: (appletsShown ? 0.85 : 0.6) * Math.max(0, Math.min(1, offset / window.collapsedHeight)) Behavior on opacity { // smooth opacity changes NumberAnimation { duration: 70 } } } PlasmaCore.ColorScope { id: mainScope colorGroup: PlasmaCore.Theme.ViewColorGroup anchors.fill: parent Flickable { id: mainFlickable anchors.fill: parent property real oldContentY contentY: contentHeight onContentYChanged: { if (contentY === oldContentY) { window.direction = SlidingContainer.MovementDirection.None; } else { window.direction = contentY > oldContentY ? SlidingContainer.MovementDirection.Up : SlidingContainer.MovementDirection.Down; } window.offset = contentYToOffset(contentY); oldContentY = contentY; // close panel immediately after panel is not shown, and the flickable is not being dragged if (initiallyOpened && window.offset <= 0 && !mainFlickable.dragging && !closeAnim.running && !openAnim.running) { window.updateState(); focus = false; } } boundsMovement: Flickable.StopAtBounds contentWidth: window.width contentHeight: window.height bottomMargin: window.height onMovementStarted: { window.cancelAnimations(); window.userInteracting = true; } onFlickStarted: window.userInteracting = true; onMovementEnded: { window.userInteracting = false; window.updateState(); } onFlickEnded: { window.userInteracting = true; window.updateState(); } MouseArea { id: dismissArea z: 2 width: parent.width height: mainFlickable.contentHeight onClicked: window.close(); // actual sliding contents PlasmaComponents.Control { id: contentArea z: 1 x: drawerX width: drawerWidth } } } } }