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

cleanup and qmlfmt some modules

This commit is contained in:
bbedward
2025-09-03 15:00:03 -04:00
parent d4db8a01fe
commit 3856ce14cd
17 changed files with 792 additions and 1393 deletions

View File

@@ -23,10 +23,6 @@ DankPopout {
appLauncher.keyboardNavigationActive = false appLauncher.keyboardNavigationActive = false
} }
function hide() {
close()
}
function setTriggerPosition(x, y, width, section, screen) { function setTriggerPosition(x, y, width, section, screen) {
triggerX = x triggerX = x
triggerY = y triggerY = y
@@ -46,10 +42,10 @@ DankPopout {
onOpened: { onOpened: {
Qt.callLater(() => { Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) { if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.forceActiveFocus() contentLoader.item.searchField.forceActiveFocus()
} }
}) })
} }
AppLauncher { AppLauncher {
@@ -57,7 +53,7 @@ DankPopout {
viewMode: SettingsData.appLauncherViewMode viewMode: SettingsData.appLauncherViewMode
gridColumns: 4 gridColumns: 4
onAppLaunched: appDrawerPopout.hide() onAppLaunched: appDrawerPopout.close()
onViewModeSelected: function (mode) { onViewModeSelected: function (mode) {
SettingsData.setAppLauncherViewMode(mode) SettingsData.setAppLauncherViewMode(mode)
} }
@@ -74,34 +70,30 @@ DankPopout {
antialiasing: true antialiasing: true
smooth: true smooth: true
Rectangle { // Multi-layer border effect
anchors.fill: parent Repeater {
anchors.margins: -3 model: [{
color: "transparent" "margin": -3,
radius: parent.radius + 3 "color": Qt.rgba(0, 0, 0, 0.05),
border.color: Qt.rgba(0, 0, 0, 0.05) "z": -3
border.width: 1 }, {
z: -3 "margin": -2,
} "color": Qt.rgba(0, 0, 0, 0.08),
"z": -2
Rectangle { }, {
anchors.fill: parent "margin": 0,
anchors.margins: -2 "color": Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12),
color: "transparent" "z": -1
radius: parent.radius + 2 }]
border.color: Qt.rgba(0, 0, 0, 0.08) Rectangle {
border.width: 1 anchors.fill: parent
z: -2 anchors.margins: modelData.margin
} color: "transparent"
radius: parent.radius + Math.abs(modelData.margin)
Rectangle { border.color: modelData.color
anchors.fill: parent border.width: 1
color: "transparent" z: modelData.z
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, }
Theme.outline.b, 0.12)
border.width: 1
radius: parent.radius
z: -1
} }
Item { Item {
@@ -109,31 +101,30 @@ DankPopout {
anchors.fill: parent anchors.fill: parent
focus: true focus: true
readonly property var keyMappings: {
const mappings = {}
mappings[Qt.Key_Escape] = () => appDrawerPopout.close()
mappings[Qt.Key_Down] = () => appLauncher.selectNext()
mappings[Qt.Key_Up] = () => appLauncher.selectPrevious()
mappings[Qt.Key_Return] = () => appLauncher.launchSelected()
mappings[Qt.Key_Enter] = () => appLauncher.launchSelected()
if (appLauncher.viewMode === "grid") {
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow()
mappings[Qt.Key_Left] = () => appLauncher.selectPreviousInRow()
}
return mappings
}
Keys.onPressed: function (event) { Keys.onPressed: function (event) {
if (event.key === Qt.Key_Escape) { if (keyMappings[event.key]) {
appDrawerPopout.close() keyMappings[event.key]()
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_Down) { return
appLauncher.selectNext() }
event.accepted = true
} else if (event.key === Qt.Key_Up) { if (!searchField.activeFocus && event.text && /[a-zA-Z0-9\s]/.test(event.text)) {
appLauncher.selectPrevious()
event.accepted = true
} else if (event.key === Qt.Key_Right
&& appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
event.accepted = true
} else if (event.key === Qt.Key_Left
&& appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
event.accepted = true
} else if (event.key === Qt.Key_Return
|| event.key === Qt.Key_Enter) {
appLauncher.launchSelected()
event.accepted = true
} else if (!searchField.activeFocus && event.text
&& event.text.length > 0 && event.text.match(
/[a-zA-Z0-9\\s]/)) {
searchField.forceActiveFocus() searchField.forceActiveFocus()
searchField.insertText(event.text) searchField.insertText(event.text)
event.accepted = true event.accepted = true
@@ -178,15 +169,8 @@ DankPopout {
width: parent.width width: parent.width
height: 52 height: 52
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
backgroundColor: Qt.rgba( backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
Theme.surfaceVariant.r, normalBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
Theme.getContentBackgroundAlpha(
) * 0.7)
normalBorderColor: Qt.rgba(Theme.outline.r,
Theme.outline.g,
Theme.outline.b, 0.3)
focusedBorderColor: Theme.primary focusedBorderColor: Theme.primary
leftIconName: "search" leftIconName: "search"
leftIconSize: Theme.iconSize leftIconSize: Theme.iconSize
@@ -204,28 +188,34 @@ DankPopout {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
appDrawerPopout.close() appDrawerPopout.close()
event.accepted = true event.accepted = true
} else if ((event.key === Qt.Key_Return return
|| event.key === Qt.Key_Enter) }
&& text.length > 0) {
if (appLauncher.keyboardNavigationActive const isEnterKey = [Qt.Key_Return, Qt.Key_Enter].includes(event.key)
&& appLauncher.model.count > 0) { const hasText = text.length > 0
if (isEnterKey && hasText) {
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0) {
appLauncher.launchSelected() appLauncher.launchSelected()
} else if (appLauncher.model.count > 0) { } else if (appLauncher.model.count > 0) {
var firstApp = appLauncher.model.get(0) appLauncher.launchApp(appLauncher.model.get(0))
appLauncher.launchApp(firstApp)
} }
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_Down || event.key return
=== Qt.Key_Up || event.key === Qt.Key_Left || event.key
=== Qt.Key_Right || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
event.accepted = false
} }
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right]
const isNavigationKey = navigationKeys.includes(event.key)
const isEmptyEnter = isEnterKey && !hasText
event.accepted = !(isNavigationKey || isEmptyEnter)
} }
Connections { Connections {
function onShouldBeVisibleChanged() { function onShouldBeVisibleChanged() {
if (!appDrawerPopout.shouldBeVisible) if (!appDrawerPopout.shouldBeVisible) {
searchField.clearFocus() searchField.focus = false
}
} }
target: appDrawerPopout target: appDrawerPopout
@@ -268,14 +258,8 @@ DankPopout {
circular: false circular: false
iconName: "view_list" iconName: "view_list"
iconSize: 20 iconSize: 20
iconColor: appLauncher.viewMode iconColor: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
=== "list" ? Theme.primary : Theme.surfaceText backgroundColor: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
backgroundColor: appLauncher.viewMode
=== "list" ? Qt.rgba(
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : "transparent"
onClicked: { onClicked: {
appLauncher.setViewMode("list") appLauncher.setViewMode("list")
} }
@@ -286,14 +270,8 @@ DankPopout {
circular: false circular: false
iconName: "grid_view" iconName: "grid_view"
iconSize: 20 iconSize: 20
iconColor: appLauncher.viewMode iconColor: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
=== "grid" ? Theme.primary : Theme.surfaceText backgroundColor: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
backgroundColor: appLauncher.viewMode
=== "grid" ? Qt.rgba(
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : "transparent"
onClicked: { onClicked: {
appLauncher.setViewMode("grid") appLauncher.setViewMode("grid")
} }
@@ -310,11 +288,8 @@ DankPopout {
return parent.height - usedHeight return parent.height - usedHeight
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
Theme.surfaceVariant.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
Theme.surfaceVariant.b, 0.1)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.05)
border.width: 1 border.width: 1
DankListView { DankListView {
@@ -408,11 +383,7 @@ DankPopout {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: (model.name text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
&& model.name.length
> 0) ? model.name.charAt(
0).toUpperCase(
) : "A"
font.pixelSize: appList.iconSize * 0.4 font.pixelSize: appList.iconSize * 0.4
color: Theme.primary color: Theme.primary
font.weight: Font.Bold font.weight: Font.Bold
@@ -440,9 +411,7 @@ DankPopout {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
elide: Text.ElideRight elide: Text.ElideRight
visible: appList.showDescription visible: appList.showDescription && model.comment && model.comment.length > 0
&& model.comment
&& model.comment.length > 0
} }
} }
} }
@@ -456,8 +425,7 @@ DankPopout {
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
z: 10 z: 10
onEntered: { onEntered: {
if (appList.hoverUpdatesSelection if (appList.hoverUpdatesSelection && !appList.keyboardNavigationActive)
&& !appList.keyboardNavigationActive)
appList.currentIndex = index appList.currentIndex = index
} }
onPositionChanged: { onPositionChanged: {
@@ -465,16 +433,10 @@ DankPopout {
} }
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appList.itemClicked( appList.itemClicked(index, model)
index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var panelPos = mapToItem( var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y)
contextMenu.parent, appList.itemRightClicked(index, model, panelPos.x, panelPos.y)
mouse.x, mouse.y)
appList.itemRightClicked(
index, model,
panelPos.x,
panelPos.y)
} }
} }
} }
@@ -508,8 +470,7 @@ DankPopout {
if (index < 0 || index >= count) if (index < 0 || index >= count)
return return
var itemY = Math.floor( var itemY = Math.floor(index / actualColumns) * cellHeight
index / actualColumns) * cellHeight
var itemBottom = itemY + cellHeight var itemBottom = itemY + cellHeight
if (itemY < contentY) if (itemY < contentY)
contentY = itemY contentY = itemY
@@ -524,8 +485,7 @@ DankPopout {
clip: true clip: true
cellWidth: baseCellWidth cellWidth: baseCellWidth
cellHeight: baseCellHeight cellHeight: baseCellHeight
leftMargin: Math.max(Theme.spacingS, leftMargin: Math.max(Theme.spacingS, remainingSpace / 2)
remainingSpace / 2)
rightMargin: leftMargin rightMargin: leftMargin
focus: true focus: true
interactive: true interactive: true
@@ -560,12 +520,7 @@ DankPopout {
spacing: Theme.spacingS spacing: Theme.spacingS
Item { Item {
property int iconSize: Math.min( property int iconSize: Math.min(appGrid.maxIconSize, Math.max(appGrid.minIconSize, appGrid.cellWidth * appGrid.iconSizeRatio))
appGrid.maxIconSize,
Math.max(
appGrid.minIconSize,
appGrid.cellWidth
* appGrid.iconSizeRatio))
width: iconSize width: iconSize
height: iconSize height: iconSize
@@ -591,14 +546,8 @@ DankPopout {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: (model.name text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
&& model.name.length font.pixelSize: Math.min(28, parent.width * 0.5)
> 0) ? model.name.charAt(
0).toUpperCase(
) : "A"
font.pixelSize: Math.min(
28,
parent.width * 0.5)
color: Theme.primary color: Theme.primary
font.weight: Font.Bold font.weight: Font.Bold
} }
@@ -628,8 +577,7 @@ DankPopout {
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
z: 10 z: 10
onEntered: { onEntered: {
if (appGrid.hoverUpdatesSelection if (appGrid.hoverUpdatesSelection && !appGrid.keyboardNavigationActive)
&& !appGrid.keyboardNavigationActive)
appGrid.currentIndex = index appGrid.currentIndex = index
} }
onPositionChanged: { onPositionChanged: {
@@ -637,16 +585,10 @@ DankPopout {
} }
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
appGrid.itemClicked( appGrid.itemClicked(index, model)
index, model)
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
var panelPos = mapToItem( var panelPos = mapToItem(contextMenu.parent, mouse.x, mouse.y)
contextMenu.parent, appGrid.itemRightClicked(index, model, panelPos.x, panelPos.y)
mouse.x, mouse.y)
appGrid.itemRightClicked(
index, model,
panelPos.x,
panelPos.y)
} }
} }
} }
@@ -664,6 +606,9 @@ DankPopout {
property var currentApp: null property var currentApp: null
property bool menuVisible: false property bool menuVisible: false
readonly property string appId: (currentApp && currentApp.desktopEntry) ? (currentApp.desktopEntry.id || currentApp.desktopEntry.execString || "") : ""
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
function show(x, y, app) { function show(x, y, app) {
currentApp = app currentApp = app
@@ -681,12 +626,8 @@ DankPopout {
finalY = y - menuHeight - 8 finalY = y - menuHeight - 8
} }
finalX = Math.max( finalX = Math.max(8, Math.min(finalX, appDrawerPopout.popupWidth - menuWidth - 8))
8, Math.min(finalX, finalY = Math.max(8, Math.min(finalY, appDrawerPopout.popupHeight - menuHeight - 8))
appDrawerPopout.popupWidth - menuWidth - 8))
finalY = Math.max(8, Math.min(
finalY,
appDrawerPopout.popupHeight - menuHeight - 8))
contextMenu.x = finalX contextMenu.x = finalX
contextMenu.y = finalY contextMenu.y = finalY
@@ -706,8 +647,7 @@ DankPopout {
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.popupBackground() color: Theme.popupBackground()
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
Theme.outline.b, 0.08)
border.width: 1 border.width: 1
z: 1000 z: 1000
opacity: menuVisible ? 1 : 0 opacity: menuVisible ? 1 : 0
@@ -735,11 +675,7 @@ DankPopout {
width: parent.width width: parent.width
height: 32 height: 32
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: pinMouseArea.containsMouse ? Qt.rgba( color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : "transparent"
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -748,17 +684,7 @@ DankPopout {
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: { name: contextMenu.isPinned ? "keep_off" : "push_pin"
if (!contextMenu.currentApp
|| !contextMenu.currentApp.desktopEntry)
return "push_pin"
var appId = contextMenu.currentApp.desktopEntry.id
|| contextMenu.currentApp.desktopEntry.execString
|| ""
return SessionData.isPinnedApp(
appId) ? "keep_off" : "push_pin"
}
size: Theme.iconSize - 2 size: Theme.iconSize - 2
color: Theme.surfaceText color: Theme.surfaceText
opacity: 0.7 opacity: 0.7
@@ -766,17 +692,7 @@ DankPopout {
} }
StyledText { StyledText {
text: { text: contextMenu.isPinned ? "Unpin from Dock" : "Pin to Dock"
if (!contextMenu.currentApp
|| !contextMenu.currentApp.desktopEntry)
return "Pin to Dock"
var appId = contextMenu.currentApp.desktopEntry.id
|| contextMenu.currentApp.desktopEntry.execString
|| ""
return SessionData.isPinnedApp(
appId) ? "Unpin from Dock" : "Pin to Dock"
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -791,17 +707,15 @@ DankPopout {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (!contextMenu.currentApp if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) {
|| !contextMenu.currentApp.desktopEntry)
return return
}
var appId = contextMenu.currentApp.desktopEntry.id if (contextMenu.isPinned) {
|| contextMenu.currentApp.desktopEntry.execString SessionData.removePinnedApp(contextMenu.appId)
|| "" } else {
if (SessionData.isPinnedApp(appId)) SessionData.addPinnedApp(contextMenu.appId)
SessionData.removePinnedApp(appId) }
else
SessionData.addPinnedApp(appId)
contextMenu.close() contextMenu.close()
} }
} }
@@ -817,8 +731,7 @@ DankPopout {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
Theme.outline.b, 0.2)
} }
} }
@@ -826,11 +739,7 @@ DankPopout {
width: parent.width width: parent.width
height: 32 height: 32
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: launchMouseArea.containsMouse ? Qt.rgba( color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : "transparent"
Row { Row {
anchors.left: parent.left anchors.left: parent.left

View File

@@ -17,20 +17,12 @@ Item {
property bool debounceSearch: true property bool debounceSearch: true
property int debounceInterval: 50 property int debounceInterval: 50
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
property var categories: { readonly property var categories: {
var allCategories = AppSearchService.getAllCategories().filter(cat => { const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science")
return cat !== "Education" const result = ["All"]
&& cat !== "Science" return result.concat(allCategories.filter(cat => cat !== "All"))
})
var result = ["All"]
return result.concat(allCategories.filter(cat => {
return cat !== "All"
}))
} }
property var categoryIcons: categories.map(category => { readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
return AppSearchService.getCategoryIcon(
category)
})
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {} property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
property alias model: filteredModel property alias model: filteredModel
property var _watchApplications: AppSearchService.applications property var _watchApplications: AppSearchService.applications
@@ -43,116 +35,96 @@ Item {
filteredModel.clear() filteredModel.clear()
selectedIndex = 0 selectedIndex = 0
keyboardNavigationActive = false keyboardNavigationActive = false
var apps = []
let apps = []
if (searchQuery.length === 0) { if (searchQuery.length === 0) {
if (selectedCategory === "All") { apps = selectedCategory === "All" ? AppSearchService.getAppsInCategory("All") : AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults)
apps = AppSearchService.getAppsInCategory(
"All") // HACK: Use function call instead of property
} else {
var categoryApps = AppSearchService.getAppsInCategory(
selectedCategory)
apps = categoryApps.slice(0, maxResults)
}
} else { } else {
if (selectedCategory === "All") { if (selectedCategory === "All") {
apps = AppSearchService.searchApplications(searchQuery) apps = AppSearchService.searchApplications(searchQuery)
} else { } else {
var categoryApps = AppSearchService.getAppsInCategory( const categoryApps = AppSearchService.getAppsInCategory(selectedCategory)
selectedCategory)
if (categoryApps.length > 0) { if (categoryApps.length > 0) {
var allSearchResults = AppSearchService.searchApplications( const allSearchResults = AppSearchService.searchApplications(searchQuery)
searchQuery) const categoryNames = new Set(categoryApps.map(app => app.name))
var categoryNames = new Set(categoryApps.map(app => { apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults)
return app.name
}))
apps = allSearchResults.filter(searchApp => {
return categoryNames.has(
searchApp.name)
}).slice(0, maxResults)
} else { } else {
apps = [] apps = []
} }
} }
} }
if (searchQuery.length === 0)
apps = apps.sort(function (a, b) {
var aId = a.id || (a.execString || a.exec || "")
var bId = b.id || (b.execString || b.exec || "")
var aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0
var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0
if (aUsage !== bUsage)
return bUsage - aUsage
return (a.name || "").localeCompare(b.name || "") if (searchQuery.length === 0) {
}) apps = apps.sort((a, b) => {
const aId = a.id || a.execString || a.exec || ""
const bId = b.id || b.execString || b.exec || ""
const aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0
const bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0
if (aUsage !== bUsage) {
return bUsage - aUsage
}
return (a.name || "").localeCompare(b.name || "")
})
}
apps.forEach(app => { apps.forEach(app => {
if (app) if (app) {
filteredModel.append({ filteredModel.append({
"name": app.name || "", "name": app.name || "",
"exec": app.execString || "", "exec": app.execString || "",
"icon": app.icon "icon": app.icon || "application-x-executable",
|| "application-x-executable", "comment": app.comment || "",
"comment": app.comment || "", "categories": app.categories || [],
"categories": app.categories "desktopEntry": app
|| [], })
"desktopEntry": app }
})
}) })
} }
function selectNext() { function selectNext() {
if (filteredModel.count > 0) { if (filteredModel.count === 0) {
keyboardNavigationActive = true return
if (viewMode === "grid") {
var newIndex = Math.min(selectedIndex + gridColumns,
filteredModel.count - 1)
selectedIndex = newIndex
} else {
selectedIndex = Math.min(selectedIndex + 1,
filteredModel.count - 1)
}
} }
keyboardNavigationActive = true
selectedIndex = viewMode === "grid" ? Math.min(selectedIndex + gridColumns, filteredModel.count - 1) : Math.min(selectedIndex + 1, filteredModel.count - 1)
} }
function selectPrevious() { function selectPrevious() {
if (filteredModel.count > 0) { if (filteredModel.count === 0) {
keyboardNavigationActive = true return
if (viewMode === "grid") {
var newIndex = Math.max(selectedIndex - gridColumns, 0)
selectedIndex = newIndex
} else {
selectedIndex = Math.max(selectedIndex - 1, 0)
}
} }
keyboardNavigationActive = true
selectedIndex = viewMode === "grid" ? Math.max(selectedIndex - gridColumns, 0) : Math.max(selectedIndex - 1, 0)
} }
function selectNextInRow() { function selectNextInRow() {
if (filteredModel.count > 0 && viewMode === "grid") { if (filteredModel.count === 0 || viewMode !== "grid") {
keyboardNavigationActive = true return
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1)
} }
keyboardNavigationActive = true
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1)
} }
function selectPreviousInRow() { function selectPreviousInRow() {
if (filteredModel.count > 0 && viewMode === "grid") { if (filteredModel.count === 0 || viewMode !== "grid") {
keyboardNavigationActive = true return
selectedIndex = Math.max(selectedIndex - 1, 0)
} }
keyboardNavigationActive = true
selectedIndex = Math.max(selectedIndex - 1, 0)
} }
function launchSelected() { function launchSelected() {
if (filteredModel.count > 0 && selectedIndex >= 0 if (filteredModel.count === 0 || selectedIndex < 0 || selectedIndex >= filteredModel.count) {
&& selectedIndex < filteredModel.count) { return
var selectedApp = filteredModel.get(selectedIndex)
launchApp(selectedApp)
} }
const selectedApp = filteredModel.get(selectedIndex)
launchApp(selectedApp)
} }
function launchApp(appData) { function launchApp(appData) {
if (!appData) if (!appData) {
return return
}
appData.desktopEntry.execute() appData.desktopEntry.execute()
appLaunched(appData) appLaunched(appData)
AppUsageHistoryData.addAppUsage(appData.desktopEntry) AppUsageHistoryData.addAppUsage(appData.desktopEntry)
@@ -169,10 +141,11 @@ Item {
} }
onSearchQueryChanged: { onSearchQueryChanged: {
if (debounceSearch) if (debounceSearch) {
searchDebounceTimer.restart() searchDebounceTimer.restart()
else } else {
updateFilteredModel() updateFilteredModel()
}
} }
onSelectedCategoryChanged: updateFilteredModel() onSelectedCategoryChanged: updateFilteredModel()
onAppUsageRankingChanged: updateFilteredModel() onAppUsageRankingChanged: updateFilteredModel()

View File

@@ -8,11 +8,25 @@ Item {
property var categories: [] property var categories: []
property string selectedCategory: "All" property string selectedCategory: "All"
property bool compact: false // For different layout styles property bool compact: false
signal categorySelected(string category) signal categorySelected(string category)
height: compact ? 36 : (72 + Theme.spacingS) // Single row vs two rows readonly property int maxCompactItems: 8
readonly property int itemHeight: 36
readonly property color selectedBorderColor: "transparent"
readonly property color unselectedBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
function handleCategoryClick(category) {
selectedCategory = category
categorySelected(category)
}
function getButtonWidth(itemCount, containerWidth) {
return itemCount > 0 ? (containerWidth - (itemCount - 1) * Theme.spacingS) / itemCount : 0
}
height: compact ? itemHeight : (itemHeight * 2 + Theme.spacingS)
Row { Row {
visible: compact visible: compact
@@ -20,22 +34,16 @@ Item {
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: categories.slice(0, Math.min(categories.length, model: categories ? categories.slice(0, Math.min(categories.length || 0, maxCompactItems)) : []
8)) // Limit for space
Rectangle { Rectangle {
height: 36 property int itemCount: Math.min(categories ? categories.length || 0 : 0, maxCompactItems)
width: (parent.width - (Math.min(
categories.length, height: root.itemHeight
8) - 1) * Theme.spacingS) / Math.min( width: root.getButtonWidth(itemCount, parent.width)
categories.length, 8)
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : "transparent" color: selectedCategory === modelData ? Theme.primary : "transparent"
border.color: selectedCategory === modelData ? "transparent" : Qt.rgba( border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
Theme.outline.r,
Theme.outline.g,
Theme.outline.b,
0.3)
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
@@ -50,10 +58,7 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: root.handleCategoryClick(modelData)
selectedCategory = modelData
categorySelected(modelData)
}
} }
} }
} }
@@ -65,27 +70,20 @@ Item {
spacing: Theme.spacingS spacing: Theme.spacingS
Row { Row {
property var firstRowCategories: categories.slice(
0, Math.min(4,
categories.length))
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: parent.firstRowCategories model: categories ? categories.slice(0, Math.min(4, categories.length || 0)) : []
Rectangle { Rectangle {
height: 36 property int itemCount: Math.min(4, categories ? categories.length || 0 : 0)
width: (parent.width - (parent.firstRowCategories.length - 1)
* Theme.spacingS) / parent.firstRowCategories.length height: root.itemHeight
width: root.getButtonWidth(itemCount, parent.width)
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : "transparent" color: selectedCategory === modelData ? Theme.primary : "transparent"
border.color: selectedCategory border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
=== modelData ? "transparent" : Qt.rgba(
Theme.outline.r,
Theme.outline.g,
Theme.outline.b, 0.3)
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
@@ -100,37 +98,28 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: root.handleCategoryClick(modelData)
selectedCategory = modelData
categorySelected(modelData)
}
} }
} }
} }
} }
Row { Row {
property var secondRowCategories: categories.slice(
4, categories.length)
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
visible: secondRowCategories.length > 0 visible: categories && categories.length > 4
Repeater { Repeater {
model: parent.secondRowCategories model: categories && categories.length > 4 ? categories.slice(4) : []
Rectangle { Rectangle {
height: 36 property int itemCount: categories && categories.length > 4 ? categories.length - 4 : 0
width: (parent.width - (parent.secondRowCategories.length - 1)
* Theme.spacingS) / parent.secondRowCategories.length height: root.itemHeight
width: root.getButtonWidth(itemCount, parent.width)
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: selectedCategory === modelData ? Theme.primary : "transparent" color: selectedCategory === modelData ? Theme.primary : "transparent"
border.color: selectedCategory border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
=== modelData ? "transparent" : Qt.rgba(
Theme.outline.r,
Theme.outline.g,
Theme.outline.b, 0.3)
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
@@ -145,10 +134,7 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: root.handleCategoryClick(modelData)
selectedCategory = modelData
categorySelected(modelData)
}
} }
} }
} }

View File

@@ -12,35 +12,31 @@ Column {
property date selectedDate: new Date() property date selectedDate: new Date()
function loadEventsForMonth() { function loadEventsForMonth() {
if (!CalendarService || !CalendarService.khalAvailable) if (!CalendarService || !CalendarService.khalAvailable) {
return return
}
const firstDay = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
const dayOfWeek = firstDay.getDay()
const startDate = new Date(firstDay)
startDate.setDate(startDate.getDate() - dayOfWeek - 7)
const lastDay = new Date(displayDate.getFullYear(), displayDate.getMonth() + 1, 0)
const endDate = new Date(lastDay)
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay()) + 7)
let firstDay = new Date(displayDate.getFullYear(),
displayDate.getMonth(), 1)
let dayOfWeek = firstDay.getDay()
let startDate = new Date(firstDay)
startDate.setDate(startDate.getDate(
) - dayOfWeek - 7) // Extra week padding
let lastDay = new Date(displayDate.getFullYear(),
displayDate.getMonth() + 1, 0)
let endDate = new Date(lastDay)
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay(
)) + 7) // Extra week padding
CalendarService.loadEvents(startDate, endDate) CalendarService.loadEvents(startDate, endDate)
} }
spacing: Theme.spacingM spacing: Theme.spacingM
onDisplayDateChanged: { onDisplayDateChanged: loadEventsForMonth()
loadEventsForMonth() Component.onCompleted: loadEventsForMonth()
}
Component.onCompleted: {
loadEventsForMonth()
}
Connections { Connections {
function onKhalAvailableChanged() { function onKhalAvailableChanged() {
if (CalendarService && CalendarService.khalAvailable) if (CalendarService && CalendarService.khalAvailable) {
loadEventsForMonth() loadEventsForMonth()
}
} }
target: CalendarService target: CalendarService
@@ -55,10 +51,7 @@ Column {
width: 40 width: 40
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: prevMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, color: prevMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Theme.primary.g,
Theme.primary.b,
0.12) : "transparent"
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -96,10 +89,7 @@ Column {
width: 40 width: 40
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, color: nextMonthArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Theme.primary.g,
Theme.primary.b,
0.12) : "transparent"
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -129,10 +119,10 @@ Column {
Repeater { Repeater {
model: { model: {
var days = [] const days = []
var locale = Qt.locale() const locale = Qt.locale()
for (var i = 0; i < 7; i++) { for (var i = 0; i < 7; i++) {
var date = new Date(2024, 0, 7 + i) const date = new Date(2024, 0, 7 + i)
days.push(locale.dayName(i, Locale.ShortFormat)) days.push(locale.dayName(i, Locale.ShortFormat))
} }
return days return days
@@ -147,8 +137,7 @@ Column {
anchors.centerIn: parent anchors.centerIn: parent
text: modelData text: modelData
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
Theme.surfaceText.b, 0.6)
font.weight: Font.Medium font.weight: Font.Medium
} }
} }
@@ -156,10 +145,9 @@ Column {
} }
Grid { Grid {
property date firstDay: { readonly property date firstDay: {
let date = new Date(displayDate.getFullYear(), const date = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
displayDate.getMonth(), 1) const dayOfWeek = date.getDay()
let dayOfWeek = date.getDay()
date.setDate(date.getDate() - dayOfWeek) date.setDate(date.getDate() - dayOfWeek)
return date return date
} }
@@ -173,17 +161,14 @@ Column {
model: 42 model: 42
Rectangle { Rectangle {
property date dayDate: { readonly property date dayDate: {
let date = new Date(parent.firstDay) const date = new Date(parent.firstDay)
date.setDate(date.getDate() + index) date.setDate(date.getDate() + index)
return date return date
} }
property bool isCurrentMonth: dayDate.getMonth( readonly property bool isCurrentMonth: dayDate.getMonth() === displayDate.getMonth()
) === displayDate.getMonth() readonly property bool isToday: dayDate.toDateString() === new Date().toDateString()
property bool isToday: dayDate.toDateString( readonly property bool isSelected: dayDate.toDateString() === selectedDate.toDateString()
) === new Date().toDateString()
property bool isSelected: dayDate.toDateString(
) === selectedDate.toDateString()
width: parent.width / 7 width: parent.width / 7
height: parent.height / 6 height: parent.height / 6
@@ -194,11 +179,7 @@ Column {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - 4 width: parent.width - 4
height: parent.height - 4 height: parent.height - 4
color: isSelected ? Theme.primary : isToday ? Qt.rgba( color: isSelected ? Theme.primary : isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
radius: Theme.cornerRadius radius: Theme.cornerRadius
clip: true clip: true
@@ -207,8 +188,7 @@ Column {
text: dayDate.getDate() text: dayDate.getDate()
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: isSelected ? Theme.surface : isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4) color: isSelected ? Theme.surface : isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
font.weight: isToday font.weight: isToday || isSelected ? Font.Medium : Font.Normal
|| isSelected ? Font.Medium : Font.Normal
} }
Rectangle { Rectangle {
@@ -216,17 +196,8 @@ Column {
anchors.fill: parent anchors.fill: parent
radius: parent.radius radius: parent.radius
visible: CalendarService visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)
&& CalendarService.khalAvailable opacity: isSelected ? 0.9 : isToday ? 0.8 : 0.6
&& CalendarService.hasEventsForDate(dayDate)
opacity: {
if (isSelected)
return 0.9
else if (isToday)
return 0.8
else
return 0.6
}
gradient: Gradient { gradient: Gradient {
GradientStop { GradientStop {
@@ -236,26 +207,12 @@ Column {
GradientStop { GradientStop {
position: 0.9 position: 0.9
color: { color: isSelected ? Qt.lighter(Theme.primary, 1.3) : Theme.primary
if (isSelected)
return Qt.lighter(Theme.primary, 1.3)
else if (isToday)
return Theme.primary
else
return Theme.primary
}
} }
GradientStop { GradientStop {
position: 1 position: 1
color: { color: isSelected ? Qt.lighter(Theme.primary, 1.3) : Theme.primary
if (isSelected)
return Qt.lighter(Theme.primary, 1.3)
else if (isToday)
return Theme.primary
else
return Theme.primary
}
} }
} }
@@ -274,9 +231,7 @@ Column {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: selectedDate = dayDate
selectedDate = dayDate
}
} }
} }
} }

View File

@@ -36,9 +36,7 @@ PanelWindow {
closeTimer.stop() closeTimer.stop()
shouldBeVisible = true shouldBeVisible = true
visible = true visible = true
Qt.callLater(() => { Qt.callLater(() => calendarGrid.loadEventsForMonth())
calendarGrid.loadEventsForMonth()
})
} else { } else {
shouldBeVisible = false shouldBeVisible = false
closeTimer.restart() closeTimer.restart()
@@ -55,8 +53,9 @@ PanelWindow {
} }
} }
onVisibleChanged: { onVisibleChanged: {
if (visible && calendarGrid) if (visible && calendarGrid) {
calendarGrid.loadEventsForMonth() calendarGrid.loadEventsForMonth()
}
} }
implicitWidth: 480 implicitWidth: 480
implicitHeight: 600 implicitHeight: 600
@@ -75,32 +74,27 @@ PanelWindow {
Rectangle { Rectangle {
id: mainContainer id: mainContainer
readonly property real targetWidth: Math.min( readonly property real targetWidth: Math.min((root.screen ? root.screen.width : Screen.width) * 0.9, 600)
(root.screen ? root.screen.width : Screen.width)
* 0.9, 600)
function calculateWidth() { function calculateWidth() {
let baseWidth = 320 const baseWidth = 320
if (leftWidgets.hasAnyWidgets) if (leftWidgets.hasAnyWidgets) {
return Math.min(parent.width * 0.9, 600) return Math.min(parent.width * 0.9, 600)
}
return Math.min(parent.width * 0.7, 400) return Math.min(parent.width * 0.7, 400)
} }
function calculateHeight() { function calculateHeight() {
let contentHeight = Theme.spacingM * 2 let contentHeight = Theme.spacingM * 2
// margins
let widgetHeight = 160 let widgetHeight = 160
widgetHeight += 140 + Theme.spacingM widgetHeight += 140 + Theme.spacingM
let calendarHeight = 300 const calendarHeight = 300
let mainRowHeight = Math.max(widgetHeight, calendarHeight) const mainRowHeight = Math.max(widgetHeight, calendarHeight)
contentHeight += mainRowHeight + Theme.spacingM contentHeight += mainRowHeight + Theme.spacingM
if (CalendarService && CalendarService.khalAvailable) { if (CalendarService && CalendarService.khalAvailable) {
let hasEvents = events.selectedDateEvents const hasEvents = events.selectedDateEvents && events.selectedDateEvents.length > 0
&& events.selectedDateEvents.length > 0 const eventsHeight = hasEvents ? Math.min(300, 80 + events.selectedDateEvents.length * 60) : 120
let eventsHeight = hasEvents ? Math.min(
300,
80 + events.selectedDateEvents.length * 60) : 120
contentHeight += eventsHeight contentHeight += eventsHeight
} else { } else {
contentHeight -= Theme.spacingM contentHeight -= Theme.spacingM
@@ -109,26 +103,22 @@ PanelWindow {
} }
readonly property real calculatedX: { readonly property real calculatedX: {
var screenWidth = root.screen ? root.screen.width : Screen.width const screenWidth = root.screen ? root.screen.width : Screen.width
if (root.triggerSection === "center") { if (root.triggerSection === "center") {
return (screenWidth - targetWidth) / 2 return (screenWidth - targetWidth) / 2
} }
var centerX = root.triggerX + (root.triggerWidth / 2) - (targetWidth / 2) const centerX = root.triggerX + (root.triggerWidth / 2) - (targetWidth / 2)
if (centerX >= Theme.spacingM if (centerX >= Theme.spacingM && centerX + targetWidth <= screenWidth - Theme.spacingM) {
&& centerX + targetWidth <= screenWidth - Theme.spacingM) {
return centerX return centerX
} }
if (centerX < Theme.spacingM) { if (centerX < Theme.spacingM) {
return Theme.spacingM return Theme.spacingM
} }
if (centerX + targetWidth > screenWidth - Theme.spacingM) { if (centerX + targetWidth > screenWidth - Theme.spacingM) {
return screenWidth - targetWidth - Theme.spacingM return screenWidth - targetWidth - Theme.spacingM
} }
return centerX return centerX
} }
@@ -136,8 +126,7 @@ PanelWindow {
height: calculateHeight() height: calculateHeight()
color: Theme.surfaceContainer color: Theme.surfaceContainer
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
Theme.outline.b, 0.08)
border.width: 1 border.width: 1
layer.enabled: true layer.enabled: true
opacity: shouldBeVisible ? 1 : 0 opacity: shouldBeVisible ? 1 : 0
@@ -145,21 +134,24 @@ PanelWindow {
x: calculatedX x: calculatedX
y: root.triggerY y: root.triggerY
onOpacityChanged: { onOpacityChanged: {
if (opacity === 1) if (opacity === 1) {
Qt.callLater(() => { Qt.callLater(() => {
height = calculateHeight() height = calculateHeight()
}) })
}
} }
Connections { Connections {
function onEventsByDateChanged() { function onEventsByDateChanged() {
if (mainContainer.opacity === 1) if (mainContainer.opacity === 1) {
mainContainer.height = mainContainer.calculateHeight() mainContainer.height = mainContainer.calculateHeight()
}
} }
function onKhalAvailableChanged() { function onKhalAvailableChanged() {
if (mainContainer.opacity === 1) if (mainContainer.opacity === 1) {
mainContainer.height = mainContainer.calculateHeight() mainContainer.height = mainContainer.calculateHeight()
}
} }
target: CalendarService target: CalendarService
@@ -168,8 +160,9 @@ PanelWindow {
Connections { Connections {
function onSelectedDateEventsChanged() { function onSelectedDateEventsChanged() {
if (mainContainer.opacity === 1) if (mainContainer.opacity === 1) {
mainContainer.height = mainContainer.calculateHeight() mainContainer.height = mainContainer.calculateHeight()
}
} }
target: events target: events
@@ -178,8 +171,7 @@ PanelWindow {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
Theme.surfaceTint.b, 0.04)
radius: parent.radius radius: parent.radius
SequentialAnimation on opacity { SequentialAnimation on opacity {
@@ -219,10 +211,8 @@ PanelWindow {
width: parent.width width: parent.width
height: { height: {
let widgetHeight = 160 let widgetHeight = 160
// Media widget widgetHeight += 140 + Theme.spacingM
widgetHeight += 140 + Theme.spacingM // Weather/SystemInfo widget with spacing const calendarHeight = 300
let calendarHeight = 300
// Calendar
return Math.max(widgetHeight, calendarHeight) return Math.max(widgetHeight, calendarHeight)
} }
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -232,8 +222,7 @@ PanelWindow {
property bool hasAnyWidgets: true property bool hasAnyWidgets: true
width: hasAnyWidgets ? parent.width width: hasAnyWidgets ? parent.width * 0.42 : 0
* 0.42 : 0 // Slightly narrower for better proportions
height: childrenRect.height height: childrenRect.height
spacing: Theme.spacingM spacing: Theme.spacingM
visible: hasAnyWidgets visible: hasAnyWidgets
@@ -258,15 +247,11 @@ PanelWindow {
} }
Rectangle { Rectangle {
width: leftWidgets.hasAnyWidgets ? parent.width - leftWidgets.width width: leftWidgets.hasAnyWidgets ? parent.width - leftWidgets.width - Theme.spacingM : parent.width
- Theme.spacingM : parent.width
height: parent.height height: parent.height
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2)
Theme.surfaceVariant.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
Theme.surfaceVariant.b, 0.2)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1 border.width: 1
CalendarGrid { CalendarGrid {
@@ -317,10 +302,10 @@ PanelWindow {
z: -1 z: -1
enabled: shouldBeVisible enabled: shouldBeVisible
onClicked: function (mouse) { onClicked: function (mouse) {
var localPos = mapToItem(mainContainer, mouse.x, mouse.y) const localPos = mapToItem(mainContainer, mouse.x, mouse.y)
if (localPos.x < 0 || localPos.x > mainContainer.width if (localPos.x < 0 || localPos.x > mainContainer.width || localPos.y < 0 || localPos.y > mainContainer.height) {
|| localPos.y < 0 || localPos.y > mainContainer.height)
calendarVisible = false calendarVisible = false
}
} }
} }
} }

View File

@@ -14,7 +14,7 @@ Rectangle {
function updateSelectedDateEvents() { function updateSelectedDateEvents() {
if (CalendarService && CalendarService.khalAvailable) { if (CalendarService && CalendarService.khalAvailable) {
let events = CalendarService.getEventsForDate(selectedDate) const events = CalendarService.getEventsForDate(selectedDate)
selectedDateEvents = events selectedDateEvents = events
} else { } else {
selectedDateEvents = [] selectedDateEvents = []
@@ -25,23 +25,15 @@ Rectangle {
eventsList.model = selectedDateEvents eventsList.model = selectedDateEvents
} }
width: parent.width width: parent.width
height: shouldShow ? (hasEvents ? Math.min( height: shouldShow ? (hasEvents ? Math.min(300, 80 + selectedDateEvents.length * 60) : 120) : 0
300,
80 + selectedDateEvents.length * 60) : 120) : 0
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12)
Theme.surfaceVariant.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1 border.width: 1
visible: shouldShow visible: shouldShow
layer.enabled: true layer.enabled: true
Component.onCompleted: { Component.onCompleted: updateSelectedDateEvents()
updateSelectedDateEvents() onSelectedDateChanged: updateSelectedDateEvents()
}
onSelectedDateChanged: {
updateSelectedDateEvents()
}
Connections { Connections {
function onEventsByDateChanged() { function onEventsByDateChanged() {
@@ -73,11 +65,7 @@ Rectangle {
} }
StyledText { StyledText {
text: hasEvents ? (Qt.formatDate(selectedDate, "MMM d") + " • " text: hasEvents ? (Qt.formatDate(selectedDate, "MMM d") + " • " + (selectedDateEvents.length === 1 ? "1 event" : selectedDateEvents.length + " events")) : Qt.formatDate(selectedDate, "MMM d")
+ (selectedDateEvents.length
=== 1 ? "1 event" : selectedDateEvents.length
+ " events")) : Qt.formatDate(
selectedDate, "MMM d")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -93,16 +81,14 @@ Rectangle {
DankIcon { DankIcon {
name: "event_busy" name: "event_busy"
size: Theme.iconSize + 8 size: Theme.iconSize + 8
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
Theme.surfaceText.b, 0.3)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: "No events" text: "No events"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
Theme.surfaceText.b, 0.5)
font.weight: Font.Normal font.weight: Font.Normal
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
@@ -123,7 +109,6 @@ Rectangle {
spacing: Theme.spacingS spacing: Theme.spacingS
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
interactive: true interactive: true
flickDeceleration: 1500 flickDeceleration: 1500
maximumFlickVelocity: 2000 maximumFlickVelocity: 2000
@@ -131,26 +116,20 @@ Rectangle {
pressDelay: 0 pressDelay: 0
flickableDirection: Flickable.VerticalFlick flickableDirection: Flickable.VerticalFlick
// Custom wheel handler for Qt 6.9+ responsive mouse wheel scrolling
WheelHandler { WheelHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
property real momentum: 0 property real momentum: 0
onWheel: event => { onWheel: event => {
if (event.pixelDelta.y !== 0) { if (event.pixelDelta.y !== 0) {
// Touchpad with pixel delta
momentum = event.pixelDelta.y * 1.8 momentum = event.pixelDelta.y * 1.8
} else { } else {
// Mouse wheel with angle delta momentum = (event.angleDelta.y / 120) * (60 * 2.5)
momentum = (event.angleDelta.y / 120)
* (60 * 2.5) // ~2.5 items per wheel step
} }
let newY = parent.contentY - momentum let newY = parent.contentY - momentum
newY = Math.max( newY = Math.max(0, Math.min(parent.contentHeight - parent.height, newY))
0, Math.min(parent.contentHeight - parent.height,
newY))
parent.contentY = newY parent.contentY = newY
momentum *= 0.92 // Decay for smooth momentum momentum *= 0.92
event.accepted = true event.accepted = true
} }
} }
@@ -167,22 +146,19 @@ Rectangle {
height: eventContent.implicitHeight + Theme.spacingM height: eventContent.implicitHeight + Theme.spacingM
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (modelData.url && eventMouseArea.containsMouse) if (modelData.url && eventMouseArea.containsMouse) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
Theme.primary.b, 0.12) } else if (eventMouseArea.containsMouse) {
else if (eventMouseArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06)
return Qt.rgba(Theme.primary.r, Theme.primary.g, }
Theme.primary.b, 0.06) return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.06)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.06)
} }
border.color: { border.color: {
if (modelData.url && eventMouseArea.containsMouse) if (modelData.url && eventMouseArea.containsMouse) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
Theme.primary.b, 0.3) } else if (eventMouseArea.containsMouse) {
else if (eventMouseArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15)
return Qt.rgba(Theme.primary.r, Theme.primary.g, }
Theme.primary.b, 0.15)
return "transparent" return "transparent"
} }
border.width: 1 border.width: 1
@@ -233,9 +209,7 @@ Rectangle {
DankIcon { DankIcon {
name: "schedule" name: "schedule"
size: Theme.fontSizeSmall size: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
Theme.surfaceText.g,
Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -244,23 +218,16 @@ Rectangle {
if (modelData.allDay) { if (modelData.allDay) {
return "All day" return "All day"
} else { } else {
let timeFormat = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP" const timeFormat = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP"
let startTime = Qt.formatTime( const startTime = Qt.formatTime(modelData.start, timeFormat)
modelData.start, timeFormat) if (modelData.start.toDateString() !== modelData.end.toDateString() || modelData.start.getTime() !== modelData.end.getTime()) {
if (modelData.start.toDateString( return startTime + " " + Qt.formatTime(modelData.end, timeFormat)
) !== modelData.end.toDateString( }
) || modelData.start.getTime(
) !== modelData.end.getTime())
return startTime + " " + Qt.formatTime(
modelData.end, timeFormat)
return startTime return startTime
} }
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
Theme.surfaceText.g,
Theme.surfaceText.b, 0.7)
font.weight: Font.Normal font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -277,18 +244,14 @@ Rectangle {
DankIcon { DankIcon {
name: "location_on" name: "location_on"
size: Theme.fontSizeSmall size: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
Theme.surfaceText.g,
Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: modelData.location text: modelData.location
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
Theme.surfaceText.g,
Theme.surfaceText.b, 0.7)
elide: Text.ElideRight elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1 maximumLineCount: 1
@@ -307,8 +270,9 @@ Rectangle {
enabled: modelData.url !== "" enabled: modelData.url !== ""
onClicked: { onClicked: {
if (modelData.url && modelData.url !== "") { if (modelData.url && modelData.url !== "") {
if (Qt.openUrlExternally(modelData.url) === false) if (Qt.openUrlExternally(modelData.url) === false) {
console.warn("Failed to open URL: " + modelData.url) console.warn("Failed to open URL: " + modelData.url)
}
} }
} }
} }

View File

@@ -15,30 +15,27 @@ Rectangle {
property string lastValidArtist: "" property string lastValidArtist: ""
property string lastValidAlbum: "" property string lastValidAlbum: ""
property string lastValidArtUrl: "" property string lastValidArtUrl: ""
property real currentPosition: activePlayer property real currentPosition: activePlayer && activePlayer.positionSupported ? activePlayer.position : 0
&& activePlayer.positionSupported ? activePlayer.position : 0
property real displayPosition: currentPosition property real displayPosition: currentPosition
function ratio() { readonly property real ratio: {
if (!activePlayer || activePlayer.length <= 0) { if (!activePlayer || activePlayer.length <= 0) {
return 0 return 0
} }
let calculatedRatio = displayPosition / activePlayer.length const calculatedRatio = displayPosition / activePlayer.length
return Math.max(0, Math.min(1, calculatedRatio)) return Math.max(0, Math.min(1, calculatedRatio))
} }
onActivePlayerChanged: { onActivePlayerChanged: {
if (activePlayer && activePlayer.positionSupported) { if (activePlayer && activePlayer.positionSupported) {
currentPosition = Qt.binding(() => activePlayer.position) currentPosition = Qt.binding(() => activePlayer?.position)
} }
} }
Timer { Timer {
id: positionTimer id: positionTimer
interval: 300 interval: 300
running: activePlayer running: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing && !progressMouseArea.isSeeking
&& activePlayer.playbackState === MprisPlaybackState.Playing
&& !progressMouseArea.isSeeking
repeat: true repeat: true
onTriggered: activePlayer && activePlayer.positionSupported && activePlayer.positionChanged() onTriggered: activePlayer && activePlayer.positionSupported && activePlayer.positionChanged()
} }
@@ -46,14 +43,11 @@ Rectangle {
width: parent.width width: parent.width
height: parent.height height: parent.height
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
Theme.surfaceContainer.b, 0.4) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1 border.width: 1
layer.enabled: true layer.enabled: true
Timer { Timer {
id: cleanupTimer id: cleanupTimer
@@ -69,7 +63,6 @@ Rectangle {
} }
} }
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
@@ -77,23 +70,19 @@ Rectangle {
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
visible: (!activePlayer && !lastValidTitle) visible: (!activePlayer && !lastValidTitle) || (activePlayer && activePlayer.trackTitle === "" && lastValidTitle === "")
|| (activePlayer && activePlayer.trackTitle === ""
&& lastValidTitle === "")
DankIcon { DankIcon {
name: "music_note" name: "music_note"
size: Theme.iconSize + 8 size: Theme.iconSize + 8
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: "No Media Playing" text: "No Media Playing"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
@@ -101,8 +90,7 @@ Rectangle {
Column { Column {
anchors.fill: parent anchors.fill: parent
spacing: Theme.spacingS spacing: Theme.spacingS
visible: activePlayer && activePlayer.trackTitle !== "" visible: (activePlayer && activePlayer.trackTitle !== "") || lastValidTitle !== ""
|| lastValidTitle !== ""
Row { Row {
width: parent.width width: parent.width
@@ -113,9 +101,7 @@ Rectangle {
width: 60 width: 60
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
Item { Item {
anchors.fill: parent anchors.fill: parent
@@ -125,12 +111,11 @@ Rectangle {
id: albumArt id: albumArt
anchors.fill: parent anchors.fill: parent
source: activePlayer && activePlayer.trackArtUrl source: (activePlayer && activePlayer.trackArtUrl) || lastValidArtUrl || ""
|| lastValidArtUrl || ""
onSourceChanged: { onSourceChanged: {
if (activePlayer && activePlayer.trackArtUrl if (activePlayer && activePlayer.trackArtUrl && albumArt.status !== Image.Error) {
&& albumArt.status !== Image.Error)
lastValidArtUrl = activePlayer.trackArtUrl lastValidArtUrl = activePlayer.trackArtUrl
}
} }
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
smooth: true smooth: true
@@ -168,11 +153,11 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: activePlayer && activePlayer.trackTitle text: (activePlayer && activePlayer.trackTitle) || lastValidTitle || "Unknown Track"
|| lastValidTitle || "Unknown Track"
onTextChanged: { onTextChanged: {
if (activePlayer && activePlayer.trackTitle) if (activePlayer && activePlayer.trackTitle) {
lastValidTitle = activePlayer.trackTitle lastValidTitle = activePlayer.trackTitle
}
} }
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold font.weight: Font.Bold
@@ -184,16 +169,14 @@ Rectangle {
} }
StyledText { StyledText {
text: activePlayer && activePlayer.trackArtist text: (activePlayer && activePlayer.trackArtist) || lastValidArtist || "Unknown Artist"
|| lastValidArtist || "Unknown Artist"
onTextChanged: { onTextChanged: {
if (activePlayer && activePlayer.trackArtist) if (activePlayer && activePlayer.trackArtist) {
lastValidArtist = activePlayer.trackArtist lastValidArtist = activePlayer.trackArtist
}
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
Theme.surfaceText.g,
Theme.surfaceText.b, 0.8)
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
@@ -201,16 +184,14 @@ Rectangle {
} }
StyledText { StyledText {
text: activePlayer && activePlayer.trackAlbum text: (activePlayer && activePlayer.trackAlbum) || lastValidAlbum || ""
|| lastValidAlbum || ""
onTextChanged: { onTextChanged: {
if (activePlayer && activePlayer.trackAlbum) if (activePlayer && activePlayer.trackAlbum) {
lastValidAlbum = activePlayer.trackAlbum lastValidAlbum = activePlayer.trackAlbum
}
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
Theme.surfaceText.g,
Theme.surfaceText.b, 0.6)
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
@@ -232,9 +213,7 @@ Rectangle {
width: parent.width width: parent.width
height: 6 height: 6
radius: 3 radius: 3
color: Qt.rgba(Theme.surfaceVariant.r, color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
visible: activePlayer !== null visible: activePlayer !== null
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -244,8 +223,7 @@ Rectangle {
height: parent.height height: parent.height
radius: parent.radius radius: parent.radius
color: Theme.primary color: Theme.primary
width: Math.max(0, Math.min(parent.width, width: Math.max(0, Math.min(parent.width, parent.width * ratio))
parent.width * ratio()))
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {
@@ -263,12 +241,10 @@ Rectangle {
color: Theme.primary color: Theme.primary
border.color: Qt.lighter(Theme.primary, 1.3) border.color: Qt.lighter(Theme.primary, 1.3)
border.width: 1 border.width: 1
x: Math.max(0, Math.min(parent.width - width, x: Math.max(0, Math.min(parent.width - width, progressFill.width - width / 2))
progressFill.width - width / 2))
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: activePlayer && activePlayer.length > 0 visible: activePlayer && activePlayer.length > 0
scale: progressMouseArea.containsMouse scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1
|| progressMouseArea.pressed ? 1.2 : 1
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
@@ -290,66 +266,51 @@ Rectangle {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (progressMouseArea.pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) { if (progressMouseArea.pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) {
let clampedPosition = Math.min(progressMouseArea.pendingSeekPosition, activePlayer.length * 0.99) const clampedPosition = Math.min(progressMouseArea.pendingSeekPosition, activePlayer.length * 0.99)
activePlayer.position = clampedPosition activePlayer.position = clampedPosition
progressMouseArea.pendingSeekPosition = -1 progressMouseArea.pendingSeekPosition = -1
} }
} }
} }
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: activePlayer && activePlayer.length > 0 enabled: activePlayer && activePlayer.length > 0 && activePlayer.canSeek
&& activePlayer.canSeek
preventStealing: true preventStealing: true
onPressed: function (mouse) { onPressed: mouse => {
isSeeking = true isSeeking = true
if (activePlayer && activePlayer.length > 0 if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
&& activePlayer.canSeek) { const ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
let ratio = Math.max( pendingSeekPosition = ratio * activePlayer.length
0, Math.min( displayPosition = pendingSeekPosition
1, seekDebounceTimer.restart()
mouse.x / progressBarBackground.width)) }
pendingSeekPosition = ratio * activePlayer.length }
displayPosition = pendingSeekPosition
seekDebounceTimer.restart()
}
}
onReleased: { onReleased: {
isSeeking = false isSeeking = false
seekDebounceTimer.stop() seekDebounceTimer.stop()
if (pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) { if (pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) {
let clampedPosition = Math.min(pendingSeekPosition, activePlayer.length * 0.99) const clampedPosition = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
activePlayer.position = clampedPosition activePlayer.position = clampedPosition
pendingSeekPosition = -1 pendingSeekPosition = -1
} }
displayPosition = Qt.binding(() => currentPosition) displayPosition = Qt.binding(() => currentPosition)
} }
onPositionChanged: function (mouse) { onPositionChanged: mouse => {
if (pressed && isSeeking && activePlayer if (pressed && isSeeking && activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
&& activePlayer.length > 0 const ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
&& activePlayer.canSeek) { pendingSeekPosition = ratio * activePlayer.length
let ratio = Math.max( displayPosition = pendingSeekPosition
0, Math.min( seekDebounceTimer.restart()
1, }
mouse.x / progressBarBackground.width)) }
pendingSeekPosition = ratio * activePlayer.length onClicked: mouse => {
displayPosition = pendingSeekPosition if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
seekDebounceTimer.restart() const ratio = Math.max(0, Math.min(1, mouse.x / progressBarBackground.width))
} activePlayer.position = ratio * activePlayer.length
} }
onClicked: function (mouse) { }
if (activePlayer && activePlayer.length > 0
&& activePlayer.canSeek) {
let ratio = Math.max(
0, Math.min(
1,
mouse.x / progressBarBackground.width))
activePlayer.position = ratio * activePlayer.length
}
}
} }
MouseArea { MouseArea {
@@ -362,26 +323,20 @@ Rectangle {
enabled: progressMouseArea.isSeeking enabled: progressMouseArea.isSeeking
visible: false visible: false
preventStealing: true preventStealing: true
onPositionChanged: function (mouse) { onPositionChanged: mouse => {
if (progressMouseArea.isSeeking && activePlayer if (progressMouseArea.isSeeking && activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
&& activePlayer.length > 0 const globalPos = mapToItem(progressBarBackground, mouse.x, mouse.y)
&& activePlayer.canSeek) { const ratio = Math.max(0, Math.min(1, globalPos.x / progressBarBackground.width))
let globalPos = mapToItem(progressBarBackground, progressMouseArea.pendingSeekPosition = ratio * activePlayer.length
mouse.x, mouse.y) displayPosition = progressMouseArea.pendingSeekPosition
let ratio = Math.max( seekDebounceTimer.restart()
0, Math.min( }
1, }
globalPos.x / progressBarBackground.width))
progressMouseArea.pendingSeekPosition = ratio * activePlayer.length
displayPosition = progressMouseArea.pendingSeekPosition
seekDebounceTimer.restart()
}
}
onReleased: { onReleased: {
progressMouseArea.isSeeking = false progressMouseArea.isSeeking = false
seekDebounceTimer.stop() seekDebounceTimer.stop()
if (progressMouseArea.pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) { if (progressMouseArea.pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) {
let clampedPosition = Math.min(progressMouseArea.pendingSeekPosition, activePlayer.length * 0.99) const clampedPosition = Math.min(progressMouseArea.pendingSeekPosition, activePlayer.length * 0.99)
activePlayer.position = clampedPosition activePlayer.position = clampedPosition
progressMouseArea.pendingSeekPosition = -1 progressMouseArea.pendingSeekPosition = -1
} }
@@ -404,11 +359,7 @@ Rectangle {
width: 28 width: 28
height: 28 height: 28
radius: 14 radius: 14
color: prevBtnArea.containsMouse ? Qt.rgba( color: prevBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.12) : "transparent"
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -424,11 +375,11 @@ Rectangle {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (!activePlayer) if (!activePlayer) {
return return
}
if (activePlayer.position > 8 if (activePlayer.position > 8 && activePlayer.canSeek) {
&& activePlayer.canSeek) {
activePlayer.position = 0 activePlayer.position = 0
} else { } else {
activePlayer.previous() activePlayer.previous()
@@ -445,8 +396,7 @@ Rectangle {
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: activePlayer && activePlayer.playbackState name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
=== MprisPlaybackState.Playing ? "pause" : "play_arrow"
size: 20 size: 20
color: Theme.background color: Theme.background
} }
@@ -455,8 +405,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: activePlayer onClicked: activePlayer && activePlayer.togglePlaying()
&& activePlayer.togglePlaying()
} }
} }
@@ -464,11 +413,7 @@ Rectangle {
width: 28 width: 28
height: 28 height: 28
radius: 14 radius: 14
color: nextBtnArea.containsMouse ? Qt.rgba( color: nextBtnArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12) : "transparent"
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.12) : "transparent"
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent

View File

@@ -7,14 +7,15 @@ Rectangle {
id: root id: root
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
Theme.surfaceContainer.b, 0.4) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1 border.width: 1
Ref { Component.onCompleted: {
service: DgopService DgopService.addRef(["system", "hardware"])
}
Component.onDestruction: {
DgopService.removeRef(["system", "hardware"])
} }
Column { Column {
@@ -48,8 +49,7 @@ Rectangle {
StyledText { StyledText {
text: DgopService.distribution + " • " + DgopService.architecture text: DgopService.distribution + " • " + DgopService.architecture
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
Theme.surfaceText.b, 0.7)
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
} }
@@ -59,8 +59,7 @@ Rectangle {
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
Theme.outline.b, 0.1)
} }
Column { Column {
@@ -86,8 +85,7 @@ Rectangle {
StyledText { StyledText {
text: DgopService.processCount + " proc, " + DgopService.threadCount + " threads" text: DgopService.processCount + " proc, " + DgopService.threadCount + " threads"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
Theme.surfaceText.b, 0.8)
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
} }
@@ -95,43 +93,36 @@ Rectangle {
} }
function formatUptime(uptime) { function formatUptime(uptime) {
if (!uptime) if (!uptime) {
return "0m" return "0m"
}
// Parse the uptime string - handle formats like "1 week, 4 days, 3:45" or "4 days, 3:45" or "3:45" const uptimeStr = uptime.toString().trim()
var uptimeStr = uptime.toString().trim() const weekMatch = uptimeStr.match(/(\d+)\s+weeks?/)
const dayMatch = uptimeStr.match(/(\d+)\s+days?/)
// Check for weeks and days - need to add them together
var weekMatch = uptimeStr.match(/(\d+)\s+weeks?/)
var dayMatch = uptimeStr.match(/(\d+)\s+days?/)
if (weekMatch) { if (weekMatch) {
var weeks = parseInt(weekMatch[1]) const weeks = parseInt(weekMatch[1])
var totalDays = weeks * 7 let totalDays = weeks * 7
if (dayMatch) { if (dayMatch) {
var days = parseInt(dayMatch[1]) const days = parseInt(dayMatch[1])
totalDays += days totalDays += days
} }
return totalDays + "d" return totalDays + "d"
} else if (dayMatch) { }
var days = parseInt(dayMatch[1])
if (dayMatch) {
const days = parseInt(dayMatch[1])
return days + "d" return days + "d"
} }
// If it's just hours:minutes, show the largest unit const timeMatch = uptimeStr.match(/(\d+):(\d+)/)
var timeMatch = uptimeStr.match(/(\d+):(\d+)/)
if (timeMatch) { if (timeMatch) {
var hours = parseInt(timeMatch[1]) const hours = parseInt(timeMatch[1])
var minutes = parseInt(timeMatch[2]) const minutes = parseInt(timeMatch[2])
if (hours > 0) { return hours > 0 ? hours + "h" : minutes + "m"
return hours + "h"
} else {
return minutes + "m"
}
} }
// Fallback - return as is but truncated return uptimeStr.length > 8 ? uptimeStr.substring(0, 8) + "…" : uptimeStr
return uptimeStr.length > 8 ? uptimeStr.substring(0,
8) + "…" : uptimeStr
} }
} }

View File

@@ -11,10 +11,8 @@ Rectangle {
width: parent.width width: parent.width
height: parent.height height: parent.height
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.4)
Theme.surfaceContainer.b, 0.4) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: 1 border.width: 1
layer.enabled: true layer.enabled: true
@@ -25,22 +23,19 @@ Rectangle {
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
visible: !WeatherService.weather.available visible: !WeatherService.weather.available || WeatherService.weather.temp === 0
|| WeatherService.weather.temp === 0
DankIcon { DankIcon {
name: "cloud_off" name: "cloud_off"
size: Theme.iconSize + 8 size: Theme.iconSize + 8
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: "No Weather Data" text: "No Weather Data"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
@@ -49,8 +44,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingS spacing: Theme.spacingS
visible: WeatherService.weather.available visible: WeatherService.weather.available && WeatherService.weather.temp !== 0
&& WeatherService.weather.temp !== 0
Item { Item {
width: parent.width width: parent.width
@@ -60,8 +54,7 @@ Rectangle {
id: refreshButton id: refreshButton
name: "refresh" name: "refresh"
size: Theme.iconSize - 6 size: Theme.iconSize - 6
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
Theme.surfaceText.b, 0.3)
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.rightMargin: -Theme.spacingS anchors.rightMargin: -Theme.spacingS
@@ -102,8 +95,7 @@ Rectangle {
spacing: Theme.spacingL spacing: Theme.spacingL
DankIcon { DankIcon {
name: WeatherService.getWeatherIcon( name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
WeatherService.weather.wCode)
size: Theme.iconSize + 8 size: Theme.iconSize + 8
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -114,8 +106,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
text: (SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) text: (SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (SettingsData.useFahrenheit ? "F" : "C")
+ "°" + (SettingsData.useFahrenheit ? "F" : "C")
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Light font.weight: Font.Light
@@ -125,9 +116,9 @@ Rectangle {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (WeatherService.weather.available) if (WeatherService.weather.available) {
SettingsData.setTemperatureUnit( SettingsData.setTemperatureUnit(!SettingsData.useFahrenheit)
!SettingsData.useFahrenheit) }
} }
enabled: WeatherService.weather.available enabled: WeatherService.weather.available
} }
@@ -136,9 +127,7 @@ Rectangle {
StyledText { StyledText {
text: WeatherService.weather.city || "" text: WeatherService.weather.city || ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
Theme.surfaceText.g,
Theme.surfaceText.b, 0.7)
visible: text.length > 0 visible: text.length > 0
} }
} }
@@ -161,8 +150,7 @@ Rectangle {
} }
StyledText { StyledText {
text: WeatherService.weather.humidity ? WeatherService.weather.humidity text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
+ "%" : "--"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -19,20 +19,16 @@ PanelWindow {
property bool autoHide: SettingsData.dockAutoHide property bool autoHide: SettingsData.dockAutoHide
property real backgroundTransparency: SettingsData.dockTransparency property real backgroundTransparency: SettingsData.dockTransparency
property bool contextMenuOpen: (contextMenu && contextMenu.visible property bool contextMenuOpen: (contextMenu && contextMenu.visible && contextMenu.screen === modelData)
&& contextMenu.screen === modelData)
property bool windowIsFullscreen: { property bool windowIsFullscreen: {
if (!ToplevelManager.activeToplevel) if (!ToplevelManager.activeToplevel) {
return false return false
var activeWindow = ToplevelManager.activeToplevel }
var fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"] const activeWindow = ToplevelManager.activeToplevel
return fullscreenApps.some(app => activeWindow.appId const fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
&& activeWindow.appId.toLowerCase( return fullscreenApps.some(app => activeWindow.appId && activeWindow.appId.toLowerCase().includes(app))
).includes(app))
} }
property bool reveal: (!autoHide || dockMouseArea.containsMouse property bool reveal: (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen) && !windowIsFullscreen
|| dockApps.requestDockShow || contextMenuOpen)
&& !windowIsFullscreen
Connections { Connections {
target: SettingsData target: SettingsData
@@ -68,7 +64,7 @@ PanelWindow {
property real currentScreen: modelData ? modelData : dock.screen property real currentScreen: modelData ? modelData : dock.screen
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920 property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920
property real maxDockWidth: Math.min(screenWidth * 0.8, 1200) property real maxDockWidth: Math.min(screenWidth * 0.8, 1200)
height: dock.reveal ? 65 : 20 height: dock.reveal ? 65 : 20
width: dock.reveal ? Math.min(dockBackground.width + 32, maxDockWidth) : Math.min(Math.max(dockBackground.width + 64, 200), screenWidth * 0.5) width: dock.reveal ? Math.min(dockBackground.width + 32, maxDockWidth) : Math.min(Math.max(dockBackground.width + 64, 200), screenWidth * 0.5)
anchors { anchors {
@@ -115,9 +111,7 @@ PanelWindow {
anchors.topMargin: 4 anchors.topMargin: 4
anchors.bottomMargin: 1 anchors.bottomMargin: 1
color: Qt.rgba(Theme.surfaceContainer.r, color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, backgroundTransparency)
Theme.surfaceContainer.g,
Theme.surfaceContainer.b, backgroundTransparency)
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 1
border.color: Theme.outlineMedium border.color: Theme.outlineMedium
@@ -125,8 +119,7 @@ PanelWindow {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
Theme.surfaceTint.b, 0.04)
radius: parent.radius radius: parent.radius
} }
@@ -147,24 +140,24 @@ PanelWindow {
id: appTooltip id: appTooltip
property var hoveredButton: { property var hoveredButton: {
if (!dockApps.children[0]) if (!dockApps.children[0]) {
return null return null
var row = dockApps.children[0] }
var repeater = null const row = dockApps.children[0]
let repeater = null
for (var i = 0; i < row.children.length; i++) { for (var i = 0; i < row.children.length; i++) {
var child = row.children[i] const child = row.children[i]
if (child && typeof child.count !== "undefined" if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
&& typeof child.itemAt === "function") {
repeater = child repeater = child
break break
} }
} }
if (!repeater || !repeater.itemAt) if (!repeater || !repeater.itemAt) {
return null return null
}
for (var i = 0; i < repeater.count; i++) { for (var i = 0; i < repeater.count; i++) {
var item = repeater.itemAt(i) const item = repeater.itemAt(i)
if (item && item.dockButton if (item && item.dockButton && item.dockButton.showTooltip) {
&& item.dockButton.showTooltip) {
return item.dockButton return item.dockButton
} }
} }
@@ -183,9 +176,7 @@ PanelWindow {
border.color: Theme.outlineMedium border.color: Theme.outlineMedium
y: -height - 8 y: -height - 8
x: hoveredButton ? hoveredButton.mapToItem( x: hoveredButton ? hoveredButton.mapToItem(dockContainer, hoveredButton.width / 2, 0).x - width / 2 : 0
dockContainer, hoveredButton.width / 2,
0).x - width / 2 : 0
StyledText { StyledText {
id: tooltipLabel id: tooltipLabel

View File

@@ -28,49 +28,48 @@ Item {
if (!appData || appData.type !== "window") { if (!appData || appData.type !== "window") {
return false return false
} }
var toplevel = getToplevelObject() const toplevel = getToplevelObject()
if (!toplevel) { if (!toplevel) {
return false return false
} }
return toplevel.activated return toplevel.activated
} }
property string tooltipText: { property string tooltipText: {
if (!appData) if (!appData) {
return "" return ""
}
// For window type, show app name + window title
if (appData.type === "window" && showWindowTitle) { if (appData.type === "window" && showWindowTitle) {
var desktopEntry = DesktopEntries.heuristicLookup(appData.appId) const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
var appName = desktopEntry const appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
&& desktopEntry.name ? desktopEntry.name : appData.appId
return appName + (windowTitle ? " • " + windowTitle : "") return appName + (windowTitle ? " • " + windowTitle : "")
} }
// For pinned apps, just show app name
if (!appData.appId)
return ""
var desktopEntry = DesktopEntries.heuristicLookup(appData.appId) if (!appData.appId) {
return desktopEntry return ""
&& desktopEntry.name ? desktopEntry.name : appData.appId }
const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
} }
width: 40 width: 40
height: 40 height: 40
function getToplevelObject() { function getToplevelObject() {
if (!appData || appData.type !== "window") { if (!appData || appData.type !== "window") {
return null return null
} }
var sortedToplevels = CompositorService.sortedToplevels const sortedToplevels = CompositorService.sortedToplevels
if (!sortedToplevels) { if (!sortedToplevels) {
return null return null
} }
if (appData.uniqueId) { if (appData.uniqueId) {
for (var i = 0; i < sortedToplevels.length; i++) { for (var i = 0; i < sortedToplevels.length; i++) {
var toplevel = sortedToplevels[i] const toplevel = sortedToplevels[i]
var checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
if (checkId === appData.uniqueId) { if (checkId === appData.uniqueId) {
return toplevel return toplevel
} }
@@ -82,7 +81,7 @@ Item {
return sortedToplevels[appData.windowId] return sortedToplevels[appData.windowId]
} }
} }
return null return null
} }
onIsHoveredChanged: { onIsHoveredChanged: {
@@ -148,8 +147,9 @@ Item {
interval: 500 interval: 500
repeat: false repeat: false
onTriggered: { onTriggered: {
if (appData && appData.isPinned) if (appData && appData.isPinned) {
longPressing = true longPressing = true
}
} }
} }
@@ -162,8 +162,7 @@ Item {
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: mouse => { onPressed: mouse => {
if (mouse.button === Qt.LeftButton && appData if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
&& appData.isPinned) {
dragStartPos = Qt.point(mouse.x, mouse.y) dragStartPos = Qt.point(mouse.x, mouse.y)
longPressTimer.start() longPressTimer.start()
} }
@@ -171,9 +170,9 @@ Item {
onReleased: mouse => { onReleased: mouse => {
longPressTimer.stop() longPressTimer.stop()
if (longPressing) { if (longPressing) {
if (dragging && targetIndex >= 0 if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps) {
&& targetIndex !== originalIndex && dockApps) dockApps.movePinnedApp(originalIndex, targetIndex)
dockApps.movePinnedApp(originalIndex, targetIndex) }
longPressing = false longPressing = false
dragging = false dragging = false
@@ -184,10 +183,7 @@ Item {
} }
onPositionChanged: mouse => { onPositionChanged: mouse => {
if (longPressing && !dragging) { if (longPressing && !dragging) {
var distance = Math.sqrt( const distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2))
Math.pow(mouse.x - dragStartPos.x,
2) + Math.pow(
mouse.y - dragStartPos.y, 2))
if (distance > 5) { if (distance > 5) {
dragging = true dragging = true
targetIndex = index targetIndex = index
@@ -195,79 +191,66 @@ Item {
} }
} }
if (dragging) { if (dragging) {
dragOffset = Qt.point( dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y)
mouse.x - dragStartPos.x,
mouse.y - dragStartPos.y)
if (dockApps) { if (dockApps) {
var threshold = 40 const threshold = 40
var newTargetIndex = targetIndex let newTargetIndex = targetIndex
if (dragOffset.x > threshold if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1) {
&& targetIndex < dockApps.pinnedAppCount - 1) newTargetIndex = targetIndex + 1
newTargetIndex = targetIndex + 1 } else if (dragOffset.x < -threshold && targetIndex > 0) {
else if (dragOffset.x < -threshold newTargetIndex = targetIndex - 1
&& targetIndex > 0) }
newTargetIndex = targetIndex - 1
if (newTargetIndex !== targetIndex) { if (newTargetIndex !== targetIndex) {
targetIndex = newTargetIndex targetIndex = newTargetIndex
dragStartPos = Qt.point(mouse.x, dragStartPos = Qt.point(mouse.x, mouse.y)
mouse.y)
} }
} }
} }
} }
onClicked: mouse => { onClicked: mouse => {
if (!appData || longPressing) if (!appData || longPressing) {
return return
}
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
// Handle based on type
if (appData.type === "pinned") { if (appData.type === "pinned") {
// Launch the pinned app
if (appData && appData.appId) { if (appData && appData.appId) {
var desktopEntry = DesktopEntries.heuristicLookup( const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
appData.appId) if (desktopEntry) {
if (desktopEntry) AppUsageHistoryData.addAppUsage({
AppUsageHistoryData.addAppUsage({ "id": appData.appId,
"id": appData.appId, "name": desktopEntry.name || appData.appId,
"name": desktopEntry.name "icon": desktopEntry.icon || "",
|| appData.appId, "exec": desktopEntry.exec || "",
"icon": desktopEntry.icon "comment": desktopEntry.comment || ""
|| "", })
"exec": desktopEntry.exec }
|| "",
"comment": desktopEntry.comment || ""
})
desktopEntry.execute() desktopEntry.execute()
} }
} else if (appData.type === "window") { } else if (appData.type === "window") {
var toplevel = getToplevelObject() const toplevel = getToplevelObject()
if (toplevel) { if (toplevel) {
toplevel.activate() toplevel.activate()
} }
} }
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
if (appData && appData.appId) { if (appData && appData.appId) {
var desktopEntry = DesktopEntries.heuristicLookup( const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
appData.appId) if (desktopEntry) {
if (desktopEntry) AppUsageHistoryData.addAppUsage({
AppUsageHistoryData.addAppUsage({ "id": appData.appId,
"id": appData.appId, "name": desktopEntry.name || appData.appId,
"name": desktopEntry.name "icon": desktopEntry.icon || "",
|| appData.appId, "exec": desktopEntry.exec || "",
"icon": desktopEntry.icon "comment": desktopEntry.comment || ""
|| "", })
"exec": desktopEntry.exec }
|| "", desktopEntry.execute()
"comment": desktopEntry.comment
|| ""
})
desktopEntry.execute()
} }
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
if (contextMenu) if (contextMenu) {
contextMenu.showForButton(root, appData, 40) contextMenu.showForButton(root, appData, 40)
}
} }
} }
} }
@@ -278,8 +261,10 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
implicitSize: 40 implicitSize: 40
source: { source: {
if (appData.appId === "__SEPARATOR__") return "" if (appData.appId === "__SEPARATOR__") {
var desktopEntry = DesktopEntries.heuristicLookup(Paths.moddedAppId(appData.appId)) return ""
}
const desktopEntry = DesktopEntries.heuristicLookup(Paths.moddedAppId(appData.appId))
return desktopEntry && desktopEntry.icon ? Quickshell.iconPath(desktopEntry.icon, true) : "" return desktopEntry && desktopEntry.icon ? Quickshell.iconPath(desktopEntry.icon, true) : ""
} }
mipmap: true mipmap: true
@@ -301,12 +286,14 @@ Item {
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: { text: {
if (!appData || !appData.appId) if (!appData || !appData.appId) {
return "?" return "?"
}
var desktopEntry = DesktopEntries.heuristicLookup(appData.appId) const desktopEntry = DesktopEntries.heuristicLookup(appData.appId)
if (desktopEntry && desktopEntry.name) if (desktopEntry && desktopEntry.name) {
return desktopEntry.name.charAt(0).toUpperCase() return desktopEntry.name.charAt(0).toUpperCase()
}
return appData.appId.charAt(0).toUpperCase() return appData.appId.charAt(0).toUpperCase()
} }
@@ -326,17 +313,17 @@ Item {
radius: 1 radius: 1
visible: appData && (appData.isRunning || appData.type === "window") visible: appData && (appData.isRunning || appData.type === "window")
color: { color: {
if (!appData) if (!appData) {
return "transparent" return "transparent"
}
// For window type, check if focused using reactive property if (isWindowFocused) {
if (isWindowFocused)
return Theme.primary return Theme.primary
}
// For running apps, show dimmer indicator if (appData.isRunning || appData.type === "window") {
if (appData.isRunning || appData.type === "window") return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, }
Theme.surfaceText.b, 0.6)
return "transparent" return "transparent"
} }

View File

@@ -17,15 +17,16 @@ Item {
implicitHeight: row.height implicitHeight: row.height
function movePinnedApp(fromIndex, toIndex) { function movePinnedApp(fromIndex, toIndex) {
if (fromIndex === toIndex) if (fromIndex === toIndex) {
return return
}
var currentPinned = [...(SessionData.pinnedApps || [])] const currentPinned = [...(SessionData.pinnedApps || [])]
if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0 if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0 || toIndex >= currentPinned.length) {
|| toIndex >= currentPinned.length)
return return
}
var movedApp = currentPinned.splice(fromIndex, 1)[0] const movedApp = currentPinned.splice(fromIndex, 1)[0]
currentPinned.splice(toIndex, 0, movedApp) currentPinned.splice(toIndex, 0, movedApp)
SessionData.setPinnedApps(currentPinned) SessionData.setPinnedApps(currentPinned)
@@ -47,60 +48,48 @@ Item {
function updateModel() { function updateModel() {
clear() clear()
var items = [] const items = []
var pinnedApps = [...(SessionData.pinnedApps || [])] const pinnedApps = [...(SessionData.pinnedApps || [])]
// First section: Pinned apps (always visible, not representing running windows)
pinnedApps.forEach(appId => { pinnedApps.forEach(appId => {
items.push({ items.push({
"type": "pinned", "type": "pinned",
"appId": appId, "appId": appId,
"windowId": -1, "windowId": -1,
"windowTitle"// Use -1 instead of null to avoid ListModel warnings "windowTitle": "",
: "",
"workspaceId": -1, "workspaceId": -1,
"isPinned"// Use -1 instead of null "isPinned": true,
: true, "isRunning": false
"isRunning": false,
}) })
}) })
root.pinnedAppCount = pinnedApps.length root.pinnedAppCount = pinnedApps.length
// Get sorted toplevels from CompositorService const sortedToplevels = CompositorService.sortedToplevels
var sortedToplevels = CompositorService.sortedToplevels
if (pinnedApps.length > 0 && sortedToplevels.length > 0) {
// Add separator between pinned and running if both exist
if (pinnedApps.length > 0
&& sortedToplevels.length > 0) {
items.push({ items.push({
"type": "separator", "type": "separator",
"appId": "__SEPARATOR__", "appId": "__SEPARATOR__",
"windowId": -1, "windowId": -1,
"windowTitle"// Use -1 instead of null "windowTitle": "",
: "",
"workspaceId": -1, "workspaceId": -1,
"isPinned"// Use -1 instead of null "isPinned": false,
: false,
"isRunning": false, "isRunning": false,
"isFocused": false "isFocused": false
}) })
} }
// Second section: Running windows (sorted using Theme.sortToplevels)
sortedToplevels.forEach((toplevel, index) => { sortedToplevels.forEach((toplevel, index) => {
// Limit window title length for tooltip const title = toplevel.title || "(Unnamed)"
var title = toplevel.title || "(Unnamed)" const truncatedTitle = title.length > 50 ? title.substring(0, 47) + "..." : title
if (title.length > 50) { const uniqueId = toplevel.title + "|" + (toplevel.appId || "") + "|" + index
title = title.substring(0, 47) + "..."
}
var uniqueId = toplevel.title + "|" + (toplevel.appId || "") + "|" + index
items.push({ items.push({
"type": "window", "type": "window",
"appId": toplevel.appId, "appId": toplevel.appId,
"windowId": index, "windowId": index,
"windowTitle": title, "windowTitle": truncatedTitle,
"workspaceId": -1, "workspaceId": -1,
"isPinned": false, "isPinned": false,
"isRunning": true, "isRunning": true,
@@ -108,9 +97,7 @@ Item {
}) })
}) })
items.forEach(item => { items.forEach(item => append(item))
append(item)
})
} }
} }
@@ -125,8 +112,7 @@ Item {
visible: model.type === "separator" visible: model.type === "separator"
width: 2 width: 2
height: 20 height: 20
color: Qt.rgba(Theme.outline.r, Theme.outline.g, color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
Theme.outline.b, 0.3)
radius: 1 radius: 1
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -159,8 +145,6 @@ Item {
} }
} }
Connections { Connections {
target: SessionData target: SessionData
function onPinnedAppsChanged() { function onPinnedAppsChanged() {

View File

@@ -21,10 +21,10 @@ PanelWindow {
appData = data appData = data
dockVisibleHeight = dockHeight || 40 dockVisibleHeight = dockHeight || 40
var dockWindow = button.Window.window const dockWindow = button.Window.window
if (dockWindow) { if (dockWindow) {
for (var i = 0; i < Quickshell.screens.length; i++) { for (var i = 0; i < Quickshell.screens.length; i++) {
var s = Quickshell.screens[i] const s = Quickshell.screens[i]
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) { if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
root.screen = s root.screen = s
break break
@@ -55,8 +55,11 @@ PanelWindow {
property point anchorPos: Qt.point(screen.width / 2, screen.height - 100) property point anchorPos: Qt.point(screen.width / 2, screen.height - 100)
onAnchorItemChanged: updatePosition() onAnchorItemChanged: updatePosition()
onVisibleChanged: if (visible) onVisibleChanged: {
updatePosition() if (visible) {
updatePosition()
}
}
function updatePosition() { function updatePosition() {
if (!anchorItem) { if (!anchorItem) {
@@ -64,40 +67,40 @@ PanelWindow {
return return
} }
var dockWindow = anchorItem.Window.window const dockWindow = anchorItem.Window.window
if (!dockWindow) { if (!dockWindow) {
anchorPos = Qt.point(screen.width / 2, screen.height - 100) anchorPos = Qt.point(screen.width / 2, screen.height - 100)
return return
} }
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0) const buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
let actualDockHeight = root.dockVisibleHeight
var actualDockHeight = root.dockVisibleHeight // fallback
function findDockBackground(item) { function findDockBackground(item) {
if (item.objectName === "dockBackground") { if (item.objectName === "dockBackground") {
return item return item
} }
for (var i = 0; i < item.children.length; i++) { for (var i = 0; i < item.children.length; i++) {
var found = findDockBackground(item.children[i]) const found = findDockBackground(item.children[i])
if (found) if (found) {
return found return found
}
} }
return null return null
} }
var dockBackground = findDockBackground(dockWindow.contentItem) const dockBackground = findDockBackground(dockWindow.contentItem)
if (dockBackground) { if (dockBackground) {
actualDockHeight = dockBackground.height actualDockHeight = dockBackground.height
} }
var dockBottomMargin = 16 // The dock has bottom margin const dockBottomMargin = 16
var buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20 const buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
var dockContentWidth = dockWindow.width const dockContentWidth = dockWindow.width
var screenWidth = root.screen.width const screenWidth = root.screen.width
var dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2) const dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
var buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2 const buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2
anchorPos = Qt.point(buttonScreenX, buttonScreenY) anchorPos = Qt.point(buttonScreenX, buttonScreenY)
} }
@@ -105,22 +108,19 @@ PanelWindow {
Rectangle { Rectangle {
id: menuContainer id: menuContainer
width: Math.min(400, width: Math.min(400, Math.max(200, menuColumn.implicitWidth + Theme.spacingS * 2))
Math.max(200,
menuColumn.implicitWidth + Theme.spacingS * 2))
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2) height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
x: { x: {
var left = 10 const left = 10
var right = root.width - width - 10 const right = root.width - width - 10
var want = root.anchorPos.x - width / 2 const want = root.anchorPos.x - width / 2
return Math.max(left, Math.min(right, want)) return Math.max(left, Math.min(right, want))
} }
y: Math.max(10, root.anchorPos.y - height + 30) y: Math.max(10, root.anchorPos.y - height + 30)
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
Theme.outline.b, 0.08)
border.width: 1 border.width: 1
opacity: showContextMenu ? 1 : 0 opacity: showContextMenu ? 1 : 0
scale: showContextMenu ? 1 : 0.85 scale: showContextMenu ? 1 : 0.85
@@ -148,10 +148,7 @@ PanelWindow {
width: parent.width width: parent.width
height: 28 height: 28
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Theme.primary.g,
Theme.primary.b,
0.12) : "transparent"
StyledText { StyledText {
anchors.left: parent.left anchors.left: parent.left
@@ -159,8 +156,7 @@ PanelWindow {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: root.appData text: root.appData && root.appData.isPinned ? "Unpin from Dock" : "Pin to Dock"
&& root.appData.isPinned ? "Unpin from Dock" : "Pin to Dock"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Normal font.weight: Font.Normal
@@ -174,8 +170,9 @@ PanelWindow {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (!root.appData) if (!root.appData) {
return return
}
if (root.appData.isPinned) { if (root.appData.isPinned) {
SessionData.removePinnedApp(root.appData.appId) SessionData.removePinnedApp(root.appData.appId)
} else { } else {
@@ -190,16 +187,14 @@ PanelWindow {
visible: root.appData && root.appData.type === "window" visible: root.appData && root.appData.type === "window"
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
Theme.outline.b, 0.2)
} }
Rectangle { Rectangle {
visible: root.appData && root.appData.type === "window" visible: root.appData && root.appData.type === "window"
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
Theme.outline.b, 0.2)
} }
Rectangle { Rectangle {
@@ -207,11 +202,7 @@ PanelWindow {
width: parent.width width: parent.width
height: 28 height: 28
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.rgba( color: closeArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
Theme.error.r,
Theme.error.g,
Theme.error.b,
0.12) : "transparent"
StyledText { StyledText {
anchors.left: parent.left anchors.left: parent.left

View File

@@ -38,10 +38,10 @@ Item {
} }
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("Failed to get session path, exit code:", exitCode) console.warn("Failed to get session path, exit code:", exitCode)
} }
} }
} }
Process { Process {
@@ -52,8 +52,7 @@ Item {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (text.includes("true")) { if (text.includes("true")) {
console.log( console.log("Session is locked on startup, activating lock screen")
"Session is locked on startup, activating lock screen")
LockScreenService.resetState() LockScreenService.resetState()
loader.activeAsync = true loader.activeAsync = true
} }
@@ -61,11 +60,10 @@ Item {
} }
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("Failed to check initial lock state, exit code:", console.warn("Failed to check initial lock state, exit code:", exitCode)
exitCode) }
} }
}
} }
Process { Process {
@@ -77,32 +75,29 @@ Item {
splitMarker: "\n" splitMarker: "\n"
onRead: line => { onRead: line => {
if (line.includes("org.freedesktop.login1.Session.Lock")) { if (line.includes("org.freedesktop.login1.Session.Lock")) {
console.log("login1: Lock signal received -> show lock") console.log("login1: Lock signal received -> show lock")
LockScreenService.resetState() LockScreenService.resetState()
loader.activeAsync = true loader.activeAsync = true
} else if (line.includes( } else if (line.includes("org.freedesktop.login1.Session.Unlock")) {
"org.freedesktop.login1.Session.Unlock")) { console.log("login1: Unlock signal received -> hide lock")
console.log("login1: Unlock signal received -> hide lock") loader.active = false
loader.active = false } else if (line.includes("LockedHint") && line.includes("true")) {
} else if (line.includes("LockedHint") && line.includes( console.log("login1: LockedHint=true -> show lock")
"true")) { LockScreenService.resetState()
console.log("login1: LockedHint=true -> show lock") loader.activeAsync = true
LockScreenService.resetState() } else if (line.includes("LockedHint") && line.includes("false")) {
loader.activeAsync = true console.log("login1: LockedHint=false -> hide lock")
} else if (line.includes("LockedHint") && line.includes( loader.active = false
"false")) { }
console.log("login1: LockedHint=false -> hide lock") }
loader.active = false
}
}
} }
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("gdbus monitor failed, exit code:", exitCode) console.warn("gdbus monitor failed, exit code:", exitCode)
} }
} }
} }
LazyLoader { LazyLoader {
@@ -117,8 +112,9 @@ Item {
locked: true locked: true
onLockedChanged: { onLockedChanged: {
if (!locked) if (!locked) {
loader.active = false loader.active = false
}
} }
LockSurface { LockSurface {
@@ -126,8 +122,8 @@ Item {
lock: sessionLock lock: sessionLock
sharedPasswordBuffer: sessionLock.sharedPasswordBuffer sharedPasswordBuffer: sessionLock.sharedPasswordBuffer
onPasswordChanged: newPassword => { onPasswordChanged: newPassword => {
sessionLock.sharedPasswordBuffer = newPassword sessionLock.sharedPasswordBuffer = newPassword
} }
} }
} }
} }

View File

@@ -6,7 +6,6 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.Pam import Quickshell.Services.Pam
import qs.Common import qs.Common
import qs.Modals
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -15,27 +14,47 @@ Item {
property string passwordBuffer: "" property string passwordBuffer: ""
property bool demoMode: false property bool demoMode: false
property var powerModal: null
property string confirmAction: ""
signal unlockRequested signal unlockRequested
// Internal power dialog state
property bool powerDialogVisible: false
property string powerDialogTitle: ""
property string powerDialogMessage: ""
property string powerDialogConfirmText: ""
property color powerDialogConfirmColor: Theme.primary
property var powerDialogOnConfirm: function() {}
function showPowerDialog(title, message, confirmText, confirmColor, onConfirm) {
powerDialogTitle = title
powerDialogMessage = message
powerDialogConfirmText = confirmText
powerDialogConfirmColor = confirmColor
powerDialogOnConfirm = onConfirm
powerDialogVisible = true
}
function hidePowerDialog() {
powerDialogVisible = false
}
Component.onCompleted: { Component.onCompleted: {
if (demoMode) if (demoMode) {
LockScreenService.pickRandomFact() LockScreenService.pickRandomFact()
}
WeatherService.addRef() WeatherService.addRef()
UserInfoService.refreshUserInfo() UserInfoService.refreshUserInfo()
} }
onDemoModeChanged: { onDemoModeChanged: {
if (demoMode) if (demoMode) {
LockScreenService.pickRandomFact() LockScreenService.pickRandomFact()
}
} }
Component.onDestruction: { Component.onDestruction: {
WeatherService.removeRef() WeatherService.removeRef()
} }
// Backdrop for when no wallpaper is set
Loader { Loader {
anchors.fill: parent anchors.fill: parent
active: !SessionData.wallpaperPath active: !SessionData.wallpaperPath
@@ -159,11 +178,13 @@ Item {
id: profileImageLoader id: profileImageLoader
source: { source: {
if (PortalService.profileImage === "") if (PortalService.profileImage === "") {
return "" return ""
}
if (PortalService.profileImage.startsWith("/")) if (PortalService.profileImage.startsWith("/")) {
return "file://" + PortalService.profileImage return "file://" + PortalService.profileImage
}
return PortalService.profileImage return PortalService.profileImage
} }
@@ -221,8 +242,7 @@ Item {
name: "warning" name: "warning"
size: Theme.iconSize + 4 size: Theme.iconSize + 4
color: Theme.primaryText color: Theme.primaryText
visible: PortalService.profileImage !== "" visible: PortalService.profileImage !== "" && profileImageLoader.status === Image.Error
&& profileImageLoader.status === Image.Error
} }
} }
@@ -232,11 +252,8 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 60 Layout.preferredHeight: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.9)
Theme.surfaceContainer.g, border.color: passwordField.activeFocus ? Theme.primary : Qt.rgba(1, 1, 1, 0.3)
Theme.surfaceContainer.b, 0.9)
border.color: passwordField.activeFocus ? Theme.primary : Qt.rgba(
1, 1, 1, 0.3)
border.width: passwordField.activeFocus ? 2 : 1 border.width: passwordField.activeFocus ? 2 : 1
DankIcon { DankIcon {
@@ -255,29 +272,44 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2 anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
anchors.rightMargin: (revealButton.visible ? revealButton.width : 0) + (virtualKeyboardButton.visible ? virtualKeyboardButton.width : 0) + (enterButton.visible ? enterButton.width + 2 : 0) + (loadingSpinner.visible ? loadingSpinner.width + Theme.spacingM : Theme.spacingM) anchors.rightMargin: {
let margin = Theme.spacingM
if (loadingSpinner.visible) {
margin += loadingSpinner.width
}
if (enterButton.visible) {
margin += enterButton.width + 2
}
if (virtualKeyboardButton.visible) {
margin += virtualKeyboardButton.width
}
if (revealButton.visible) {
margin += revealButton.width
}
return margin
}
opacity: 0 opacity: 0
focus: !demoMode focus: !demoMode
enabled: !demoMode enabled: !demoMode
echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password
onTextChanged: { onTextChanged: {
if (!demoMode) if (!demoMode) {
root.passwordBuffer = text root.passwordBuffer = text
}
} }
onAccepted: { onAccepted: {
if (!demoMode && root.passwordBuffer.length > 0 if (!demoMode && root.passwordBuffer.length > 0 && !pam.active) {
&& !pam.active) {
console.log("Enter pressed, starting PAM authentication") console.log("Enter pressed, starting PAM authentication")
pam.start() pam.start()
} }
} }
Keys.onPressed: event => { Keys.onPressed: event => {
if (demoMode) if (demoMode) {
return return
}
if (pam.active) { if (pam.active) {
console.log( console.log("PAM is active, ignoring input")
"PAM is active, ignoring input")
event.accepted = true event.accepted = true
return return
} }
@@ -292,38 +324,35 @@ Item {
} }
} }
KeyboardController { KeyboardController {
id: keyboardController id: keyboardController
target: passwordField target: passwordField
rootObject: root rootObject: root
} }
StyledText { StyledText {
id: placeholder id: placeholder
property string pamState: ""
anchors.left: lockIcon.right anchors.left: lockIcon.right
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))) anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))))
anchors.rightMargin: 2 anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { text: {
if (demoMode) if (demoMode) {
return "" return ""
}
if (LockScreenService.unlocking) if (LockScreenService.unlocking) {
return "Unlocking..." return "Unlocking..."
}
if (pam.active) if (pam.active) {
return "Authenticating..." return "Authenticating..."
}
return "Password..." return "Password..."
} }
color: LockScreenService.unlocking ? Theme.primary : (pam.active ? Theme.primary : Theme.outline) color: LockScreenService.unlocking ? Theme.primary : (pam.active ? Theme.primary : Theme.outline)
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
opacity: (demoMode opacity: (demoMode || root.passwordBuffer.length === 0) ? 1 : 0
|| root.passwordBuffer.length === 0) ? 1 : 0
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
@@ -347,19 +376,17 @@ Item {
anchors.rightMargin: 2 anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: { text: {
if (demoMode) if (demoMode) {
return "••••••••" return "••••••••"
else if (parent.showPassword) }
if (parent.showPassword) {
return root.passwordBuffer return root.passwordBuffer
else }
return "•".repeat( return "•".repeat(Math.min(root.passwordBuffer.length, 25))
Math.min(
root.passwordBuffer.length, 25))
} }
color: Theme.surfaceText color: Theme.surfaceText
font.pixelSize: parent.showPassword ? Theme.fontSizeMedium : Theme.fontSizeLarge font.pixelSize: parent.showPassword ? Theme.fontSizeMedium : Theme.fontSizeLarge
opacity: (demoMode opacity: (demoMode || root.passwordBuffer.length > 0) ? 1 : 0
|| root.passwordBuffer.length > 0) ? 1 : 0
elide: Text.ElideRight elide: Text.ElideRight
Behavior on opacity { Behavior on opacity {
@@ -378,12 +405,11 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
iconName: parent.showPassword ? "visibility_off" : "visibility" iconName: parent.showPassword ? "visibility_off" : "visibility"
buttonSize: 32 buttonSize: 32
visible: !demoMode && root.passwordBuffer.length > 0 visible: !demoMode && root.passwordBuffer.length > 0 && !pam.active && !LockScreenService.unlocking
&& !pam.active && !LockScreenService.unlocking
enabled: visible enabled: visible
onClicked: parent.showPassword = !parent.showPassword onClicked: parent.showPassword = !parent.showPassword
} }
DankActionButton { DankActionButton {
id: virtualKeyboardButton id: virtualKeyboardButton
anchors.right: enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right) anchors.right: enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)
@@ -393,15 +419,12 @@ Item {
buttonSize: 32 buttonSize: 32
visible: !demoMode && !pam.active && !LockScreenService.unlocking visible: !demoMode && !pam.active && !LockScreenService.unlocking
enabled: visible enabled: visible
onClicked: onClicked: {
{ if (keyboardController.isKeyboardActive) {
if(keyboardController.isKeyboardActive)
{
keyboardController.hide() keyboardController.hide()
} else } else {
{ keyboardController.show()
keyboardController.show() }
}
} }
} }
@@ -415,8 +438,7 @@ Item {
height: 24 height: 24
radius: 12 radius: 12
color: "transparent" color: "transparent"
visible: !demoMode && (pam.active visible: !demoMode && (pam.active || LockScreenService.unlocking)
|| LockScreenService.unlocking)
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -456,9 +478,7 @@ Item {
radius: 10 radius: 10
anchors.centerIn: parent anchors.centerIn: parent
color: "transparent" color: "transparent"
border.color: Qt.rgba(Theme.primary.r, border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
Theme.primary.g,
Theme.primary.b, 0.3)
border.width: 2 border.width: 2
} }
@@ -476,15 +496,11 @@ Item {
height: parent.height / 2 height: parent.height / 2
anchors.top: parent.top anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: Qt.rgba(Theme.surfaceContainer.r, color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.9)
Theme.surfaceContainer.g,
Theme.surfaceContainer.b,
0.9)
} }
RotationAnimation on rotation { RotationAnimation on rotation {
running: pam.active running: pam.active && !LockScreenService.unlocking
&& !LockScreenService.unlocking
loops: Animation.Infinite loops: Animation.Infinite
duration: Anims.durLong duration: Anims.durLong
from: 0 from: 0
@@ -502,9 +518,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
iconName: "keyboard_return" iconName: "keyboard_return"
buttonSize: 36 buttonSize: 36
visible: (demoMode || (root.passwordBuffer.length > 0 visible: (demoMode || (root.passwordBuffer.length > 0 && !pam.active && !LockScreenService.unlocking))
&& !pam.active
&& !LockScreenService.unlocking))
enabled: !demoMode enabled: !demoMode
onClicked: { onClicked: {
if (!demoMode) { if (!demoMode) {
@@ -534,15 +548,15 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: LockScreenService.pamState ? 20 : 0 Layout.preferredHeight: LockScreenService.pamState ? 20 : 0
text: { text: {
if (LockScreenService.pamState === "error") if (LockScreenService.pamState === "error") {
return "Authentication error - try again" return "Authentication error - try again"
}
if (LockScreenService.pamState === "max") if (LockScreenService.pamState === "max") {
return "Too many attempts - locked out" return "Too many attempts - locked out"
}
if (LockScreenService.pamState === "fail") if (LockScreenService.pamState === "fail") {
return "Incorrect password - try again" return "Incorrect password - try again"
}
return "" return ""
} }
color: Theme.error color: Theme.error
@@ -578,22 +592,19 @@ Item {
visible: demoMode visible: demoMode
} }
// Status bar at top
Row { Row {
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Theme.spacingXL anchors.margins: Theme.spacingXL
spacing: Theme.spacingL spacing: Theme.spacingL
// Weather section
Row { Row {
spacing: 6 spacing: 6
visible: WeatherService.weather.available visible: WeatherService.weather.available
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
DankIcon { DankIcon {
name: WeatherService.getWeatherIcon( name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
WeatherService.weather.wCode)
size: Theme.iconSize size: Theme.iconSize
color: "white" color: "white"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -608,91 +619,63 @@ Item {
} }
} }
// Separator
Rectangle { Rectangle {
width: 1 width: 1
height: 24 height: 24
color: Qt.rgba(255, 255, 255, 0.2) color: Qt.rgba(255, 255, 255, 0.2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: WeatherService.weather.available visible: WeatherService.weather.available && (NetworkService.networkStatus !== "disconnected" || BluetoothService.enabled || (AudioService.sink && AudioService.sink.audio) || BatteryService.batteryAvailable)
&& (NetworkService.networkStatus !== "disconnected"
|| BluetoothService.enabled
|| (AudioService.sink && AudioService.sink.audio)
|| BatteryService.batteryAvailable)
} }
// System status icons
Row { Row {
spacing: Theme.spacingM spacing: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkStatus !== "disconnected" visible: NetworkService.networkStatus !== "disconnected" || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio)
|| (BluetoothService.available
&& BluetoothService.enabled)
|| (AudioService.sink && AudioService.sink.audio)
// Network icon
DankIcon { DankIcon {
name: { name: NetworkService.networkStatus === "ethernet" ? "lan" : NetworkService.wifiSignalIcon
if (NetworkService.networkStatus === "ethernet") {
return "lan"
}
return NetworkService.wifiSignalIcon
}
size: Theme.iconSize - 2 size: Theme.iconSize - 2
color: NetworkService.networkStatus color: NetworkService.networkStatus !== "disconnected" ? "white" : Qt.rgba(255, 255, 255, 0.5)
!== "disconnected" ? "white" : Qt.rgba(255,
255, 255, 0.5)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkStatus !== "disconnected" visible: NetworkService.networkStatus !== "disconnected"
} }
// Bluetooth icon
DankIcon { DankIcon {
name: "bluetooth" name: "bluetooth"
size: Theme.iconSize - 2 size: Theme.iconSize - 2
color: "white" color: "white"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: BluetoothService.available visible: BluetoothService.available && BluetoothService.enabled
&& BluetoothService.enabled
} }
// Volume icon
DankIcon { DankIcon {
name: { name: {
if (AudioService.sink && AudioService.sink.audio) { if (!AudioService.sink?.audio) {
if (AudioService.sink.audio.muted return "volume_up"
|| AudioService.sink.audio.volume === 0) }
return "volume_off" if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
else if (AudioService.sink.audio.volume * 100 < 33) return "volume_off"
return "volume_down" }
else if (AudioService.sink.audio.volume * 100 < 33) {
return "volume_up" return "volume_down"
} }
return "volume_up" return "volume_up"
} }
size: Theme.iconSize - 2 size: Theme.iconSize - 2
color: (AudioService.sink && AudioService.sink.audio color: (AudioService.sink && AudioService.sink.audio && (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white"
&& (AudioService.sink.audio.muted
|| AudioService.sink.audio.volume
=== 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: AudioService.sink && AudioService.sink.audio visible: AudioService.sink && AudioService.sink.audio
} }
} }
// Separator
Rectangle { Rectangle {
width: 1 width: 1
height: 24 height: 24
color: Qt.rgba(255, 255, 255, 0.2) color: Qt.rgba(255, 255, 255, 0.2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: BatteryService.batteryAvailable visible: BatteryService.batteryAvailable && (NetworkService.networkStatus !== "disconnected" || BluetoothService.enabled || (AudioService.sink && AudioService.sink.audio))
&& (NetworkService.networkStatus !== "disconnected"
|| BluetoothService.enabled
|| (AudioService.sink && AudioService.sink.audio))
} }
// Battery section
Row { Row {
spacing: 4 spacing: 4
visible: BatteryService.batteryAvailable visible: BatteryService.batteryAvailable
@@ -701,78 +684,94 @@ Item {
DankIcon { DankIcon {
name: { name: {
if (BatteryService.isCharging) { if (BatteryService.isCharging) {
if (BatteryService.batteryLevel >= 90) if (BatteryService.batteryLevel >= 90) {
return "battery_charging_full" return "battery_charging_full"
}
if (BatteryService.batteryLevel >= 80) if (BatteryService.batteryLevel >= 80) {
return "battery_charging_90" return "battery_charging_90"
}
if (BatteryService.batteryLevel >= 60) if (BatteryService.batteryLevel >= 60) {
return "battery_charging_80" return "battery_charging_80"
}
if (BatteryService.batteryLevel >= 50) if (BatteryService.batteryLevel >= 50) {
return "battery_charging_60" return "battery_charging_60"
}
if (BatteryService.batteryLevel >= 30) if (BatteryService.batteryLevel >= 30) {
return "battery_charging_50" return "battery_charging_50"
}
if (BatteryService.batteryLevel >= 20) if (BatteryService.batteryLevel >= 20) {
return "battery_charging_30" return "battery_charging_30"
}
return "battery_charging_20" return "battery_charging_20"
} }
// Check if plugged in but not charging (like at 80% charge limit)
if (BatteryService.isPluggedIn) { if (BatteryService.isPluggedIn) {
if (BatteryService.batteryLevel >= 90) if (BatteryService.batteryLevel >= 90) {
return "battery_charging_full" return "battery_charging_full"
}
if (BatteryService.batteryLevel >= 80) if (BatteryService.batteryLevel >= 80) {
return "battery_charging_90" return "battery_charging_90"
}
if (BatteryService.batteryLevel >= 60) if (BatteryService.batteryLevel >= 60) {
return "battery_charging_80" return "battery_charging_80"
}
if (BatteryService.batteryLevel >= 50) if (BatteryService.batteryLevel >= 50) {
return "battery_charging_60" return "battery_charging_60"
}
if (BatteryService.batteryLevel >= 30) if (BatteryService.batteryLevel >= 30) {
return "battery_charging_50" return "battery_charging_50"
}
if (BatteryService.batteryLevel >= 20) if (BatteryService.batteryLevel >= 20) {
return "battery_charging_30" return "battery_charging_30"
}
return "battery_charging_20" return "battery_charging_20"
} }
// On battery power if (BatteryService.batteryLevel >= 95) {
if (BatteryService.batteryLevel >= 95)
return "battery_full" return "battery_full"
}
if (BatteryService.batteryLevel >= 85) if (BatteryService.batteryLevel >= 85) {
return "battery_6_bar" return "battery_6_bar"
}
if (BatteryService.batteryLevel >= 70) if (BatteryService.batteryLevel >= 70) {
return "battery_5_bar" return "battery_5_bar"
}
if (BatteryService.batteryLevel >= 55) if (BatteryService.batteryLevel >= 55) {
return "battery_4_bar" return "battery_4_bar"
}
if (BatteryService.batteryLevel >= 40) if (BatteryService.batteryLevel >= 40) {
return "battery_3_bar" return "battery_3_bar"
}
if (BatteryService.batteryLevel >= 25) if (BatteryService.batteryLevel >= 25) {
return "battery_2_bar" return "battery_2_bar"
}
return "battery_1_bar" return "battery_1_bar"
} }
size: Theme.iconSize size: Theme.iconSize
color: { color: {
if (BatteryService.isLowBattery if (BatteryService.isLowBattery && !BatteryService.isCharging) {
&& !BatteryService.isCharging)
return Theme.error return Theme.error
}
if (BatteryService.isCharging if (BatteryService.isCharging || BatteryService.isPluggedIn) {
|| BatteryService.isPluggedIn)
return Theme.primary return Theme.primary
}
return "white" return "white"
} }
@@ -800,10 +799,13 @@ Item {
iconColor: Theme.error iconColor: Theme.error
buttonSize: 40 buttonSize: 40
onClicked: { onClicked: {
if (demoMode) if (demoMode) {
console.log("Demo: Power") console.log("Demo: Power")
else } else {
LockScreenService.showPowerDialog() showPowerDialog("Power Off", "Power off this computer?", "Power Off", Theme.error, function() {
SessionService.poweroff()
})
}
} }
} }
@@ -811,10 +813,13 @@ Item {
iconName: "refresh" iconName: "refresh"
buttonSize: 40 buttonSize: 40
onClicked: { onClicked: {
if (demoMode) if (demoMode) {
console.log("Demo: Reboot") console.log("Demo: Reboot")
else } else {
LockScreenService.showRebootDialog() showPowerDialog("Restart", "Restart this computer?", "Restart", Theme.primary, function() {
SessionService.reboot()
})
}
} }
} }
@@ -822,10 +827,13 @@ Item {
iconName: "logout" iconName: "logout"
buttonSize: 40 buttonSize: 40
onClicked: { onClicked: {
if (demoMode) if (demoMode) {
console.log("Demo: Logout") console.log("Demo: Logout")
else } else {
LockScreenService.showLogoutDialog() showPowerDialog("Log Out", "End this session?", "Log Out", Theme.primary, function() {
SessionService.logout()
})
}
} }
} }
} }
@@ -864,16 +872,14 @@ Item {
if (!responseRequired) if (!responseRequired)
return return
console.log("Responding to PAM with password buffer length:", console.log("Responding to PAM with password buffer length:", root.passwordBuffer.length)
root.passwordBuffer.length)
respond(root.passwordBuffer) respond(root.passwordBuffer)
} }
onCompleted: res => { onCompleted: res => {
if (demoMode) if (demoMode)
return return
console.log( console.log("PAM authentication completed with result:", res)
"PAM authentication completed with result:", res)
if (res === PamResult.Success) { if (res === PamResult.Success) {
console.log("Authentication successful, unlocking") console.log("Authentication successful, unlocking")
LockScreenService.setUnlocking(true) LockScreenService.setUnlocking(true)
@@ -906,20 +912,11 @@ Item {
onClicked: root.unlockRequested() onClicked: root.unlockRequested()
} }
// Internal power dialog
Rectangle { Rectangle {
id: powerDialog
function open() {
LockScreenService.showPowerDialog()
}
function close() {
LockScreenService.hidePowerDialog()
}
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.8) color: Qt.rgba(0, 0, 0, 0.8)
visible: LockScreenService.powerDialogVisible visible: powerDialogVisible
z: 1000 z: 1000
Rectangle { Rectangle {
@@ -939,12 +936,12 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
name: "power_settings_new" name: "power_settings_new"
size: 32 size: 32
color: Theme.error color: powerDialogConfirmColor
} }
StyledText { StyledText {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: "Power off this computer?" text: powerDialogMessage
color: Theme.surfaceText color: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
@@ -958,11 +955,7 @@ Item {
width: 100 width: 100
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: cancelMouse1.pressed ? Qt.rgba( color: Theme.surfaceVariant
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.7) : cancelMouse1.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
@@ -972,12 +965,10 @@ Item {
} }
MouseArea { MouseArea {
id: cancelMouse1
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: powerDialog.close() onClicked: hidePowerDialog()
} }
} }
@@ -985,249 +976,23 @@ Item {
width: 100 width: 100
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: powerMouse.pressed ? Qt.rgba( color: powerDialogConfirmColor
Theme.error.r,
Theme.error.g,
Theme.error.b,
0.8) : powerMouse.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 1) : Theme.error
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: "Power Off" text: powerDialogConfirmText
color: "white" color: Theme.primaryText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
} }
MouseArea { MouseArea {
id: powerMouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
powerDialog.close() hidePowerDialog()
SessionService.poweroff() powerDialogOnConfirm()
}
}
}
}
}
}
}
Rectangle {
id: rebootDialog
function open() {
LockScreenService.showRebootDialog()
}
function close() {
LockScreenService.hideRebootDialog()
}
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.8)
visible: LockScreenService.rebootDialogVisible
z: 1000
Rectangle {
anchors.centerIn: parent
width: 320
height: 180
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outline
border.width: 1
Column {
anchors.centerIn: parent
spacing: Theme.spacingXL
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: "refresh"
size: 32
color: Theme.primary
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: "Restart this computer?"
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
Rectangle {
width: 100
height: 40
radius: Theme.cornerRadius
color: cancelMouse2.pressed ? Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.7) : cancelMouse2.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant
StyledText {
anchors.centerIn: parent
text: "Cancel"
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
}
MouseArea {
id: cancelMouse2
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: rebootDialog.close()
}
}
Rectangle {
width: 100
height: 40
radius: Theme.cornerRadius
color: rebootMouse.pressed ? Qt.rgba(
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.8) : rebootMouse.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 1) : Theme.primary
StyledText {
anchors.centerIn: parent
text: "Restart"
color: "white"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
}
MouseArea {
id: rebootMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
rebootDialog.close()
SessionService.reboot()
}
}
}
}
}
}
}
Rectangle {
id: logoutDialog
function open() {
LockScreenService.showLogoutDialog()
}
function close() {
LockScreenService.hideLogoutDialog()
}
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.8)
visible: LockScreenService.logoutDialogVisible
z: 1000
Rectangle {
anchors.centerIn: parent
width: 320
height: 180
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outline
border.width: 1
Column {
anchors.centerIn: parent
spacing: Theme.spacingXL
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: "logout"
size: 32
color: Theme.primary
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: "End this session?"
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
Rectangle {
width: 100
height: 40
radius: Theme.cornerRadius
color: cancelMouse3.pressed ? Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.7) : cancelMouse3.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.9) : Theme.surfaceVariant
StyledText {
anchors.centerIn: parent
text: "Cancel"
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
}
MouseArea {
id: cancelMouse3
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: logoutDialog.close()
}
}
Rectangle {
width: 100
height: 40
radius: Theme.cornerRadius
color: logoutMouse.pressed ? Qt.rgba(
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.8) : logoutMouse.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 1) : Theme.primary
StyledText {
anchors.centerIn: parent
text: "Log Out"
color: "white"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
}
MouseArea {
id: logoutMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
logoutDialog.close()
SessionService.logout()
} }
} }
} }

View File

@@ -2,7 +2,6 @@ import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Modals.Common
PanelWindow { PanelWindow {
id: root id: root
@@ -34,16 +33,11 @@ PanelWindow {
demoActive = false demoActive = false
} }
ConfirmModal {
id: powerModal
}
Loader { Loader {
anchors.fill: parent anchors.fill: parent
active: demoActive active: demoActive
sourceComponent: LockScreenContent { sourceComponent: LockScreenContent {
demoMode: true demoMode: true
powerModal: powerModal
onUnlockRequested: root.hideDemo() onUnlockRequested: root.hideDemo()
} }
} }

View File

@@ -1,8 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Modals.Common
WlSessionLockSurface { WlSessionLockSurface {
id: root id: root
@@ -20,15 +20,10 @@ WlSessionLockSurface {
color: "transparent" color: "transparent"
ConfirmModal {
id: powerConfirmModal
}
Loader { Loader {
anchors.fill: parent anchors.fill: parent
sourceComponent: LockScreenContent { sourceComponent: LockScreenContent {
demoMode: false demoMode: false
powerModal: powerConfirmModal
passwordBuffer: root.sharedPasswordBuffer passwordBuffer: root.sharedPasswordBuffer
onUnlockRequested: root.unlock() onUnlockRequested: root.unlock()
onPasswordBufferChanged: { onPasswordBufferChanged: {