1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-08 04:09:15 -04:00

control center: improve drag handling

misc: fix layer shell enum usage
This commit is contained in:
bbedward
2026-06-01 13:13:17 -04:00
parent 0a668df138
commit 8c20f448ed
20 changed files with 634 additions and 208 deletions
@@ -54,6 +54,8 @@ Column {
}
readonly property real targetImplicitHeight: {
if (editMode)
return editModeGrid.implicitHeight;
const rows = layoutResult.rows;
let totalHeight = 0;
for (let i = 0; i < rows.length; i++) {
@@ -106,8 +108,40 @@ Column {
item.z = 1000;
}
function componentForWidget(widgetData) {
const id = widgetData.id || "";
const widgetWidth = widgetData.width || 50;
if (id.startsWith("builtin_"))
return builtinPluginWidgetComponent;
if (id.startsWith("plugin_"))
return pluginWidgetComponent;
switch (id) {
case "wifi":
case "bluetooth":
case "audioOutput":
case "audioInput":
return compoundPillComponent;
case "volumeSlider":
return audioSliderComponent;
case "brightnessSlider":
return brightnessSliderComponent;
case "inputVolumeSlider":
return inputAudioSliderComponent;
case "battery":
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent;
case "diskUsage":
return widgetWidth <= 25 ? smallDiskUsageComponent : diskUsagePillComponent;
case "colorPicker":
return colorPickerPillComponent;
case "doNotDisturb":
return widgetWidth <= 25 ? smallToggleComponent : dndPillComponent;
default:
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent;
}
}
Repeater {
model: root.layoutResult.rows
model: root.editMode ? [] : root.layoutResult.rows
Column {
width: root.width
@@ -174,32 +208,7 @@ Column {
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider";
}
widgetComponent: {
const id = modelData.id || "";
if (id.startsWith("builtin_")) {
return builtinPluginWidgetComponent;
} else if (id.startsWith("plugin_")) {
return pluginWidgetComponent;
} else 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 widgetWidth <= 25 ? smallDiskUsageComponent : diskUsagePillComponent;
} else if (id === "colorPicker") {
return colorPickerPillComponent;
} else if (id === "doNotDisturb") {
return widgetWidth <= 25 ? smallToggleComponent : dndPillComponent;
} else {
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent;
}
}
widgetComponent: root.componentForWidget(modelData)
onWidgetMoved: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
onRemoveWidget: index => root.removeWidget(index)
@@ -279,6 +288,18 @@ Column {
}
}
EditModeGrid {
id: editModeGrid
width: root.width
visible: root.editMode
active: root.editMode
model: root.model
componentProvider: root
onRemoveWidget: index => root.removeWidget(index)
onToggleWidgetSize: index => root.toggleWidgetSize(index)
onConfigRequested: (idx, data, anchor) => root.configRequested(idx, data, anchor)
}
Component {
id: errorPillComponent
ErrorPill {
@@ -0,0 +1,110 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Modules.ControlCenter.Components
import "../utils/layout.js" as LayoutUtils
Item {
id: root
property var model: null
property var componentProvider: null
property bool active: true
signal removeWidget(int index)
signal toggleWidgetSize(int index)
signal configRequested(int index, var widgetData, var anchor)
property var sourceWidgets: SettingsData.controlCenterWidgets || []
property var visualOrder: []
property int draggingSourceIndex: -1
property var dragStartOrder: []
readonly property real rowSpacing: Theme.spacingL
readonly property real sliderCellHeight: 48
readonly property real normalCellHeight: 60
readonly property var slotLayout: LayoutUtils.computeSlots(sourceWidgets, visualOrder, width, Theme.spacingS, rowSpacing, sliderCellHeight, normalCellHeight)
implicitHeight: slotLayout.totalHeight
function rebuildOrder() {
const n = (sourceWidgets || []).length;
const arr = [];
for (var i = 0; i < n; i++)
arr.push(i);
visualOrder = arr;
}
onSourceWidgetsChanged: rebuildOrder()
Component.onCompleted: rebuildOrder()
function beginDrag(sourceIndex) {
draggingSourceIndex = sourceIndex;
dragStartOrder = visualOrder.slice();
}
function sameOrder(a, b) {
if (a.length !== b.length)
return false;
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i])
return false;
}
return true;
}
function updateDragTarget(centerX, centerY) {
if (draggingSourceIndex < 0)
return;
const p = LayoutUtils.slotContainingPoint(slotLayout.slots, visualOrder, centerX, centerY);
if (p < 0)
return;
const arr = visualOrder.slice();
const d = arr.indexOf(draggingSourceIndex);
if (d < 0 || d === p)
return;
arr.splice(d, 1);
arr.splice(p, 0, draggingSourceIndex);
visualOrder = arr;
}
function endDrag() {
if (draggingSourceIndex < 0)
return;
draggingSourceIndex = -1;
if (!sameOrder(visualOrder, dragStartOrder))
commit();
}
function commit() {
const widgets = sourceWidgets || [];
const arr = visualOrder.map(i => widgets[i]);
if (root.model)
root.model.reorderWidgets(arr);
}
Repeater {
model: root.active ? root.sourceWidgets : []
EditModeWidgetDelegate {
required property int index
required property var modelData
grid: root
sourceIndex: index
widgetData: modelData
isSlider: LayoutUtils.isSliderWidget(modelData.id || "")
widgetComponent: root.componentProvider ? root.componentProvider.componentForWidget(modelData) : null
slotX: root.slotLayout.slots[index] ? root.slotLayout.slots[index].x : 0
slotY: root.slotLayout.slots[index] ? root.slotLayout.slots[index].y : 0
cellW: root.slotLayout.slots[index] ? root.slotLayout.slots[index].w : root.width
cellH: root.slotLayout.slots[index] ? root.slotLayout.slots[index].h : root.normalCellHeight
onRemoveWidget: idx => root.removeWidget(idx)
onToggleWidgetSize: idx => root.toggleWidgetSize(idx)
onConfigRequested: (idx, data, anchor) => root.configRequested(idx, data, anchor)
}
}
}
@@ -0,0 +1,242 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property var grid: null
property int sourceIndex: -1
property var widgetData: null
property Component widgetComponent: null
property bool isSlider: false
property real slotX: 0
property real slotY: 0
property real cellW: 100
property real cellH: 60
property bool dragging: !!grid && grid.draggingSourceIndex === sourceIndex
signal removeWidget(int index)
signal toggleWidgetSize(int index)
signal configRequested(int index, var widgetData, var anchor)
width: cellW
height: cellH
z: dragging ? 10000 : 1
Binding {
target: root
property: "x"
value: root.slotX
when: !root.dragging
restoreMode: Binding.RestoreNone
}
Binding {
target: root
property: "y"
value: root.slotY
when: !root.dragging
restoreMode: Binding.RestoreNone
}
onXChanged: {
if (dragging && grid)
grid.updateDragTarget(x + width / 2, y + height / 2);
}
onYChanged: {
if (dragging && grid)
grid.updateDragTarget(x + width / 2, y + height / 2);
}
Behavior on x {
enabled: !root.dragging
NumberAnimation {
duration: Theme.expressiveDurations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
}
}
Behavior on y {
enabled: !root.dragging
NumberAnimation {
duration: Theme.expressiveDurations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
}
}
Rectangle {
id: dragIndicator
anchors.fill: parent
color: "transparent"
border.color: Theme.primary
border.width: root.dragging ? 2 : 0
radius: Theme.cornerRadius
opacity: root.dragging ? 0.8 : 1.0
z: root.dragging ? 10000 : 1
Behavior on border.width {
NumberAnimation {
duration: 150
}
}
Behavior on opacity {
NumberAnimation {
duration: 150
}
}
}
Loader {
id: widgetLoader
anchors.fill: parent
sourceComponent: root.widgetComponent
property var widgetData: root.widgetData
property int widgetIndex: root.sourceIndex
property int globalWidgetIndex: root.sourceIndex
property int widgetWidth: root.widgetData?.width || 50
MouseArea {
id: editModeBlocker
anchors.fill: parent
enabled: true
acceptedButtons: Qt.AllButtons
onPressed: function (mouse) {
mouse.accepted = true;
}
onWheel: function (wheel) {
wheel.accepted = true;
}
z: 100
}
}
MouseArea {
id: dragArea
anchors.fill: parent
cursorShape: Qt.OpenHandCursor
drag.target: root
drag.axis: Drag.XAndYAxis
drag.smoothed: false
onPressed: function (mouse) {
cursorShape = Qt.ClosedHandCursor;
if (root.grid)
root.grid.beginDrag(root.sourceIndex);
}
onReleased: function (mouse) {
cursorShape = Qt.OpenHandCursor;
if (root.grid)
root.grid.endDrag();
}
}
Rectangle {
id: removeButton
width: 16
height: 16
radius: 8
color: Theme.error
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: -4
z: 10
DankIcon {
anchors.centerIn: parent
name: "close"
size: 12
color: Theme.primaryText
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.removeWidget(root.sourceIndex)
}
}
SizeControls {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: -6
z: 10
currentSize: root.widgetData?.width || 50
isSlider: root.isSlider
widgetIndex: root.sourceIndex
onSizeChanged: newSize => {
var widgets = SettingsData.controlCenterWidgets.slice();
if (root.sourceIndex >= 0 && root.sourceIndex < widgets.length) {
widgets[root.sourceIndex].width = newSize;
SettingsData.set("controlCenterWidgets", widgets);
}
}
}
readonly property bool hasConfigMenu: widgetData?.id === "diskUsage"
Rectangle {
id: configButton
width: 16
height: 16
radius: 8
color: Theme.primary
anchors.top: removeButton.top
anchors.right: removeButton.left
anchors.rightMargin: 4
visible: root.hasConfigMenu
z: 10
DankIcon {
anchors.centerIn: parent
name: "settings"
size: 12
color: Theme.primaryText
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.configRequested(root.sourceIndex, root.widgetData, configButton)
}
}
Rectangle {
id: dragHandle
width: 16
height: 12
radius: 2
color: Theme.primary
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 4
z: 15
opacity: root.dragging ? 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: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
radius: Theme.cornerRadius
border.color: "transparent"
border.width: 0
z: -1
}
}