mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
startupfeedback: Change to be window based and controlled by a model
This makes the startup feedback more robust, by having instances be controlled by a model which can listen to window changes. Being window based also allows for the close button and gestures to work properly with it, since it will show up in the task switcher as well. Fixes: * https://invent.kde.org/plasma/plasma-mobile/-/issues/357 * https://invent.kde.org/plasma/plasma-mobile/-/issues/338 * https://invent.kde.org/plasma/plasma-mobile/-/issues/335 (dark themes now tint the background color) * https://invent.kde.org/plasma/plasma-mobile/-/issues/330 * https://invent.kde.org/plasma/plasma-mobile/-/issues/30
This commit is contained in:
parent
41de61ef4c
commit
2d2b7407a6
26 changed files with 994 additions and 444 deletions
|
|
@ -65,9 +65,14 @@ MobileShell.BaseItem {
|
|||
} else if (root.settingsCommand && !root.restrictedPermissions) {
|
||||
closeRequested();
|
||||
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimation(
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimationWithPosition(
|
||||
__getCurrentScreenNumber(),
|
||||
root.icon);
|
||||
root.icon,
|
||||
root.text,
|
||||
'org.kde.mobile.plasmasettings', // settings window id
|
||||
-1,
|
||||
-1,
|
||||
Math.min(root.iconItem.width, root.iconItem.height));
|
||||
MobileShell.ShellUtil.executeCommand(root.settingsCommand);
|
||||
}
|
||||
}
|
||||
|
|
@ -75,9 +80,14 @@ MobileShell.BaseItem {
|
|||
function delegatePressAndHold() {
|
||||
if (root.settingsCommand && !root.restrictedPermissions) {
|
||||
closeRequested();
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimation(
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimationWithPosition(
|
||||
__getCurrentScreenNumber(),
|
||||
root.icon);
|
||||
root.icon,
|
||||
root.text,
|
||||
'org.kde.mobile.plasmasettings', // settings window id
|
||||
-1,
|
||||
-1,
|
||||
Math.min(root.iconItem.width, root.iconItem.height));
|
||||
MobileShell.ShellUtil.executeCommand(root.settingsCommand);
|
||||
} else if (root.toggleFunction) {
|
||||
root.toggleFunction();
|
||||
|
|
|
|||
|
|
@ -1,261 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
||||
import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
|
||||
import org.kde.plasma.plasmoid
|
||||
|
||||
/**
|
||||
* Component that animates an app opening from a location.
|
||||
*/
|
||||
|
||||
MouseArea { // use mousearea to ensure clicks don't go behind
|
||||
id: root
|
||||
visible: false
|
||||
|
||||
property alias backgroundColor: background.color
|
||||
property alias icon: icon.source
|
||||
|
||||
property bool __openRequested: false
|
||||
|
||||
function open(splashIcon) {
|
||||
iconParent.scale = 0.5;
|
||||
background.scale = 0.5;
|
||||
backgroundParent.x = 0;
|
||||
backgroundParent.y = 0;
|
||||
__openRequested = true;
|
||||
updateIconSource(splashIcon);
|
||||
}
|
||||
|
||||
function openWithPosition(splashIcon, x, y, sourceIconSize) {
|
||||
iconParent.scale = sourceIconSize/iconParent.width;
|
||||
background.scale = 0;
|
||||
backgroundParent.x = -root.width/2 + x
|
||||
backgroundParent.y = -root.height/2 + y
|
||||
__openRequested = true;
|
||||
updateIconSource(splashIcon);
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible = false;
|
||||
colorGenerator.resetColor();
|
||||
}
|
||||
|
||||
// call this after everything has loaded
|
||||
function actuallyOpen() {
|
||||
__openRequested = false;
|
||||
if (ShellSettings.Settings.animationsEnabled) {
|
||||
openAnimComplex.restart();
|
||||
} else {
|
||||
openAnimSimple.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// close when an app opens
|
||||
property bool windowActive: Window.active
|
||||
onWindowActiveChanged: root.close();
|
||||
|
||||
// close when homescreen requested
|
||||
Connections {
|
||||
target: MobileShellState.ShellDBusClient
|
||||
function onOpenHomeScreenRequested() {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: WindowPlugin.WindowUtil
|
||||
|
||||
// Open StartupFeedback when the notifier gives an app (ex. from Milou search)
|
||||
// TODO: This is problematic with multiple screens, because we don't have any info given
|
||||
// on which screen the app is opening on. Thus StartupFeedback would just open on
|
||||
// every single screen...
|
||||
// -> We have it disabled for now until some solution is found. We manually open StartupFeedback
|
||||
// from launches in the homescreen (call open()).
|
||||
//
|
||||
// function onAppActivationStarted(appId, iconName) {
|
||||
// if (!openAnimComplex.running && !root.__openRequested) {
|
||||
// // TODO: this doesn't work because it gets triggered on screen 0 even if the app is opening on screen 1
|
||||
// // HACK: We have no way of knowing which screen this app is going to open on
|
||||
// // -> Assume the first screen for now
|
||||
// if (Plasmoid.screen === 0) {
|
||||
// root.open(iconName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function onAppActivationFinished(appId, iconName) {
|
||||
if (iconName === root.icon.name) {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateIconSource(source) {
|
||||
if (icon.source !== source) {
|
||||
// the colors are generated async from the icon, so we need to ensure we don't display an old color
|
||||
// for a moment when an app opens
|
||||
colorGenerator.resetColor();
|
||||
} else {
|
||||
// case where we set the same icon, ensure the color is set
|
||||
colorGenerator.updateColor();
|
||||
}
|
||||
icon.source = source;
|
||||
}
|
||||
|
||||
Kirigami.ImageColors {
|
||||
id: colorGenerator
|
||||
source: icon.source
|
||||
|
||||
// the colors are generated async from the icon, so we need to ensure we don't display an old color
|
||||
// for a moment when an app opens
|
||||
property color colorToUse: 'transparent'
|
||||
|
||||
function resetColor() {
|
||||
colorToUse = 'transparent';
|
||||
}
|
||||
function updateColor() {
|
||||
colorToUse = colorGenerator.dominant;
|
||||
|
||||
// once color is finished updating, start the animation
|
||||
if (root.__openRequested) {
|
||||
root.actuallyOpen();
|
||||
}
|
||||
}
|
||||
onPaletteChanged: {
|
||||
// update color once palette has loaded
|
||||
updateColor();
|
||||
}
|
||||
}
|
||||
|
||||
// animation that moves the icon
|
||||
SequentialAnimation {
|
||||
id: openAnimComplex
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
root.opacity = 1;
|
||||
root.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// slight pause to give slower devices time to catch up when the item becomes visible
|
||||
PauseAnimation { duration: 20 }
|
||||
|
||||
ParallelAnimation {
|
||||
id: parallelAnim
|
||||
property real animationDuration: Kirigami.Units.longDuration + Kirigami.Units.shortDuration
|
||||
|
||||
ScaleAnimator {
|
||||
target: background
|
||||
from: background.scale
|
||||
to: 1
|
||||
duration: parallelAnim.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
ScaleAnimator {
|
||||
target: iconParent
|
||||
from: iconParent.scale
|
||||
to: 1
|
||||
duration: parallelAnim.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
XAnimator {
|
||||
target: backgroundParent
|
||||
from: backgroundParent.x
|
||||
to: 0
|
||||
duration: parallelAnim.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
YAnimator {
|
||||
target: backgroundParent
|
||||
from: backgroundParent.y
|
||||
to: 0
|
||||
duration: parallelAnim.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
// close the app drawer after it isn't visible
|
||||
MobileShellState.ShellDBusClient.resetHomeScreenPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animation that just fades in
|
||||
SequentialAnimation {
|
||||
id: openAnimSimple
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
root.opacity = 0;
|
||||
root.visible = true;
|
||||
background.scale = 1;
|
||||
iconParent.scale = 1;
|
||||
backgroundParent.x = 0;
|
||||
backgroundParent.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
properties: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
// close the app drawer after it isn't visible
|
||||
MobileShellState.ShellDBusClient.resetHomeScreenPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: backgroundParent
|
||||
width: root.width
|
||||
height: root.height
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
|
||||
color: colorGenerator.colorToUse
|
||||
}
|
||||
|
||||
Item {
|
||||
id: iconParent
|
||||
anchors.centerIn: background
|
||||
width: Kirigami.Units.iconSizes.enormous
|
||||
height: width
|
||||
|
||||
Kirigami.Icon {
|
||||
id: icon
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: icon
|
||||
source: icon
|
||||
shadowEnabled: true
|
||||
blurMax: 16
|
||||
shadowColor: "#80000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
import QtQuick
|
||||
|
||||
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
||||
|
||||
// Component to supplement the StartupFeedback window maximization animation for panel backgrounds.
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property real fullHeight
|
||||
property int screen
|
||||
property var maximizedTracker
|
||||
|
||||
// Smooth animation for colored rectangle
|
||||
NumberAnimation on height {
|
||||
id: heightAnim
|
||||
from: 0
|
||||
to: root.fullHeight
|
||||
duration: 200
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
|
||||
// Reset when maximized window state changes
|
||||
Connections {
|
||||
target: maximizedTracker
|
||||
|
||||
function onShowingWindowChanged() {
|
||||
root.color = 'transparent';
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to event from shell dbus
|
||||
Connections {
|
||||
target: MobileShellState.ShellDBusClient
|
||||
|
||||
function onAppLaunchMaximizePanelAnimationTriggered(screen, color) {
|
||||
if (root.screen !== screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
root.color = color;
|
||||
heightAnim.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
221
components/mobileshell/qml/components/StartupFeedbackWindows.qml
Normal file
221
components/mobileshell/qml/components/StartupFeedbackWindows.qml
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
||||
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property int screen
|
||||
property real topMargin
|
||||
property real bottomMargin
|
||||
property real leftMargin
|
||||
property real rightMargin
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: MobileShellState.StartupFeedbackFilterModel {
|
||||
startupFeedbackModel: MobileShellState.ShellDBusObject.startupFeedbackModel
|
||||
screen: root.screen
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
Window {
|
||||
id: window
|
||||
|
||||
property var startupFeedback: model.delegate
|
||||
|
||||
visibility: Window.Maximized
|
||||
flags: Qt.FramelessWindowHint
|
||||
color: 'transparent'
|
||||
title: startupFeedback.title
|
||||
|
||||
Component.onCompleted: {
|
||||
// root is anchored to the homescreen which fills up the whole screen,
|
||||
// but the startup feedback window will have margins (ex. status bar)
|
||||
const realHeight = root.height - root.topMargin - root.bottomMargin;
|
||||
const realWidth = root.width - root.leftMargin - root.rightMargin;
|
||||
|
||||
iconParent.scale = startupFeedback.iconSize / iconParent.width;
|
||||
background.scale = 0;
|
||||
|
||||
if (startupFeedback.iconStartX === -1 && startupFeedback.iconStartY === -1) {
|
||||
backgroundParent.x = 0;
|
||||
backgroundParent.y = 0;
|
||||
} else {
|
||||
backgroundParent.x = -realWidth/2 + startupFeedback.iconStartX - root.leftMargin;
|
||||
backgroundParent.y = -realHeight/2 + startupFeedback.iconStartY - root.topMargin;
|
||||
}
|
||||
|
||||
if (ShellSettings.Settings.animationsEnabled) {
|
||||
openAnimComplex.restart();
|
||||
} else {
|
||||
openAnimSimple.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// animation that moves the icon
|
||||
SequentialAnimation {
|
||||
id: openAnimComplex
|
||||
|
||||
// slight pause to give slower devices time to catch up when the item becomes visible
|
||||
PauseAnimation { duration: 20 }
|
||||
|
||||
ParallelAnimation {
|
||||
id: parallelAnim
|
||||
property real animationDuration: Kirigami.Units.longDuration + Kirigami.Units.shortDuration
|
||||
|
||||
ScaleAnimator {
|
||||
target: background
|
||||
from: background.scale
|
||||
to: 1
|
||||
duration: parallelAnim.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
ScaleAnimator {
|
||||
target: iconParent
|
||||
from: iconParent.scale
|
||||
to: 1
|
||||
duration: parallelAnim.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
XAnimator {
|
||||
target: backgroundParent
|
||||
from: backgroundParent.x
|
||||
to: 0
|
||||
duration: parallelAnim.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
YAnimator {
|
||||
target: backgroundParent
|
||||
from: backgroundParent.y
|
||||
to: 0
|
||||
duration: parallelAnim.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
// Animation has finished, trigger event for panels to update color
|
||||
MobileShellState.ShellDBusClient.triggerAppLaunchMaximizePanelAnimation(root.screen, background.color);
|
||||
|
||||
// close the app drawer after it isn't visible
|
||||
MobileShellState.ShellDBusClient.resetHomeScreenPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animation that just fades in
|
||||
SequentialAnimation {
|
||||
id: openAnimSimple
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
background.scale = 1;
|
||||
iconParent.scale = 1;
|
||||
backgroundParent.x = 0;
|
||||
backgroundParent.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: windowRoot
|
||||
properties: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
// Animation has finished, trigger event for panels to update color
|
||||
MobileShellState.ShellDBusClient.triggerAppLaunchMaximizePanelAnimation(root.screen, background.color);
|
||||
|
||||
// close the app drawer after it isn't visible
|
||||
MobileShellState.ShellDBusClient.resetHomeScreenPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: windowRoot
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
id: backgroundParent
|
||||
width: windowRoot.width
|
||||
height: windowRoot.height
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
|
||||
// Tint the background color if a dark theme is being used
|
||||
color: Kirigami.ColorUtils.brightnessForColor(Kirigami.Theme.backgroundColor) === Kirigami.ColorUtils.Dark ?
|
||||
Kirigami.ColorUtils.tintWithAlpha(colorGenerator.dominant, Kirigami.Theme.backgroundColor, 0.7) :
|
||||
colorGenerator.dominant
|
||||
|
||||
Kirigami.ImageColors {
|
||||
id: colorGenerator
|
||||
source: icon.source
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: iconParent
|
||||
anchors.centerIn: background
|
||||
width: Kirigami.Units.iconSizes.enormous
|
||||
height: Kirigami.Units.iconSizes.enormous
|
||||
|
||||
Kirigami.Icon {
|
||||
id: icon
|
||||
anchors.fill: parent
|
||||
source: window.startupFeedback.iconName
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: icon
|
||||
source: icon
|
||||
shadowEnabled: true
|
||||
blurMax: 16
|
||||
shadowColor: "#80000000"
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: true
|
||||
interval: 2000
|
||||
onTriggered: loadingIndicator.opacity = 1
|
||||
}
|
||||
|
||||
// Show loading indicator after two seconds have passed
|
||||
PC3.BusyIndicator {
|
||||
id: loadingIndicator
|
||||
anchors.top: icon.bottom
|
||||
anchors.horizontalCenter: icon.horizontalCenter
|
||||
anchors.topMargin: Kirigami.Units.gridUnit
|
||||
opacity: 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {}
|
||||
}
|
||||
|
||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,12 +40,6 @@ Item {
|
|||
*/
|
||||
property alias contentItem: itemContainer.contentItem
|
||||
|
||||
/**
|
||||
* Whether a component is being shown on top of the homescreen within the same
|
||||
* window.
|
||||
*/
|
||||
readonly property bool overlayShown: startupFeedback.visible
|
||||
|
||||
/**
|
||||
* The root PlasmoidItem of the containment this is used into
|
||||
*/
|
||||
|
|
@ -102,26 +96,6 @@ Item {
|
|||
root.resetHomeScreenPosition();
|
||||
}
|
||||
|
||||
function onOpenAppLaunchAnimationRequested(screen, splashIcon) {
|
||||
if (screen !== Plasmoid.screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
startupFeedback.open(splashIcon);
|
||||
}
|
||||
|
||||
function onOpenAppLaunchAnimationWithPositionRequested(screen, splashIcon, title, x, y, sourceIconSize) {
|
||||
if (screen !== Plasmoid.screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
startupFeedback.openWithPosition(splashIcon, x, y, sourceIconSize);
|
||||
}
|
||||
|
||||
function onCloseAppLaunchAnimationRequested() {
|
||||
startupFeedback.close();
|
||||
}
|
||||
|
||||
function onIsTaskSwitcherVisibleChanged() {
|
||||
if (MobileShellState.ShellDBusClient.isTaskSwitcherVisible) {
|
||||
itemContainer.zoomOutImmediately();
|
||||
|
|
@ -210,10 +184,17 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// start app animation component
|
||||
MobileShell.StartupFeedback {
|
||||
id: startupFeedback
|
||||
z: 999999
|
||||
// App start animation component
|
||||
MobileShell.StartupFeedbackWindows {
|
||||
id: startupFeedbackWindows
|
||||
screen: Plasmoid.screen
|
||||
|
||||
topMargin: root.topMargin
|
||||
bottomMargin: root.bottomMargin
|
||||
leftMargin: root.leftMargin
|
||||
rightMargin: root.rightMargin
|
||||
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ set(mobileshellstateplugin_SRCS
|
|||
shelldbusobject.cpp
|
||||
shelldbusclient.cpp
|
||||
lockscreendbusclient.cpp
|
||||
startupfeedbackmodel.cpp
|
||||
windowlistener.cpp
|
||||
)
|
||||
|
||||
qt_generate_dbus_interface(
|
||||
|
|
@ -34,6 +36,7 @@ target_link_libraries(mobileshellstateplugin
|
|||
Qt::Quick
|
||||
Qt::DBus
|
||||
Plasma::Plasma
|
||||
Plasma::KWaylandClient
|
||||
KF6::I18n
|
||||
KF6::Notifications
|
||||
Plasma::PlasmaQuick
|
||||
|
|
|
|||
|
|
@ -34,12 +34,10 @@ void ShellDBusClient::connectSignals()
|
|||
connect(m_interface, &OrgKdePlasmashellInterface::isTaskSwitcherVisibleChanged, this, &ShellDBusClient::updateIsTaskSwitcherVisible);
|
||||
connect(m_interface, &OrgKdePlasmashellInterface::openActionDrawerRequested, this, &ShellDBusClient::openActionDrawerRequested);
|
||||
connect(m_interface, &OrgKdePlasmashellInterface::closeActionDrawerRequested, this, &ShellDBusClient::closeActionDrawerRequested);
|
||||
connect(m_interface, &OrgKdePlasmashellInterface::openAppLaunchAnimationRequested, this, &ShellDBusClient::openAppLaunchAnimationRequested);
|
||||
connect(m_interface,
|
||||
&OrgKdePlasmashellInterface::openAppLaunchAnimationWithPositionRequested,
|
||||
&OrgKdePlasmashellInterface::appLaunchMaximizePanelAnimationTriggered,
|
||||
this,
|
||||
&ShellDBusClient::openAppLaunchAnimationWithPositionRequested);
|
||||
connect(m_interface, &OrgKdePlasmashellInterface::closeAppLaunchAnimationRequested, this, &ShellDBusClient::closeAppLaunchAnimationRequested);
|
||||
&ShellDBusClient::appLaunchMaximizePanelAnimationTriggered);
|
||||
connect(m_interface, &OrgKdePlasmashellInterface::openHomeScreenRequested, this, &ShellDBusClient::openHomeScreenRequested);
|
||||
connect(m_interface, &OrgKdePlasmashellInterface::resetHomeScreenPositionRequested, this, &ShellDBusClient::resetHomeScreenPositionRequested);
|
||||
connect(m_interface, &OrgKdePlasmashellInterface::showVolumeOSDRequested, this, &ShellDBusClient::showVolumeOSDRequested);
|
||||
|
|
@ -84,19 +82,20 @@ bool ShellDBusClient::isTaskSwitcherVisible() const
|
|||
return m_isTaskSwitcherVisible;
|
||||
}
|
||||
|
||||
void ShellDBusClient::openAppLaunchAnimation(int screen, QString splashIcon)
|
||||
void ShellDBusClient::openAppLaunchAnimationWithPosition(int screen,
|
||||
QString splashIcon,
|
||||
QString title,
|
||||
QString storageId,
|
||||
qreal x,
|
||||
qreal y,
|
||||
qreal sourceIconSize)
|
||||
{
|
||||
m_interface->openAppLaunchAnimation(screen, splashIcon);
|
||||
m_interface->openAppLaunchAnimationWithPosition(screen, splashIcon, title, storageId, x, y, sourceIconSize);
|
||||
}
|
||||
|
||||
void ShellDBusClient::openAppLaunchAnimationWithPosition(int screen, QString splashIcon, QString title, qreal x, qreal y, qreal sourceIconSize)
|
||||
void ShellDBusClient::triggerAppLaunchMaximizePanelAnimation(int screen, QString color)
|
||||
{
|
||||
m_interface->openAppLaunchAnimationWithPosition(screen, splashIcon, title, x, y, sourceIconSize);
|
||||
}
|
||||
|
||||
void ShellDBusClient::closeAppLaunchAnimation()
|
||||
{
|
||||
m_interface->closeAppLaunchAnimation();
|
||||
m_interface->triggerAppLaunchMaximizePanelAnimation(screen, color);
|
||||
}
|
||||
|
||||
void ShellDBusClient::openHomeScreen()
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ public:
|
|||
Q_INVOKABLE void openActionDrawer();
|
||||
Q_INVOKABLE void closeActionDrawer();
|
||||
|
||||
Q_INVOKABLE void openAppLaunchAnimation(int screen, QString splashIcon);
|
||||
Q_INVOKABLE void openAppLaunchAnimationWithPosition(int screen, QString splashIcon, QString title, qreal x, qreal y, qreal sourceIconSize);
|
||||
Q_INVOKABLE void closeAppLaunchAnimation();
|
||||
Q_INVOKABLE void
|
||||
openAppLaunchAnimationWithPosition(int screen, QString splashIcon, QString title, QString storageId, qreal x, qreal y, qreal sourceIconSize);
|
||||
Q_INVOKABLE void triggerAppLaunchMaximizePanelAnimation(int screen, QString color);
|
||||
|
||||
Q_INVOKABLE void openHomeScreen();
|
||||
Q_INVOKABLE void resetHomeScreenPosition();
|
||||
|
|
@ -48,9 +48,7 @@ Q_SIGNALS:
|
|||
void isTaskSwitcherVisibleChanged();
|
||||
void openActionDrawerRequested();
|
||||
void closeActionDrawerRequested();
|
||||
void openAppLaunchAnimationRequested(int screen, QString splashIcon);
|
||||
void openAppLaunchAnimationWithPositionRequested(int screen, QString splashIcon, QString title, qreal x, qreal y, qreal sourceIconSize);
|
||||
void closeAppLaunchAnimationRequested();
|
||||
void appLaunchMaximizePanelAnimationTriggered(int screen, QString color);
|
||||
void openHomeScreenRequested();
|
||||
void resetHomeScreenPositionRequested();
|
||||
void showVolumeOSDRequested();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
ShellDBusObject::ShellDBusObject(QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_startupFeedbackModel{new StartupFeedbackModel{this}}
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -20,6 +21,11 @@ void ShellDBusObject::registerObject()
|
|||
}
|
||||
}
|
||||
|
||||
StartupFeedbackModel *ShellDBusObject::startupFeedbackModel()
|
||||
{
|
||||
return m_startupFeedbackModel;
|
||||
}
|
||||
|
||||
bool ShellDBusObject::doNotDisturb()
|
||||
{
|
||||
return m_doNotDisturb;
|
||||
|
|
@ -69,19 +75,25 @@ void ShellDBusObject::closeActionDrawer()
|
|||
Q_EMIT closeActionDrawerRequested();
|
||||
}
|
||||
|
||||
void ShellDBusObject::openAppLaunchAnimation(int screen, QString splashIcon)
|
||||
void ShellDBusObject::openAppLaunchAnimationWithPosition(int screen,
|
||||
QString splashIcon,
|
||||
QString title,
|
||||
QString storageId,
|
||||
qreal x,
|
||||
qreal y,
|
||||
qreal sourceIconSize)
|
||||
{
|
||||
Q_EMIT openAppLaunchAnimationRequested(screen, splashIcon);
|
||||
if (!m_startupFeedbackModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
StartupFeedback *feedback = new StartupFeedback{m_startupFeedbackModel, splashIcon, title, storageId, x, y, sourceIconSize, screen};
|
||||
m_startupFeedbackModel->addApp(feedback);
|
||||
}
|
||||
|
||||
void ShellDBusObject::openAppLaunchAnimationWithPosition(int screen, QString splashIcon, QString title, qreal x, qreal y, qreal sourceIconSize)
|
||||
void ShellDBusObject::triggerAppLaunchMaximizePanelAnimation(int screen, QString color)
|
||||
{
|
||||
Q_EMIT openAppLaunchAnimationWithPositionRequested(screen, splashIcon, title, x, y, sourceIconSize);
|
||||
}
|
||||
|
||||
void ShellDBusObject::closeAppLaunchAnimation()
|
||||
{
|
||||
Q_EMIT closeAppLaunchAnimationRequested();
|
||||
Q_EMIT appLaunchMaximizePanelAnimationTriggered(screen, color);
|
||||
}
|
||||
|
||||
void ShellDBusObject::openHomeScreen()
|
||||
|
|
|
|||
|
|
@ -7,12 +7,15 @@
|
|||
#include <QString>
|
||||
#include <qqmlregistration.h>
|
||||
|
||||
#include "startupfeedbackmodel.h"
|
||||
|
||||
class ShellDBusObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
Q_CLASSINFO("D-Bus Interface", "org.kde.plasmashell")
|
||||
Q_PROPERTY(StartupFeedbackModel *startupFeedbackModel READ startupFeedbackModel CONSTANT)
|
||||
|
||||
public:
|
||||
ShellDBusObject(QObject *parent = nullptr);
|
||||
|
|
@ -20,15 +23,15 @@ public:
|
|||
// called by QML
|
||||
Q_INVOKABLE void registerObject();
|
||||
|
||||
StartupFeedbackModel *startupFeedbackModel();
|
||||
|
||||
Q_SIGNALS:
|
||||
Q_SCRIPTABLE void doNotDisturbChanged();
|
||||
Q_SCRIPTABLE void isActionDrawerOpenChanged();
|
||||
Q_SCRIPTABLE void isTaskSwitcherVisibleChanged();
|
||||
Q_SCRIPTABLE void openActionDrawerRequested();
|
||||
Q_SCRIPTABLE void closeActionDrawerRequested();
|
||||
Q_SCRIPTABLE void openAppLaunchAnimationRequested(int screen, QString splashIcon);
|
||||
Q_SCRIPTABLE void openAppLaunchAnimationWithPositionRequested(int screen, QString splashIcon, QString title, qreal x, qreal y, qreal sourceIconSize);
|
||||
Q_SCRIPTABLE void closeAppLaunchAnimationRequested();
|
||||
Q_SCRIPTABLE void appLaunchMaximizePanelAnimationTriggered(int screen, QString color);
|
||||
Q_SCRIPTABLE void openHomeScreenRequested();
|
||||
Q_SCRIPTABLE void resetHomeScreenPositionRequested();
|
||||
Q_SCRIPTABLE void showVolumeOSDRequested();
|
||||
|
|
@ -47,18 +50,20 @@ public Q_SLOTS:
|
|||
Q_SCRIPTABLE void openActionDrawer();
|
||||
Q_SCRIPTABLE void closeActionDrawer();
|
||||
|
||||
Q_SCRIPTABLE void openAppLaunchAnimation(int screen, QString splashIcon);
|
||||
Q_SCRIPTABLE void openAppLaunchAnimationWithPosition(int screen, QString splashIcon, QString title, qreal x, qreal y, qreal sourceIconSize);
|
||||
Q_SCRIPTABLE void closeAppLaunchAnimation();
|
||||
Q_SCRIPTABLE void
|
||||
openAppLaunchAnimationWithPosition(int screen, QString splashIcon, QString title, QString storageId, qreal x, qreal y, qreal sourceIconSize);
|
||||
Q_SCRIPTABLE void triggerAppLaunchMaximizePanelAnimation(int screen, QString color);
|
||||
|
||||
Q_SCRIPTABLE void openHomeScreen();
|
||||
Q_SCRIPTABLE void resetHomeScreenPosition();
|
||||
Q_SCRIPTABLE void showVolumeOSD();
|
||||
|
||||
private:
|
||||
bool m_initialized = false;
|
||||
bool m_initialized{false};
|
||||
|
||||
bool m_doNotDisturb = false;
|
||||
bool m_isActionDrawerOpen = false;
|
||||
bool m_isTaskSwitcherVisible = false;
|
||||
bool m_doNotDisturb{false};
|
||||
bool m_isActionDrawerOpen{false};
|
||||
bool m_isTaskSwitcherVisible{false};
|
||||
|
||||
StartupFeedbackModel *m_startupFeedbackModel{nullptr};
|
||||
};
|
||||
|
|
|
|||
300
components/mobileshellstate/startupfeedbackmodel.cpp
Normal file
300
components/mobileshellstate/startupfeedbackmodel.cpp
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "startupfeedbackmodel.h"
|
||||
#include "windowlistener.h"
|
||||
|
||||
constexpr int STARTUP_FEEDBACK_TIMEOUT_MS = 8000;
|
||||
|
||||
StartupFeedback::StartupFeedback(QObject *parent,
|
||||
QString iconName,
|
||||
QString title,
|
||||
QString storageId,
|
||||
qreal iconStartX,
|
||||
qreal iconStartY,
|
||||
qreal iconSize,
|
||||
int screen)
|
||||
: QObject{parent}
|
||||
, m_iconName{iconName}
|
||||
, m_title{title}
|
||||
, m_storageId{storageId}
|
||||
, m_iconStartX{iconStartX}
|
||||
, m_iconStartY{iconStartY}
|
||||
, m_iconSize{iconSize}
|
||||
, m_screen{screen}
|
||||
, m_timeoutTimer{new QTimer{this}}
|
||||
{
|
||||
connect(m_timeoutTimer, &QTimer::timeout, this, &StartupFeedback::timeout);
|
||||
}
|
||||
|
||||
QString StartupFeedback::iconName() const
|
||||
{
|
||||
return m_iconName;
|
||||
}
|
||||
|
||||
QString StartupFeedback::title() const
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
QString StartupFeedback::storageId() const
|
||||
{
|
||||
return m_storageId;
|
||||
}
|
||||
|
||||
qreal StartupFeedback::iconStartX() const
|
||||
{
|
||||
return m_iconStartX;
|
||||
}
|
||||
|
||||
qreal StartupFeedback::iconStartY() const
|
||||
{
|
||||
return m_iconStartY;
|
||||
}
|
||||
|
||||
qreal StartupFeedback::iconSize() const
|
||||
{
|
||||
return m_iconSize;
|
||||
}
|
||||
|
||||
int StartupFeedback::screen() const
|
||||
{
|
||||
return m_screen;
|
||||
}
|
||||
|
||||
QString StartupFeedback::windowUuid() const
|
||||
{
|
||||
return m_windowUuid;
|
||||
}
|
||||
|
||||
void StartupFeedback::setWindowUuid(QString uuid)
|
||||
{
|
||||
m_windowUuid = uuid;
|
||||
}
|
||||
|
||||
void StartupFeedback::startTimeoutTimer()
|
||||
{
|
||||
// Timeout of 5 seconds before closing
|
||||
m_timeoutTimer->start(STARTUP_FEEDBACK_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
StartupFeedbackModel::StartupFeedbackModel(QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
{
|
||||
connect(WindowListener::instance(), &WindowListener::windowCreated, this, &StartupFeedbackModel::onWindowOpened);
|
||||
connect(WindowListener::instance(), &WindowListener::plasmaWindowCreated, this, &StartupFeedbackModel::onPlasmaWindowOpened);
|
||||
connect(WindowListener::instance(), &WindowListener::activeWindowChanged, this, &StartupFeedbackModel::onActiveWindowChanged);
|
||||
}
|
||||
|
||||
void StartupFeedbackModel::addApp(StartupFeedback *startupFeedback)
|
||||
{
|
||||
beginInsertRows(QModelIndex{}, m_list.size(), m_list.size());
|
||||
|
||||
m_list.append(startupFeedback);
|
||||
updateActiveWindowIsStartupFeedback();
|
||||
|
||||
startupFeedback->startTimeoutTimer();
|
||||
|
||||
connect(startupFeedback, &StartupFeedback::timeout, this, [this, startupFeedback]() {
|
||||
int index = m_list.indexOf(startupFeedback);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex{}, index, index);
|
||||
m_list.removeAt(index);
|
||||
updateActiveWindowIsStartupFeedback();
|
||||
endRemoveRows();
|
||||
});
|
||||
|
||||
// Prepare state for active window being startupfeedback early, otherwise we have a race condition between
|
||||
// the Plasma window opening and the visual (causes panels to flash background color)
|
||||
m_activeWindowIsStartupFeedback = true;
|
||||
Q_EMIT activeWindowIsStartupFeedbackChanged();
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int StartupFeedbackModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_list.count();
|
||||
}
|
||||
|
||||
QVariant StartupFeedbackModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
auto delegate = m_list[index.row()];
|
||||
|
||||
switch (role) {
|
||||
case DelegateRole:
|
||||
return QVariant::fromValue(delegate);
|
||||
case ScreenRole:
|
||||
return delegate->screen();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> StartupFeedbackModel::roleNames() const
|
||||
{
|
||||
return {{DelegateRole, QByteArrayLiteral("delegate")}, {ScreenRole, QByteArrayLiteral("screen")}};
|
||||
}
|
||||
|
||||
bool StartupFeedbackModel::activeWindowIsStartupFeedback() const
|
||||
{
|
||||
return m_activeWindowIsStartupFeedback;
|
||||
}
|
||||
|
||||
void StartupFeedbackModel::onWindowOpened(KWayland::Client::PlasmaWindow *window)
|
||||
{
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString appId = window->appId();
|
||||
|
||||
int indexToRemove = 0;
|
||||
|
||||
// storageId may get suffixed with ".desktop", check for that
|
||||
const QString suffix = QStringLiteral(".desktop");
|
||||
|
||||
// Remove StartupFeedback when the respective window is created
|
||||
// NOTE: often, the window "appId" does not match the actual app storageId in third-party apps, so we can't rely on this.
|
||||
for (int i = 0; i < m_list.size(); ++i) {
|
||||
auto *startupFeedback = m_list[i];
|
||||
if (startupFeedback->storageId() == appId || startupFeedback->storageId() == appId + suffix) {
|
||||
indexToRemove = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no windows were matched, the oldest StartupFeedback (since indexToRemove = 0)
|
||||
// NOTE: This is our fallback if the window "appId" doesn't match anything.
|
||||
|
||||
if (m_list.size() > indexToRemove) {
|
||||
StartupFeedback *feedbackToRemove = m_list[indexToRemove];
|
||||
|
||||
// Only delete StartupFeedback once the window becomes active
|
||||
// -> There is a gap of time between when a window is created and when it is actually visible/active
|
||||
connect(window, &KWayland::Client::PlasmaWindow::activeChanged, this, [this, window, feedbackToRemove]() {
|
||||
if (!window->isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int indexToRemove = m_list.indexOf(feedbackToRemove);
|
||||
|
||||
if (indexToRemove != -1) {
|
||||
beginRemoveRows(QModelIndex{}, indexToRemove, indexToRemove);
|
||||
|
||||
m_list[indexToRemove]->deleteLater();
|
||||
m_list.removeAt(indexToRemove);
|
||||
updateActiveWindowIsStartupFeedback();
|
||||
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
window->disconnect(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void StartupFeedbackModel::onPlasmaWindowOpened(KWayland::Client::PlasmaWindow *window)
|
||||
{
|
||||
// Fill in the respective StartupFeedback with the window uuid
|
||||
// Heuristic: window title should match
|
||||
for (auto *startupFeedback : m_list) {
|
||||
if (startupFeedback->title() == window->title() && startupFeedback->windowUuid().isEmpty()) {
|
||||
startupFeedback->setWindowUuid(window->uuid());
|
||||
}
|
||||
}
|
||||
|
||||
// Update variable that depends on window uuid
|
||||
updateActiveWindowIsStartupFeedback();
|
||||
}
|
||||
|
||||
void StartupFeedbackModel::onActiveWindowChanged(KWayland::Client::PlasmaWindow *activeWindow)
|
||||
{
|
||||
m_activeWindow = activeWindow;
|
||||
updateActiveWindowIsStartupFeedback();
|
||||
}
|
||||
|
||||
void StartupFeedbackModel::updateActiveWindowIsStartupFeedback()
|
||||
{
|
||||
bool isStartupFeedback = false;
|
||||
|
||||
if (m_activeWindow) {
|
||||
// Check if there exists a StartupFeedback window with the same id as the active window
|
||||
for (const auto *startupFeedback : m_list) {
|
||||
if (startupFeedback->windowUuid() == m_activeWindow->uuid()) {
|
||||
isStartupFeedback = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isStartupFeedback != m_activeWindowIsStartupFeedback) {
|
||||
m_activeWindowIsStartupFeedback = isStartupFeedback;
|
||||
Q_EMIT activeWindowIsStartupFeedbackChanged();
|
||||
}
|
||||
}
|
||||
|
||||
StartupFeedbackFilterModel::StartupFeedbackFilterModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
setSortRole(StartupFeedbackModel::ScreenRole);
|
||||
}
|
||||
|
||||
StartupFeedbackModel *StartupFeedbackFilterModel::startupFeedbackModel() const
|
||||
{
|
||||
return m_startupFeedbackModel;
|
||||
}
|
||||
|
||||
void StartupFeedbackFilterModel::setStartupFeedbackModel(StartupFeedbackModel *startupFeedbackModel)
|
||||
{
|
||||
if (startupFeedbackModel == m_startupFeedbackModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_startupFeedbackModel = startupFeedbackModel;
|
||||
setSourceModel(m_startupFeedbackModel);
|
||||
Q_EMIT startupFeedbackModelChanged();
|
||||
}
|
||||
|
||||
int StartupFeedbackFilterModel::screen() const
|
||||
{
|
||||
return m_screen;
|
||||
}
|
||||
|
||||
void StartupFeedbackFilterModel::setScreen(int screen)
|
||||
{
|
||||
if (m_screen == screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_screen = screen;
|
||||
Q_EMIT screenChanged();
|
||||
}
|
||||
|
||||
bool StartupFeedbackFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
if (!m_startupFeedbackModel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QModelIndex index = m_startupFeedbackModel->index(sourceRow, 0, sourceParent);
|
||||
if (!index.isValid()) {
|
||||
return false;
|
||||
}
|
||||
const QVariant data = index.data();
|
||||
if (!data.isValid()) {
|
||||
// an invalid QVariant is valid data
|
||||
return true;
|
||||
}
|
||||
|
||||
StartupFeedback *startupFeedback = qvariant_cast<StartupFeedback *>(data);
|
||||
return startupFeedback->screen() == m_screen;
|
||||
}
|
||||
132
components/mobileshellstate/startupfeedbackmodel.h
Normal file
132
components/mobileshellstate/startupfeedbackmodel.h
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// SPDX-FileCopyrightText: 2024 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
#include <qqmlregistration.h>
|
||||
|
||||
#include <KWayland/Client/plasmawindowmanagement.h>
|
||||
|
||||
class StartupFeedback : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString iconName READ iconName CONSTANT)
|
||||
Q_PROPERTY(QString title READ title CONSTANT)
|
||||
Q_PROPERTY(QString storageId READ storageId CONSTANT)
|
||||
Q_PROPERTY(qreal iconStartX READ iconStartX CONSTANT)
|
||||
Q_PROPERTY(qreal iconStartY READ iconStartY CONSTANT)
|
||||
Q_PROPERTY(qreal iconSize READ iconSize CONSTANT)
|
||||
Q_PROPERTY(int screen READ screen CONSTANT)
|
||||
|
||||
public:
|
||||
explicit StartupFeedback(QObject *parent = nullptr,
|
||||
QString iconName = "",
|
||||
QString title = "",
|
||||
QString storageId = "",
|
||||
qreal iconStartX = 0.0,
|
||||
qreal iconStartY = 0.0,
|
||||
qreal iconSize = 0.0,
|
||||
int screen = 0);
|
||||
|
||||
explicit StartupFeedback();
|
||||
|
||||
QString iconName() const;
|
||||
QString title() const;
|
||||
QString storageId() const;
|
||||
|
||||
qreal iconStartX() const;
|
||||
qreal iconStartY() const;
|
||||
qreal iconSize() const;
|
||||
|
||||
int screen() const;
|
||||
|
||||
// Set by StartupFeedbackModel
|
||||
QString windowUuid() const;
|
||||
void setWindowUuid(QString uuid);
|
||||
|
||||
void startTimeoutTimer();
|
||||
|
||||
Q_SIGNALS:
|
||||
void timeout();
|
||||
|
||||
private:
|
||||
const QString m_iconName;
|
||||
const QString m_title;
|
||||
const QString m_storageId;
|
||||
const qreal m_iconStartX;
|
||||
const qreal m_iconStartY;
|
||||
const qreal m_iconSize;
|
||||
const int m_screen;
|
||||
QString m_windowUuid;
|
||||
|
||||
QTimer *m_timeoutTimer{nullptr};
|
||||
};
|
||||
|
||||
class StartupFeedbackModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool activeWindowIsStartupFeedback READ activeWindowIsStartupFeedback NOTIFY activeWindowIsStartupFeedbackChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
DelegateRole = Qt::UserRole,
|
||||
ScreenRole,
|
||||
};
|
||||
|
||||
explicit StartupFeedbackModel(QObject *parent = nullptr);
|
||||
|
||||
void addApp(StartupFeedback *startupFeedback);
|
||||
|
||||
bool activeWindowIsStartupFeedback() const;
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void activeWindowIsStartupFeedbackChanged();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onWindowOpened(KWayland::Client::PlasmaWindow *window);
|
||||
void onPlasmaWindowOpened(KWayland::Client::PlasmaWindow *window);
|
||||
void onActiveWindowChanged(KWayland::Client::PlasmaWindow *activeWindow);
|
||||
|
||||
private:
|
||||
void updateActiveWindowIsStartupFeedback();
|
||||
|
||||
bool m_activeWindowIsStartupFeedback{false};
|
||||
QList<StartupFeedback *> m_list;
|
||||
KWayland::Client::PlasmaWindow *m_activeWindow{nullptr};
|
||||
};
|
||||
|
||||
class StartupFeedbackFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(StartupFeedbackModel *startupFeedbackModel READ startupFeedbackModel WRITE setStartupFeedbackModel NOTIFY startupFeedbackModelChanged)
|
||||
Q_PROPERTY(int screen READ screen WRITE setScreen NOTIFY screenChanged)
|
||||
|
||||
public:
|
||||
explicit StartupFeedbackFilterModel(QObject *parent = nullptr);
|
||||
|
||||
StartupFeedbackModel *startupFeedbackModel() const;
|
||||
void setStartupFeedbackModel(StartupFeedbackModel *taskModel);
|
||||
|
||||
int screen() const;
|
||||
void setScreen(int screen);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void screenChanged();
|
||||
void startupFeedbackModelChanged();
|
||||
|
||||
private:
|
||||
StartupFeedbackModel *m_startupFeedbackModel{nullptr};
|
||||
int m_screen{0};
|
||||
};
|
||||
57
components/mobileshellstate/windowlistener.cpp
Normal file
57
components/mobileshellstate/windowlistener.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "windowlistener.h"
|
||||
|
||||
WindowListener::WindowListener(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
// initialize wayland window checking
|
||||
KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this);
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *registry = new KWayland::Client::Registry(this);
|
||||
registry->create(connection);
|
||||
|
||||
connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) {
|
||||
m_windowManagement = registry->createPlasmaWindowManagement(name, version, this);
|
||||
connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, &WindowListener::onWindowCreated);
|
||||
connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::activeWindowChanged, this, [this]() {
|
||||
Q_EMIT activeWindowChanged(m_windowManagement->activeWindow());
|
||||
});
|
||||
});
|
||||
|
||||
registry->setup();
|
||||
connection->roundtrip();
|
||||
}
|
||||
|
||||
WindowListener *WindowListener::instance()
|
||||
{
|
||||
static WindowListener *listener = new WindowListener();
|
||||
return listener;
|
||||
}
|
||||
|
||||
void WindowListener::onWindowCreated(KWayland::Client::PlasmaWindow *window)
|
||||
{
|
||||
QString storageId = window->appId();
|
||||
|
||||
// Ignore empty windows
|
||||
if (storageId == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handling for plasmashell windows, don't track them
|
||||
if (storageId == "org.kde.plasmashell") {
|
||||
Q_EMIT plasmaWindowCreated(window);
|
||||
return;
|
||||
}
|
||||
|
||||
// listen for window close
|
||||
connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, storageId]() {
|
||||
Q_EMIT windowRemoved(storageId);
|
||||
});
|
||||
|
||||
Q_EMIT windowCreated(window);
|
||||
}
|
||||
36
components/mobileshellstate/windowlistener.h
Normal file
36
components/mobileshellstate/windowlistener.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
|
||||
#include <KWayland/Client/connection_thread.h>
|
||||
#include <KWayland/Client/plasmawindowmanagement.h>
|
||||
#include <KWayland/Client/registry.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
|
||||
class WindowListener : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WindowListener(QObject *parent = nullptr);
|
||||
|
||||
static WindowListener *instance();
|
||||
|
||||
QList<KWayland::Client::PlasmaWindow *> windowsFromStorageId(QString &storageId) const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void onWindowCreated(KWayland::Client::PlasmaWindow *window);
|
||||
|
||||
Q_SIGNALS:
|
||||
void windowCreated(KWayland::Client::PlasmaWindow *window);
|
||||
void plasmaWindowCreated(KWayland::Client::PlasmaWindow *window);
|
||||
void windowRemoved(QString storageId);
|
||||
void activeWindowChanged(KWayland::Client::PlasmaWindow *activeWindow);
|
||||
|
||||
private:
|
||||
KWayland::Client::PlasmaWindowManagement *m_windowManagement{nullptr};
|
||||
};
|
||||
|
|
@ -15,6 +15,7 @@ QtObject {
|
|||
property alias screenGeometry: tasksModel.screenGeometry
|
||||
|
||||
readonly property bool showingWindow: __internal.count > 0 && !WindowPlugin.WindowUtil.isShowingDesktop
|
||||
readonly property int windowCount: __internal.count
|
||||
|
||||
property var __internal: KItemModels.KSortFilterProxyModel {
|
||||
id: visibleMaximizedWindowsModel
|
||||
|
|
|
|||
|
|
@ -41,9 +41,6 @@ public:
|
|||
public Q_SLOTS:
|
||||
void sycocaDbChanged();
|
||||
|
||||
Q_SIGNALS:
|
||||
void launchError(const QString &msg);
|
||||
|
||||
protected:
|
||||
HomeScreen *m_homeScreen{nullptr};
|
||||
QList<FolioDelegate *> m_delegates;
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ AbstractDelegate {
|
|||
property bool turnToFolderAnimEnabled: false
|
||||
|
||||
function launchApp() {
|
||||
if (application.icon !== "") {
|
||||
if (application.icon !== "" && !root.application.running) {
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimationWithPosition(
|
||||
Plasmoid.screen,
|
||||
application.icon,
|
||||
application.name,
|
||||
application.storageId,
|
||||
root.iconItem.Kirigami.ScenePosition.x + root.iconItem.width/2,
|
||||
root.iconItem.Kirigami.ScenePosition.y + root.iconItem.height/2,
|
||||
Math.min(root.iconItem.width, root.iconItem.height));
|
||||
|
|
|
|||
|
|
@ -166,17 +166,6 @@ ContainmentItem {
|
|||
bottomMargin: homeScreen.bottomMargin
|
||||
leftMargin: homeScreen.leftMargin
|
||||
rightMargin: homeScreen.rightMargin
|
||||
|
||||
// make the homescreen not interactable when task switcher or startup feedback is on
|
||||
interactive: !homeScreen.overlayShown
|
||||
}
|
||||
}
|
||||
|
||||
// listen to app launch errors
|
||||
Connections {
|
||||
target: folio.ApplicationListModel
|
||||
function onLaunchError(msg) {
|
||||
MobileShellState.ShellDBusClient.closeAppLaunchAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ Item {
|
|||
Plasmoid.screen,
|
||||
source,
|
||||
title,
|
||||
storageId,
|
||||
iconLoader.Kirigami.ScenePosition.x + iconLoader.width/2,
|
||||
iconLoader.Kirigami.ScenePosition.y + iconLoader.height/2,
|
||||
Math.min(iconLoader.width, iconLoader.height));
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ MobileShell.GridView {
|
|||
Plasmoid.screen,
|
||||
icon,
|
||||
title,
|
||||
storageId,
|
||||
delegate.iconItem.Kirigami.ScenePosition.x + delegate.iconItem.width/2,
|
||||
delegate.iconItem.Kirigami.ScenePosition.y + delegate.iconItem.height/2,
|
||||
Math.min(delegate.iconItem.width, delegate.iconItem.height));
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ ContainmentItem {
|
|||
|
||||
Rectangle {
|
||||
id: darkenBackground
|
||||
color: homeScreen.overlayShown ? 'transparent' : (halcyonHomeScreen.page == 1 ? Qt.rgba(0, 0, 0, 0.7) : Qt.rgba(0, 0, 0, 0.2))
|
||||
color: (halcyonHomeScreen.page == 1 ? Qt.rgba(0, 0, 0, 0.7) : Qt.rgba(0, 0, 0, 0.2))
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
Behavior on color {
|
||||
|
|
@ -101,8 +101,6 @@ ContainmentItem {
|
|||
leftMargin: homeScreen.leftMargin
|
||||
rightMargin: homeScreen.rightMargin
|
||||
|
||||
// make the homescreen not interactable when task switcher or startup feedback is on
|
||||
interactive: !homeScreen.overlayShown
|
||||
searchWidget: search
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,6 @@ public:
|
|||
public Q_SLOTS:
|
||||
void sycocaDbChanged();
|
||||
|
||||
Q_SIGNALS:
|
||||
void launchError(const QString &msg);
|
||||
|
||||
protected:
|
||||
QList<Application *> m_applicationList;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ ContainmentItem {
|
|||
}
|
||||
|
||||
// only opaque if there are no maximized windows on this screen
|
||||
readonly property bool showingApp: windowMaximizedTracker.showingWindow
|
||||
readonly property bool showingStartupFeedback: MobileShellState.ShellDBusObject.startupFeedbackModel.activeWindowIsStartupFeedback && windowMaximizedTracker.windowCount === 1
|
||||
readonly property bool showingApp: windowMaximizedTracker.showingWindow && !showingStartupFeedback
|
||||
readonly property color backgroundColor: topPanel.colorScopeColor
|
||||
|
||||
WindowPlugin.WindowMaximizedTracker {
|
||||
|
|
@ -96,6 +97,17 @@ ContainmentItem {
|
|||
MobileShell.VolumeOSDProviderLoader.load();
|
||||
}
|
||||
|
||||
MobileShell.StartupFeedbackPanelFill {
|
||||
id: startupFeedbackColorAnimation
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
fullHeight: root.height
|
||||
screen: Plasmoid.screen
|
||||
maximizedTracker: windowMaximizedTracker
|
||||
}
|
||||
|
||||
// top panel component
|
||||
MobileShell.StatusBar {
|
||||
id: topPanel
|
||||
|
|
|
|||
|
|
@ -98,7 +98,6 @@ MobileShell.NavigationPanel {
|
|||
if (tasksModel.activeTask !== 0) {
|
||||
tasksModel.requestClose(tasksModel.activeTask);
|
||||
}
|
||||
MobileShellState.ShellDBusClient.closeAppLaunchAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import org.kde.kquickcontrolsaddons 2.0
|
|||
import org.kde.plasma.private.mobileshell as MobileShell
|
||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
|
||||
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
||||
|
||||
ContainmentItem {
|
||||
id: root
|
||||
|
|
@ -110,13 +111,25 @@ ContainmentItem {
|
|||
Component.onCompleted: setWindowProperties();
|
||||
|
||||
// only opaque if there are no maximized windows on this screen
|
||||
readonly property bool opaqueBar: windowMaximizedTracker.showingWindow
|
||||
readonly property bool showingStartupFeedback: MobileShellState.ShellDBusObject.startupFeedbackModel.activeWindowIsStartupFeedback && windowMaximizedTracker.windowCount === 1
|
||||
readonly property bool opaqueBar: windowMaximizedTracker.showingWindow && !showingStartupFeedback
|
||||
|
||||
WindowPlugin.WindowMaximizedTracker {
|
||||
id: windowMaximizedTracker
|
||||
screenGeometry: Plasmoid.containment.screenGeometry
|
||||
}
|
||||
|
||||
MobileShell.StartupFeedbackPanelFill {
|
||||
id: startupFeedbackColorAnimation
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
fullHeight: root.height
|
||||
screen: Plasmoid.screen
|
||||
maximizedTracker: windowMaximizedTracker
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue