diff --git a/Modules/ControlCenter/Components/DragDropDetailHost.qml b/Modules/ControlCenter/Components/DragDropDetailHost.qml deleted file mode 100644 index 7492198c..00000000 --- a/Modules/ControlCenter/Components/DragDropDetailHost.qml +++ /dev/null @@ -1,76 +0,0 @@ -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?.currentMountPath || "/" - instanceId: root.expandedWidgetData?.instanceId || "" - } - } -} \ No newline at end of file diff --git a/Modules/ControlCenter/Components/DragDropGrid.qml b/Modules/ControlCenter/Components/DragDropGrid.qml deleted file mode 100644 index 2de74827..00000000 --- a/Modules/ControlCenter/Components/DragDropGrid.qml +++ /dev/null @@ -1,703 +0,0 @@ -import QtQuick -import qs.Common -import qs.Services -import qs.Modules.ControlCenter.Widgets -import qs.Modules.ControlCenter.Components - -Item { - id: root - - property bool editMode: false - property string expandedSection: "" - property int expandedWidgetIndex: -1 - property var model: null - property var expandedWidgetData: null - - signal expandClicked(var widgetData, int globalIndex) - signal removeWidget(int index) - signal moveWidget(int fromIndex, int toIndex) - signal toggleWidgetSize(int index) - - readonly property int gridColumns: 4 - readonly property real cellWidth: (width - (gridSpacing + 1) * (gridColumns - 1)) / gridColumns - readonly property real cellHeight: 60 - readonly property real gridSpacing: 4 - - height: { - const dummy = [SettingsData.controlCenterWidgets?.length, widgetPositions.length] - return calculateGridHeight() + (detailHost.active ? detailHost.height + Theme.spacingL : 0) - } - - function calculateGridHeight() { - const widgets = SettingsData.controlCenterWidgets || [] - if (widgets.length === 0) - return 0 - - let rows = [] - let currentRow = [] - let currentWidth = 0 - const spacing = gridSpacing - const baseWidth = width - - for (var i = 0; i < widgets.length; i++) { - const widget = widgets[i] - const widgetWidth = widget.width || 50 - - let 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 (currentRow.length > 0) { - rows.push(currentRow) - } - - return rows.length * cellHeight + (rows.length > 0 ? (rows.length - 1) * spacing : 0) - } - - DragDropDetailHost { - id: detailHost - y: calculateGridHeight() - anchors.left: parent.left - anchors.right: parent.right - expandedSection: root.expandedSection - expandedWidgetData: root.expandedWidgetData - } - - 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 { - id: widgetRepeater - model: SettingsData.controlCenterWidgets || [] - - DragDropWidgetWrapper { - id: widgetWrapper - - editMode: root.editMode - widgetData: modelData - widgetIndex: index - gridCellWidth: root.cellWidth - gridCellHeight: root.cellHeight - gridColumns: root.gridColumns - 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") { - const widgetWidth = modelData.width || 50 - return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent - } else if (id === "diskUsage") { - return diskUsagePillComponent - } else { - const widgetWidth = modelData.width || 50 - return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent - } - } - - x: calculateWidgetX(index) - y: calculateWidgetY(index) - - onWidgetMoved: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex) - onRemoveWidget: index => root.removeWidget(index) - onToggleWidgetSize: index => root.toggleWidgetSize(index) - - Behavior on x { - enabled: !editMode - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Easing.OutCubic - } - } - - Behavior on y { - enabled: !editMode - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Easing.OutCubic - } - } - } - } - - property var widgetPositions: calculateAllWidgetPositions() - - function calculateAllWidgetPositions() { - const widgets = SettingsData.controlCenterWidgets || [] - let positions = [] - let currentX = 0 - let currentY = 0 - - for (var i = 0; i < widgets.length; i++) { - const widget = widgets[i] - const widgetWidth = widget.width || 50 - let cellsNeeded = 1 - - if (widgetWidth <= 25) - cellsNeeded = 1 - else if (widgetWidth <= 50) - cellsNeeded = 2 - else if (widgetWidth <= 75) - cellsNeeded = 3 - else - cellsNeeded = 4 - - if (currentX + cellsNeeded > gridColumns) { - currentX = 0 - currentY++ - } - - const horizontalSpacing = gridSpacing - positions[i] = { - "x": currentX * cellWidth + (currentX > 0 ? currentX * horizontalSpacing : 0), - "y": currentY * cellHeight + (currentY > 0 ? currentY * gridSpacing : 0), - "cellsUsed": cellsNeeded - } - - currentX += cellsNeeded - - if (currentX >= gridColumns) { - currentX = 0 - currentY++ - } - } - - return positions - } - - function calculateWidgetX(widgetIndex) { - if (widgetIndex < 0 || widgetIndex >= widgetPositions.length) - return 0 - return widgetPositions[widgetIndex].x - } - - function calculateWidgetY(widgetIndex) { - if (widgetIndex < 0 || widgetIndex >= widgetPositions.length) - return 0 - return widgetPositions[widgetIndex].y - } - - Connections { - target: SettingsData - function onControlCenterWidgetsChanged() { - widgetPositions = calculateAllWidgetPositions() - } - } - - 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: cellHeight - 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: !root.editMode && (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) - root.expandClicked(widgetData, widgetIndex) - } - onWheelEvent: function (wheelEvent) { - if (root.editMode) - return - 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 - } - } - } - } - - Component { - id: audioSliderComponent - AudioSliderRow { - width: parent.width - height: 14 - enabled: !root.editMode - property color sliderTrackColor: Theme.surfaceContainerHigh - } - } - - Component { - id: brightnessSliderComponent - BrightnessSliderRow { - width: parent.width - height: 14 - enabled: !root.editMode - property color sliderTrackColor: Theme.surfaceContainerHigh - } - } - - Component { - id: inputAudioSliderComponent - InputAudioSliderRow { - width: parent.width - height: 14 - enabled: !root.editMode - property color sliderTrackColor: Theme.surfaceContainerHigh - } - } - - Component { - id: batteryPillComponent - BatteryPill { - width: parent.width - height: cellHeight - enabled: !root.editMode - onExpandClicked: { - if (!root.editMode) - root.expandClicked(parent.widgetData, parent.widgetIndex) - } - } - } - - Component { - id: smallBatteryComponent - SmallBatteryButton { - width: parent.width - height: 48 - enabled: !root.editMode - onClicked: { - if (!root.editMode) - root.expandClicked(parent.widgetData, parent.widgetIndex) - } - } - } - - 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: cellHeight - - 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" - } - } - - 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: !root.editMode && (widgetDef?.enabled ?? true) - - onClicked: { - if (root.editMode) - return - 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 - } - } - } - } - } - - 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: !root.editMode && (widgetDef?.enabled ?? true) - - onClicked: { - if (root.editMode) - return - 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 - } - } - } - } - } - - Component { - id: diskUsagePillComponent - DiskUsagePill { - width: parent.width - height: cellHeight - enabled: !root.editMode - mountPath: parent.widgetData?.mountPath || "/" - instanceId: parent.widgetData?.instanceId || "" - onExpandClicked: { - if (!root.editMode) - root.expandClicked(parent.widgetData, parent.widgetIndex) - } - } - } -} diff --git a/Modules/ControlCenter/Components/DragDropWidgetWrapper.qml b/Modules/ControlCenter/Components/DragDropWidgetWrapper.qml deleted file mode 100644 index 78f7e048..00000000 --- a/Modules/ControlCenter/Components/DragDropWidgetWrapper.qml +++ /dev/null @@ -1,262 +0,0 @@ -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 - - 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: 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 ? 1000 : 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: mouse.accepted = true - onWheel: wheel.accepted = true - z: 100 - } - } - - MouseArea { - id: dragArea - anchors.fill: parent - enabled: editMode - cursorShape: editMode ? Qt.OpenHandCursor : Qt.ArrowCursor - drag.target: editMode ? root : null - drag.axis: Drag.XAndYAxis - drag.smoothed: true - - onPressed: { - if (editMode) { - cursorShape = Qt.ClosedHandCursor - root.z = 1000 - root.parent.moveToTop(root) - } - } - - onReleased: { - if (editMode) { - cursorShape = Qt.OpenHandCursor - root.z = 1 - root.snapToGrid() - } - } - } - - Drag.active: dragArea.drag.active - Drag.hotSpot.x: width / 2 - Drag.hotSpot.y: height / 2 - - function snapToGrid() { - if (!editMode || !gridLayout) return - - const globalPos = root.mapToItem(gridLayout, 0, 0) - const cellWidth = gridLayout.width / gridColumns - const cellHeight = gridCellHeight + Theme.spacingS - - let targetCol = Math.max(0, Math.round(globalPos.x / cellWidth)) - let targetRow = Math.max(0, Math.round(globalPos.y / cellHeight)) - - const widgetCells = Math.ceil(root.width / cellWidth) - - if (targetCol + widgetCells > gridColumns) { - targetCol = Math.max(0, gridColumns - widgetCells) - } - - const newIndex = findBestInsertionIndex(targetRow, targetCol) - - if (newIndex !== widgetIndex && newIndex >= 0 && newIndex < (SettingsData.controlCenterWidgets?.length || 0)) { - widgetMoved(widgetIndex, newIndex) - } - } - - function findBestInsertionIndex(targetRow, targetCol) { - const widgets = SettingsData.controlCenterWidgets || [] - if (!widgets.length) return 0 - - const targetPosition = targetRow * gridColumns + targetCol - - // Find the widget position closest to our target - let bestIndex = 0 - let bestDistance = Infinity - - for (let i = 0; i <= widgets.length; i++) { - if (i === widgetIndex) continue - - let currentPos = calculatePositionForIndex(i) - let distance = Math.abs(currentPos - targetPosition) - - if (distance < bestDistance) { - bestDistance = distance - bestIndex = i > widgetIndex ? i - 1 : i - } - } - - return Math.max(0, Math.min(bestIndex, widgets.length - 1)) - } - - function calculatePositionForIndex(index) { - const widgets = SettingsData.controlCenterWidgets || [] - let currentX = 0 - let currentY = 0 - - for (let i = 0; i < index && i < widgets.length; i++) { - if (i === widgetIndex) continue - - const widget = widgets[i] - const widgetWidth = widget.width || 50 - let cellsNeeded = widgetWidth <= 25 ? 1 : widgetWidth <= 50 ? 2 : widgetWidth <= 75 ? 3 : 4 - - if (currentX + cellsNeeded > gridColumns) { - currentX = 0 - currentY++ - } - - currentX += cellsNeeded - if (currentX >= gridColumns) { - currentX = 0 - currentY++ - } - } - - return currentY * gridColumns + currentX - } - - function getWidgetWidth(widgetWidth) { - const cellWidth = gridLayout ? gridLayout.width / gridColumns : gridCellWidth - if (widgetWidth <= 25) return cellWidth - else if (widgetWidth <= 50) return cellWidth * 2 - else if (widgetWidth <= 75) return cellWidth * 3 - else return cellWidth * 4 - } - - 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: removeWidget(widgetIndex) - } - } - - PieChartSizeControl { - 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 } - } - } -} \ No newline at end of file diff --git a/Modules/ControlCenter/Components/EditModeOverlay.qml b/Modules/ControlCenter/Components/EditModeOverlay.qml new file mode 100644 index 00000000..fa255a5f --- /dev/null +++ b/Modules/ControlCenter/Components/EditModeOverlay.qml @@ -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: 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 } + } + } +} \ No newline at end of file diff --git a/Modules/ControlCenter/Components/WidgetGrid.qml b/Modules/ControlCenter/Components/WidgetGrid.qml index 86a8a45c..3446b78a 100644 --- a/Modules/ControlCenter/Components/WidgetGrid.qml +++ b/Modules/ControlCenter/Components/WidgetGrid.qml @@ -178,6 +178,21 @@ Column { 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": { diff --git a/Modules/ControlCenter/ControlCenterPopout.qml b/Modules/ControlCenter/ControlCenterPopout.qml index 267c41b7..dc12942f 100644 --- a/Modules/ControlCenter/ControlCenterPopout.qml +++ b/Modules/ControlCenter/ControlCenterPopout.qml @@ -145,7 +145,7 @@ DankPopout { } } - DragDropGrid { + WidgetGrid { id: widgetGrid width: parent.width editMode: root.editMode diff --git a/Widgets/PieChartSizeControl.qml b/Widgets/PieChartSizeControl.qml deleted file mode 100644 index 12eebd89..00000000 --- a/Widgets/PieChartSizeControl.qml +++ /dev/null @@ -1,85 +0,0 @@ -import QtQuick -import QtQuick.Controls -import qs.Common - -Item { - id: root - - property int currentSize: 50 - property bool isSlider: false - property int widgetIndex: -1 - - signal sizeChanged(int newSize) - - width: 28 - height: 28 - - readonly property var availableSizes: isSlider ? [50, 100] : [25, 50, 75, 100] - readonly property int currentSizeIndex: availableSizes.indexOf(currentSize) - - Canvas { - id: pieCanvas - anchors.fill: parent - - onPaint: { - const ctx = getContext("2d") - const centerX = width / 2 - const centerY = height / 2 - const radius = Math.min(width, height) / 2 - 2 - - ctx.clearRect(0, 0, width, height) - - ctx.strokeStyle = Theme.primary - ctx.lineWidth = 1.5 - ctx.beginPath() - ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) - ctx.stroke() - - if (availableSizes.length > 0 && currentSizeIndex >= 0) { - const segmentAngle = (2 * Math.PI) / availableSizes.length - const startAngle = -Math.PI / 2 - const endAngle = startAngle + segmentAngle * (currentSizeIndex + 1) - - ctx.fillStyle = Theme.primary - ctx.beginPath() - ctx.moveTo(centerX, centerY) - ctx.arc(centerX, centerY, radius - 1, startAngle, endAngle) - ctx.closePath() - ctx.fill() - } - } - } - - Rectangle { - anchors.centerIn: parent - width: 12 - height: 12 - radius: 6 - color: Theme.surfaceContainer - border.color: Theme.outline - border.width: 1 - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - const nextIndex = (currentSizeIndex + 1) % availableSizes.length - const newSize = availableSizes[nextIndex] - currentSize = newSize - pieCanvas.requestPaint() - sizeChanged(newSize) - } - } - - onCurrentSizeChanged: { - pieCanvas.requestPaint() - } - - onIsSliderChanged: { - if (isSlider && currentSize !== 50 && currentSize !== 100) { - currentSize = 50 - } - pieCanvas.requestPaint() - } -} \ No newline at end of file