From 9bd4f0b74798b5764d249a8275052ecf0d7bbb04 Mon Sep 17 00:00:00 2001 From: Micah Stanley Date: Mon, 21 Apr 2025 22:09:14 +0000 Subject: [PATCH] GestureNavigation: Screen Rotation Popup Button Implementation of a popup button to rotate the screen while using gesture navigation. The button is set to appear when the device rotates while auto rotation is off. Then the button will be visible for a short period of time before disappearing. --- components/CMakeLists.txt | 1 + .../qml/popups/PopupProviderLoader.qml | 8 + .../qml/popups/actionbuttons/ActionButton.qml | 186 ++++++++++++++++++ .../actionbuttons/ActionButtonsProvider.qml | 21 ++ .../popups/actionbuttons/RotationButton.qml | 63 ++++++ components/rotationplugin/CMakeLists.txt | 18 ++ components/rotationplugin/rotationutil.cpp | 158 +++++++++++++++ components/rotationplugin/rotationutil.h | 57 ++++++ .../panel/package/contents/ui/main.qml | 5 +- .../contents/ui/NavigationPanelComponent.qml | 5 +- containments/taskpanel/taskpanel.cpp | 114 ----------- containments/taskpanel/taskpanel.h | 21 -- 12 files changed, 518 insertions(+), 139 deletions(-) create mode 100644 components/mobileshell/qml/popups/actionbuttons/ActionButton.qml create mode 100644 components/mobileshell/qml/popups/actionbuttons/ActionButtonsProvider.qml create mode 100644 components/mobileshell/qml/popups/actionbuttons/RotationButton.qml create mode 100644 components/rotationplugin/CMakeLists.txt create mode 100644 components/rotationplugin/rotationutil.cpp create mode 100644 components/rotationplugin/rotationutil.h diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c88fcc1c..8930097f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(mmplugin) add_subdirectory(mobileshell) add_subdirectory(mobileshellstate) add_subdirectory(quicksettingsplugin) +add_subdirectory(rotationplugin) add_subdirectory(windowplugin) add_subdirectory(shellsettingsplugin) add_subdirectory(wallpaperimageplugin) diff --git a/components/mobileshell/qml/popups/PopupProviderLoader.qml b/components/mobileshell/qml/popups/PopupProviderLoader.qml index a343ab3a..07d83ba9 100644 --- a/components/mobileshell/qml/popups/PopupProviderLoader.qml +++ b/components/mobileshell/qml/popups/PopupProviderLoader.qml @@ -22,6 +22,7 @@ Item { function load() { volumeOSD.active = true; notifications.active = true; + actionButtons.active = true; } Loader { @@ -37,4 +38,11 @@ Item { MobileShell.NotificationPopupProvider {} } } + + Loader { + id: actionButtons + sourceComponent: Component { + MobileShell.ActionButtonsProvider {} + } + } } diff --git a/components/mobileshell/qml/popups/actionbuttons/ActionButton.qml b/components/mobileshell/qml/popups/actionbuttons/ActionButton.qml new file mode 100644 index 00000000..6b33331c --- /dev/null +++ b/components/mobileshell/qml/popups/actionbuttons/ActionButton.qml @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2025 Micah Stanley + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls as Controls +import QtQuick.Layouts +import QtQuick.Effects + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.private.mobileshell.state as MobileShellState +import org.kde.plasma.private.mobileshell as MobileShell + +import org.kde.layershell 1.0 as LayerShell + + +Window { + id: root + + readonly property int size: Kirigami.Units.gridUnit * 2 + readonly property int margins: Math.round(Kirigami.Units.largeSpace * 0.5) + + property int screenCorner: ActionButton.ScreenCorner.BottomRight + property int angle: 0 + property string iconSource + property bool active: false + + signal triggered() + + enum ScreenCorner { + BottomRight, + BottomLeft, + TopLeft, + TopRight + } + + // When the button is animating its disappearance, make sure it is transparent to inputs. + onActiveChanged: { + ShellUtil.setInputTransparent(root, !active) + if (active) { + root.visible = true; + root.raise(); + hideButton.stop(); + return; + } + hideButton.restart(); + } + + LayerShell.Window.scope: "overlay" + LayerShell.Window.margins.top: margins + LayerShell.Window.margins.bottom: margins + LayerShell.Window.margins.left: margins + LayerShell.Window.margins.right: margins + LayerShell.Window.layer: LayerShell.Window.LayerOverlay + LayerShell.Window.exclusionZone: -1 + LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone + LayerShell.Window.anchors: { + if (screenCorner === ActionButton.ScreenCorner.TopLeft) { + return LayerShell.Window.AnchorTop | LayerShell.Window.AnchorLeft + } else if (screenCorner === ActionButton.ScreenCorner.BottomRight) { + return LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorRight + } else if (screenCorner === ActionButton.ScreenCorner.BottomLeft) { + return LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft + } else { + return LayerShell.Window.AnchorTop | LayerShell.Window.AnchorRight + } + } + + Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Theme.inherit: false + + // Double the set button size to leave room for button scale animation. + width: size * 2 + height: size * 2 + + visible: active + + color: "transparent" + + // Hide the root window after the button disappearing animation finishes. + Timer { + id: hideButton + interval: Kirigami.Units.longDuration + repeat: false + onTriggered: if (!active) root.visible = false; + } + + Component.onCompleted: { + // Because the window surface area had to be made larger to accommodate the button scale animation, + // set the input region to the size of the actual button. + ShellUtil.setInputRegion(root, Qt.rect((root.width - size) / 2, (root.height - size) / 2, size, size)); + ShellUtil.setInputTransparent(root, !active); + } + + Controls.Control { + id: content + anchors.centerIn: parent + width: root.size + height: root.size + opacity: root.active ? 1 : 0 + + property double scale: !root.active ? 0.5 : (button.pressed ? 1.5 : 1) + + Behavior on scale { + NumberAnimation { + duration: Kirigami.Units.longDuration + easing.type: Easing.OutBack + } + } + + Behavior on opacity { + NumberAnimation { + duration: Kirigami.Units.longDuration + easing.type: Easing.OutCirc + } + } + + transform: Scale { + origin.x: root.size / 2 + origin.y: root.size / 2 + xScale: content.scale + yScale: content.scale + } + + MultiEffect { + anchors.fill: parent + source: simpleShadow + blurMax: 16 + shadowEnabled: true + shadowVerticalOffset: 1 + shadowOpacity: 0.85 + shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.2) + } + + Rectangle { + id: simpleShadow + anchors.fill: parent + anchors.leftMargin: -1 + anchors.rightMargin: -1 + anchors.bottomMargin: -1 + + color: { + let darkerBackgroundColor = Qt.darker(Kirigami.Theme.backgroundColor, 1.3); + return Qt.rgba(darkerBackgroundColor.r, darkerBackgroundColor.g, darkerBackgroundColor.b, 0.5) + } + radius: root.size + } + + Rectangle { + anchors.fill: parent + color: Qt.lighter(Kirigami.Theme.backgroundColor, 1.5) + radius: root.size + opacity: 0.85 + } + + Controls.AbstractButton { + id: button + anchors.fill: parent + + MobileShell.HapticsEffect { + id: haptics + } + + contentItem: Item { + Kirigami.Icon { + anchors.centerIn: parent + width: Kirigami.Units.iconSizes.small + height: Kirigami.Units.iconSizes.small + transformOrigin: Item.Center + rotation: root.angle + source: root.iconSource + } + } + + onPressed: { + haptics.buttonVibrate(); + } + + onReleased: { + if (active) root.triggered(); + } + } + } +} diff --git a/components/mobileshell/qml/popups/actionbuttons/ActionButtonsProvider.qml b/components/mobileshell/qml/popups/actionbuttons/ActionButtonsProvider.qml new file mode 100644 index 00000000..72e79fbb --- /dev/null +++ b/components/mobileshell/qml/popups/actionbuttons/ActionButtonsProvider.qml @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025 Micah Stanley + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Layouts + +import org.kde.plasma.private.mobileshell.state as MobileShellState +import org.kde.plasma.private.mobileshell as MobileShell + +/** + * This sets up the popup action buttons. + */ +QtObject { + id: component + + property var rotationButton: RotationButton {} +} + diff --git a/components/mobileshell/qml/popups/actionbuttons/RotationButton.qml b/components/mobileshell/qml/popups/actionbuttons/RotationButton.qml new file mode 100644 index 00000000..c0c7b231 --- /dev/null +++ b/components/mobileshell/qml/popups/actionbuttons/RotationButton.qml @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2025 Micah Stanley + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Layouts + +import org.kde.plasma.private.mobileshell.rotationplugin as RotationPlugin +import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings + +ActionButton { + id: root + + readonly property int deviceRotation: RotationPlugin.RotationUtil.deviceRotation + readonly property int currentRotation: RotationPlugin.RotationUtil.currentRotation + + iconSource: "rotation-allowed-symbolic" + + // Update button position and timeout when device rotation changes. + onDeviceRotationChanged: { + if (ShellSettings.Settings.navigationPanelEnabled) return; + // reset button if visible + if (root.visible) { + root.active = false; + timeout.stop(); + } + if (!RotationPlugin.RotationUtil.showRotationButton) return; + // Position at the bottom left edge of actual device, regardless of current rotation. + root.screenCorner = (currentRotation + 1) % 4; + // match angle to physical device rotation. + root.angle = ((4 + currentRotation - deviceRotation) % 4) * 90; + root.active = true; + } + + // Rotate to suggested rotation if button is pressed. + onTriggered: { + root.visible = false; + root.active = false; + timeout.stop(); + rotate.restart(); + } + + // rotate on timeout to give time to hide the button before rotation happens + Timer { + id: rotate + interval: 0 + repeat: false + onTriggered: RotationPlugin.RotationUtil.rotateToSuggestedRotation(); + } + + // When the button is active, hide it after a certain amount of time has passed. + // This is to prevent the button form bothering the user when they do not wish to rotate. + onActiveChanged: if (active) timeout.restart(); + + Timer { + id: timeout + interval: 10000 + repeat: false + onTriggered: active = false; + } +} diff --git a/components/rotationplugin/CMakeLists.txt b/components/rotationplugin/CMakeLists.txt new file mode 100644 index 00000000..843494c8 --- /dev/null +++ b/components/rotationplugin/CMakeLists.txt @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2025 Micah Stanley +# SPDX-License-Identifier: GPL-2.0-or-later + +ecm_add_qml_module(rotationplugin URI org.kde.plasma.private.mobileshell.rotationplugin GENERATE_PLUGIN_SOURCE) +target_sources(rotationplugin PRIVATE rotationutil.cpp) + +target_link_libraries(rotationplugin PRIVATE + Qt::Gui + Qt::DBus + Qt::Qml + Qt::Quick + Qt::Sensors + Plasma::Plasma + KF6::Screen + Qt::Qml +) + +ecm_finalize_qml_module(rotationplugin) \ No newline at end of file diff --git a/components/rotationplugin/rotationutil.cpp b/components/rotationplugin/rotationutil.cpp new file mode 100644 index 00000000..51d9ccb6 --- /dev/null +++ b/components/rotationplugin/rotationutil.cpp @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: 2025 Micah Stanley + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "rotationutil.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +KScreen::Output::Rotation mapReadingOrientation(QOrientationReading::Orientation orientation) +{ + switch (orientation) { + case QOrientationReading::Orientation::TopUp: + return KScreen::Output::Rotation::None; + case QOrientationReading::Orientation::TopDown: + return KScreen::Output::Rotation::Inverted; + case QOrientationReading::Orientation::LeftUp: + return KScreen::Output::Rotation::Left; + case QOrientationReading::Orientation::RightUp: + return KScreen::Output::Rotation::Right; + case QOrientationReading::Orientation::FaceUp: + case QOrientationReading::Orientation::FaceDown: + case QOrientationReading::Orientation::Undefined: + return KScreen::Output::Rotation::None; + } + return KScreen::Output::Rotation::None; +} + +RotationUtil::Rotation mapRotation(KScreen::Output::Rotation rotation) +{ + switch (rotation) { + case KScreen::Output::Rotation::Left: + return RotationUtil::Rotation::LandscapeLeft; + case KScreen::Output::Rotation::Inverted: + return RotationUtil::Rotation::UpsideDown; + case KScreen::Output::Rotation::Right: + return RotationUtil::Rotation::LandscapeRight; + default: + return RotationUtil::Rotation::Portrait; + } +} + +RotationUtil::RotationUtil(QObject *parent) +: QObject{parent} +, m_sensor{new QOrientationSensor(this)} +{ + connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, [this](auto *op) { + m_config = qobject_cast(op)->config(); + KScreen::ConfigMonitor::instance()->addConfig(m_config); + + // update all screens with event connect + for (KScreen::OutputPtr output : m_config->outputs()) { + connect(output.data(), &KScreen::Output::autoRotatePolicyChanged, this, &RotationUtil::updateShowRotationButton); + } + + // listen to all new screens and connect + connect(m_config.data(), &KScreen::Config::outputAdded, this, [this](const auto &output) { + connect(output.data(), &KScreen::Output::autoRotatePolicyChanged, this, &RotationUtil::updateShowRotationButton); + }); + }); + + connect(m_sensor, &QOrientationSensor::readingChanged, this, &RotationUtil::updateShowRotationButton); + m_sensor->start(); +} + +void RotationUtil::rotateToSuggestedRotation() +{ + if (!m_config || !m_showRotationButton) { + return; + } + + const auto outputs = m_config->outputs(); + if (outputs.empty()) { + return; + } + + // HACK: Assume the output we care about is the first device + for (KScreen::OutputPtr output : outputs) { + // apparently it's possible to get nullptr outputs? + if (!output) { + continue; + } + + output->setRotation(m_rotateTo); + } + + auto setop = new KScreen::SetConfigOperation(m_config, this); + setop->exec(); + + updateShowRotationButton(); +} + +bool RotationUtil::showRotationButton() const +{ + return m_showRotationButton; +} + +RotationUtil::Rotation RotationUtil::deviceRotation() const +{ + return m_deviceRotation; +} + +RotationUtil::Rotation RotationUtil::currentRotation() const +{ + return m_currentRotation; +} + +void RotationUtil::updateShowRotationButton() +{ + if (!m_config) { + return; + } + + QOrientationReading *reading = m_sensor->reading(); + if (!reading) { + return; + } + + m_rotateTo = mapReadingOrientation(reading->orientation()); + m_deviceRotation = mapRotation(m_rotateTo); + + const auto outputs = m_config->outputs(); + + if (outputs.empty()) { + m_showRotationButton = false; + Q_EMIT rotationChanged(); + return; + } + + // HACK: Assume the output we care about is the first device + for (KScreen::OutputPtr output : outputs) { + if (!output) { + // apparently it's possible to get nullptr outputs? + continue; + } + if (output->autoRotatePolicy() != KScreen::Output::AutoRotatePolicy::Never) { + // only check displays that have autorotate on + continue; + } + m_currentRotation = mapRotation(output->rotation()); + m_showRotationButton = output->rotation() != m_rotateTo; + Q_EMIT rotationChanged(); + return; + } + + m_showRotationButton = false; + Q_EMIT rotationChanged(); +} \ No newline at end of file diff --git a/components/rotationplugin/rotationutil.h b/components/rotationplugin/rotationutil.h new file mode 100644 index 00000000..8a9af36b --- /dev/null +++ b/components/rotationplugin/rotationutil.h @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2025 Micah Stanley + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include + +#include +#include +#include + +class RotationUtil : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + + Q_PROPERTY(bool showRotationButton READ showRotationButton NOTIFY rotationChanged) + Q_PROPERTY(Rotation deviceRotation READ deviceRotation NOTIFY rotationChanged) + Q_PROPERTY(Rotation currentRotation READ currentRotation NOTIFY rotationChanged) + +public: + RotationUtil(QObject *parent = nullptr); + + enum Rotation { + Portrait = 0, + LandscapeLeft, + UpsideDown, + LandscapeRight + }; + Q_ENUM(Rotation) + + bool showRotationButton() const; + Rotation deviceRotation() const; + Rotation currentRotation() const; + + Q_INVOKABLE void rotateToSuggestedRotation(); + +Q_SIGNALS: + void rotationChanged(); + +private Q_SLOTS: + void updateShowRotationButton(); + +private: + bool m_showRotationButton{false}; + KScreen::Output::Rotation m_rotateTo; + Rotation m_deviceRotation; + Rotation m_currentRotation; + + KScreen::ConfigPtr m_config{nullptr}; + QOrientationSensor *m_sensor{nullptr}; +}; diff --git a/containments/panel/package/contents/ui/main.qml b/containments/panel/package/contents/ui/main.qml index b0b2bb18..ed55ba37 100644 --- a/containments/panel/package/contents/ui/main.qml +++ b/containments/panel/package/contents/ui/main.qml @@ -141,8 +141,9 @@ ContainmentItem { MobileShellState.ShellDBusObject.registerObject(); // HACK: we need to initialize the DBus server somewhere, it might as well be here... - // initialize the volume osd, and volume keys - // initialize notification popups + // Initialize the volume osd, and volume keys. + // Initialize notification popups. + // Initialize action popup buttons. MobileShell.PopupProviderLoader.load(); } diff --git a/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml b/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml index edd0d89f..045ef199 100644 --- a/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml +++ b/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml @@ -14,6 +14,7 @@ import org.kde.plasma.private.mobileshell.state as MobileShellState import org.kde.taskmanager as TaskManager import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings +import org.kde.plasma.private.mobileshell.rotationplugin as RotationPlugin import org.kde.kirigami as Kirigami @@ -105,13 +106,13 @@ MobileShell.NavigationPanel { leftCornerAction: MobileShell.NavigationPanelAction { id: rotationAction - visible: Plasmoid.showRotationButton + visible: RotationPlugin.RotationUtil.showRotationButton enabled: true iconSource: "rotation-allowed-symbolic" iconSizeFactor: 0.75 onTriggered: { - Plasmoid.rotateToSuggestedRotation(); + RotationPlugin.RotationUtil.rotateToSuggestedRotation(); } } diff --git a/containments/taskpanel/taskpanel.cpp b/containments/taskpanel/taskpanel.cpp index 61a4bbb1..dc88d042 100644 --- a/containments/taskpanel/taskpanel.cpp +++ b/containments/taskpanel/taskpanel.cpp @@ -11,54 +11,13 @@ #include #include -#include -#include -#include -#include - // register type for Keyboards.KWinVirtualKeyboard.forceActivate(); Q_DECLARE_METATYPE(QDBusPendingReply<>) -KScreen::Output::Rotation mapReadingOrientation(QOrientationReading::Orientation orientation) -{ - switch (orientation) { - case QOrientationReading::Orientation::TopUp: - return KScreen::Output::Rotation::None; - case QOrientationReading::Orientation::TopDown: - return KScreen::Output::Rotation::Inverted; - case QOrientationReading::Orientation::LeftUp: - return KScreen::Output::Rotation::Left; - case QOrientationReading::Orientation::RightUp: - return KScreen::Output::Rotation::Right; - case QOrientationReading::Orientation::FaceUp: - case QOrientationReading::Orientation::FaceDown: - case QOrientationReading::Orientation::Undefined: - return KScreen::Output::Rotation::None; - } - return KScreen::Output::Rotation::None; -} - TaskPanel::TaskPanel(QObject *parent, const KPluginMetaData &data, const QVariantList &args) : Plasma::Containment(parent, data, args) - , m_sensor{new QOrientationSensor(this)} { - connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, [this](auto *op) { - m_config = qobject_cast(op)->config(); - KScreen::ConfigMonitor::instance()->addConfig(m_config); - // update all screens with event connect - for (KScreen::OutputPtr output : m_config->outputs()) { - connect(output.data(), &KScreen::Output::autoRotatePolicyChanged, this, &TaskPanel::updateShowRotationButton); - } - - // listen to all new screens and connect - connect(m_config.data(), &KScreen::Config::outputAdded, this, [this](const auto &output) { - connect(output.data(), &KScreen::Output::autoRotatePolicyChanged, this, &TaskPanel::updateShowRotationButton); - }); - }); - - connect(m_sensor, &QOrientationSensor::readingChanged, this, &TaskPanel::updateShowRotationButton); - m_sensor->start(); } void TaskPanel::triggerTaskSwitcher() const @@ -70,79 +29,6 @@ void TaskPanel::triggerTaskSwitcher() const QDBusConnection::sessionBus().send(message); } -void TaskPanel::rotateToSuggestedRotation() -{ - if (!m_config || !m_showRotationButton) { - return; - } - - const auto outputs = m_config->outputs(); - if (outputs.empty()) { - return; - } - - // HACK: Assume the output we care about is the first device - for (KScreen::OutputPtr output : outputs) { - // apparently it's possible to get nullptr outputs? - if (!output) { - continue; - } - - output->setRotation(m_rotateTo); - } - - auto setop = new KScreen::SetConfigOperation(m_config, this); - setop->exec(); - - updateShowRotationButton(); -} - -bool TaskPanel::showRotationButton() const -{ - return m_showRotationButton; -} - -void TaskPanel::updateShowRotationButton() -{ - if (!m_config) { - return; - } - - QOrientationReading *reading = m_sensor->reading(); - if (!reading) { - return; - } - - m_rotateTo = mapReadingOrientation(reading->orientation()); - - const auto outputs = m_config->outputs(); - - if (outputs.empty()) { - m_showRotationButton = false; - Q_EMIT showRotationButtonChanged(); - return; - } - - // HACK: Assume the output we care about is the first device - for (KScreen::OutputPtr output : outputs) { - if (!output) { - // apparently it's possible to get nullptr outputs? - continue; - } - if (output->autoRotatePolicy() != KScreen::Output::AutoRotatePolicy::Never) { - // only check displays that have autorotate on - continue; - } - - m_showRotationButton = output->rotation() != m_rotateTo; - Q_EMIT showRotationButtonChanged(); - return; - } - - m_showRotationButton = false; - Q_EMIT showRotationButtonChanged(); -} - K_PLUGIN_CLASS(TaskPanel) #include "taskpanel.moc" diff --git a/containments/taskpanel/taskpanel.h b/containments/taskpanel/taskpanel.h index 9a17509c..6bf4552a 100644 --- a/containments/taskpanel/taskpanel.h +++ b/containments/taskpanel/taskpanel.h @@ -7,33 +7,12 @@ #pragma once #include -#include - -#include class TaskPanel : public Plasma::Containment { Q_OBJECT - Q_PROPERTY(bool showRotationButton READ showRotationButton NOTIFY showRotationButtonChanged) public: TaskPanel(QObject *parent, const KPluginMetaData &data, const QVariantList &args); - Q_INVOKABLE void triggerTaskSwitcher() const; - - bool showRotationButton() const; - Q_INVOKABLE void rotateToSuggestedRotation(); - -Q_SIGNALS: - void showRotationButtonChanged(); - -private Q_SLOTS: - void updateShowRotationButton(); - -private: - bool m_showRotationButton{false}; - KScreen::Output::Rotation m_rotateTo; - - KScreen::ConfigPtr m_config{nullptr}; - QOrientationSensor *m_sensor{nullptr}; };