mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
Show applet popup on tile long-press in convergence
Long-pressing a mapped quick-settings tile in convergence mode opens a floating popup with the Plasma applet UI instead of launching System Settings. Unmapped tiles still open Settings. AppletHost creates a standalone Corona to host applets outside the shell containment tree.
This commit is contained in:
parent
b77bf41cdc
commit
8017e4eaa0
6 changed files with 265 additions and 0 deletions
|
|
@ -5,6 +5,8 @@ ecm_add_qml_module(mobileshellplugin URI org.kde.plasma.private.mobileshell GENE
|
||||||
|
|
||||||
set(mobileshellplugin_SRCS
|
set(mobileshellplugin_SRCS
|
||||||
shellutil.cpp
|
shellutil.cpp
|
||||||
|
components/applethost.cpp
|
||||||
|
components/applethost.h
|
||||||
components/direction.cpp
|
components/direction.cpp
|
||||||
components/direction.h
|
components/direction.h
|
||||||
components/swipearea.cpp
|
components/swipearea.cpp
|
||||||
|
|
@ -137,6 +139,7 @@ ecm_target_qml_sources(mobileshellplugin SOURCES
|
||||||
qml/actiondrawer/private/QuickSettingsMinimizedDelegate.qml
|
qml/actiondrawer/private/QuickSettingsMinimizedDelegate.qml
|
||||||
qml/actiondrawer/private/QuickSettingsPanel.qml
|
qml/actiondrawer/private/QuickSettingsPanel.qml
|
||||||
qml/actiondrawer/private/ContentContainer.qml
|
qml/actiondrawer/private/ContentContainer.qml
|
||||||
|
qml/actiondrawer/private/DetailPopup.qml
|
||||||
qml/actiondrawer/private/LandscapeContentContainer.qml
|
qml/actiondrawer/private/LandscapeContentContainer.qml
|
||||||
qml/actiondrawer/private/NotificationDrawer.qml
|
qml/actiondrawer/private/NotificationDrawer.qml
|
||||||
qml/actiondrawer/private/PortraitContentContainer.qml
|
qml/actiondrawer/private/PortraitContentContainer.qml
|
||||||
|
|
|
||||||
97
components/mobileshell/components/applethost.cpp
Normal file
97
components/mobileshell/components/applethost.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
#include "applethost.h"
|
||||||
|
|
||||||
|
#include <Plasma/Containment>
|
||||||
|
#include <Plasma/Corona>
|
||||||
|
#include <Plasma/PluginLoader>
|
||||||
|
#include <PlasmaQuick/AppletQuickItem>
|
||||||
|
|
||||||
|
#include <KPackage/Package>
|
||||||
|
#include <KPackage/PackageLoader>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
// Minimal Corona to host applets outside the shell's own containment tree.
|
||||||
|
class AppletHost::HostCorona : public Plasma::Corona
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit HostCorona(QObject *parent = nullptr)
|
||||||
|
: Plasma::Corona(parent)
|
||||||
|
{
|
||||||
|
KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(u"Plasma/Shell"_s);
|
||||||
|
pkg.setPath(u"org.kde.plasma.mobile"_s);
|
||||||
|
setKPackage(pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect screenGeometry(int id) const override
|
||||||
|
{
|
||||||
|
Q_UNUSED(id);
|
||||||
|
return {0, 0, 400, 600};
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadDefaultLayout() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AppletHost::AppletHost(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AppletHost::~AppletHost() = default;
|
||||||
|
|
||||||
|
void AppletHost::ensureCorona()
|
||||||
|
{
|
||||||
|
if (m_corona)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_corona = new HostCorona(this);
|
||||||
|
|
||||||
|
m_containment = m_corona->createContainment(u"null"_s);
|
||||||
|
if (m_containment) {
|
||||||
|
m_containment->setFormFactor(Plasma::Types::Application);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickItem *AppletHost::fullRepresentationFor(const QString &pluginId)
|
||||||
|
{
|
||||||
|
auto it = m_items.constFind(pluginId);
|
||||||
|
if (it != m_items.constEnd()) {
|
||||||
|
auto *item = *it;
|
||||||
|
return item ? item->fullRepresentationItem() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureCorona();
|
||||||
|
if (!m_containment) {
|
||||||
|
qWarning() << "AppletHost: failed to create containment";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *applet = Plasma::PluginLoader::self()->loadApplet(pluginId, 0);
|
||||||
|
if (!applet) {
|
||||||
|
qWarning() << "AppletHost: failed to load applet" << pluginId;
|
||||||
|
m_items.insert(pluginId, nullptr);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_containment->addApplet(applet);
|
||||||
|
auto *item = PlasmaQuick::AppletQuickItem::itemForApplet(applet);
|
||||||
|
m_items.insert(pluginId, item);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
qWarning() << "AppletHost: no AppletQuickItem for" << pluginId;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
item->setPreloadFullRepresentation(true);
|
||||||
|
|
||||||
|
return item->fullRepresentationItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "applethost.moc"
|
||||||
42
components/mobileshell/components/applethost.h
Normal file
42
components/mobileshell/components/applethost.h
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQuickItem>
|
||||||
|
|
||||||
|
namespace Plasma
|
||||||
|
{
|
||||||
|
class Applet;
|
||||||
|
class Containment;
|
||||||
|
class Corona;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace PlasmaQuick
|
||||||
|
{
|
||||||
|
class AppletQuickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppletHost : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_SINGLETON
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AppletHost(QObject *parent = nullptr);
|
||||||
|
~AppletHost() override;
|
||||||
|
|
||||||
|
Q_INVOKABLE QQuickItem *fullRepresentationFor(const QString &pluginId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ensureCorona();
|
||||||
|
|
||||||
|
class HostCorona;
|
||||||
|
HostCorona *m_corona = nullptr;
|
||||||
|
Plasma::Containment *m_containment = nullptr;
|
||||||
|
QHash<QString, PlasmaQuick::AppletQuickItem *> m_items;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.private.mobileshell as MobileShell
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Popup hosting a Plasma applet's fullRepresentation for convergence mode.
|
||||||
|
*/
|
||||||
|
QQC2.Popup {
|
||||||
|
id: popup
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
dim: true
|
||||||
|
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutside
|
||||||
|
|
||||||
|
x: parent ? Math.round((parent.width - width) / 2) : 0
|
||||||
|
y: parent ? Math.round((parent.height - height) / 2) : 0
|
||||||
|
|
||||||
|
width: Math.min(Kirigami.Units.gridUnit * 18,
|
||||||
|
parent ? parent.width - Kirigami.Units.gridUnit * 4 : 360)
|
||||||
|
height: Math.min(Kirigami.Units.gridUnit * 22,
|
||||||
|
parent ? parent.height - Kirigami.Units.gridUnit * 4 : 440)
|
||||||
|
|
||||||
|
padding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
property string currentPluginId: ""
|
||||||
|
property Item __currentItem: null
|
||||||
|
|
||||||
|
function show(pluginId) {
|
||||||
|
if (!pluginId) return;
|
||||||
|
|
||||||
|
if (__currentItem && pluginId !== currentPluginId) {
|
||||||
|
__currentItem.parent = null;
|
||||||
|
__currentItem.visible = false;
|
||||||
|
__currentItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPluginId = pluginId;
|
||||||
|
|
||||||
|
var item = MobileShell.AppletHost.fullRepresentationFor(pluginId);
|
||||||
|
if (!item) {
|
||||||
|
console.warn("DetailPopup: no fullRepresentation for", pluginId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__currentItem = item;
|
||||||
|
item.parent = content;
|
||||||
|
item.anchors.fill = content;
|
||||||
|
item.visible = true;
|
||||||
|
|
||||||
|
popup.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
if (__currentItem) {
|
||||||
|
__currentItem.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enter: Transition {
|
||||||
|
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
||||||
|
NumberAnimation { property: "scale"; from: 0.9; to: 1; duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
exit: Transition {
|
||||||
|
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: Kirigami.Units.shortDuration; easing.type: Easing.InCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
|
||||||
|
border.color: Kirigami.ColorUtils.linearInterpolation(
|
||||||
|
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
shadow.size: Kirigami.Units.gridUnit
|
||||||
|
shadow.color: Qt.rgba(0, 0, 0, 0.45)
|
||||||
|
shadow.yOffset: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
id: content
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Overlay.modal: Rectangle {
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -109,6 +109,9 @@ Item {
|
||||||
onCloseRequested: {
|
onCloseRequested: {
|
||||||
actionDrawer.close();
|
actionDrawer.close();
|
||||||
}
|
}
|
||||||
|
onDetailRequested: (pluginId) => {
|
||||||
|
detailPopup.show(pluginId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,6 +176,9 @@ Item {
|
||||||
onCloseRequested: {
|
onCloseRequested: {
|
||||||
actionDrawer.close();
|
actionDrawer.close();
|
||||||
}
|
}
|
||||||
|
onDetailRequested: (pluginId) => {
|
||||||
|
detailPopup.show(pluginId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,4 +229,9 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DetailPopup {
|
||||||
|
id: detailPopup
|
||||||
|
parent: root.Window.window ? root.Window.window.contentItem : root
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import org.kde.plasma.core as PlasmaCore
|
||||||
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
|
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
|
||||||
import org.kde.plasma.private.mobileshell as MobileShell
|
import org.kde.plasma.private.mobileshell as MobileShell
|
||||||
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
||||||
|
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||||
|
|
||||||
MobileShell.BaseItem {
|
MobileShell.BaseItem {
|
||||||
|
|
@ -30,6 +31,7 @@ MobileShell.BaseItem {
|
||||||
required property var toggleFunction
|
required property var toggleFunction
|
||||||
|
|
||||||
signal closeRequested()
|
signal closeRequested()
|
||||||
|
signal detailRequested(string pluginId)
|
||||||
|
|
||||||
// set by children
|
// set by children
|
||||||
property var iconItem
|
property var iconItem
|
||||||
|
|
@ -88,7 +90,25 @@ MobileShell.BaseItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map quick-setting settingsCommand → desktop Plasma applet pluginId.
|
||||||
|
// Only tiles listed here get an inline detail popup in convergence mode.
|
||||||
|
readonly property var __appletForCommand: ({
|
||||||
|
"plasma-open-settings kcm_mobile_wifi": "org.kde.plasma.networkmanagement",
|
||||||
|
"plasma-open-settings kcm_bluetooth": "org.kde.plasma.bluetooth",
|
||||||
|
"plasma-open-settings kcm_pulseaudio": "org.kde.plasma.volume",
|
||||||
|
"plasma-open-settings kcm_mobile_power": "org.kde.plasma.battery",
|
||||||
|
})
|
||||||
|
|
||||||
function delegatePressAndHold() {
|
function delegatePressAndHold() {
|
||||||
|
// In convergence mode, show inline detail popup if available.
|
||||||
|
if (ShellSettings.Settings.convergenceModeEnabled && root.settingsCommand) {
|
||||||
|
let pluginId = __appletForCommand[root.settingsCommand];
|
||||||
|
if (pluginId) {
|
||||||
|
root.detailRequested(pluginId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (root.settingsCommand && !root.restrictedPermissions) {
|
if (root.settingsCommand && !root.restrictedPermissions) {
|
||||||
closeRequested();
|
closeRequested();
|
||||||
MobileShellState.ShellDBusClient.openAppLaunchAnimationWithPosition(
|
MobileShellState.ShellDBusClient.openAppLaunchAnimationWithPosition(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue