1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00
Files
DankMaterialShell/Modules/ControlCenter/ControlCenterPopout.qml

751 lines
32 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Modules.ControlCenter
import qs.Modules.ControlCenter.Widgets
import qs.Modules.ControlCenter.Details
import qs.Modules.ControlCenter.Details 1.0 as Details
import qs.Services
import qs.Widgets
DankPopout {
id: root
property string expandedSection: ""
property bool powerOptionsExpanded: false
property string triggerSection: "right"
property var triggerScreen: null
function setTriggerPosition(x, y, width, section, screen) {
triggerX = x
triggerY = y
triggerWidth = width
triggerSection = section
triggerScreen = screen
}
function openWithSection(section) {
if (shouldBeVisible) {
close()
} else {
expandedSection = section
open()
}
}
function toggleSection(section) {
if (expandedSection === section) {
expandedSection = ""
} else {
expandedSection = section
}
}
signal powerActionRequested(string action, string title, string message)
signal lockRequested
popupWidth: 550
popupHeight: Math.min((triggerScreen?.height ?? 1080) - 100, contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400)
triggerX: (triggerScreen?.width ?? 1920) - 600 - Theme.spacingL
triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingXS
triggerWidth: 80
positioning: "center"
screen: triggerScreen
shouldBeVisible: false
visible: shouldBeVisible
onShouldBeVisibleChanged: {
if (shouldBeVisible) {
Qt.callLater(() => {
NetworkService.autoRefreshEnabled = NetworkService.wifiEnabled
if (UserInfoService)
UserInfoService.getUptime()
})
} else {
Qt.callLater(() => {
NetworkService.autoRefreshEnabled = false
if (BluetoothService.adapter
&& BluetoothService.adapter.discovering)
BluetoothService.adapter.discovering = false
})
}
}
content: Component {
Rectangle {
id: controlContent
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
property alias bluetoothCodecSelector: bluetoothCodecSelector
color: {
const transparency = Theme.popupTransparency || 0.92
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
return Qt.rgba(surface.r, surface.g, surface.b, transparency)
}
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1
antialiasing: true
smooth: true
Column {
id: mainColumn
width: parent.width - Theme.spacingL * 2
x: Theme.spacingL
y: Theme.spacingL
spacing: Theme.spacingS
Rectangle {
width: parent.width
height: 90
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL
spacing: Theme.spacingL
Item {
id: avatarContainer
property bool hasImage: profileImageLoader.status === Image.Ready
width: 64
height: 64
Rectangle {
anchors.fill: parent
radius: width / 2
color: "transparent"
border.color: Theme.primary
border.width: 1
visible: parent.hasImage
}
Image {
id: profileImageLoader
source: {
if (PortalService.profileImage === "")
return ""
if (PortalService.profileImage.startsWith(
"/"))
return "file://" + PortalService.profileImage
return PortalService.profileImage
}
smooth: true
asynchronous: true
mipmap: true
cache: true
visible: false
}
MultiEffect {
anchors.fill: parent
anchors.margins: 5
source: profileImageLoader
maskEnabled: true
maskSource: circularMask
visible: avatarContainer.hasImage
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: circularMask
width: 64 - 10
height: 64 - 10
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: width / 2
color: "black"
antialiasing: true
}
}
Rectangle {
anchors.fill: parent
radius: width / 2
color: Theme.primary
visible: !parent.hasImage
DankIcon {
anchors.centerIn: parent
name: "person"
size: Theme.iconSize + 8
color: Theme.primaryText
}
}
DankIcon {
anchors.centerIn: parent
name: "warning"
size: Theme.iconSize + 8
color: Theme.primaryText
visible: PortalService.profileImage !== ""
&& profileImageLoader.status === Image.Error
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
StyledText {
text: UserInfoService.fullName
|| UserInfoService.username || "User"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: (UserInfoService.uptime
|| "Unknown")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
font.weight: Font.Normal
}
}
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.spacingL
spacing: Theme.spacingS
DankActionButton {
buttonSize: 40
iconName: "lock"
iconSize: Theme.iconSize - 2
iconColor: Theme.surfaceText
backgroundColor: Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.5)
onClicked: {
root.close()
root.lockRequested()
}
}
DankActionButton {
buttonSize: 40
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
iconSize: Theme.iconSize - 2
iconColor: Theme.surfaceText
backgroundColor: Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.5)
onClicked: {
root.powerOptionsExpanded = !root.powerOptionsExpanded
}
}
DankActionButton {
buttonSize: 40
iconName: "settings"
iconSize: Theme.iconSize - 2
iconColor: Theme.surfaceText
backgroundColor: Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.5)
onClicked: {
root.close()
settingsModal.show()
}
}
}
}
Item {
width: parent.width
implicitHeight: root.powerOptionsExpanded ? 60 : 0
height: implicitHeight
clip: true
Rectangle {
width: parent.width
height: 60
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha() * 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: root.powerOptionsExpanded ? 1 : 0
opacity: root.powerOptionsExpanded ? 1 : 0
clip: true
Row {
anchors.centerIn: parent
spacing: Theme.spacingL
visible: root.powerOptionsExpanded
Rectangle {
width: 100
height: 34
radius: Theme.cornerRadius
color: logoutButton.containsMouse ? Qt.rgba(
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.5)
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "logout"
size: Theme.fontSizeSmall
color: logoutButton.containsMouse ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Logout"
font.pixelSize: Theme.fontSizeSmall
color: logoutButton.containsMouse ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: logoutButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.powerOptionsExpanded = false
root.close()
root.powerActionRequested(
"logout", "Logout",
"Are you sure you want to logout?")
}
}
}
Rectangle {
width: 100
height: 34
radius: Theme.cornerRadius
color: rebootButton.containsMouse ? Qt.rgba(
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.5)
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "restart_alt"
size: Theme.fontSizeSmall
color: rebootButton.containsMouse ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Restart"
font.pixelSize: Theme.fontSizeSmall
color: rebootButton.containsMouse ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: rebootButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.powerOptionsExpanded = false
root.close()
root.powerActionRequested(
"reboot", "Restart",
"Are you sure you want to restart?")
}
}
}
Rectangle {
width: 100
height: 34
radius: Theme.cornerRadius
color: suspendButton.containsMouse ? Qt.rgba(
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.5)
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "bedtime"
size: Theme.fontSizeSmall
color: suspendButton.containsMouse ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Suspend"
font.pixelSize: Theme.fontSizeSmall
color: suspendButton.containsMouse ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: suspendButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.powerOptionsExpanded = false
root.close()
root.powerActionRequested(
"suspend", "Suspend",
"Are you sure you want to suspend?")
}
}
}
Rectangle {
width: 100
height: 34
radius: Theme.cornerRadius
color: shutdownButton.containsMouse ? Qt.rgba(
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.5)
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "power_settings_new"
size: Theme.fontSizeSmall
color: shutdownButton.containsMouse ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Shutdown"
font.pixelSize: Theme.fontSizeSmall
color: shutdownButton.containsMouse ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: shutdownButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.powerOptionsExpanded = false
root.close()
root.powerActionRequested(
"poweroff", "Shutdown",
"Are you sure you want to shutdown?")
}
}
}
}
}
}
Item {
width: parent.width
height: audioSliderRow.implicitHeight
Row {
id: audioSliderRow
x: -Theme.spacingS
width: parent.width + Theme.spacingS * 2
spacing: Theme.spacingM
AudioSliderRow {
width: SettingsData.hideBrightnessSlider ? parent.width - Theme.spacingM : (parent.width - Theme.spacingM) / 2
}
Item {
width: (parent.width - Theme.spacingM) / 2
height: parent.height
visible: !SettingsData.hideBrightnessSlider
BrightnessSliderRow {
width: parent.width
height: parent.height
x: -Theme.spacingS
}
}
}
}
Row {
width: parent.width
spacing: Theme.spacingM
NetworkPill {
width: (parent.width - Theme.spacingM) / 2
expanded: root.expandedSection === "network"
onClicked: {
if (NetworkService.wifiToggling) {
return
}
if (NetworkService.networkStatus === "ethernet") {
if (NetworkService.ethernetConnected && !NetworkService.wifiEnabled) {
NetworkService.toggleWifiRadio()
return
}
root.toggleSection("network")
return
}
if (NetworkService.networkStatus === "wifi") {
if (NetworkService.ethernetConnected) {
NetworkService.toggleWifiRadio()
return
}
NetworkService.disconnectWifi()
return
}
if (!NetworkService.wifiEnabled) {
NetworkService.toggleWifiRadio()
return
}
if (NetworkService.wifiEnabled && NetworkService.networkStatus === "disconnected") {
root.toggleSection("network")
}
}
onExpandClicked: root.toggleSection("network")
}
BluetoothPill {
width: (parent.width - Theme.spacingM) / 2
expanded: root.expandedSection === "bluetooth"
onClicked: {
if (BluetoothService.adapter)
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
}
onExpandClicked: root.toggleSection("bluetooth")
visible: BluetoothService.available
}
}
Loader {
width: parent.width
active: root.expandedSection === "network" || root.expandedSection === "bluetooth"
visible: active
sourceComponent: DetailView {
width: parent.width
isVisible: true
title: {
switch (root.expandedSection) {
case "network": return "Network Settings"
case "bluetooth": return "Bluetooth Settings"
default: return ""
}
}
content: {
switch (root.expandedSection) {
case "network": return networkDetailComponent
case "bluetooth": return bluetoothDetailComponent
default: return null
}
}
contentHeight: 250
}
}
Row {
width: parent.width
spacing: Theme.spacingM
AudioOutputPill {
width: (parent.width - Theme.spacingM) / 2
expanded: root.expandedSection === "audio_output"
onClicked: {
if (AudioService.sink) {
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
}
}
onExpandClicked: root.toggleSection("audio_output")
}
AudioInputPill {
width: (parent.width - Theme.spacingM) / 2
expanded: root.expandedSection === "audio_input"
onClicked: {
if (AudioService.source) {
AudioService.source.audio.muted = !AudioService.source.audio.muted
}
}
onExpandClicked: root.toggleSection("audio_input")
}
}
Loader {
width: parent.width
active: root.expandedSection === "audio_output" || root.expandedSection === "audio_input"
visible: active
sourceComponent: DetailView {
width: parent.width
isVisible: true
title: {
switch (root.expandedSection) {
case "audio_output": return "Audio Output"
case "audio_input": return "Audio Input"
default: return ""
}
}
content: {
switch (root.expandedSection) {
case "audio_output": return audioOutputDetailComponent
case "audio_input": return audioInputDetailComponent
default: return null
}
}
contentHeight: 250
}
}
Row {
width: parent.width
spacing: Theme.spacingM
ToggleButton {
width: (parent.width - Theme.spacingM) / 2
iconName: DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
text: "Night Mode"
secondaryText: SessionData.nightModeAutoEnabled ? "Auto" : (DisplayService.nightModeEnabled ? "On" : "Off")
isActive: DisplayService.nightModeEnabled
enabled: DisplayService.automationAvailable
onClicked: DisplayService.toggleNightMode()
DankIcon {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
name: "schedule"
size: 12
color: Theme.primary
visible: SessionData.nightModeAutoEnabled
opacity: 0.8
}
}
ToggleButton {
width: (parent.width - Theme.spacingM) / 2
iconName: SessionData.isLightMode ? "light_mode" : "palette"
text: "Theme"
secondaryText: SessionData.isLightMode ? "Light" : "Dark"
isActive: true
onClicked: Theme.toggleLightMode()
}
}
}
Details.BluetoothCodecSelector {
id: bluetoothCodecSelector
anchors.fill: parent
z: 10000
}
}
}
Component {
id: networkDetailComponent
NetworkDetail {}
}
Component {
id: bluetoothDetailComponent
BluetoothDetail {
id: bluetoothDetail
onShowCodecSelector: function(device) {
if (contentLoader.item && contentLoader.item.bluetoothCodecSelector) {
contentLoader.item.bluetoothCodecSelector.show(device)
contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
})
}
}
}
}
Component {
id: audioOutputDetailComponent
AudioOutputDetail {}
}
Component {
id: audioInputDetailComponent
AudioInputDetail {}
}
}