mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-13 09:47:42 +00:00
413 lines
14 KiB
QML
413 lines
14 KiB
QML
|
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||
|
|
// SPDX-License-Identifier: EUPL-1.2
|
||
|
|
|
||
|
|
import QtQuick
|
||
|
|
import QtQuick.Layouts
|
||
|
|
import QtQuick.Controls as QQC2
|
||
|
|
|
||
|
|
import org.kde.kirigami as Kirigami
|
||
|
|
import org.kde.plasma.components 3.0 as PC3
|
||
|
|
import org.kde.plasma.private.mobileshell as MobileShell
|
||
|
|
import org.kde.plasma.private.mobileshell.gamingshellplugin as GamingShell
|
||
|
|
import org.kde.plasma.private.mobileshell.screenbrightnessplugin as ScreenBrightness
|
||
|
|
import org.kde.plasma.private.volume
|
||
|
|
import org.kde.plasma.networkmanagement as PlasmaNM
|
||
|
|
import org.kde.bluezqt 1.0 as BluezQt
|
||
|
|
|
||
|
|
Item {
|
||
|
|
id: root
|
||
|
|
anchors.fill: parent
|
||
|
|
|
||
|
|
property bool opened: false
|
||
|
|
|
||
|
|
// Focusable controls for gamepad navigation
|
||
|
|
property var _controls: []
|
||
|
|
property int _focusIndex: 0
|
||
|
|
|
||
|
|
function _buildControlsList() {
|
||
|
|
var list = []
|
||
|
|
if (screenBrightness.brightnessAvailable) list.push(brightnessSlider)
|
||
|
|
if (PreferredDevice.sink) list.push(volumeSlider)
|
||
|
|
list.push(wifiSwitch)
|
||
|
|
list.push(btSwitch)
|
||
|
|
list.push(airplaneSwitch)
|
||
|
|
_controls = list
|
||
|
|
}
|
||
|
|
|
||
|
|
function open() {
|
||
|
|
opened = true
|
||
|
|
_buildControlsList()
|
||
|
|
_focusIndex = 0
|
||
|
|
_highlightCurrent()
|
||
|
|
}
|
||
|
|
function close() {
|
||
|
|
opened = false
|
||
|
|
}
|
||
|
|
function toggle() {
|
||
|
|
if (opened) close(); else open()
|
||
|
|
}
|
||
|
|
|
||
|
|
function _highlightCurrent() {
|
||
|
|
if (_controls.length > 0 && _focusIndex >= 0 && _focusIndex < _controls.length) {
|
||
|
|
_controls[_focusIndex].forceActiveFocus()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Gamepad input handlers called from GameCenterOverlay
|
||
|
|
function gamepadUp() {
|
||
|
|
if (_focusIndex > 0) {
|
||
|
|
_focusIndex--
|
||
|
|
_highlightCurrent()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function gamepadDown() {
|
||
|
|
if (_focusIndex < _controls.length - 1) {
|
||
|
|
_focusIndex++
|
||
|
|
_highlightCurrent()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function gamepadLeft() {
|
||
|
|
var ctrl = _controls[_focusIndex]
|
||
|
|
if (ctrl instanceof PC3.Slider) {
|
||
|
|
ctrl.decrease()
|
||
|
|
ctrl.moved()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function gamepadRight() {
|
||
|
|
var ctrl = _controls[_focusIndex]
|
||
|
|
if (ctrl instanceof PC3.Slider) {
|
||
|
|
ctrl.increase()
|
||
|
|
ctrl.moved()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function gamepadAccept() {
|
||
|
|
var ctrl = _controls[_focusIndex]
|
||
|
|
if (ctrl instanceof QQC2.Switch) {
|
||
|
|
ctrl.toggle()
|
||
|
|
ctrl.toggled()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Eat clicks on the dimmed backdrop
|
||
|
|
MouseArea {
|
||
|
|
anchors.fill: parent
|
||
|
|
visible: root.opened
|
||
|
|
onClicked: root.close()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Dim backdrop
|
||
|
|
Rectangle {
|
||
|
|
anchors.fill: parent
|
||
|
|
color: "black"
|
||
|
|
opacity: root.opened ? 0.4 : 0
|
||
|
|
Behavior on opacity {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Panel sliding in from the right
|
||
|
|
Rectangle {
|
||
|
|
id: panel
|
||
|
|
width: Math.min(root.width * 0.35, Kirigami.Units.gridUnit * 22)
|
||
|
|
height: root.height
|
||
|
|
anchors.top: root.top
|
||
|
|
anchors.bottom: root.bottom
|
||
|
|
|
||
|
|
x: root.opened ? root.width - width : root.width
|
||
|
|
|
||
|
|
Behavior on x {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
||
|
|
}
|
||
|
|
|
||
|
|
Kirigami.Theme.inherit: false
|
||
|
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||
|
|
color: Qt.rgba(Kirigami.Theme.backgroundColor.r,
|
||
|
|
Kirigami.Theme.backgroundColor.g,
|
||
|
|
Kirigami.Theme.backgroundColor.b, 0.96)
|
||
|
|
|
||
|
|
// Subtle left border
|
||
|
|
Rectangle {
|
||
|
|
width: 1
|
||
|
|
anchors.top: parent.top
|
||
|
|
anchors.bottom: parent.bottom
|
||
|
|
anchors.left: parent.left
|
||
|
|
color: Kirigami.Theme.disabledTextColor
|
||
|
|
opacity: 0.3
|
||
|
|
}
|
||
|
|
|
||
|
|
ScreenBrightness.ScreenBrightnessUtil {
|
||
|
|
id: screenBrightness
|
||
|
|
}
|
||
|
|
|
||
|
|
PlasmaNM.Handler {
|
||
|
|
id: nmHandler
|
||
|
|
}
|
||
|
|
|
||
|
|
PlasmaNM.EnabledConnections {
|
||
|
|
id: enabledConnections
|
||
|
|
}
|
||
|
|
|
||
|
|
Flickable {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.margins: Kirigami.Units.largeSpacing * 2
|
||
|
|
contentHeight: settingsColumn.implicitHeight
|
||
|
|
clip: true
|
||
|
|
|
||
|
|
ColumnLayout {
|
||
|
|
id: settingsColumn
|
||
|
|
width: parent.width
|
||
|
|
spacing: Kirigami.Units.largeSpacing * 2
|
||
|
|
|
||
|
|
// ---- Header ----
|
||
|
|
RowLayout {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
|
||
|
|
Kirigami.Heading {
|
||
|
|
text: i18n("Quick Settings")
|
||
|
|
level: 2
|
||
|
|
Layout.fillWidth: true
|
||
|
|
}
|
||
|
|
|
||
|
|
QQC2.ToolButton {
|
||
|
|
icon.name: "window-close-symbolic"
|
||
|
|
onClicked: root.close()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Kirigami.Separator { Layout.fillWidth: true }
|
||
|
|
|
||
|
|
// ---- Brightness ----
|
||
|
|
ColumnLayout {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
visible: screenBrightness.brightnessAvailable
|
||
|
|
|
||
|
|
PC3.Label {
|
||
|
|
text: i18n("Brightness")
|
||
|
|
font.bold: true
|
||
|
|
}
|
||
|
|
|
||
|
|
RowLayout {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
|
||
|
|
Kirigami.Icon {
|
||
|
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
source: "low-brightness"
|
||
|
|
}
|
||
|
|
|
||
|
|
PC3.Slider {
|
||
|
|
id: brightnessSlider
|
||
|
|
Layout.fillWidth: true
|
||
|
|
from: 1
|
||
|
|
to: screenBrightness.maxBrightness
|
||
|
|
stepSize: Math.max(1, Math.round(screenBrightness.maxBrightness / 20))
|
||
|
|
value: screenBrightness.brightness
|
||
|
|
onMoved: screenBrightness.brightness = value
|
||
|
|
|
||
|
|
Timer {
|
||
|
|
interval: 0
|
||
|
|
running: true
|
||
|
|
repeat: false
|
||
|
|
onTriggered: brightnessSlider.value = Qt.binding(() => screenBrightness.brightness)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Focus highlight
|
||
|
|
Rectangle {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.margins: -Kirigami.Units.smallSpacing
|
||
|
|
radius: Kirigami.Units.smallSpacing
|
||
|
|
color: "transparent"
|
||
|
|
border.color: Kirigami.Theme.highlightColor
|
||
|
|
border.width: parent.activeFocus ? 2 : 0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Kirigami.Icon {
|
||
|
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
source: "high-brightness"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---- Volume ----
|
||
|
|
ColumnLayout {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
visible: PreferredDevice.sink != null
|
||
|
|
|
||
|
|
PC3.Label {
|
||
|
|
text: i18n("Volume")
|
||
|
|
font.bold: true
|
||
|
|
}
|
||
|
|
|
||
|
|
RowLayout {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
|
||
|
|
Kirigami.Icon {
|
||
|
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
source: "audio-volume-low"
|
||
|
|
}
|
||
|
|
|
||
|
|
PC3.Slider {
|
||
|
|
id: volumeSlider
|
||
|
|
Layout.fillWidth: true
|
||
|
|
from: PulseAudio.MinimalVolume
|
||
|
|
to: PulseAudio.NormalVolume
|
||
|
|
stepSize: PulseAudio.NormalVolume / 20
|
||
|
|
value: PreferredDevice.sink ? PreferredDevice.sink.volume : 0
|
||
|
|
onMoved: {
|
||
|
|
if (PreferredDevice.sink) {
|
||
|
|
PreferredDevice.sink.volume = value
|
||
|
|
PreferredDevice.sink.muted = (value === 0)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Focus highlight
|
||
|
|
Rectangle {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.margins: -Kirigami.Units.smallSpacing
|
||
|
|
radius: Kirigami.Units.smallSpacing
|
||
|
|
color: "transparent"
|
||
|
|
border.color: Kirigami.Theme.highlightColor
|
||
|
|
border.width: parent.activeFocus ? 2 : 0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Kirigami.Icon {
|
||
|
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
source: "audio-volume-high"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Kirigami.Separator { Layout.fillWidth: true }
|
||
|
|
|
||
|
|
// ---- Connectivity toggles ----
|
||
|
|
PC3.Label {
|
||
|
|
text: i18n("Connectivity")
|
||
|
|
font.bold: true
|
||
|
|
}
|
||
|
|
|
||
|
|
GridLayout {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
columns: 2
|
||
|
|
rowSpacing: Kirigami.Units.smallSpacing
|
||
|
|
columnSpacing: Kirigami.Units.largeSpacing
|
||
|
|
|
||
|
|
// WiFi toggle
|
||
|
|
QQC2.Switch {
|
||
|
|
id: wifiSwitch
|
||
|
|
text: i18n("Wi-Fi")
|
||
|
|
checked: enabledConnections.wirelessEnabled
|
||
|
|
onToggled: nmHandler.enableWireless(checked)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Bluetooth toggle
|
||
|
|
QQC2.Switch {
|
||
|
|
id: btSwitch
|
||
|
|
text: i18n("Bluetooth")
|
||
|
|
checked: !BluezQt.Manager.bluetoothBlocked
|
||
|
|
onToggled: BluezQt.Manager.bluetoothBlocked = !checked
|
||
|
|
}
|
||
|
|
|
||
|
|
// Airplane mode
|
||
|
|
QQC2.Switch {
|
||
|
|
id: airplaneSwitch
|
||
|
|
text: i18n("Airplane Mode")
|
||
|
|
checked: PlasmaNM.Configuration.airplaneModeEnabled
|
||
|
|
onToggled: {
|
||
|
|
nmHandler.enableAirplaneMode(!PlasmaNM.Configuration.airplaneModeEnabled)
|
||
|
|
PlasmaNM.Configuration.airplaneModeEnabled = !PlasmaNM.Configuration.airplaneModeEnabled
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Kirigami.Separator { Layout.fillWidth: true }
|
||
|
|
|
||
|
|
// ---- Battery info ----
|
||
|
|
ColumnLayout {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
visible: MobileShell.BatteryInfo.isVisible
|
||
|
|
|
||
|
|
PC3.Label {
|
||
|
|
text: i18n("Battery")
|
||
|
|
font.bold: true
|
||
|
|
}
|
||
|
|
|
||
|
|
RowLayout {
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
|
||
|
|
MobileShell.BatteryIndicator {
|
||
|
|
textPixelSize: Kirigami.Units.gridUnit * 0.7
|
||
|
|
}
|
||
|
|
|
||
|
|
PC3.Label {
|
||
|
|
text: MobileShell.BatteryInfo.pluggedIn ? i18n("Charging") : ""
|
||
|
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
|
||
|
|
opacity: 0.7
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---- Controller info ----
|
||
|
|
ColumnLayout {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
visible: GamingShell.GamepadManager.hasGamepad
|
||
|
|
|
||
|
|
PC3.Label {
|
||
|
|
text: i18n("Controllers")
|
||
|
|
font.bold: true
|
||
|
|
}
|
||
|
|
|
||
|
|
Repeater {
|
||
|
|
model: GamingShell.GamepadManager
|
||
|
|
|
||
|
|
RowLayout {
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
required property string name
|
||
|
|
required property int battery
|
||
|
|
required property string type
|
||
|
|
|
||
|
|
Kirigami.Icon {
|
||
|
|
implicitWidth: Kirigami.Units.iconSizes.small
|
||
|
|
implicitHeight: Kirigami.Units.iconSizes.small
|
||
|
|
source: "input-gaming"
|
||
|
|
}
|
||
|
|
|
||
|
|
PC3.Label {
|
||
|
|
text: name
|
||
|
|
}
|
||
|
|
|
||
|
|
PC3.Label {
|
||
|
|
text: battery >= 0 ? battery + "%" : i18n("Wired")
|
||
|
|
opacity: 0.7
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Kirigami.Separator { Layout.fillWidth: true }
|
||
|
|
|
||
|
|
// ---- Gamepad legend ----
|
||
|
|
PC3.Label {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
text: i18n("↕: Navigate ↔: Adjust A: Toggle B: Close")
|
||
|
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
|
||
|
|
opacity: 0.5
|
||
|
|
horizontalAlignment: Text.AlignHCenter
|
||
|
|
}
|
||
|
|
|
||
|
|
Item { Layout.fillHeight: true }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|