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:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
41
shell.qml
41
shell.qml
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user