mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 00:47:22 +00:00
Promote desktop utilities in action drawer
Reshape the convergence quick settings into a compact promoted grid and add Clipboard, Disks & Devices, and a drawer-hosted System Tray popup. Retry applet popups when their full representation becomes ready so the desktop utilities open reliably.
This commit is contained in:
parent
85b5aec742
commit
c5582393bc
5 changed files with 332 additions and 16 deletions
|
|
@ -141,6 +141,7 @@ ecm_target_qml_sources(mobileshellplugin SOURCES
|
|||
qml/actiondrawer/private/QuickSettingsPanel.qml
|
||||
qml/actiondrawer/private/ContentContainer.qml
|
||||
qml/actiondrawer/private/DetailPopup.qml
|
||||
qml/actiondrawer/private/SystemTrayPopup.qml
|
||||
qml/actiondrawer/private/LandscapeContentContainer.qml
|
||||
qml/actiondrawer/private/NotificationDrawer.qml
|
||||
qml/actiondrawer/private/PortraitContentContainer.qml
|
||||
|
|
|
|||
|
|
@ -55,6 +55,16 @@ QQC2.Popup {
|
|||
popup.open();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: MobileShell.AppletHost
|
||||
|
||||
function onAppletReady(pluginId) {
|
||||
if (pluginId === popup.currentPluginId && !popup.opened) {
|
||||
popup.show(pluginId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (__currentItem) {
|
||||
__currentItem.visible = false;
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ Item {
|
|||
readonly property int columnCount: Math.floor(width/columnWidth)
|
||||
readonly property int rowCount: {
|
||||
let totalRows = Math.ceil(quickSettingsCount / columnCount);
|
||||
let maxRows = 5; // more than 5 is just disorienting
|
||||
let targetRows = Math.floor(Window.height * 0.65 / rowHeight);
|
||||
return Math.min(maxRows, Math.min(totalRows, targetRows));
|
||||
let maxRows = root.isConvergence ? 3 : 5; // more than 5 is just disorienting
|
||||
let targetRows = Math.floor(Window.height * (root.isConvergence ? 0.42 : 0.65) / rowHeight);
|
||||
return Math.max(1, Math.min(maxRows, Math.min(totalRows, targetRows)));
|
||||
}
|
||||
|
||||
readonly property int pageSize: rowCount * columnCount
|
||||
|
|
@ -63,6 +63,10 @@ Item {
|
|||
})
|
||||
readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled
|
||||
function isManagementTile(cmd) { return cmd in __managementCommands; }
|
||||
readonly property int promotedColumns: isConvergence && width >= Kirigami.Units.gridUnit * 18 ? 2 : 1
|
||||
readonly property real promotedSpacing: Kirigami.Units.smallSpacing
|
||||
readonly property real promotedHorizontalMargin: Kirigami.Units.smallSpacing
|
||||
readonly property real promotedCellWidth: Math.floor((width - 2 * promotedHorizontalMargin - (promotedColumns - 1) * promotedSpacing) / promotedColumns)
|
||||
|
||||
readonly property alias brightnessPressedValue: brightnessItem.brightnessPressedValue
|
||||
|
||||
|
|
@ -136,21 +140,72 @@ Item {
|
|||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
// Management status rows (convergence mode only)
|
||||
ColumnLayout {
|
||||
// Promoted desktop controls (convergence mode only)
|
||||
GridLayout {
|
||||
id: promotedGrid
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
Layout.leftMargin: root.promotedHorizontalMargin
|
||||
Layout.rightMargin: root.promotedHorizontalMargin
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
columns: root.promotedColumns
|
||||
rowSpacing: root.promotedSpacing
|
||||
columnSpacing: root.promotedSpacing
|
||||
visible: root.isConvergence
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
text: i18n("Clipboard"),
|
||||
status: i18n("History"),
|
||||
icon: "klipper-symbolic",
|
||||
pluginId: "org.kde.plasma.clipboard"
|
||||
},
|
||||
{
|
||||
text: i18n("Disks & Devices"),
|
||||
status: i18n("Removable media"),
|
||||
icon: "device-notifier-symbolic",
|
||||
pluginId: "org.kde.plasma.devicenotifier"
|
||||
},
|
||||
{
|
||||
text: i18n("System Tray"),
|
||||
status: systemTrayPopup.trayItemCount > 0 ? i18np("%1 status item", "%1 status items", systemTrayPopup.trayItemCount) : i18n("No status items"),
|
||||
icon: "preferences-desktop-notification-symbolic",
|
||||
trayPopup: true
|
||||
}
|
||||
]
|
||||
|
||||
delegate: QuickSettingsStatusRow {
|
||||
required property var modelData
|
||||
Layout.preferredWidth: root.promotedCellWidth
|
||||
Layout.fillWidth: true
|
||||
compact: true
|
||||
text: modelData.text
|
||||
status: modelData.status
|
||||
icon: modelData.icon
|
||||
enabled: false
|
||||
toggleFunction: null
|
||||
onDetailClicked: {
|
||||
if (modelData.trayPopup) {
|
||||
systemTrayPopup.show();
|
||||
} else {
|
||||
detailPopup.show(modelData.pluginId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.quickSettingsModel
|
||||
delegate: QuickSettingsStatusRow {
|
||||
required property var modelData
|
||||
readonly property bool isPromoted: root.isManagementTile(modelData.settingsCommand)
|
||||
Layout.preferredWidth: isPromoted ? root.promotedCellWidth : 0
|
||||
Layout.preferredHeight: isPromoted ? implicitHeight : 0
|
||||
Layout.maximumWidth: isPromoted ? root.promotedCellWidth : 0
|
||||
Layout.maximumHeight: isPromoted ? implicitHeight : 0
|
||||
Layout.fillWidth: true
|
||||
visible: root.isManagementTile(modelData.settingsCommand)
|
||||
visible: isPromoted
|
||||
compact: true
|
||||
text: modelData.text
|
||||
status: modelData.status
|
||||
icon: modelData.icon
|
||||
|
|
@ -286,4 +341,9 @@ Item {
|
|||
parent: root.Window.window ? root.Window.window.contentItem : root
|
||||
}
|
||||
|
||||
SystemTrayPopup {
|
||||
id: systemTrayPopup
|
||||
parent: root.Window.window ? root.Window.window.contentItem : root
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import org.kde.plasma.private.mobileshell as MobileShell
|
|||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
|
||||
/**
|
||||
* Full-width management row (Wi-Fi, Bluetooth, Audio, Battery) shown in
|
||||
* convergence mode. Two interaction zones:
|
||||
* Management/detail row shown in convergence mode. Two interaction zones:
|
||||
* - Left toggle pill: icon + indicator dot, tap toggles the service.
|
||||
* - Right detail area: name + status + chevron, tap opens detail popup.
|
||||
*/
|
||||
|
|
@ -22,10 +21,11 @@ Item {
|
|||
required property string icon
|
||||
required property bool enabled
|
||||
required property var toggleFunction
|
||||
property bool compact: false
|
||||
|
||||
signal detailClicked()
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 3.6
|
||||
implicitHeight: Kirigami.Units.gridUnit * (compact ? 3.1 : 3.6)
|
||||
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||
|
|
@ -35,6 +35,7 @@ Item {
|
|||
readonly property color enabledBgHover: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.32)
|
||||
readonly property color enabledBgPressed: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.12)
|
||||
readonly property color enabledBorder: Qt.darker(Kirigami.Theme.highlightColor, 1.25)
|
||||
readonly property bool hasToggle: toggleFunction !== null && toggleFunction !== undefined
|
||||
|
||||
readonly property color disabledBg: Kirigami.Theme.alternateBackgroundColor
|
||||
readonly property color disabledBgHover: mixColor(Kirigami.Theme.alternateBackgroundColor, Kirigami.Theme.textColor, 0.06)
|
||||
|
|
@ -146,8 +147,10 @@ Item {
|
|||
// Indicator bar
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: root.hasToggle
|
||||
Layout.preferredHeight: visible ? Math.max(2, Math.round(Kirigami.Units.devicePixelRatio)) : 0
|
||||
width: root.enabled ? Kirigami.Units.smallSpacing * 3 : Kirigami.Units.smallSpacing * 1.5
|
||||
height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio))
|
||||
height: Layout.preferredHeight
|
||||
radius: height / 2
|
||||
color: root.enabled ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
|
||||
opacity: root.enabled ? 1.0 : 0.4
|
||||
|
|
@ -162,10 +165,14 @@ Item {
|
|||
id: toggleMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: haptics.buttonVibrate()
|
||||
cursorShape: root.hasToggle ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onPressed: {
|
||||
if (root.hasToggle) {
|
||||
haptics.buttonVibrate()
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
if (root.toggleFunction) root.toggleFunction();
|
||||
if (root.hasToggle) root.toggleFunction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.1
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.private.mobileshell as MobileShell
|
||||
import org.kde.plasma.private.systemtray as SystemTray
|
||||
|
||||
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 * 22,
|
||||
parent ? parent.width - Kirigami.Units.gridUnit * 4 : 420)
|
||||
height: Math.min(Kirigami.Units.gridUnit * 24,
|
||||
parent ? parent.height - Kirigami.Units.gridUnit * 4 : 480)
|
||||
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
|
||||
readonly property int trayItemCount: trayList.count
|
||||
|
||||
function show() {
|
||||
popup.open();
|
||||
}
|
||||
|
||||
SystemTray.StatusNotifierModel {
|
||||
id: trayModel
|
||||
}
|
||||
|
||||
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: ColumnLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitHeight: implicitWidth
|
||||
source: "preferences-desktop-notification-symbolic"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("System Tray")
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: trayList.count > 0 ? i18np("%1 status item", "%1 status items", trayList.count) : i18n("No status items")
|
||||
opacity: 0.65
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Kirigami.ColorUtils.linearInterpolation(
|
||||
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.12)
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: trayList
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
model: trayModel
|
||||
|
||||
delegate: Item {
|
||||
id: trayItem
|
||||
|
||||
width: ListView.view.width
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
|
||||
readonly property string itemTitle: model.toolTipTitle ? model.toolTipTitle : (model.title ? model.title : i18n("Status Item"))
|
||||
readonly property string itemStatus: {
|
||||
if (model.status === "Passive") {
|
||||
return i18n("Hidden");
|
||||
}
|
||||
if (model.category === "ApplicationStatus") {
|
||||
return i18n("Application status");
|
||||
}
|
||||
return model.status ? model.status : i18n("Active");
|
||||
}
|
||||
readonly property bool itemActive: model.category !== "ApplicationStatus" && model.status !== "Passive"
|
||||
|
||||
function triggerOperation(operationName) {
|
||||
if (!model.service) {
|
||||
return;
|
||||
}
|
||||
|
||||
let operation = model.service.operationDescription(operationName);
|
||||
if (!operation) {
|
||||
return;
|
||||
}
|
||||
|
||||
let operationPoint = trayItem.mapToGlobal(trayItem.width, trayItem.height / 2);
|
||||
operation.x = operationPoint.x;
|
||||
operation.y = operationPoint.y;
|
||||
model.service.startOperationCall(operation);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rowBackground
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
color: trayMouse.pressed ? Qt.rgba(Kirigami.Theme.textColor.r,
|
||||
Kirigami.Theme.textColor.g,
|
||||
Kirigami.Theme.textColor.b, 0.08)
|
||||
: trayMouse.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r,
|
||||
Kirigami.Theme.textColor.g,
|
||||
Kirigami.Theme.textColor.b, 0.04)
|
||||
: Kirigami.Theme.alternateBackgroundColor
|
||||
border.width: 1
|
||||
border.color: Kirigami.ColorUtils.linearInterpolation(
|
||||
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, trayItem.itemActive ? 0.16 : 0.08)
|
||||
opacity: trayItem.itemActive ? 1 : 0.72
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: rowBackground
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
anchors.rightMargin: Kirigami.Units.smallSpacing * 2
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitHeight: implicitWidth
|
||||
source: model.iconName ? model.iconName : (model.icon ? model.icon : "")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 2
|
||||
|
||||
MobileShell.MarqueeLabel {
|
||||
Layout.fillWidth: true
|
||||
inputText: trayItem.itemTitle
|
||||
font.weight: Font.Bold
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
|
||||
}
|
||||
|
||||
MobileShell.MarqueeLabel {
|
||||
Layout.fillWidth: true
|
||||
inputText: trayItem.itemStatus
|
||||
opacity: 0.6
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Icon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
implicitHeight: implicitWidth
|
||||
source: "go-next-symbolic"
|
||||
opacity: 0.45
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: trayItem.itemTitle
|
||||
QQC2.ToolTip.visible: trayMouse.containsMouse && trayItem.itemTitle !== ""
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
MouseArea {
|
||||
id: trayMouse
|
||||
anchors.fill: rowBackground
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: (mouse) => {
|
||||
trayItem.triggerOperation(mouse.button === Qt.RightButton ? "ContextMenu" : "Activate");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
anchors.centerIn: parent
|
||||
visible: trayList.count === 0
|
||||
text: i18n("No status items")
|
||||
opacity: 0.65
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
QQC2.Overlay.modal: Rectangle {
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue