mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-06-03 10:56:16 +00:00
ControlCenter: modularity!
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Public API
|
||||
property var model: []
|
||||
property color activeColor: Color.mPrimary
|
||||
property color activeOnColor: Color.mOnPrimary
|
||||
property color dragHandleColor: Color.mOutline
|
||||
property int baseSize: Style.baseWidgetSize * 0.7
|
||||
property int spacing: Style.marginM
|
||||
|
||||
signal itemToggled(int index, bool enabled)
|
||||
signal itemsReordered(int fromIndex, int toIndex)
|
||||
signal dragPotentialStarted
|
||||
signal dragPotentialEnded
|
||||
|
||||
implicitHeight: listView.contentHeight
|
||||
|
||||
function toggleItem(index) {
|
||||
if (index < 0 || index >= root.model.length)
|
||||
return
|
||||
|
||||
var item = root.model[index]
|
||||
if (item.required)
|
||||
return
|
||||
|
||||
// Create a new array to trigger binding update
|
||||
var newModel = root.model.slice()
|
||||
newModel[index] = Object.assign({}, item, {
|
||||
"enabled": !item.enabled
|
||||
})
|
||||
root.model = newModel
|
||||
|
||||
root.itemToggled(index, newModel[index].enabled)
|
||||
}
|
||||
|
||||
function moveItem(fromIndex, toIndex) {
|
||||
if (fromIndex === toIndex)
|
||||
return
|
||||
if (fromIndex < 0 || fromIndex >= root.model.length)
|
||||
return
|
||||
if (toIndex < 0 || toIndex >= root.model.length)
|
||||
return
|
||||
|
||||
// Create a new array with item moved
|
||||
var newModel = root.model.slice()
|
||||
var item = newModel.splice(fromIndex, 1)[0]
|
||||
newModel.splice(toIndex, 0, item)
|
||||
root.model = newModel
|
||||
|
||||
root.itemsReordered(fromIndex, toIndex)
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: root.spacing
|
||||
interactive: false
|
||||
clip: true
|
||||
model: root.model
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
|
||||
width: listView.width
|
||||
height: checkboxRow.height
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
property string text: modelData.text || ""
|
||||
property bool enabled: modelData.enabled || false
|
||||
property bool required: modelData.required || false
|
||||
property bool dragging: false
|
||||
property int dragStartY: 0
|
||||
property int dragStartIndex: -1
|
||||
property int dragTargetIndex: -1
|
||||
property int itemSpacing: root.spacing
|
||||
|
||||
RowLayout {
|
||||
id: checkboxRow
|
||||
|
||||
width: parent.width
|
||||
spacing: Style.marginS
|
||||
|
||||
// Drag handle
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
|
||||
Layout.preferredWidth: root.baseSize
|
||||
Layout.preferredHeight: root.baseSize
|
||||
radius: Style.radiusXS
|
||||
color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : Color.transparent
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: 3
|
||||
Rectangle {
|
||||
Layout.preferredWidth: root.baseSize * 0.4
|
||||
Layout.preferredHeight: 2
|
||||
radius: 1
|
||||
color: root.dragHandleColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragHandleMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
hoverEnabled: true
|
||||
preventStealing: false
|
||||
z: 1000
|
||||
|
||||
onPressed: mouse => {
|
||||
delegateItem.dragStartIndex = delegateItem.index
|
||||
delegateItem.dragTargetIndex = delegateItem.index
|
||||
delegateItem.dragStartY = delegateItem.y
|
||||
delegateItem.dragging = true
|
||||
delegateItem.z = 999
|
||||
|
||||
// Signal that interaction started (prevents panel close)
|
||||
preventStealing = true
|
||||
root.dragPotentialStarted()
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (delegateItem.dragging) {
|
||||
var dy = mouse.y - dragHandle.height / 2
|
||||
var newY = delegateItem.y + dy
|
||||
|
||||
// Constrain within bounds
|
||||
newY = Math.max(0, Math.min(newY, listView.contentHeight - delegateItem.height))
|
||||
delegateItem.y = newY
|
||||
|
||||
// Calculate target index (but don't apply yet)
|
||||
var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + delegateItem.itemSpacing))
|
||||
targetIndex = Math.max(0, Math.min(targetIndex, listView.count - 1))
|
||||
|
||||
delegateItem.dragTargetIndex = targetIndex
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
// Always signal end of interaction
|
||||
preventStealing = false
|
||||
root.dragPotentialEnded()
|
||||
|
||||
// Apply the model change now that drag is complete
|
||||
if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) {
|
||||
root.moveItem(delegateItem.dragStartIndex, delegateItem.dragTargetIndex)
|
||||
}
|
||||
|
||||
delegateItem.dragging = false
|
||||
delegateItem.dragStartIndex = -1
|
||||
delegateItem.dragTargetIndex = -1
|
||||
delegateItem.z = 0
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
// Handle cancel (e.g., ESC key pressed during drag)
|
||||
preventStealing = false
|
||||
root.dragPotentialEnded()
|
||||
|
||||
delegateItem.dragging = false
|
||||
delegateItem.dragStartIndex = -1
|
||||
delegateItem.dragTargetIndex = -1
|
||||
delegateItem.z = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
Rectangle {
|
||||
id: box
|
||||
|
||||
Layout.preferredWidth: root.baseSize
|
||||
Layout.preferredHeight: root.baseSize
|
||||
radius: Style.radiusXS
|
||||
color: delegateItem.enabled ? root.activeColor : Color.mSurface
|
||||
border.color: delegateItem.required ? root.activeColor : Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS)
|
||||
opacity: delegateItem.required ? 0.7 : 1.0
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
visible: delegateItem.enabled
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -1
|
||||
icon: "check"
|
||||
color: root.activeOnColor
|
||||
pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5)
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: delegateItem.required ? Qt.ForbiddenCursor : Qt.PointingHandCursor
|
||||
enabled: !delegateItem.required
|
||||
|
||||
onClicked: {
|
||||
root.toggleItem(delegateItem.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Label
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: delegateItem.text
|
||||
color: Color.mOnSurface
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Required indicator
|
||||
NText {
|
||||
visible: delegateItem.required
|
||||
text: "(required)"
|
||||
color: Color.mOnSurfaceVariant
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Position binding for non-dragging state
|
||||
y: {
|
||||
if (delegateItem.dragging) {
|
||||
return delegateItem.y
|
||||
}
|
||||
|
||||
// Check if any item is being dragged
|
||||
var draggedIndex = -1
|
||||
var targetIndex = -1
|
||||
for (var i = 0; i < listView.count; i++) {
|
||||
var item = listView.itemAtIndex(i)
|
||||
if (item && item.dragging) {
|
||||
draggedIndex = item.dragStartIndex
|
||||
targetIndex = item.dragTargetIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If an item is being dragged, adjust positions
|
||||
if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
|
||||
var currentIndex = delegateItem.index
|
||||
|
||||
if (draggedIndex < targetIndex) {
|
||||
// Dragging down: shift items up between draggedIndex and targetIndex
|
||||
if (currentIndex > draggedIndex && currentIndex <= targetIndex) {
|
||||
return (currentIndex - 1) * (delegateItem.height + delegateItem.itemSpacing)
|
||||
}
|
||||
} else {
|
||||
// Dragging up: shift items down between targetIndex and draggedIndex
|
||||
if (currentIndex >= targetIndex && currentIndex < draggedIndex) {
|
||||
return (currentIndex + 1) * (delegateItem.height + delegateItem.itemSpacing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return delegateItem.index * (delegateItem.height + delegateItem.itemSpacing)
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: !delegateItem.dragging
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user