shift-shell/components/mobileshell/qml/popups/actionbuttons/ActionButton.qml
Marco Allegretti 7f9087f342 Move shell popups to shared motion
Apply Motion tokens to notification drag and stack transitions, volume OSD feedback, KRunner field state, media controls height changes, and action-button popup affordances. Leave timeout and marquee animations as timing behavior.
2026-05-21 11:13:06 +02:00

168 lines
5.2 KiB
QML

/*
* SPDX-FileCopyrightText: 2025 Micah Stanley <stanleymicah@proton.me>
*
* 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 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
readonly property int buttonAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.Standard)
readonly property real hiddenScale: MobileShell.Motion.hiddenScale
readonly property real pressedScale: MobileShell.Motion.pressScaleOut
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: root.buttonAnimationDuration
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 ? root.hiddenScale : (button.pressed ? root.pressedScale : 1)
Behavior on scale {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.StandardDecel }
}
Behavior on opacity {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.StandardDecel }
}
transform: Scale {
origin.x: root.size / 2
origin.y: root.size / 2
xScale: content.scale
yScale: content.scale
}
MobileShell.PanelBackground {
anchors.fill: parent
panelType: MobileShell.PanelBackground.PanelType.Popup
radius: root.size
}
Controls.AbstractButton {
id: button
anchors.fill: parent
hoverEnabled: true
MobileShell.HapticsEffect {
id: haptics
}
contentItem: Item {
MobileShell.MotionStateLayer {
anchors.fill: parent
radius: root.size
hovered: button.hovered
pressed: button.pressed
}
Kirigami.Icon {
anchors.centerIn: parent
width: Kirigami.Units.iconSizes.small
height: Kirigami.Units.iconSizes.small
transformOrigin: Item.Center
rotation: root.angle
source: root.iconSource
isMask: true
color: Kirigami.Theme.textColor
}
}
onPressed: {
haptics.buttonVibrate();
}
onReleased: {
if (active) root.triggered();
}
}
}
}