/* * Copyright (C) 2021 CutefishOS Team. * * Author: Reion Wong * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 import QtQuick.Layouts 1.12 import QtGraphicalEffects 1.0 import Cutefish.Accounts 1.0 as Accounts import Cutefish.Bluez 1.0 as Bluez import Cutefish.StatusBar 1.0 import Cutefish.Audio 1.0 import FishUI 1.0 as FishUI ControlCenterDialog { id: control width: 450 height: _mainLayout.implicitHeight + FishUI.Units.largeSpacing * 2 property var margin: 4 * Screen.devicePixelRatio property point position: Qt.point(0, 0) property var defaultSink: paSinkModel.defaultSink property bool bluetoothDisConnected: Bluez.Manager.bluetoothBlocked property var defaultSinkValue: defaultSink ? defaultSink.volume / PulseAudio.NormalVolume * 100.0 : -1 property var borderColor: windowHelper.compositing ? FishUI.Theme.darkMode ? Qt.rgba(255, 255, 255, 0.3) : Qt.rgba(0, 0, 0, 0.2) : FishUI.Theme.darkMode ? Qt.rgba(255, 255, 255, 0.15) : Qt.rgba(0, 0, 0, 0.15) property var volumeIconName: { if (defaultSinkValue <= 0) return "audio-volume-muted-symbolic" else if (defaultSinkValue <= 25) return "audio-volume-low-symbolic" else if (defaultSinkValue <= 75) return "audio-volume-medium-symbolic" else return "audio-volume-high-symbolic" } onBluetoothDisConnectedChanged: { bluetoothItem.checked = !bluetoothDisConnected } onWidthChanged: adjustCorrectLocation() onHeightChanged: adjustCorrectLocation() onPositionChanged: adjustCorrectLocation() color: "transparent" LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true Appearance { id: appearance } Notifications { id: notifications } SinkModel { id: paSinkModel onDefaultSinkChanged: { if (!defaultSink) { return } } } function toggleBluetooth() { const enable = !control.bluetoothDisConnected Bluez.Manager.bluetoothBlocked = enable for (var i = 0; i < Bluez.Manager.adapters.length; ++i) { var adapter = Bluez.Manager.adapters[i] adapter.powered = enable } } function adjustCorrectLocation() { var posX = control.position.x var posY = control.position.y if (posX + control.width >= StatusBar.screenRect.x + StatusBar.screenRect.width) posX = StatusBar.screenRect.x + StatusBar.screenRect.width - control.width - control.margin posY = rootItem.y + rootItem.height + control.margin control.x = posX control.y = posY } Brightness { id: brightness } Accounts.UserAccount { id: currentUser } FishUI.WindowBlur { view: control geometry: Qt.rect(control.x, control.y, control.width, control.height) windowRadius: _background.radius enabled: true } FishUI.WindowShadow { view: control geometry: Qt.rect(control.x, control.y, control.width, control.height) radius: _background.radius } Rectangle { id: _background anchors.fill: parent radius: windowHelper.compositing ? FishUI.Theme.bigRadius * 1.5 : 0 color: FishUI.Theme.darkMode ? "#4D4D4D" : "#F0F0F0" opacity: windowHelper.compositing ? FishUI.Theme.darkMode ? 0.6 : 0.8 : 1.0 antialiasing: true border.width: 1 / Screen.devicePixelRatio border.pixelAligned: Screen.devicePixelRatio > 1 ? false : true border.color: control.borderColor Behavior on color { ColorAnimation { duration: 200 easing.type: Easing.Linear } } } ColumnLayout { id: _mainLayout anchors.fill: parent anchors.margins: FishUI.Units.largeSpacing spacing: FishUI.Units.largeSpacing Item { id: topItem Layout.fillWidth: true height: 32 RowLayout { id: topItemLayout anchors.fill: parent anchors.rightMargin: FishUI.Units.largeSpacing spacing: FishUI.Units.largeSpacing Label { leftPadding: FishUI.Units.largeSpacing text: qsTr("Control Center") font.bold: true font.pointSize: 14 Layout.fillWidth: true } IconButton { id: settingsButton implicitWidth: topItem.height implicitHeight: topItem.height Layout.alignment: Qt.AlignTop source: "qrc:/images/" + (FishUI.Theme.darkMode ? "dark/" : "light/") + "settings.svg" onLeftButtonClicked: { control.visible = false process.startDetached("cutefish-settings") } } // IconButton { // id: shutdownButton // implicitWidth: topItem.height // implicitHeight: topItem.height // Layout.alignment: Qt.AlignTop // source: "qrc:/images/" + (FishUI.Theme.darkMode ? "dark/" : "light/") + "system-shutdown-symbolic.svg" // onLeftButtonClicked: { // control.visible = false // process.startDetached("cutefish-shutdown") // } // } } } Item { id: cardItems Layout.fillWidth: true Layout.preferredHeight: Math.ceil(cardLayout.count / 4) * 110 property var cellWidth: cardItems.width / 4 Rectangle { anchors.fill: parent color: "white" radius: FishUI.Theme.bigRadius opacity: FishUI.Theme.darkMode ? 0.2 : 0.7 } GridLayout { id: cardLayout anchors.fill: parent columnSpacing: 0 columns: 4 property int count: { var count = 0 for (var i in cardLayout.children) { if (cardLayout.children[i].visible) ++count } return count } CardItem { id: wirelessItem Layout.fillHeight: true Layout.preferredWidth: cardItems.cellWidth icon: FishUI.Theme.darkMode || checked ? "qrc:/images/dark/network-wireless-connected-100.svg" : "qrc:/images/light/network-wireless-connected-100.svg" visible: enabledConnections.wirelessHwEnabled checked: enabledConnections.wirelessEnabled label: activeConnection.wirelessName ? activeConnection.wirelessName : qsTr("Wi-Fi") onClicked: nmHandler.enableWireless(!checked) onPressAndHold: { control.visible = false process.startDetached("cutefish-settings", ["-m", "wlan"]) } } CardItem { id: bluetoothItem Layout.fillHeight: true Layout.preferredWidth: cardItems.cellWidth icon: FishUI.Theme.darkMode || checked ? "qrc:/images/dark/bluetooth-symbolic.svg" : "qrc:/images/light/bluetooth-symbolic.svg" checked: !control.bluetoothDisConnected label: qsTr("Bluetooth") visible: Bluez.Manager.adapters.length onClicked: control.toggleBluetooth() onPressAndHold: { control.visible = false process.startDetached("cutefish-settings", ["-m", "bluetooth"]) } } CardItem { id: darkModeItem Layout.fillHeight: true Layout.preferredWidth: cardItems.cellWidth icon: FishUI.Theme.darkMode || checked ? "qrc:/images/dark/dark-mode.svg" : "qrc:/images/light/dark-mode.svg" checked: FishUI.Theme.darkMode label: qsTr("Dark Mode") onClicked: appearance.switchDarkMode(!FishUI.Theme.darkMode) } CardItem { Layout.fillHeight: true Layout.preferredWidth: cardItems.cellWidth icon: FishUI.Theme.darkMode || checked ? "qrc:/images/dark/do-not-disturb.svg" : "qrc:/images/light/do-not-disturb.svg" checked: notifications.doNotDisturb label: qsTr("Do Not Disturb") onClicked: notifications.doNotDisturb = !notifications.doNotDisturb } CardItem { Layout.fillHeight: true Layout.preferredWidth: cardItems.cellWidth icon: FishUI.Theme.darkMode || checked ? "qrc:/images/dark/screenshot.svg" : "qrc:/images/light/screenshot.svg" checked: false label: qsTr("Screenshot") onClicked: { control.visible = false process.startDetached("cutefish-screenshot", ["-d", "500"]) } } } } MprisItem { height: 96 Layout.fillWidth: true } Item { id: brightnessItem Layout.fillWidth: true height: 40 visible: brightness.enabled Rectangle { id: brightnessItemBg anchors.fill: parent color: "white" radius: FishUI.Theme.bigRadius opacity: FishUI.Theme.darkMode ? 0.2 : 0.7 } RowLayout { anchors.fill: brightnessItemBg anchors.leftMargin: FishUI.Units.largeSpacing anchors.rightMargin: FishUI.Units.largeSpacing anchors.topMargin: FishUI.Units.smallSpacing anchors.bottomMargin: FishUI.Units.smallSpacing spacing: FishUI.Units.largeSpacing Image { height: 16 width: height sourceSize: Qt.size(width, height) source: "qrc:/images/" + (FishUI.Theme.darkMode ? "dark" : "light") + "/brightness.svg" smooth: false antialiasing: true } Timer { id: brightnessTimer interval: 100 repeat: false onTriggered: brightness.setValue(brightnessSlider.value) } Slider { id: brightnessSlider from: 1 to: 100 stepSize: 1 value: brightness.value Layout.fillWidth: true Layout.fillHeight: true onMoved: brightnessTimer.start() } // Label { // text: brightnessSlider.value + "%" // color: FishUI.Theme.disabledTextColor // Layout.preferredWidth: _fontMetrics.advanceWidth("100%") // } } } Item { id: volumeItem Layout.fillWidth: true height: 40 visible: defaultSink Rectangle { id: volumeItemBg anchors.fill: parent color: "white" radius: FishUI.Theme.bigRadius opacity: FishUI.Theme.darkMode ? 0.2 : 0.7 } RowLayout { anchors.fill: volumeItemBg anchors.leftMargin: FishUI.Units.largeSpacing anchors.rightMargin: FishUI.Units.largeSpacing anchors.topMargin: FishUI.Units.smallSpacing anchors.bottomMargin: FishUI.Units.smallSpacing spacing: FishUI.Units.largeSpacing Image { height: 16 width: height sourceSize: Qt.size(width, height) source: "qrc:/images/" + (FishUI.Theme.darkMode ? "dark" : "light") + "/" + control.volumeIconName + ".svg" smooth: false antialiasing: true } Slider { id: volumeSlider Layout.fillWidth: true Layout.fillHeight: true from: PulseAudio.MinimalVolume to: PulseAudio.NormalVolume stepSize: to / (to / PulseAudio.NormalVolume * 100.0) value: defaultSink ? defaultSink.volume : 0 onValueChanged: { if (!defaultSink) return defaultSink.volume = value defaultSink.muted = (value === 0) } } // Label { // text: parseInt(volumeSlider.value / PulseAudio.NormalVolume * 100.0) + "%" // Layout.preferredWidth: _fontMetrics.advanceWidth("100%") // color: FishUI.Theme.disabledTextColor // } } } FontMetrics { id: _fontMetrics } RowLayout { Layout.leftMargin: FishUI.Units.smallSpacing Layout.rightMargin: FishUI.Units.smallSpacing spacing: 0 Label { id: timeLabel leftPadding: FishUI.Units.smallSpacing / 2 color: FishUI.Theme.textColor Timer { interval: 1000 repeat: true running: true triggeredOnStart: true onTriggered: { timeLabel.text = new Date().toLocaleDateString(Qt.locale(), Locale.LongFormat) } } } Item { Layout.fillWidth: true } StandardItem { width: batteryLayout.implicitWidth + FishUI.Units.largeSpacing height: batteryLayout.implicitHeight + FishUI.Units.largeSpacing onClicked: { control.visible = false process.startDetached("cutefish-settings", ["-m", "battery"]) } RowLayout { id: batteryLayout anchors.fill: parent visible: battery.available spacing: 0 Image { id: batteryIcon width: 22 height: 16 sourceSize: Qt.size(width, height) source: "qrc:/images/" + (FishUI.Theme.darkMode ? "dark/" : "light/") + battery.iconSource asynchronous: true Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter antialiasing: true smooth: false } Label { text: battery.chargePercent + "%" color: FishUI.Theme.textColor rightPadding: FishUI.Units.smallSpacing / 2 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter } } } } } function calcExtraSpacing(cellSize, containerSize) { var availableColumns = Math.floor(containerSize / cellSize) var extraSpacing = 0 if (availableColumns > 0) { var allColumnSize = availableColumns * cellSize var extraSpace = Math.max(containerSize - allColumnSize, 0) extraSpacing = extraSpace / availableColumns } return Math.floor(extraSpacing) } }