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

widgets: add spacer, divider, tweak interface

This commit is contained in:
bbedward
2025-08-02 13:10:39 -04:00
parent 2e85494236
commit 21c40b58bc
47 changed files with 2660 additions and 2205 deletions

View File

@@ -1,25 +1,24 @@
pragma Singleton
import Quickshell import Quickshell
import qs.Common import qs.Common
pragma Singleton
Singleton { Singleton {
id: root id: root
// Clear all image cache // Clear all image cache
function clearImageCache() { function clearImageCache() {
Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]) Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]);
Paths.mkdir(Paths.imagecache) Paths.mkdir(Paths.imagecache);
} }
// Clear cache older than specified minutes // Clear cache older than specified minutes
function clearOldCache(ageInMinutes) { function clearOldCache(ageInMinutes) {
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]) Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]);
} }
// Clear cache for specific size // Clear cache for specific size
function clearCacheForSize(size) { function clearCacheForSize(size) {
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]) Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]);
} }
// Get cache size in MB // Get cache size in MB
@@ -36,6 +35,7 @@ Singleton {
} }
} }
} }
`, root) `, root);
} }
}
}

View File

@@ -500,7 +500,13 @@ Singleton {
// Handle both old string format and new object format // Handle both old string format and new object format
var widgetId = typeof order[i] === "string" ? order[i] : order[i].id; var widgetId = typeof order[i] === "string" ? order[i] : order[i].id;
var enabled = typeof order[i] === "string" ? true : order[i].enabled; var enabled = typeof order[i] === "string" ? true : order[i].enabled;
listModel.append({"widgetId": widgetId, "enabled": enabled}); var size = typeof order[i] === "string" ? undefined : order[i].size;
var item = {"widgetId": widgetId, "enabled": enabled};
if (size !== undefined) {
item.size = size;
}
listModel.append(item);
} }
} }

View File

@@ -1,9 +1,9 @@
import Quickshell
import QtQuick import QtQuick
import Quickshell
QtObject { QtObject {
required property Singleton service required property Singleton service
Component.onCompleted: service.refCount++ Component.onCompleted: service.refCount++
Component.onDestruction: service.refCount-- Component.onDestruction: service.refCount--
} }

View File

@@ -71,20 +71,16 @@ DankModal {
} }
function generateThumbnails() { function generateThumbnails() {
if (!imagemagickAvailable) return; if (!imagemagickAvailable)
return ;
for (let i = 0; i < clipboardModel.count; i++) { for (let i = 0; i < clipboardModel.count; i++) {
const entry = clipboardModel.get(i).entry; const entry = clipboardModel.get(i).entry;
const entryType = getEntryType(entry); const entryType = getEntryType(entry);
if (entryType === "image") { if (entryType === "image") {
const entryId = entry.split('\t')[0]; const entryId = entry.split('\t')[0];
const thumbnailPath = `${thumbnailCacheDir}/${entryId}.png`; const thumbnailPath = `${thumbnailCacheDir}/${entryId}.png`;
thumbnailGenProcess.command = ["sh", "-c", `mkdir -p "${thumbnailCacheDir}" && cliphist decode ${entryId} | magick - -resize '128x128>' "${thumbnailPath}"`];
thumbnailGenProcess.command = [
"sh", "-c",
`mkdir -p "${thumbnailCacheDir}" && cliphist decode ${entryId} | magick - -resize '128x128>' "${thumbnailPath}"`
];
thumbnailGenProcess.running = true; thumbnailGenProcess.running = true;
} }
} }
@@ -95,7 +91,6 @@ DankModal {
return `${thumbnailCacheDir}/${entryId}.png`; return `${thumbnailCacheDir}/${entryId}.png`;
} }
function refreshClipboard() { function refreshClipboard() {
clipboardProcess.running = true; clipboardProcess.running = true;
} }
@@ -157,7 +152,7 @@ DankModal {
cornerRadius: Theme.cornerRadiusLarge cornerRadius: Theme.cornerRadiusLarge
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1
enableShadow: true enableShadow: true
onBackgroundClicked: { onBackgroundClicked: {
hide(); hide();
} }
@@ -292,8 +287,8 @@ DankModal {
for (const line of lines) { for (const line of lines) {
if (line.trim().length > 0) if (line.trim().length > 0)
clipboardModel.append({ clipboardModel.append({
"entry": line "entry": line
}); });
} }
updateFilteredModel(); updateFilteredModel();
@@ -360,6 +355,7 @@ DankModal {
checkImageMagickProcess.running = true; checkImageMagickProcess.running = true;
} }
} }
} }
Process { Process {
@@ -367,12 +363,11 @@ DankModal {
command: ["which", "magick"] command: ["which", "magick"]
running: false running: false
onExited: (exitCode) => { onExited: (exitCode) => {
imagemagickAvailable = (exitCode === 0); imagemagickAvailable = (exitCode === 0);
if (!imagemagickAvailable) { if (!imagemagickAvailable)
console.warn("ClipboardHistoryModal: ImageMagick not available, thumbnails disabled"); console.warn("ClipboardHistoryModal: ImageMagick not available, thumbnails disabled");
}
} }
} }
@@ -380,15 +375,13 @@ DankModal {
id: thumbnailGenProcess id: thumbnailGenProcess
running: false running: false
onExited: (exitCode) => { onExited: (exitCode) => {
if (exitCode !== 0) { if (exitCode !== 0)
console.warn("ClipboardHistoryModal: Thumbnail generation failed with exit code:", exitCode); console.warn("ClipboardHistoryModal: Thumbnail generation failed with exit code:", exitCode);
}
} }
} }
IpcHandler { IpcHandler {
function open() { function open() {
console.log("ClipboardHistoryModal: IPC open() called"); console.log("ClipboardHistoryModal: IPC open() called");
@@ -440,6 +433,7 @@ DankModal {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Row { Row {
@@ -464,7 +458,9 @@ DankModal {
hoverColor: Theme.errorHover hoverColor: Theme.errorHover
onClicked: hide() onClicked: hide()
} }
} }
} }
DankTextField { DankTextField {
@@ -490,6 +486,7 @@ DankModal {
target: clipboardHistoryModal target: clipboardHistoryModal
} }
} }
Rectangle { Rectangle {
@@ -504,30 +501,24 @@ DankModal {
ListView { ListView {
id: clipboardListView id: clipboardListView
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
clip: true clip: true
model: filteredClipboardModel model: filteredClipboardModel
spacing: Theme.spacingXS spacing: Theme.spacingXS
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler { WheelHandler {
target: null target: null
onWheel: (ev) => { onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0 let dy = ev.pixelDelta.y !== 0 ? ev.pixelDelta.y : (ev.angleDelta.y / 120) * clipboardListView.wheelBaseStep;
? ev.pixelDelta.y if (ev.inverted)
: (ev.angleDelta.y / 120) * clipboardListView.wheelBaseStep; dy = -dy;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, clipboardListView.contentHeight - clipboardListView.height); const maxY = Math.max(0, clipboardListView.contentHeight - clipboardListView.height);
clipboardListView.contentY = Math.max(0, Math.min(maxY, clipboardListView.contentY = Math.max(0, Math.min(maxY, clipboardListView.contentY - dy * clipboardListView.wheelMultiplier));
clipboardListView.contentY - dy * clipboardListView.wheelMultiplier));
ev.accepted = true; ev.accepted = true;
} }
} }
@@ -540,212 +531,228 @@ DankModal {
visible: filteredClipboardModel.count === 0 visible: filteredClipboardModel.count === 0
} }
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AlwaysOff
}
delegate: Rectangle { delegate: Rectangle {
property string entryType: getEntryType(model.entry) property string entryType: getEntryType(model.entry)
property string entryPreview: getEntryPreview(model.entry) property string entryPreview: getEntryPreview(model.entry)
property int entryIndex: index + 1 property int entryIndex: index + 1
property string entryData: model.entry property string entryData: model.entry
property alias thumbnailImageSource: thumbnailImageSource property alias thumbnailImageSource: thumbnailImageSource
width: clipboardListView.width width: clipboardListView.width
height: Math.max(entryType === "image" ? 72 : 60, contentText.contentHeight + Theme.spacingL) height: Math.max(entryType === "image" ? 72 : 60, contentText.contentHeight + Theme.spacingL)
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: mouseArea.containsMouse ? Theme.primaryHover : Theme.primaryBackground color: mouseArea.containsMouse ? Theme.primaryHover : Theme.primaryBackground
border.color: Theme.outlineStrong border.color: Theme.outlineStrong
border.width: 1 border.width: 1
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
anchors.rightMargin: Theme.spacingS // Reduced right margin
spacing: Theme.spacingL
// Index number
Rectangle {
width: 24
height: 24
radius: 12
color: Theme.primarySelected
anchors.verticalCenter: parent.verticalCenter
StyledText {
anchors.centerIn: parent
text: entryIndex.toString()
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.primary
}
}
// Content thumbnail/icon and text
Row { Row {
anchors.fill: parent anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingM width: parent.width - 68 // Account for index (24) + spacing (16) + delete button (32) - small margin
anchors.rightMargin: Theme.spacingS // Reduced right margin spacing: Theme.spacingM
spacing: Theme.spacingL
// Index number // Thumbnail or icon container
Rectangle { Item {
width: 24 width: entryType === "image" ? 48 : Theme.iconSize
height: 24 height: entryType === "image" ? 48 : Theme.iconSize
radius: 12
color: Theme.primarySelected
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
// Image thumbnail
CachingImage {
id: thumbnailImageSource
anchors.fill: parent
source: entryType === "image" && imagemagickAvailable ? "file://" + getThumbnailPath(model.entry) : ""
fillMode: Image.PreserveAspectCrop
smooth: true
cache: true
visible: false
asynchronous: true
// Handle loading errors gracefully and retry once
onStatusChanged: {
if (status === Image.Error && source !== "") {
// Clear source to prevent repeated error attempts
const originalSource = source;
source = "";
// Retry once after 2 seconds to allow thumbnail generation
retryTimer.originalSource = originalSource;
retryTimer.start();
}
}
Timer {
id: retryTimer
property string originalSource: ""
interval: 2000
repeat: false
onTriggered: {
if (originalSource !== "" && thumbnailImageSource.source === "")
thumbnailImageSource.source = originalSource;
}
}
}
MultiEffect {
anchors.fill: parent
source: thumbnailImageSource
maskEnabled: true
maskSource: clipboardCircularMask
visible: entryType === "image" && imagemagickAvailable && thumbnailImageSource.status === Image.Ready
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: clipboardCircularMask
width: 48
height: 48
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: width / 2
color: "black"
antialiasing: true
}
}
// Fallback icon
DankIcon {
visible: !(entryType === "image" && imagemagickAvailable && thumbnailImageSource.status === Image.Ready)
name: {
if (entryType === "image")
return "image";
if (entryType === "long_text")
return "subject";
return "content_copy";
}
size: Theme.iconSize
color: Theme.primary
anchors.centerIn: parent
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - (entryType === "image" ? 48 : Theme.iconSize) - Theme.spacingM
spacing: Theme.spacingXS
StyledText { StyledText {
anchors.centerIn: parent text: {
text: entryIndex.toString() switch (entryType) {
case "image":
return "Image • " + entryPreview;
case "long_text":
return "Long Text";
default:
return "Text";
}
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.primary color: Theme.primary
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
} }
} StyledText {
id: contentText
// Content thumbnail/icon and text
Row {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 68 // Account for index (24) + spacing (16) + delete button (32) - small margin
spacing: Theme.spacingM
// Thumbnail or icon container
Item {
width: entryType === "image" ? 48 : Theme.iconSize
height: entryType === "image" ? 48 : Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
// Image thumbnail
CachingImage {
id: thumbnailImageSource
anchors.fill: parent
source: entryType === "image" && imagemagickAvailable ? "file://" + getThumbnailPath(model.entry) : ""
fillMode: Image.PreserveAspectCrop
smooth: true
cache: true
visible: false
asynchronous: true
// Handle loading errors gracefully and retry once
onStatusChanged: {
if (status === Image.Error && source !== "") {
// Clear source to prevent repeated error attempts
const originalSource = source;
source = "";
// Retry once after 2 seconds to allow thumbnail generation
retryTimer.originalSource = originalSource;
retryTimer.start();
}
}
Timer {
id: retryTimer
interval: 2000
repeat: false
property string originalSource: ""
onTriggered: {
if (originalSource !== "" && thumbnailImageSource.source === "") {
thumbnailImageSource.source = originalSource;
}
}
}
}
MultiEffect {
anchors.fill: parent
source: thumbnailImageSource
maskEnabled: true
maskSource: clipboardCircularMask
visible: entryType === "image" && imagemagickAvailable && thumbnailImageSource.status === Image.Ready
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: clipboardCircularMask
width: 48
height: 48
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: width / 2
color: "black"
antialiasing: true
}
}
// Fallback icon
DankIcon {
visible: !(entryType === "image" && imagemagickAvailable && thumbnailImageSource.status === Image.Ready)
name: {
if (entryType === "image")
return "image";
if (entryType === "long_text")
return "subject";
return "content_copy";
}
size: Theme.iconSize
color: Theme.primary
anchors.centerIn: parent
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - (entryType === "image" ? 48 : Theme.iconSize) - Theme.spacingM
spacing: Theme.spacingXS
StyledText {
text: {
switch (entryType) {
case "image":
return "Image • " + entryPreview;
case "long_text":
return "Long Text";
default:
return "Text";
}
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
}
StyledText {
id: contentText
text: entryPreview
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
wrapMode: Text.WordWrap
maximumLineCount: entryType === "long_text" ? 3 : 1
elide: Text.ElideRight
}
text: entryPreview
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
wrapMode: Text.WordWrap
maximumLineCount: entryType === "long_text" ? 3 : 1
elide: Text.ElideRight
} }
} }
} }
// Delete button }
DankActionButton {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
iconName: "close"
iconSize: Theme.iconSize - 6
iconColor: Theme.error
hoverColor: Theme.errorHover
onClicked: {
console.log("Delete clicked for entry:", model.entry);
deleteEntry(model.entry);
}
}
// Main click area - explicitly excludes delete button area // Delete button
MouseArea { DankActionButton {
id: mouseArea anchors.right: parent.right
anchors.fill: parent anchors.rightMargin: Theme.spacingM
anchors.rightMargin: 40 // Enough space to avoid delete button (32 + 8 margin) anchors.verticalCenter: parent.verticalCenter
hoverEnabled: true iconName: "close"
cursorShape: Qt.PointingHandCursor iconSize: Theme.iconSize - 6
onClicked: copyEntry(model.entry) iconColor: Theme.error
hoverColor: Theme.errorHover
onClicked: {
console.log("Delete clicked for entry:", model.entry);
deleteEntry(model.entry);
} }
}
Behavior on color { // Main click area - explicitly excludes delete button area
ColorAnimation { MouseArea {
duration: Theme.shortDuration id: mouseArea
}
anchors.fill: parent
anchors.rightMargin: 40 // Enough space to avoid delete button (32 + 8 margin)
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: copyEntry(model.entry)
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
} }
} }
} }
}
} }
} }

View File

@@ -48,79 +48,73 @@ DankModal {
Item { Item {
anchors.fill: parent anchors.fill: parent
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingL spacing: Theme.spacingL
// Header // Header
Row { Row {
width: parent.width width: parent.width
Column { Column {
width: parent.width - 40 width: parent.width - 40
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText {
text: "Network Information"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: "Details for \"" + networkSSID + "\""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
elide: Text.ElideRight
}
StyledText {
text: "Network Information"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
} }
StyledText { DankActionButton {
text: "Details for \"" + networkSSID + "\"" iconName: "close"
font.pixelSize: Theme.fontSizeMedium iconSize: Theme.iconSize - 4
color: Theme.surfaceTextMedium iconColor: Theme.surfaceText
width: parent.width hoverColor: Theme.errorHover
elide: Text.ElideRight onClicked: {
root.hideDialog();
}
} }
} }
DankActionButton { // Network Details
iconName: "close" Flickable {
iconSize: Theme.iconSize - 4 property real wheelMultiplier: 1.8
iconColor: Theme.surfaceText property int wheelBaseStep: 160
hoverColor: Theme.errorHover
onClicked: { width: parent.width
root.hideDialog(); height: parent.height - 140
clip: true
contentWidth: width
contentHeight: detailsRect.height
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0 ? ev.pixelDelta.y : (ev.angleDelta.y / 120) * parent.wheelBaseStep;
if (ev.inverted)
dy = -dy;
const maxY = Math.max(0, parent.contentHeight - parent.height);
parent.contentY = Math.max(0, Math.min(maxY, parent.contentY - dy * parent.wheelMultiplier));
ev.accepted = true;
}
} }
}
} Rectangle {
// Network Details
Flickable {
width: parent.width
height: parent.height - 140
clip: true
contentWidth: width
contentHeight: detailsRect.height
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * parent.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, parent.contentHeight - parent.height);
parent.contentY = Math.max(0, Math.min(maxY,
parent.contentY - dy * parent.wheelMultiplier));
ev.accepted = true;
}
}
Rectangle {
id: detailsRect id: detailsRect
width: parent.width width: parent.width
@@ -142,48 +136,58 @@ DankModal {
lineHeight: 1.5 lineHeight: 1.5
} }
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AlwaysOff
}
} }
} // Close Button
Item {
width: parent.width
height: 40
// Close Button Rectangle {
Item { anchors.right: parent.right
width: parent.width anchors.verticalCenter: parent.verticalCenter
height: 40 width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
Rectangle { StyledText {
anchors.right: parent.right id: closeText
anchors.verticalCenter: parent.verticalCenter
width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
StyledText { anchors.centerIn: parent
id: closeText text: "Close"
font.pixelSize: Theme.fontSizeMedium
anchors.centerIn: parent color: Theme.background
text: "Close" font.weight: Font.Medium
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.hideDialog();
} }
}
Behavior on color { MouseArea {
ColorAnimation { id: closeArea
duration: Theme.shortDuration
easing.type: Theme.standardEasing anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.hideDialog();
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
} }
} }
@@ -193,7 +197,7 @@ DankModal {
} }
} }
}
} }
} }

View File

@@ -7,9 +7,9 @@ import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modules.ProcessList
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.ProcessList
DankModal { DankModal {
id: processListModal id: processListModal
@@ -25,9 +25,9 @@ DankModal {
function hide() { function hide() {
processListModal.visible = false; processListModal.visible = false;
// Close any open context menus // Close any open context menus
if (processContextMenu.visible) { if (processContextMenu.visible)
processContextMenu.close(); processContextMenu.close();
}
} }
function toggle() { function toggle() {
@@ -44,13 +44,40 @@ DankModal {
backgroundColor: Theme.popupBackground() backgroundColor: Theme.popupBackground()
cornerRadius: Theme.cornerRadiusXLarge cornerRadius: Theme.cornerRadiusXLarge
enableShadow: true enableShadow: true
onBackgroundClicked: hide()
Ref { Ref {
service: SysMonitorService service: SysMonitorService
} }
onBackgroundClicked: hide() Component {
id: processesTabComponent
ProcessesTab {
contextMenu: processContextMenu
}
}
Component {
id: performanceTabComponent
PerformanceTab {
}
}
Component {
id: systemTabComponent
SystemTab {
}
}
ProcessContextMenu {
id: processContextMenu
}
content: Component { content: Component {
Item { Item {
@@ -102,6 +129,7 @@ DankModal {
onClicked: processListModal.hide() onClicked: processListModal.hide()
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
} }
Rectangle { Rectangle {
@@ -234,7 +262,9 @@ DankModal {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
} }
Loader { Loader {
@@ -252,7 +282,9 @@ DankModal {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
} }
Loader { Loader {
@@ -270,33 +302,17 @@ DankModal {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
} }
} }
} }
} }
}
Component {
id: processesTabComponent
ProcessesTab {
contextMenu: processContextMenu
}
}
Component {
id: performanceTabComponent
PerformanceTab {}
}
Component {
id: systemTabComponent
SystemTab {}
}
ProcessContextMenu {
id: processContextMenu
} }
} }

View File

@@ -94,6 +94,7 @@ DankModal {
content: Component { content: Component {
Item { Item {
id: spotlightKeyHandler id: spotlightKeyHandler
anchors.fill: parent anchors.fill: parent
focus: true focus: true
// Handle keyboard shortcuts // Handle keyboard shortcuts
@@ -139,6 +140,7 @@ DankModal {
CategorySelector { CategorySelector {
id: categorySelector id: categorySelector
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2 width: parent.width - Theme.spacingM * 2
categories: appLauncher.categories categories: appLauncher.categories
@@ -148,6 +150,7 @@ DankModal {
return appLauncher.setCategory(category); return appLauncher.setCategory(category);
} }
} }
} }
// Search field with view toggle buttons // Search field with view toggle buttons
@@ -184,11 +187,10 @@ DankModal {
hide(); hide();
event.accepted = true; event.accepted = true;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) { } else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0) { if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0)
appLauncher.launchSelected(); appLauncher.launchSelected();
} else if (appLauncher.model.count > 0) { else if (appLauncher.model.count > 0)
appLauncher.launchApp(appLauncher.model.get(0)); appLauncher.launchApp(appLauncher.model.get(0));
}
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Down || event.key === 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)) { } else if (event.key === Qt.Key_Down || event.key === 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; event.accepted = false;

View File

@@ -78,15 +78,15 @@ Item {
} }
if (searchQuery.length === 0) if (searchQuery.length === 0)
apps = apps.sort(function(a, b) { apps = apps.sort(function(a, b) {
var aId = a.id || (a.execString || a.exec || ""); var aId = a.id || (a.execString || a.exec || "");
var bId = b.id || (b.execString || b.exec || ""); var bId = b.id || (b.execString || b.exec || "");
var aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0; var aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0;
var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0; var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0;
if (aUsage !== bUsage) if (aUsage !== bUsage)
return bUsage - aUsage; return bUsage - aUsage;
return (a.name || "").localeCompare(b.name || ""); return (a.name || "").localeCompare(b.name || "");
}); });
// Convert to model format and populate // Convert to model format and populate
apps.forEach((app) => { apps.forEach((app) => {

View File

@@ -10,9 +10,9 @@ import qs.Widgets
Column { Column {
id: root id: root
property string currentSinkDisplayName: AudioService.sink ? AudioService.displayName(AudioService.sink) : "" property string currentSinkDisplayName: AudioService.sink ? AudioService.displayName(AudioService.sink) : ""
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -50,21 +50,27 @@ Column {
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
} }
} }
} }
Repeater { Repeater {
model: { model: {
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values) return [] if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values)
let sinks = [] return [];
let sinks = [];
for (let i = 0; i < Pipewire.nodes.values.length; i++) { for (let i = 0; i < Pipewire.nodes.values.length; i++) {
let node = Pipewire.nodes.values[i] let node = Pipewire.nodes.values[i];
if (!node || node.isStream) continue if (!node || node.isStream)
if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink) { continue;
sinks.push(node)
} if ((node.type & PwNodeType.AudioSink) === PwNodeType.AudioSink)
sinks.push(node);
} }
return sinks return sinks;
} }
Rectangle { Rectangle {
@@ -119,7 +125,9 @@ Column {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text !== "" visible: text !== ""
} }
} }
} }
MouseArea { MouseArea {
@@ -131,8 +139,12 @@ Column {
onClicked: { onClicked: {
if (modelData) if (modelData)
Pipewire.preferredDefaultAudioSink = modelData; Pipewire.preferredDefaultAudioSink = modelData;
} }
} }
} }
} }
}
}

View File

@@ -10,9 +10,9 @@ import qs.Widgets
Column { Column {
id: root id: root
property string currentSourceDisplayName: AudioService.source ? AudioService.displayName(AudioService.source) : "" property string currentSourceDisplayName: AudioService.source ? AudioService.displayName(AudioService.source) : ""
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -50,21 +50,27 @@ Column {
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
} }
} }
} }
Repeater { Repeater {
model: { model: {
if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values) return [] if (!Pipewire.ready || !Pipewire.nodes || !Pipewire.nodes.values)
let sources = [] return [];
let sources = [];
for (let i = 0; i < Pipewire.nodes.values.length; i++) { for (let i = 0; i < Pipewire.nodes.values.length; i++) {
let node = Pipewire.nodes.values[i] let node = Pipewire.nodes.values[i];
if (!node || node.isStream) continue if (!node || node.isStream)
if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.name.includes(".monitor")) { continue;
sources.push(node)
} if ((node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource && !node.name.includes(".monitor"))
sources.push(node);
} }
return sources return sources;
} }
Rectangle { Rectangle {
@@ -117,7 +123,9 @@ Column {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text !== "" visible: text !== ""
} }
} }
} }
MouseArea { MouseArea {
@@ -129,8 +137,12 @@ Column {
onClicked: { onClicked: {
if (modelData) if (modelData)
Pipewire.preferredDefaultAudioSource = modelData; Pipewire.preferredDefaultAudioSource = modelData;
} }
} }
} }
} }
}
}

View File

@@ -9,10 +9,10 @@ import qs.Widgets
Column { Column {
id: root id: root
property real micLevel: Math.min(100, (AudioService.source && AudioService.source.audio && AudioService.source.audio.volume * 100) || 0) property real micLevel: Math.min(100, (AudioService.source && AudioService.source.audio && AudioService.source.audio.volume * 100) || 0)
property bool micMuted: (AudioService.source && AudioService.source.audio && AudioService.source.audio.muted) || false property bool micMuted: (AudioService.source && AudioService.source.audio && AudioService.source.audio.muted) || false
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -40,8 +40,10 @@ Column {
onClicked: { onClicked: {
if (AudioService.source && AudioService.source.audio) if (AudioService.source && AudioService.source.audio)
AudioService.source.audio.muted = !AudioService.source.audio.muted; AudioService.source.audio.muted = !AudioService.source.audio.muted;
} }
} }
} }
Item { Item {
@@ -74,7 +76,9 @@ Column {
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standardDecel easing.bezierCurve: Anims.standardDecel
} }
} }
} }
Rectangle { Rectangle {
@@ -90,16 +94,9 @@ Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1 scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1
Behavior on scale {
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standard
}
}
Rectangle { Rectangle {
id: micTooltip id: micTooltip
width: tooltipText.contentWidth + Theme.spacingS * 2 width: tooltipText.contentWidth + Theme.spacingS * 2
height: tooltipText.contentHeight + Theme.spacingXS * 2 height: tooltipText.contentHeight + Theme.spacingXS * 2
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
@@ -111,24 +108,38 @@ Column {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: (micMouseArea.containsMouse && !root.micMuted) || micMouseArea.isDragging visible: (micMouseArea.containsMouse && !root.micMuted) || micMouseArea.isDragging
opacity: visible ? 1 : 0 opacity: visible ? 1 : 0
StyledText { StyledText {
id: tooltipText id: tooltipText
text: Math.round(root.micLevel) + "%" text: Math.round(root.micLevel) + "%"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
anchors.centerIn: parent anchors.centerIn: parent
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
Behavior on scale {
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standard
}
}
} }
} }
MouseArea { MouseArea {
@@ -197,6 +208,7 @@ Column {
micMouseArea.isDragging = false; micMouseArea.isDragging = false;
} }
} }
} }
DankIcon { DankIcon {
@@ -205,5 +217,7 @@ Column {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
}
}

View File

@@ -10,7 +10,7 @@ import qs.Widgets
Column { Column {
id: root id: root
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
@@ -34,6 +34,7 @@ Column {
Rectangle { Rectangle {
id: scanButton id: scanButton
width: Math.max(100, scanText.contentWidth + Theme.spacingL * 2) width: Math.max(100, scanText.contentWidth + Theme.spacingL * 2)
height: 32 height: 32
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -61,6 +62,7 @@ Column {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
@@ -70,12 +72,14 @@ Column {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (BluetoothService.adapter) { if (BluetoothService.adapter)
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering; BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
}
} }
} }
} }
} }
Rectangle { Rectangle {
@@ -88,6 +92,7 @@ Column {
Column { Column {
id: noteColumn id: noteColumn
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -110,6 +115,7 @@ Column {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
StyledText { StyledText {
@@ -119,14 +125,16 @@ Column {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
} }
} }
Repeater { Repeater {
model: { model: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
return []; return [];
var filtered = Bluetooth.devices.values.filter((dev) => { var filtered = Bluetooth.devices.values.filter((dev) => {
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0); return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
}); });
@@ -213,8 +221,10 @@ Column {
text: { text: {
if (modelData.pairing) if (modelData.pairing)
return "Pairing..."; return "Pairing...";
if (modelData.blocked) if (modelData.blocked)
return "Blocked"; return "Blocked";
return BluetoothService.getSignalStrength(modelData); return BluetoothService.getSignalStrength(modelData);
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -242,9 +252,13 @@ Column {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked visible: modelData.signalStrength !== undefined && modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
} }
} }
} }
} }
} }
Rectangle { Rectangle {
@@ -292,11 +306,12 @@ Column {
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor) cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
enabled: canConnect && !isBusy enabled: canConnect && !isBusy
onClicked: { onClicked: {
if (modelData) { if (modelData)
BluetoothService.connectDeviceWithTrust(modelData); BluetoothService.connectDeviceWithTrust(modelData);
}
} }
} }
} }
MouseArea { MouseArea {
@@ -308,12 +323,14 @@ Column {
cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor) cursorShape: canConnect && !isBusy ? Qt.PointingHandCursor : (isBusy ? Qt.BusyCursor : Qt.ArrowCursor)
enabled: canConnect && !isBusy enabled: canConnect && !isBusy
onClicked: { onClicked: {
if (modelData) { if (modelData)
BluetoothService.connectDeviceWithTrust(modelData); BluetoothService.connectDeviceWithTrust(modelData);
}
} }
} }
} }
} }
Column { Column {
@@ -322,11 +339,10 @@ Column {
visible: { visible: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
return false; return false;
var availableCount = Bluetooth.devices.values.filter((dev) => { var availableCount = Bluetooth.devices.values.filter((dev) => {
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0); return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
}).length; }).length;
return availableCount === 0; return availableCount === 0;
} }
@@ -347,6 +363,7 @@ Column {
to: 360 to: 360
duration: 2000 duration: 2000
} }
} }
StyledText { StyledText {
@@ -356,6 +373,7 @@ Column {
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
StyledText { StyledText {
@@ -364,6 +382,7 @@ Column {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
StyledText { StyledText {
@@ -373,15 +392,15 @@ Column {
visible: { visible: {
if (!BluetoothService.adapter || !Bluetooth.devices) if (!BluetoothService.adapter || !Bluetooth.devices)
return true; return true;
var availableCount = Bluetooth.devices.values.filter((dev) => { var availableCount = Bluetooth.devices.values.filter((dev) => {
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0); return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
}).length; }).length;
return availableCount === 0 && !BluetoothService.adapter.discovering; return availableCount === 0 && !BluetoothService.adapter.discovering;
} }
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
}
}

View File

@@ -10,11 +10,11 @@ import qs.Widgets
Rectangle { Rectangle {
id: root id: root
property var deviceData: null property var deviceData: null
property bool menuVisible: false property bool menuVisible: false
property var parentItem property var parentItem
function show(x, y) { function show(x, y) {
const menuWidth = 160; const menuWidth = 160;
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2; const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
@@ -27,14 +27,14 @@ Rectangle {
root.visible = true; root.visible = true;
root.menuVisible = true; root.menuVisible = true;
} }
function hide() { function hide() {
root.menuVisible = false; root.menuVisible = false;
Qt.callLater(() => { Qt.callLater(() => {
root.visible = false; root.visible = false;
}); });
} }
visible: false visible: false
width: 160 width: 160
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
@@ -45,7 +45,7 @@ Rectangle {
z: 1000 z: 1000
opacity: menuVisible ? 1 : 0 opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85 scale: menuVisible ? 1 : 0.85
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 4 anchors.topMargin: 4
@@ -56,26 +56,26 @@ Rectangle {
color: Qt.rgba(0, 0, 0, 0.15) color: Qt.rgba(0, 0, 0, 0.15)
z: parent.z - 1 z: parent.z - 1
} }
Column { Column {
id: menuColumn id: menuColumn
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
spacing: 1 spacing: 1
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: connectArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: root.deviceData && root.deviceData.connected ? "link_off" : "link" name: root.deviceData && root.deviceData.connected ? "link_off" : "link"
size: Theme.iconSize - 2 size: Theme.iconSize - 2
@@ -83,7 +83,7 @@ Rectangle {
opacity: 0.7 opacity: 0.7
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: root.deviceData && root.deviceData.connected ? "Disconnect" : "Connect" text: root.deviceData && root.deviceData.connected ? "Disconnect" : "Connect"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -91,60 +91,63 @@ Rectangle {
font.weight: Font.Normal font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: connectArea id: connectArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (root.deviceData) { if (root.deviceData) {
if (root.deviceData.connected) { if (root.deviceData.connected)
root.deviceData.disconnect(); root.deviceData.disconnect();
} else { else
BluetoothService.connectDeviceWithTrust(root.deviceData); BluetoothService.connectDeviceWithTrust(root.deviceData);
}
} }
root.hide(); root.hide();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
Rectangle { Rectangle {
width: parent.width - Theme.spacingS * 2 width: parent.width - Theme.spacingS * 2
height: 5 height: 5
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: "transparent" color: "transparent"
Rectangle { Rectangle {
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, Theme.outline.b, 0.2) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 32 height: 32
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: forgetArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" color: forgetArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: "delete" name: "delete"
size: Theme.iconSize - 2 size: Theme.iconSize - 2
@@ -152,7 +155,7 @@ Rectangle {
opacity: 0.7 opacity: 0.7
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: "Forget Device" text: "Forget Device"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -160,42 +163,49 @@ Rectangle {
font.weight: Font.Normal font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: forgetArea id: forgetArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (root.deviceData) { if (root.deviceData)
root.deviceData.forget(); root.deviceData.forget();
}
root.hide(); root.hide();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
}
}

View File

@@ -10,7 +10,7 @@ import qs.Widgets
Rectangle { Rectangle {
id: root id: root
width: parent.width width: parent.width
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -47,7 +47,9 @@ Rectangle {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
} }
} }
MouseArea { MouseArea {
@@ -57,9 +59,10 @@ Rectangle {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (BluetoothService.adapter) { if (BluetoothService.adapter)
BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled; BluetoothService.adapter.enabled = !BluetoothService.adapter.enabled;
}
} }
} }
}
}

View File

@@ -10,9 +10,9 @@ import qs.Widgets
Column { Column {
id: root id: root
property var bluetoothContextMenuWindow property var bluetoothContextMenuWindow
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: BluetoothService.adapter && BluetoothService.adapter.enabled visible: BluetoothService.adapter && BluetoothService.adapter.enabled
@@ -84,8 +84,11 @@ Column {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: text.length > 0 visible: text.length > 0
} }
} }
} }
} }
Rectangle { Rectangle {
@@ -126,7 +129,9 @@ Column {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
} }
} }
} }
MouseArea { MouseArea {
@@ -138,13 +143,15 @@ Column {
enabled: !BluetoothService.isDeviceBusy(modelData) enabled: !BluetoothService.isDeviceBusy(modelData)
cursorShape: enabled ? Qt.PointingHandCursor : Qt.BusyCursor cursorShape: enabled ? Qt.PointingHandCursor : Qt.BusyCursor
onClicked: { onClicked: {
if (modelData.connected) { if (modelData.connected)
modelData.disconnect(); modelData.disconnect();
} else { else
BluetoothService.connectDeviceWithTrust(modelData); BluetoothService.connectDeviceWithTrust(modelData);
}
} }
} }
} }
} }
}
}

View File

@@ -5,9 +5,9 @@ import Quickshell.Bluetooth
import Quickshell.Io import Quickshell.Io
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modules.ControlCenter.Bluetooth
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.ControlCenter.Bluetooth
Item { Item {
id: bluetoothTab id: bluetoothTab
@@ -22,18 +22,23 @@ Item {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL
BluetoothToggle { } BluetoothToggle {
}
PairedDevicesList { PairedDevicesList {
bluetoothContextMenuWindow: bluetoothContextMenuWindow bluetoothContextMenuWindow: bluetoothContextMenuWindow
} }
AvailableDevicesList { } AvailableDevicesList {
}
} }
} }
BluetoothContextMenu { BluetoothContextMenu {
id: bluetoothContextMenuWindow id: bluetoothContextMenuWindow
parentItem: bluetoothTab parentItem: bluetoothTab
} }
@@ -52,5 +57,7 @@ Item {
onClicked: { onClicked: {
} }
} }
} }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,25 +4,27 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services
import qs.Modules import qs.Modules
import qs.Services
import qs.Widgets import qs.Widgets
ScrollView { ScrollView {
id: displayTab id: displayTab
clip: true property var brightnessDebounceTimer
property var brightnessDebounceTimer: Timer { brightnessDebounceTimer: Timer {
interval: BrightnessService.ddcAvailable ? 500 : 50 // 500ms for slow DDC (i2c), 50ms for fast laptop backlight
repeat: false
property int pendingValue: 0 property int pendingValue: 0
interval: BrightnessService.ddcAvailable ? 500 : 50 // 500ms for slow DDC (i2c), 50ms for fast laptop backlight
repeat: false
onTriggered: { onTriggered: {
console.log("Debounce timer fired, setting brightness to:", pendingValue); console.log("Debounce timer fired, setting brightness to:", pendingValue);
BrightnessService.setBrightness(pendingValue); BrightnessService.setBrightness(pendingValue);
} }
} }
clip: true
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingL spacing: Theme.spacingL

View File

@@ -9,13 +9,14 @@ import qs.Widgets
Rectangle { Rectangle {
id: ethernetCard id: ethernetCard
width: parent.width width: parent.width
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (ethernetPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet") if (ethernetPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "ethernet")
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8); return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8);
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5); return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5);
} }
border.color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
@@ -47,6 +48,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
StyledText { StyledText {
@@ -56,11 +58,13 @@ Rectangle {
leftPadding: Theme.iconSize + Theme.spacingM leftPadding: Theme.iconSize + Theme.spacingM
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
// Loading spinner for preference changes // Loading spinner for preference changes
DankIcon { DankIcon {
id: ethernetLoadingSpinner id: ethernetLoadingSpinner
name: "refresh" name: "refresh"
size: Theme.iconSize - 4 size: Theme.iconSize - 4
color: Theme.primary color: Theme.primary
@@ -69,7 +73,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.changingPreference && NetworkService.targetPreference === "ethernet" visible: NetworkService.changingPreference && NetworkService.targetPreference === "ethernet"
z: 10 z: 10
RotationAnimation { RotationAnimation {
target: ethernetLoadingSpinner target: ethernetLoadingSpinner
property: "rotation" property: "rotation"
@@ -79,11 +83,13 @@ Rectangle {
duration: 1000 duration: 1000
loops: Animation.Infinite loops: Animation.Infinite
} }
} }
// Ethernet toggle switch (matching WiFi style) // Ethernet toggle switch (matching WiFi style)
DankToggle { DankToggle {
id: ethernetToggle id: ethernetToggle
checked: NetworkService.ethernetConnected checked: NetworkService.ethernetConnected
enabled: true enabled: true
anchors.right: parent.right anchors.right: parent.right
@@ -97,6 +103,7 @@ Rectangle {
// MouseArea for network preference (excluding toggle area) // MouseArea for network preference (excluding toggle area)
MouseArea { MouseArea {
id: ethernetPreferenceArea id: ethernetPreferenceArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 60 // Exclude toggle area anchors.rightMargin: 60 // Exclude toggle area
hoverEnabled: true hoverEnabled: true
@@ -118,5 +125,7 @@ Rectangle {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
}
}

View File

@@ -9,25 +9,31 @@ import qs.Widgets
Rectangle { Rectangle {
id: wifiCard id: wifiCard
property var refreshTimer property var refreshTimer
function getWiFiSignalIcon(signalStrength) { function getWiFiSignalIcon(signalStrength) {
switch (signalStrength) { switch (signalStrength) {
case "excellent": return "wifi"; case "excellent":
case "good": return "wifi_2_bar"; return "wifi";
case "fair": return "wifi_1_bar"; case "good":
case "poor": return "signal_wifi_0_bar"; return "wifi_2_bar";
default: return "wifi"; case "fair":
return "wifi_1_bar";
case "poor":
return "signal_wifi_0_bar";
default:
return "wifi";
} }
} }
width: parent.width width: parent.width
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (wifiPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi") if (wifiPreferenceArea.containsMouse && NetworkService.ethernetConnected && NetworkService.wifiEnabled && NetworkService.networkStatus !== "wifi")
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8); return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8);
return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5); return Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5);
} }
border.color: NetworkService.networkStatus === "wifi" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: NetworkService.networkStatus === "wifi" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
@@ -47,13 +53,12 @@ Rectangle {
DankIcon { DankIcon {
name: { name: {
if (!NetworkService.wifiEnabled) { if (!NetworkService.wifiEnabled)
return "wifi_off"; return "wifi_off";
} else if (NetworkService.currentWifiSSID !== "") { else if (NetworkService.currentWifiSSID !== "")
return getWiFiSignalIcon(NetworkService.wifiSignalStrength); return getWiFiSignalIcon(NetworkService.wifiSignalStrength);
} else { else
return "wifi"; return "wifi";
}
} }
size: Theme.iconSize size: Theme.iconSize
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
@@ -62,13 +67,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
if (!NetworkService.wifiEnabled) { if (!NetworkService.wifiEnabled)
return "WiFi is off"; return "WiFi is off";
} else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID) { else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID)
return NetworkService.currentWifiSSID || "Connected"; return NetworkService.currentWifiSSID || "Connected";
} else { else
return "Not Connected"; return "Not Connected";
}
} }
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText color: NetworkService.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
@@ -76,28 +80,30 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
StyledText { StyledText {
text: { text: {
if (!NetworkService.wifiEnabled) { if (!NetworkService.wifiEnabled)
return "Turn on WiFi to see networks"; return "Turn on WiFi to see networks";
} else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID) { else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID)
return NetworkService.wifiIP || "Connected"; return NetworkService.wifiIP || "Connected";
} else { else
return "Select a network below"; return "Select a network below";
}
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
leftPadding: Theme.iconSize + Theme.spacingM leftPadding: Theme.iconSize + Theme.spacingM
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
// Loading spinner for preference changes // Loading spinner for preference changes
DankIcon { DankIcon {
id: wifiLoadingSpinner id: wifiLoadingSpinner
name: "refresh" name: "refresh"
size: Theme.iconSize - 4 size: Theme.iconSize - 4
color: Theme.primary color: Theme.primary
@@ -106,7 +112,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.changingPreference && NetworkService.targetPreference === "wifi" visible: NetworkService.changingPreference && NetworkService.targetPreference === "wifi"
z: 10 z: 10
RotationAnimation { RotationAnimation {
target: wifiLoadingSpinner target: wifiLoadingSpinner
property: "rotation" property: "rotation"
@@ -116,11 +122,13 @@ Rectangle {
duration: 1000 duration: 1000
loops: Animation.Infinite loops: Animation.Infinite
} }
} }
// WiFi toggle switch // WiFi toggle switch
DankToggle { DankToggle {
id: wifiToggle id: wifiToggle
checked: NetworkService.wifiEnabled checked: NetworkService.wifiEnabled
enabled: true enabled: true
toggling: NetworkService.wifiToggling toggling: NetworkService.wifiToggling
@@ -140,15 +148,16 @@ Rectangle {
NetworkService.refreshNetworkStatus(); NetworkService.refreshNetworkStatus();
} }
NetworkService.toggleWifiRadio(); NetworkService.toggleWifiRadio();
if (refreshTimer) { if (refreshTimer)
refreshTimer.triggered = true; refreshTimer.triggered = true;
}
} }
} }
// MouseArea for network preference (excluding toggle area) // MouseArea for network preference (excluding toggle area)
MouseArea { MouseArea {
id: wifiPreferenceArea id: wifiPreferenceArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 60 // Exclude toggle area anchors.rightMargin: 60 // Exclude toggle area
hoverEnabled: true hoverEnabled: true
@@ -170,5 +179,7 @@ Rectangle {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
}
}

View File

@@ -19,20 +19,16 @@ Rectangle {
function show(x, y) { function show(x, y) {
const menuWidth = 160; const menuWidth = 160;
wifiContextMenuWindow.visible = true; wifiContextMenuWindow.visible = true;
Qt.callLater(() => { Qt.callLater(() => {
const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2; const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2;
let finalX = x - menuWidth / 2; let finalX = x - menuWidth / 2;
let finalY = y + 4; let finalY = y + 4;
finalX = Math.max(Theme.spacingS, Math.min(finalX, parentItem.width - menuWidth - Theme.spacingS)); finalX = Math.max(Theme.spacingS, Math.min(finalX, parentItem.width - menuWidth - Theme.spacingS));
finalY = Math.max(Theme.spacingS, Math.min(finalY, parentItem.height - menuHeight - Theme.spacingS)); finalY = Math.max(Theme.spacingS, Math.min(finalY, parentItem.height - menuHeight - Theme.spacingS));
if (finalY + menuHeight > parentItem.height - Theme.spacingS) { if (finalY + menuHeight > parentItem.height - Theme.spacingS) {
finalY = y - menuHeight - 4; finalY = y - menuHeight - 4;
finalY = Math.max(Theme.spacingS, finalY); finalY = Math.max(Theme.spacingS, finalY);
} }
wifiContextMenuWindow.x = finalX; wifiContextMenuWindow.x = finalX;
wifiContextMenuWindow.y = finalY; wifiContextMenuWindow.y = finalY;
wifiContextMenuWindow.menuVisible = true; wifiContextMenuWindow.menuVisible = true;
@@ -56,7 +52,6 @@ Rectangle {
z: 1000 z: 1000
opacity: menuVisible ? 1 : 0 opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85 scale: menuVisible ? 1 : 0.85
Component.onCompleted: { Component.onCompleted: {
menuVisible = false; menuVisible = false;
visible = false; visible = false;
@@ -76,6 +71,7 @@ Rectangle {
Column { Column {
id: wifiMenuColumn id: wifiMenuColumn
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
spacing: 1 spacing: 1
@@ -108,10 +104,12 @@ Rectangle {
font.weight: Font.Normal font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: connectWifiArea id: connectWifiArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -142,7 +140,9 @@ Rectangle {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
// Separator // Separator
@@ -158,6 +158,7 @@ Rectangle {
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
} }
// Forget Network option (only for saved networks) // Forget Network option (only for saved networks)
@@ -189,17 +190,19 @@ Rectangle {
font.weight: Font.Normal font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: forgetWifiArea id: forgetWifiArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (wifiContextMenuWindow.networkData) { if (wifiContextMenuWindow.networkData)
NetworkService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid); NetworkService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid);
}
wifiContextMenuWindow.hide(); wifiContextMenuWindow.hide();
} }
} }
@@ -209,7 +212,9 @@ Rectangle {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
// Network Info option // Network Info option
@@ -240,17 +245,19 @@ Rectangle {
font.weight: Font.Normal font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: infoWifiArea id: infoWifiArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (wifiContextMenuWindow.networkData && networkInfoModalRef) { if (wifiContextMenuWindow.networkData && networkInfoModalRef)
networkInfoModalRef.showNetworkInfo(wifiContextMenuWindow.networkData.ssid, wifiContextMenuWindow.networkData); networkInfoModalRef.showNetworkInfo(wifiContextMenuWindow.networkData.ssid, wifiContextMenuWindow.networkData);
}
wifiContextMenuWindow.hide(); wifiContextMenuWindow.hide();
} }
} }
@@ -260,8 +267,11 @@ Rectangle {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
Behavior on opacity { Behavior on opacity {
@@ -269,6 +279,7 @@ Rectangle {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
Behavior on scale { Behavior on scale {
@@ -276,5 +287,7 @@ Rectangle {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing easing.type: Theme.emphasizedEasing
} }
} }
}
}

View File

@@ -9,21 +9,26 @@ import qs.Widgets
Column { Column {
id: root id: root
property var wifiContextMenuWindow property var wifiContextMenuWindow
property var sortedWifiNetworks property var sortedWifiNetworks
property var wifiPasswordModalRef property var wifiPasswordModalRef
function getWiFiSignalIcon(signalStrength) { function getWiFiSignalIcon(signalStrength) {
switch (signalStrength) { switch (signalStrength) {
case "excellent": return "wifi"; case "excellent":
case "good": return "wifi_2_bar"; return "wifi";
case "fair": return "wifi_1_bar"; case "good":
case "poor": return "signal_wifi_0_bar"; return "wifi_2_bar";
default: return "wifi"; case "fair":
return "wifi_1_bar";
case "poor":
return "signal_wifi_0_bar";
default:
return "wifi";
} }
} }
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 100 anchors.topMargin: 100
anchors.left: parent.left anchors.left: parent.left
@@ -31,7 +36,7 @@ Column {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
visible: NetworkService.wifiEnabled visible: NetworkService.wifiEnabled
spacing: Theme.spacingS spacing: Theme.spacingS
// Available Networks Section with refresh button (spanning version) // Available Networks Section with refresh button (spanning version)
Row { Row {
width: parent.width width: parent.width
@@ -59,6 +64,7 @@ Column {
DankIcon { DankIcon {
id: refreshIconSpan id: refreshIconSpan
anchors.centerIn: parent anchors.centerIn: parent
name: "refresh" name: "refresh"
size: Theme.iconSize - 6 size: Theme.iconSize - 6
@@ -80,11 +86,14 @@ Column {
duration: 200 duration: 200
easing.type: Easing.OutQuad easing.type: Easing.OutQuad
} }
} }
} }
MouseArea { MouseArea {
id: refreshAreaSpan id: refreshAreaSpan
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -96,9 +105,11 @@ Column {
} }
} }
} }
} }
} }
// Scrollable networks container // Scrollable networks container
Flickable { Flickable {
width: parent.width width: parent.width
@@ -109,12 +120,13 @@ Column {
boundsBehavior: Flickable.DragAndOvershootBounds boundsBehavior: Flickable.DragAndOvershootBounds
flickDeceleration: 8000 flickDeceleration: 8000
maximumFlickVelocity: 15000 maximumFlickVelocity: 15000
Column { Column {
id: spanningNetworksColumn id: spanningNetworksColumn
width: parent.width width: parent.width
spacing: Theme.spacingXS spacing: Theme.spacingXS
Repeater { Repeater {
model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? sortedWifiNetworks : [] model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? sortedWifiNetworks : []
@@ -129,11 +141,12 @@ Column {
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingXS anchors.margins: Theme.spacingXS
anchors.rightMargin: Theme.spacingM // Extra right margin for scrollbar anchors.rightMargin: Theme.spacingM // Extra right margin for scrollbar
// Signal strength icon // Signal strength icon
DankIcon { DankIcon {
id: signalIcon2 id: signalIcon2
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
name: getWiFiSignalIcon(modelData.signalStrength) name: getWiFiSignalIcon(modelData.signalStrength)
@@ -164,29 +177,37 @@ Column {
text: { text: {
if (modelData.connected) if (modelData.connected)
return "Connected"; return "Connected";
if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid) if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid)
return "Connecting..."; return "Connecting...";
if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid) if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid)
return "Invalid password"; return "Invalid password";
if (modelData.saved) if (modelData.saved)
return "Saved" + (modelData.secured ? " • Secured" : " • Open"); return "Saved" + (modelData.secured ? " • Secured" : " • Open");
return modelData.secured ? "Secured" : "Open"; return modelData.secured ? "Secured" : "Open";
} }
font.pixelSize: Theme.fontSizeSmall - 1 font.pixelSize: Theme.fontSizeSmall - 1
color: { color: {
if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid) if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid)
return Theme.primary; return Theme.primary;
if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid) if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid)
return Theme.error; return Theme.error;
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7); return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
} }
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
// Right side icons // Right side icons
Row { Row {
id: rightIcons2 id: rightIcons2
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
@@ -203,6 +224,7 @@ Column {
// Context menu button // Context menu button
Rectangle { Rectangle {
id: wifiMenuButton id: wifiMenuButton
width: 24 width: 24
height: 24 height: 24
radius: 12 radius: 12
@@ -218,6 +240,7 @@ Column {
MouseArea { MouseArea {
id: wifiMenuButtonArea id: wifiMenuButtonArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -226,7 +249,6 @@ Column {
let buttonCenter = wifiMenuButtonArea.width / 2; let buttonCenter = wifiMenuButtonArea.width / 2;
let buttonBottom = wifiMenuButtonArea.height; let buttonBottom = wifiMenuButtonArea.height;
let globalPos = wifiMenuButtonArea.mapToItem(wifiContextMenuWindow.parentItem, buttonCenter, buttonBottom); let globalPos = wifiMenuButtonArea.mapToItem(wifiContextMenuWindow.parentItem, buttonCenter, buttonBottom);
Qt.callLater(() => { Qt.callLater(() => {
wifiContextMenuWindow.show(globalPos.x, globalPos.y); wifiContextMenuWindow.show(globalPos.x, globalPos.y);
}); });
@@ -237,20 +259,25 @@ Column {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
} }
} }
} }
} }
} }
MouseArea { MouseArea {
id: networkArea2 id: networkArea2
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 32 // Exclude menu button area anchors.rightMargin: 32 // Exclude menu button area
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (modelData.connected) if (modelData.connected)
return; return ;
if (modelData.saved) { if (modelData.saved) {
NetworkService.connectToWifi(modelData.ssid); NetworkService.connectToWifi(modelData.ssid);
@@ -265,12 +292,17 @@ Column {
} }
} }
} }
} }
} }
} }
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded policy: ScrollBar.AsNeeded
} }
} }
}
}

View File

@@ -25,6 +25,7 @@ Item {
DankActionButton { DankActionButton {
id: doNotDisturbButton id: doNotDisturbButton
iconName: Prefs.doNotDisturb ? "notifications_off" : "notifications" iconName: Prefs.doNotDisturb ? "notifications_off" : "notifications"
iconColor: Prefs.doNotDisturb ? Theme.error : Theme.surfaceText iconColor: Prefs.doNotDisturb ? Theme.error : Theme.surfaceText
buttonSize: 28 buttonSize: 28
@@ -62,13 +63,18 @@ Item {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
Rectangle { Rectangle {
id: clearAllButton id: clearAllButton
width: 120 width: 120
height: 28 height: 28
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge

View File

@@ -12,9 +12,50 @@ PanelWindow {
required property var notificationData required property var notificationData
required property string notificationId required property string notificationId
readonly property bool hasValidData: notificationData && notificationData.notification readonly property bool hasValidData: notificationData && notificationData.notification
property int screenY: 0
property bool exiting: false
property bool _isDestroying: false
property bool _finalized: false
signal entered()
signal exitFinished()
function startExit() {
if (exiting || _isDestroying)
return ;
exiting = true;
exitAnim.restart();
exitWatchdog.restart();
if (NotificationService.removeFromVisibleNotifications)
NotificationService.removeFromVisibleNotifications(win.notificationData);
}
function forceExit() {
if (_isDestroying)
return ;
_isDestroying = true;
exiting = true;
visible = false;
exitWatchdog.stop();
finalizeExit("forced");
}
function finalizeExit(reason) {
if (_finalized)
return ;
_finalized = true;
_isDestroying = true;
exitWatchdog.stop();
wrapperConn.enabled = false;
wrapperConn.target = null;
win.exitFinished();
}
visible: hasValidData visible: hasValidData
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
@@ -22,6 +63,41 @@ PanelWindow {
color: "transparent" color: "transparent"
implicitWidth: 400 implicitWidth: 400
implicitHeight: 122 implicitHeight: 122
onScreenYChanged: margins.top = Theme.barHeight + 4 + screenY
onHasValidDataChanged: {
if (!hasValidData && !exiting && !_isDestroying) {
console.warn("NotificationPopup: Data became invalid, forcing exit");
forceExit();
}
}
Component.onCompleted: {
if (hasValidData) {
Qt.callLater(() => {
return enterX.restart();
});
} else {
console.warn("NotificationPopup created with invalid data");
forceExit();
}
}
onNotificationDataChanged: {
if (!_isDestroying) {
wrapperConn.target = win.notificationData || null;
notificationConn.target = (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null;
}
}
onEntered: {
if (!_isDestroying)
enterDelay.start();
}
Component.onDestruction: {
_isDestroying = true;
exitWatchdog.stop();
if (notificationData && notificationData.timer)
notificationData.timer.stop();
}
anchors { anchors {
top: true top: true
@@ -33,37 +109,11 @@ PanelWindow {
right: 12 right: 12
} }
property int screenY: 0
onScreenYChanged: margins.top = Theme.barHeight + 4 + screenY
Behavior on screenY {
id: screenYAnim
enabled: !exiting && !_isDestroying
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standardDecel
}
}
property bool exiting: false
property bool _isDestroying: false
property bool _finalized: false
signal entered()
signal exitFinished()
onHasValidDataChanged: {
if (!hasValidData && !exiting && !_isDestroying) {
console.warn("NotificationPopup: Data became invalid, forcing exit");
forceExit();
}
}
Item { Item {
id: content id: content
anchors.fill: parent anchors.fill: parent
visible: win.hasValidData visible: win.hasValidData
transform: Translate { id: tx; x: Anims.slidePx }
layer.enabled: (enterX.running || exitAnim.running) layer.enabled: (enterX.running || exitAnim.running)
layer.smooth: true layer.smooth: true
@@ -80,6 +130,7 @@ PanelWindow {
Rectangle { Rectangle {
id: shadowLayer1 id: shadowLayer1
anchors.fill: parent anchors.fill: parent
anchors.margins: -3 anchors.margins: -3
color: "transparent" color: "transparent"
@@ -91,6 +142,7 @@ PanelWindow {
Rectangle { Rectangle {
id: shadowLayer2 id: shadowLayer2
anchors.fill: parent anchors.fill: parent
anchors.margins: -2 anchors.margins: -2
color: "transparent" color: "transparent"
@@ -102,6 +154,7 @@ PanelWindow {
Rectangle { Rectangle {
id: shadowLayer3 id: shadowLayer3
anchors.fill: parent anchors.fill: parent
color: "transparent" color: "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
@@ -133,11 +186,14 @@ PanelWindow {
position: 0.021 position: 0.021
color: "transparent" color: "transparent"
} }
} }
} }
Item { Item {
id: notificationContent id: notificationContent
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@@ -148,6 +204,7 @@ PanelWindow {
Rectangle { Rectangle {
id: iconContainer id: iconContainer
readonly property bool hasNotificationImage: notificationData && notificationData.image && notificationData.image !== "" readonly property bool hasNotificationImage: notificationData && notificationData.image && notificationData.image !== ""
width: 55 width: 55
@@ -161,12 +218,14 @@ PanelWindow {
IconImage { IconImage {
id: iconImage id: iconImage
anchors.fill: parent anchors.fill: parent
anchors.margins: 2 anchors.margins: 2
asynchronous: true asynchronous: true
source: { source: {
if (!notificationData) return ""; if (!notificationData)
return "";
if (parent.hasNotificationImage) if (parent.hasNotificationImage)
return notificationData.cleanImage || ""; return notificationData.cleanImage || "";
@@ -193,10 +252,12 @@ PanelWindow {
font.weight: Font.Bold font.weight: Font.Bold
color: Theme.primaryText color: Theme.primaryText
} }
} }
Rectangle { Rectangle {
id: textContainer id: textContainer
anchors.left: iconContainer.right anchors.left: iconContainer.right
anchors.leftMargin: 12 anchors.leftMargin: 12
anchors.right: parent.right anchors.right: parent.right
@@ -219,7 +280,9 @@ PanelWindow {
StyledText { StyledText {
width: parent.width width: parent.width
text: { text: {
if (!notificationData) return ""; if (!notificationData)
return "";
const appName = notificationData.appName || ""; const appName = notificationData.appName || "";
const timeStr = notificationData.timeStr || ""; const timeStr = notificationData.timeStr || "";
if (timeStr.length > 0) if (timeStr.length > 0)
@@ -255,22 +318,29 @@ PanelWindow {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
visible: text.length > 0 visible: text.length > 0
linkColor: Theme.primary linkColor: Theme.primary
onLinkActivated: (link) => Qt.openUrlExternally(link) onLinkActivated: (link) => {
return Qt.openUrlExternally(link);
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }
} }
} }
} }
} }
DankActionButton { DankActionButton {
id: closeButton id: closeButton
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 12 anchors.topMargin: 12
@@ -282,6 +352,7 @@ PanelWindow {
onClicked: { onClicked: {
if (notificationData && !win.exiting) if (notificationData && !win.exiting)
notificationData.popup = false; notificationData.popup = false;
} }
} }
@@ -292,20 +363,21 @@ PanelWindow {
anchors.bottomMargin: 8 anchors.bottomMargin: 8
spacing: 8 spacing: 8
z: 20 z: 20
Repeater { Repeater {
model: notificationData ? (notificationData.actions || []) : [] model: notificationData ? (notificationData.actions || []) : []
Rectangle { Rectangle {
property bool isHovered: false property bool isHovered: false
width: Math.max(actionText.implicitWidth + 12, 50) width: Math.max(actionText.implicitWidth + 12, 50)
height: 24 height: 24
radius: 4 radius: 4
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent" color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText { StyledText {
id: actionText id: actionText
text: modelData.text || "" text: modelData.text || ""
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -313,7 +385,7 @@ PanelWindow {
anchors.centerIn: parent anchors.centerIn: parent
elide: Text.ElideRight elide: Text.ElideRight
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
@@ -322,20 +394,24 @@ PanelWindow {
onEntered: parent.isHovered = true onEntered: parent.isHovered = true
onExited: parent.isHovered = false onExited: parent.isHovered = false
onClicked: { onClicked: {
if (modelData && modelData.invoke) { if (modelData && modelData.invoke)
modelData.invoke(); modelData.invoke();
}
if (notificationData && !win.exiting) { if (notificationData && !win.exiting)
notificationData.popup = false; notificationData.popup = false;
}
} }
} }
} }
} }
} }
Rectangle { Rectangle {
id: clearButton id: clearButton
property bool isHovered: false property bool isHovered: false
anchors.right: parent.right anchors.right: parent.right
@@ -350,6 +426,7 @@ PanelWindow {
StyledText { StyledText {
id: clearText id: clearText
text: "Clear" text: "Clear"
color: clearButton.isHovered ? Theme.primary : Theme.surfaceVariantText color: clearButton.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -365,15 +442,17 @@ PanelWindow {
onEntered: clearButton.isHovered = true onEntered: clearButton.isHovered = true
onExited: clearButton.isHovered = false onExited: clearButton.isHovered = false
onClicked: { onClicked: {
if (notificationData && !win.exiting) { if (notificationData && !win.exiting)
NotificationService.dismissNotification(notificationData); NotificationService.dismissNotification(notificationData);
}
} }
} }
} }
MouseArea { MouseArea {
id: cardHoverArea id: cardHoverArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
@@ -382,153 +461,146 @@ PanelWindow {
onEntered: { onEntered: {
if (notificationData && notificationData.timer) if (notificationData && notificationData.timer)
notificationData.timer.stop(); notificationData.timer.stop();
} }
onExited: { onExited: {
if (notificationData && notificationData.popup && notificationData.timer) if (notificationData && notificationData.popup && notificationData.timer)
notificationData.timer.restart(); notificationData.timer.restart();
} }
onClicked: { onClicked: {
if (notificationData && !win.exiting) if (notificationData && !win.exiting)
notificationData.popup = false; notificationData.popup = false;
} }
} }
} }
transform: Translate {
id: tx
x: Anims.slidePx
}
} }
NumberAnimation { NumberAnimation {
id: enterX id: enterX
target: tx; property: "x"; from: Anims.slidePx; to: 0
target: tx
property: "x"
from: Anims.slidePx
to: 0
duration: Anims.durMed duration: Anims.durMed
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasizedDecel easing.bezierCurve: Anims.emphasizedDecel
onStopped: if (!win.exiting && !win._isDestroying && Math.abs(tx.x) < 0.5) win.entered(); onStopped: {
if (!win.exiting && !win._isDestroying && Math.abs(tx.x) < 0.5) {
win.entered();
}
}
} }
ParallelAnimation { ParallelAnimation {
id: exitAnim id: exitAnim
PropertyAnimation {
target: tx; property: "x"; from: 0; to: Anims.slidePx onStopped: finalizeExit("animStopped")
PropertyAnimation {
target: tx
property: "x"
from: 0
to: Anims.slidePx
duration: Anims.durShort duration: Anims.durShort
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasizedAccel easing.bezierCurve: Anims.emphasizedAccel
} }
NumberAnimation {
target: content; property: "opacity"; from: 1; to: 0 NumberAnimation {
target: content
property: "opacity"
from: 1
to: 0
duration: Anims.durShort duration: Anims.durShort
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standardAccel easing.bezierCurve: Anims.standardAccel
} }
NumberAnimation {
target: content; property: "scale"; from: 1; to: 0.98 NumberAnimation {
target: content
property: "scale"
from: 1
to: 0.98
duration: Anims.durShort duration: Anims.durShort
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasizedAccel easing.bezierCurve: Anims.emphasizedAccel
} }
onStopped: finalizeExit("animStopped")
}
Component.onCompleted: {
if (hasValidData) {
Qt.callLater(() => enterX.restart())
} else {
console.warn("NotificationPopup created with invalid data");
forceExit();
}
} }
Connections { Connections {
id: wrapperConn id: wrapperConn
function onPopupChanged() {
if (!win.notificationData || win._isDestroying)
return ;
if (!win.notificationData.popup && !win.exiting)
startExit();
}
target: win.notificationData || null target: win.notificationData || null
ignoreUnknownSignals: true ignoreUnknownSignals: true
enabled: !win._isDestroying enabled: !win._isDestroying
function onPopupChanged() {
if (!win.notificationData || win._isDestroying) return;
if (!win.notificationData.popup && !win.exiting) {
startExit();
}
}
} }
Connections { Connections {
id: notificationConn id: notificationConn
function onDropped() {
if (!win._isDestroying && !win.exiting)
forceExit();
}
target: (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null target: (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null
ignoreUnknownSignals: true ignoreUnknownSignals: true
enabled: !win._isDestroying enabled: !win._isDestroying
function onDropped() {
if (!win._isDestroying && !win.exiting) {
forceExit();
}
}
}
onNotificationDataChanged: {
if (!_isDestroying) {
wrapperConn.target = win.notificationData || null;
notificationConn.target = (win.notificationData && win.notificationData.notification && win.notificationData.notification.Retainable) || null;
}
} }
Timer { Timer {
id: enterDelay id: enterDelay
interval: 160 interval: 160
repeat: false repeat: false
onTriggered: { onTriggered: {
if (notificationData && notificationData.timer && !exiting && !_isDestroying) if (notificationData && notificationData.timer && !exiting && !_isDestroying)
notificationData.timer.start(); notificationData.timer.start();
} }
} }
onEntered: { Timer {
if (!_isDestroying) enterDelay.start();
}
function startExit() {
if (exiting || _isDestroying) return;
exiting = true;
exitAnim.restart();
exitWatchdog.restart();
if (NotificationService.removeFromVisibleNotifications) {
NotificationService.removeFromVisibleNotifications(win.notificationData);
}
}
function forceExit() {
if (_isDestroying) return;
_isDestroying = true;
exiting = true;
visible = false;
exitWatchdog.stop();
finalizeExit("forced");
}
function finalizeExit(reason) {
if (_finalized) return;
_finalized = true;
_isDestroying = true;
exitWatchdog.stop();
wrapperConn.enabled = false;
wrapperConn.target = null;
win.exitFinished();
}
Timer {
id: exitWatchdog id: exitWatchdog
interval: 600 interval: 600
repeat: false repeat: false
onTriggered: finalizeExit("watchdog") onTriggered: finalizeExit("watchdog")
} }
Component.onDestruction: { Behavior on screenY {
_isDestroying = true; id: screenYAnim
exitWatchdog.stop();
if (notificationData && notificationData.timer) { enabled: !exiting && !_isDestroying
notificationData.timer.stop();
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.standardDecel
} }
} }
}
}

View File

@@ -5,61 +5,113 @@ import qs.Services
QtObject { QtObject {
id: manager id: manager
property var modelData property var modelData
property int topMargin: 0 property int topMargin: 0
property int baseNotificationHeight: 120 property int baseNotificationHeight: 120
property int maxTargetNotifications: 3 property int maxTargetNotifications: 3
property var popupWindows: [] // strong refs to windows (live until exitFinished) property var popupWindows: [] // strong refs to windows (live until exitFinished)
// Track destroying windows to prevent duplicate cleanup // Track destroying windows to prevent duplicate cleanup
property var destroyingWindows: new Set() property var destroyingWindows: new Set()
// Factory // Factory
property Component popupComponent: Component { property Component popupComponent
popupComponent: Component {
NotificationPopup { NotificationPopup {
onEntered: manager._onPopupEntered(this) onEntered: manager._onPopupEntered(this)
onExitFinished: manager._onPopupExitFinished(this) onExitFinished: manager._onPopupExitFinished(this)
} }
} }
property Connections notificationConnections: Connections { property Connections notificationConnections
target: NotificationService
notificationConnections: Connections {
function onVisibleNotificationsChanged() { function onVisibleNotificationsChanged() {
manager._sync(NotificationService.visibleNotifications); manager._sync(NotificationService.visibleNotifications);
} }
target: NotificationService
}
// Smart sweeper that only runs when needed
property Timer sweeper
sweeper: Timer {
interval: 2000
running: false // Not running by default
repeat: true
onTriggered: {
let toRemove = [];
for (let p of popupWindows) {
if (!p) {
toRemove.push(p);
continue;
}
// Check for various zombie conditions
const isZombie = p.status === Component.Null || (!p.visible && !p.exiting) || (!p.notificationData && !p._isDestroying) || (!p.hasValidData && !p._isDestroying);
if (isZombie) {
console.warn("Sweeper found zombie window, cleaning up");
toRemove.push(p);
// Try to force cleanup
if (p.forceExit) {
p.forceExit();
} else if (p.destroy) {
try {
p.destroy();
} catch (e) {
console.warn("Error destroying zombie:", e);
}
}
}
}
// Remove all zombies from array
if (toRemove.length > 0) {
for (let zombie of toRemove) {
const i = popupWindows.indexOf(zombie);
if (i !== -1)
popupWindows.splice(i, 1);
}
popupWindows = popupWindows.slice();
// Recompact after cleanup
const survivors = _active().sort((a, b) => {
return a.screenY - b.screenY;
});
for (let k = 0; k < survivors.length; ++k) {
survivors[k].screenY = topMargin + k * baseNotificationHeight;
}
}
// Stop the timer if no windows remain
if (popupWindows.length === 0)
sweeper.stop();
}
} }
function _hasWindowFor(w) { function _hasWindowFor(w) {
return popupWindows.some(p => { return popupWindows.some((p) => {
// More robust check for valid windows // More robust check for valid windows
return p && return p && p.notificationData === w && !p._isDestroying && p.status !== Component.Null;
p.notificationData === w &&
!p._isDestroying &&
p.status !== Component.Null;
}); });
} }
function _isValidWindow(p) { function _isValidWindow(p) {
return p && return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
p.status !== Component.Null &&
!p._isDestroying &&
p.hasValidData;
} }
function _sync(newWrappers) { function _sync(newWrappers) {
// Add new notifications // Add new notifications
for (let w of newWrappers) { for (let w of newWrappers) {
if (w && !_hasWindowFor(w)) { if (w && !_hasWindowFor(w))
insertNewestAtTop(w); insertNewestAtTop(w);
}
} }
// Remove old notifications // Remove old notifications
for (let p of popupWindows.slice()) { for (let p of popupWindows.slice()) {
if (!_isValidWindow(p)) continue; if (!_isValidWindow(p))
continue;
if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1 && !p.exiting) { if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1 && !p.exiting) {
p.notificationData.removedByLimit = true; p.notificationData.removedByLimit = true;
p.notificationData.popup = false; p.notificationData.popup = false;
@@ -71,73 +123,67 @@ QtObject {
function insertNewestAtTop(wrapper) { function insertNewestAtTop(wrapper) {
if (!wrapper) { if (!wrapper) {
console.warn("insertNewestAtTop: wrapper is null"); console.warn("insertNewestAtTop: wrapper is null");
return; return ;
} }
// Shift live, non-exiting windows down *now* // Shift live, non-exiting windows down *now*
for (let p of popupWindows) { for (let p of popupWindows) {
if (!_isValidWindow(p)) continue; if (!_isValidWindow(p))
if (p.exiting) continue; continue;
if (p.exiting)
continue;
p.screenY = p.screenY + baseNotificationHeight; p.screenY = p.screenY + baseNotificationHeight;
} }
// Create the new top window at fixed Y // Create the new top window at fixed Y
const notificationId = wrapper && wrapper.notification ? wrapper.notification.id : ""; const notificationId = wrapper && wrapper.notification ? wrapper.notification.id : "";
const win = popupComponent.createObject(null, { const win = popupComponent.createObject(null, {
notificationData: wrapper, "notificationData": wrapper,
notificationId: notificationId, "notificationId": notificationId,
screenY: topMargin, "screenY": topMargin,
screen: manager.modelData "screen": manager.modelData
}); });
if (!win) {
if (!win) { console.warn("Popup create failed");
console.warn("Popup create failed"); return ;
return;
} }
// Validate the window was created properly // Validate the window was created properly
if (!win.hasValidData) { if (!win.hasValidData) {
console.warn("Popup created with invalid data, destroying"); console.warn("Popup created with invalid data, destroying");
win.destroy(); win.destroy();
return; return ;
} }
popupWindows.push(win); popupWindows.push(win);
// Start sweeper if it's not running // Start sweeper if it's not running
if (!sweeper.running) { if (!sweeper.running)
sweeper.start(); sweeper.start();
}
_maybeStartOverflow(); _maybeStartOverflow();
} }
// Overflow: keep one extra (slot #4), then ask bottom to exit gracefully // Overflow: keep one extra (slot #4), then ask bottom to exit gracefully
function _active() { function _active() {
return popupWindows.filter(p => { return popupWindows.filter((p) => {
return _isValidWindow(p) && return _isValidWindow(p) && p.notificationData && p.notificationData.popup && !p.exiting;
p.notificationData &&
p.notificationData.popup &&
!p.exiting;
}); });
} }
function _bottom() { function _bottom() {
let b = null, maxY = -1; let b = null, maxY = -1;
for (let p of _active()) { for (let p of _active()) {
if (p.screenY > maxY) { if (p.screenY > maxY) {
maxY = p.screenY; maxY = p.screenY;
b = p; b = p;
} }
} }
return b; return b;
} }
function _maybeStartOverflow() { function _maybeStartOverflow() {
const activeWindows = _active(); const activeWindows = _active();
if (activeWindows.length <= maxTargetNotifications + 1) return; if (activeWindows.length <= maxTargetNotifications + 1)
return ;
const b = _bottom(); const b = _bottom();
if (b && !b.exiting) { if (b && !b.exiting) {
// Tell the popup to animate out (don't destroy here) // Tell the popup to animate out (don't destroy here)
@@ -148,34 +194,32 @@ QtObject {
// After entrance, you may kick overflow (optional) // After entrance, you may kick overflow (optional)
function _onPopupEntered(p) { function _onPopupEntered(p) {
if (_isValidWindow(p)) { if (_isValidWindow(p))
_maybeStartOverflow(); _maybeStartOverflow();
}
} }
// Primary cleanup path (after the popup finishes its exit) // Primary cleanup path (after the popup finishes its exit)
function _onPopupExitFinished(p) { function _onPopupExitFinished(p) {
if (!p) return; if (!p)
return ;
// Prevent duplicate cleanup // Prevent duplicate cleanup
const windowId = p.toString(); const windowId = p.toString();
if (destroyingWindows.has(windowId)) { if (destroyingWindows.has(windowId))
return; return ;
}
destroyingWindows.add(windowId); destroyingWindows.add(windowId);
// Remove from popupWindows // Remove from popupWindows
const i = popupWindows.indexOf(p); const i = popupWindows.indexOf(p);
if (i !== -1) { if (i !== -1) {
popupWindows.splice(i, 1); popupWindows.splice(i, 1);
popupWindows = popupWindows.slice(); popupWindows = popupWindows.slice();
} }
// Release the wrapper // Release the wrapper
if (NotificationService.releaseWrapper && p.notificationData) { if (NotificationService.releaseWrapper && p.notificationData)
NotificationService.releaseWrapper(p.notificationData); NotificationService.releaseWrapper(p.notificationData);
}
// Schedule destruction // Schedule destruction
Qt.callLater(() => { Qt.callLater(() => {
if (p && p.destroy) { if (p && p.destroy) {
@@ -190,103 +234,40 @@ QtObject {
destroyingWindows.delete(windowId); destroyingWindows.delete(windowId);
}); });
}); });
// Compact survivors (only live, non-exiting) // Compact survivors (only live, non-exiting)
const survivors = _active().sort((a, b) => a.screenY - b.screenY); const survivors = _active().sort((a, b) => {
return a.screenY - b.screenY;
});
for (let k = 0; k < survivors.length; ++k) { for (let k = 0; k < survivors.length; ++k) {
survivors[k].screenY = topMargin + k * baseNotificationHeight; survivors[k].screenY = topMargin + k * baseNotificationHeight;
} }
_maybeStartOverflow(); _maybeStartOverflow();
} }
// Smart sweeper that only runs when needed
property Timer sweeper: Timer {
interval: 2000
running: false // Not running by default
repeat: true
onTriggered: {
let toRemove = [];
for (let p of popupWindows) {
if (!p) {
toRemove.push(p);
continue;
}
// Check for various zombie conditions
const isZombie =
p.status === Component.Null ||
(!p.visible && !p.exiting) ||
(!p.notificationData && !p._isDestroying) ||
(!p.hasValidData && !p._isDestroying);
if (isZombie) {
console.warn("Sweeper found zombie window, cleaning up");
toRemove.push(p);
// Try to force cleanup
if (p.forceExit) {
p.forceExit();
} else if (p.destroy) {
try {
p.destroy();
} catch (e) {
console.warn("Error destroying zombie:", e);
}
}
}
}
// Remove all zombies from array
if (toRemove.length > 0) {
for (let zombie of toRemove) {
const i = popupWindows.indexOf(zombie);
if (i !== -1) {
popupWindows.splice(i, 1);
}
}
popupWindows = popupWindows.slice();
// Recompact after cleanup
const survivors = _active().sort((a, b) => a.screenY - b.screenY);
for (let k = 0; k < survivors.length; ++k) {
survivors[k].screenY = topMargin + k * baseNotificationHeight;
}
}
// Stop the timer if no windows remain
if (popupWindows.length === 0) {
sweeper.stop();
}
}
}
// Watch for changes to popup windows array
onPopupWindowsChanged: {
if (popupWindows.length > 0 && !sweeper.running) {
sweeper.start();
} else if (popupWindows.length === 0 && sweeper.running) {
sweeper.stop();
}
}
// Emergency cleanup function // Emergency cleanup function
function cleanupAllWindows() { function cleanupAllWindows() {
sweeper.stop(); sweeper.stop();
for (let p of popupWindows.slice()) { for (let p of popupWindows.slice()) {
if (p) { if (p) {
try { try {
if (p.forceExit) p.forceExit(); if (p.forceExit)
else if (p.destroy) p.destroy(); p.forceExit();
else if (p.destroy)
p.destroy();
} catch (e) { } catch (e) {
console.warn("Error during emergency cleanup:", e); console.warn("Error during emergency cleanup:", e);
} }
} }
} }
popupWindows = []; popupWindows = [];
destroyingWindows.clear(); destroyingWindows.clear();
} }
}
// Watch for changes to popup windows array
onPopupWindowsChanged: {
if (popupWindows.length > 0 && !sweeper.running)
sweeper.start();
else if (popupWindows.length === 0 && sweeper.running)
sweeper.stop();
}
}

View File

@@ -5,21 +5,6 @@ import qs.Services
import qs.Widgets import qs.Widgets
Column { Column {
anchors.fill: parent
spacing: Theme.spacingM
Component.onCompleted: {
SysMonitorService.addRef();
SysMonitorService.addRef();
// Trigger immediate updates for both services
SysMonitorService.updateAllStats();
}
Component.onDestruction: {
SysMonitorService.removeRef();
SysMonitorService.removeRef();
}
function formatNetworkSpeed(bytesPerSec) { function formatNetworkSpeed(bytesPerSec) {
if (bytesPerSec < 1024) if (bytesPerSec < 1024)
return bytesPerSec.toFixed(0) + " B/s"; return bytesPerSec.toFixed(0) + " B/s";
@@ -40,6 +25,19 @@ Column {
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s"; return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
} }
anchors.fill: parent
spacing: Theme.spacingM
Component.onCompleted: {
SysMonitorService.addRef();
SysMonitorService.addRef();
// Trigger immediate updates for both services
SysMonitorService.updateAllStats();
}
Component.onDestruction: {
SysMonitorService.removeRef();
SysMonitorService.removeRef();
}
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 200 height: 200
@@ -279,9 +277,7 @@ Column {
} }
StyledText { StyledText {
text: SysMonitorService.totalSwapKB > 0 ? text: SysMonitorService.totalSwapKB > 0 ? SysMonitorService.formatSystemMemory(SysMonitorService.usedSwapKB) + " / " + SysMonitorService.formatSystemMemory(SysMonitorService.totalSwapKB) : "No swap configured"
SysMonitorService.formatSystemMemory(SysMonitorService.usedSwapKB) + " / " + SysMonitorService.formatSystemMemory(SysMonitorService.totalSwapKB) :
"No swap configured"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
@@ -309,10 +305,16 @@ Column {
height: parent.height height: parent.height
radius: parent.radius radius: parent.radius
color: { color: {
if (!SysMonitorService.totalSwapKB) return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3); if (!SysMonitorService.totalSwapKB)
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3);
const usage = SysMonitorService.usedSwapKB / SysMonitorService.totalSwapKB; const usage = SysMonitorService.usedSwapKB / SysMonitorService.totalSwapKB;
if (usage > 0.9) return Theme.error; if (usage > 0.9)
if (usage > 0.7) return Theme.warning; return Theme.error;
if (usage > 0.7)
return Theme.warning;
return Theme.info; return Theme.info;
} }
@@ -320,8 +322,11 @@ Column {
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
} }
} }
} }
} }
StyledText { StyledText {

View File

@@ -12,24 +12,20 @@ Popup {
property var processData: null property var processData: null
function show(x, y) { function show(x, y) {
if (!processContextMenu.parent && typeof Overlay !== "undefined" && Overlay.overlay) { if (!processContextMenu.parent && typeof Overlay !== "undefined" && Overlay.overlay)
processContextMenu.parent = Overlay.overlay; processContextMenu.parent = Overlay.overlay;
}
const menuWidth = 180; const menuWidth = 180;
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2; const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
const screenWidth = Screen.width; const screenWidth = Screen.width;
const screenHeight = Screen.height; const screenHeight = Screen.height;
let finalX = x; let finalX = x;
let finalY = y; let finalY = y;
if (x + menuWidth > screenWidth - 20)
if (x + menuWidth > screenWidth - 20) {
finalX = x - menuWidth; finalX = x - menuWidth;
}
if (y + menuHeight > screenHeight - 20) { if (y + menuHeight > screenHeight - 20)
finalY = y - menuHeight; finalY = y - menuHeight;
}
processContextMenu.x = Math.max(20, finalX); processContextMenu.x = Math.max(20, finalX);
processContextMenu.y = Math.max(20, finalY); processContextMenu.y = Math.max(20, finalY);
@@ -41,29 +37,29 @@ Popup {
padding: 0 padding: 0
modal: false modal: false
closePolicy: Popup.CloseOnEscape closePolicy: Popup.CloseOnEscape
onClosed: { onClosed: {
closePolicy = Popup.CloseOnEscape; closePolicy = Popup.CloseOnEscape;
} }
onOpened: { onOpened: {
outsideClickTimer.start(); outsideClickTimer.start();
} }
Timer { Timer {
id: outsideClickTimer id: outsideClickTimer
interval: 100 interval: 100
onTriggered: { onTriggered: {
processContextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside; processContextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside;
} }
} }
background: Rectangle { background: Rectangle {
color: "transparent" color: "transparent"
} }
contentItem: Rectangle { contentItem: Rectangle {
id: menuContent id: menuContent
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
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)
@@ -71,6 +67,7 @@ Popup {
Column { Column {
id: menuColumn id: menuColumn
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
spacing: 1 spacing: 1
@@ -93,16 +90,18 @@ Popup {
MouseArea { MouseArea {
id: copyPidArea id: copyPidArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (processContextMenu.processData) { if (processContextMenu.processData)
Quickshell.execDetached(["wl-copy", processContextMenu.processData.pid.toString()]); Quickshell.execDetached(["wl-copy", processContextMenu.processData.pid.toString()]);
}
processContextMenu.close(); processContextMenu.close();
} }
} }
} }
Rectangle { Rectangle {
@@ -123,6 +122,7 @@ Popup {
MouseArea { MouseArea {
id: copyNameArea id: copyNameArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -134,6 +134,7 @@ Popup {
processContextMenu.close(); processContextMenu.close();
} }
} }
} }
Rectangle { Rectangle {
@@ -148,6 +149,7 @@ Popup {
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
} }
Rectangle { Rectangle {
@@ -170,17 +172,19 @@ Popup {
MouseArea { MouseArea {
id: killArea id: killArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: { onClicked: {
if (processContextMenu.processData) { if (processContextMenu.processData)
Quickshell.execDetached(["kill", processContextMenu.processData.pid.toString()]); Quickshell.execDetached(["kill", processContextMenu.processData.pid.toString()]);
}
processContextMenu.close(); processContextMenu.close();
} }
} }
} }
Rectangle { Rectangle {
@@ -203,18 +207,23 @@ Popup {
MouseArea { MouseArea {
id: forceKillArea id: forceKillArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: { onClicked: {
if (processContextMenu.processData) { if (processContextMenu.processData)
Quickshell.execDetached(["kill", "-9", processContextMenu.processData.pid.toString()]); Quickshell.execDetached(["kill", "-9", processContextMenu.processData.pid.toString()]);
}
processContextMenu.close(); processContextMenu.close();
} }
} }
} }
} }
} }
}
}

View File

@@ -144,10 +144,10 @@ Rectangle {
if (process && process.memoryKB > 1024 * 1024) if (process && process.memoryKB > 1024 * 1024)
return Theme.error; return Theme.error;
if (process && process.memoryKB > 512 * 1024) if (process && process.memoryKB > 512 * 1024)
return Theme.warning; return Theme.warning;
return Theme.surfaceText; return Theme.surfaceText;
} }
anchors.centerIn: parent anchors.centerIn: parent
} }
@@ -212,4 +212,4 @@ Rectangle {
} }
} }

View File

@@ -7,9 +7,9 @@ import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modules.ProcessList
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.ProcessList
PanelWindow { PanelWindow {
id: processListPopout id: processListPopout
@@ -20,9 +20,9 @@ PanelWindow {
function hide() { function hide() {
isVisible = false; isVisible = false;
// Close any open context menus // Close any open context menus
if (processContextMenu.visible) { if (processContextMenu.visible)
processContextMenu.close(); processContextMenu.close();
}
} }
function show() { function show() {
@@ -37,11 +37,6 @@ PanelWindow {
} }
visible: isVisible visible: isVisible
Ref {
service: SysMonitorService
}
implicitWidth: 600 implicitWidth: 600
implicitHeight: 600 implicitHeight: 600
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
@@ -49,6 +44,10 @@ PanelWindow {
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
Ref {
service: SysMonitorService
}
anchors { anchors {
top: true top: true
left: true left: true
@@ -61,58 +60,58 @@ PanelWindow {
onClicked: function(mouse) { onClicked: function(mouse) {
// Only close if click is outside the content loader // Only close if click is outside the content loader
var localPos = mapToItem(contentLoader, mouse.x, mouse.y); var localPos = mapToItem(contentLoader, mouse.x, mouse.y);
if (localPos.x < 0 || localPos.x > contentLoader.width || if (localPos.x < 0 || localPos.x > contentLoader.width || localPos.y < 0 || localPos.y > contentLoader.height)
localPos.y < 0 || localPos.y > contentLoader.height) {
processListPopout.hide(); processListPopout.hide();
}
} }
} }
Loader { Loader {
id: contentLoader id: contentLoader
asynchronous: true
active: processListPopout.isVisible
readonly property real targetWidth: Math.min(600, Screen.width - Theme.spacingL * 2) readonly property real targetWidth: Math.min(600, Screen.width - Theme.spacingL * 2)
readonly property real targetHeight: Math.min(600, Screen.height - Theme.barHeight - Theme.spacingS * 2) readonly property real targetHeight: Math.min(600, Screen.height - Theme.barHeight - Theme.spacingS * 2)
asynchronous: true
active: processListPopout.isVisible
width: targetWidth width: targetWidth
height: targetHeight height: targetHeight
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL) x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
// GPU-accelerated scale + opacity animation // GPU-accelerated scale + opacity animation
opacity: processListPopout.isVisible ? 1 : 0 opacity: processListPopout.isVisible ? 1 : 0
scale: processListPopout.isVisible ? 1 : 0.9 scale: processListPopout.isVisible ? 1 : 0.9
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Anims.durMed duration: Anims.durMed
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized easing.bezierCurve: Anims.emphasized
} }
} }
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
duration: Anims.durMed duration: Anims.durMed
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized easing.bezierCurve: Anims.emphasized
} }
} }
sourceComponent: Rectangle { sourceComponent: Rectangle {
id: dropdownContent id: dropdownContent
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: Theme.popupBackground() color: Theme.popupBackground()
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
clip: true clip: true
// Remove layer rendering for better performance // Remove layer rendering for better performance
antialiasing: true antialiasing: true
smooth: true smooth: true
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
@@ -128,9 +127,11 @@ PanelWindow {
SystemOverview { SystemOverview {
id: systemOverview id: systemOverview
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2 width: parent.width - Theme.spacingM * 2
} }
} }
Rectangle { Rectangle {
@@ -146,12 +147,17 @@ PanelWindow {
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
contextMenu: processContextMenu contextMenu: processContextMenu
} }
} }
} }
} }
} }
ProcessContextMenu { ProcessContextMenu {
id: processContextMenu id: processContextMenu
} }
}
}

View File

@@ -6,12 +6,12 @@ import qs.Widgets
Column { Column {
id: root id: root
property var contextMenu: null property var contextMenu: null
Component.onCompleted: { Component.onCompleted: {
SysMonitorService.addRef(); SysMonitorService.addRef();
} }
Component.onDestruction: { Component.onDestruction: {
SysMonitorService.removeRef(); SysMonitorService.removeRef();
} }
@@ -31,19 +31,20 @@ Column {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 0 anchors.leftMargin: 0
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
text: "Process" text: "Process"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily font.family: Prefs.monoFontFamily
font.weight: SysMonitorService.sortBy === "name" ? Font.Bold : Font.Medium font.weight: SysMonitorService.sortBy === "name" ? Font.Bold : Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
opacity: SysMonitorService.sortBy === "name" ? 1.0 : 0.7 opacity: SysMonitorService.sortBy === "name" ? 1 : 0.7
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: processHeaderArea id: processHeaderArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -53,10 +54,14 @@ Column {
processListView.restoreAnchor(); processListView.restoreAnchor();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { duration: Theme.shortDuration } ColorAnimation {
duration: Theme.shortDuration
}
} }
} }
Rectangle { Rectangle {
@@ -74,12 +79,13 @@ Column {
font.family: Prefs.monoFontFamily font.family: Prefs.monoFontFamily
font.weight: SysMonitorService.sortBy === "cpu" ? Font.Bold : Font.Medium font.weight: SysMonitorService.sortBy === "cpu" ? Font.Bold : Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
opacity: SysMonitorService.sortBy === "cpu" ? 1.0 : 0.7 opacity: SysMonitorService.sortBy === "cpu" ? 1 : 0.7
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: cpuHeaderArea id: cpuHeaderArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -89,10 +95,14 @@ Column {
processListView.restoreAnchor(); processListView.restoreAnchor();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { duration: Theme.shortDuration } ColorAnimation {
duration: Theme.shortDuration
}
} }
} }
Rectangle { Rectangle {
@@ -110,12 +120,13 @@ Column {
font.family: Prefs.monoFontFamily font.family: Prefs.monoFontFamily
font.weight: SysMonitorService.sortBy === "memory" ? Font.Bold : Font.Medium font.weight: SysMonitorService.sortBy === "memory" ? Font.Bold : Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
opacity: SysMonitorService.sortBy === "memory" ? 1.0 : 0.7 opacity: SysMonitorService.sortBy === "memory" ? 1 : 0.7
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: memoryHeaderArea id: memoryHeaderArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -125,10 +136,14 @@ Column {
processListView.restoreAnchor(); processListView.restoreAnchor();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { duration: Theme.shortDuration } ColorAnimation {
duration: Theme.shortDuration
}
} }
} }
Rectangle { Rectangle {
@@ -139,20 +154,21 @@ Column {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 53 anchors.rightMargin: 53
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
text: "PID" text: "PID"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily font.family: Prefs.monoFontFamily
font.weight: SysMonitorService.sortBy === "pid" ? Font.Bold : Font.Medium font.weight: SysMonitorService.sortBy === "pid" ? Font.Bold : Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
opacity: SysMonitorService.sortBy === "pid" ? 1.0 : 0.7 opacity: SysMonitorService.sortBy === "pid" ? 1 : 0.7
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: pidHeaderArea id: pidHeaderArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -162,10 +178,14 @@ Column {
processListView.restoreAnchor(); processListView.restoreAnchor();
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { duration: Theme.shortDuration } ColorAnimation {
duration: Theme.shortDuration
}
} }
} }
Rectangle { Rectangle {
@@ -214,6 +234,43 @@ Column {
property real stableY: 0 property real stableY: 0
property bool isUserScrolling: false property bool isUserScrolling: false
property bool isScrollBarDragging: false property bool isScrollBarDragging: false
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
property string keyRoleName: "pid"
property var _anchorKey: undefined
property real _anchorOffset: 0
function captureAnchor() {
const y = contentY + 1;
const idx = indexAt(0, y);
if (idx < 0 || !model || idx >= model.length)
return ;
_anchorKey = model[idx][keyRoleName];
const it = itemAtIndex(idx);
_anchorOffset = it ? (y - it.y) : 0;
}
function restoreAnchor() {
Qt.callLater(function() {
if (_anchorKey === undefined || !model)
return ;
var i = -1;
for (var j = 0; j < model.length; ++j) {
if (model[j][keyRoleName] === _anchorKey) {
i = j;
break;
}
}
if (i < 0)
return ;
positionViewAtIndex(i, ListView.Beginning);
const maxY = Math.max(0, contentHeight - height);
contentY = Math.max(0, Math.min(maxY, contentY + _anchorOffset - 1));
});
}
width: parent.width width: parent.width
height: parent.height - columnHeaders.height height: parent.height - columnHeaders.height
@@ -223,18 +280,37 @@ Column {
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
flickDeceleration: 1500 flickDeceleration: 1500
maximumFlickVelocity: 2000 maximumFlickVelocity: 2000
onMovementStarted: isUserScrolling = true onMovementStarted: isUserScrolling = true
onMovementEnded: { onMovementEnded: {
isUserScrolling = false isUserScrolling = false;
if (contentY > 40) { if (contentY > 40)
stableY = contentY stableY = contentY;
}
}
onContentYChanged: {
if (!isUserScrolling && !isScrollBarDragging && visible && stableY > 40 && Math.abs(contentY - stableY) > 10)
contentY = stableY;
}
onModelChanged: {
if (model && model.length > 0 && !isUserScrolling && stableY > 40)
// Preserve scroll position when model updates
Qt.callLater(function() {
contentY = stableY;
});
} }
onContentYChanged: { WheelHandler {
if (!isUserScrolling && !isScrollBarDragging && visible && stableY > 40 && Math.abs(contentY - stableY) > 10) { target: null
contentY = stableY onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0 ? ev.pixelDelta.y : (ev.angleDelta.y / 120) * processListView.wheelBaseStep;
if (ev.inverted)
dy = -dy;
const maxY = Math.max(0, processListView.contentHeight - processListView.height);
processListView.contentY = Math.max(0, Math.min(maxY, processListView.contentY - dy * processListView.wheelMultiplier));
ev.accepted = true;
} }
} }
@@ -243,74 +319,22 @@ Column {
contextMenu: root.contextMenu contextMenu: root.contextMenu
} }
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
id: verticalScrollBar id: verticalScrollBar
policy: ScrollBar.AsNeeded
policy: ScrollBar.AsNeeded
onPressedChanged: { onPressedChanged: {
processListView.isScrollBarDragging = pressed processListView.isScrollBarDragging = pressed;
if (!pressed && processListView.contentY > 40) { if (!pressed && processListView.contentY > 40)
processListView.stableY = processListView.contentY processListView.stableY = processListView.contentY;
}
}
}
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * processListView.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, processListView.contentHeight - processListView.height);
processListView.contentY = Math.max(0, Math.min(maxY,
processListView.contentY - dy * processListView.wheelMultiplier));
ev.accepted = true;
} }
} }
property string keyRoleName: "pid" ScrollBar.horizontal: ScrollBar {
property var _anchorKey: undefined policy: ScrollBar.AlwaysOff
property real _anchorOffset: 0
function captureAnchor() {
const y = contentY + 1;
const idx = indexAt(0, y);
if (idx < 0 || !model || idx >= model.length) return;
_anchorKey = model[idx][keyRoleName];
const it = itemAtIndex(idx);
_anchorOffset = it ? (y - it.y) : 0;
} }
function restoreAnchor() {
Qt.callLater(function() {
if (_anchorKey === undefined || !model) return;
var i = -1;
for (var j = 0; j < model.length; ++j) {
if (model[j][keyRoleName] === _anchorKey) { i = j; break; }
}
if (i < 0) return;
positionViewAtIndex(i, ListView.Beginning);
const maxY = Math.max(0, contentHeight - height);
contentY = Math.max(0, Math.min(maxY, contentY + _anchorOffset - 1));
});
}
onModelChanged: {
if (model && model.length > 0 && !isUserScrolling && stableY > 40) {
// Preserve scroll position when model updates
Qt.callLater(function() {
contentY = stableY
})
}
}
} }
}
}

View File

@@ -1,15 +1,16 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Common import qs.Common
import qs.Services
import qs.Modules.ProcessList import qs.Modules.ProcessList
import qs.Services
ColumnLayout { ColumnLayout {
id: processesTab id: processesTab
property var contextMenu: null
anchors.fill: parent anchors.fill: parent
spacing: Theme.spacingM spacing: Theme.spacingM
property var contextMenu: null
SystemOverview { SystemOverview {
Layout.fillWidth: true Layout.fillWidth: true
@@ -24,4 +25,5 @@ ColumnLayout {
ProcessContextMenu { ProcessContextMenu {
id: localContextMenu id: localContextMenu
} }
}
}

View File

@@ -6,11 +6,9 @@ import qs.Widgets
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Component.onCompleted: { Component.onCompleted: {
SysMonitorService.addRef(); SysMonitorService.addRef();
} }
Component.onDestruction: { Component.onDestruction: {
SysMonitorService.removeRef(); SysMonitorService.removeRef();
} }

View File

@@ -9,11 +9,9 @@ ScrollView {
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
Component.onCompleted: { Component.onCompleted: {
SysMonitorService.addRef(); SysMonitorService.addRef();
} }
Component.onDestruction: { Component.onDestruction: {
SysMonitorService.removeRef(); SysMonitorService.removeRef();
} }
@@ -31,6 +29,7 @@ ScrollView {
Column { Column {
id: systemInfoColumn id: systemInfoColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
@@ -108,6 +107,7 @@ ScrollView {
Column { Column {
id: hardwareColumn id: hardwareColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
@@ -133,6 +133,7 @@ ScrollView {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
StyledText { StyledText {
@@ -169,7 +170,9 @@ ScrollView {
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
} }
Rectangle { Rectangle {
@@ -182,6 +185,7 @@ ScrollView {
Column { Column {
id: memoryColumn id: memoryColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
@@ -207,6 +211,7 @@ ScrollView {
color: Theme.secondary color: Theme.secondary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
StyledText { StyledText {
@@ -234,7 +239,9 @@ ScrollView {
width: parent.width width: parent.width
height: Theme.fontSizeSmall + Theme.spacingXS height: Theme.fontSizeSmall + Theme.spacingXS
} }
} }
} }
} }
@@ -243,7 +250,6 @@ ScrollView {
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: storageColumn.implicitHeight + 2 * Theme.spacingL height: storageColumn.implicitHeight + 2 * Theme.spacingL
@@ -253,6 +259,7 @@ ScrollView {
Column { Column {
id: storageColumn id: storageColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
@@ -281,181 +288,178 @@ ScrollView {
} }
Column { Column {
width: parent.width width: parent.width
spacing: 2 spacing: 2
Row { Row {
width: parent.width width: parent.width
height: 24 height: 24
spacing: Theme.spacingS spacing: Theme.spacingS
StyledText {
text: "Device"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.25
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: "Mount"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.2
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: "Size"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: "Used"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: "Available"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: "Use%"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.1
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: "Device"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.25
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
} }
Repeater { StyledText {
id: diskMountRepeater text: "Mount"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.2
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
model: SysMonitorService.diskMounts StyledText {
text: "Size"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
Rectangle { StyledText {
width: parent.width text: "Used"
height: 24 font.pixelSize: Theme.fontSizeSmall
radius: Theme.cornerRadiusSmall font.family: Prefs.monoFontFamily
color: diskMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04) : "transparent" font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
MouseArea { StyledText {
id: diskMouseArea text: "Available"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
anchors.fill: parent StyledText {
hoverEnabled: true text: "Use%"
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.1
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
Repeater {
id: diskMountRepeater
model: SysMonitorService.diskMounts
Rectangle {
width: parent.width
height: 24
radius: Theme.cornerRadiusSmall
color: diskMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04) : "transparent"
MouseArea {
id: diskMouseArea
anchors.fill: parent
hoverEnabled: true
}
Row {
anchors.fill: parent
spacing: Theme.spacingS
StyledText {
text: modelData.device
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.25
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
} }
Row { StyledText {
anchors.fill: parent text: modelData.mount
spacing: Theme.spacingS font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.2
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText { StyledText {
text: modelData.device text: modelData.size
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily font.family: Prefs.monoFontFamily
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width * 0.25 width: parent.width * 0.15
elide: Text.ElideRight elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.used
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.avail
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.percent
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: {
const percent = parseInt(modelData.percent);
if (percent > 90)
return Theme.error;
if (percent > 75)
return Theme.warning;
return Theme.surfaceText;
} }
width: parent.width * 0.1
StyledText { elide: Text.ElideRight
text: modelData.mount anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Theme.fontSizeSmall verticalAlignment: Text.AlignVCenter
font.family: Prefs.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.2
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.size
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.used
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.avail
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.percent
font.pixelSize: Theme.fontSizeSmall
font.family: Prefs.monoFontFamily
color: {
const percent = parseInt(modelData.percent);
if (percent > 90)
return Theme.error;
if (percent > 75)
return Theme.warning;
return Theme.surfaceText;
}
width: parent.width * 0.1
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
} }
} }
@@ -464,6 +468,8 @@ ScrollView {
} }
}
} }
} }

View File

@@ -109,11 +109,10 @@ ScrollView {
description: "Select system font family" description: "Select system font family"
currentValue: { currentValue: {
// Always show the font name in parentheses for clarity // Always show the font name in parentheses for clarity
if (Prefs.fontFamily === Prefs.defaultFontFamily) { if (Prefs.fontFamily === Prefs.defaultFontFamily)
return "Default (" + Prefs.defaultFontFamily + ")"; return "Default (" + Prefs.defaultFontFamily + ")";
} else { else
return Prefs.fontFamily || "Default (" + Prefs.defaultFontFamily + ")"; return Prefs.fontFamily || "Default (" + Prefs.defaultFontFamily + ")";
}
} }
enableFuzzySearch: true enableFuzzySearch: true
popupWidthOffset: 100 popupWidthOffset: 100
@@ -757,18 +756,17 @@ ScrollView {
ColorAnimation { ColorAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
}
} }
} }
} }
} }
} }
}
// System App Theming Section // System App Theming Section
StyledRect { StyledRect {
@@ -816,9 +814,9 @@ ScrollView {
checked: Colors.gtkThemingEnabled && Prefs.gtkThemingEnabled checked: Colors.gtkThemingEnabled && Prefs.gtkThemingEnabled
onToggled: function(checked) { onToggled: function(checked) {
Prefs.setGtkThemingEnabled(checked); Prefs.setGtkThemingEnabled(checked);
if (checked && Theme.isDynamicTheme) { if (checked && Theme.isDynamicTheme)
Colors.generateGtkThemes(); Colors.generateGtkThemes();
}
} }
} }
@@ -830,9 +828,9 @@ ScrollView {
checked: Colors.qtThemingEnabled && Prefs.qtThemingEnabled checked: Colors.qtThemingEnabled && Prefs.qtThemingEnabled
onToggled: function(checked) { onToggled: function(checked) {
Prefs.setQtThemingEnabled(checked); Prefs.setQtThemingEnabled(checked);
if (checked && Theme.isDynamicTheme) { if (checked && Theme.isDynamicTheme)
Colors.generateQtThemes(); Colors.generateQtThemes();
}
} }
} }

View File

@@ -6,257 +6,324 @@ import qs.Widgets
ScrollView { ScrollView {
id: widgetsTab id: widgetsTab
contentHeight: column.implicitHeight + Theme.spacingXL property var baseWidgetDefinitions: [{
clip: true "id": "launcherButton",
"text": "App Launcher",
property var baseWidgetDefinitions: [ "description": "Quick access to application launcher",
{ "icon": "apps",
id: "launcherButton", "enabled": true
text: "App Launcher", }, {
description: "Quick access to application launcher", "id": "workspaceSwitcher",
icon: "apps", "text": "Workspace Switcher",
enabled: true "description": "Shows current workspace and allows switching",
}, "icon": "view_module",
{ "enabled": true
id: "workspaceSwitcher", }, {
text: "Workspace Switcher", "id": "focusedWindow",
description: "Shows current workspace and allows switching", "text": "Focused Window",
icon: "view_module", "description": "Display currently focused application title",
enabled: true "icon": "window",
}, "enabled": true
{ }, {
id: "focusedWindow", "id": "clock",
text: "Focused Window", "text": "Clock",
description: "Display currently focused application title", "description": "Current time and date display",
icon: "window", "icon": "schedule",
enabled: true "enabled": true
}, }, {
{ "id": "weather",
id: "clock", "text": "Weather Widget",
text: "Clock", "description": "Current weather conditions and temperature",
description: "Current time and date display", "icon": "wb_sunny",
icon: "schedule", "enabled": true
enabled: true }, {
}, "id": "music",
{ "text": "Media Controls",
id: "weather", "description": "Control currently playing media",
text: "Weather Widget", "icon": "music_note",
description: "Current weather conditions and temperature", "enabled": true
icon: "wb_sunny", }, {
enabled: true "id": "clipboard",
}, "text": "Clipboard Manager",
{ "description": "Access clipboard history",
id: "music", "icon": "content_paste",
text: "Media Controls", "enabled": true
description: "Control currently playing media", }, {
icon: "music_note", "id": "systemResources",
enabled: true "text": "System Resources",
}, "description": "CPU and memory usage indicators",
{ "icon": "memory",
id: "clipboard", "enabled": true
text: "Clipboard Manager", }, {
description: "Access clipboard history", "id": "systemTray",
icon: "content_paste", "text": "System Tray",
enabled: true "description": "System notification area icons",
}, "icon": "notifications",
{ "enabled": true
id: "systemResources", }, {
text: "System Resources", "id": "controlCenterButton",
description: "CPU and memory usage indicators", "text": "Control Center",
icon: "memory", "description": "Access to system controls and settings",
enabled: true "icon": "settings",
}, "enabled": true
{ }, {
id: "systemTray", "id": "notificationButton",
text: "System Tray", "text": "Notification Center",
description: "System notification area icons", "description": "Access to notifications and do not disturb",
icon: "notifications", "icon": "notifications",
enabled: true "enabled": true
}, }, {
{ "id": "battery",
id: "controlCenterButton", "text": "Battery",
text: "Control Center", "description": "Battery level and power management",
description: "Access to system controls and settings", "icon": "battery_std",
icon: "settings", "enabled": true
enabled: true }, {
}, "id": "spacer",
{ "text": "Spacer",
id: "notificationButton", "description": "Customizable empty space",
text: "Notification Center", "icon": "more_horiz",
description: "Access to notifications and do not disturb", "enabled": true
icon: "notifications", }, {
enabled: true "id": "separator",
}, "text": "Separator",
{ "description": "Visual divider between widgets",
id: "battery", "icon": "remove",
text: "Battery", "enabled": true
description: "Battery level and power management", }]
icon: "battery_std",
enabled: true
},
{
id: "spacer",
text: "Spacer",
description: "Empty space to separate widgets",
icon: "more_horiz",
enabled: true
},
{
id: "separator",
text: "Separator",
description: "Visual divider between widgets",
icon: "remove",
enabled: true
}
]
// Default widget configurations for each section (with enabled states) // Default widget configurations for each section (with enabled states)
property var defaultLeftWidgets: [ property var defaultLeftWidgets: [{
{id: "launcherButton", enabled: true}, "id": "launcherButton",
{id: "workspaceSwitcher", enabled: true}, "enabled": true
{id: "focusedWindow", enabled: true} }, {
] "id": "workspaceSwitcher",
property var defaultCenterWidgets: [ "enabled": true
{id: "music", enabled: true}, }, {
{id: "clock", enabled: true}, "id": "focusedWindow",
{id: "weather", enabled: true} "enabled": true
] }]
property var defaultRightWidgets: [ property var defaultCenterWidgets: [{
{id: "systemTray", enabled: true}, "id": "music",
{id: "clipboard", enabled: true}, "enabled": true
{id: "systemResources", enabled: true}, }, {
{id: "notificationButton", enabled: true}, "id": "clock",
{id: "battery", enabled: true}, "enabled": true
{id: "controlCenterButton", enabled: true} }, {
] "id": "weather",
"enabled": true
Component.onCompleted: { }]
// Initialize sections with defaults if they're empty property var defaultRightWidgets: [{
if (!Prefs.topBarLeftWidgets || Prefs.topBarLeftWidgets.length === 0) { "id": "systemTray",
Prefs.setTopBarLeftWidgets(defaultLeftWidgets) "enabled": true
} }, {
if (!Prefs.topBarCenterWidgets || Prefs.topBarCenterWidgets.length === 0) { "id": "clipboard",
Prefs.setTopBarCenterWidgets(defaultCenterWidgets) "enabled": true
} }, {
if (!Prefs.topBarRightWidgets || Prefs.topBarRightWidgets.length === 0) { "id": "systemResources",
Prefs.setTopBarRightWidgets(defaultRightWidgets) "enabled": true
} }, {
} "id": "notificationButton",
"enabled": true
}, {
"id": "battery",
"enabled": true
}, {
"id": "controlCenterButton",
"enabled": true
}]
function addWidgetToSection(widgetId, targetSection) { function addWidgetToSection(widgetId, targetSection) {
var leftWidgets = Prefs.topBarLeftWidgets.slice() var widgetObj = {
var centerWidgets = Prefs.topBarCenterWidgets.slice() "id": widgetId,
var rightWidgets = Prefs.topBarRightWidgets.slice() "enabled": true
};
// Create widget object with enabled state if (widgetId === "spacer")
var widgetObj = {id: widgetId, enabled: true} widgetObj.size = 20;
var widgets = [];
if (targetSection === "left") { if (targetSection === "left") {
leftWidgets.push(widgetObj) widgets = Prefs.topBarLeftWidgets.slice();
Prefs.setTopBarLeftWidgets(leftWidgets) widgets.push(widgetObj);
Prefs.setTopBarLeftWidgets(widgets);
} else if (targetSection === "center") { } else if (targetSection === "center") {
centerWidgets.push(widgetObj) widgets = Prefs.topBarCenterWidgets.slice();
Prefs.setTopBarCenterWidgets(centerWidgets) widgets.push(widgetObj);
Prefs.setTopBarCenterWidgets(widgets);
} else if (targetSection === "right") { } else if (targetSection === "right") {
rightWidgets.push(widgetObj) widgets = Prefs.topBarRightWidgets.slice();
Prefs.setTopBarRightWidgets(rightWidgets) widgets.push(widgetObj);
Prefs.setTopBarRightWidgets(widgets);
} }
} }
function removeLastWidgetFromSection(sectionId) { function removeWidgetFromSection(sectionId, itemId) {
var leftWidgets = Prefs.topBarLeftWidgets.slice() var widgets = [];
var centerWidgets = Prefs.topBarCenterWidgets.slice() if (sectionId === "left") {
var rightWidgets = Prefs.topBarRightWidgets.slice() widgets = Prefs.topBarLeftWidgets.slice();
widgets = widgets.filter((widget) => {
if (sectionId === "left" && leftWidgets.length > 0) { var widgetId = typeof widget === "string" ? widget : widget.id;
leftWidgets.pop() return widgetId !== itemId;
Prefs.setTopBarLeftWidgets(leftWidgets) });
} else if (sectionId === "center" && centerWidgets.length > 0) { Prefs.setTopBarLeftWidgets(widgets);
centerWidgets.pop() } else if (sectionId === "center") {
Prefs.setTopBarCenterWidgets(centerWidgets) widgets = Prefs.topBarCenterWidgets.slice();
} else if (sectionId === "right" && rightWidgets.length > 0) { widgets = widgets.filter((widget) => {
rightWidgets.pop() var widgetId = typeof widget === "string" ? widget : widget.id;
Prefs.setTopBarRightWidgets(rightWidgets) return widgetId !== itemId;
});
Prefs.setTopBarCenterWidgets(widgets);
} else if (sectionId === "right") {
widgets = Prefs.topBarRightWidgets.slice();
widgets = widgets.filter((widget) => {
var widgetId = typeof widget === "string" ? widget : widget.id;
return widgetId !== itemId;
});
Prefs.setTopBarRightWidgets(widgets);
} }
} }
function handleItemEnabledChanged(sectionId, itemId, enabled) { function handleItemEnabledChanged(sectionId, itemId, enabled) {
// Update the specific widget instance's enabled state in the section var widgets = [];
var widgets = [] if (sectionId === "left")
widgets = Prefs.topBarLeftWidgets.slice();
if (sectionId === "left") { else if (sectionId === "center")
widgets = Prefs.topBarLeftWidgets.slice() widgets = Prefs.topBarCenterWidgets.slice();
} else if (sectionId === "center") { else if (sectionId === "right")
widgets = Prefs.topBarCenterWidgets.slice() widgets = Prefs.topBarRightWidgets.slice();
} else if (sectionId === "right") {
widgets = Prefs.topBarRightWidgets.slice()
}
// Find and update the specific widget instance
for (var i = 0; i < widgets.length; i++) { for (var i = 0; i < widgets.length; i++) {
// Handle both old string format and new object format for backward compatibility var widget = widgets[i];
var widget = widgets[i] var widgetId = typeof widget === "string" ? widget : widget.id;
var widgetId = typeof widget === "string" ? widget : widget.id
// Update the enabled state for this specific instance
if (widgetId === itemId) { if (widgetId === itemId) {
if (typeof widget === "string") { widgets[i] = typeof widget === "string" ? {
// Convert old string format to object format "id": widget,
widgets[i] = {id: widget, enabled: enabled} "enabled": enabled
} else { } : {
widgets[i] = {id: widget.id, enabled: enabled} "id": widget.id,
} "enabled": enabled,
break "size": widget.size
};
break;
} }
} }
if (sectionId === "left")
// Save the updated widgets array Prefs.setTopBarLeftWidgets(widgets);
if (sectionId === "left") { else if (sectionId === "center")
Prefs.setTopBarLeftWidgets(widgets) Prefs.setTopBarCenterWidgets(widgets);
} else if (sectionId === "center") { else if (sectionId === "right")
Prefs.setTopBarCenterWidgets(widgets) Prefs.setTopBarRightWidgets(widgets);
} else if (sectionId === "right") {
Prefs.setTopBarRightWidgets(widgets)
}
} }
function handleItemOrderChanged(sectionId, newOrder) { function handleItemOrderChanged(sectionId, newOrder) {
if (sectionId === "left") { if (sectionId === "left")
Prefs.setTopBarLeftWidgets(newOrder) Prefs.setTopBarLeftWidgets(newOrder);
} else if (sectionId === "center") { else if (sectionId === "center")
Prefs.setTopBarCenterWidgets(newOrder) Prefs.setTopBarCenterWidgets(newOrder);
} else if (sectionId === "right") { else if (sectionId === "right")
Prefs.setTopBarRightWidgets(newOrder) Prefs.setTopBarRightWidgets(newOrder);
}
function handleSpacerSizeChanged(sectionId, itemId, newSize) {
var widgets = [];
if (sectionId === "left")
widgets = Prefs.topBarLeftWidgets.slice();
else if (sectionId === "center")
widgets = Prefs.topBarCenterWidgets.slice();
else if (sectionId === "right")
widgets = Prefs.topBarRightWidgets.slice();
for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i];
var widgetId = typeof widget === "string" ? widget : widget.id;
if (widgetId === itemId && widgetId === "spacer") {
widgets[i] = typeof widget === "string" ? {
"id": widget,
"enabled": true,
"size": newSize
} : {
"id": widget.id,
"enabled": widget.enabled,
"size": newSize
};
break;
}
} }
if (sectionId === "left")
Prefs.setTopBarLeftWidgets(widgets);
else if (sectionId === "center")
Prefs.setTopBarCenterWidgets(widgets);
else if (sectionId === "right")
Prefs.setTopBarRightWidgets(widgets);
} }
function getItemsForSection(sectionId) { function getItemsForSection(sectionId) {
var widgets = [] var widgets = [];
var widgetData = [] var widgetData = [];
if (sectionId === "left")
if (sectionId === "left") { widgetData = Prefs.topBarLeftWidgets || [];
widgetData = Prefs.topBarLeftWidgets || [] else if (sectionId === "center")
} else if (sectionId === "center") { widgetData = Prefs.topBarCenterWidgets || [];
widgetData = Prefs.topBarCenterWidgets || [] else if (sectionId === "right")
} else if (sectionId === "right") { widgetData = Prefs.topBarRightWidgets || [];
widgetData = Prefs.topBarRightWidgets || [] widgetData.forEach((widget) => {
} var widgetId = typeof widget === "string" ? widget : widget.id;
var widgetEnabled = typeof widget === "string" ? true : widget.enabled;
widgetData.forEach(widget => { var widgetSize = typeof widget === "string" ? undefined : widget.size;
// Handle both old string format and new object format for backward compatibility var widgetDef = baseWidgetDefinitions.find((w) => {
var widgetId = typeof widget === "string" ? widget : widget.id return w.id === widgetId;
var widgetEnabled = typeof widget === "string" ? true : widget.enabled });
var widgetDef = baseWidgetDefinitions.find(w => w.id === widgetId)
if (widgetDef) { if (widgetDef) {
var item = Object.assign({}, widgetDef) var item = Object.assign({
// Use the per-instance enabled state }, widgetDef);
item.enabled = widgetEnabled item.enabled = widgetEnabled;
widgets.push(item) if (widgetSize !== undefined)
item.size = widgetSize;
widgets.push(item);
} }
}) });
return widgets;
return widgets }
contentHeight: column.implicitHeight + Theme.spacingXL
clip: true
Component.onCompleted: {
if (!Prefs.topBarLeftWidgets || Prefs.topBarLeftWidgets.length === 0)
Prefs.setTopBarLeftWidgets(defaultLeftWidgets);
if (!Prefs.topBarCenterWidgets || Prefs.topBarCenterWidgets.length === 0)
Prefs.setTopBarCenterWidgets(defaultCenterWidgets);
if (!Prefs.topBarRightWidgets || Prefs.topBarRightWidgets.length === 0)
Prefs.setTopBarRightWidgets(defaultRightWidgets);
// Ensure existing spacers have default sizes
["left", "center", "right"].forEach((sectionId) => {
var widgets = [];
if (sectionId === "left")
widgets = Prefs.topBarLeftWidgets.slice();
else if (sectionId === "center")
widgets = Prefs.topBarCenterWidgets.slice();
else if (sectionId === "right")
widgets = Prefs.topBarRightWidgets.slice();
var updated = false;
for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i];
if (typeof widget === "object" && widget.id === "spacer" && !widget.size) {
widgets[i] = Object.assign({
}, widget, {
"size": 20
});
updated = true;
}
}
if (updated) {
if (sectionId === "left")
Prefs.setTopBarLeftWidgets(widgets);
else if (sectionId === "center")
Prefs.setTopBarCenterWidgets(widgets);
else if (sectionId === "right")
Prefs.setTopBarRightWidgets(widgets);
}
});
} }
Column { Column {
@@ -300,18 +367,18 @@ ScrollView {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
border.width: 1 border.width: 1
border.color: resetArea.containsMouse ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5) border.color: resetArea.containsMouse ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5)
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankIcon { DankIcon {
name: "refresh" name: "refresh"
size: 14 size: 14
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: "Reset" text: "Reset"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -319,35 +386,40 @@ ScrollView {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: resetArea id: resetArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
// Reset all sections to defaults (with per-instance enabled states) Prefs.setTopBarLeftWidgets(defaultLeftWidgets);
Prefs.setTopBarLeftWidgets(defaultLeftWidgets) Prefs.setTopBarCenterWidgets(defaultCenterWidgets);
Prefs.setTopBarCenterWidgets(defaultCenterWidgets) Prefs.setTopBarRightWidgets(defaultRightWidgets);
Prefs.setTopBarRightWidgets(defaultRightWidgets)
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on border.color { Behavior on border.color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
Rectangle { Rectangle {
@@ -358,18 +430,20 @@ ScrollView {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
visible: true visible: true
opacity: 1.0 opacity: 1
z: 1 z: 1
StyledText { StyledText {
id: messageText id: messageText
anchors.centerIn: parent anchors.centerIn: parent
text: "Drag widgets to reorder within sections. Use + to add widgets and - to remove the last widget from each section." text: "Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely."
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.outline color: Theme.outline
width: parent.width - Theme.spacingM * 2 width: parent.width - Theme.spacingM * 2
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
} }
// Widget sections // Widget sections
@@ -385,23 +459,22 @@ ScrollView {
sectionId: "left" sectionId: "left"
allWidgets: widgetsTab.baseWidgetDefinitions allWidgets: widgetsTab.baseWidgetDefinitions
items: widgetsTab.getItemsForSection("left") items: widgetsTab.getItemsForSection("left")
onItemEnabledChanged: (sectionId, itemId, enabled) => { onItemEnabledChanged: (sectionId, itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(sectionId, itemId, enabled) widgetsTab.handleItemEnabledChanged(sectionId, itemId, enabled);
} }
onItemOrderChanged: (newOrder) => { onItemOrderChanged: (newOrder) => {
widgetsTab.handleItemOrderChanged("left", newOrder) widgetsTab.handleItemOrderChanged("left", newOrder);
} }
onAddWidget: (sectionId) => { onAddWidget: (sectionId) => {
widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions;
widgetSelectionPopup.targetSection = sectionId widgetSelectionPopup.targetSection = sectionId;
widgetSelectionPopup.safeOpen() widgetSelectionPopup.safeOpen();
} }
onRemoveWidget: (sectionId, itemId) => {
onRemoveLastWidget: (sectionId) => { widgetsTab.removeWidgetFromSection(sectionId, itemId);
widgetsTab.removeLastWidgetFromSection(sectionId) }
onSpacerSizeChanged: (sectionId, itemId, newSize) => {
widgetsTab.handleSpacerSizeChanged(sectionId, itemId, newSize);
} }
} }
@@ -413,23 +486,22 @@ ScrollView {
sectionId: "center" sectionId: "center"
allWidgets: widgetsTab.baseWidgetDefinitions allWidgets: widgetsTab.baseWidgetDefinitions
items: widgetsTab.getItemsForSection("center") items: widgetsTab.getItemsForSection("center")
onItemEnabledChanged: (sectionId, itemId, enabled) => { onItemEnabledChanged: (sectionId, itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(sectionId, itemId, enabled) widgetsTab.handleItemEnabledChanged(sectionId, itemId, enabled);
} }
onItemOrderChanged: (newOrder) => { onItemOrderChanged: (newOrder) => {
widgetsTab.handleItemOrderChanged("center", newOrder) widgetsTab.handleItemOrderChanged("center", newOrder);
} }
onAddWidget: (sectionId) => { onAddWidget: (sectionId) => {
widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions;
widgetSelectionPopup.targetSection = sectionId widgetSelectionPopup.targetSection = sectionId;
widgetSelectionPopup.safeOpen() widgetSelectionPopup.safeOpen();
} }
onRemoveWidget: (sectionId, itemId) => {
onRemoveLastWidget: (sectionId) => { widgetsTab.removeWidgetFromSection(sectionId, itemId);
widgetsTab.removeLastWidgetFromSection(sectionId) }
onSpacerSizeChanged: (sectionId, itemId, newSize) => {
widgetsTab.handleSpacerSizeChanged(sectionId, itemId, newSize);
} }
} }
@@ -441,25 +513,25 @@ ScrollView {
sectionId: "right" sectionId: "right"
allWidgets: widgetsTab.baseWidgetDefinitions allWidgets: widgetsTab.baseWidgetDefinitions
items: widgetsTab.getItemsForSection("right") items: widgetsTab.getItemsForSection("right")
onItemEnabledChanged: (sectionId, itemId, enabled) => { onItemEnabledChanged: (sectionId, itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(sectionId, itemId, enabled) widgetsTab.handleItemEnabledChanged(sectionId, itemId, enabled);
} }
onItemOrderChanged: (newOrder) => { onItemOrderChanged: (newOrder) => {
widgetsTab.handleItemOrderChanged("right", newOrder) widgetsTab.handleItemOrderChanged("right", newOrder);
} }
onAddWidget: (sectionId) => { onAddWidget: (sectionId) => {
widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions widgetSelectionPopup.allWidgets = widgetsTab.baseWidgetDefinitions;
widgetSelectionPopup.targetSection = sectionId widgetSelectionPopup.targetSection = sectionId;
widgetSelectionPopup.safeOpen() widgetSelectionPopup.safeOpen();
} }
onRemoveWidget: (sectionId, itemId) => {
onRemoveLastWidget: (sectionId) => { widgetsTab.removeWidgetFromSection(sectionId, itemId);
widgetsTab.removeLastWidgetFromSection(sectionId) }
onSpacerSizeChanged: (sectionId, itemId, newSize) => {
widgetsTab.handleSpacerSizeChanged(sectionId, itemId, newSize);
} }
} }
} }
// Workspace Section // Workspace Section
@@ -496,6 +568,7 @@ ScrollView {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
DankToggle { DankToggle {
@@ -517,8 +590,11 @@ ScrollView {
return Prefs.setShowWorkspacePadding(checked); return Prefs.setShowWorkspacePadding(checked);
} }
} }
} }
} }
} }
// Tooltip for reset button (positioned above the button) // Tooltip for reset button (positioned above the button)
@@ -534,30 +610,34 @@ ScrollView {
y: column.y + 48 // Position above the reset button in the header y: column.y + 48 // Position above the reset button in the header
x: parent.width - width - Theme.spacingM x: parent.width - width - Theme.spacingM
z: 100 z: 100
StyledText { StyledText {
id: tooltipText id: tooltipText
anchors.centerIn: parent anchors.centerIn: parent
text: "Reset widget layout to defaults" text: "Reset widget layout to defaults"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
// Widget selection popup // Widget selection popup
DankWidgetSelectionPopup { DankWidgetSelectionPopup {
id: widgetSelectionPopup id: widgetSelectionPopup
anchors.centerIn: parent
anchors.centerIn: parent
onWidgetSelected: (widgetId, targetSection) => { onWidgetSelected: (widgetId, targetSection) => {
widgetsTab.addWidgetToSection(widgetId, targetSection) widgetsTab.addWidgetToSection(widgetId, targetSection);
} }
} }
}
}

View File

@@ -9,10 +9,10 @@ import qs.Widgets
PanelWindow { PanelWindow {
id: root id: root
property var modelData property var modelData
screen: modelData screen: modelData
visible: ToastService.toastVisible visible: ToastService.toastVisible
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1

View File

@@ -25,14 +25,7 @@ Item {
repeat: true repeat: true
onTriggered: { onTriggered: {
// Generate fake audio levels when cava is unavailable // Generate fake audio levels when cava is unavailable
CavaService.values = [ CavaService.values = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25];
Math.random() * 40 + 10,
Math.random() * 60 + 20,
Math.random() * 50 + 15,
Math.random() * 35 + 20,
Math.random() * 45 + 15,
Math.random() * 55 + 25
];
} }
} }

View File

@@ -5,11 +5,10 @@ import qs.Common
Rectangle { Rectangle {
id: root id: root
readonly property int calculatedWidth: SystemTray.items.values.length > 0 ? SystemTray.items.values.length * 24 + (SystemTray.items.values.length - 1) * Theme.spacingXS + Theme.spacingS * 2 : 0
signal menuRequested(var menu, var item, real x, real y) signal menuRequested(var menu, var item, real x, real y)
readonly property int calculatedWidth: SystemTray.items.values.length > 0 ?
SystemTray.items.values.length * 24 + (SystemTray.items.values.length - 1) * Theme.spacingXS + Theme.spacingS * 2 : 0
width: calculatedWidth width: calculatedWidth
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius

View File

@@ -30,7 +30,6 @@ PanelWindow {
ToastService.showError("Please install Material Symbols Rounded and Restart your Shell. See README.md for instructions"); ToastService.showError("Please install Material Symbols Rounded and Restart your Shell. See README.md for instructions");
Prefs.forceTopBarLayoutRefresh.connect(function() { Prefs.forceTopBarLayoutRefresh.connect(function() {
console.log("TopBar: Forcing layout refresh");
Qt.callLater(() => { Qt.callLater(() => {
leftSection.visible = false; leftSection.visible = false;
centerSection.visible = false; centerSection.visible = false;
@@ -39,14 +38,12 @@ PanelWindow {
leftSection.visible = true; leftSection.visible = true;
centerSection.visible = true; centerSection.visible = true;
rightSection.visible = true; rightSection.visible = true;
console.log("TopBar: Layout refresh completed");
}); });
}); });
}); });
} }
Connections { Connections {
function onTopBarTransparencyChanged() { function onTopBarTransparencyChanged() {
root.backgroundTransparency = Prefs.topBarTransparency; root.backgroundTransparency = Prefs.topBarTransparency;
} }
@@ -180,6 +177,10 @@ PanelWindow {
return true; return true;
case "controlCenterButton": case "controlCenterButton":
return true; return true;
case "spacer":
return true;
case "separator":
return true;
default: default:
return false; return false;
} }
@@ -241,10 +242,13 @@ PanelWindow {
Loader { Loader {
property string widgetId: model.widgetId property string widgetId: model.widgetId
property var widgetData: model
property int spacerSize: model.size || 20
anchors.verticalCenter: parent ? parent.verticalCenter : undefined anchors.verticalCenter: parent ? parent.verticalCenter : undefined
active: topBarContent.getWidgetEnabled(model.enabled) && topBarContent.getWidgetVisible(model.widgetId) active: topBarContent.getWidgetVisible(model.widgetId)
sourceComponent: topBarContent.getWidgetComponent(model.widgetId) sourceComponent: topBarContent.getWidgetComponent(model.widgetId)
opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0
} }
} }
@@ -263,26 +267,14 @@ PanelWindow {
centerWidgets = []; centerWidgets = [];
totalWidgets = 0; totalWidgets = 0;
totalWidth = 0; totalWidth = 0;
let allItemsReady = true;
for (let i = 0; i < centerRepeater.count; i++) { for (let i = 0; i < centerRepeater.count; i++) {
let item = centerRepeater.itemAt(i); let item = centerRepeater.itemAt(i);
if (item && item.active && item.item) { if (item && item.active && item.item) {
if (item.item.width <= 0) {
allItemsReady = false;
break;
}
centerWidgets.push(item.item); centerWidgets.push(item.item);
totalWidgets++; totalWidgets++;
totalWidth += item.item.width; totalWidth += item.item.width;
} }
} }
if (!allItemsReady) {
Qt.callLater(updateLayout);
return;
}
if (totalWidgets > 1) if (totalWidgets > 1)
totalWidth += spacing * (totalWidgets - 1); totalWidth += spacing * (totalWidgets - 1);
@@ -341,7 +333,6 @@ PanelWindow {
width: parent.width width: parent.width
anchors.centerIn: parent anchors.centerIn: parent
Component.onCompleted: { Component.onCompleted: {
console.log("Center widgets model count:", Prefs.topBarCenterWidgetsModel.count);
Qt.callLater(() => { Qt.callLater(() => {
Qt.callLater(updateLayout); Qt.callLater(updateLayout);
}); });
@@ -354,13 +345,21 @@ PanelWindow {
Loader { Loader {
property string widgetId: model.widgetId property string widgetId: model.widgetId
property var widgetData: model
property int spacerSize: model.size || 20
anchors.verticalCenter: parent ? parent.verticalCenter : undefined anchors.verticalCenter: parent ? parent.verticalCenter : undefined
active: topBarContent.getWidgetEnabled(model.enabled) && topBarContent.getWidgetVisible(model.widgetId) active: topBarContent.getWidgetVisible(model.widgetId)
sourceComponent: topBarContent.getWidgetComponent(model.widgetId) sourceComponent: topBarContent.getWidgetComponent(model.widgetId)
opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0
onLoaded: { onLoaded: {
if (item) { if (item) {
item.onWidthChanged.connect(centerSection.updateLayout); item.onWidthChanged.connect(centerSection.updateLayout);
if (model.widgetId === "spacer")
item.spacerSize = Qt.binding(() => {
return model.size || 20;
});
Qt.callLater(centerSection.updateLayout); Qt.callLater(centerSection.updateLayout);
} }
} }
@@ -388,19 +387,19 @@ PanelWindow {
spacing: Theme.spacingXS spacing: Theme.spacingXS
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Component.onCompleted: {
console.log("Right widgets model count:", Prefs.topBarRightWidgetsModel.count);
}
Repeater { Repeater {
model: Prefs.topBarRightWidgetsModel model: Prefs.topBarRightWidgetsModel
Loader { Loader {
property string widgetId: model.widgetId property string widgetId: model.widgetId
property var widgetData: model
property int spacerSize: model.size || 20
anchors.verticalCenter: parent ? parent.verticalCenter : undefined anchors.verticalCenter: parent ? parent.verticalCenter : undefined
active: topBarContent.getWidgetEnabled(model.enabled) && topBarContent.getWidgetVisible(model.widgetId) active: topBarContent.getWidgetVisible(model.widgetId)
sourceComponent: topBarContent.getWidgetComponent(model.widgetId) sourceComponent: topBarContent.getWidgetComponent(model.widgetId)
opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0
} }
} }
@@ -601,8 +600,26 @@ PanelWindow {
id: spacerComponent id: spacerComponent
Item { Item {
width: 20 width: parent.spacerSize || 20
height: 30 height: 30
Rectangle {
anchors.fill: parent
color: "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
border.width: 1
radius: 2
visible: false
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: parent.visible = true
onExited: parent.visible = false
}
}
} }
} }

View File

@@ -10,7 +10,7 @@ Singleton {
id: root id: root
property int refCount: 0 property int refCount: 0
property int updateInterval: 30000 property int updateInterval: refCount > 0 ? 2000 : 30000
property int maxProcesses: 100 property int maxProcesses: 100
property bool isUpdating: false property bool isUpdating: false

View File

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

View File

@@ -5,6 +5,8 @@ import qs.Common
import qs.Widgets import qs.Widgets
Rectangle { Rectangle {
// Force recreate popup when component becomes visible
id: root id: root
property string text: "" property string text: ""
@@ -38,7 +40,6 @@ Rectangle {
if (!visible && popup && popup.visible) if (!visible && popup && popup.visible)
popup.close(); popup.close();
else if (visible) else if (visible)
// Force recreate popup when component becomes visible
forceRecreateTimer.start(); forceRecreateTimer.start();
} }

View File

@@ -15,7 +15,8 @@ Column {
signal itemEnabledChanged(string sectionId, string itemId, bool enabled) signal itemEnabledChanged(string sectionId, string itemId, bool enabled)
signal itemOrderChanged(var newOrder) signal itemOrderChanged(var newOrder)
signal addWidget(string sectionId) signal addWidget(string sectionId)
signal removeLastWidget(string sectionId) signal removeWidget(string sectionId, string itemId)
signal spacerSizeChanged(string sectionId, string itemId, int newSize)
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -44,30 +45,32 @@ Column {
width: parent.width - 60 width: parent.width - 60
height: 1 height: 1
} }
} }
// Widget Items // Widget Items
Column { Column {
id: itemsList id: itemsList
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: root.items model: root.items
delegate: Item { delegate: Item {
id: delegateItem id: delegateItem
property bool held: dragArea.pressed
property real originalY: y
width: itemsList.width width: itemsList.width
height: 70 height: 70
property int visualIndex: index
property bool held: dragArea.pressed
property string itemId: modelData.id
z: held ? 2 : 1 z: held ? 2 : 1
Rectangle { Rectangle {
id: itemBackground id: itemBackground
anchors.fill: parent anchors.fill: parent
anchors.margins: 2 anchors.margins: 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -75,39 +78,29 @@ Column {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
// Drag handle DankIcon {
Rectangle { name: "drag_indicator"
width: 40 size: Theme.iconSize - 4
height: parent.height color: Theme.outline
color: "transparent"
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM + 8
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
opacity: 0.8
DankIcon {
name: "drag_indicator"
size: Theme.iconSize - 4
color: Theme.outline
anchors.centerIn: parent
opacity: 0.8
}
} }
// Widget icon
DankIcon { DankIcon {
name: modelData.icon name: modelData.icon
size: Theme.iconSize size: Theme.iconSize
color: modelData.enabled ? Theme.primary : Theme.outline color: modelData.enabled ? Theme.primary : Theme.outline
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM + 40 + Theme.spacingM anchors.leftMargin: Theme.spacingM * 2 + 40
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Widget info
Column { Column {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM + 40 + Theme.spacingM + Theme.iconSize + Theme.spacingM anchors.leftMargin: Theme.spacingM * 3 + 40 + Theme.iconSize
anchors.right: toggle.left anchors.right: actionButtons.left
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: 2 spacing: 2
@@ -129,135 +122,174 @@ Column {
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
} }
// Toggle - positioned at right edge Row {
DankToggle { id: actionButtons
id: toggle
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 48 spacing: Theme.spacingXS
height: 24
hideText: true DankActionButton {
checked: modelData.enabled visible: modelData.id !== "spacer"
onToggled: (checked) => { buttonSize: 32
root.itemEnabledChanged(root.sectionId, modelData.id, checked) iconName: modelData.enabled ? "visibility" : "visibility_off"
iconSize: 18
iconColor: modelData.enabled ? Theme.primary : Theme.outline
onClicked: {
root.itemEnabledChanged(root.sectionId, modelData.id, !modelData.enabled);
}
} }
Row {
visible: modelData.id === "spacer"
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
DankActionButton {
buttonSize: 24
iconName: "remove"
iconSize: 14
iconColor: Theme.outline
onClicked: {
var currentSize = modelData.size || 20;
var newSize = Math.max(5, currentSize - 5);
root.spacerSizeChanged(root.sectionId, modelData.id, newSize);
}
}
StyledText {
text: (modelData.size || 20).toString()
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankActionButton {
buttonSize: 24
iconName: "add"
iconSize: 14
iconColor: Theme.outline
onClicked: {
var currentSize = modelData.size || 20;
var newSize = Math.min(5000, currentSize + 5);
root.spacerSizeChanged(root.sectionId, modelData.id, newSize);
}
}
}
DankActionButton {
buttonSize: 32
iconName: "close"
iconSize: 18
iconColor: Theme.error
onClicked: {
root.removeWidget(root.sectionId, modelData.id);
}
}
} }
// Drag functionality
MouseArea { MouseArea {
id: dragArea id: dragArea
anchors.fill: parent
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 60
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.SizeVerCursor
property bool validDragStart: false drag.target: held ? delegateItem : undefined
drag.target: held && validDragStart ? delegateItem : undefined
drag.axis: Drag.YAxis drag.axis: Drag.YAxis
drag.minimumY: -delegateItem.height drag.minimumY: -delegateItem.height
drag.maximumY: itemsList.height drag.maximumY: itemsList.height
onPressed: {
onPressed: (mouse) => { delegateItem.z = 2;
// Only allow dragging from the drag handle area (first 60px) delegateItem.originalY = delegateItem.y;
if (mouse.x <= 60) {
validDragStart = true
delegateItem.z = 2
} else {
validDragStart = false
mouse.accepted = false
}
} }
onReleased: { onReleased: {
delegateItem.z = 1 delegateItem.z = 1;
if (drag.active) {
if (drag.active && validDragStart) { var newIndex = Math.round(delegateItem.y / (delegateItem.height + itemsList.spacing));
// Calculate new index based on position newIndex = Math.max(0, Math.min(newIndex, root.items.length - 1));
var newIndex = Math.round(delegateItem.y / (delegateItem.height + itemsList.spacing))
newIndex = Math.max(0, Math.min(newIndex, root.items.length - 1))
if (newIndex !== index) { if (newIndex !== index) {
var newItems = root.items.slice() var newItems = root.items.slice();
var draggedItem = newItems.splice(index, 1)[0] var draggedItem = newItems.splice(index, 1)[0];
newItems.splice(newIndex, 0, draggedItem) newItems.splice(newIndex, 0, draggedItem);
root.itemOrderChanged(newItems.map((item) => {
root.itemOrderChanged(newItems.map(item => ({id: item.id, enabled: item.enabled}))) return ({
"id": item.id,
"enabled": item.enabled,
"size": item.size
});
}));
} }
} }
delegateItem.x = 0;
// Reset position delegateItem.y = delegateItem.originalY;
delegateItem.x = 0
delegateItem.y = 0
validDragStart = false
} }
} }
// Animations for drag
Behavior on y { Behavior on y {
enabled: !dragArea.held && !dragArea.drag.active enabled: !dragArea.held && !dragArea.drag.active
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
} }
// Add/Remove Controls // Add Widget Control
Rectangle { Rectangle {
width: parent.width * 0.5 width: 200
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: addButtonArea.containsMouse ? Theme.primaryContainer : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Row { StyledText {
text: "Add Widget"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingL }
StyledText { MouseArea {
text: "Add or remove widgets" id: addButtonArea
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
anchors.verticalCenter: parent.verticalCenter
}
Row { anchors.fill: parent
spacing: Theme.spacingS hoverEnabled: true
anchors.verticalCenter: parent.verticalCenter cursorShape: Qt.PointingHandCursor
onClicked: {
// Add button root.addWidget(root.sectionId);
DankActionButton {
iconName: "add"
iconSize: Theme.iconSize - 4
iconColor: Theme.primary
hoverColor: Theme.primaryContainer
onClicked: {
root.addWidget(root.sectionId);
}
}
// Remove button
DankActionButton {
iconName: "remove"
iconSize: Theme.iconSize - 4
iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.1)
enabled: root.items.length > 0
opacity: root.items.length > 0 ? 1.0 : 0.5
onClicked: {
if (root.items.length > 0) {
root.removeLastWidget(root.sectionId);
}
}
}
} }
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
} }

View File

@@ -44,30 +44,33 @@ Column {
width: parent.width - 60 width: parent.width - 60
height: 1 height: 1
} }
} }
// Widget Items // Widget Items
Column { Column {
id: itemsList id: itemsList
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: root.items model: root.items
delegate: Item { delegate: Item {
id: delegateItem id: delegateItem
width: itemsList.width
height: 70
property int visualIndex: index property int visualIndex: index
property bool held: dragArea.pressed property bool held: dragArea.pressed
property string itemId: modelData.id property string itemId: modelData.id
width: itemsList.width
height: 70
z: held ? 2 : 1 z: held ? 2 : 1
Rectangle { Rectangle {
id: itemBackground id: itemBackground
anchors.fill: parent anchors.fill: parent
anchors.margins: 2 anchors.margins: 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -86,7 +89,7 @@ Column {
height: parent.height height: parent.height
color: "transparent" color: "transparent"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
DankIcon { DankIcon {
name: "drag_indicator" name: "drag_indicator"
size: Theme.iconSize - 4 size: Theme.iconSize - 4
@@ -94,6 +97,7 @@ Column {
anchors.centerIn: parent anchors.centerIn: parent
opacity: 0.8 opacity: 0.8
} }
} }
// Widget icon // Widget icon
@@ -127,6 +131,7 @@ Column {
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
} }
// Spacer to push toggle to right // Spacer to push toggle to right
@@ -143,70 +148,73 @@ Column {
hideText: true hideText: true
checked: modelData.enabled checked: modelData.enabled
onToggled: (checked) => { onToggled: (checked) => {
root.itemEnabledChanged(modelData.id, checked) root.itemEnabledChanged(modelData.id, checked);
} }
} }
} }
// Drag functionality // Drag functionality
MouseArea { MouseArea {
id: dragArea id: dragArea
property bool validDragStart: false
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
property bool validDragStart: false
drag.target: held && validDragStart ? delegateItem : undefined drag.target: held && validDragStart ? delegateItem : undefined
drag.axis: Drag.YAxis drag.axis: Drag.YAxis
drag.minimumY: -delegateItem.height drag.minimumY: -delegateItem.height
drag.maximumY: itemsList.height drag.maximumY: itemsList.height
onPressed: (mouse) => { onPressed: (mouse) => {
// Only allow dragging from the drag handle area (first 60px) // Only allow dragging from the drag handle area (first 60px)
if (mouse.x <= 60) { if (mouse.x <= 60) {
validDragStart = true validDragStart = true;
delegateItem.z = 2 delegateItem.z = 2;
} else { } else {
validDragStart = false validDragStart = false;
mouse.accepted = false mouse.accepted = false;
} }
} }
onReleased: { onReleased: {
delegateItem.z = 1 delegateItem.z = 1;
if (drag.active && validDragStart) { if (drag.active && validDragStart) {
// Calculate new index based on position // Calculate new index based on position
var newIndex = Math.round(delegateItem.y / (delegateItem.height + itemsList.spacing)) var newIndex = Math.round(delegateItem.y / (delegateItem.height + itemsList.spacing));
newIndex = Math.max(0, Math.min(newIndex, root.items.length - 1)) newIndex = Math.max(0, Math.min(newIndex, root.items.length - 1));
if (newIndex !== index) { if (newIndex !== index) {
var newItems = root.items.slice() var newItems = root.items.slice();
var draggedItem = newItems.splice(index, 1)[0] var draggedItem = newItems.splice(index, 1)[0];
newItems.splice(newIndex, 0, draggedItem) newItems.splice(newIndex, 0, draggedItem);
root.itemOrderChanged(newItems.map((item) => {
root.itemOrderChanged(newItems.map(item => item.id)) return item.id;
}));
} }
} }
// Reset position // Reset position
delegateItem.x = 0 delegateItem.x = 0;
delegateItem.y = 0 delegateItem.y = 0;
validDragStart = false validDragStart = false;
} }
} }
// Animations for drag // Animations for drag
Behavior on y { Behavior on y {
enabled: !dragArea.held && !dragArea.drag.active enabled: !dragArea.held && !dragArea.drag.active
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
} }
// Add/Remove Controls // Add/Remove Controls
@@ -252,14 +260,18 @@ Column {
iconColor: Theme.error iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.1) hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.1)
enabled: root.items.length > 0 enabled: root.items.length > 0
opacity: root.items.length > 0 ? 1.0 : 0.5 opacity: root.items.length > 0 ? 1 : 0.5
onClicked: { onClicked: {
if (root.items.length > 0) { if (root.items.length > 0)
root.removeLastWidget(root.sectionId); root.removeLastWidget(root.sectionId);
}
} }
} }
} }
} }
} }
} }

View File

@@ -12,30 +12,28 @@ Popup {
signal widgetSelected(string widgetId, string targetSection) signal widgetSelected(string widgetId, string targetSection)
// Prevent multiple openings
function safeOpen() {
if (!isOpening && !visible) {
isOpening = true;
open();
}
}
width: 400 width: 400
height: 450 height: 450
modal: true modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
// Prevent multiple openings
function safeOpen() {
if (!isOpening && !visible) {
isOpening = true
open()
}
}
onOpened: { onOpened: {
isOpening = false isOpening = false;
} }
onClosed: { onClosed: {
isOpening = false isOpening = false;
// Clear references to prevent memory leaks // Clear references to prevent memory leaks
allWidgets = [] allWidgets = [];
targetSection = "" targetSection = "";
} }
background: Rectangle { background: Rectangle {
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
border.color: Theme.primarySelected border.color: Theme.primarySelected
@@ -61,6 +59,7 @@ Popup {
Column { Column {
id: contentColumn id: contentColumn
spacing: Theme.spacingM spacing: Theme.spacingM
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
@@ -85,15 +84,16 @@ Popup {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
StyledText { StyledText {
text: "Select a widget to add to the " + root.targetSection.toLowerCase() + " section of the top bar. You can add multiple instances of the same widget if needed." text: "Select a widget to add to the " + root.targetSection.toLowerCase() + " section of the top bar. You can add multiple instances of the same widget if needed."
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.outline color: Theme.outline
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
// Widget List // Widget List
ScrollView { ScrollView {
@@ -103,6 +103,7 @@ Popup {
ListView { ListView {
id: widgetList id: widgetList
spacing: Theme.spacingS spacing: Theme.spacingS
model: root.allWidgets model: root.allWidgets
@@ -150,6 +151,7 @@ Popup {
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
} }
// Add icon // Add icon
@@ -159,17 +161,18 @@ Popup {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
MouseArea { MouseArea {
id: widgetArea id: widgetArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.widgetSelected(modelData.id, root.targetSection) root.widgetSelected(modelData.id, root.targetSection);
root.close() root.close();
} }
} }
@@ -178,10 +181,17 @@ Popup {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
} }
} }
} }
} }
}
}

View File

@@ -10,13 +10,14 @@ Text {
color: Theme.surfaceText color: Theme.surfaceText
font.pixelSize: Appearance.fontSize.normal font.pixelSize: Appearance.fontSize.normal
font.family: { font.family: {
// Use system default
var requestedFont = isMonospace ? Prefs.monoFontFamily : Prefs.fontFamily; var requestedFont = isMonospace ? Prefs.monoFontFamily : Prefs.fontFamily;
var defaultFont = isMonospace ? Prefs.defaultMonoFontFamily : Prefs.defaultFontFamily; var defaultFont = isMonospace ? Prefs.defaultMonoFontFamily : Prefs.defaultFontFamily;
// If user hasn't overridden the font and we're using the default // If user hasn't overridden the font and we're using the default
if (requestedFont === defaultFont) { if (requestedFont === defaultFont) {
var availableFonts = Qt.fontFamilies(); var availableFonts = Qt.fontFamilies();
if (!availableFonts.includes(requestedFont)) if (!availableFonts.includes(requestedFont))
// Use system default
return isMonospace ? "Monospace" : "DejaVu Sans"; return isMonospace ? "Monospace" : "DejaVu Sans";
} }

View File

@@ -4,30 +4,32 @@ import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Widgets import Quickshell.Widgets
import qs.Modals
import qs.Modules import qs.Modules
import qs.Modules.TopBar
import qs.Modules.AppDrawer import qs.Modules.AppDrawer
import qs.Modules.CentcomCenter import qs.Modules.CentcomCenter
import qs.Modules.ControlCenter import qs.Modules.ControlCenter
import qs.Modules.Settings
import qs.Modules.ProcessList
import qs.Modules.ControlCenter.Network import qs.Modules.ControlCenter.Network
import qs.Modules.Lock import qs.Modules.Lock
import qs.Modules.Notifications.Center import qs.Modules.Notifications.Center
import qs.Modules.Notifications.Popup import qs.Modules.Notifications.Popup
import qs.Modals import qs.Modules.ProcessList
import qs.Modules.Settings
import qs.Modules.TopBar
import qs.Services import qs.Services
ShellRoot { ShellRoot {
id: root id: root
WallpaperBackground {} WallpaperBackground {
}
Lock { Lock {
id: lock id: lock
anchors.fill: parent anchors.fill: parent
} }
// Multi-monitor support using Variants // Multi-monitor support using Variants
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
@@ -35,18 +37,17 @@ ShellRoot {
delegate: TopBar { delegate: TopBar {
modelData: item modelData: item
} }
} }
CentcomPopout { CentcomPopout {
id: centcomPopout id: centcomPopout
} }
SystemTrayContextMenu { SystemTrayContextMenu {
id: systemTrayContextMenu id: systemTrayContextMenu
} }
NotificationCenterPopout { NotificationCenterPopout {
id: notificationCenter id: notificationCenter
} }
@@ -57,10 +58,12 @@ ShellRoot {
delegate: NotificationPopupManager { delegate: NotificationPopupManager {
modelData: item modelData: item
} }
} }
ControlCenterPopout { ControlCenterPopout {
id: controlCenterPopout id: controlCenterPopout
onPowerActionRequested: (action, title, message) => { onPowerActionRequested: (action, title, message) => {
powerConfirmModal.powerConfirmAction = action; powerConfirmModal.powerConfirmAction = action;
powerConfirmModal.powerConfirmTitle = title; powerConfirmModal.powerConfirmTitle = title;
@@ -115,33 +118,36 @@ ShellRoot {
LazyLoader { LazyLoader {
id: processListModalLoader id: processListModalLoader
active: false active: false
ProcessListModal { ProcessListModal {
id: processListModal id: processListModal
} }
} }
IpcHandler { IpcHandler {
function open() { function open() {
processListModalLoader.active = true; processListModalLoader.active = true;
if (processListModalLoader.item) { if (processListModalLoader.item)
processListModalLoader.item.show(); processListModalLoader.item.show();
}
return "PROCESSLIST_OPEN_SUCCESS"; return "PROCESSLIST_OPEN_SUCCESS";
} }
function close() { function close() {
if (processListModalLoader.item) { if (processListModalLoader.item)
processListModalLoader.item.hide(); processListModalLoader.item.hide();
}
return "PROCESSLIST_CLOSE_SUCCESS"; return "PROCESSLIST_CLOSE_SUCCESS";
} }
function toggle() { function toggle() {
processListModalLoader.active = true; processListModalLoader.active = true;
if (processListModalLoader.item) { if (processListModalLoader.item)
processListModalLoader.item.toggle(); processListModalLoader.item.toggle();
}
return "PROCESSLIST_TOGGLE_SUCCESS"; return "PROCESSLIST_TOGGLE_SUCCESS";
} }
@@ -154,6 +160,7 @@ ShellRoot {
delegate: Toast { delegate: Toast {
modelData: item modelData: item
} }
} }
Variants { Variants {
@@ -162,5 +169,7 @@ ShellRoot {
delegate: VolumePopup { delegate: VolumePopup {
modelData: item modelData: item
} }
} }
} }