1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 23:42:51 -05:00

feat: Implement drag & drop topbar widget sections in settings

This commit is contained in:
purian23
2025-08-01 12:29:21 -04:00
parent ce9f4efb5d
commit f66b5181ce
9 changed files with 1601 additions and 187 deletions

View File

@@ -30,7 +30,10 @@ StyledRect {
StateLayer {
stateColor: Theme.primary
cornerRadius: root.radius
onClicked: root.clicked()
onClicked: {
console.log("StateLayer clicked for button:", root.iconName);
root.clicked();
}
}
}

View File

@@ -208,7 +208,7 @@ Rectangle {
if (selectedIndex >= 0 && selectedIndex < filteredOptions.length) {
root.currentValue = filteredOptions[selectedIndex];
root.valueChanged(filteredOptions[selectedIndex]);
dropdownMenu.close();
close();
}
}
@@ -256,17 +256,17 @@ Rectangle {
anchors.fill: parent
anchors.margins: 1
placeholderText: "Search..."
text: dropdownMenu.searchQuery
text: searchQuery
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
onTextChanged: {
dropdownMenu.searchQuery = text;
dropdownMenu.updateFilteredOptions();
searchQuery = text;
updateFilteredOptions();
}
Keys.onDownPressed: dropdownMenu.selectNext()
Keys.onUpPressed: dropdownMenu.selectPrevious()
Keys.onReturnPressed: dropdownMenu.selectCurrent()
Keys.onEnterPressed: dropdownMenu.selectCurrent()
Keys.onDownPressed: selectNext()
Keys.onUpPressed: selectPrevious()
Keys.onReturnPressed: selectCurrent()
Keys.onEnterPressed: selectCurrent()
}
}
@@ -286,7 +286,7 @@ Rectangle {
width: parent.width
height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0)
clip: true
model: dropdownMenu.filteredOptions
model: filteredOptions
spacing: 2
WheelHandler {
@@ -311,7 +311,7 @@ Rectangle {
}
delegate: Rectangle {
property bool isSelected: dropdownMenu.selectedIndex === index
property bool isSelected: selectedIndex === index
property bool isCurrentValue: root.currentValue === modelData
property int optionIndex: root.options.indexOf(modelData)
@@ -354,7 +354,7 @@ Rectangle {
onClicked: {
root.currentValue = modelData;
root.valueChanged(modelData);
dropdownMenu.close();
close();
}
}

263
Widgets/DankSections.qml Normal file
View File

@@ -0,0 +1,263 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Widgets
Column {
id: root
property var items: []
property var allWidgets: []
property string title: ""
property string titleIcon: "widgets"
property string sectionId: ""
signal itemEnabledChanged(string itemId, bool enabled)
signal itemOrderChanged(var newOrder)
signal addWidget(string sectionId)
signal removeLastWidget(string sectionId)
width: parent.width
spacing: Theme.spacingM
// Header
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: root.titleIcon
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.title
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - 60
height: 1
}
}
// Widget Items
Column {
id: itemsList
width: parent.width
spacing: Theme.spacingS
Repeater {
model: root.items
delegate: Item {
id: delegateItem
width: itemsList.width
height: 70
property int visualIndex: index
property bool held: dragArea.pressed
property string itemId: modelData.id
z: held ? 2 : 1
Rectangle {
id: itemBackground
anchors.fill: parent
anchors.margins: 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
// Drag handle
Rectangle {
width: 40
height: parent.height
color: "transparent"
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "drag_indicator"
size: Theme.iconSize - 4
color: Theme.outline
anchors.centerIn: parent
opacity: 0.8
}
}
// Widget icon
DankIcon {
name: modelData.icon
size: Theme.iconSize
color: modelData.enabled ? Theme.primary : Theme.outline
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM + 40 + Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
}
// Widget info
Column {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM + 40 + Theme.spacingM + Theme.iconSize + Theme.spacingM
anchors.right: toggle.left
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: 2
StyledText {
text: modelData.text
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: modelData.enabled ? Theme.surfaceText : Theme.outline
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.description
font.pixelSize: Theme.fontSizeSmall
color: modelData.enabled ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
}
}
// Toggle - positioned at right edge
DankToggle {
id: toggle
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
width: 48
height: 24
hideText: true
checked: modelData.enabled
onToggled: (checked) => {
root.itemEnabledChanged(modelData.id, checked)
}
}
// Drag functionality
MouseArea {
id: dragArea
anchors.fill: parent
hoverEnabled: true
property bool validDragStart: false
drag.target: held && validDragStart ? delegateItem : undefined
drag.axis: Drag.YAxis
drag.minimumY: -delegateItem.height
drag.maximumY: itemsList.height
onPressed: (mouse) => {
// Only allow dragging from the drag handle area (first 60px)
if (mouse.x <= 60) {
validDragStart = true
delegateItem.z = 2
} else {
validDragStart = false
mouse.accepted = false
}
}
onReleased: {
delegateItem.z = 1
if (drag.active && validDragStart) {
// Calculate new index based on position
var newIndex = Math.round(delegateItem.y / (delegateItem.height + itemsList.spacing))
newIndex = Math.max(0, Math.min(newIndex, root.items.length - 1))
if (newIndex !== index) {
var newItems = root.items.slice()
var draggedItem = newItems.splice(index, 1)[0]
newItems.splice(newIndex, 0, draggedItem)
root.itemOrderChanged(newItems.map(item => item.id))
}
}
// Reset position
delegateItem.x = 0
delegateItem.y = 0
validDragStart = false
}
}
// Animations for drag
Behavior on y {
enabled: !dragArea.held && !dragArea.drag.active
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
// Add/Remove Controls
Rectangle {
width: parent.width * 0.5
height: 40
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
anchors.horizontalCenter: parent.horizontalCenter
Row {
anchors.centerIn: parent
spacing: Theme.spacingL
StyledText {
text: "Add or remove widgets"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
anchors.verticalCenter: parent.verticalCenter
}
Row {
spacing: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
// Add button
DankActionButton {
iconName: "add"
iconSize: Theme.iconSize - 4
iconColor: Theme.primary
hoverColor: Theme.primaryContainer
onClicked: {
root.addWidget(root.sectionId);
}
}
// Remove button
DankActionButton {
iconName: "remove"
iconSize: Theme.iconSize - 4
iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.1)
enabled: root.items.length > 0
opacity: root.items.length > 0 ? 1.0 : 0.5
onClicked: {
if (root.items.length > 0) {
root.removeLastWidget(root.sectionId);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,265 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Widgets
Column {
id: root
property var items: []
property var allWidgets: []
property string title: ""
property string titleIcon: "widgets"
property string sectionId: ""
signal itemEnabledChanged(string itemId, bool enabled)
signal itemOrderChanged(var newOrder)
signal addWidget(string sectionId)
signal removeLastWidget(string sectionId)
width: parent.width
spacing: Theme.spacingM
// Header
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: root.titleIcon
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.title
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - 60
height: 1
}
}
// Widget Items
Column {
id: itemsList
width: parent.width
spacing: Theme.spacingS
Repeater {
model: root.items
delegate: Item {
id: delegateItem
width: itemsList.width
height: 70
property int visualIndex: index
property bool held: dragArea.pressed
property string itemId: modelData.id
z: held ? 2 : 1
Rectangle {
id: itemBackground
anchors.fill: parent
anchors.margins: 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
// Drag handle
Rectangle {
width: 40
height: parent.height
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "drag_indicator"
size: Theme.iconSize - 4
color: Theme.outline
anchors.centerIn: parent
opacity: 0.8
}
}
// Widget icon
DankIcon {
name: modelData.icon
size: Theme.iconSize
color: modelData.enabled ? Theme.primary : Theme.outline
anchors.verticalCenter: parent.verticalCenter
}
// Widget info
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - 200 // Leave space for toggle
StyledText {
text: modelData.text
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: modelData.enabled ? Theme.surfaceText : Theme.outline
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.description
font.pixelSize: Theme.fontSizeSmall
color: modelData.enabled ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
}
}
// Spacer to push toggle to right
Item {
width: parent.width - 280 // Dynamic width
height: 1
}
// Toggle - positioned at right edge
DankToggle {
anchors.verticalCenter: parent.verticalCenter
width: 48
height: 24
hideText: true
checked: modelData.enabled
onToggled: (checked) => {
root.itemEnabledChanged(modelData.id, checked)
}
}
}
// Drag functionality
MouseArea {
id: dragArea
anchors.fill: parent
hoverEnabled: true
property bool validDragStart: false
drag.target: held && validDragStart ? delegateItem : undefined
drag.axis: Drag.YAxis
drag.minimumY: -delegateItem.height
drag.maximumY: itemsList.height
onPressed: (mouse) => {
// Only allow dragging from the drag handle area (first 60px)
if (mouse.x <= 60) {
validDragStart = true
delegateItem.z = 2
} else {
validDragStart = false
mouse.accepted = false
}
}
onReleased: {
delegateItem.z = 1
if (drag.active && validDragStart) {
// Calculate new index based on position
var newIndex = Math.round(delegateItem.y / (delegateItem.height + itemsList.spacing))
newIndex = Math.max(0, Math.min(newIndex, root.items.length - 1))
if (newIndex !== index) {
var newItems = root.items.slice()
var draggedItem = newItems.splice(index, 1)[0]
newItems.splice(newIndex, 0, draggedItem)
root.itemOrderChanged(newItems.map(item => item.id))
}
}
// Reset position
delegateItem.x = 0
delegateItem.y = 0
validDragStart = false
}
}
// Animations for drag
Behavior on y {
enabled: !dragArea.held && !dragArea.drag.active
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
// Add/Remove Controls
Rectangle {
width: parent.width * 0.5
height: 40
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
anchors.horizontalCenter: parent.horizontalCenter
Row {
anchors.centerIn: parent
spacing: Theme.spacingL
StyledText {
text: "Add or remove widgets"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
anchors.verticalCenter: parent.verticalCenter
}
Row {
spacing: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
// Add button
DankActionButton {
iconName: "add"
iconSize: Theme.iconSize - 4
iconColor: Theme.primary
hoverColor: Theme.primaryContainer
onClicked: {
root.addWidget(root.sectionId);
}
}
// Remove button
DankActionButton {
iconName: "remove"
iconSize: Theme.iconSize - 4
iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.1)
enabled: root.items.length > 0
opacity: root.items.length > 0 ? 1.0 : 0.5
onClicked: {
if (root.items.length > 0) {
root.removeLastWidget(root.sectionId);
}
}
}
}
}
}
}

View File

@@ -10,23 +10,24 @@ Item {
property bool toggling: false
property string text: ""
property string description: ""
property bool hideText: false
signal clicked()
signal toggled(bool checked)
width: text ? parent.width : 48
height: text ? 60 : 24
width: (text && !hideText) ? parent.width : 48
height: (text && !hideText) ? 60 : 24
StyledRect {
id: background
anchors.fill: parent
radius: toggle.text ? Theme.cornerRadius : 0
color: toggle.text ? Theme.surfaceHover : "transparent"
visible: toggle.text
radius: (toggle.text && !toggle.hideText) ? Theme.cornerRadius : 0
color: (toggle.text && !toggle.hideText) ? Theme.surfaceHover : "transparent"
visible: (toggle.text && !toggle.hideText)
StateLayer {
visible: toggle.text
visible: (toggle.text && !toggle.hideText)
disabled: !toggle.enabled
stateColor: Theme.primary
cornerRadius: parent.radius
@@ -50,7 +51,7 @@ Item {
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingXS
visible: toggle.text
visible: (toggle.text && !toggle.hideText)
Column {
anchors.verticalCenter: parent.verticalCenter

View File

@@ -0,0 +1,187 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Widgets
Popup {
id: root
property var allWidgets: []
property string targetSection: ""
property bool isOpening: false
signal widgetSelected(string widgetId, string targetSection)
width: 400
height: 450
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
// Prevent multiple openings
function safeOpen() {
if (!isOpening && !visible) {
isOpening = true
open()
}
}
onOpened: {
isOpening = false
}
onClosed: {
isOpening = false
// Clear references to prevent memory leaks
allWidgets = []
targetSection = ""
}
background: Rectangle {
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
border.color: Theme.primarySelected
border.width: 1
radius: Theme.cornerRadiusLarge
}
contentItem: Item {
anchors.fill: parent
// Close button in top-right
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 2
iconColor: Theme.outline
hoverColor: Theme.primaryContainer
anchors.top: parent.top
anchors.topMargin: Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
onClicked: root.close()
}
Column {
id: contentColumn
spacing: Theme.spacingM
anchors.fill: parent
anchors.margins: Theme.spacingL
anchors.topMargin: Theme.spacingL + 30 // Space for close button
// Header
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "add_circle"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Add Widget to " + root.targetSection + " Section"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: "Select a widget to add to the " + root.targetSection.toLowerCase() + " section of the top bar. You can add multiple instances of the same widget if needed."
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
width: parent.width
wrapMode: Text.WordWrap
}
// Widget List
ScrollView {
width: parent.width
height: parent.height - 120 // Leave space for header and description
clip: true
ListView {
id: widgetList
spacing: Theme.spacingS
model: root.allWidgets
delegate: Rectangle {
width: widgetList.width
height: 60
radius: Theme.cornerRadius
color: widgetArea.containsMouse ? Theme.primaryHover : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
// Widget icon
DankIcon {
name: modelData.icon
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
// Widget info
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - Theme.iconSize - Theme.spacingM * 3
StyledText {
text: modelData.text
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.description
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
}
}
// Add icon
DankIcon {
name: "add"
size: Theme.iconSize - 4
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: widgetArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.widgetSelected(modelData.id, root.targetSection)
root.close()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
}