1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-30 00:12:50 -05:00

cc: fixes to edit mode

This commit is contained in:
bbedward
2025-09-25 21:37:32 -04:00
parent 3155bf24c1
commit 066d1847e4
6 changed files with 666 additions and 537 deletions

View File

@@ -0,0 +1,91 @@
import QtQuick
import qs.Common
import qs.Modules.ControlCenter.Details
Item {
id: root
property string expandedSection: ""
property var expandedWidgetData: null
height: active ? 250 : 0
visible: active
readonly property bool active: expandedSection !== ""
Behavior on height {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Easing.OutCubic
}
}
Loader {
anchors.fill: parent
anchors.topMargin: Theme.spacingS
sourceComponent: {
if (!root.active) return null
if (expandedSection.startsWith("diskUsage_")) {
return diskUsageDetailComponent
}
switch (expandedSection) {
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 {}
}
Component {
id: diskUsageDetailComponent
DiskUsageDetail {
currentMountPath: root.expandedWidgetData?.mountPath || "/"
instanceId: root.expandedWidgetData?.instanceId || ""
onMountPathChanged: (newMountPath) => {
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
const widgets = SettingsData.controlCenterWidgets || []
const newWidgets = widgets.map(w => {
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w)
updatedWidget.mountPath = newMountPath
return updatedWidget
}
return w
})
SettingsData.setControlCenterWidgets(newWidgets)
}
}
}
}
}

View File

@@ -38,6 +38,17 @@ Column {
expandedRowIndex = layoutResult.expandedRowIndex expandedRowIndex = layoutResult.expandedRowIndex
} }
function moveToTop(item) {
const children = root.children
for (var i = 0; i < children.length; i++) {
if (children[i] === item)
continue
if (children[i].z)
children[i].z = Math.min(children[i].z, 999)
}
item.z = 1000
}
Repeater { Repeater {
model: root.layoutResult.rows model: root.layoutResult.rows
@@ -51,8 +62,8 @@ Column {
if (widgets.length === 0) return false if (widgets.length === 0) return false
return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider") return widgets.every(w => w.id === "volumeSlider" || w.id === "brightnessSlider" || w.id === "inputVolumeSlider")
} }
topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -12) : 0 topPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
bottomPadding: isSliderOnlyRow ? (root.editMode ? 4 : -12) : 0 bottomPadding: isSliderOnlyRow ? (root.editMode ? 4 : -6) : 0
Flow { Flow {
width: parent.width width: parent.width
@@ -61,8 +72,8 @@ Column {
Repeater { Repeater {
model: rowWidgets || [] model: rowWidgets || []
Item { DragDropWidgetWrapper {
property var widgetData: modelData widgetData: modelData
property int globalWidgetIndex: { property int globalWidgetIndex: {
const widgets = SettingsData.controlCenterWidgets || [] const widgets = SettingsData.controlCenterWidgets || []
for (var i = 0; i < widgets.length; i++) { for (var i = 0; i < widgets.length; i++) {
@@ -92,36 +103,41 @@ Column {
return baseWidth return baseWidth
} }
} }
height: 60 height: isSliderOnlyRow ? 48 : 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 if (id === "diskUsage") {
return diskUsagePillComponent
} else {
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
}
}
editMode: root.editMode
widgetIndex: globalWidgetIndex
gridCellWidth: width
gridCellHeight: height
gridColumns: 4
gridLayout: root
isSlider: {
const id = modelData.id || ""
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider"
} }
widgetComponent: {
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 if (id === "diskUsage") {
return diskUsagePillComponent
} else {
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent
}
}
onWidgetMoved: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
onRemoveWidget: index => root.removeWidget(index)
onToggleWidgetSize: index => root.toggleWidgetSize(index)
} }
} }
} }
@@ -156,201 +172,198 @@ Column {
height: 60 height: 60
iconName: { iconName: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": { case "wifi":
if (NetworkService.wifiToggling) { {
if (NetworkService.wifiToggling)
return "sync" return "sync"
} if (NetworkService.networkStatus === "ethernet")
if (NetworkService.networkStatus === "ethernet") {
return "settings_ethernet" return "settings_ethernet"
} if (NetworkService.networkStatus === "wifi")
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalIcon return NetworkService.wifiSignalIcon
} if (NetworkService.wifiEnabled)
if (NetworkService.wifiEnabled) {
return "wifi_off" return "wifi_off"
}
return "wifi_off" return "wifi_off"
} }
case "bluetooth": { case "bluetooth":
if (!BluetoothService.available) { {
if (!BluetoothService.available)
return "bluetooth_disabled" return "bluetooth_disabled"
} if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "bluetooth_disabled" return "bluetooth_disabled"
}
const primaryDevice = (() => { const primaryDevice = (() => {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) { if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
return null return null
} let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))] for (let device of devices) {
for (let device of devices) { if (device && device.connected)
if (device && device.connected) { return device
return device }
} return null
} })()
return null if (primaryDevice)
})()
if (primaryDevice) {
return BluetoothService.getDeviceIcon(primaryDevice) return BluetoothService.getDeviceIcon(primaryDevice)
}
return "bluetooth" return "bluetooth"
} }
case "audioOutput": { case "audioOutput":
if (!AudioService.sink) return "volume_off" {
if (!AudioService.sink)
return "volume_off"
let volume = AudioService.sink.audio.volume let volume = AudioService.sink.audio.volume
let muted = AudioService.sink.audio.muted let muted = AudioService.sink.audio.muted
if (muted || volume === 0.0) return "volume_off" if (muted || volume === 0.0)
if (volume <= 0.33) return "volume_down" return "volume_off"
if (volume <= 0.66) return "volume_up" if (volume <= 0.33)
return "volume_down"
if (volume <= 0.66)
return "volume_up"
return "volume_up" return "volume_up"
} }
case "audioInput": { case "audioInput":
if (!AudioService.source) return "mic_off" {
if (!AudioService.source)
return "mic_off"
let muted = AudioService.source.audio.muted let muted = AudioService.source.audio.muted
return muted ? "mic_off" : "mic" return muted ? "mic_off" : "mic"
} }
default: return widgetDef?.icon || "help" default:
return widgetDef?.icon || "help"
} }
} }
primaryText: { primaryText: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": { case "wifi":
if (NetworkService.wifiToggling) { {
if (NetworkService.wifiToggling)
return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..." return NetworkService.wifiEnabled ? "Disabling WiFi..." : "Enabling WiFi..."
} if (NetworkService.networkStatus === "ethernet")
if (NetworkService.networkStatus === "ethernet") {
return "Ethernet" return "Ethernet"
} if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID)
if (NetworkService.networkStatus === "wifi" && NetworkService.currentWifiSSID) {
return NetworkService.currentWifiSSID return NetworkService.currentWifiSSID
} if (NetworkService.wifiEnabled)
if (NetworkService.wifiEnabled) {
return "Not connected" return "Not connected"
}
return "WiFi off" return "WiFi off"
} }
case "bluetooth": { case "bluetooth":
if (!BluetoothService.available) { {
if (!BluetoothService.available)
return "Bluetooth" return "Bluetooth"
} if (!BluetoothService.adapter)
if (!BluetoothService.adapter) {
return "No adapter" return "No adapter"
} if (!BluetoothService.adapter.enabled)
if (!BluetoothService.adapter.enabled) {
return "Disabled" return "Disabled"
}
return "Enabled" return "Enabled"
} }
case "audioOutput": return AudioService.sink?.description || "No output device" case "audioOutput":
case "audioInput": return AudioService.source?.description || "No input device" return AudioService.sink?.description || "No output device"
default: return widgetDef?.text || "Unknown" case "audioInput":
return AudioService.source?.description || "No input device"
default:
return widgetDef?.text || "Unknown"
} }
} }
secondaryText: { secondaryText: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": { case "wifi":
if (NetworkService.wifiToggling) { {
if (NetworkService.wifiToggling)
return "Please wait..." return "Please wait..."
} if (NetworkService.networkStatus === "ethernet")
if (NetworkService.networkStatus === "ethernet") {
return "Connected" return "Connected"
} if (NetworkService.networkStatus === "wifi")
if (NetworkService.networkStatus === "wifi") {
return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected" return NetworkService.wifiSignalStrength > 0 ? NetworkService.wifiSignalStrength + "%" : "Connected"
} if (NetworkService.wifiEnabled)
if (NetworkService.wifiEnabled) {
return "Select network" return "Select network"
}
return "" return ""
} }
case "bluetooth": { case "bluetooth":
if (!BluetoothService.available) { {
if (!BluetoothService.available)
return "No adapters" return "No adapters"
} if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled) {
return "Off" return "Off"
}
const primaryDevice = (() => { const primaryDevice = (() => {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) { if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
return null return null
} let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))] for (let device of devices) {
for (let device of devices) { if (device && device.connected)
if (device && device.connected) { return device
return device }
} return null
} })()
return null if (primaryDevice)
})()
if (primaryDevice) {
return primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device" return primaryDevice.name || primaryDevice.alias || primaryDevice.deviceName || "Connected Device"
}
return "No devices" return "No devices"
} }
case "audioOutput": { case "audioOutput":
if (!AudioService.sink) { {
if (!AudioService.sink)
return "Select device" return "Select device"
} if (AudioService.sink.audio.muted)
if (AudioService.sink.audio.muted) {
return "Muted" return "Muted"
}
return Math.round(AudioService.sink.audio.volume * 100) + "%" return Math.round(AudioService.sink.audio.volume * 100) + "%"
} }
case "audioInput": { case "audioInput":
if (!AudioService.source) { {
if (!AudioService.source)
return "Select device" return "Select device"
} if (AudioService.source.audio.muted)
if (AudioService.source.audio.muted) {
return "Muted" return "Muted"
}
return Math.round(AudioService.source.audio.volume * 100) + "%" return Math.round(AudioService.source.audio.volume * 100) + "%"
} }
default: return widgetDef?.description || "" default:
return widgetDef?.description || ""
} }
} }
isActive: { isActive: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": { case "wifi":
if (NetworkService.wifiToggling) { {
if (NetworkService.wifiToggling)
return false return false
} if (NetworkService.networkStatus === "ethernet")
if (NetworkService.networkStatus === "ethernet") {
return true return true
} if (NetworkService.networkStatus === "wifi")
if (NetworkService.networkStatus === "wifi") {
return true return true
}
return NetworkService.wifiEnabled return NetworkService.wifiEnabled
} }
case "bluetooth": return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled) case "bluetooth":
case "audioOutput": return !!(AudioService.sink && !AudioService.sink.audio.muted) return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled)
case "audioInput": return !!(AudioService.source && !AudioService.source.audio.muted) case "audioOutput":
default: return false return !!(AudioService.sink && !AudioService.sink.audio.muted)
case "audioInput":
return !!(AudioService.source && !AudioService.source.audio.muted)
default:
return false
} }
} }
enabled: (widgetDef?.enabled ?? true) enabled: widgetDef?.enabled ?? true
onToggled: { onToggled: {
if (root.editMode) return if (root.editMode) return
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "wifi": { case "wifi":
{
if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) { if (NetworkService.networkStatus !== "ethernet" && !NetworkService.wifiToggling) {
NetworkService.toggleWifiRadio() NetworkService.toggleWifiRadio()
} }
break break
} }
case "bluetooth": { case "bluetooth":
{
if (BluetoothService.available && BluetoothService.adapter) { if (BluetoothService.available && BluetoothService.adapter) {
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled
} }
break break
} }
case "audioOutput": { case "audioOutput":
{
if (AudioService.sink && AudioService.sink.audio) { if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = !AudioService.sink.audio.muted AudioService.sink.audio.muted = !AudioService.sink.audio.muted
} }
break break
} }
case "audioInput": { case "audioInput":
{
if (AudioService.source && AudioService.source.audio) { if (AudioService.source && AudioService.source.audio) {
AudioService.source.audio.muted = !AudioService.source.audio.muted AudioService.source.audio.muted = !AudioService.source.audio.muted
} }
@@ -363,9 +376,11 @@ Column {
root.expandClicked(widgetData, widgetIndex) root.expandClicked(widgetData, widgetIndex)
} }
onWheelEvent: function (wheelEvent) { onWheelEvent: function (wheelEvent) {
if (root.editMode) return
const id = widgetData.id || "" const id = widgetData.id || ""
if (id === "audioOutput") { if (id === "audioOutput") {
if (!AudioService.sink || !AudioService.sink.audio) return if (!AudioService.sink || !AudioService.sink.audio)
return
let delta = wheelEvent.angleDelta.y let delta = wheelEvent.angleDelta.y
let currentVolume = AudioService.sink.audio.volume * 100 let currentVolume = AudioService.sink.audio.volume * 100
let newVolume let newVolume
@@ -377,7 +392,8 @@ Column {
AudioService.sink.audio.volume = newVolume / 100 AudioService.sink.audio.volume = newVolume / 100
wheelEvent.accepted = true wheelEvent.accepted = true
} else if (id === "audioInput") { } else if (id === "audioInput") {
if (!AudioService.source || !AudioService.source.audio) return if (!AudioService.source || !AudioService.source.audio)
return
let delta = wheelEvent.angleDelta.y let delta = wheelEvent.angleDelta.y
let currentVolume = AudioService.source.audio.volume * 100 let currentVolume = AudioService.source.audio.volume * 100
let newVolume let newVolume
@@ -390,18 +406,6 @@ Column {
wheelEvent.accepted = true 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)
}
} }
} }
@@ -410,7 +414,6 @@ Column {
Item { Item {
property var widgetData: parent.widgetData || {} property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0 property int widgetIndex: parent.widgetIndex || 0
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
width: parent.width width: parent.width
height: 16 height: 16
@@ -420,18 +423,6 @@ Column {
height: 14 height: 14
property color sliderTrackColor: Theme.surfaceContainerHigh property color sliderTrackColor: Theme.surfaceContainerHigh
} }
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)
}
} }
} }
@@ -449,18 +440,6 @@ Column {
height: 14 height: 14
property color sliderTrackColor: Theme.surfaceContainerHigh property color sliderTrackColor: Theme.surfaceContainerHigh
} }
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)
}
} }
} }
@@ -478,18 +457,6 @@ Column {
height: 14 height: 14
property color sliderTrackColor: Theme.surfaceContainerHigh property color sliderTrackColor: Theme.surfaceContainerHigh
} }
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)
}
} }
} }
@@ -506,18 +473,6 @@ Column {
root.expandClicked(widgetData, widgetIndex) 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)
}
} }
} }
@@ -534,18 +489,6 @@ Column {
root.expandClicked(widgetData, widgetIndex) 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)
}
} }
} }
@@ -554,80 +497,85 @@ Column {
ToggleButton { ToggleButton {
property var widgetData: parent.widgetData || {} property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0 property int widgetIndex: parent.widgetIndex || 0
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
width: parent.width width: parent.width
height: 60 height: 60
iconName: { iconName: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode" case "nightMode":
case "darkMode": return "contrast" return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
case "doNotDisturb": return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off" case "darkMode":
case "idleInhibitor": return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle" return "contrast"
default: return widgetDef?.icon || "help" 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 "help"
} }
} }
text: { text: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": return "Night Mode" case "nightMode":
case "darkMode": return SessionData.isLightMode ? "Light Mode" : "Dark Mode" return "Night Mode"
case "doNotDisturb": return "Do Not Disturb" case "darkMode":
case "idleInhibitor": return SessionService.idleInhibited ? "Keeping Awake" : "Keep Awake" return SessionData.isLightMode ? "Light Mode" : "Dark Mode"
default: return widgetDef?.text || "Unknown" case "doNotDisturb":
return "Do Not Disturb"
case "idleInhibitor":
return SessionService.idleInhibited ? "Keeping Awake" : "Keep Awake"
default:
return "Unknown"
} }
} }
secondaryText: ""
iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0 iconRotation: widgetData.id === "darkMode" && SessionData.isLightMode ? 180 : 0
isActive: { isActive: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": return DisplayService.nightModeEnabled || false case "nightMode":
case "darkMode": return !SessionData.isLightMode return DisplayService.nightModeEnabled || false
case "doNotDisturb": return SessionData.doNotDisturb || false case "darkMode":
case "idleInhibitor": return SessionService.idleInhibited || false return !SessionData.isLightMode
default: return false case "doNotDisturb":
return SessionData.doNotDisturb || false
case "idleInhibitor":
return SessionService.idleInhibited || false
default:
return false
} }
} }
enabled: (widgetDef?.enabled ?? true) && !root.editMode enabled: !root.editMode
onClicked: { onClicked: {
if (root.editMode)
return
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": { case "nightMode":
if (DisplayService.automationAvailable) { {
if (DisplayService.automationAvailable)
DisplayService.toggleNightMode() DisplayService.toggleNightMode()
}
break break
} }
case "darkMode": { case "darkMode":
{
Theme.toggleLightMode() Theme.toggleLightMode()
break break
} }
case "doNotDisturb": { case "doNotDisturb":
{
SessionData.setDoNotDisturb(!SessionData.doNotDisturb) SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
break break
} }
case "idleInhibitor": { case "idleInhibitor":
{
SessionService.toggleIdleInhibit() SessionService.toggleIdleInhibit()
break 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)
}
} }
} }
@@ -636,17 +584,21 @@ Column {
SmallToggleButton { SmallToggleButton {
property var widgetData: parent.widgetData || {} property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0 property int widgetIndex: parent.widgetIndex || 0
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
width: parent.width width: parent.width
height: 48 height: 48
iconName: { iconName: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode" case "nightMode":
case "darkMode": return "contrast" return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode"
case "doNotDisturb": return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off" case "darkMode":
case "idleInhibitor": return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle" return "contrast"
default: return widgetDef?.icon || "help" 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 "help"
} }
} }
@@ -654,50 +606,48 @@ Column {
isActive: { isActive: {
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": return DisplayService.nightModeEnabled || false case "nightMode":
case "darkMode": return !SessionData.isLightMode return DisplayService.nightModeEnabled || false
case "doNotDisturb": return SessionData.doNotDisturb || false case "darkMode":
case "idleInhibitor": return SessionService.idleInhibited || false return !SessionData.isLightMode
default: return false case "doNotDisturb":
return SessionData.doNotDisturb || false
case "idleInhibitor":
return SessionService.idleInhibited || false
default:
return false
} }
} }
enabled: (widgetDef?.enabled ?? true) && !root.editMode enabled: !root.editMode
onClicked: { onClicked: {
if (root.editMode)
return
switch (widgetData.id || "") { switch (widgetData.id || "") {
case "nightMode": { case "nightMode":
if (DisplayService.automationAvailable) { {
if (DisplayService.automationAvailable)
DisplayService.toggleNightMode() DisplayService.toggleNightMode()
}
break break
} }
case "darkMode": { case "darkMode":
{
Theme.toggleLightMode() Theme.toggleLightMode()
break break
} }
case "doNotDisturb": { case "doNotDisturb":
{
SessionData.setDoNotDisturb(!SessionData.doNotDisturb) SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
break break
} }
case "idleInhibitor": { case "idleInhibitor":
{
SessionService.toggleIdleInhibit() SessionService.toggleIdleInhibit()
break 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)
}
} }
} }
@@ -717,18 +667,6 @@ Column {
root.expandClicked(widgetData, widgetIndex) 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)
}
} }
} }
} }

View File

@@ -0,0 +1,289 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property bool editMode: false
property var widgetData: null
property int widgetIndex: -1
property bool isSlider: false
property Component widgetComponent: null
property real gridCellWidth: 100
property real gridCellHeight: 60
property int gridColumns: 4
property var gridLayout: null
z: dragArea.drag.active ? 10000 : 1
signal widgetMoved(int fromIndex, int toIndex)
signal removeWidget(int index)
signal toggleWidgetSize(int index)
width: {
const widgetWidth = widgetData?.width || 50
if (widgetWidth <= 25) return gridCellWidth
else if (widgetWidth <= 50) return gridCellWidth * 2
else if (widgetWidth <= 75) return gridCellWidth * 3
else return gridCellWidth * 4
}
height: isSlider ? 16 : gridCellHeight
Rectangle {
id: dragIndicator
anchors.fill: parent
color: "transparent"
border.color: Theme.primary
border.width: dragArea.drag.active ? 2 : 0
radius: Theme.cornerRadius
opacity: dragArea.drag.active ? 0.8 : 1.0
z: dragArea.drag.active ? 10000 : 1
Behavior on border.width {
NumberAnimation { duration: 150 }
}
Behavior on opacity {
NumberAnimation { duration: 150 }
}
}
Loader {
id: widgetLoader
anchors.fill: parent
sourceComponent: widgetComponent
property var widgetData: root.widgetData
property int widgetIndex: root.widgetIndex
property int globalWidgetIndex: root.widgetIndex
property int widgetWidth: root.widgetData?.width || 50
MouseArea {
id: editModeBlocker
anchors.fill: parent
enabled: root.editMode
acceptedButtons: Qt.AllButtons
onPressed: function(mouse) { mouse.accepted = true }
onWheel: function(wheel) { wheel.accepted = true }
z: 100
}
}
MouseArea {
id: dragArea
anchors.fill: parent
enabled: editMode
cursorShape: editMode ? Qt.OpenHandCursor : Qt.PointingHandCursor
drag.target: editMode ? root : null
drag.axis: Drag.XAndYAxis
drag.smoothed: true
onPressed: function(mouse) {
if (editMode) {
cursorShape = Qt.ClosedHandCursor
if (root.gridLayout && root.gridLayout.moveToTop) {
root.gridLayout.moveToTop(root)
}
}
}
onReleased: function(mouse) {
if (editMode) {
cursorShape = Qt.OpenHandCursor
root.snapToGrid()
}
}
}
Drag.active: dragArea.drag.active
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
function swapIndices(i, j) {
if (i === j) return;
const arr = SettingsData.controlCenterWidgets;
if (!arr || i < 0 || j < 0 || i >= arr.length || j >= arr.length) return;
const copy = arr.slice();
const tmp = copy[i];
copy[i] = copy[j];
copy[j] = tmp;
SettingsData.setControlCenterWidgets(copy);
}
function snapToGrid() {
if (!editMode || !gridLayout) return
const globalPos = root.mapToItem(gridLayout, 0, 0)
const cellWidth = gridLayout.width / gridColumns
const cellHeight = gridCellHeight + Theme.spacingS
const centerX = globalPos.x + (root.width / 2)
const centerY = globalPos.y + (root.height / 2)
let targetCol = Math.max(0, Math.floor(centerX / cellWidth))
let targetRow = Math.max(0, Math.floor(centerY / cellHeight))
targetCol = Math.min(targetCol, gridColumns - 1)
const newIndex = findBestInsertionIndex(targetRow, targetCol)
if (newIndex !== widgetIndex && newIndex >= 0 && newIndex < (SettingsData.controlCenterWidgets?.length || 0)) {
swapIndices(widgetIndex, newIndex)
}
}
function findBestInsertionIndex(targetRow, targetCol) {
const widgets = SettingsData.controlCenterWidgets || [];
const n = widgets.length;
if (!n || widgetIndex < 0 || widgetIndex >= n) return -1;
function spanFor(width) {
const w = width ?? 50;
if (w <= 25) return 1;
if (w <= 50) return 2;
if (w <= 75) return 3;
return 4;
}
const cols = gridColumns || 4;
let row = 0, col = 0;
let draggedOrigKey = null;
const pos = [];
for (let i = 0; i < n; i++) {
const span = Math.min(spanFor(widgets[i].width), cols);
if (col + span > cols) {
row++;
col = 0;
}
const startCol = col;
const centerKey = row * cols + (startCol + (span - 1) / 2);
if (i === widgetIndex) {
draggedOrigKey = centerKey;
} else {
pos.push({ index: i, row, startCol, span, centerKey });
}
col += span;
if (col >= cols) {
row++;
col = 0;
}
}
if (pos.length === 0) return -1;
const centerColCoord = targetCol + 0.5;
const targetKey = targetRow * cols + centerColCoord;
for (let k = 0; k < pos.length; k++) {
const p = pos[k];
if (p.row === targetRow && centerColCoord >= p.startCol && centerColCoord < (p.startCol + p.span)) {
return p.index;
}
}
let lo = 0, hi = pos.length - 1;
if (targetKey <= pos[0].centerKey) return pos[0].index;
if (targetKey >= pos[hi].centerKey) return pos[hi].index;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
const mk = pos[mid].centerKey;
if (targetKey < mk) hi = mid - 1;
else if (targetKey > mk) lo = mid + 1;
else return pos[mid].index;
}
const movingUp = (draggedOrigKey != null) ? (targetKey < draggedOrigKey) : false;
return (movingUp ? pos[lo].index : pos[hi].index);
}
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
cursorShape: Qt.PointingHandCursor
onClicked: removeWidget(widgetIndex)
}
}
SizeControls {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: -6
visible: editMode
z: 10
currentSize: root.widgetData?.width || 50
isSlider: root.isSlider
widgetIndex: root.widgetIndex
onSizeChanged: (newSize) => {
var widgets = SettingsData.controlCenterWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets[widgetIndex].width = newSize
SettingsData.setControlCenterWidgets(widgets)
}
}
}
Rectangle {
id: dragHandle
width: 16
height: 12
radius: 2
color: Theme.primary
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 4
visible: editMode
z: 15
opacity: dragArea.drag.active ? 1.0 : 0.7
DankIcon {
anchors.centerIn: parent
name: "drag_indicator"
size: 10
color: Theme.primaryText
}
Behavior on opacity {
NumberAnimation { duration: 150 }
}
}
Rectangle {
anchors.fill: parent
color: editMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius
border.color: "transparent"
border.width: 0
z: -1
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
}
}

View File

@@ -1,241 +0,0 @@
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: 0
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: 0
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: 0
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: 0
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: 0
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: 0
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 }
}
}
}

View File

@@ -0,0 +1,52 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Widgets
Row {
id: root
property int currentSize: 50
property bool isSlider: false
property int widgetIndex: -1
signal sizeChanged(int newSize)
readonly property var availableSizes: isSlider ? [50, 100] : [25, 50, 75, 100]
spacing: 2
Repeater {
model: root.availableSizes
Rectangle {
width: 16
height: 16
radius: 3
color: modelData === root.currentSize ? Theme.primary : Theme.surfaceContainer
border.color: modelData === root.currentSize ? Theme.primary : Theme.outline
border.width: 1
StyledText {
anchors.centerIn: parent
text: modelData.toString()
font.pixelSize: 8
font.weight: Font.Medium
color: modelData === root.currentSize ? Theme.primaryContainer : Theme.surfaceText
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.currentSize = modelData
root.sizeChanged(modelData)
}
}
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
}
}
}

View File

@@ -145,7 +145,7 @@ DankPopout {
} }
} }
WidgetGrid { DragDropGrid {
id: widgetGrid id: widgetGrid
width: parent.width width: parent.width
editMode: root.editMode editMode: root.editMode