1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 05:55:37 -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

@@ -24,14 +24,43 @@ Singleton {
property string profileImage: "" property string profileImage: ""
property string weatherLocation: "New York, NY" property string weatherLocation: "New York, NY"
property string weatherCoordinates: "40.7128,-74.0060" property string weatherCoordinates: "40.7128,-74.0060"
property bool showLauncherButton: true
property bool showWorkspaceSwitcher: true
property bool showFocusedWindow: true property bool showFocusedWindow: true
property bool showWeather: true property bool showWeather: true
property bool showMusic: true property bool showMusic: true
property bool showClipboard: true property bool showClipboard: true
property bool showSystemResources: true property bool showSystemResources: true
property bool showSystemTray: true property bool showSystemTray: true
property bool showClock: true
property bool showNotificationButton: true
property bool showBattery: true
property bool showControlCenterButton: true
property bool showWorkspaceIndex: false property bool showWorkspaceIndex: false
property bool showWorkspacePadding: false property bool showWorkspacePadding: false
property var topBarLeftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"]
property var topBarCenterWidgets: ["clock", "music", "weather"]
property var topBarRightWidgets: ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"]
// Reactive ListModel properties for TopBar
property alias topBarLeftWidgetsModel: leftWidgetsModel
property alias topBarCenterWidgetsModel: centerWidgetsModel
property alias topBarRightWidgetsModel: rightWidgetsModel
// Signal to force immediate TopBar layout refresh
signal forceTopBarLayoutRefresh()
ListModel {
id: leftWidgetsModel
}
ListModel {
id: centerWidgetsModel
}
ListModel {
id: rightWidgetsModel
}
property string appLauncherViewMode: "list" property string appLauncherViewMode: "list"
property string spotlightModalViewMode: "list" property string spotlightModalViewMode: "list"
property string networkPreference: "auto" property string networkPreference: "auto"
@@ -85,7 +114,14 @@ Singleton {
Component.onCompleted: { Component.onCompleted: {
loadSettings(); loadSettings();
fontCheckTimer.start() fontCheckTimer.start();
initializeListModels();
}
function initializeListModels() {
updateListModel(leftWidgetsModel, topBarLeftWidgets);
updateListModel(centerWidgetsModel, topBarCenterWidgets);
updateListModel(rightWidgetsModel, topBarRightWidgets);
} }
function loadSettings() { function loadSettings() {
@@ -109,14 +145,38 @@ Singleton {
profileImage = settings.profileImage !== undefined ? settings.profileImage : ""; profileImage = settings.profileImage !== undefined ? settings.profileImage : "";
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY"; weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY";
weatherCoordinates = settings.weatherCoordinates !== undefined ? settings.weatherCoordinates : "40.7128,-74.0060"; weatherCoordinates = settings.weatherCoordinates !== undefined ? settings.weatherCoordinates : "40.7128,-74.0060";
showLauncherButton = settings.showLauncherButton !== undefined ? settings.showLauncherButton : true;
showWorkspaceSwitcher = settings.showWorkspaceSwitcher !== undefined ? settings.showWorkspaceSwitcher : true;
showFocusedWindow = settings.showFocusedWindow !== undefined ? settings.showFocusedWindow : true; showFocusedWindow = settings.showFocusedWindow !== undefined ? settings.showFocusedWindow : true;
showWeather = settings.showWeather !== undefined ? settings.showWeather : true; showWeather = settings.showWeather !== undefined ? settings.showWeather : true;
showMusic = settings.showMusic !== undefined ? settings.showMusic : true; showMusic = settings.showMusic !== undefined ? settings.showMusic : true;
showClipboard = settings.showClipboard !== undefined ? settings.showClipboard : true; showClipboard = settings.showClipboard !== undefined ? settings.showClipboard : true;
showSystemResources = settings.showSystemResources !== undefined ? settings.showSystemResources : true; showSystemResources = settings.showSystemResources !== undefined ? settings.showSystemResources : true;
showSystemTray = settings.showSystemTray !== undefined ? settings.showSystemTray : true; showSystemTray = settings.showSystemTray !== undefined ? settings.showSystemTray : true;
showClock = settings.showClock !== undefined ? settings.showClock : true;
showNotificationButton = settings.showNotificationButton !== undefined ? settings.showNotificationButton : true;
showBattery = settings.showBattery !== undefined ? settings.showBattery : true;
showControlCenterButton = settings.showControlCenterButton !== undefined ? settings.showControlCenterButton : true;
showWorkspaceIndex = settings.showWorkspaceIndex !== undefined ? settings.showWorkspaceIndex : false; showWorkspaceIndex = settings.showWorkspaceIndex !== undefined ? settings.showWorkspaceIndex : false;
showWorkspacePadding = settings.showWorkspacePadding !== undefined ? settings.showWorkspacePadding : false; showWorkspacePadding = settings.showWorkspacePadding !== undefined ? settings.showWorkspacePadding : false;
if (settings.topBarWidgetOrder) {
// Migrate from old single list to new three-list system
topBarLeftWidgets = settings.topBarWidgetOrder.filter(w => ["launcherButton", "workspaceSwitcher", "focusedWindow"].includes(w));
topBarCenterWidgets = settings.topBarWidgetOrder.filter(w => ["clock", "music", "weather"].includes(w));
topBarRightWidgets = settings.topBarWidgetOrder.filter(w => ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"].includes(w));
} else {
var leftWidgets = settings.topBarLeftWidgets !== undefined ? settings.topBarLeftWidgets : ["launcherButton", "workspaceSwitcher", "focusedWindow"];
var centerWidgets = settings.topBarCenterWidgets !== undefined ? settings.topBarCenterWidgets : ["clock", "music", "weather"];
var rightWidgets = settings.topBarRightWidgets !== undefined ? settings.topBarRightWidgets : ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"];
topBarLeftWidgets = leftWidgets;
topBarCenterWidgets = centerWidgets;
topBarRightWidgets = rightWidgets;
updateListModel(leftWidgetsModel, leftWidgets);
updateListModel(centerWidgetsModel, centerWidgets);
updateListModel(rightWidgetsModel, rightWidgets);
}
appLauncherViewMode = settings.appLauncherViewMode !== undefined ? settings.appLauncherViewMode : "list"; appLauncherViewMode = settings.appLauncherViewMode !== undefined ? settings.appLauncherViewMode : "list";
spotlightModalViewMode = settings.spotlightModalViewMode !== undefined ? settings.spotlightModalViewMode : "list"; spotlightModalViewMode = settings.spotlightModalViewMode !== undefined ? settings.spotlightModalViewMode : "list";
networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto"; networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto";
@@ -163,14 +223,23 @@ Singleton {
"profileImage": profileImage, "profileImage": profileImage,
"weatherLocation": weatherLocation, "weatherLocation": weatherLocation,
"weatherCoordinates": weatherCoordinates, "weatherCoordinates": weatherCoordinates,
"showLauncherButton": showLauncherButton,
"showWorkspaceSwitcher": showWorkspaceSwitcher,
"showFocusedWindow": showFocusedWindow, "showFocusedWindow": showFocusedWindow,
"showWeather": showWeather, "showWeather": showWeather,
"showMusic": showMusic, "showMusic": showMusic,
"showClipboard": showClipboard, "showClipboard": showClipboard,
"showSystemResources": showSystemResources, "showSystemResources": showSystemResources,
"showSystemTray": showSystemTray, "showSystemTray": showSystemTray,
"showClock": showClock,
"showNotificationButton": showNotificationButton,
"showBattery": showBattery,
"showControlCenterButton": showControlCenterButton,
"showWorkspaceIndex": showWorkspaceIndex, "showWorkspaceIndex": showWorkspaceIndex,
"showWorkspacePadding": showWorkspacePadding, "showWorkspacePadding": showWorkspacePadding,
"topBarLeftWidgets": topBarLeftWidgets,
"topBarCenterWidgets": topBarCenterWidgets,
"topBarRightWidgets": topBarRightWidgets,
"appLauncherViewMode": appLauncherViewMode, "appLauncherViewMode": appLauncherViewMode,
"spotlightModalViewMode": spotlightModalViewMode, "spotlightModalViewMode": spotlightModalViewMode,
"networkPreference": networkPreference, "networkPreference": networkPreference,
@@ -340,6 +409,16 @@ Singleton {
} }
// Widget visibility setters // Widget visibility setters
function setShowLauncherButton(enabled) {
showLauncherButton = enabled;
saveSettings();
}
function setShowWorkspaceSwitcher(enabled) {
showWorkspaceSwitcher = enabled;
saveSettings();
}
function setShowFocusedWindow(enabled) { function setShowFocusedWindow(enabled) {
showFocusedWindow = enabled; showFocusedWindow = enabled;
saveSettings(); saveSettings();
@@ -370,6 +449,84 @@ Singleton {
saveSettings(); saveSettings();
} }
function setShowClock(enabled) {
showClock = enabled;
saveSettings();
}
function setShowNotificationButton(enabled) {
showNotificationButton = enabled;
saveSettings();
}
function setShowBattery(enabled) {
showBattery = enabled;
saveSettings();
}
function setShowControlCenterButton(enabled) {
showControlCenterButton = enabled;
saveSettings();
}
function setTopBarWidgetOrder(order) {
topBarWidgetOrder = order;
saveSettings();
}
function setTopBarLeftWidgets(order) {
topBarLeftWidgets = order;
updateListModel(leftWidgetsModel, order);
saveSettings();
}
function setTopBarCenterWidgets(order) {
topBarCenterWidgets = order;
updateListModel(centerWidgetsModel, order);
saveSettings();
}
function setTopBarRightWidgets(order) {
topBarRightWidgets = order;
updateListModel(rightWidgetsModel, order);
saveSettings();
}
function updateListModel(listModel, order) {
listModel.clear();
for (var i = 0; i < order.length; i++) {
listModel.append({"widgetId": order[i]});
}
}
function resetTopBarWidgetsToDefault() {
var defaultLeft = ["launcherButton", "workspaceSwitcher", "focusedWindow"];
var defaultCenter = ["clock", "music", "weather"];
var defaultRight = ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"];
topBarLeftWidgets = defaultLeft;
topBarCenterWidgets = defaultCenter;
topBarRightWidgets = defaultRight;
updateListModel(leftWidgetsModel, defaultLeft);
updateListModel(centerWidgetsModel, defaultCenter);
updateListModel(rightWidgetsModel, defaultRight);
showLauncherButton = true;
showWorkspaceSwitcher = true;
showFocusedWindow = true;
showWeather = true;
showMusic = true;
showClipboard = true;
showSystemResources = true;
showSystemTray = true;
showClock = true;
showNotificationButton = true;
showBattery = true;
showControlCenterButton = true;
saveSettings();
}
// View mode setters // View mode setters
function setAppLauncherViewMode(mode) { function setAppLauncherViewMode(mode) {
appLauncherViewMode = mode; appLauncherViewMode = mode;

View File

@@ -10,6 +10,249 @@ ScrollView {
contentHeight: column.implicitHeight + Theme.spacingXL contentHeight: column.implicitHeight + Theme.spacingXL
clip: true clip: true
property var baseWidgetDefinitions: [
{
id: "launcherButton",
text: "App Launcher",
description: "Quick access to application launcher",
icon: "apps",
enabled: true
},
{
id: "workspaceSwitcher",
text: "Workspace Switcher",
description: "Shows current workspace and allows switching",
icon: "view_module",
enabled: true
},
{
id: "focusedWindow",
text: "Focused Window",
description: "Display currently focused application title",
icon: "window",
enabled: true
},
{
id: "clock",
text: "Clock",
description: "Current time and date display",
icon: "schedule",
enabled: true
},
{
id: "weather",
text: "Weather Widget",
description: "Current weather conditions and temperature",
icon: "wb_sunny",
enabled: true
},
{
id: "music",
text: "Media Controls",
description: "Control currently playing media",
icon: "music_note",
enabled: true
},
{
id: "clipboard",
text: "Clipboard Manager",
description: "Access clipboard history",
icon: "content_paste",
enabled: true
},
{
id: "systemResources",
text: "System Resources",
description: "CPU and memory usage indicators",
icon: "memory",
enabled: true
},
{
id: "systemTray",
text: "System Tray",
description: "System notification area icons",
icon: "notifications",
enabled: true
},
{
id: "controlCenterButton",
text: "Control Center",
description: "Access to system controls and settings",
icon: "settings",
enabled: true
},
{
id: "notificationButton",
text: "Notification Center",
description: "Access to notifications and do not disturb",
icon: "notifications",
enabled: true
},
{
id: "battery",
text: "Battery",
description: "Battery level and power management",
icon: "battery_std",
enabled: true
},
{
id: "spacer",
text: "Spacer",
description: "Empty space to separate widgets",
icon: "more_horiz",
enabled: true
},
{
id: "separator",
text: "Separator",
description: "Visual divider between widgets",
icon: "remove",
enabled: true
}
]
// Default widget configurations for each section
property var defaultLeftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"]
property var defaultCenterWidgets: ["music", "clock", "weather"]
property var defaultRightWidgets: ["clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"]
Component.onCompleted: {
// Initialize sections with defaults if they're empty
if (!Prefs.topBarLeftWidgets || Prefs.topBarLeftWidgets.length === 0) {
Prefs.setTopBarLeftWidgets(defaultLeftWidgets)
}
if (!Prefs.topBarCenterWidgets || Prefs.topBarCenterWidgets.length === 0) {
Prefs.setTopBarCenterWidgets(defaultCenterWidgets)
}
if (!Prefs.topBarRightWidgets || Prefs.topBarRightWidgets.length === 0) {
Prefs.setTopBarRightWidgets(defaultRightWidgets)
}
}
function addWidgetToSection(widgetId, targetSection) {
var leftWidgets = Prefs.topBarLeftWidgets.slice()
var centerWidgets = Prefs.topBarCenterWidgets.slice()
var rightWidgets = Prefs.topBarRightWidgets.slice()
if (targetSection === "left") {
leftWidgets.push(widgetId)
Prefs.setTopBarLeftWidgets(leftWidgets)
} else if (targetSection === "center") {
centerWidgets.push(widgetId)
Prefs.setTopBarCenterWidgets(centerWidgets)
} else if (targetSection === "right") {
rightWidgets.push(widgetId)
Prefs.setTopBarRightWidgets(rightWidgets)
}
}
function removeLastWidgetFromSection(sectionId) {
var leftWidgets = Prefs.topBarLeftWidgets.slice()
var centerWidgets = Prefs.topBarCenterWidgets.slice()
var rightWidgets = Prefs.topBarRightWidgets.slice()
if (sectionId === "left" && leftWidgets.length > 0) {
leftWidgets.pop()
Prefs.setTopBarLeftWidgets(leftWidgets)
} else if (sectionId === "center" && centerWidgets.length > 0) {
centerWidgets.pop()
Prefs.setTopBarCenterWidgets(centerWidgets)
} else if (sectionId === "right" && rightWidgets.length > 0) {
rightWidgets.pop()
Prefs.setTopBarRightWidgets(rightWidgets)
}
}
function handleItemEnabledChanged(itemId, enabled) {
// Update the widget's enabled state in preferences
if (itemId === "focusedWindow") {
Prefs.setShowFocusedWindow(enabled)
} else if (itemId === "weather") {
Prefs.setShowWeather(enabled)
} else if (itemId === "music") {
Prefs.setShowMusic(enabled)
} else if (itemId === "clipboard") {
Prefs.setShowClipboard(enabled)
} else if (itemId === "systemResources") {
Prefs.setShowSystemResources(enabled)
} else if (itemId === "systemTray") {
Prefs.setShowSystemTray(enabled)
} else if (itemId === "clock") {
Prefs.setShowClock(enabled)
} else if (itemId === "notificationButton") {
Prefs.setShowNotificationButton(enabled)
} else if (itemId === "controlCenterButton") {
Prefs.setShowControlCenterButton(enabled)
} else if (itemId === "battery") {
Prefs.setShowBattery(enabled)
} else if (itemId === "launcherButton") {
Prefs.setShowLauncherButton(enabled)
} else if (itemId === "workspaceSwitcher") {
Prefs.setShowWorkspaceSwitcher(enabled)
}
// Note: spacer and separator don't need preference handling as they're always enabled
}
function handleItemOrderChanged(sectionId, newOrder) {
if (sectionId === "left") {
Prefs.setTopBarLeftWidgets(newOrder)
} else if (sectionId === "center") {
Prefs.setTopBarCenterWidgets(newOrder)
} else if (sectionId === "right") {
Prefs.setTopBarRightWidgets(newOrder)
}
}
function getItemsForSection(sectionId) {
var widgets = []
var widgetIds = []
if (sectionId === "left") {
widgetIds = Prefs.topBarLeftWidgets || []
} else if (sectionId === "center") {
widgetIds = Prefs.topBarCenterWidgets || []
} else if (sectionId === "right") {
widgetIds = Prefs.topBarRightWidgets || []
}
widgetIds.forEach(widgetId => {
var widgetDef = baseWidgetDefinitions.find(w => w.id === widgetId)
if (widgetDef) {
var item = Object.assign({}, widgetDef)
// Set enabled state based on preferences
if (widgetId === "focusedWindow") {
item.enabled = Prefs.showFocusedWindow
} else if (widgetId === "weather") {
item.enabled = Prefs.showWeather
} else if (widgetId === "music") {
item.enabled = Prefs.showMusic
} else if (widgetId === "clipboard") {
item.enabled = Prefs.showClipboard
} else if (widgetId === "systemResources") {
item.enabled = Prefs.showSystemResources
} else if (widgetId === "systemTray") {
item.enabled = Prefs.showSystemTray
} else if (widgetId === "clock") {
item.enabled = Prefs.showClock
} else if (widgetId === "notificationButton") {
item.enabled = Prefs.showNotificationButton
} else if (widgetId === "controlCenterButton") {
item.enabled = Prefs.showControlCenterButton
} else if (widgetId === "battery") {
item.enabled = Prefs.showBattery
} else if (widgetId === "launcherButton") {
item.enabled = Prefs.showLauncherButton
} else if (widgetId === "workspaceSwitcher") {
item.enabled = Prefs.showWorkspaceSwitcher
}
// spacer and separator are always enabled (no preference toggle needed)
widgets.push(item)
}
})
return widgets
}
Column { Column {
id: column id: column
@@ -18,22 +261,7 @@ ScrollView {
topPadding: Theme.spacingL topPadding: Theme.spacingL
bottomPadding: Theme.spacingXL bottomPadding: Theme.spacingXL
// Top Bar Widgets Section // Header section
StyledRect {
width: parent.width
height: topBarSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadiusLarge
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
Column {
id: topBarSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -46,77 +274,200 @@ ScrollView {
} }
StyledText { StyledText {
text: "Top Bar Widgets" text: "Top Bar Widget Management"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Item {
width: parent.width - 400
height: 1
} }
DankToggle { Rectangle {
width: 80
height: 28
radius: Theme.cornerRadius
color: resetArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariant
anchors.verticalCenter: parent.verticalCenter
border.width: 1
border.color: resetArea.containsMouse ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5)
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "refresh"
size: 14
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Reset"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: resetArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
// Reset all sections to defaults
Prefs.setTopBarLeftWidgets(defaultLeftWidgets)
Prefs.setTopBarCenterWidgets(defaultCenterWidgets)
Prefs.setTopBarRightWidgets(defaultRightWidgets)
// Reset all widget enabled states to defaults (all enabled)
Prefs.setShowFocusedWindow(true)
Prefs.setShowWeather(true)
Prefs.setShowMusic(true)
Prefs.setShowClipboard(true)
Prefs.setShowSystemResources(true)
Prefs.setShowSystemTray(true)
Prefs.setShowClock(true)
Prefs.setShowNotificationButton(true)
Prefs.setShowControlCenterButton(true)
Prefs.setShowBattery(true)
Prefs.setShowLauncherButton(true)
Prefs.setShowWorkspaceSwitcher(true)
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on border.color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
Rectangle {
width: parent.width width: parent.width
text: "Focused Window" height: messageText.contentHeight + Theme.spacingM * 2
description: "Show the currently focused application in the top bar" radius: Theme.cornerRadius
checked: Prefs.showFocusedWindow color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
onToggled: (checked) => { border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
return Prefs.setShowFocusedWindow(checked); border.width: 1
visible: true
opacity: 1.0
z: 1
StyledText {
id: messageText
anchors.centerIn: parent
text: "Drag widgets to reorder within sections. Use + to add widgets and - to remove the last widget from each section."
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
width: parent.width - Theme.spacingM * 2
wrapMode: Text.WordWrap
} }
} }
DankToggle { // Widget sections
Column {
width: parent.width width: parent.width
text: "Weather Widget" spacing: Theme.spacingL
description: "Display weather information in the top bar"
checked: Prefs.showWeather
onToggled: (checked) => {
return Prefs.setShowWeather(checked);
}
}
DankToggle { // Left Section
DankSections {
width: parent.width width: parent.width
text: "Media Controls" title: "Left Section"
description: "Show currently playing media in the top bar" titleIcon: "format_align_left"
checked: Prefs.showMusic sectionId: "left"
onToggled: (checked) => { allWidgets: widgetsTab.baseWidgetDefinitions
return Prefs.setShowMusic(checked); items: widgetsTab.getItemsForSection("left")
onItemEnabledChanged: (itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(itemId, enabled)
}
onItemOrderChanged: (newOrder) => {
widgetsTab.handleItemOrderChanged("left", newOrder)
}
onAddWidget: (sectionId) => {
widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
}
onRemoveLastWidget: (sectionId) => {
widgetsTab.removeLastWidgetFromSection(sectionId)
} }
} }
DankToggle { // Center Section
DankSections {
width: parent.width width: parent.width
text: "Clipboard Button" title: "Center Section"
description: "Show clipboard access button in the top bar" titleIcon: "format_align_center"
checked: Prefs.showClipboard sectionId: "center"
onToggled: (checked) => { allWidgets: widgetsTab.baseWidgetDefinitions
return Prefs.setShowClipboard(checked); items: widgetsTab.getItemsForSection("center")
onItemEnabledChanged: (itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(itemId, enabled)
}
onItemOrderChanged: (newOrder) => {
widgetsTab.handleItemOrderChanged("center", newOrder)
}
onAddWidget: (sectionId) => {
widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
}
onRemoveLastWidget: (sectionId) => {
widgetsTab.removeLastWidgetFromSection(sectionId)
} }
} }
DankToggle { // Right Section
DankSections {
width: parent.width width: parent.width
text: "System Resources" title: "Right Section"
description: "Display CPU and RAM usage indicators" titleIcon: "format_align_right"
checked: Prefs.showSystemResources sectionId: "right"
onToggled: (checked) => { allWidgets: widgetsTab.baseWidgetDefinitions
return Prefs.setShowSystemResources(checked); items: widgetsTab.getItemsForSection("right")
}
onItemEnabledChanged: (itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(itemId, enabled)
} }
DankToggle { onItemOrderChanged: (newOrder) => {
width: parent.width widgetsTab.handleItemOrderChanged("right", newOrder)
text: "System Tray"
description: "Show system tray icons in the top bar"
checked: Prefs.showSystemTray
onToggled: (checked) => {
return Prefs.setShowSystemTray(checked);
}
} }
onAddWidget: (sectionId) => {
widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
} }
onRemoveLastWidget: (sectionId) => {
widgetsTab.removeLastWidgetFromSection(sectionId)
}
}
} }
// Workspace Section // Workspace Section
@@ -153,7 +504,6 @@ ScrollView {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
DankToggle { DankToggle {
@@ -175,11 +525,47 @@ ScrollView {
return Prefs.setShowWorkspacePadding(checked); return Prefs.setShowWorkspacePadding(checked);
} }
} }
}
}
} }
// Tooltip for reset button (positioned above the button)
Rectangle {
width: tooltipText.contentWidth + Theme.spacingM * 2
height: tooltipText.contentHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outline
border.width: 1
visible: resetArea.containsMouse
opacity: resetArea.containsMouse ? 1 : 0
y: column.y + 48 // Position above the reset button in the header
x: parent.width - width - Theme.spacingM
z: 100
StyledText {
id: tooltipText
anchors.centerIn: parent
text: "Reset widget layout to defaults"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
} }
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
// Widget selection popup
DankWidgetSelectionPopup {
id: widgetSelectionPopup
anchors.centerIn: parent
onWidgetSelected: (widgetId, targetSection) => {
widgetsTab.addWidgetToSection(widgetId, targetSection)
}
}
} }

View File

@@ -29,6 +29,22 @@ PanelWindow {
if (fonts.indexOf("Material Symbols Rounded") === -1) if (fonts.indexOf("Material Symbols Rounded") === -1)
ToastService.showError("Please install Material Symbols Rounded and Restart your Shell. See README.md for instructions"); ToastService.showError("Please install Material Symbols Rounded and Restart your Shell. See README.md for instructions");
// Connect to the force refresh signal
Prefs.forceTopBarLayoutRefresh.connect(function() {
console.log("TopBar: Forcing layout refresh");
// Force layout recalculation by toggling visibility briefly
Qt.callLater(() => {
leftSection.visible = false;
centerSection.visible = false;
rightSection.visible = false;
Qt.callLater(() => {
leftSection.visible = true;
centerSection.visible = true;
rightSection.visible = true;
console.log("TopBar: Layout refresh completed");
});
});
});
} }
Connections { Connections {
@@ -36,6 +52,8 @@ PanelWindow {
root.backgroundTransparency = Prefs.topBarTransparency; root.backgroundTransparency = Prefs.topBarTransparency;
} }
// Remove old manual refresh handlers - ListModel updates are automatic
target: Prefs target: Prefs
} }
@@ -116,12 +134,12 @@ PanelWindow {
// Use estimated fixed widths to break circular dependencies // Use estimated fixed widths to break circular dependencies
readonly property int launcherButtonWidth: 40 readonly property int launcherButtonWidth: 40
readonly property int workspaceSwitcherWidth: 120 // Approximate readonly property int workspaceSwitcherWidth: 120 // Approximate
readonly property int focusedAppMaxWidth: focusedApp.visible ? 456 : 0 readonly property int focusedAppMaxWidth: 456 // Fixed width since we don't have focusedApp reference
readonly property int estimatedLeftSectionWidth: launcherButtonWidth + workspaceSwitcherWidth + focusedAppMaxWidth + (Theme.spacingXS * 2) readonly property int estimatedLeftSectionWidth: launcherButtonWidth + workspaceSwitcherWidth + focusedAppMaxWidth + (Theme.spacingXS * 2)
readonly property int rightSectionWidth: rightSection.width readonly property int rightSectionWidth: rightSection.width
readonly property int clockWidth: clock.width readonly property int clockWidth: 120 // Approximate clock width
readonly property int mediaMaxWidth: media.visible ? 280 : 0 // Normal max width readonly property int mediaMaxWidth: 280 // Normal max width
readonly property int weatherWidth: weather.visible ? weather.width : 0 readonly property int weatherWidth: 80 // Approximate weather width
readonly property bool validLayout: availableWidth > 100 && estimatedLeftSectionWidth > 0 && rightSectionWidth > 0 readonly property bool validLayout: availableWidth > 100 && estimatedLeftSectionWidth > 0 && rightSectionWidth > 0
readonly property int clockLeftEdge: (availableWidth - clockWidth) / 2 readonly property int clockLeftEdge: (availableWidth - clockWidth) / 2
readonly property int clockRightEdge: clockLeftEdge + clockWidth readonly property int clockRightEdge: clockLeftEdge + clockWidth
@@ -135,6 +153,79 @@ PanelWindow {
readonly property bool spacingTight: validLayout && (leftToMediaGap < 150 || clockToRightGap < 100) readonly property bool spacingTight: validLayout && (leftToMediaGap < 150 || clockToRightGap < 100)
readonly property bool overlapping: validLayout && (leftToMediaGap < 100 || clockToRightGap < 50) readonly property bool overlapping: validLayout && (leftToMediaGap < 100 || clockToRightGap < 50)
// Helper functions
function getWidgetEnabled(widgetId) {
switch (widgetId) {
case "launcherButton": return Prefs.showLauncherButton
case "workspaceSwitcher": return Prefs.showWorkspaceSwitcher
case "focusedWindow": return Prefs.showFocusedWindow
case "clock": return Prefs.showClock
case "music": return Prefs.showMusic
case "weather": return Prefs.showWeather
case "systemTray": return Prefs.showSystemTray
case "clipboard": return Prefs.showClipboard
case "systemResources": return Prefs.showSystemResources
case "notificationButton": return Prefs.showNotificationButton
case "battery": return Prefs.showBattery
case "controlCenterButton": return Prefs.showControlCenterButton
default: return false
}
}
function getWidgetVisible(widgetId) {
// Some widgets have additional visibility conditions
switch (widgetId) {
case "launcherButton": return true
case "workspaceSwitcher": return true // Simplified - was NiriService.niriAvailable
case "focusedWindow": return true
case "clock": return true
case "music": return true // Simplified - was MprisController.activePlayer
case "weather": return true // Simplified - was complex weather condition
case "systemTray": return true
case "clipboard": return true
case "systemResources": return true
case "notificationButton": return true
case "battery": return true
case "controlCenterButton": return true
default: return false
}
}
function getWidgetComponent(widgetId) {
switch (widgetId) {
case "launcherButton":
return launcherButtonComponent
case "workspaceSwitcher":
return workspaceSwitcherComponent
case "focusedWindow":
return focusedWindowComponent
case "clock":
return clockComponent
case "music":
return mediaComponent
case "weather":
return weatherComponent
case "systemTray":
return systemTrayComponent
case "clipboard":
return clipboardComponent
case "systemResources":
return systemResourcesComponent
case "notificationButton":
return notificationButtonComponent
case "battery":
return batteryComponent
case "controlCenterButton":
return controlCenterButtonComponent
case "spacer":
return spacerComponent
case "separator":
return separatorComponent
default:
return null
}
}
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
@@ -142,6 +233,7 @@ PanelWindow {
anchors.bottomMargin: Theme.spacingXS anchors.bottomMargin: Theme.spacingXS
clip: true clip: true
// Dynamic left section
Row { Row {
id: leftSection id: leftSection
@@ -150,67 +242,45 @@ PanelWindow {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
LauncherButton { Repeater {
anchors.verticalCenter: parent.verticalCenter model: Prefs.topBarLeftWidgetsModel
isActive: appDrawerPopout ? appDrawerPopout.isVisible : false
onClicked: {
if (appDrawerPopout)
appDrawerPopout.toggle();
Loader {
anchors.verticalCenter: parent ? parent.verticalCenter : undefined
active: topBarContent.getWidgetEnabled(model.widgetId) && topBarContent.getWidgetVisible(model.widgetId)
sourceComponent: topBarContent.getWidgetComponent(model.widgetId)
property string widgetId: model.widgetId
}
} }
} }
WorkspaceSwitcher { // Dynamic center section
anchors.verticalCenter: parent.verticalCenter Row {
screenName: root.screenName id: centerSection
}
FocusedApp {
id: focusedApp
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showFocusedWindow
compactMode: topBarContent.spacingTight
availableWidth: topBarContent.leftToMediaGap
}
}
Clock {
id: clock
height: parent.height
spacing: Theme.spacingS
anchors.centerIn: parent anchors.centerIn: parent
compactMode: topBarContent.overlapping
onClockClicked: { Component.onCompleted: {
centcomPopout.calendarVisible = !centcomPopout.calendarVisible; console.log("Center widgets model count:", Prefs.topBarCenterWidgetsModel.count)
} }
}
Repeater {
Media { model: Prefs.topBarCenterWidgetsModel
id: media
Loader {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent ? parent.verticalCenter : undefined
anchors.right: clock.left active: topBarContent.getWidgetEnabled(model.widgetId) && topBarContent.getWidgetVisible(model.widgetId)
anchors.rightMargin: Theme.spacingS sourceComponent: topBarContent.getWidgetComponent(model.widgetId)
visible: Prefs.showMusic && MprisController.activePlayer
compactMode: topBarContent.spacingTight || topBarContent.overlapping property string widgetId: model.widgetId
onClicked: { }
centcomPopout.calendarVisible = !centcomPopout.calendarVisible;
}
}
Weather {
id: weather
anchors.verticalCenter: parent.verticalCenter
anchors.left: clock.right
anchors.leftMargin: Theme.spacingS
visible: Prefs.showWeather && WeatherService.weather.available && WeatherService.weather.temp > 0 && WeatherService.weather.tempF > 0
onClicked: {
centcomPopout.calendarVisible = !centcomPopout.calendarVisible;
} }
} }
// Dynamic right section
Row { Row {
id: rightSection id: rightSection
@@ -219,9 +289,82 @@ PanelWindow {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Component.onCompleted: {
console.log("Right widgets model count:", Prefs.topBarRightWidgetsModel.count)
}
Repeater {
model: Prefs.topBarRightWidgetsModel
Loader {
anchors.verticalCenter: parent ? parent.verticalCenter : undefined
active: topBarContent.getWidgetEnabled(model.widgetId) && topBarContent.getWidgetVisible(model.widgetId)
sourceComponent: topBarContent.getWidgetComponent(model.widgetId)
property string widgetId: model.widgetId
}
}
}
// Widget Components
Component {
id: launcherButtonComponent
LauncherButton {
isActive: appDrawerPopout ? appDrawerPopout.isVisible : false
onClicked: {
if (appDrawerPopout)
appDrawerPopout.toggle();
}
}
}
Component {
id: workspaceSwitcherComponent
WorkspaceSwitcher {
screenName: root.screenName
}
}
Component {
id: focusedWindowComponent
FocusedApp {
compactMode: topBarContent.spacingTight
availableWidth: topBarContent.leftToMediaGap
}
}
Component {
id: clockComponent
Clock {
compactMode: topBarContent.overlapping
onClockClicked: {
centcomPopout.calendarVisible = !centcomPopout.calendarVisible;
}
}
}
Component {
id: mediaComponent
Media {
compactMode: topBarContent.spacingTight || topBarContent.overlapping
onClicked: {
centcomPopout.calendarVisible = !centcomPopout.calendarVisible;
}
}
}
Component {
id: weatherComponent
Weather {
onClicked: {
centcomPopout.calendarVisible = !centcomPopout.calendarVisible;
}
}
}
Component {
id: systemTrayComponent
SystemTrayBar { SystemTrayBar {
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemTray
onMenuRequested: (menu, item, x, y) => { onMenuRequested: (menu, item, x, y) => {
systemTrayContextMenu.currentTrayMenu = menu; systemTrayContextMenu.currentTrayMenu = menu;
systemTrayContextMenu.currentTrayItem = item; systemTrayContextMenu.currentTrayItem = item;
@@ -231,7 +374,10 @@ PanelWindow {
menu.menuVisible = true; menu.menuVisible = true;
} }
} }
}
Component {
id: clipboardComponent
Rectangle { Rectangle {
width: 40 width: 40
height: 30 height: 30
@@ -240,8 +386,6 @@ PanelWindow {
const baseColor = clipboardArea.containsMouse ? Theme.primaryHover : Theme.secondaryHover; const baseColor = clipboardArea.containsMouse ? Theme.primaryHover : Theme.secondaryHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showClipboard
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -252,7 +396,6 @@ PanelWindow {
MouseArea { MouseArea {
id: clipboardArea id: clipboardArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -266,75 +409,84 @@ PanelWindow {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
}
}
} }
} Component {
id: systemResourcesComponent
Row {
spacing: Theme.spacingXS
Loader {
anchors.verticalCenter: parent.verticalCenter
active: Prefs.showSystemResources
sourceComponent: Component {
CpuMonitor { CpuMonitor {
toggleProcessList: () => { toggleProcessList: () => {
return processListPopout.toggle(); return processListPopout.toggle();
} }
} }
}
}
Loader {
anchors.verticalCenter: parent.verticalCenter
active: Prefs.showSystemResources
sourceComponent: Component {
RamMonitor { RamMonitor {
toggleProcessList: () => { toggleProcessList: () => {
return processListPopout.toggle(); return processListPopout.toggle();
} }
} }
}
}
} }
Component {
id: notificationButtonComponent
NotificationCenterButton { NotificationCenterButton {
anchors.verticalCenter: parent.verticalCenter
hasUnread: root.notificationCount > 0 hasUnread: root.notificationCount > 0
isActive: notificationCenter.notificationHistoryVisible isActive: notificationCenter.notificationHistoryVisible
onClicked: { onClicked: {
notificationCenter.notificationHistoryVisible = !notificationCenter.notificationHistoryVisible; notificationCenter.notificationHistoryVisible = !notificationCenter.notificationHistoryVisible;
} }
} }
}
Component {
id: batteryComponent
Battery { Battery {
anchors.verticalCenter: parent.verticalCenter
batteryPopupVisible: batteryPopout.batteryPopupVisible batteryPopupVisible: batteryPopout.batteryPopupVisible
onToggleBatteryPopup: { onToggleBatteryPopup: {
batteryPopout.batteryPopupVisible = !batteryPopout.batteryPopupVisible; batteryPopout.batteryPopupVisible = !batteryPopout.batteryPopupVisible;
} }
} }
}
Component {
id: controlCenterButtonComponent
ControlCenterButton { ControlCenterButton {
anchors.verticalCenter: parent.verticalCenter
isActive: controlCenterPopout.controlCenterVisible isActive: controlCenterPopout.controlCenterVisible
onClicked: { onClicked: {
controlCenterPopout.controlCenterVisible = !controlCenterPopout.controlCenterVisible; controlCenterPopout.controlCenterVisible = !controlCenterPopout.controlCenterVisible;
if (controlCenterPopout.controlCenterVisible) { if (controlCenterPopout.controlCenterVisible) {
if (NetworkService.wifiEnabled) if (NetworkService.wifiEnabled)
NetworkService.scanWifi(); NetworkService.scanWifi();
} }
} }
} }
}
Component {
id: spacerComponent
Item {
width: 20
height: 30
}
}
Component {
id: separatorComponent
Rectangle {
width: 1
height: 20
color: Theme.outline
opacity: 0.3
}
}
} }
} }
} }
}

View File

@@ -30,7 +30,10 @@ StyledRect {
StateLayer { StateLayer {
stateColor: Theme.primary stateColor: Theme.primary
cornerRadius: root.radius 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) { if (selectedIndex >= 0 && selectedIndex < filteredOptions.length) {
root.currentValue = filteredOptions[selectedIndex]; root.currentValue = filteredOptions[selectedIndex];
root.valueChanged(filteredOptions[selectedIndex]); root.valueChanged(filteredOptions[selectedIndex]);
dropdownMenu.close(); close();
} }
} }
@@ -256,17 +256,17 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: 1 anchors.margins: 1
placeholderText: "Search..." placeholderText: "Search..."
text: dropdownMenu.searchQuery text: searchQuery
topPadding: Theme.spacingS topPadding: Theme.spacingS
bottomPadding: Theme.spacingS bottomPadding: Theme.spacingS
onTextChanged: { onTextChanged: {
dropdownMenu.searchQuery = text; searchQuery = text;
dropdownMenu.updateFilteredOptions(); updateFilteredOptions();
} }
Keys.onDownPressed: dropdownMenu.selectNext() Keys.onDownPressed: selectNext()
Keys.onUpPressed: dropdownMenu.selectPrevious() Keys.onUpPressed: selectPrevious()
Keys.onReturnPressed: dropdownMenu.selectCurrent() Keys.onReturnPressed: selectCurrent()
Keys.onEnterPressed: dropdownMenu.selectCurrent() Keys.onEnterPressed: selectCurrent()
} }
} }
@@ -286,7 +286,7 @@ Rectangle {
width: parent.width width: parent.width
height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0) height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0)
clip: true clip: true
model: dropdownMenu.filteredOptions model: filteredOptions
spacing: 2 spacing: 2
WheelHandler { WheelHandler {
@@ -311,7 +311,7 @@ Rectangle {
} }
delegate: Rectangle { delegate: Rectangle {
property bool isSelected: dropdownMenu.selectedIndex === index property bool isSelected: selectedIndex === index
property bool isCurrentValue: root.currentValue === modelData property bool isCurrentValue: root.currentValue === modelData
property int optionIndex: root.options.indexOf(modelData) property int optionIndex: root.options.indexOf(modelData)
@@ -354,7 +354,7 @@ Rectangle {
onClicked: { onClicked: {
root.currentValue = modelData; root.currentValue = modelData;
root.valueChanged(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 bool toggling: false
property string text: "" property string text: ""
property string description: "" property string description: ""
property bool hideText: false
signal clicked() signal clicked()
signal toggled(bool checked) signal toggled(bool checked)
width: text ? parent.width : 48 width: (text && !hideText) ? parent.width : 48
height: text ? 60 : 24 height: (text && !hideText) ? 60 : 24
StyledRect { StyledRect {
id: background id: background
anchors.fill: parent anchors.fill: parent
radius: toggle.text ? Theme.cornerRadius : 0 radius: (toggle.text && !toggle.hideText) ? Theme.cornerRadius : 0
color: toggle.text ? Theme.surfaceHover : "transparent" color: (toggle.text && !toggle.hideText) ? Theme.surfaceHover : "transparent"
visible: toggle.text visible: (toggle.text && !toggle.hideText)
StateLayer { StateLayer {
visible: toggle.text visible: (toggle.text && !toggle.hideText)
disabled: !toggle.enabled disabled: !toggle.enabled
stateColor: Theme.primary stateColor: Theme.primary
cornerRadius: parent.radius cornerRadius: parent.radius
@@ -50,7 +51,7 @@ Item {
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: toggle.text visible: (toggle.text && !toggle.hideText)
Column { Column {
anchors.verticalCenter: parent.verticalCenter 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
}
}
}
}
}
}
}
}