mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
ControlCenter: Implement edit mode for customizing widgets
This commit is contained in:
@@ -45,6 +45,16 @@ Singleton {
|
|||||||
property bool controlCenterShowNetworkIcon: true
|
property bool controlCenterShowNetworkIcon: true
|
||||||
property bool controlCenterShowBluetoothIcon: true
|
property bool controlCenterShowBluetoothIcon: true
|
||||||
property bool controlCenterShowAudioIcon: true
|
property bool controlCenterShowAudioIcon: true
|
||||||
|
property var controlCenterWidgets: [
|
||||||
|
{"id": "volumeSlider", "enabled": true, "width": 50},
|
||||||
|
{"id": "brightnessSlider", "enabled": true, "width": 50},
|
||||||
|
{"id": "wifi", "enabled": true, "width": 50},
|
||||||
|
{"id": "bluetooth", "enabled": true, "width": 50},
|
||||||
|
{"id": "audioOutput", "enabled": true, "width": 50},
|
||||||
|
{"id": "audioInput", "enabled": true, "width": 50},
|
||||||
|
{"id": "nightMode", "enabled": true, "width": 50},
|
||||||
|
{"id": "darkMode", "enabled": true, "width": 50}
|
||||||
|
]
|
||||||
property bool showWorkspaceIndex: false
|
property bool showWorkspaceIndex: false
|
||||||
property bool showWorkspacePadding: false
|
property bool showWorkspacePadding: false
|
||||||
property bool showWorkspaceApps: false
|
property bool showWorkspaceApps: false
|
||||||
@@ -226,6 +236,16 @@ Singleton {
|
|||||||
controlCenterShowNetworkIcon = settings.controlCenterShowNetworkIcon !== undefined ? settings.controlCenterShowNetworkIcon : true
|
controlCenterShowNetworkIcon = settings.controlCenterShowNetworkIcon !== undefined ? settings.controlCenterShowNetworkIcon : true
|
||||||
controlCenterShowBluetoothIcon = settings.controlCenterShowBluetoothIcon !== undefined ? settings.controlCenterShowBluetoothIcon : true
|
controlCenterShowBluetoothIcon = settings.controlCenterShowBluetoothIcon !== undefined ? settings.controlCenterShowBluetoothIcon : true
|
||||||
controlCenterShowAudioIcon = settings.controlCenterShowAudioIcon !== undefined ? settings.controlCenterShowAudioIcon : true
|
controlCenterShowAudioIcon = settings.controlCenterShowAudioIcon !== undefined ? settings.controlCenterShowAudioIcon : true
|
||||||
|
controlCenterWidgets = settings.controlCenterWidgets !== undefined ? settings.controlCenterWidgets : [
|
||||||
|
{"id": "volumeSlider", "enabled": true, "width": 50},
|
||||||
|
{"id": "brightnessSlider", "enabled": true, "width": 50},
|
||||||
|
{"id": "wifi", "enabled": true, "width": 50},
|
||||||
|
{"id": "bluetooth", "enabled": true, "width": 50},
|
||||||
|
{"id": "audioOutput", "enabled": true, "width": 50},
|
||||||
|
{"id": "audioInput", "enabled": true, "width": 50},
|
||||||
|
{"id": "nightMode", "enabled": true, "width": 50},
|
||||||
|
{"id": "darkMode", "enabled": true, "width": 50}
|
||||||
|
]
|
||||||
showWorkspaceIndex = settings.showWorkspaceIndex !== undefined ? settings.showWorkspaceIndex : false
|
showWorkspaceIndex = settings.showWorkspaceIndex !== undefined ? settings.showWorkspaceIndex : false
|
||||||
showWorkspacePadding = settings.showWorkspacePadding !== undefined ? settings.showWorkspacePadding : false
|
showWorkspacePadding = settings.showWorkspacePadding !== undefined ? settings.showWorkspacePadding : false
|
||||||
showWorkspaceApps = settings.showWorkspaceApps !== undefined ? settings.showWorkspaceApps : false
|
showWorkspaceApps = settings.showWorkspaceApps !== undefined ? settings.showWorkspaceApps : false
|
||||||
@@ -354,6 +374,7 @@ Singleton {
|
|||||||
"controlCenterShowNetworkIcon": controlCenterShowNetworkIcon,
|
"controlCenterShowNetworkIcon": controlCenterShowNetworkIcon,
|
||||||
"controlCenterShowBluetoothIcon": controlCenterShowBluetoothIcon,
|
"controlCenterShowBluetoothIcon": controlCenterShowBluetoothIcon,
|
||||||
"controlCenterShowAudioIcon": controlCenterShowAudioIcon,
|
"controlCenterShowAudioIcon": controlCenterShowAudioIcon,
|
||||||
|
"controlCenterWidgets": controlCenterWidgets,
|
||||||
"showWorkspaceIndex": showWorkspaceIndex,
|
"showWorkspaceIndex": showWorkspaceIndex,
|
||||||
"showWorkspacePadding": showWorkspacePadding,
|
"showWorkspacePadding": showWorkspacePadding,
|
||||||
"showWorkspaceApps": showWorkspaceApps,
|
"showWorkspaceApps": showWorkspaceApps,
|
||||||
@@ -681,6 +702,10 @@ Singleton {
|
|||||||
controlCenterShowAudioIcon = enabled
|
controlCenterShowAudioIcon = enabled
|
||||||
saveSettings()
|
saveSettings()
|
||||||
}
|
}
|
||||||
|
function setControlCenterWidgets(widgets) {
|
||||||
|
controlCenterWidgets = widgets
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
|
||||||
function setTopBarWidgetOrder(order) {
|
function setTopBarWidgetOrder(order) {
|
||||||
topBarWidgetOrder = order
|
topBarWidgetOrder = order
|
||||||
|
|||||||
121
Modules/ControlCenter/Components/ActionTile.qml
Normal file
121
Modules/ControlCenter/Components/ActionTile.qml
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string iconName: ""
|
||||||
|
property string text: ""
|
||||||
|
property string secondaryText: ""
|
||||||
|
property bool isActive: false
|
||||||
|
property bool enabled: true
|
||||||
|
property int widgetIndex: 0
|
||||||
|
property var widgetData: null
|
||||||
|
property bool editMode: false
|
||||||
|
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
width: parent ? parent.width : 200
|
||||||
|
height: 60
|
||||||
|
radius: {
|
||||||
|
if (Theme.cornerRadius === 0) return 0
|
||||||
|
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color _tileBgActive: Theme.primary
|
||||||
|
readonly property color _tileBgInactive:
|
||||||
|
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
|
||||||
|
Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
readonly property color _tileRingActive:
|
||||||
|
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||||
|
|
||||||
|
color: isActive ? _tileBgActive : _tileBgInactive
|
||||||
|
border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
border.width: isActive ? 1 : 1
|
||||||
|
opacity: enabled ? 1.0 : 0.6
|
||||||
|
|
||||||
|
function hoverTint(base) {
|
||||||
|
const factor = 1.2
|
||||||
|
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: mouseArea.containsMouse ? hoverTint(root.color) : "transparent"
|
||||||
|
opacity: mouseArea.containsMouse ? 0.08 : 0.0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Theme.shortDuration }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingL + 2
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: root.iconName
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: isActive ? Theme.primaryContainer : Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width - Theme.iconSize - parent.spacing
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
width: parent.width
|
||||||
|
text: root.text
|
||||||
|
style: Typography.Style.Body
|
||||||
|
color: isActive ? Theme.primaryContainer : Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
width: parent.width
|
||||||
|
text: root.secondaryText
|
||||||
|
style: Typography.Style.Caption
|
||||||
|
color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
|
||||||
|
visible: text.length > 0
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: root.enabled
|
||||||
|
onClicked: root.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Modules/ControlCenter/Components/DetailHost.qml
Normal file
52
Modules/ControlCenter/Components/DetailHost.qml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Modules.ControlCenter.Details
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string expandedSection: ""
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
width: parent.width
|
||||||
|
height: 250
|
||||||
|
y: Theme.spacingS
|
||||||
|
active: parent.height > 0
|
||||||
|
sourceComponent: {
|
||||||
|
switch (root.expandedSection) {
|
||||||
|
case "network":
|
||||||
|
case "wifi": return networkDetailComponent
|
||||||
|
case "bluetooth": return bluetoothDetailComponent
|
||||||
|
case "audioOutput": return audioOutputDetailComponent
|
||||||
|
case "audioInput": return audioInputDetailComponent
|
||||||
|
case "battery": return batteryDetailComponent
|
||||||
|
default: return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: networkDetailComponent
|
||||||
|
NetworkDetail {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: bluetoothDetailComponent
|
||||||
|
BluetoothDetail {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: audioOutputDetailComponent
|
||||||
|
AudioOutputDetail {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: audioInputDetailComponent
|
||||||
|
AudioInputDetail {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: batteryDetailComponent
|
||||||
|
BatteryDetail {}
|
||||||
|
}
|
||||||
|
}
|
||||||
236
Modules/ControlCenter/Components/EditControls.qml
Normal file
236
Modules/ControlCenter/Components/EditControls.qml
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var availableWidgets: []
|
||||||
|
|
||||||
|
signal addWidget(string widgetId)
|
||||||
|
signal resetToDefault()
|
||||||
|
signal clearAll()
|
||||||
|
|
||||||
|
height: 48
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
onAddWidget: addWidgetPopup.close()
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: addWidgetPopup
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 400
|
||||||
|
height: 300
|
||||||
|
modal: true
|
||||||
|
focus: true
|
||||||
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
border.color: Theme.primarySelected
|
||||||
|
border.width: 1
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: headerRow
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "add_circle"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: "Add Widget"
|
||||||
|
style: Typography.Style.Subtitle
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankListView {
|
||||||
|
anchors.top: headerRow.bottom
|
||||||
|
anchors.topMargin: Theme.spacingM
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
model: root.availableWidgets
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: 400 - Theme.spacingL * 2
|
||||||
|
height: 50
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: widgetMouseArea.containsMouse ? Theme.primaryHover : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: modelData.icon
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
width: 400 - Theme.spacingL * 2 - Theme.iconSize - Theme.spacingM * 3 - Theme.iconSize
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: modelData.text
|
||||||
|
style: Typography.Style.Body
|
||||||
|
color: Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: modelData.description
|
||||||
|
style: Typography.Style.Caption
|
||||||
|
color: Theme.outline
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "add"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: widgetMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
root.addWidget(modelData.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingS * 2) / 3
|
||||||
|
height: 48
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "add"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: "Add Widget"
|
||||||
|
style: Typography.Style.Button
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: addWidgetPopup.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingS * 2) / 3
|
||||||
|
height: 48
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
|
||||||
|
border.color: Theme.warning
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "settings_backup_restore"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.warning
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: "Defaults"
|
||||||
|
style: Typography.Style.Button
|
||||||
|
color: Theme.warning
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.resetToDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: (parent.width - Theme.spacingS * 2) / 3
|
||||||
|
height: 48
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||||
|
border.color: Theme.error
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "clear_all"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: "Reset"
|
||||||
|
style: Typography.Style.Button
|
||||||
|
color: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.clearAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
241
Modules/ControlCenter/Components/EditModeOverlay.qml
Normal file
241
Modules/ControlCenter/Components/EditModeOverlay.qml
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool editMode: false
|
||||||
|
property var widgetData: null
|
||||||
|
property int widgetIndex: -1
|
||||||
|
property bool showSizeControls: true
|
||||||
|
property bool isSlider: false
|
||||||
|
|
||||||
|
signal removeWidget(int index)
|
||||||
|
signal toggleWidgetSize(int index)
|
||||||
|
signal moveWidget(int fromIndex, int toIndex)
|
||||||
|
|
||||||
|
// Delete button in top-right
|
||||||
|
Rectangle {
|
||||||
|
width: 16
|
||||||
|
height: 16
|
||||||
|
radius: 8
|
||||||
|
color: Theme.error
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: -4
|
||||||
|
visible: editMode
|
||||||
|
z: 10
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "close"
|
||||||
|
size: 12
|
||||||
|
color: Theme.primaryText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.removeWidget(widgetIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size control buttons in bottom-right
|
||||||
|
Row {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: -8
|
||||||
|
spacing: 4
|
||||||
|
visible: editMode && showSizeControls
|
||||||
|
z: 10
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
radius: 12
|
||||||
|
color: (widgetData?.width || 50) === 25 ? Theme.primary : Theme.primaryContainer
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 1
|
||||||
|
visible: !isSlider
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "25"
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: (widgetData?.width || 50) === 25 ? Theme.primaryText : Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||||
|
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
|
||||||
|
widgets[widgetIndex].width = 25
|
||||||
|
SettingsData.setControlCenterWidgets(widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
radius: 12
|
||||||
|
color: (widgetData?.width || 50) === 50 ? Theme.primary : Theme.primaryContainer
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "50"
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: (widgetData?.width || 50) === 50 ? Theme.primaryText : Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||||
|
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
|
||||||
|
widgets[widgetIndex].width = 50
|
||||||
|
SettingsData.setControlCenterWidgets(widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
radius: 12
|
||||||
|
color: (widgetData?.width || 50) === 75 ? Theme.primary : Theme.primaryContainer
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 1
|
||||||
|
visible: !isSlider
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "75"
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: (widgetData?.width || 50) === 75 ? Theme.primaryText : Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||||
|
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
|
||||||
|
widgets[widgetIndex].width = 75
|
||||||
|
SettingsData.setControlCenterWidgets(widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
radius: 12
|
||||||
|
color: (widgetData?.width || 50) === 100 ? Theme.primary : Theme.primaryContainer
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "100"
|
||||||
|
font.pixelSize: 9
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: (widgetData?.width || 50) === 100 ? Theme.primaryText : Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||||
|
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
|
||||||
|
widgets[widgetIndex].width = 100
|
||||||
|
SettingsData.setControlCenterWidgets(widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrow buttons for reordering in top-left
|
||||||
|
Row {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: 4
|
||||||
|
spacing: 2
|
||||||
|
visible: editMode
|
||||||
|
z: 20
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 16
|
||||||
|
height: 16
|
||||||
|
radius: 8
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
border.color: Theme.outline
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "keyboard_arrow_left"
|
||||||
|
size: 12
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: widgetIndex > 0
|
||||||
|
opacity: enabled ? 1.0 : 0.5
|
||||||
|
onClicked: root.moveWidget(widgetIndex, widgetIndex - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 16
|
||||||
|
height: 16
|
||||||
|
radius: 8
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
border.color: Theme.outline
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "keyboard_arrow_right"
|
||||||
|
size: 12
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: widgetIndex < ((SettingsData.controlCenterWidgets?.length ?? 0) - 1)
|
||||||
|
opacity: enabled ? 1.0 : 0.5
|
||||||
|
onClicked: root.moveWidget(widgetIndex, widgetIndex + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Border highlight
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: editMode ? 1 : 0
|
||||||
|
visible: editMode
|
||||||
|
z: -1
|
||||||
|
|
||||||
|
Behavior on border.width {
|
||||||
|
NumberAnimation { duration: Theme.shortDuration }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Modules/ControlCenter/Components/HeaderPane.qml
Normal file
122
Modules/ControlCenter/Components/HeaderPane.qml
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool powerOptionsExpanded: false
|
||||||
|
property bool editMode: false
|
||||||
|
|
||||||
|
signal powerActionRequested(string action, string title, string message)
|
||||||
|
signal lockRequested()
|
||||||
|
signal editModeToggled()
|
||||||
|
|
||||||
|
implicitHeight: 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.spacingM
|
||||||
|
|
||||||
|
DankCircularImage {
|
||||||
|
id: avatarContainer
|
||||||
|
|
||||||
|
width: 64
|
||||||
|
height: 64
|
||||||
|
imageSource: {
|
||||||
|
if (PortalService.profileImage === "")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if (PortalService.profileImage.startsWith("/"))
|
||||||
|
return "file://" + PortalService.profileImage
|
||||||
|
|
||||||
|
return PortalService.profileImage
|
||||||
|
}
|
||||||
|
fallbackIcon: "person"
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: UserInfoService.fullName
|
||||||
|
|| UserInfoService.username || "User"
|
||||||
|
style: Typography.Style.Subtitle
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: (UserInfoService.uptime || "Unknown")
|
||||||
|
style: Typography.Style.Caption
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: actionButtonsRow
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.rightMargin: Theme.spacingXS
|
||||||
|
anchors.topMargin: Theme.spacingXS
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 36
|
||||||
|
iconName: "lock"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
onClicked: {
|
||||||
|
root.lockRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 36
|
||||||
|
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
onClicked: {
|
||||||
|
root.powerOptionsExpanded = !root.powerOptionsExpanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 36
|
||||||
|
iconName: "settings"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
onClicked: {
|
||||||
|
settingsModal.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 24
|
||||||
|
iconName: editMode ? "done" : "edit"
|
||||||
|
iconSize: 14
|
||||||
|
iconColor: editMode ? Theme.primary : Theme.outline
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Theme.spacingXS
|
||||||
|
onClicked: root.editModeToggled()
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Modules/ControlCenter/Components/PowerButton.qml
Normal file
52
Modules/ControlCenter/Components/PowerButton.qml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string iconName: ""
|
||||||
|
property string text: ""
|
||||||
|
|
||||||
|
signal pressed()
|
||||||
|
|
||||||
|
height: 34
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: mouseArea.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: root.iconName
|
||||||
|
size: Theme.fontSizeSmall
|
||||||
|
color: mouseArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Typography {
|
||||||
|
text: root.text
|
||||||
|
style: Typography.Style.Button
|
||||||
|
color: mouseArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onPressed: root.pressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
73
Modules/ControlCenter/Components/PowerOptionsPane.qml
Normal file
73
Modules/ControlCenter/Components/PowerOptionsPane.qml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool expanded: false
|
||||||
|
|
||||||
|
signal powerActionRequested(string action, string title, string message)
|
||||||
|
|
||||||
|
implicitHeight: expanded ? 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.expanded ? 1 : 0
|
||||||
|
opacity: root.expanded ? 1 : 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: SessionService.hibernateSupported ? Theme.spacingS : Theme.spacingL
|
||||||
|
visible: root.expanded
|
||||||
|
|
||||||
|
PowerButton {
|
||||||
|
width: SessionService.hibernateSupported ? 85 : 100
|
||||||
|
iconName: "logout"
|
||||||
|
text: "Logout"
|
||||||
|
onPressed: root.powerActionRequested("logout", "Logout", "Are you sure you want to logout?")
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerButton {
|
||||||
|
width: SessionService.hibernateSupported ? 85 : 100
|
||||||
|
iconName: "restart_alt"
|
||||||
|
text: "Restart"
|
||||||
|
onPressed: root.powerActionRequested("reboot", "Restart", "Are you sure you want to restart?")
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerButton {
|
||||||
|
width: SessionService.hibernateSupported ? 85 : 100
|
||||||
|
iconName: "bedtime"
|
||||||
|
text: "Suspend"
|
||||||
|
onPressed: root.powerActionRequested("suspend", "Suspend", "Are you sure you want to suspend?")
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerButton {
|
||||||
|
width: SessionService.hibernateSupported ? 85 : 100
|
||||||
|
iconName: "ac_unit"
|
||||||
|
text: "Hibernate"
|
||||||
|
visible: SessionService.hibernateSupported
|
||||||
|
onPressed: root.powerActionRequested("hibernate", "Hibernate", "Are you sure you want to hibernate?")
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerButton {
|
||||||
|
width: SessionService.hibernateSupported ? 85 : 100
|
||||||
|
iconName: "power_settings_new"
|
||||||
|
text: "Shutdown"
|
||||||
|
onPressed: root.powerActionRequested("poweroff", "Shutdown", "Are you sure you want to shutdown?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
Modules/ControlCenter/Components/Typography.qml
Normal file
46
Modules/ControlCenter/Components/Typography.qml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
enum Style {
|
||||||
|
Title,
|
||||||
|
Subtitle,
|
||||||
|
Body,
|
||||||
|
Caption,
|
||||||
|
Button
|
||||||
|
}
|
||||||
|
|
||||||
|
property int style: Typography.Style.Body
|
||||||
|
|
||||||
|
font.pixelSize: {
|
||||||
|
switch (style) {
|
||||||
|
case Typography.Style.Title: return Theme.fontSizeXLarge
|
||||||
|
case Typography.Style.Subtitle: return Theme.fontSizeLarge
|
||||||
|
case Typography.Style.Body: return Theme.fontSizeMedium
|
||||||
|
case Typography.Style.Caption: return Theme.fontSizeSmall
|
||||||
|
case Typography.Style.Button: return Theme.fontSizeSmall
|
||||||
|
default: return Theme.fontSizeMedium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
font.weight: {
|
||||||
|
switch (style) {
|
||||||
|
case Typography.Style.Title: return Font.Bold
|
||||||
|
case Typography.Style.Subtitle: return Font.Medium
|
||||||
|
case Typography.Style.Body: return Font.Normal
|
||||||
|
case Typography.Style.Caption: return Font.Normal
|
||||||
|
case Typography.Style.Button: return Font.Medium
|
||||||
|
default: return Font.Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color: {
|
||||||
|
switch (style) {
|
||||||
|
case Typography.Style.Caption: return Theme.surfaceVariantText
|
||||||
|
default: return Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
679
Modules/ControlCenter/Components/WidgetGrid.qml
Normal file
679
Modules/ControlCenter/Components/WidgetGrid.qml
Normal file
@@ -0,0 +1,679 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Modules.ControlCenter.Widgets
|
||||||
|
import qs.Modules.ControlCenter.Components
|
||||||
|
import "../utils/layout.js" as LayoutUtils
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool editMode: false
|
||||||
|
property string expandedSection: ""
|
||||||
|
property int expandedWidgetIndex: -1
|
||||||
|
property var model: null
|
||||||
|
|
||||||
|
signal expandClicked(var widgetData, int globalIndex)
|
||||||
|
signal removeWidget(int index)
|
||||||
|
signal moveWidget(int fromIndex, int toIndex)
|
||||||
|
signal toggleWidgetSize(int index)
|
||||||
|
|
||||||
|
spacing: editMode ? Theme.spacingL : Theme.spacingS
|
||||||
|
|
||||||
|
property var currentRowWidgets: []
|
||||||
|
property real currentRowWidth: 0
|
||||||
|
property int expandedRowIndex: -1
|
||||||
|
|
||||||
|
function calculateRowsAndWidgets() {
|
||||||
|
return LayoutUtils.calculateRowsAndWidgets(root, expandedSection, expandedWidgetIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: {
|
||||||
|
const result = root.calculateRowsAndWidgets()
|
||||||
|
root.expandedRowIndex = result.expandedRowIndex
|
||||||
|
return result.rows
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: root.width
|
||||||
|
spacing: 0
|
||||||
|
property int rowIndex: index
|
||||||
|
property var rowWidgets: modelData
|
||||||
|
property bool isSliderOnlyRow: {
|
||||||
|
const widgets = rowWidgets || []
|
||||||
|
if (widgets.length === 0) return false
|
||||||
|
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
|
||||||
|
}
|
||||||
|
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -12) : 0
|
||||||
|
bottomPadding: isSliderOnlyRow ? (root.editMode ? 4 : -12) : 0
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: rowWidgets || []
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property var widgetData: modelData
|
||||||
|
property int globalWidgetIndex: {
|
||||||
|
const widgets = SettingsData.controlCenterWidgets || []
|
||||||
|
for (var i = 0; i < widgets.length; i++) {
|
||||||
|
if (widgets[i].id === modelData.id) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
property int widgetWidth: modelData.width || 50
|
||||||
|
width: {
|
||||||
|
const baseWidth = root.width
|
||||||
|
const spacing = Theme.spacingS
|
||||||
|
if (widgetWidth <= 25) {
|
||||||
|
return (baseWidth - spacing * 3) / 4
|
||||||
|
} else if (widgetWidth <= 50) {
|
||||||
|
return (baseWidth - spacing) / 2
|
||||||
|
} else if (widgetWidth <= 75) {
|
||||||
|
return (baseWidth - spacing * 2) * 0.75
|
||||||
|
} else {
|
||||||
|
return baseWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
height: 60
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: widgetLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
property var widgetData: parent.widgetData
|
||||||
|
property int widgetIndex: parent.globalWidgetIndex
|
||||||
|
property int globalWidgetIndex: parent.globalWidgetIndex
|
||||||
|
property int widgetWidth: parent.widgetWidth
|
||||||
|
|
||||||
|
sourceComponent: {
|
||||||
|
const id = modelData.id || ""
|
||||||
|
if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
|
||||||
|
return compoundPillComponent
|
||||||
|
} else if (id === "volumeSlider") {
|
||||||
|
return audioSliderComponent
|
||||||
|
} else if (id === "brightnessSlider") {
|
||||||
|
return brightnessSliderComponent
|
||||||
|
} else if (id === "inputVolumeSlider") {
|
||||||
|
return inputAudioSliderComponent
|
||||||
|
} else if (id === "battery") {
|
||||||
|
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent
|
||||||
|
} else {
|
||||||
|
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DetailHost {
|
||||||
|
width: parent.width
|
||||||
|
height: active ? (250 + Theme.spacingS) : 0
|
||||||
|
property bool active: root.expandedSection !== "" && rowIndex === root.expandedRowIndex
|
||||||
|
visible: active
|
||||||
|
expandedSection: root.expandedSection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: compoundPillComponent
|
||||||
|
CompoundPill {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||||
|
width: parent.width
|
||||||
|
height: 60
|
||||||
|
iconName: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "wifi": {
|
||||||
|
if (NetworkService.wifiToggling) {
|
||||||
|
return "sync"
|
||||||
|
}
|
||||||
|
if (NetworkService.networkStatus === "ethernet") {
|
||||||
|
return "settings_ethernet"
|
||||||
|
}
|
||||||
|
if (NetworkService.networkStatus === "wifi") {
|
||||||
|
return NetworkService.wifiSignalIcon
|
||||||
|
}
|
||||||
|
if (NetworkService.wifiEnabled) {
|
||||||
|
return "wifi_off"
|
||||||
|
}
|
||||||
|
return "wifi_off"
|
||||||
|
}
|
||||||
|
case "bluetooth": {
|
||||||
|
if (!BluetoothService.available) {
|
||||||
|
return "bluetooth_disabled"
|
||||||
|
}
|
||||||
|
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
|
||||||
|
return "bluetooth_disabled"
|
||||||
|
}
|
||||||
|
const primaryDevice = (() => {
|
||||||
|
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||||
|
for (let device of devices) {
|
||||||
|
if (device && device.connected) {
|
||||||
|
return device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})()
|
||||||
|
if (primaryDevice) {
|
||||||
|
return BluetoothService.getDeviceIcon(primaryDevice)
|
||||||
|
}
|
||||||
|
return "bluetooth"
|
||||||
|
}
|
||||||
|
case "audioOutput": {
|
||||||
|
if (!AudioService.sink) return "volume_off"
|
||||||
|
let volume = AudioService.sink.audio.volume
|
||||||
|
let muted = AudioService.sink.audio.muted
|
||||||
|
if (muted || volume === 0.0) return "volume_off"
|
||||||
|
if (volume <= 0.33) return "volume_down"
|
||||||
|
if (volume <= 0.66) return "volume_up"
|
||||||
|
return "volume_up"
|
||||||
|
}
|
||||||
|
case "audioInput": {
|
||||||
|
if (!AudioService.source) return "mic_off"
|
||||||
|
let muted = AudioService.source.audio.muted
|
||||||
|
return muted ? "mic_off" : "mic"
|
||||||
|
}
|
||||||
|
default: return widgetDef?.icon || "help"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
primaryText: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "wifi": {
|
||||||
|
if (NetworkService.wifiToggling) {
|
||||||
|
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
|
||||||
|
}
|
||||||
|
if (NetworkService.networkStatus === "ethernet") {
|
||||||
|
return "Ethernet"
|
||||||
|
}
|
||||||
|
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID) {
|
||||||
|
return NetworkService.currentWifiSSID
|
||||||
|
}
|
||||||
|
if (NetworkService.wifiEnabled) {
|
||||||
|
return "Not connected"
|
||||||
|
}
|
||||||
|
return "WiFi off"
|
||||||
|
}
|
||||||
|
case "bluetooth": {
|
||||||
|
if (!BluetoothService.available) {
|
||||||
|
return "Bluetooth"
|
||||||
|
}
|
||||||
|
if (!BluetoothService.adapter) {
|
||||||
|
return "No adapter"
|
||||||
|
}
|
||||||
|
if (!BluetoothService.adapter.enabled) {
|
||||||
|
return "Disabled"
|
||||||
|
}
|
||||||
|
return "Enabled"
|
||||||
|
}
|
||||||
|
case "audioOutput": return AudioService.sink?.description || "No output device"
|
||||||
|
case "audioInput": return AudioService.source?.description || "No input device"
|
||||||
|
default: return widgetDef?.text || "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secondaryText: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "wifi": {
|
||||||
|
if (NetworkService.wifiToggling) {
|
||||||
|
return "Please wait..."
|
||||||
|
}
|
||||||
|
if (NetworkService.networkStatus === "ethernet") {
|
||||||
|
return "Connected"
|
||||||
|
}
|
||||||
|
if (NetworkService.networkStatus === "wifi") {
|
||||||
|
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
|
||||||
|
}
|
||||||
|
if (NetworkService.wifiEnabled) {
|
||||||
|
return "Select network"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
case "bluetooth": {
|
||||||
|
if (!BluetoothService.available) {
|
||||||
|
return "No adapters"
|
||||||
|
}
|
||||||
|
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
|
||||||
|
return "Off"
|
||||||
|
}
|
||||||
|
const primaryDevice = (() => {
|
||||||
|
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
|
||||||
|
for (let device of devices) {
|
||||||
|
if (device && device.connected) {
|
||||||
|
return device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})()
|
||||||
|
if (primaryDevice) {
|
||||||
|
return primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device"
|
||||||
|
}
|
||||||
|
return "No devices"
|
||||||
|
}
|
||||||
|
case "audioOutput": {
|
||||||
|
if (!AudioService.sink) {
|
||||||
|
return "Select device"
|
||||||
|
}
|
||||||
|
if (AudioService.sink.audio.muted) {
|
||||||
|
return "Muted"
|
||||||
|
}
|
||||||
|
return Math.round(AudioService.sink.audio.volume * 100) + "%"
|
||||||
|
}
|
||||||
|
case "audioInput": {
|
||||||
|
if (!AudioService.source) {
|
||||||
|
return "Select device"
|
||||||
|
}
|
||||||
|
if (AudioService.source.audio.muted) {
|
||||||
|
return "Muted"
|
||||||
|
}
|
||||||
|
return Math.round(AudioService.source.audio.volume * 100) + "%"
|
||||||
|
}
|
||||||
|
default: return widgetDef?.description || ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isActive: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "wifi": {
|
||||||
|
if (NetworkService.wifiToggling) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (NetworkService.networkStatus === "ethernet") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (NetworkService.networkStatus === "wifi") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return NetworkService.wifiEnabled
|
||||||
|
}
|
||||||
|
case "bluetooth": return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||||
|
case "audioOutput": return !!(AudioService.sink && !AudioService.sink.audio.muted)
|
||||||
|
case "audioInput": return !!(AudioService.source && !AudioService.source.audio.muted)
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enabled: (widgetDef?.enabled ?? true)
|
||||||
|
onToggled: {
|
||||||
|
if (root.editMode) return
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "wifi": {
|
||||||
|
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
|
||||||
|
NetworkService.toggleWifiRadio()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "bluetooth": {
|
||||||
|
if (BluetoothService.available && BluetoothService.adapter) {
|
||||||
|
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "audioOutput": {
|
||||||
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "audioInput": {
|
||||||
|
if (AudioService.source && AudioService.source.audio) {
|
||||||
|
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onExpandClicked: {
|
||||||
|
if (root.editMode) return
|
||||||
|
root.expandClicked(widgetData, widgetIndex)
|
||||||
|
}
|
||||||
|
onWheelEvent: function (wheelEvent) {
|
||||||
|
const id = widgetData.id || ""
|
||||||
|
if (id === "audioOutput") {
|
||||||
|
if (!AudioService.sink || !AudioService.sink.audio) return
|
||||||
|
let delta = wheelEvent.angleDelta.y
|
||||||
|
let currentVolume = AudioService.sink.audio.volume * 100
|
||||||
|
let newVolume
|
||||||
|
if (delta > 0)
|
||||||
|
newVolume = Math.min(100, currentVolume + 5)
|
||||||
|
else
|
||||||
|
newVolume = Math.max(0, currentVolume - 5)
|
||||||
|
AudioService.sink.audio.muted = false
|
||||||
|
AudioService.sink.audio.volume = newVolume / 100
|
||||||
|
wheelEvent.accepted = true
|
||||||
|
} else if (id === "audioInput") {
|
||||||
|
if (!AudioService.source || !AudioService.source.audio) return
|
||||||
|
let delta = wheelEvent.angleDelta.y
|
||||||
|
let currentVolume = AudioService.source.audio.volume * 100
|
||||||
|
let newVolume
|
||||||
|
if (delta > 0)
|
||||||
|
newVolume = Math.min(100, currentVolume + 5)
|
||||||
|
else
|
||||||
|
newVolume = Math.max(0, currentVolume - 5)
|
||||||
|
AudioService.source.audio.muted = false
|
||||||
|
AudioService.source.audio.volume = newVolume / 100
|
||||||
|
wheelEvent.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditModeOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
editMode: root.editMode
|
||||||
|
widgetData: parent.widgetData
|
||||||
|
widgetIndex: parent.widgetIndex
|
||||||
|
showSizeControls: true
|
||||||
|
isSlider: false
|
||||||
|
onRemoveWidget: (index) => root.removeWidget(index)
|
||||||
|
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: audioSliderComponent
|
||||||
|
Item {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||||
|
width: parent.width
|
||||||
|
height: 16
|
||||||
|
|
||||||
|
AudioSliderRow {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: 14
|
||||||
|
property color sliderTrackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
}
|
||||||
|
|
||||||
|
EditModeOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
editMode: root.editMode
|
||||||
|
widgetData: parent.widgetData
|
||||||
|
widgetIndex: parent.widgetIndex
|
||||||
|
showSizeControls: true
|
||||||
|
isSlider: true
|
||||||
|
onRemoveWidget: (index) => root.removeWidget(index)
|
||||||
|
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: brightnessSliderComponent
|
||||||
|
Item {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
width: parent.width
|
||||||
|
height: 16
|
||||||
|
|
||||||
|
BrightnessSliderRow {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: 14
|
||||||
|
property color sliderTrackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
}
|
||||||
|
|
||||||
|
EditModeOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
editMode: root.editMode
|
||||||
|
widgetData: parent.widgetData
|
||||||
|
widgetIndex: parent.widgetIndex
|
||||||
|
showSizeControls: true
|
||||||
|
isSlider: true
|
||||||
|
onRemoveWidget: (index) => root.removeWidget(index)
|
||||||
|
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: inputAudioSliderComponent
|
||||||
|
Item {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
width: parent.width
|
||||||
|
height: 16
|
||||||
|
|
||||||
|
InputAudioSliderRow {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: 14
|
||||||
|
property color sliderTrackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
}
|
||||||
|
|
||||||
|
EditModeOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
editMode: root.editMode
|
||||||
|
widgetData: parent.widgetData
|
||||||
|
widgetIndex: parent.widgetIndex
|
||||||
|
showSizeControls: true
|
||||||
|
isSlider: true
|
||||||
|
onRemoveWidget: (index) => root.removeWidget(index)
|
||||||
|
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: batteryPillComponent
|
||||||
|
BatteryPill {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
width: parent.width
|
||||||
|
height: 60
|
||||||
|
|
||||||
|
onExpandClicked: {
|
||||||
|
if (!root.editMode) {
|
||||||
|
root.expandClicked(widgetData, widgetIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditModeOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
editMode: root.editMode
|
||||||
|
widgetData: parent.widgetData
|
||||||
|
widgetIndex: parent.widgetIndex
|
||||||
|
showSizeControls: true
|
||||||
|
isSlider: false
|
||||||
|
onRemoveWidget: (index) => root.removeWidget(index)
|
||||||
|
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: smallBatteryComponent
|
||||||
|
SmallBatteryButton {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (!root.editMode) {
|
||||||
|
root.expandClicked(widgetData, widgetIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditModeOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
editMode: root.editMode
|
||||||
|
widgetData: parent.widgetData
|
||||||
|
widgetIndex: parent.widgetIndex
|
||||||
|
showSizeControls: true
|
||||||
|
isSlider: false
|
||||||
|
onRemoveWidget: (index) => root.removeWidget(index)
|
||||||
|
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: toggleButtonComponent
|
||||||
|
ToggleButton {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||||
|
width: parent.width
|
||||||
|
height: 60
|
||||||
|
|
||||||
|
iconName: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "nightMode": return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||||
|
case "darkMode": return "contrast"
|
||||||
|
case "doNotDisturb": return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
|
||||||
|
case "idleInhibitor": return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||||
|
default: return widgetDef?.icon || "help"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "nightMode": return "Night Mode"
|
||||||
|
case "darkMode": return SessionData.isLightMode ? "Light Mode" : "Dark Mode"
|
||||||
|
case "doNotDisturb": return "Do Not Disturb"
|
||||||
|
case "idleInhibitor": return SessionService.idleInhibited ? "Keeping Awake" : "Keep Awake"
|
||||||
|
default: return widgetDef?.text || "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
secondaryText: ""
|
||||||
|
|
||||||
|
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
|
||||||
|
|
||||||
|
isActive: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "nightMode": return DisplayService.nightModeEnabled || false
|
||||||
|
case "darkMode": return !SessionData.isLightMode
|
||||||
|
case "doNotDisturb": return SessionData.doNotDisturb || false
|
||||||
|
case "idleInhibitor": return SessionService.idleInhibited || false
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled: (widgetDef?.enabled ?? true) && !root.editMode
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "nightMode": {
|
||||||
|
if (DisplayService.automationAvailable) {
|
||||||
|
DisplayService.toggleNightMode()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "darkMode": {
|
||||||
|
Theme.toggleLightMode()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "doNotDisturb": {
|
||||||
|
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "idleInhibitor": {
|
||||||
|
SessionService.toggleIdleInhibit()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditModeOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
editMode: root.editMode
|
||||||
|
widgetData: parent.widgetData
|
||||||
|
widgetIndex: parent.widgetIndex
|
||||||
|
showSizeControls: true
|
||||||
|
isSlider: false
|
||||||
|
onRemoveWidget: (index) => root.removeWidget(index)
|
||||||
|
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: smallToggleComponent
|
||||||
|
SmallToggleButton {
|
||||||
|
property var widgetData: parent.widgetData || {}
|
||||||
|
property int widgetIndex: parent.widgetIndex || 0
|
||||||
|
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
|
||||||
|
iconName: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "nightMode": return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||||
|
case "darkMode": return "contrast"
|
||||||
|
case "doNotDisturb": return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
|
||||||
|
case "idleInhibitor": return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||||
|
default: return widgetDef?.icon || "help"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
|
||||||
|
|
||||||
|
isActive: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "nightMode": return DisplayService.nightModeEnabled || false
|
||||||
|
case "darkMode": return !SessionData.isLightMode
|
||||||
|
case "doNotDisturb": return SessionData.doNotDisturb || false
|
||||||
|
case "idleInhibitor": return SessionService.idleInhibited || false
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled: (widgetDef?.enabled ?? true) && !root.editMode
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
switch (widgetData.id || "") {
|
||||||
|
case "nightMode": {
|
||||||
|
if (DisplayService.automationAvailable) {
|
||||||
|
DisplayService.toggleNightMode()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "darkMode": {
|
||||||
|
Theme.toggleLightMode()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "doNotDisturb": {
|
||||||
|
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "idleInhibitor": {
|
||||||
|
SessionService.toggleIdleInhibit()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditModeOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
editMode: root.editMode
|
||||||
|
widgetData: parent.widgetData
|
||||||
|
widgetIndex: parent.widgetIndex
|
||||||
|
showSizeControls: true
|
||||||
|
isSlider: false
|
||||||
|
onRemoveWidget: (index) => root.removeWidget(index)
|
||||||
|
onToggleWidgetSize: (index) => root.toggleWidgetSize(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,10 +10,12 @@ import qs.Common
|
|||||||
import qs.Modules.ControlCenter
|
import qs.Modules.ControlCenter
|
||||||
import qs.Modules.ControlCenter.Widgets
|
import qs.Modules.ControlCenter.Widgets
|
||||||
import qs.Modules.ControlCenter.Details
|
import qs.Modules.ControlCenter.Details
|
||||||
import qs.Modules.ControlCenter.Details 1.0 as Details
|
|
||||||
import qs.Modules.TopBar
|
import qs.Modules.TopBar
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import qs.Modules.ControlCenter.Components
|
||||||
|
import qs.Modules.ControlCenter.Models
|
||||||
|
import "./utils/state.js" as StateUtils
|
||||||
|
|
||||||
DankPopout {
|
DankPopout {
|
||||||
id: root
|
id: root
|
||||||
@@ -22,37 +24,26 @@ DankPopout {
|
|||||||
property bool powerOptionsExpanded: false
|
property bool powerOptionsExpanded: false
|
||||||
property string triggerSection: "right"
|
property string triggerSection: "right"
|
||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
|
property bool editMode: false
|
||||||
|
property int expandedWidgetIndex: -1
|
||||||
|
|
||||||
|
signal powerActionRequested(string action, string title, string message)
|
||||||
|
signal lockRequested
|
||||||
|
|
||||||
readonly property color _containerBg: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
|
readonly property color _containerBg: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
|
||||||
function setTriggerPosition(x, y, width, section, screen) {
|
function setTriggerPosition(x, y, width, section, screen) {
|
||||||
triggerX = x
|
StateUtils.setTriggerPosition(root, x, y, width, section, screen)
|
||||||
triggerY = y
|
|
||||||
triggerWidth = width
|
|
||||||
triggerSection = section
|
|
||||||
triggerScreen = screen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openWithSection(section) {
|
function openWithSection(section) {
|
||||||
if (shouldBeVisible) {
|
StateUtils.openWithSection(root, section)
|
||||||
close()
|
|
||||||
} else {
|
|
||||||
expandedSection = section
|
|
||||||
open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSection(section) {
|
function toggleSection(section) {
|
||||||
if (expandedSection === section) {
|
StateUtils.toggleSection(root, section)
|
||||||
expandedSection = ""
|
|
||||||
} else {
|
|
||||||
expandedSection = section
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signal powerActionRequested(string action, string title, string message)
|
|
||||||
signal lockRequested
|
|
||||||
|
|
||||||
popupWidth: 550
|
popupWidth: 550
|
||||||
popupHeight: Math.min((triggerScreen?.height ?? 1080) - 100, contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400)
|
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
|
triggerX: (triggerScreen?.width ?? 1920) - 600 - Theme.spacingL
|
||||||
@@ -73,20 +64,24 @@ DankPopout {
|
|||||||
} else {
|
} else {
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
NetworkService.autoRefreshEnabled = false
|
NetworkService.autoRefreshEnabled = false
|
||||||
if (BluetoothService.adapter
|
if (BluetoothService.adapter && BluetoothService.adapter.discovering)
|
||||||
&& BluetoothService.adapter.discovering)
|
|
||||||
BluetoothService.adapter.discovering = false
|
BluetoothService.adapter.discovering = false
|
||||||
|
editMode = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WidgetModel {
|
||||||
|
id: widgetModel
|
||||||
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: controlContent
|
id: controlContent
|
||||||
|
|
||||||
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
|
implicitHeight: mainColumn.implicitHeight + Theme.spacingM
|
||||||
property alias bluetoothCodecSelector: bluetoothCodecSelector
|
property alias bluetoothCodecSelector: bluetoothCodecSelector
|
||||||
|
|
||||||
color: {
|
color: {
|
||||||
const transparency = Theme.popupTransparency || 0.92
|
const transparency = Theme.popupTransparency || 0.92
|
||||||
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
|
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1)
|
||||||
@@ -106,621 +101,61 @@ DankPopout {
|
|||||||
y: Theme.spacingL
|
y: Theme.spacingL
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Rectangle {
|
HeaderPane {
|
||||||
|
id: headerPane
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 90
|
powerOptionsExpanded: root.powerOptionsExpanded
|
||||||
radius: Theme.cornerRadius
|
editMode: root.editMode
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r,
|
onPowerOptionsExpandedChanged: root.powerOptionsExpanded = powerOptionsExpanded
|
||||||
Theme.surfaceVariant.g,
|
onEditModeToggled: root.editMode = !root.editMode
|
||||||
Theme.surfaceVariant.b,
|
onPowerActionRequested: (action, title, message) => root.powerActionRequested(action, title, message)
|
||||||
Theme.getContentBackgroundAlpha() * 0.4)
|
onLockRequested: {
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
root.close()
|
||||||
Theme.outline.b, 0.08)
|
root.lockRequested()
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.leftMargin: Theme.spacingL
|
|
||||||
anchors.rightMargin: Theme.spacingL
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankCircularImage {
|
|
||||||
id: avatarContainer
|
|
||||||
|
|
||||||
width: 64
|
|
||||||
height: 64
|
|
||||||
imageSource: {
|
|
||||||
if (PortalService.profileImage === "")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
if (PortalService.profileImage.startsWith("/"))
|
|
||||||
return "file://" + PortalService.profileImage
|
|
||||||
|
|
||||||
return PortalService.profileImage
|
|
||||||
}
|
|
||||||
fallbackIcon: "person"
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
width: actionButtonsRow.implicitWidth + Theme.spacingM * 2
|
|
||||||
height: 48
|
|
||||||
radius: Theme.cornerRadius + 2
|
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.6)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: actionButtonsRow
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: batteryContentRow.implicitWidth
|
|
||||||
height: 36
|
|
||||||
visible: BatteryService.batteryAvailable
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: batteryContentRow
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: {
|
|
||||||
if (batteryMouseArea.containsMouse) {
|
|
||||||
return Theme.primary
|
|
||||||
}
|
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
|
||||||
return Theme.error
|
|
||||||
}
|
|
||||||
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
|
||||||
return Theme.primary
|
|
||||||
}
|
|
||||||
return Theme.surfaceText
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: `${BatteryService.batteryLevel}%`
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: {
|
|
||||||
if (batteryMouseArea.containsMouse) {
|
|
||||||
return Theme.primary
|
|
||||||
}
|
|
||||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
|
||||||
return Theme.error
|
|
||||||
}
|
|
||||||
if (BatteryService.isCharging) {
|
|
||||||
return Theme.primary
|
|
||||||
}
|
|
||||||
return Theme.surfaceText
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: batteryMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
const globalPos = mapToGlobal(0, 0)
|
|
||||||
const currentScreen = root.triggerScreen || Screen
|
|
||||||
const screenX = currentScreen.x || 0
|
|
||||||
const relativeX = globalPos.x - screenX
|
|
||||||
controlCenterBatteryPopout.setTriggerPosition(relativeX, 123 + Theme.spacingXS, width, "right", currentScreen)
|
|
||||||
|
|
||||||
if (controlCenterBatteryPopout.shouldBeVisible) {
|
|
||||||
controlCenterBatteryPopout.close()
|
|
||||||
} else {
|
|
||||||
controlCenterBatteryPopout.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
buttonSize: 36
|
|
||||||
iconName: "lock"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
onClicked: {
|
|
||||||
root.close()
|
|
||||||
root.lockRequested()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
buttonSize: 36
|
|
||||||
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
onClicked: {
|
|
||||||
root.powerOptionsExpanded = !root.powerOptionsExpanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
buttonSize: 36
|
|
||||||
iconName: "settings"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
onClicked: {
|
|
||||||
root.close()
|
|
||||||
settingsModal.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
PowerOptionsPane {
|
||||||
|
id: powerOptionsPane
|
||||||
width: parent.width
|
width: parent.width
|
||||||
implicitHeight: root.powerOptionsExpanded ? 60 : 0
|
expanded: root.powerOptionsExpanded
|
||||||
height: implicitHeight
|
onPowerActionRequested: (action, title, message) => {
|
||||||
clip: true
|
root.powerOptionsExpanded = false
|
||||||
|
root.close()
|
||||||
Rectangle {
|
root.powerActionRequested(action, title, message)
|
||||||
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: SessionService.hibernateSupported ? Theme.spacingS : Theme.spacingL
|
|
||||||
visible: root.powerOptionsExpanded
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: SessionService.hibernateSupported ? 85 : 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: SessionService.hibernateSupported ? 85 : 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: SessionService.hibernateSupported ? 85 : 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: SessionService.hibernateSupported ? 85 : 100
|
|
||||||
height: 34
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: hibernateButton.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)
|
|
||||||
visible: SessionService.hibernateSupported
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "ac_unit"
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: hibernateButton.containsMouse ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "Hibernate"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: hibernateButton.containsMouse ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: hibernateButton
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onPressed: {
|
|
||||||
root.powerOptionsExpanded = false
|
|
||||||
root.close()
|
|
||||||
root.powerActionRequested(
|
|
||||||
"hibernate", "Hibernate",
|
|
||||||
"Are you sure you want to hibernate?")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: SessionService.hibernateSupported ? 85 : 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?")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WidgetGrid {
|
||||||
|
id: widgetGrid
|
||||||
|
width: parent.width
|
||||||
|
editMode: root.editMode
|
||||||
|
expandedSection: root.expandedSection
|
||||||
|
expandedWidgetIndex: root.expandedWidgetIndex
|
||||||
|
model: widgetModel
|
||||||
|
onExpandClicked: (widgetData, globalIndex) => {
|
||||||
|
root.expandedWidgetIndex = globalIndex
|
||||||
|
root.toggleSection(widgetData.id)
|
||||||
|
}
|
||||||
|
onRemoveWidget: (index) => widgetModel.removeWidget(index)
|
||||||
|
onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex)
|
||||||
|
onToggleWidgetSize: (index) => widgetModel.toggleWidgetSize(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
EditControls {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: audioSliderRow.implicitHeight
|
visible: editMode
|
||||||
|
availableWidgets: {
|
||||||
Row {
|
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id)
|
||||||
id: audioSliderRow
|
return widgetModel.baseWidgetDefinitions.filter(w => !existingIds.includes(w.id))
|
||||||
x: -Theme.spacingS
|
|
||||||
width: parent.width + Theme.spacingS * 2
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
AudioSliderRow {
|
|
||||||
width: SettingsData.hideBrightnessSlider ? parent.width - Theme.spacingS : (parent.width - Theme.spacingM) / 2
|
|
||||||
property color sliderTrackColor: root._containerBg
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
onExpandClicked: root.toggleSection("network")
|
|
||||||
}
|
|
||||||
|
|
||||||
BluetoothPill {
|
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
|
||||||
expanded: root.expandedSection === "bluetooth"
|
|
||||||
onExpandClicked: {
|
|
||||||
if (!BluetoothService.available) return
|
|
||||||
root.toggleSection("bluetooth")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
onExpandClicked: root.toggleSection("audio_output")
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioInputPill {
|
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
|
||||||
expanded: root.expandedSection === "audio_input"
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
onAddWidget: (widgetId) => widgetModel.addWidget(widgetId)
|
||||||
|
onResetToDefault: () => widgetModel.resetToDefault()
|
||||||
|
onClearAll: () => widgetModel.clearAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Details.BluetoothCodecSelector {
|
BluetoothCodecSelector {
|
||||||
id: bluetoothCodecSelector
|
id: bluetoothCodecSelector
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
z: 10000
|
z: 10000
|
||||||
@@ -728,10 +163,6 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BatteryPopout {
|
|
||||||
id: controlCenterBatteryPopout
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: networkDetailComponent
|
id: networkDetailComponent
|
||||||
NetworkDetail {}
|
NetworkDetail {}
|
||||||
@@ -761,4 +192,9 @@ DankPopout {
|
|||||||
id: audioInputDetailComponent
|
id: audioInputDetailComponent
|
||||||
AudioInputDetail {}
|
AudioInputDetail {}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Component {
|
||||||
|
id: batteryDetailComponent
|
||||||
|
BatteryDetail {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,12 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
implicitHeight: headerRow.height + volumeSlider.height + audioContent.height + Theme.spacingM
|
property bool hasInputVolumeSliderInCC: {
|
||||||
|
const widgets = SettingsData.controlCenterWidgets || []
|
||||||
|
return widgets.some(widget => widget.id === "inputVolumeSlider")
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
@@ -43,6 +48,7 @@ Rectangle {
|
|||||||
anchors.topMargin: Theme.spacingXS
|
anchors.topMargin: Theme.spacingXS
|
||||||
height: 35
|
height: 35
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
visible: !hasInputVolumeSliderInCC
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize + Theme.spacingS * 2
|
width: Theme.iconSize + Theme.spacingS * 2
|
||||||
@@ -106,12 +112,12 @@ Rectangle {
|
|||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
id: audioContent
|
id: audioContent
|
||||||
anchors.top: volumeSlider.bottom
|
anchors.top: hasInputVolumeSliderInCC ? headerRow.bottom : volumeSlider.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.margins: Theme.spacingM
|
anchors.margins: Theme.spacingM
|
||||||
anchors.topMargin: Theme.spacingS
|
anchors.topMargin: hasInputVolumeSliderInCC ? Theme.spacingM : Theme.spacingS
|
||||||
contentHeight: audioColumn.height
|
contentHeight: audioColumn.height
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
implicitHeight: headerRow.height + audioContent.height + Theme.spacingM
|
property bool hasVolumeSliderInCC: {
|
||||||
|
const widgets = SettingsData.controlCenterWidgets || []
|
||||||
|
return widgets.some(widget => widget.id === "volumeSlider")
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: headerRow.height + (!hasVolumeSliderInCC ? volumeSlider.height : 0) + audioContent.height + Theme.spacingM
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
@@ -32,15 +37,92 @@ Rectangle {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: volumeSlider
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: headerRow.bottom
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.topMargin: Theme.spacingXS
|
||||||
|
height: 35
|
||||||
|
spacing: 0
|
||||||
|
visible: !hasVolumeSliderInCC
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Theme.iconSize + Theme.spacingS * 2
|
||||||
|
height: Theme.iconSize + Theme.spacingS * 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||||
|
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation { duration: Theme.shortDuration }
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: iconArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: {
|
||||||
|
if (!AudioService.sink || !AudioService.sink.audio) return "volume_off"
|
||||||
|
let muted = AudioService.sink.audio.muted
|
||||||
|
let volume = AudioService.sink.audio.volume
|
||||||
|
if (muted || volume === 0.0) return "volume_off"
|
||||||
|
if (volume <= 0.33) return "volume_down"
|
||||||
|
if (volume <= 0.66) return "volume_up"
|
||||||
|
return "volume_up"
|
||||||
|
}
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: AudioService.sink && AudioService.sink.audio && !AudioService.sink.audio.muted && AudioService.sink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
readonly property real actualVolumePercent: AudioService.sink && AudioService.sink.audio ? Math.round(AudioService.sink.audio.volume * 100) : 0
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||||
|
enabled: AudioService.sink && AudioService.sink.audio
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||||
|
showValue: true
|
||||||
|
unit: "%"
|
||||||
|
valueOverride: actualVolumePercent
|
||||||
|
thumbOutlineColor: Theme.surfaceVariant
|
||||||
|
|
||||||
|
onSliderValueChanged: function(newValue) {
|
||||||
|
if (AudioService.sink && AudioService.sink.audio) {
|
||||||
|
AudioService.sink.audio.volume = newValue / 100
|
||||||
|
if (newValue > 0 && AudioService.sink.audio.muted) {
|
||||||
|
AudioService.sink.audio.muted = false
|
||||||
|
}
|
||||||
|
AudioService.volumeChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
id: audioContent
|
id: audioContent
|
||||||
anchors.top: headerRow.bottom
|
anchors.top: volumeSlider.visible ? volumeSlider.bottom : headerRow.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.margins: Theme.spacingM
|
anchors.margins: Theme.spacingM
|
||||||
anchors.topMargin: Theme.spacingM
|
anchors.topMargin: volumeSlider.visible ? Theme.spacingS : Theme.spacingM
|
||||||
contentHeight: audioColumn.height
|
contentHeight: audioColumn.height
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
|||||||
260
Modules/ControlCenter/Details/BatteryDetail.qml
Normal file
260
Modules/ControlCenter/Details/BatteryDetail.qml
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.UPower
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
function isActiveProfile(profile) {
|
||||||
|
if (typeof PowerProfiles === "undefined") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return PowerProfiles.profile === profile
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProfile(profile) {
|
||||||
|
if (typeof PowerProfiles === "undefined") {
|
||||||
|
ToastService.showError("power-profiles-daemon not available")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
PowerProfiles.profile = profile
|
||||||
|
if (PowerProfiles.profile !== profile) {
|
||||||
|
ToastService.showError("Failed to set power profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: contentColumn
|
||||||
|
width: parent.width - Theme.spacingL * 2
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: headerRow
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: BatteryService.getBatteryIcon()
|
||||||
|
size: Theme.iconSizeLarge
|
||||||
|
color: {
|
||||||
|
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||||
|
return Theme.error
|
||||||
|
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||||
|
return Theme.primary
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: parent.width - Theme.iconSizeLarge - Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : "Power"
|
||||||
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
|
color: {
|
||||||
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||||
|
return Theme.error
|
||||||
|
}
|
||||||
|
if (BatteryService.isCharging) {
|
||||||
|
return Theme.primary
|
||||||
|
}
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
font.weight: Font.Bold
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: BatteryService.batteryAvailable ? BatteryService.batteryStatus : "Management"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: {
|
||||||
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||||
|
return Theme.error
|
||||||
|
}
|
||||||
|
if (BatteryService.isCharging) {
|
||||||
|
return Theme.primary
|
||||||
|
}
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (!BatteryService.batteryAvailable) return "Power profile management available"
|
||||||
|
const time = BatteryService.formatTimeRemaining()
|
||||||
|
if (time !== "Unknown") {
|
||||||
|
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
visible: text.length > 0
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: BatteryService.batteryAvailable
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: 64
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||||
|
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Health"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: BatteryService.batteryHealth
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: {
|
||||||
|
if (BatteryService.batteryHealth === "N/A") {
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
const healthNum = parseInt(BatteryService.batteryHealth)
|
||||||
|
return healthNum < 80 ? Theme.error : Theme.surfaceText
|
||||||
|
}
|
||||||
|
font.weight: Font.Bold
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
height: 64
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||||
|
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Capacity"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : "Unknown"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Bold
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance]
|
||||||
|
property int currentProfileIndex: {
|
||||||
|
if (typeof PowerProfiles === "undefined") return 1
|
||||||
|
return profileModel.findIndex(profile => isActiveProfile(profile))
|
||||||
|
}
|
||||||
|
|
||||||
|
model: profileModel.map(profile => Theme.getPowerProfileLabel(profile))
|
||||||
|
currentIndex: currentProfileIndex
|
||||||
|
selectionMode: "single"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected) return
|
||||||
|
setProfile(profileModel[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: degradationContent.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
||||||
|
border.color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
|
||||||
|
border.width: 1
|
||||||
|
visible: (typeof PowerProfiles !== "undefined") && PowerProfiles.degradationReason !== PerformanceDegradationReason.None
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: degradationContent
|
||||||
|
width: parent.width - Theme.spacingL * 2
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "warning"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Power Profile Degradation"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.error
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: (typeof PowerProfiles !== "undefined") ? PerformanceDegradationReason.toString(PowerProfiles.degradationReason) : ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8)
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
139
Modules/ControlCenter/Models/WidgetModel.qml
Normal file
139
Modules/ControlCenter/Models/WidgetModel.qml
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import "../utils/widgets.js" as WidgetUtils
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property var baseWidgetDefinitions: [
|
||||||
|
{
|
||||||
|
"id": "nightMode",
|
||||||
|
"text": "Night Mode",
|
||||||
|
"description": "Blue light filter",
|
||||||
|
"icon": "nightlight",
|
||||||
|
"type": "toggle",
|
||||||
|
"enabled": DisplayService.automationAvailable,
|
||||||
|
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "darkMode",
|
||||||
|
"text": "Dark Mode",
|
||||||
|
"description": "System theme toggle",
|
||||||
|
"icon": "contrast",
|
||||||
|
"type": "toggle",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doNotDisturb",
|
||||||
|
"text": "Do Not Disturb",
|
||||||
|
"description": "Block notifications",
|
||||||
|
"icon": "do_not_disturb_on",
|
||||||
|
"type": "toggle",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "idleInhibitor",
|
||||||
|
"text": "Keep Awake",
|
||||||
|
"description": "Prevent screen timeout",
|
||||||
|
"icon": "motion_sensor_active",
|
||||||
|
"type": "toggle",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wifi",
|
||||||
|
"text": "Network",
|
||||||
|
"description": "Wi-Fi and Ethernet connection",
|
||||||
|
"icon": "wifi",
|
||||||
|
"type": "connection",
|
||||||
|
"enabled": NetworkService.wifiAvailable,
|
||||||
|
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bluetooth",
|
||||||
|
"text": "Bluetooth",
|
||||||
|
"description": "Device connections",
|
||||||
|
"icon": "bluetooth",
|
||||||
|
"type": "connection",
|
||||||
|
"enabled": BluetoothService.available,
|
||||||
|
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "audioOutput",
|
||||||
|
"text": "Audio Output",
|
||||||
|
"description": "Speaker settings",
|
||||||
|
"icon": "volume_up",
|
||||||
|
"type": "connection",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "audioInput",
|
||||||
|
"text": "Audio Input",
|
||||||
|
"description": "Microphone settings",
|
||||||
|
"icon": "mic",
|
||||||
|
"type": "connection",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "volumeSlider",
|
||||||
|
"text": "Volume Slider",
|
||||||
|
"description": "Audio volume control",
|
||||||
|
"icon": "volume_up",
|
||||||
|
"type": "slider",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "brightnessSlider",
|
||||||
|
"text": "Brightness Slider",
|
||||||
|
"description": "Display brightness control",
|
||||||
|
"icon": "brightness_6",
|
||||||
|
"type": "slider",
|
||||||
|
"enabled": DisplayService.brightnessAvailable,
|
||||||
|
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "inputVolumeSlider",
|
||||||
|
"text": "Input Volume Slider",
|
||||||
|
"description": "Microphone volume control",
|
||||||
|
"icon": "mic",
|
||||||
|
"type": "slider",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "battery",
|
||||||
|
"text": "Battery",
|
||||||
|
"description": "Battery and power management",
|
||||||
|
"icon": "battery_std",
|
||||||
|
"type": "action",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
function getWidgetForId(widgetId) {
|
||||||
|
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addWidget(widgetId) {
|
||||||
|
WidgetUtils.addWidget(widgetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeWidget(index) {
|
||||||
|
WidgetUtils.removeWidget(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleWidgetSize(index) {
|
||||||
|
WidgetUtils.toggleWidgetSize(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveWidget(fromIndex, toIndex) {
|
||||||
|
WidgetUtils.moveWidget(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetToDefault() {
|
||||||
|
WidgetUtils.resetToDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAll() {
|
||||||
|
WidgetUtils.clearAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Modules/ControlCenter/Widgets/BatteryPill.qml
Normal file
48
Modules/ControlCenter/Widgets/BatteryPill.qml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
import qs.Modules.ControlCenter.Widgets
|
||||||
|
|
||||||
|
CompoundPill {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
iconName: BatteryService.getBatteryIcon()
|
||||||
|
|
||||||
|
isActive: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||||
|
|
||||||
|
primaryText: {
|
||||||
|
if (!BatteryService.batteryAvailable) {
|
||||||
|
return "No battery"
|
||||||
|
}
|
||||||
|
return "Battery"
|
||||||
|
}
|
||||||
|
|
||||||
|
secondaryText: {
|
||||||
|
if (!BatteryService.batteryAvailable) {
|
||||||
|
return "Not available"
|
||||||
|
}
|
||||||
|
if (BatteryService.isCharging) {
|
||||||
|
return `${BatteryService.batteryLevel}% • Charging`
|
||||||
|
}
|
||||||
|
if (BatteryService.isPluggedIn) {
|
||||||
|
return `${BatteryService.batteryLevel}% • Plugged in`
|
||||||
|
}
|
||||||
|
return `${BatteryService.batteryLevel}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
iconColor: {
|
||||||
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||||
|
return Theme.error
|
||||||
|
}
|
||||||
|
if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|
||||||
|
return Theme.primary
|
||||||
|
}
|
||||||
|
return Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggled: {
|
||||||
|
expandClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -40,8 +39,11 @@ Rectangle {
|
|||||||
readonly property color _labelPrimary: Theme.surfaceText
|
readonly property color _labelPrimary: Theme.surfaceText
|
||||||
readonly property color _labelSecondary: Theme.surfaceVariantText
|
readonly property color _labelSecondary: Theme.surfaceVariantText
|
||||||
readonly property color _tileBgActive: Theme.primary
|
readonly property color _tileBgActive: Theme.primary
|
||||||
readonly property color _tileBgInactive:
|
readonly property color _tileBgInactive: {
|
||||||
Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, 0.85)
|
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)
|
||||||
|
}
|
||||||
readonly property color _tileRingActive:
|
readonly property color _tileRingActive:
|
||||||
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||||
readonly property color _tileRingInactive:
|
readonly property color _tileRingInactive:
|
||||||
|
|||||||
81
Modules/ControlCenter/Widgets/InputAudioSliderRow.qml
Normal file
81
Modules/ControlCenter/Widgets/InputAudioSliderRow.qml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var defaultSource: AudioService.source
|
||||||
|
property color sliderTrackColor: "transparent"
|
||||||
|
|
||||||
|
height: 40
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Theme.iconSize + Theme.spacingS * 2
|
||||||
|
height: Theme.iconSize + Theme.spacingS * 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: (Theme.iconSize + Theme.spacingS * 2) / 2
|
||||||
|
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation { duration: Theme.shortDuration }
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: iconArea
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: defaultSource !== null
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (defaultSource) {
|
||||||
|
defaultSource.audio.muted = !defaultSource.audio.muted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: {
|
||||||
|
if (!defaultSource) return "mic_off"
|
||||||
|
|
||||||
|
let volume = defaultSource.audio.volume
|
||||||
|
let muted = defaultSource.audio.muted
|
||||||
|
|
||||||
|
if (muted || volume === 0.0) return "mic_off"
|
||||||
|
return "mic"
|
||||||
|
}
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: defaultSource && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSlider {
|
||||||
|
readonly property real actualVolumePercent: defaultSource ? Math.round(defaultSource.audio.volume * 100) : 0
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||||
|
enabled: defaultSource !== null
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
value: defaultSource ? Math.min(100, Math.round(defaultSource.audio.volume * 100)) : 0
|
||||||
|
showValue: true
|
||||||
|
unit: "%"
|
||||||
|
valueOverride: actualVolumePercent
|
||||||
|
thumbOutlineColor: Theme.surfaceContainer
|
||||||
|
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
onSliderValueChanged: function(newValue) {
|
||||||
|
if (defaultSource) {
|
||||||
|
defaultSource.audio.volume = newValue / 100.0
|
||||||
|
if (newValue > 0 && defaultSource.audio.muted) {
|
||||||
|
defaultSource.audio.muted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
105
Modules/ControlCenter/Widgets/SmallBatteryButton.qml
Normal file
105
Modules/ControlCenter/Widgets/SmallBatteryButton.qml
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool isActive: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||||
|
property bool enabled: BatteryService.batteryAvailable
|
||||||
|
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
|
||||||
|
height: 48
|
||||||
|
radius: {
|
||||||
|
if (Theme.cornerRadius === 0) return 0
|
||||||
|
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
function hoverTint(base) {
|
||||||
|
const factor = 1.2
|
||||||
|
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color _tileBgActive: Theme.primary
|
||||||
|
readonly property color _tileBgInactive:
|
||||||
|
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
|
||||||
|
Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
readonly property color _tileRingActive:
|
||||||
|
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||||
|
readonly property color _tileIconActive: Theme.primaryContainer
|
||||||
|
readonly property color _tileIconInactive: Theme.primary
|
||||||
|
|
||||||
|
color: isActive ? _tileBgActive : _tileBgInactive
|
||||||
|
border.color: isActive ? _tileRingActive : "transparent"
|
||||||
|
border.width: isActive ? 1 : 0
|
||||||
|
antialiasing: true
|
||||||
|
opacity: enabled ? 1.0 : 0.6
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: parent.radius
|
||||||
|
color: hoverTint(root.color)
|
||||||
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
|
visible: opacity > 0
|
||||||
|
antialiasing: true
|
||||||
|
Behavior on opacity { NumberAnimation { duration: Theme.shortDuration } }
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: BatteryService.getBatteryIcon()
|
||||||
|
size: parent.parent.width * 0.25
|
||||||
|
color: {
|
||||||
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||||
|
return Theme.error
|
||||||
|
}
|
||||||
|
return isActive ? _tileIconActive : _tileIconInactive
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : ""
|
||||||
|
font.pixelSize: parent.parent.width * 0.15
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: {
|
||||||
|
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||||
|
return Theme.error
|
||||||
|
}
|
||||||
|
return isActive ? _tileIconActive : _tileIconInactive
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: BatteryService.batteryAvailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: root.enabled
|
||||||
|
onClicked: root.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Modules/ControlCenter/Widgets/SmallToggleButton.qml
Normal file
83
Modules/ControlCenter/Widgets/SmallToggleButton.qml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string iconName: ""
|
||||||
|
property bool isActive: false
|
||||||
|
property bool enabled: true
|
||||||
|
property real iconRotation: 0
|
||||||
|
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
width: parent ? ((parent.width - parent.spacing * 3) / 4) : 48
|
||||||
|
height: 48
|
||||||
|
radius: {
|
||||||
|
if (Theme.cornerRadius === 0) return 0
|
||||||
|
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
function hoverTint(base) {
|
||||||
|
const factor = 1.2
|
||||||
|
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color _tileBgActive: Theme.primary
|
||||||
|
readonly property color _tileBgInactive:
|
||||||
|
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
|
||||||
|
Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
readonly property color _tileRingActive:
|
||||||
|
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||||
|
readonly property color _tileIconActive: Theme.primaryContainer
|
||||||
|
readonly property color _tileIconInactive: Theme.primary
|
||||||
|
|
||||||
|
color: isActive ? _tileBgActive : _tileBgInactive
|
||||||
|
border.color: isActive ? _tileRingActive : "transparent"
|
||||||
|
border.width: isActive ? 1 : 0
|
||||||
|
antialiasing: true
|
||||||
|
opacity: enabled ? 1.0 : 0.6
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: parent.radius
|
||||||
|
color: hoverTint(root.color)
|
||||||
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
|
visible: opacity > 0
|
||||||
|
antialiasing: true
|
||||||
|
Behavior on opacity { NumberAnimation { duration: Theme.shortDuration } }
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: iconName
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: isActive ? _tileIconActive : _tileIconInactive
|
||||||
|
rotation: iconRotation
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: root.enabled
|
||||||
|
onClicked: root.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -12,15 +11,27 @@ Rectangle {
|
|||||||
property bool isActive: false
|
property bool isActive: false
|
||||||
property bool enabled: true
|
property bool enabled: true
|
||||||
property string secondaryText: ""
|
property string secondaryText: ""
|
||||||
|
property real iconRotation: 0
|
||||||
|
|
||||||
signal clicked()
|
signal clicked()
|
||||||
|
|
||||||
width: parent ? parent.width : 200
|
width: parent ? parent.width : 200
|
||||||
height: 60
|
height: 60
|
||||||
radius: Theme.cornerRadius
|
radius: {
|
||||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.6)
|
if (Theme.cornerRadius === 0) return 0
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
|
||||||
border.width: 1
|
}
|
||||||
|
|
||||||
|
readonly property color _tileBgActive: Theme.primary
|
||||||
|
readonly property color _tileBgInactive:
|
||||||
|
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b,
|
||||||
|
Theme.getContentBackgroundAlpha() * 0.60)
|
||||||
|
readonly property color _tileRingActive:
|
||||||
|
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
|
||||||
|
|
||||||
|
color: isActive ? _tileBgActive : _tileBgInactive
|
||||||
|
border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
border.width: isActive ? 1 : 1
|
||||||
opacity: enabled ? 1.0 : 0.6
|
opacity: enabled ? 1.0 : 0.6
|
||||||
|
|
||||||
function hoverTint(base) {
|
function hoverTint(base) {
|
||||||
@@ -52,8 +63,9 @@ Rectangle {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
name: root.iconName
|
name: root.iconName
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: Theme.primary
|
color: isActive ? Theme.primaryContainer : Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
rotation: root.iconRotation
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -70,7 +82,7 @@ Rectangle {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
text: root.text
|
text: root.text
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceText
|
color: isActive ? Theme.primaryContainer : Theme.surfaceText
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
wrapMode: Text.NoWrap
|
wrapMode: Text.NoWrap
|
||||||
@@ -80,7 +92,7 @@ Rectangle {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
text: root.secondaryText
|
text: root.secondaryText
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceVariantText
|
color: isActive ? Theme.primaryContainer : Theme.surfaceVariantText
|
||||||
visible: text.length > 0
|
visible: text.length > 0
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
wrapMode: Text.NoWrap
|
wrapMode: Text.NoWrap
|
||||||
@@ -104,4 +116,11 @@ Rectangle {
|
|||||||
easing.type: Theme.standardEasing
|
easing.type: Theme.standardEasing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
45
Modules/ControlCenter/utils/layout.js
Normal file
45
Modules/ControlCenter/utils/layout.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
function calculateRowsAndWidgets(controlCenterColumn, expandedSection, expandedWidgetIndex) {
|
||||||
|
var rows = []
|
||||||
|
var currentRow = []
|
||||||
|
var currentWidth = 0
|
||||||
|
var expandedRow = -1
|
||||||
|
|
||||||
|
const widgets = SettingsData.controlCenterWidgets || []
|
||||||
|
const baseWidth = controlCenterColumn.width
|
||||||
|
const spacing = Theme.spacingS
|
||||||
|
|
||||||
|
for (var i = 0; i < widgets.length; i++) {
|
||||||
|
const widget = widgets[i]
|
||||||
|
const widgetWidth = widget.width || 50
|
||||||
|
|
||||||
|
var itemWidth
|
||||||
|
if (widgetWidth <= 25) {
|
||||||
|
itemWidth = (baseWidth - spacing * 3) / 4
|
||||||
|
} else if (widgetWidth <= 50) {
|
||||||
|
itemWidth = (baseWidth - spacing) / 2
|
||||||
|
} else if (widgetWidth <= 75) {
|
||||||
|
itemWidth = (baseWidth - spacing * 2) * 0.75
|
||||||
|
} else {
|
||||||
|
itemWidth = baseWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentRow.length > 0 && (currentWidth + spacing + itemWidth > baseWidth)) {
|
||||||
|
rows.push([...currentRow])
|
||||||
|
currentRow = [widget]
|
||||||
|
currentWidth = itemWidth
|
||||||
|
} else {
|
||||||
|
currentRow.push(widget)
|
||||||
|
currentWidth += (currentRow.length > 1 ? spacing : 0) + itemWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.id === expandedSection && expandedWidgetIndex === i) {
|
||||||
|
expandedRow = rows.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentRow.length > 0) {
|
||||||
|
rows.push(currentRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { rows: rows, expandedRowIndex: expandedRow }
|
||||||
|
}
|
||||||
25
Modules/ControlCenter/utils/state.js
Normal file
25
Modules/ControlCenter/utils/state.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
function setTriggerPosition(root, x, y, width, section, screen) {
|
||||||
|
root.triggerX = x
|
||||||
|
root.triggerY = y
|
||||||
|
root.triggerWidth = width
|
||||||
|
root.triggerSection = section
|
||||||
|
root.triggerScreen = screen
|
||||||
|
}
|
||||||
|
|
||||||
|
function openWithSection(root, section) {
|
||||||
|
if (root.shouldBeVisible) {
|
||||||
|
root.close()
|
||||||
|
} else {
|
||||||
|
root.expandedSection = section
|
||||||
|
root.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSection(root, section) {
|
||||||
|
if (root.expandedSection === section) {
|
||||||
|
root.expandedSection = ""
|
||||||
|
root.expandedWidgetIndex = -1
|
||||||
|
} else {
|
||||||
|
root.expandedSection = section
|
||||||
|
}
|
||||||
|
}
|
||||||
75
Modules/ControlCenter/utils/widgets.js
Normal file
75
Modules/ControlCenter/utils/widgets.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
function getWidgetForId(baseWidgetDefinitions, widgetId) {
|
||||||
|
return baseWidgetDefinitions.find(w => w.id === widgetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addWidget(widgetId) {
|
||||||
|
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||||
|
var widget = {
|
||||||
|
"id": widgetId,
|
||||||
|
"enabled": true,
|
||||||
|
"width": 50
|
||||||
|
}
|
||||||
|
widgets.push(widget)
|
||||||
|
SettingsData.setControlCenterWidgets(widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeWidget(index) {
|
||||||
|
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||||
|
if (index >= 0 && index < widgets.length) {
|
||||||
|
widgets.splice(index, 1)
|
||||||
|
SettingsData.setControlCenterWidgets(widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleWidgetSize(index) {
|
||||||
|
var widgets = SettingsData.controlCenterWidgets.slice()
|
||||||
|
if (index >= 0 && index < widgets.length) {
|
||||||
|
const currentWidth = widgets[index].width || 50
|
||||||
|
const id = widgets[index].id || ""
|
||||||
|
|
||||||
|
if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
|
||||||
|
widgets[index].width = currentWidth <= 50 ? 100 : 50
|
||||||
|
} else {
|
||||||
|
if (currentWidth <= 25) {
|
||||||
|
widgets[index].width = 50
|
||||||
|
} else if (currentWidth <= 50) {
|
||||||
|
widgets[index].width = 100
|
||||||
|
} else {
|
||||||
|
widgets[index].width = 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsData.setControlCenterWidgets(widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reorderWidgets(newOrder) {
|
||||||
|
SettingsData.setControlCenterWidgets(newOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveWidget(fromIndex, toIndex) {
|
||||||
|
let widgets = [...(SettingsData.controlCenterWidgets || [])]
|
||||||
|
if (fromIndex >= 0 && fromIndex < widgets.length && toIndex >= 0 && toIndex < widgets.length) {
|
||||||
|
const movedWidget = widgets.splice(fromIndex, 1)[0]
|
||||||
|
widgets.splice(toIndex, 0, movedWidget)
|
||||||
|
SettingsData.setControlCenterWidgets(widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetToDefault() {
|
||||||
|
const defaultWidgets = [
|
||||||
|
{"id": "volumeSlider", "enabled": true, "width": 50},
|
||||||
|
{"id": "brightnessSlider", "enabled": true, "width": 50},
|
||||||
|
{"id": "wifi", "enabled": true, "width": 50},
|
||||||
|
{"id": "bluetooth", "enabled": true, "width": 50},
|
||||||
|
{"id": "audioOutput", "enabled": true, "width": 50},
|
||||||
|
{"id": "audioInput", "enabled": true, "width": 50},
|
||||||
|
{"id": "nightMode", "enabled": true, "width": 50},
|
||||||
|
{"id": "darkMode", "enabled": true, "width": 50}
|
||||||
|
]
|
||||||
|
SettingsData.setControlCenterWidgets(defaultWidgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAll() {
|
||||||
|
SettingsData.setControlCenterWidgets([])
|
||||||
|
}
|
||||||
@@ -677,6 +677,7 @@ Item {
|
|||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 200
|
height: 200
|
||||||
|
clip: false
|
||||||
|
|
||||||
DankAlbumArt {
|
DankAlbumArt {
|
||||||
width: Math.min(parent.width * 0.8, parent.height * 0.9)
|
width: Math.min(parent.width * 0.8, parent.height * 0.9)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import qs.Widgets
|
|||||||
|
|
||||||
Card {
|
Card {
|
||||||
id: root
|
id: root
|
||||||
|
clip: false
|
||||||
|
|
||||||
signal clicked()
|
signal clicked()
|
||||||
|
|
||||||
@@ -65,13 +66,20 @@ Card {
|
|||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
visible: activePlayer
|
visible: activePlayer
|
||||||
|
|
||||||
DankAlbumArt {
|
Item {
|
||||||
width: 110
|
width: 140
|
||||||
height: 80
|
height: 110
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
activePlayer: root.activePlayer
|
clip: false
|
||||||
albumSize: 76
|
|
||||||
animationScale: 1.05
|
DankAlbumArt {
|
||||||
|
width: 110
|
||||||
|
height: 80
|
||||||
|
anchors.centerIn: parent
|
||||||
|
activePlayer: root.activePlayer
|
||||||
|
albumSize: 76
|
||||||
|
animationScale: 1.05
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
@@ -352,14 +352,22 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if (!activeFocus && !demoMode && visible) {
|
if (!activeFocus && !demoMode && visible && passwordField) {
|
||||||
Qt.callLater(() => forceActiveFocus())
|
Qt.callLater(() => {
|
||||||
|
if (passwordField && passwordField.forceActiveFocus) {
|
||||||
|
passwordField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnabledChanged: {
|
onEnabledChanged: {
|
||||||
if (enabled && !demoMode && visible) {
|
if (enabled && !demoMode && visible && passwordField) {
|
||||||
Qt.callLater(() => forceActiveFocus())
|
Qt.callLater(() => {
|
||||||
|
if (passwordField && passwordField.forceActiveFocus) {
|
||||||
|
passwordField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -946,15 +946,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankToggle {
|
|
||||||
width: parent.width
|
|
||||||
text: "Hide Brightness Slider"
|
|
||||||
description: "Hide the brightness slider in Control Center and make audio slider full width"
|
|
||||||
checked: SettingsData.hideBrightnessSlider
|
|
||||||
onToggled: checked => {
|
|
||||||
SettingsData.setHideBrightnessSlider(checked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -37,93 +37,7 @@ Rectangle {
|
|||||||
spacing: SettingsData.topBarNoBackground ? 1 : 2
|
spacing: SettingsData.topBarNoBackground ? 1 : 2
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: {
|
name: BatteryService.getBatteryIcon()
|
||||||
if (!BatteryService.batteryAvailable) {
|
|
||||||
return "power";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.isCharging) {
|
|
||||||
if (BatteryService.batteryLevel >= 90) {
|
|
||||||
return "battery_charging_full";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 80) {
|
|
||||||
return "battery_charging_90";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 60) {
|
|
||||||
return "battery_charging_80";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 50) {
|
|
||||||
return "battery_charging_60";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 30) {
|
|
||||||
return "battery_charging_50";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 20) {
|
|
||||||
return "battery_charging_30";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "battery_charging_20";
|
|
||||||
}
|
|
||||||
// Check if plugged in but not charging (like at 80% charge limit)
|
|
||||||
if (BatteryService.isPluggedIn) {
|
|
||||||
if (BatteryService.batteryLevel >= 90) {
|
|
||||||
return "battery_charging_full";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 80) {
|
|
||||||
return "battery_charging_90";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 60) {
|
|
||||||
return "battery_charging_80";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 50) {
|
|
||||||
return "battery_charging_60";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 30) {
|
|
||||||
return "battery_charging_50";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 20) {
|
|
||||||
return "battery_charging_30";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "battery_charging_20";
|
|
||||||
}
|
|
||||||
// On battery power
|
|
||||||
if (BatteryService.batteryLevel >= 95) {
|
|
||||||
return "battery_full";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 85) {
|
|
||||||
return "battery_6_bar";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 70) {
|
|
||||||
return "battery_5_bar";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 55) {
|
|
||||||
return "battery_4_bar";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 40) {
|
|
||||||
return "battery_3_bar";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BatteryService.batteryLevel >= 25) {
|
|
||||||
return "battery_2_bar";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "battery_1_bar";
|
|
||||||
}
|
|
||||||
size: Theme.iconSize - 6
|
size: Theme.iconSize - 6
|
||||||
color: {
|
color: {
|
||||||
if (!BatteryService.batteryAvailable) {
|
if (!BatteryService.batteryAvailable) {
|
||||||
|
|||||||
@@ -546,47 +546,82 @@ PanelWindow {
|
|||||||
totalWidgets = 0
|
totalWidgets = 0
|
||||||
totalWidth = 0
|
totalWidth = 0
|
||||||
|
|
||||||
|
let configuredWidgets = 0
|
||||||
for (var i = 0; i < centerRepeater.count; i++) {
|
for (var i = 0; i < centerRepeater.count; i++) {
|
||||||
const item = centerRepeater.itemAt(i)
|
const item = centerRepeater.itemAt(i)
|
||||||
if (item?.active && item.item) {
|
if (item && topBarContent.getWidgetVisible(item.widgetId)) {
|
||||||
centerWidgets.push(item.item)
|
configuredWidgets++
|
||||||
totalWidgets++
|
if (item.active && item.item) {
|
||||||
totalWidth += item.item.width
|
centerWidgets.push(item.item)
|
||||||
|
totalWidgets++
|
||||||
|
totalWidth += item.item.width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalWidgets > 1) {
|
if (totalWidgets > 1) {
|
||||||
totalWidth += spacing * (totalWidgets - 1)
|
totalWidth += spacing * (totalWidgets - 1)
|
||||||
}
|
}
|
||||||
positionWidgets()
|
positionWidgets(configuredWidgets)
|
||||||
}
|
}
|
||||||
|
|
||||||
function positionWidgets() {
|
function positionWidgets(configuredWidgets) {
|
||||||
if (totalWidgets === 0 || width <= 0) {
|
if (totalWidgets === 0 || width <= 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentCenterX = width / 2
|
const parentCenterX = width / 2
|
||||||
const isOdd = totalWidgets % 2 === 1
|
const isOdd = configuredWidgets % 2 === 1
|
||||||
|
|
||||||
centerWidgets.forEach(widget => widget.anchors.horizontalCenter = undefined)
|
centerWidgets.forEach(widget => widget.anchors.horizontalCenter = undefined)
|
||||||
|
|
||||||
if (isOdd) {
|
if (isOdd) {
|
||||||
const middleIndex = Math.floor(totalWidgets / 2)
|
const middleIndex = Math.floor(configuredWidgets / 2)
|
||||||
const middleWidget = centerWidgets[middleIndex]
|
let currentActiveIndex = 0
|
||||||
middleWidget.x = parentCenterX - (middleWidget.width / 2)
|
let middleWidget = null
|
||||||
|
|
||||||
let currentX = middleWidget.x
|
for (var i = 0; i < centerRepeater.count; i++) {
|
||||||
for (var i = middleIndex - 1; i >= 0; i--) {
|
const item = centerRepeater.itemAt(i)
|
||||||
currentX -= (spacing + centerWidgets[i].width)
|
if (item && topBarContent.getWidgetVisible(item.widgetId)) {
|
||||||
centerWidgets[i].x = currentX
|
if (currentActiveIndex === middleIndex && item.active && item.item) {
|
||||||
|
middleWidget = item.item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentActiveIndex++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentX = middleWidget.x + middleWidget.width
|
if (middleWidget) {
|
||||||
for (var i = middleIndex + 1; i < totalWidgets; i++) {
|
middleWidget.x = parentCenterX - (middleWidget.width / 2)
|
||||||
currentX += spacing
|
|
||||||
centerWidgets[i].x = currentX
|
let leftWidgets = []
|
||||||
currentX += centerWidgets[i].width
|
let rightWidgets = []
|
||||||
|
let foundMiddle = false
|
||||||
|
|
||||||
|
for (var i = 0; i < centerWidgets.length; i++) {
|
||||||
|
if (centerWidgets[i] === middleWidget) {
|
||||||
|
foundMiddle = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!foundMiddle) {
|
||||||
|
leftWidgets.push(centerWidgets[i])
|
||||||
|
} else {
|
||||||
|
rightWidgets.push(centerWidgets[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentX = middleWidget.x
|
||||||
|
for (var i = leftWidgets.length - 1; i >= 0; i--) {
|
||||||
|
currentX -= (spacing + leftWidgets[i].width)
|
||||||
|
leftWidgets[i].x = currentX
|
||||||
|
}
|
||||||
|
|
||||||
|
currentX = middleWidget.x + middleWidget.width
|
||||||
|
for (var i = 0; i < rightWidgets.length; i++) {
|
||||||
|
currentX += spacing
|
||||||
|
rightWidgets[i].x = currentX
|
||||||
|
currentX += rightWidgets[i].width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const leftIndex = (totalWidgets / 2) - 1
|
const leftIndex = (totalWidgets / 2) - 1
|
||||||
|
|||||||
@@ -80,4 +80,72 @@ Singleton {
|
|||||||
|
|
||||||
return `${minutes}m`
|
return `${minutes}m`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBatteryIcon() {
|
||||||
|
if (!batteryAvailable) {
|
||||||
|
return "power"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCharging) {
|
||||||
|
if (batteryLevel >= 90) {
|
||||||
|
return "battery_charging_full"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 80) {
|
||||||
|
return "battery_charging_90"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 60) {
|
||||||
|
return "battery_charging_80"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 50) {
|
||||||
|
return "battery_charging_60"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 30) {
|
||||||
|
return "battery_charging_50"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 20) {
|
||||||
|
return "battery_charging_30"
|
||||||
|
}
|
||||||
|
return "battery_charging_20"
|
||||||
|
}
|
||||||
|
if (isPluggedIn) {
|
||||||
|
if (batteryLevel >= 90) {
|
||||||
|
return "battery_charging_full"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 80) {
|
||||||
|
return "battery_charging_90"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 60) {
|
||||||
|
return "battery_charging_80"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 50) {
|
||||||
|
return "battery_charging_60"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 30) {
|
||||||
|
return "battery_charging_50"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 20) {
|
||||||
|
return "battery_charging_30"
|
||||||
|
}
|
||||||
|
return "battery_charging_20"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 95) {
|
||||||
|
return "battery_full"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 85) {
|
||||||
|
return "battery_6_bar"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 70) {
|
||||||
|
return "battery_5_bar"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 55) {
|
||||||
|
return "battery_4_bar"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 40) {
|
||||||
|
return "battery_3_bar"
|
||||||
|
}
|
||||||
|
if (batteryLevel >= 25) {
|
||||||
|
return "battery_2_bar"
|
||||||
|
}
|
||||||
|
return "battery_1_bar"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,19 @@
|
|||||||
layout(location = 0) in vec2 qt_TexCoord0;
|
layout(location = 0) in vec2 qt_TexCoord0;
|
||||||
layout(location = 0) out vec4 fragColor;
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
layout(binding = 1) uniform sampler2D source1; // Current wallpaper
|
layout(binding = 1) uniform sampler2D source1;
|
||||||
layout(binding = 2) uniform sampler2D source2; // Next wallpaper
|
layout(binding = 2) uniform sampler2D source2;
|
||||||
|
|
||||||
layout(std140, binding = 0) uniform buf {
|
layout(std140, binding = 0) uniform buf {
|
||||||
mat4 qt_Matrix;
|
mat4 qt_Matrix;
|
||||||
float qt_Opacity;
|
float qt_Opacity;
|
||||||
float progress; // 0.0 -> 1.0
|
float progress;
|
||||||
float centerX; // 0..1
|
float centerX;
|
||||||
float centerY; // 0..1
|
float centerY;
|
||||||
float smoothness; // 0..1 (edge softness)
|
float smoothness;
|
||||||
float aspectRatio; // width / height
|
float aspectRatio;
|
||||||
|
|
||||||
// Fill mode parameters
|
float fillMode;
|
||||||
float fillMode; // 0=no(center), 1=crop(fill), 2=fit(contain), 3=stretch
|
|
||||||
float imageWidth1;
|
float imageWidth1;
|
||||||
float imageHeight1;
|
float imageHeight1;
|
||||||
float imageWidth2;
|
float imageWidth2;
|
||||||
@@ -29,7 +28,6 @@ layout(std140, binding = 0) uniform buf {
|
|||||||
|
|
||||||
vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) {
|
vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) {
|
||||||
vec2 transformedUV = uv;
|
vec2 transformedUV = uv;
|
||||||
|
|
||||||
if (ubuf.fillMode < 0.5) {
|
if (ubuf.fillMode < 0.5) {
|
||||||
vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight);
|
vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight);
|
||||||
vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5;
|
vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5;
|
||||||
@@ -50,8 +48,6 @@ vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) {
|
|||||||
vec2 imagePixel = (screenPixel - offset) / scale;
|
vec2 imagePixel = (screenPixel - offset) / scale;
|
||||||
transformedUV = imagePixel / vec2(imgWidth, imgHeight);
|
transformedUV = imagePixel / vec2(imgWidth, imgHeight);
|
||||||
}
|
}
|
||||||
// else stretch
|
|
||||||
|
|
||||||
return transformedUV;
|
return transformedUV;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,34 +61,34 @@ vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight)
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 uv = qt_TexCoord0;
|
vec2 uv = qt_TexCoord0;
|
||||||
|
|
||||||
vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1);
|
vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1);
|
||||||
vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2);
|
vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2);
|
||||||
|
|
||||||
// Edge softness mapping
|
|
||||||
float edgeSoft = mix(0.001, 0.45, ubuf.smoothness * ubuf.smoothness);
|
float edgeSoft = mix(0.001, 0.45, ubuf.smoothness * ubuf.smoothness);
|
||||||
|
|
||||||
// Aspect-corrected coordinates so the iris stays circular
|
|
||||||
vec2 center = vec2(ubuf.centerX, ubuf.centerY);
|
vec2 center = vec2(ubuf.centerX, ubuf.centerY);
|
||||||
vec2 acUv = vec2(uv.x * ubuf.aspectRatio, uv.y);
|
vec2 acUv = vec2(uv.x * ubuf.aspectRatio, uv.y);
|
||||||
vec2 acCenter = vec2(center.x * ubuf.aspectRatio, center.y);
|
vec2 acCenter = vec2(center.x * ubuf.aspectRatio, center.y);
|
||||||
float dist = length(acUv - acCenter);
|
vec2 q = acUv - acCenter;
|
||||||
|
|
||||||
// Max radius needed to cover the screen from the chosen center
|
float maxX = max(center.x * ubuf.aspectRatio, (1.0 - center.x) * ubuf.aspectRatio);
|
||||||
float maxDistX = max(center.x * ubuf.aspectRatio, (1.0 - center.x) * ubuf.aspectRatio);
|
float maxY = max(center.y, 1.0 - center.y);
|
||||||
float maxDistY = max(center.y, 1.0 - center.y);
|
float maxDist = length(vec2(maxX, maxY));
|
||||||
float maxDist = length(vec2(maxDistX, maxDistY));
|
|
||||||
|
|
||||||
float p = ubuf.progress;
|
float p = ubuf.progress;
|
||||||
p = p * p * (3.0 - 2.0 * p);
|
p = p * p * (3.0 - 2.0 * p);
|
||||||
|
|
||||||
float radius = p * maxDist - edgeSoft;
|
float radius = p * maxDist;
|
||||||
|
|
||||||
// Soft circular edge: inside -> color2 (new), outside -> color1 (old)
|
// squash factor for the "eye" slit
|
||||||
|
float squash = mix(0.2, 1.0, p);
|
||||||
|
q.y /= squash;
|
||||||
|
|
||||||
|
float dist = length(q);
|
||||||
float t = smoothstep(radius - edgeSoft, radius + edgeSoft, dist);
|
float t = smoothstep(radius - edgeSoft, radius + edgeSoft, dist);
|
||||||
|
|
||||||
vec4 col = mix(color2, color1, t);
|
vec4 col = mix(color2, color1, t);
|
||||||
|
|
||||||
// Exact snaps at ends
|
|
||||||
if (ubuf.progress <= 0.0) col = color1;
|
if (ubuf.progress <= 0.0) col = color1;
|
||||||
if (ubuf.progress >= 1.0) col = color2;
|
if (ubuf.progress >= 1.0) col = color2;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user